dsh-rewind-plugin 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +14 -10
- package/README.md +10 -7
- package/assets/screenshots/cleanup-setting.png +0 -0
- package/docs/compat/audit.md +5 -0
- package/docs/format.md +11 -0
- package/docs/snapshot-auto-cleanup.md +4 -12
- package/docs/snapshot-auto-cleanup.zh.md +4 -8
- package/lib/client.js +513 -2
- package/lib/index.js +70 -32
- package/lib/types/client/locales.d.ts +34 -0
- package/lib/types/client/portals.d.ts +7 -6
- package/lib/types/client/settings-card.d.ts +76 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/locales.d.ts +0 -2
- package/lib/types/snapshot-cleanup.d.ts +67 -7
- package/package.json +12 -2
package/lib/client.js
CHANGED
|
@@ -319,6 +319,249 @@ var STYLE = `
|
|
|
319
319
|
color: var(--dsw-alias-label-primary);
|
|
320
320
|
pointer-events: none;
|
|
321
321
|
}
|
|
322
|
+
|
|
323
|
+
/* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */
|
|
324
|
+
.dsh-rewind-cleanup-card {
|
|
325
|
+
list-style: none;
|
|
326
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
327
|
+
border-radius: 12px;
|
|
328
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
329
|
+
transition: border-color .16s, background .16s;
|
|
330
|
+
}
|
|
331
|
+
.dsh-rewind-cleanup-card:hover {
|
|
332
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
333
|
+
}
|
|
334
|
+
.dsh-rewind-cleanup-card-open {
|
|
335
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
336
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
337
|
+
}
|
|
338
|
+
.dsh-rewind-cleanup-header {
|
|
339
|
+
width: 100%;
|
|
340
|
+
appearance: none;
|
|
341
|
+
border: 0;
|
|
342
|
+
background: none;
|
|
343
|
+
font: inherit;
|
|
344
|
+
color: inherit;
|
|
345
|
+
text-align: left;
|
|
346
|
+
cursor: pointer;
|
|
347
|
+
display: flex;
|
|
348
|
+
align-items: center;
|
|
349
|
+
gap: 12px;
|
|
350
|
+
padding: 14px 16px;
|
|
351
|
+
border-radius: 12px;
|
|
352
|
+
}
|
|
353
|
+
.dsh-rewind-cleanup-header:focus-visible {
|
|
354
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
355
|
+
outline-offset: -2px;
|
|
356
|
+
}
|
|
357
|
+
.dsh-rewind-cleanup-head-text {
|
|
358
|
+
flex: 1;
|
|
359
|
+
min-width: 0;
|
|
360
|
+
display: flex;
|
|
361
|
+
flex-direction: column;
|
|
362
|
+
gap: 4px;
|
|
363
|
+
}
|
|
364
|
+
.dsh-rewind-cleanup-name {
|
|
365
|
+
font-size: 15px;
|
|
366
|
+
font-weight: 600;
|
|
367
|
+
line-height: 1.4;
|
|
368
|
+
color: var(--dsw-alias-label-primary);
|
|
369
|
+
}
|
|
370
|
+
.dsh-rewind-cleanup-desc {
|
|
371
|
+
font-size: 13px;
|
|
372
|
+
line-height: 1.5;
|
|
373
|
+
color: var(--dsw-alias-label-tertiary);
|
|
374
|
+
}
|
|
375
|
+
.dsh-rewind-cleanup-chevron {
|
|
376
|
+
flex: none;
|
|
377
|
+
color: var(--dsw-alias-label-tertiary);
|
|
378
|
+
transition: transform .16s;
|
|
379
|
+
}
|
|
380
|
+
.dsh-rewind-cleanup-chevron-open {
|
|
381
|
+
transform: rotate(180deg);
|
|
382
|
+
}
|
|
383
|
+
.dsh-rewind-cleanup-pending {
|
|
384
|
+
flex: none;
|
|
385
|
+
border-radius: 999px;
|
|
386
|
+
padding: 1px 8px;
|
|
387
|
+
font-size: 11px;
|
|
388
|
+
line-height: 17px;
|
|
389
|
+
font-weight: 500;
|
|
390
|
+
white-space: nowrap;
|
|
391
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
392
|
+
color: var(--dsw-alias-label-secondary);
|
|
393
|
+
}
|
|
394
|
+
.dsh-rewind-cleanup-body {
|
|
395
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
396
|
+
margin: 0 16px;
|
|
397
|
+
padding: 4px 0 8px;
|
|
398
|
+
}
|
|
399
|
+
.dsh-rewind-cleanup-readonly {
|
|
400
|
+
margin: 12px 0 0;
|
|
401
|
+
font-size: 12px;
|
|
402
|
+
line-height: 1.5;
|
|
403
|
+
color: var(--dsw-alias-label-tertiary);
|
|
404
|
+
}
|
|
405
|
+
.dsh-rewind-cleanup-permission {
|
|
406
|
+
display: grid;
|
|
407
|
+
gap: 6px;
|
|
408
|
+
padding: 12px 0;
|
|
409
|
+
}
|
|
410
|
+
.dsh-rewind-cleanup-field {
|
|
411
|
+
display: flex;
|
|
412
|
+
flex-direction: column;
|
|
413
|
+
gap: 6px;
|
|
414
|
+
padding: 12px 0;
|
|
415
|
+
}
|
|
416
|
+
.dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {
|
|
417
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
418
|
+
}
|
|
419
|
+
.dsh-rewind-cleanup-head {
|
|
420
|
+
display: flex;
|
|
421
|
+
align-items: center;
|
|
422
|
+
gap: 8px;
|
|
423
|
+
}
|
|
424
|
+
.dsh-rewind-cleanup-label {
|
|
425
|
+
flex: 1;
|
|
426
|
+
min-width: 0;
|
|
427
|
+
font-size: 13px;
|
|
428
|
+
font-weight: 500;
|
|
429
|
+
line-height: 1.5;
|
|
430
|
+
color: var(--dsw-alias-label-primary);
|
|
431
|
+
}
|
|
432
|
+
.dsh-rewind-cleanup-hint {
|
|
433
|
+
margin: 0;
|
|
434
|
+
font-size: 12px;
|
|
435
|
+
line-height: 1.5;
|
|
436
|
+
color: var(--dsw-alias-label-tertiary);
|
|
437
|
+
}
|
|
438
|
+
.dsh-rewind-cleanup-error {
|
|
439
|
+
margin: 0;
|
|
440
|
+
font-size: 12px;
|
|
441
|
+
line-height: 1.5;
|
|
442
|
+
color: var(--dsw-alias-label-error);
|
|
443
|
+
}
|
|
444
|
+
/* Switch row: label left, role=switch button right, hint below (Subagent module). */
|
|
445
|
+
.dsh-rewind-cleanup-toggle-row {
|
|
446
|
+
display: flex;
|
|
447
|
+
align-items: flex-start;
|
|
448
|
+
justify-content: space-between;
|
|
449
|
+
gap: 16px;
|
|
450
|
+
font-size: 13px;
|
|
451
|
+
line-height: 1.5;
|
|
452
|
+
color: var(--dsw-alias-label-primary);
|
|
453
|
+
}
|
|
454
|
+
.dsh-rewind-cleanup-toggle-label {
|
|
455
|
+
flex: 1;
|
|
456
|
+
min-width: 0;
|
|
457
|
+
}
|
|
458
|
+
.dsh-rewind-cleanup-switch {
|
|
459
|
+
box-sizing: border-box;
|
|
460
|
+
position: relative;
|
|
461
|
+
flex: 0 0 auto;
|
|
462
|
+
width: 36px;
|
|
463
|
+
height: 20px;
|
|
464
|
+
padding: 2px;
|
|
465
|
+
border: 0;
|
|
466
|
+
border-radius: 10px;
|
|
467
|
+
background: var(--dsw-alias-border-l3);
|
|
468
|
+
cursor: pointer;
|
|
469
|
+
}
|
|
470
|
+
.dsh-rewind-cleanup-switch-on {
|
|
471
|
+
background: var(--dsw-alias-brand-primary);
|
|
472
|
+
}
|
|
473
|
+
.dsh-rewind-cleanup-switch:disabled {
|
|
474
|
+
cursor: default;
|
|
475
|
+
opacity: 0.5;
|
|
476
|
+
}
|
|
477
|
+
.dsh-rewind-cleanup-switch:focus-visible {
|
|
478
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
479
|
+
outline-offset: 2px;
|
|
480
|
+
}
|
|
481
|
+
.dsh-rewind-cleanup-thumb {
|
|
482
|
+
display: block;
|
|
483
|
+
width: 16px;
|
|
484
|
+
height: 16px;
|
|
485
|
+
border-radius: 50%;
|
|
486
|
+
background: var(--dsw-alias-label-primary-foreground);
|
|
487
|
+
transition: transform 120ms ease;
|
|
488
|
+
}
|
|
489
|
+
.dsh-rewind-cleanup-switch-on .dsh-rewind-cleanup-thumb {
|
|
490
|
+
transform: translateX(16px);
|
|
491
|
+
}
|
|
492
|
+
.dsh-rewind-cleanup-input {
|
|
493
|
+
box-sizing: border-box;
|
|
494
|
+
height: 34px;
|
|
495
|
+
padding: 0 12px;
|
|
496
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
497
|
+
border-radius: 8px;
|
|
498
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
499
|
+
font: inherit;
|
|
500
|
+
font-size: 13px;
|
|
501
|
+
line-height: 1.5;
|
|
502
|
+
color: var(--dsw-alias-label-primary);
|
|
503
|
+
}
|
|
504
|
+
.dsh-rewind-cleanup-input:focus-visible {
|
|
505
|
+
outline: none;
|
|
506
|
+
border-color: var(--dsw-alias-brand-primary);
|
|
507
|
+
}
|
|
508
|
+
.dsh-rewind-cleanup-input:disabled {
|
|
509
|
+
color: var(--dsw-alias-label-tertiary);
|
|
510
|
+
cursor: default;
|
|
511
|
+
}
|
|
512
|
+
.dsh-rewind-cleanup-input-invalid {
|
|
513
|
+
border-color: var(--dsw-alias-label-error);
|
|
514
|
+
}
|
|
515
|
+
.dsh-rewind-cleanup-footer {
|
|
516
|
+
display: flex;
|
|
517
|
+
align-items: center;
|
|
518
|
+
justify-content: flex-end;
|
|
519
|
+
gap: 8px;
|
|
520
|
+
padding: 12px 0 4px;
|
|
521
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
522
|
+
}
|
|
523
|
+
.dsh-rewind-cleanup-failed {
|
|
524
|
+
flex: 1;
|
|
525
|
+
min-width: 0;
|
|
526
|
+
margin: 0;
|
|
527
|
+
font-size: 12px;
|
|
528
|
+
line-height: 1.5;
|
|
529
|
+
color: var(--dsw-alias-label-error);
|
|
530
|
+
}
|
|
531
|
+
.dsh-rewind-cleanup-discard,
|
|
532
|
+
.dsh-rewind-cleanup-save {
|
|
533
|
+
appearance: none;
|
|
534
|
+
border: 1px solid transparent;
|
|
535
|
+
border-radius: 8px;
|
|
536
|
+
padding: 5px 14px;
|
|
537
|
+
font: inherit;
|
|
538
|
+
font-size: 13px;
|
|
539
|
+
line-height: 1.5;
|
|
540
|
+
cursor: pointer;
|
|
541
|
+
}
|
|
542
|
+
.dsh-rewind-cleanup-discard {
|
|
543
|
+
border-color: var(--dsw-alias-border-l2);
|
|
544
|
+
background: none;
|
|
545
|
+
color: var(--dsw-alias-label-secondary);
|
|
546
|
+
}
|
|
547
|
+
.dsh-rewind-cleanup-discard:hover:not(:disabled) {
|
|
548
|
+
color: var(--dsw-alias-label-primary);
|
|
549
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
550
|
+
}
|
|
551
|
+
.dsh-rewind-cleanup-save {
|
|
552
|
+
background: var(--dsw-alias-label-primary);
|
|
553
|
+
color: var(--dsw-alias-bg-layer-3);
|
|
554
|
+
}
|
|
555
|
+
.dsh-rewind-cleanup-discard:disabled,
|
|
556
|
+
.dsh-rewind-cleanup-save:disabled {
|
|
557
|
+
opacity: 0.4;
|
|
558
|
+
cursor: default;
|
|
559
|
+
}
|
|
560
|
+
.dsh-rewind-cleanup-discard:focus-visible,
|
|
561
|
+
.dsh-rewind-cleanup-save:focus-visible {
|
|
562
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
563
|
+
outline-offset: 1px;
|
|
564
|
+
}
|
|
322
565
|
`;
|
|
323
566
|
|
|
324
567
|
// src/client/popover.ts
|
|
@@ -841,6 +1084,40 @@ function sameTargets(left, right) {
|
|
|
841
1084
|
return other.kind === "pending" && target.itemId === other.itemId;
|
|
842
1085
|
});
|
|
843
1086
|
}
|
|
1087
|
+
function hasExecutedRewindCommand(chat) {
|
|
1088
|
+
for (const key of chat.order) {
|
|
1089
|
+
const node = chat.nodes.get(key);
|
|
1090
|
+
if (node === void 0 || node.kind !== "command") continue;
|
|
1091
|
+
const command = node.data;
|
|
1092
|
+
if (command.name !== "rewind") continue;
|
|
1093
|
+
const args = command.args ?? "";
|
|
1094
|
+
if (args.includes("preview") || args.includes("__candidates")) continue;
|
|
1095
|
+
return true;
|
|
1096
|
+
}
|
|
1097
|
+
return false;
|
|
1098
|
+
}
|
|
1099
|
+
function describeHiding(chat, hiddenSeqs) {
|
|
1100
|
+
const commands = [];
|
|
1101
|
+
for (const key of chat.order) {
|
|
1102
|
+
const node = chat.nodes.get(key);
|
|
1103
|
+
if (node === void 0 || node.kind !== "command") continue;
|
|
1104
|
+
const c = node.data;
|
|
1105
|
+
commands.push(`#${c.seq} name=${c.name} outcome=${c.outcome?.kind ?? "-"} marker=${c.outcome?.sourceEventSeq ?? "-"} args=${JSON.stringify(c.args)}`);
|
|
1106
|
+
}
|
|
1107
|
+
const scoped = hiddenSeqs.size > 0 ? `hidden=[${[...hiddenSeqs].slice(0, 20).join(",")}${hiddenSeqs.size > 20 ? "\u2026" : ""}]` : "";
|
|
1108
|
+
let seats = 0;
|
|
1109
|
+
let resolved = 0;
|
|
1110
|
+
let inHidden = 0;
|
|
1111
|
+
for (const seat of document.querySelectorAll(CHAT_SEAT_SELECTOR)) {
|
|
1112
|
+
seats += 1;
|
|
1113
|
+
const key = seat.dataset.chatAnchorKey;
|
|
1114
|
+
const node = key === void 0 ? void 0 : chat.nodes.get(key);
|
|
1115
|
+
if (node === void 0) continue;
|
|
1116
|
+
resolved += 1;
|
|
1117
|
+
if (hiddenSeqs.has(node.anchorSeq)) inHidden += 1;
|
|
1118
|
+
}
|
|
1119
|
+
return `commands=[${commands.join(" | ")}] ${scoped} seats=${seats} resolved=${resolved} inHidden=${inHidden}`;
|
|
1120
|
+
}
|
|
844
1121
|
function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale, setComposerText }) {
|
|
845
1122
|
const [targets, setTargets] = (0, import_react.useState)([]);
|
|
846
1123
|
const hidden = (0, import_react.useRef)(/* @__PURE__ */ new WeakSet());
|
|
@@ -881,6 +1158,9 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subs
|
|
|
881
1158
|
`[dsh-rewind] hiding: ${hiddenCount} rows, seqs [${[...hiddenSeqs].slice(0, 20).join(", ")}${hiddenSeqs.size > 20 ? "\u2026" : ""}]`
|
|
882
1159
|
);
|
|
883
1160
|
}
|
|
1161
|
+
if (chat !== void 0 && hiddenCount === 0 && hasExecutedRewindCommand(chat)) {
|
|
1162
|
+
console.warn(`[dsh-rewind] rewind not hidden: ${describeHiding(chat, hiddenSeqs)}`);
|
|
1163
|
+
}
|
|
884
1164
|
const durable = chat === void 0 ? [] : collectTargets(chat, hiddenSeqs);
|
|
885
1165
|
const next = [...durable, ...collectPendingTargets(snapshot)];
|
|
886
1166
|
setTargets((current) => sameTargets(current, next) ? current : next);
|
|
@@ -1042,7 +1322,24 @@ var zh = {
|
|
|
1042
1322
|
"popover.impact.restore": "\u8FD8\u539F {path}",
|
|
1043
1323
|
"popover.impact.delete": "\u5220\u9664 {path}",
|
|
1044
1324
|
"popover.confirm": "\u786E\u8BA4\u56DE\u9000",
|
|
1045
|
-
"popover.back": "\u8FD4\u56DE"
|
|
1325
|
+
"popover.back": "\u8FD4\u56DE",
|
|
1326
|
+
"cleanup.title": "\u5FEB\u7167\u6E05\u7406",
|
|
1327
|
+
"cleanup.desc": "dsh-rewind \xB7 \u7BA1\u7406\u4F1A\u8BDD\u5FEB\u7167\u5907\u4EFD\u7684\u81EA\u52A8\u6E05\u7406\u7B56\u7565",
|
|
1328
|
+
"cleanup.expand": "\u5C55\u5F00",
|
|
1329
|
+
"cleanup.collapse": "\u6536\u8D77",
|
|
1330
|
+
"cleanup.unsaved": "\u672A\u4FDD\u5B58\u4FEE\u6539",
|
|
1331
|
+
"cleanup.auto": "\u81EA\u52A8\u6E05\u7406",
|
|
1332
|
+
"cleanup.auto.on": "\u5F00\u542F\u540E\u6309\u4E0D\u6D3B\u8DC3\u5929\u6570\u81EA\u52A8\u6E05\u7406\u8FC7\u671F\u4F1A\u8BDD\u5FEB\u7167",
|
|
1333
|
+
"cleanup.auto.off": "\u5173\u95ED\u540E\u4FDD\u7559\u5168\u90E8\u5FEB\u7167\uFF0C\u4E0D\u81EA\u52A8\u6E05\u7406",
|
|
1334
|
+
"cleanup.maxAge": "\u4E0D\u6D3B\u8DC3\u65F6\u95F4\uFF08\u5929\uFF09",
|
|
1335
|
+
"cleanup.maxAge.hint": "\u8D85\u8FC7\u8BE5\u5929\u6570\u672A\u6D3B\u52A8\u7684\u4F1A\u8BDD\u5FEB\u7167\u4F1A\u88AB\u6E05\u7406",
|
|
1336
|
+
"cleanup.invalid": "\u4EC5\u63A5\u53D7\u6B63\u6574\u6570",
|
|
1337
|
+
"cleanup.discard": "\u653E\u5F03\u4FEE\u6539",
|
|
1338
|
+
"cleanup.save": "\u4FDD\u5B58",
|
|
1339
|
+
"cleanup.saving": "\u4FDD\u5B58\u4E2D\u2026",
|
|
1340
|
+
"cleanup.saved": "\u5DF2\u4FDD\u5B58\u5E76\u751F\u6548",
|
|
1341
|
+
"cleanup.saveFailed": "\u4FDD\u5B58\u5931\u8D25\uFF1A{message}",
|
|
1342
|
+
"cleanup.readonly": "\u8BBE\u7F6E\u6E90\u53EA\u8BFB"
|
|
1046
1343
|
};
|
|
1047
1344
|
var en = {
|
|
1048
1345
|
"button.aria": "Rewind to this message",
|
|
@@ -1068,9 +1365,189 @@ var en = {
|
|
|
1068
1365
|
"popover.impact.restore": "Restore {path}",
|
|
1069
1366
|
"popover.impact.delete": "Delete {path}",
|
|
1070
1367
|
"popover.confirm": "Confirm rewind",
|
|
1071
|
-
"popover.back": "Back"
|
|
1368
|
+
"popover.back": "Back",
|
|
1369
|
+
"cleanup.title": "Snapshot cleanup",
|
|
1370
|
+
"cleanup.desc": "dsh-rewind \xB7 Manage the auto-cleanup policy of session snapshot backups",
|
|
1371
|
+
"cleanup.expand": "Expand",
|
|
1372
|
+
"cleanup.collapse": "Collapse",
|
|
1373
|
+
"cleanup.unsaved": "Unsaved changes",
|
|
1374
|
+
"cleanup.auto": "Auto cleanup",
|
|
1375
|
+
"cleanup.auto.on": "When enabled, session snapshots idle past the cutoff are cleaned automatically",
|
|
1376
|
+
"cleanup.auto.off": "When disabled, all snapshot backups are kept; nothing is cleaned automatically.",
|
|
1377
|
+
"cleanup.maxAge": "Idle time (days)",
|
|
1378
|
+
"cleanup.maxAge.hint": "Snapshot backups of sessions idle longer than this many days are cleaned",
|
|
1379
|
+
"cleanup.invalid": "A positive integer only",
|
|
1380
|
+
"cleanup.discard": "Discard changes",
|
|
1381
|
+
"cleanup.save": "Save",
|
|
1382
|
+
"cleanup.saving": "Saving\u2026",
|
|
1383
|
+
"cleanup.saved": "Saved and applied",
|
|
1384
|
+
"cleanup.saveFailed": "Save failed: {message}",
|
|
1385
|
+
"cleanup.readonly": "Read-only settings source"
|
|
1072
1386
|
};
|
|
1073
1387
|
|
|
1388
|
+
// src/client/settings-card.tsx
|
|
1389
|
+
var import_react2 = require("react");
|
|
1390
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1391
|
+
var CLEANUP_SETTINGS_NAMESPACE = "dsh-rewind-snapshot-cleanup";
|
|
1392
|
+
var DEFAULT_MAX_AGE_DAYS = 30;
|
|
1393
|
+
function draftFrom(policy) {
|
|
1394
|
+
return { enabled: policy?.enabled ?? false, maxAgeDays: String(policy?.maxAgeDays ?? "") };
|
|
1395
|
+
}
|
|
1396
|
+
function maxAgeOf(text) {
|
|
1397
|
+
const trimmed = text.trim();
|
|
1398
|
+
if (!/^\d+$/.test(trimmed)) return null;
|
|
1399
|
+
const days = Number(trimmed);
|
|
1400
|
+
return Number.isSafeInteger(days) && days > 0 ? days : null;
|
|
1401
|
+
}
|
|
1402
|
+
function configOf(draft) {
|
|
1403
|
+
const days = maxAgeOf(draft.maxAgeDays);
|
|
1404
|
+
if (days === null) return null;
|
|
1405
|
+
return { enabled: draft.enabled, maxAgeDays: days };
|
|
1406
|
+
}
|
|
1407
|
+
function dirtyOf(base, draft) {
|
|
1408
|
+
return base.enabled !== draft.enabled || base.maxAgeDays !== draft.maxAgeDays;
|
|
1409
|
+
}
|
|
1410
|
+
function SettingsCleanupCard({ api, t }) {
|
|
1411
|
+
const [open, setOpen] = (0, import_react2.useState)(false);
|
|
1412
|
+
const [baseline, setBaseline] = (0, import_react2.useState)(() => draftFrom(api.read()));
|
|
1413
|
+
const [draft, setDraft] = (0, import_react2.useState)(() => draftFrom(api.read()));
|
|
1414
|
+
const [busy, setBusy] = (0, import_react2.useState)(false);
|
|
1415
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
1416
|
+
const writable = api.writable();
|
|
1417
|
+
(0, import_react2.useEffect)(() => api.subscribe(() => {
|
|
1418
|
+
const next = draftFrom(api.read());
|
|
1419
|
+
setBaseline((base) => {
|
|
1420
|
+
setDraft((cur) => dirtyOf(base, cur) ? cur : next);
|
|
1421
|
+
return next;
|
|
1422
|
+
});
|
|
1423
|
+
}), [api]);
|
|
1424
|
+
const dirty = dirtyOf(baseline, draft);
|
|
1425
|
+
const days = maxAgeOf(draft.maxAgeDays);
|
|
1426
|
+
const invalid = days === null;
|
|
1427
|
+
const disabled = busy || !writable;
|
|
1428
|
+
const edit = (patch) => {
|
|
1429
|
+
setDraft((cur) => ({ ...cur, ...patch }));
|
|
1430
|
+
setError(null);
|
|
1431
|
+
};
|
|
1432
|
+
const save = async () => {
|
|
1433
|
+
if (busy || !writable || !dirty) return;
|
|
1434
|
+
const next = configOf(draft);
|
|
1435
|
+
if (next === null) {
|
|
1436
|
+
setError(t("cleanup.invalid"));
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
setBusy(true);
|
|
1440
|
+
setError(null);
|
|
1441
|
+
try {
|
|
1442
|
+
await api.save(next);
|
|
1443
|
+
setBaseline(draft);
|
|
1444
|
+
setError(null);
|
|
1445
|
+
} catch (e) {
|
|
1446
|
+
setError(t("cleanup.saveFailed", { message: e instanceof Error ? e.message : String(e) }));
|
|
1447
|
+
} finally {
|
|
1448
|
+
setBusy(false);
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
const discard = () => {
|
|
1452
|
+
if (busy) return;
|
|
1453
|
+
setDraft(baseline);
|
|
1454
|
+
setError(null);
|
|
1455
|
+
};
|
|
1456
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: `dsh-rewind-cleanup-card${open ? " dsh-rewind-cleanup-card-open" : ""}`, children: [
|
|
1457
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1458
|
+
"button",
|
|
1459
|
+
{
|
|
1460
|
+
type: "button",
|
|
1461
|
+
className: "dsh-rewind-cleanup-header",
|
|
1462
|
+
"aria-expanded": open,
|
|
1463
|
+
"aria-label": `${t(open ? "cleanup.collapse" : "cleanup.expand")}: ${t("cleanup.title")}`,
|
|
1464
|
+
onClick: () => setOpen(!open),
|
|
1465
|
+
children: [
|
|
1466
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dsh-rewind-cleanup-head-text", children: [
|
|
1467
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-name", children: t("cleanup.title") }),
|
|
1468
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-desc", children: t("cleanup.desc") })
|
|
1469
|
+
] }),
|
|
1470
|
+
dirty ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-pending", children: t("cleanup.unsaved") }) : null,
|
|
1471
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1472
|
+
"svg",
|
|
1473
|
+
{
|
|
1474
|
+
className: `dsh-rewind-cleanup-chevron${open ? " dsh-rewind-cleanup-chevron-open" : ""}`,
|
|
1475
|
+
width: "14",
|
|
1476
|
+
height: "14",
|
|
1477
|
+
viewBox: "0 0 16 16",
|
|
1478
|
+
"aria-hidden": "true",
|
|
1479
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4 6l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
1480
|
+
}
|
|
1481
|
+
)
|
|
1482
|
+
]
|
|
1483
|
+
}
|
|
1484
|
+
),
|
|
1485
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-body", children: [
|
|
1486
|
+
!writable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-readonly", role: "status", children: t("cleanup.readonly") }) : null,
|
|
1487
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-permission", children: [
|
|
1488
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-toggle-row", children: [
|
|
1489
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-toggle-label", id: "dsh-rewind-cleanup-enabled-label", children: t("cleanup.auto") }),
|
|
1490
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1491
|
+
"button",
|
|
1492
|
+
{
|
|
1493
|
+
type: "button",
|
|
1494
|
+
role: "switch",
|
|
1495
|
+
className: `dsh-rewind-cleanup-switch${draft.enabled ? " dsh-rewind-cleanup-switch-on" : ""}`,
|
|
1496
|
+
"aria-checked": draft.enabled,
|
|
1497
|
+
"aria-labelledby": "dsh-rewind-cleanup-enabled-label",
|
|
1498
|
+
disabled,
|
|
1499
|
+
onClick: () => edit({ enabled: !draft.enabled }),
|
|
1500
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-thumb" })
|
|
1501
|
+
}
|
|
1502
|
+
)
|
|
1503
|
+
] }),
|
|
1504
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-hint", children: t(draft.enabled ? "cleanup.auto.on" : "cleanup.auto.off") })
|
|
1505
|
+
] }),
|
|
1506
|
+
draft.enabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-field", children: [
|
|
1507
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-rewind-cleanup-head", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "dsh-rewind-cleanup-label", htmlFor: "dsh-rewind-cleanup-maxage", children: t("cleanup.maxAge") }) }),
|
|
1508
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1509
|
+
"input",
|
|
1510
|
+
{
|
|
1511
|
+
className: `dsh-rewind-cleanup-input${invalid ? " dsh-rewind-cleanup-input-invalid" : ""}`,
|
|
1512
|
+
type: "text",
|
|
1513
|
+
inputMode: "numeric",
|
|
1514
|
+
id: "dsh-rewind-cleanup-maxage",
|
|
1515
|
+
value: draft.maxAgeDays,
|
|
1516
|
+
disabled,
|
|
1517
|
+
"aria-invalid": invalid || void 0,
|
|
1518
|
+
placeholder: String(DEFAULT_MAX_AGE_DAYS),
|
|
1519
|
+
onChange: (e) => edit({ maxAgeDays: e.target.value })
|
|
1520
|
+
}
|
|
1521
|
+
),
|
|
1522
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: invalid ? "dsh-rewind-cleanup-error" : "dsh-rewind-cleanup-hint", children: invalid ? t("cleanup.invalid") : t("cleanup.maxAge.hint") })
|
|
1523
|
+
] }) : null,
|
|
1524
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-footer", children: [
|
|
1525
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-failed", role: "status", children: error }) : null,
|
|
1526
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1527
|
+
"button",
|
|
1528
|
+
{
|
|
1529
|
+
type: "button",
|
|
1530
|
+
className: "dsh-rewind-cleanup-discard",
|
|
1531
|
+
disabled: !dirty || busy || !writable,
|
|
1532
|
+
onClick: discard,
|
|
1533
|
+
children: t("cleanup.discard")
|
|
1534
|
+
}
|
|
1535
|
+
),
|
|
1536
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1537
|
+
"button",
|
|
1538
|
+
{
|
|
1539
|
+
type: "button",
|
|
1540
|
+
className: "dsh-rewind-cleanup-save",
|
|
1541
|
+
disabled: !dirty || busy || !writable || invalid,
|
|
1542
|
+
onClick: save,
|
|
1543
|
+
children: busy ? t("cleanup.saving") : t("cleanup.save")
|
|
1544
|
+
}
|
|
1545
|
+
)
|
|
1546
|
+
] })
|
|
1547
|
+
] }) : null
|
|
1548
|
+
] });
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1074
1551
|
// src/client/index.ts
|
|
1075
1552
|
var name = "dsh-rewind";
|
|
1076
1553
|
var inject = ["slots", "sessions", "locale", "commandUi"];
|
|
@@ -1122,6 +1599,40 @@ function apply(ctx) {
|
|
|
1122
1599
|
},
|
|
1123
1600
|
createRewindBridge({ sessionOf, chatOf, currentSessionId, setComposerText, t, subscribeLocale })
|
|
1124
1601
|
));
|
|
1602
|
+
const clientCtx = ctx;
|
|
1603
|
+
clientCtx.inject(["settingsScope"], (scoped) => {
|
|
1604
|
+
try {
|
|
1605
|
+
const scope = scoped.settingsScope.bind({ namespace: CLEANUP_SETTINGS_NAMESPACE });
|
|
1606
|
+
const cardApi = {
|
|
1607
|
+
read: () => {
|
|
1608
|
+
const value = scope.getSnapshot().value;
|
|
1609
|
+
return value === void 0 ? void 0 : { enabled: value.enabled, maxAgeDays: value.maxAgeDays };
|
|
1610
|
+
},
|
|
1611
|
+
writable: () => {
|
|
1612
|
+
return scope.getSnapshot().writable === true;
|
|
1613
|
+
},
|
|
1614
|
+
save: async (next) => {
|
|
1615
|
+
await scope.set("enabled", next.enabled);
|
|
1616
|
+
await scope.set("maxAgeDays", next.maxAgeDays);
|
|
1617
|
+
},
|
|
1618
|
+
subscribe: (cb) => scope.subscribe(cb)
|
|
1619
|
+
};
|
|
1620
|
+
scoped.slots.inject("settings.plugin.item", () => scoped.slots.register(
|
|
1621
|
+
{
|
|
1622
|
+
name: "settings.plugin.item",
|
|
1623
|
+
key: CLEANUP_SETTINGS_NAMESPACE,
|
|
1624
|
+
// Match the official cards / dsh-market: locale + inject provide
|
|
1625
|
+
// the card its props through the slot renderer (the keyed card owns
|
|
1626
|
+
// its internals, but the page feeds it locale + the bound api).
|
|
1627
|
+
locale: NS,
|
|
1628
|
+
inject: () => ({ t, api: cardApi })
|
|
1629
|
+
},
|
|
1630
|
+
SettingsCleanupCard
|
|
1631
|
+
));
|
|
1632
|
+
} catch (error) {
|
|
1633
|
+
console.error("[dsh-rewind] settings card register failed:", error);
|
|
1634
|
+
}
|
|
1635
|
+
});
|
|
1125
1636
|
const commandUi = ctx.get("commandUi");
|
|
1126
1637
|
const hasCandidates = (sessionId) => {
|
|
1127
1638
|
const face = sessionId === void 0 ? void 0 : sessionOf(sessionId);
|