@michengai/dsh-archive-manager 0.1.21 → 0.1.23
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/CHANGELOG.md +16 -0
- package/CHANGELOG.zh-CN.md +16 -0
- package/lib/client.js +342 -20
- package/lib/workspace.js +6 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
Published release notes are retained below; new versions are added without removing earlier entries.
|
|
6
6
|
|
|
7
|
+
## 0.1.23 — 2026-09-01
|
|
8
|
+
|
|
9
|
+
- Fixed missing sidebar pending-state indicators on DSH `v0.1.2-alpha.2`: workspace rows now read pending UI interactions from the `ui-session` `pendingInteractions` Map.
|
|
10
|
+
- Whitelisted the official visible pending states (`question`, `approval`, and `plan-review`) before passing them to row rendering, so unknown future states are ignored safely.
|
|
11
|
+
- Kept the legacy `SessionSummary.pendingInteraction` fallback and added regression coverage for grouped, flat, and search result rows.
|
|
12
|
+
|
|
13
|
+
Published package: [`@michengai/dsh-archive-manager@0.1.23`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.23).
|
|
14
|
+
|
|
15
|
+
## 0.1.22 — 2026-09-01
|
|
16
|
+
|
|
17
|
+
- Added arbitrary archived-chat selection in **Settings → Archived**, including per-row checkboxes, select-all for the current filtered results, cross-project selection, batch restore, and confirmed batch permanent deletion.
|
|
18
|
+
- Kept selected operations on the existing authoritative host batch path, so stale records, partial deletion failures, and retry behavior match existing project-wide and all-chat actions.
|
|
19
|
+
- Refreshed the client session projection after restoring chats so restored sessions immediately reappear in their original workspace.
|
|
20
|
+
|
|
21
|
+
Published package: [`@michengai/dsh-archive-manager@0.1.22`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.22).
|
|
22
|
+
|
|
7
23
|
## 0.1.21 — 2026-08-31
|
|
8
24
|
|
|
9
25
|
- Preserved the projection-cache `put()` Promise contract when a tombstone suppresses a deleted Session write, so DSH `v0.1.2-alpha.2` cold-read write-back remains fail-soft instead of throwing a synchronous `TypeError`.
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
以下发布说明会持续保留;新增版本时不再删除较早记录。
|
|
6
6
|
|
|
7
|
+
## 0.1.23 — 2026-09-01
|
|
8
|
+
|
|
9
|
+
- 修复 DSH `v0.1.2-alpha.2` 后会话侧栏“等待回答 / 等待审批”状态点不显示的问题:侧栏行现在从 `ui-session` 的 `pendingInteractions` Map 读取待处理交互状态。
|
|
10
|
+
- 对 `question`、`approval`、`plan-review` 三种官方可见状态做白名单透传,未知状态会被忽略,避免触发行渲染断言。
|
|
11
|
+
- 保留旧版 `SessionSummary.pendingInteraction` 兜底,并补充分组、扁平列表和搜索结果三条路径的回归测试。
|
|
12
|
+
|
|
13
|
+
已发布包:[`@michengai/dsh-archive-manager@0.1.23`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.23)。
|
|
14
|
+
|
|
15
|
+
## 0.1.22 — 2026-09-01
|
|
16
|
+
|
|
17
|
+
- 在「设置 → 已归档」新增任意多选:每行复选框、全选当前筛选结果、跨项目保留选择、批量恢复和确认后的批量永久删除。
|
|
18
|
+
- 所选操作复用宿主权威批量路径,陈旧记录清理、部分删除失败和重试行为与项目级及全部操作保持一致。
|
|
19
|
+
- 恢复聊天后刷新客户端会话投影,使其立刻重新出现在原工作区。
|
|
20
|
+
|
|
21
|
+
已发布包:[`@michengai/dsh-archive-manager@0.1.22`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.22)。
|
|
22
|
+
|
|
7
23
|
## 0.1.21 — 2026-08-31
|
|
8
24
|
|
|
9
25
|
- 墓碑拦截已删除会话的投影缓存写入时仍保持 `put()` 的 Promise 契约,使 DSH `v0.1.2-alpha.2` 冷读写回继续按 fail-soft 方式处理,而不会同步抛出 `TypeError`。
|
package/lib/client.js
CHANGED
|
@@ -49,7 +49,8 @@ window.__ModuleLoader__.load({
|
|
|
49
49
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("target must be an object");
|
|
50
50
|
if (value.scope === "all" || value.scope === "ungrouped") return value;
|
|
51
51
|
if (value.scope === "workspace" && typeof value.workspaceId === "string" && value.workspaceId.length > 0) return value;
|
|
52
|
-
|
|
52
|
+
if (value.scope === "sessions" && Array.isArray(value.sessionIds) && value.sessionIds.length > 0 && value.sessionIds.every((id) => typeof id === "string" && id.length > 0)) return value;
|
|
53
|
+
throw new TypeError("target.scope must be all, ungrouped, workspace with a non-empty workspaceId, or sessions with non-empty sessionIds");
|
|
53
54
|
}
|
|
54
55
|
};
|
|
55
56
|
const unarchivedBatchSchema = {
|
|
@@ -283,6 +284,195 @@ window.__ModuleLoader__.load({
|
|
|
283
284
|
}
|
|
284
285
|
});
|
|
285
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* 归档多选原型。保留既有设置页作为兼容回退,原型直接使用相同宿主
|
|
289
|
+
* 批量接口,因此其恢复、删除和并发安全语义与原有项目级操作一致。
|
|
290
|
+
*/
|
|
291
|
+
function ArchivedSessionsSectionPrototype({ sessionStore, workspaceStore, unarchiveSession, deleteSession, unarchiveSessions, deleteArchivedSessions, archivedSessionMetadata, t }) {
|
|
292
|
+
const sessions = (0, react.useSyncExternalStore)(sessionStore.subscribe, sessionStore.getSnapshot);
|
|
293
|
+
const workspaceState = (0, react.useSyncExternalStore)(workspaceStore.subscribe, workspaceStore.getSnapshot);
|
|
294
|
+
const [deleteTarget, setDeleteTarget] = (0, react.useState)(null);
|
|
295
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
296
|
+
const [error, setError] = (0, react.useState)(null);
|
|
297
|
+
const [notice, setNotice] = (0, react.useState)(null);
|
|
298
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
299
|
+
const [project, setProject] = (0, react.useState)("all");
|
|
300
|
+
const [sortBy, setSortBy] = (0, react.useState)("updated");
|
|
301
|
+
const [createdAtById, setCreatedAtById] = (0, react.useState)({});
|
|
302
|
+
const [unarchivingSessionIds, setUnarchivingSessionIds] = (0, react.useState)(() => /* @__PURE__ */ new Set());
|
|
303
|
+
const unarchivingSessionIdsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
304
|
+
const [selectedSessionIds, setSelectedSessionIds] = (0, react.useState)([]);
|
|
305
|
+
const groups = (0, react.useMemo)(() => deriveArchivedGroups(sessions.byId, workspaceState.items, workspaceState.archivedSessionIds, t("group.ungrouped")), [sessions.byId, workspaceState, t]);
|
|
306
|
+
const sortedGroups = (0, react.useMemo)(() => sortArchivedGroups(groups, sortBy, createdAtById, t), [groups, sortBy, createdAtById, t]);
|
|
307
|
+
(0, react.useEffect)(() => {
|
|
308
|
+
let cancelled = false;
|
|
309
|
+
archivedSessionMetadata().then((result) => {
|
|
310
|
+
if (!cancelled) setCreatedAtById(Object.fromEntries(result.items.map((item) => [item.sessionId, item.createdAt])));
|
|
311
|
+
}).catch(() => {
|
|
312
|
+
if (!cancelled) setCreatedAtById({});
|
|
313
|
+
});
|
|
314
|
+
return () => {
|
|
315
|
+
cancelled = true;
|
|
316
|
+
};
|
|
317
|
+
}, [archivedSessionMetadata, workspaceState.archivedSessionIds]);
|
|
318
|
+
(0, react.useEffect)(() => {
|
|
319
|
+
if (project !== "all" && !groups.some((group) => group.key === project)) setProject("all");
|
|
320
|
+
}, [groups, project]);
|
|
321
|
+
const filteredGroups = (0, react.useMemo)(() => {
|
|
322
|
+
const normalizedQuery = query.trim().toLocaleLowerCase();
|
|
323
|
+
return sortedGroups.filter((group) => project === "all" || project === group.key).map((group) => ({
|
|
324
|
+
...group,
|
|
325
|
+
sessions: group.sessions.filter((session) => normalizedQuery === "" || displayTitle(session, t).toLocaleLowerCase().includes(normalizedQuery))
|
|
326
|
+
})).filter((group) => group.sessions.length > 0);
|
|
327
|
+
}, [sortedGroups, project, query, t]);
|
|
328
|
+
const visibleSessionIds = (0, react.useMemo)(() => archivedSessionIdsInGroups(filteredGroups), [filteredGroups]);
|
|
329
|
+
const selectedSessionIdSet = (0, react.useMemo)(() => new Set(selectedSessionIds), [selectedSessionIds]);
|
|
330
|
+
const selectedVisibleCount = visibleSessionIds.filter((sessionId) => selectedSessionIdSet.has(sessionId)).length;
|
|
331
|
+
const allVisibleSelected = visibleSessionIds.length > 0 && selectedVisibleCount === visibleSessionIds.length;
|
|
332
|
+
const allBatchTarget = { scope: "all" };
|
|
333
|
+
const allBatchSessionIds = (0, react.useMemo)(() => deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, allBatchTarget), [workspaceState.archivedSessionIds, workspaceState.items]);
|
|
334
|
+
(0, react.useEffect)(() => {
|
|
335
|
+
setSelectedSessionIds((current) => pruneArchivedSelection(current, workspaceState.archivedSessionIds));
|
|
336
|
+
}, [workspaceState.archivedSessionIds]);
|
|
337
|
+
const toggleSessionSelection = (sessionId, checked) => {
|
|
338
|
+
setSelectedSessionIds((current) => toggleArchivedSelection(current, [sessionId], checked));
|
|
339
|
+
};
|
|
340
|
+
const toggleVisibleSelection = (checked) => {
|
|
341
|
+
setSelectedSessionIds((current) => toggleArchivedSelection(current, visibleSessionIds, checked));
|
|
342
|
+
};
|
|
343
|
+
const onUnarchive = (sessionId) => {
|
|
344
|
+
if (busy || unarchivingSessionIdsRef.current.has(sessionId)) return;
|
|
345
|
+
unarchivingSessionIdsRef.current.add(sessionId);
|
|
346
|
+
setUnarchivingSessionIds(new Set(unarchivingSessionIdsRef.current));
|
|
347
|
+
setError(null);
|
|
348
|
+
setNotice(null);
|
|
349
|
+
unarchiveSession(sessionId).catch((reason) => {
|
|
350
|
+
setError(formatUnarchiveError(reason, t));
|
|
351
|
+
}).finally(() => {
|
|
352
|
+
unarchivingSessionIdsRef.current.delete(sessionId);
|
|
353
|
+
setUnarchivingSessionIds(new Set(unarchivingSessionIdsRef.current));
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
const onBatchUnarchive = async (target) => {
|
|
357
|
+
if (busy) return;
|
|
358
|
+
setBusy(true);
|
|
359
|
+
setError(null);
|
|
360
|
+
setNotice(null);
|
|
361
|
+
try {
|
|
362
|
+
const result = await unarchiveSessions(target);
|
|
363
|
+
setNotice(t("archives.restoreSuccess", { n: result.unarchivedSessionIds.length }));
|
|
364
|
+
return result;
|
|
365
|
+
} catch (reason) {
|
|
366
|
+
setError(t("archives.restoreBatchFailed", { detail: reason instanceof Error ? reason.message : String(reason) }));
|
|
367
|
+
} finally {
|
|
368
|
+
setBusy(false);
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
const onSelectedUnarchive = async () => {
|
|
372
|
+
if (selectedSessionIds.length === 0) return;
|
|
373
|
+
const result = await onBatchUnarchive({ scope: "sessions", sessionIds: selectedSessionIds });
|
|
374
|
+
if (result !== void 0) {
|
|
375
|
+
const restored = new Set(result.unarchivedSessionIds);
|
|
376
|
+
setSelectedSessionIds((current) => current.filter((sessionId) => !restored.has(sessionId)));
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
const closeDelete = () => {
|
|
380
|
+
if (!busy) setDeleteTarget(null);
|
|
381
|
+
};
|
|
382
|
+
(0, react.useEffect)(() => {
|
|
383
|
+
if (deleteTarget === null) return;
|
|
384
|
+
const onKeyDown = (event) => {
|
|
385
|
+
if (event.key !== "Escape") return;
|
|
386
|
+
event.preventDefault();
|
|
387
|
+
event.stopPropagation();
|
|
388
|
+
if (typeof event.stopImmediatePropagation === "function") event.stopImmediatePropagation();
|
|
389
|
+
if (!busy) setDeleteTarget(null);
|
|
390
|
+
};
|
|
391
|
+
window.addEventListener("keydown", onKeyDown, true);
|
|
392
|
+
return () => window.removeEventListener("keydown", onKeyDown, true);
|
|
393
|
+
}, [deleteTarget, busy]);
|
|
394
|
+
const confirmDelete = async () => {
|
|
395
|
+
if (busy || deleteTarget === null) return;
|
|
396
|
+
setBusy(true);
|
|
397
|
+
setError(null);
|
|
398
|
+
setNotice(null);
|
|
399
|
+
try {
|
|
400
|
+
if (deleteTarget.kind === "batch") {
|
|
401
|
+
const result = await deleteArchivedSessions(deleteTarget.target);
|
|
402
|
+
const feedback = archivedDeleteFeedback(result, t);
|
|
403
|
+
if (feedback.kind === "error") setError(feedback.message);
|
|
404
|
+
else setNotice(feedback.message);
|
|
405
|
+
if (deleteTarget.target.scope === "sessions") {
|
|
406
|
+
const completed = new Set([...result.deletedSessionIds, ...result.skippedSessionIds]);
|
|
407
|
+
setSelectedSessionIds((current) => current.filter((sessionId) => !completed.has(sessionId)));
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
await deleteSession(deleteTarget.session.id);
|
|
411
|
+
}
|
|
412
|
+
setDeleteTarget(null);
|
|
413
|
+
} catch (reason) {
|
|
414
|
+
setError(formatDeleteError(reason, t));
|
|
415
|
+
} finally {
|
|
416
|
+
setBusy(false);
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
const batchScope = deleteTarget?.kind === "batch" ? deleteTarget.target.scope : null;
|
|
420
|
+
const deleteDialogTitle = batchScope === "all" ? t("archives.deleteAllTitle") : batchScope === "ungrouped" ? t("archives.deleteUngroupedTitle") : batchScope === "workspace" ? t("archives.deleteProjectTitle", { name: deleteTarget.title }) : batchScope === "sessions" ? t("archives.deleteSelectedTitle") : t("deleteSession.title");
|
|
421
|
+
const deleteDialogDescription = deleteTarget === null ? void 0 : batchScope === "all" ? t("archives.deleteAllDesc", { n: deleteTarget.count }) : batchScope === "ungrouped" ? t("archives.deleteUngroupedDesc", { n: deleteTarget.count }) : batchScope === "workspace" ? t("archives.deleteProjectDesc", { name: deleteTarget.title, n: deleteTarget.count }) : batchScope === "sessions" ? t("archives.deleteSelectedDesc", { n: deleteTarget.count }) : t("deleteSession.desc", { name: displayTitle(deleteTarget.session, t) });
|
|
422
|
+
const deleteConfirmLabel = batchScope === "all" ? t("archives.deleteAll") : batchScope === "ungrouped" ? t("archives.deleteUngroupedConfirm") : batchScope === "workspace" ? t("archives.deleteProjectConfirm") : batchScope === "sessions" ? t("archives.deleteSelectedConfirm") : t("deleteSession.title");
|
|
423
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
424
|
+
className: "dsham_settings",
|
|
425
|
+
"aria-label": t("archives.title"),
|
|
426
|
+
children: [(0, react_jsx_runtime.jsx)("style", { children: ARCHIVE_SETTINGS_CSS + ARCHIVE_SETTINGS_BATCH_CSS + ARCHIVE_SETTINGS_SELECTION_CSS }), (0, react_jsx_runtime.jsxs)("header", {
|
|
427
|
+
className: "dsham_settingsHeader",
|
|
428
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
429
|
+
children: [(0, react_jsx_runtime.jsx)("h2", { children: t("archives.title") }), (0, react_jsx_runtime.jsx)("p", { className: "dsham_settingsIntro", children: t("archives.description") })]
|
|
430
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
431
|
+
className: "dsham_settingsHeaderActions",
|
|
432
|
+
children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsRestoreAll", disabled: busy || allBatchSessionIds.length === 0, onClick: () => onBatchUnarchive(allBatchTarget), children: t("archives.restoreAll") }), (0, react_jsx_runtime.jsxs)("button", { type: "button", className: "dsham_settingsDanger", disabled: busy || allBatchSessionIds.length === 0, onClick: () => setDeleteTarget({ kind: "batch", target: allBatchTarget, title: t("archives.allProjects"), count: allBatchSessionIds.length }), children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}), t("archives.deleteAll")] })]
|
|
433
|
+
})]
|
|
434
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
435
|
+
className: "dsham_settingsToolbar",
|
|
436
|
+
children: [(0, react_jsx_runtime.jsxs)("label", {
|
|
437
|
+
className: "dsham_settingsSearch",
|
|
438
|
+
children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, {}), (0, react_jsx_runtime.jsx)("input", { type: "search", value: query, onChange: (event) => setQuery(event.target.value), placeholder: t("archives.searchPlaceholder"), "aria-label": t("archives.searchPlaceholder") })]
|
|
439
|
+
}), (0, react_jsx_runtime.jsx)(ArchiveProjectSelect, { id: "dsham-sort-filter", value: sortBy, options: [{ value: "updated", label: t("archives.sortUpdated") }, { value: "created", label: t("archives.sortCreated") }, { value: "alphabetical", label: t("archives.sortAlphabetical") }], onChange: setSortBy, "aria-label": t("archives.sortBy") }), (0, react_jsx_runtime.jsx)(ArchiveProjectSelect, { id: "dsham-project-filter", value: project, options: [{ value: "all", label: t("archives.allProjects") }, ...sortedGroups.map((group) => ({ value: group.key, label: group.title }))], onChange: setProject, "aria-label": t("archives.projectFilter") })]
|
|
440
|
+
}), groups.length > 0 && (0, react_jsx_runtime.jsxs)("div", {
|
|
441
|
+
className: "dsham_settingsSelection",
|
|
442
|
+
children: [(0, react_jsx_runtime.jsxs)("label", {
|
|
443
|
+
className: "dsham_settingsSelectionToggle",
|
|
444
|
+
children: [(0, react_jsx_runtime.jsx)(ArchiveSelectionCheckbox, { checked: allVisibleSelected, indeterminate: selectedVisibleCount > 0 && !allVisibleSelected, disabled: busy || visibleSessionIds.length === 0, label: t("archives.selectAllFiltered"), onChange: (event) => toggleVisibleSelection(event.target.checked) }), t("archives.selectAllFiltered")]
|
|
445
|
+
}), (0, react_jsx_runtime.jsx)("span", { className: "dsham_settingsSelectionCount", children: t("archives.selectedCount", { n: selectedSessionIds.length }) }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction", disabled: busy || selectedSessionIds.length === 0, onClick: onSelectedUnarchive, children: t("archives.restoreSelected") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction dsham_settingsSelectionDelete", disabled: busy || selectedSessionIds.length === 0, onClick: () => setDeleteTarget({ kind: "batch", target: { scope: "sessions", sessionIds: selectedSessionIds }, count: selectedSessionIds.length }), children: t("archives.deleteSelected") })]
|
|
446
|
+
}), groups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.empty") }) : filteredGroups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.emptyFiltered") }) : filteredGroups.map((group) => {
|
|
447
|
+
const target = archivedBatchTargetForGroup(group.key);
|
|
448
|
+
const count = deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, target).length;
|
|
449
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
450
|
+
className: "dsham_settingsGroup",
|
|
451
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
452
|
+
className: "dsham_settingsGroupHeading",
|
|
453
|
+
children: [(0, react_jsx_runtime.jsxs)("h3", { className: "dsham_settingsGroupTitle", children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, {}), group.title] }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsGroupMeta", children: [(0, react_jsx_runtime.jsx)("span", { className: "dsham_settingsCount", children: t("archives.sessionCount", { n: count }) }), (0, react_jsx_runtime.jsx)(ArchivedGroupActions, { group, busy, onRestore: () => onBatchUnarchive(target), onDelete: () => setDeleteTarget({ kind: "batch", target, title: group.title, count }), t })] })]
|
|
454
|
+
}), (0, react_jsx_runtime.jsx)("div", {
|
|
455
|
+
className: "dsham_settingsList",
|
|
456
|
+
children: group.sessions.map((session) => (0, react_jsx_runtime.jsxs)("article", {
|
|
457
|
+
className: "dsham_settingsRow",
|
|
458
|
+
children: [(0, react_jsx_runtime.jsx)(ArchiveSelectionCheckbox, { checked: selectedSessionIdSet.has(session.id), disabled: busy, label: t("archives.selectSession", { name: displayTitle(session, t) }), onChange: (event) => toggleSessionSelection(session.id, event.target.checked) }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsContent", children: [(0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsTitle", children: displayTitle(session, t) }), (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsMeta", children: archiveTimeLabel(session.updatedAt, t) })] }), (0, react_jsx_runtime.jsxs)("div", {
|
|
459
|
+
className: "dsham_settingsActions",
|
|
460
|
+
children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => onUnarchive(session.id), children: t("menu.unarchive") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsDelete", disabled: busy || unarchivingSessionIds.has(session.id), "aria-label": t("menu.deleteSession"), onClick: () => setDeleteTarget({ kind: "session", session }), children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}) })]
|
|
461
|
+
})]
|
|
462
|
+
}, session.id))
|
|
463
|
+
})]
|
|
464
|
+
}, group.key);
|
|
465
|
+
}), error !== null && (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsError", role: "alert", children: error }), notice !== null && (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsStatus", role: "status", children: notice }), (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
466
|
+
open: deleteTarget !== null,
|
|
467
|
+
onClose: closeDelete,
|
|
468
|
+
closeLabel: t("close"),
|
|
469
|
+
title: deleteDialogTitle,
|
|
470
|
+
...deleteDialogDescription === void 0 ? {} : { description: deleteDialogDescription },
|
|
471
|
+
footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", disabled: busy, onClick: closeDelete, children: t("cancel") }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", disabled: busy, onClick: confirmDelete, children: deleteConfirmLabel })] }),
|
|
472
|
+
children: busy && (0, react_jsx_runtime.jsx)("div", { role: "status", children: deleteTarget?.kind === "batch" ? t("archives.deleteBatchPending") : t("deleteSession.pending") })
|
|
473
|
+
})]
|
|
474
|
+
});
|
|
475
|
+
}
|
|
286
476
|
//#endregion
|
|
287
477
|
//#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
|
|
288
478
|
function r(e) {
|
|
@@ -413,7 +603,26 @@ window.__ModuleLoader__.load({
|
|
|
413
603
|
if (stray.length > 0) groups.push(buildGroup("", void 0, void 0, void 0, UNGROUPED_LABEL, ungroupedOrder === void 0 ? stray : orderedUngrouped(stray, ungroupedOrder), ungroupedOrder === void 0 ? "recency" : "account"));
|
|
414
604
|
return groups;
|
|
415
605
|
}
|
|
416
|
-
|
|
606
|
+
/** 只把侧栏已支持的待处理交互类型交给行渲染,避免未知类型触发断言。 */
|
|
607
|
+
function visiblePendingKind(kind) {
|
|
608
|
+
switch (kind) {
|
|
609
|
+
case "approval":
|
|
610
|
+
case "plan-review":
|
|
611
|
+
case "question": return kind;
|
|
612
|
+
default: return;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function pendingInteractionForSession(session, pendingInteractions) {
|
|
616
|
+
const entry = pendingInteractions.get(session.id);
|
|
617
|
+
const kind = entry === void 0 ? session.pendingInteraction : entry.kind;
|
|
618
|
+
return visiblePendingKind(kind);
|
|
619
|
+
}
|
|
620
|
+
const EMPTY_PENDING_INTERACTIONS = /* @__PURE__ */ new Map();
|
|
621
|
+
function useEmptySessionPendingInteraction(selector) {
|
|
622
|
+
return selector(EMPTY_PENDING_INTERACTIONS);
|
|
623
|
+
}
|
|
624
|
+
function sessionNode(s, descendants, archived, pendingInteractions) {
|
|
625
|
+
const pendingInteraction = pendingInteractionForSession(s, pendingInteractions);
|
|
417
626
|
return {
|
|
418
627
|
id: s.id,
|
|
419
628
|
title: sessionTitle(s),
|
|
@@ -423,7 +632,7 @@ window.__ModuleLoader__.load({
|
|
|
423
632
|
completed: s.completed === true,
|
|
424
633
|
updatedAt: s.updatedAt,
|
|
425
634
|
archived: archived.has(s.id),
|
|
426
|
-
...
|
|
635
|
+
...pendingInteraction === void 0 ? {} : { pendingInteraction }
|
|
427
636
|
};
|
|
428
637
|
}
|
|
429
638
|
/**
|
|
@@ -438,10 +647,11 @@ window.__ModuleLoader__.load({
|
|
|
438
647
|
* @param list - sessions list snapshot (`current` feeds containsCurrent).
|
|
439
648
|
* @param workspaces - real workspaces in stable Host order.
|
|
440
649
|
* @param archivedSessionIds - registry-global archive set.
|
|
650
|
+
* @param pendingInteractions - pending UI interactions by Session.
|
|
441
651
|
* @param view - local expansion arrays and the show-archived toggle.
|
|
442
652
|
* @returns group sections in render order.
|
|
443
653
|
*/
|
|
444
|
-
function deriveGroups(list, workspaces, archivedSessionIds, view) {
|
|
654
|
+
function deriveGroups(list, workspaces, archivedSessionIds, pendingInteractions, view) {
|
|
445
655
|
const archived = new Set(archivedSessionIds);
|
|
446
656
|
const expandedGroups = new Set(view.expandedGroups);
|
|
447
657
|
const descendants = indexSubagentDescendants(list.byId);
|
|
@@ -458,7 +668,7 @@ window.__ModuleLoader__.load({
|
|
|
458
668
|
sessionCount: g.sessions.length,
|
|
459
669
|
expanded,
|
|
460
670
|
containsCurrent: g.key === currentGroup,
|
|
461
|
-
sessions: expanded ? g.sessions.map((session) => sessionNode(session, descendants, archived)) : []
|
|
671
|
+
sessions: expanded ? g.sessions.map((session) => sessionNode(session, descendants, archived, pendingInteractions)) : []
|
|
462
672
|
});
|
|
463
673
|
}
|
|
464
674
|
return groups;
|
|
@@ -472,10 +682,11 @@ window.__ModuleLoader__.load({
|
|
|
472
682
|
* (see {@link deriveSearchResults}).
|
|
473
683
|
* @param list - sessions list snapshot.
|
|
474
684
|
* @param archivedSessionIds - registry-global archive set.
|
|
685
|
+
* @param pendingInteractions - pending UI interactions by Session.
|
|
475
686
|
* @param showArchived - "show archived" view toggle.
|
|
476
687
|
* @returns flat rows in render order.
|
|
477
688
|
*/
|
|
478
|
-
function deriveFlat(list, archivedSessionIds, showArchived) {
|
|
689
|
+
function deriveFlat(list, archivedSessionIds, pendingInteractions, showArchived) {
|
|
479
690
|
const archived = new Set(archivedSessionIds);
|
|
480
691
|
const descendants = indexSubagentDescendants(list.byId);
|
|
481
692
|
const rows = [];
|
|
@@ -485,7 +696,7 @@ window.__ModuleLoader__.load({
|
|
|
485
696
|
rows.push(s);
|
|
486
697
|
}
|
|
487
698
|
rows.sort(byRecency);
|
|
488
|
-
return rows.map((session) => sessionNode(session, descendants, archived));
|
|
699
|
+
return rows.map((session) => sessionNode(session, descendants, archived, pendingInteractions));
|
|
489
700
|
}
|
|
490
701
|
/**
|
|
491
702
|
* Merge immediate title/Workspace substring matches with ranked Host content
|
|
@@ -496,12 +707,13 @@ window.__ModuleLoader__.load({
|
|
|
496
707
|
* @param workspaces - Workspace membership and display labels.
|
|
497
708
|
* @param query - caller text; surrounding whitespace is ignored.
|
|
498
709
|
* @param archivedSessionIds - registry-global archive set.
|
|
710
|
+
* @param pendingInteractions - pending UI interactions by Session.
|
|
499
711
|
* @param content - ranked Host content-search page.
|
|
500
712
|
* @param limit - protocol-owned maximum merged row count.
|
|
501
713
|
* @param showArchived - "show archived" view toggle.
|
|
502
714
|
* @returns bounded deduplicated flat rows and a refine-query hint bit.
|
|
503
715
|
*/
|
|
504
|
-
function deriveSearchResults(list, workspaces, query, archivedSessionIds, content, limit, showArchived) {
|
|
716
|
+
function deriveSearchResults(list, workspaces, query, archivedSessionIds, pendingInteractions, content, limit, showArchived) {
|
|
505
717
|
const q = query.trim().toLowerCase();
|
|
506
718
|
if (q === "") return {
|
|
507
719
|
items: [],
|
|
@@ -536,6 +748,7 @@ window.__ModuleLoader__.load({
|
|
|
536
748
|
return {
|
|
537
749
|
items: ordered.slice(0, limit).map((summary) => {
|
|
538
750
|
const match = contentBySession.get(summary.id);
|
|
751
|
+
const pendingInteraction = pendingInteractionForSession(summary, pendingInteractions);
|
|
539
752
|
return {
|
|
540
753
|
id: summary.id,
|
|
541
754
|
title: sessionTitle(summary),
|
|
@@ -543,7 +756,7 @@ window.__ModuleLoader__.load({
|
|
|
543
756
|
running: summary.running,
|
|
544
757
|
runningSubagentCount: descendants.get(summary.id)?.runningCount ?? 0,
|
|
545
758
|
archived: archived.has(summary.id),
|
|
546
|
-
...
|
|
759
|
+
...pendingInteraction === void 0 ? {} : { pendingInteraction },
|
|
547
760
|
completed: summary.completed === true,
|
|
548
761
|
...match === void 0 ? {} : { snippet: match.snippet }
|
|
549
762
|
};
|
|
@@ -1523,8 +1736,9 @@ window.__ModuleLoader__.load({
|
|
|
1523
1736
|
return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
|
|
1524
1737
|
}
|
|
1525
1738
|
/** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */
|
|
1526
|
-
function SessionTree({ useSessions, startSession, open, forkSession, workspaces, archivedSessionIds, showArchived, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, onSessionUnarchive, onSessionDelete, insertWorkspaceBefore, insertSessionBefore, orderBy, groupExpansion, setGroupExpanded, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
|
|
1739
|
+
function SessionTree({ useSessions, useSessionPendingInteraction, startSession, open, forkSession, workspaces, archivedSessionIds, showArchived, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, onSessionUnarchive, onSessionDelete, insertWorkspaceBefore, insertSessionBefore, orderBy, groupExpansion, setGroupExpanded, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
|
|
1527
1740
|
const list = useSessions((s) => s);
|
|
1741
|
+
const pendingInteractions = useSessionPendingInteraction((s) => s);
|
|
1528
1742
|
const current = list.current;
|
|
1529
1743
|
const [expandedSessionGroups, setExpandedSessionGroups] = (0, react.useState)([]);
|
|
1530
1744
|
const [drag, setDrag] = (0, react.useState)(null);
|
|
@@ -1591,7 +1805,7 @@ window.__ModuleLoader__.load({
|
|
|
1591
1805
|
});
|
|
1592
1806
|
}, [sessionOrderByAccount, workspaces]);
|
|
1593
1807
|
const orderedUngroupedSessionIds = (0, react.useMemo)(() => reconciledSessionOrder(ungroupedSessionIds, sessionOrderByAccount[""]), [sessionOrderByAccount, ungroupedSessionIds]);
|
|
1594
|
-
const groups = (0, react.useMemo)(() => deriveGroups(list, orderedWorkspaces, archivedSessionIds, {
|
|
1808
|
+
const groups = (0, react.useMemo)(() => deriveGroups(list, orderedWorkspaces, archivedSessionIds, pendingInteractions, {
|
|
1595
1809
|
expandedGroups,
|
|
1596
1810
|
showArchived,
|
|
1597
1811
|
...sessionOrderByAccount[""] === void 0 ? {} : { ungroupedOrder: sessionOrderByAccount[""] }
|
|
@@ -1599,6 +1813,7 @@ window.__ModuleLoader__.load({
|
|
|
1599
1813
|
list,
|
|
1600
1814
|
orderedWorkspaces,
|
|
1601
1815
|
archivedSessionIds,
|
|
1816
|
+
pendingInteractions,
|
|
1602
1817
|
showArchived,
|
|
1603
1818
|
expandedGroups,
|
|
1604
1819
|
sessionOrderByAccount
|
|
@@ -1800,9 +2015,15 @@ window.__ModuleLoader__.load({
|
|
|
1800
2015
|
});
|
|
1801
2016
|
}
|
|
1802
2017
|
/** The flat "In one list" body: every session is one draggable top-level row. */
|
|
1803
|
-
function FlatList({ useSessions, open, forkSession, onSessionRename, onSessionArchive, onSessionUnarchive, onSessionDelete, archivedSessionIds, showArchived, orderBy, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
|
|
2018
|
+
function FlatList({ useSessions, useSessionPendingInteraction, open, forkSession, onSessionRename, onSessionArchive, onSessionUnarchive, onSessionDelete, archivedSessionIds, showArchived, orderBy, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
|
|
1804
2019
|
const list = useSessions((s) => s);
|
|
1805
|
-
const
|
|
2020
|
+
const pendingInteractions = useSessionPendingInteraction((s) => s);
|
|
2021
|
+
const baseRows = (0, react.useMemo)(() => deriveFlat(list, archivedSessionIds, pendingInteractions, showArchived), [
|
|
2022
|
+
list,
|
|
2023
|
+
archivedSessionIds,
|
|
2024
|
+
pendingInteractions,
|
|
2025
|
+
showArchived
|
|
2026
|
+
]);
|
|
1806
2027
|
const sessionIds = (0, react.useMemo)(() => baseRows.map((row) => row.id), [baseRows]);
|
|
1807
2028
|
const previousOrderBy = (0, react.useRef)(orderBy);
|
|
1808
2029
|
(0, react.useEffect)(() => {
|
|
@@ -1924,19 +2145,21 @@ window.__ModuleLoader__.load({
|
|
|
1924
2145
|
});
|
|
1925
2146
|
}
|
|
1926
2147
|
/** Flat search body: local metadata matches plus the current Host result page. */
|
|
1927
|
-
function SearchResults({ useSessions, open, workspaces, archivedSessionIds, showArchived, query, remote, resultLimit, t }) {
|
|
2148
|
+
function SearchResults({ useSessions, useSessionPendingInteraction, open, workspaces, archivedSessionIds, showArchived, query, remote, resultLimit, t }) {
|
|
1928
2149
|
const list = useSessions((s) => s);
|
|
2150
|
+
const pendingInteractions = useSessionPendingInteraction((s) => s);
|
|
1929
2151
|
const currentRemote = remote.query === query ? remote : {
|
|
1930
2152
|
query,
|
|
1931
2153
|
status: "loading",
|
|
1932
2154
|
items: [],
|
|
1933
2155
|
hasMore: false
|
|
1934
2156
|
};
|
|
1935
|
-
const results = (0, react.useMemo)(() => deriveSearchResults(list, workspaces, query, archivedSessionIds, currentRemote, resultLimit, showArchived), [
|
|
2157
|
+
const results = (0, react.useMemo)(() => deriveSearchResults(list, workspaces, query, archivedSessionIds, pendingInteractions, currentRemote, resultLimit, showArchived), [
|
|
1936
2158
|
list,
|
|
1937
2159
|
workspaces,
|
|
1938
2160
|
query,
|
|
1939
2161
|
archivedSessionIds,
|
|
2162
|
+
pendingInteractions,
|
|
1940
2163
|
currentRemote,
|
|
1941
2164
|
resultLimit,
|
|
1942
2165
|
showArchived
|
|
@@ -1986,7 +2209,8 @@ window.__ModuleLoader__.load({
|
|
|
1986
2209
|
* @param props - composed slot props (shell owner share + store + injected actions).
|
|
1987
2210
|
* @returns the region element tree.
|
|
1988
2211
|
*/
|
|
1989
|
-
function WorkspaceBrowser({ wide, expandSidebar, useSessions, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, unarchiveSession, deleteSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, renderSlot, t }) {
|
|
2212
|
+
function WorkspaceBrowser({ wide, expandSidebar, useSessions, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, unarchiveSession, deleteSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, renderSlot, t }) {
|
|
2213
|
+
const useEffectiveSessionPendingInteraction = useSessionPendingInteraction ?? useEmptySessionPendingInteraction;
|
|
1990
2214
|
const workspaces = useWorkspaces((state) => state.items);
|
|
1991
2215
|
const workspacePhase = useWorkspaces((state) => state.phase);
|
|
1992
2216
|
const archivedSessionIds = useWorkspaces((state) => state.archivedSessionIds);
|
|
@@ -2381,6 +2605,7 @@ window.__ModuleLoader__.load({
|
|
|
2381
2605
|
className: WorkspaceBrowser_module_css_default.listArea,
|
|
2382
2606
|
children: wide && (normalizedQuery !== "" ? (0, react_jsx_runtime.jsx)(SearchResults, {
|
|
2383
2607
|
useSessions,
|
|
2608
|
+
useSessionPendingInteraction: useEffectiveSessionPendingInteraction,
|
|
2384
2609
|
open: guardedOpen,
|
|
2385
2610
|
workspaces,
|
|
2386
2611
|
archivedSessionIds,
|
|
@@ -2391,6 +2616,7 @@ window.__ModuleLoader__.load({
|
|
|
2391
2616
|
t
|
|
2392
2617
|
}) : groupBy === "flat" ? (0, react_jsx_runtime.jsx)(FlatList, {
|
|
2393
2618
|
useSessions,
|
|
2619
|
+
useSessionPendingInteraction: useEffectiveSessionPendingInteraction,
|
|
2394
2620
|
open: guardedOpen,
|
|
2395
2621
|
forkSession,
|
|
2396
2622
|
onSessionRename,
|
|
@@ -2410,6 +2636,7 @@ window.__ModuleLoader__.load({
|
|
|
2410
2636
|
t
|
|
2411
2637
|
}) : (0, react_jsx_runtime.jsx)(SessionTree, {
|
|
2412
2638
|
useSessions,
|
|
2639
|
+
useSessionPendingInteraction: useEffectiveSessionPendingInteraction,
|
|
2413
2640
|
onSessionRename,
|
|
2414
2641
|
onSessionArchive,
|
|
2415
2642
|
onSessionUnarchive,
|
|
@@ -2622,6 +2849,7 @@ window.__ModuleLoader__.load({
|
|
|
2622
2849
|
//#region dsh-archive-manager: settings section
|
|
2623
2850
|
const ARCHIVE_SETTINGS_CSS = ".dsham_settings{box-sizing:border-box;width:min(100%,760px);margin:0 auto;padding:0 0 32px;color:var(--dsw-alias-label-primary)}.dsham_settingsHeader{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:12px}.dsham_settings h2{margin:0;font-size:20px;font-weight:650;letter-spacing:-.2px;line-height:28px}.dsham_settingsIntro{margin:4px 0 0;max-width:42em;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.dsham_settingsDanger{display:inline-flex;align-items:center;gap:6px;min-height:32px;padding:0 12px;color:var(--dsw-alias-state-error-primary);background:transparent;border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsDanger:hover{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 20%,transparent)}.dsham_settingsToolbar{display:flex;gap:8px;margin-bottom:16px}.dsham_settingsSearch{display:flex;align-items:center;gap:8px;min-width:0;flex:1;height:32px;padding:0 12px;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-button-elevated-fill));border:1px solid var(--dsw-alias-border-l2);border-radius:8px}.dsham_settingsSearch:focus-within{border-color:var(--dsw-alias-label-tertiary)}.dsham_settingsSearch input{width:100%;min-width:0;padding:0;color:var(--dsw-alias-label-primary);background:transparent;border:0;outline:0;font:inherit;font-size:12px}.dsham_settingsSearch input::placeholder{color:var(--dsw-alias-label-tertiary)}.dsham_settingsFilter{position:relative;min-width:168px;flex:none}.dsham_selectTrigger{box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;min-height:32px;padding:0 10px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-button-elevated-fill));border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;line-height:20px;text-align:left}.dsham_selectTrigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsham_selectTrigger:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsham_selectTrigger[aria-expanded='true']{border-color:var(--dsw-alias-state-success-primary)}.dsham_selectValue{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dsham_selectCaret{flex:none;width:12px;height:12px;color:var(--dsw-alias-label-tertiary)}.dsham_selectMenu{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:30;box-sizing:border-box;min-width:100%;max-height:280px;overflow:auto;padding:4px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2));box-shadow:var(--dsw-shadow-lv3)}.dsham_selectOption{box-sizing:border-box;display:flex;align-items:center;width:100%;min-height:32px;padding:0 10px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;text-align:left;cursor:pointer}.dsham_selectOption:hover,.dsham_selectOption[data-active='true']{background:var(--dsw-alias-interactive-bg-hover)}.dsham_selectOption[aria-selected='true']{color:var(--dsw-alias-state-success-primary)}.dsham_settingsGroup{margin:0 0 20px}.dsham_settingsGroupHeading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 14px}.dsham_settingsGroupTitle{display:flex;align-items:center;gap:8px;min-width:0;margin:0;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.dsham_settingsGroupTitle svg{flex:none;color:var(--dsw-alias-label-secondary)}.dsham_settingsCount{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsham_settingsList{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-button-elevated-fill))}.dsham_settingsRow{display:flex;align-items:center;gap:12px;min-height:60px;padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dsham_settingsRow:last-child{border-bottom:0}.dsham_settingsContent{min-width:0;flex:1}.dsham_settingsTitle{overflow:hidden;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:18px;text-overflow:ellipsis;white-space:nowrap}.dsham_settingsMeta{margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:16px}.dsham_settingsActions{display:flex;align-items:center;gap:8px}.dsham_settingsAction{min-height:32px;padding:0 12px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsAction:hover{filter:brightness(1.12)}.dsham_settingsDelete{display:flex;align-items:center;justify-content:center;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:transparent;border:0;border-radius:8px;cursor:pointer}.dsham_settingsDelete:hover{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsEmpty{padding:28px 8px;color:var(--dsw-alias-label-secondary);text-align:center}.dsham_settingsError{margin-top:10px;color:var(--dsw-alias-state-error-primary);font-size:12px}@media(max-width:720px){.dsham_settings{width:100%;margin:28px auto 48px;padding:0 16px}.dsham_settingsHeader{margin-bottom:28px}.dsham_settingsToolbar{flex-wrap:wrap;margin-bottom:28px}.dsham_settingsSearch{flex-basis:100%}.dsham_settingsFilter{flex:1;min-width:0}.dsham_settingsGroup{margin-bottom:32px}.dsham_settingsRow{padding:10px 12px}.dsham_settingsActions{gap:4px}}";
|
|
2624
2851
|
const ARCHIVE_SETTINGS_BATCH_CSS = ".dsham_settingsHeaderActions,.dsham_settingsGroupMeta{display:flex;align-items:center;gap:8px;flex:none}.dsham_settingsRestoreAll{display:inline-flex;align-items:center;min-height:32px;padding:0 12px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsRestoreAll:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsRestoreAll:disabled,.dsham_settingsDanger:disabled,.dsham_settingsGroupMenu:disabled{cursor:not-allowed;opacity:.5}.dsham_settingsGroupMenu{display:flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;color:var(--dsw-alias-label-tertiary);background:transparent;border:0;border-radius:8px;cursor:pointer}.dsham_settingsGroupMenu:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsStatus{margin-top:10px;color:var(--dsw-alias-label-secondary);font-size:12px}@media(max-width:720px){.dsham_settingsHeader{flex-direction:column}.dsham_settingsHeaderActions{align-self:flex-end}}";
|
|
2852
|
+
const ARCHIVE_SETTINGS_SELECTION_CSS = ".dsham_settingsSelection{display:flex;align-items:center;gap:10px;min-height:40px;margin:-8px 0 16px;padding:4px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-button-elevated-fill))}.dsham_settingsSelectionToggle{display:inline-flex;align-items:center;gap:8px;min-width:0;color:var(--dsw-alias-label-primary);font-size:13px;cursor:pointer}.dsham_settingsCheckbox{width:16px;height:16px;margin:0;accent-color:#3b82f6}.dsham_settingsSelectionCount{margin-right:auto;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsham_settingsSelectionAction{min-height:28px;padding:0 10px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;cursor:pointer;font:inherit;font-size:12px;font-weight:500}.dsham_settingsSelectionAction:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsSelectionDelete{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.dsham_settingsSelectionAction:disabled{cursor:not-allowed;opacity:.5}@media(max-width:720px){.dsham_settingsSelection{flex-wrap:wrap}.dsham_settingsSelectionCount{margin-right:0}.dsham_settingsSelectionAction{margin-left:auto}.dsham_settingsSelectionAction+.dsham_settingsSelectionAction{margin-left:-2px}}";
|
|
2625
2853
|
const ARCHIVE_SETTINGS_LAYOUT_OVERRIDE = ".dsham_settings{margin:0 auto!important}@media(max-width:720px){.dsham_settings{margin:0 auto!important}}";
|
|
2626
2854
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify("dsh-archive-manager/ArchiveSettings.layout.css") + "]") === null) {
|
|
2627
2855
|
const tag = document.createElement("style");
|
|
@@ -2762,6 +2990,22 @@ window.__ModuleLoader__.load({
|
|
|
2762
2990
|
}) : null]
|
|
2763
2991
|
});
|
|
2764
2992
|
}
|
|
2993
|
+
/** 原生 checkbox 保留浏览器的正确键盘语义,并显式同步三态显示。 */
|
|
2994
|
+
function ArchiveSelectionCheckbox({ checked, indeterminate = false, disabled = false, label, onChange }) {
|
|
2995
|
+
const inputRef = (0, react.useRef)(null);
|
|
2996
|
+
(0, react.useEffect)(() => {
|
|
2997
|
+
if (inputRef.current !== null) inputRef.current.indeterminate = indeterminate;
|
|
2998
|
+
}, [indeterminate]);
|
|
2999
|
+
return (0, react_jsx_runtime.jsx)("input", {
|
|
3000
|
+
ref: inputRef,
|
|
3001
|
+
type: "checkbox",
|
|
3002
|
+
className: "dsham_settingsCheckbox",
|
|
3003
|
+
checked,
|
|
3004
|
+
disabled,
|
|
3005
|
+
"aria-label": label,
|
|
3006
|
+
onChange
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
2765
3009
|
/** 项目标题右侧的批量恢复/删除菜单,复用宿主菜单组件的键盘和焦点行为。 */
|
|
2766
3010
|
function ArchivedGroupActions({ group, busy, onRestore, onDelete, t }) {
|
|
2767
3011
|
const [open, setOpen] = (0, react.useState)(false);
|
|
@@ -2805,6 +3049,10 @@ window.__ModuleLoader__.load({
|
|
|
2805
3049
|
function deriveArchivedBatchIds(archivedSessionIds, items, target) {
|
|
2806
3050
|
const ids = [...new Set(archivedSessionIds ?? [])];
|
|
2807
3051
|
if (target.scope === "all") return ids;
|
|
3052
|
+
if (target.scope === "sessions") {
|
|
3053
|
+
const selected = new Set(target.sessionIds ?? []);
|
|
3054
|
+
return ids.filter((id) => selected.has(id));
|
|
3055
|
+
}
|
|
2808
3056
|
if (target.scope === "workspace") {
|
|
2809
3057
|
const accounted = new Set(items.find((workspace) => workspace.workspaceId === target.workspaceId)?.sessionIds ?? []);
|
|
2810
3058
|
return ids.filter((id) => accounted.has(id));
|
|
@@ -2812,6 +3060,24 @@ window.__ModuleLoader__.load({
|
|
|
2812
3060
|
const accounted = new Set(items.flatMap((workspace) => workspace.sessionIds));
|
|
2813
3061
|
return ids.filter((id) => !accounted.has(id));
|
|
2814
3062
|
}
|
|
3063
|
+
/** 当前筛选结果中的可选会话;按列表显示顺序去重。 */
|
|
3064
|
+
function archivedSessionIdsInGroups(groups) {
|
|
3065
|
+
return [...new Set(groups.flatMap((group) => group.sessions.map((session) => session.id)))];
|
|
3066
|
+
}
|
|
3067
|
+
/** 只保留仍属于权威归档集合的选中项,避免对已变化的列表执行旧操作。 */
|
|
3068
|
+
function pruneArchivedSelection(selectedSessionIds, archivedSessionIds) {
|
|
3069
|
+
const archived = new Set(archivedSessionIds);
|
|
3070
|
+
return [...new Set(selectedSessionIds)].filter((id) => archived.has(id));
|
|
3071
|
+
}
|
|
3072
|
+
/** 向当前选择中加入或移除一组可见会话,供“全选筛选结果”复用。 */
|
|
3073
|
+
function toggleArchivedSelection(selectedSessionIds, sessionIds, checked) {
|
|
3074
|
+
const selected = new Set(selectedSessionIds);
|
|
3075
|
+
for (const sessionId of sessionIds) {
|
|
3076
|
+
if (checked) selected.add(sessionId);
|
|
3077
|
+
else selected.delete(sessionId);
|
|
3078
|
+
}
|
|
3079
|
+
return [...selected];
|
|
3080
|
+
}
|
|
2815
3081
|
/** 把批量删除结果转换为准确区分“已删除”与“陈旧标记已清理”的反馈。 */
|
|
2816
3082
|
function archivedDeleteFeedback(result, t) {
|
|
2817
3083
|
const deleted = result.deletedSessionIds.length;
|
|
@@ -2898,6 +3164,7 @@ window.__ModuleLoader__.load({
|
|
|
2898
3164
|
const [createdAtById, setCreatedAtById] = (0, react.useState)({});
|
|
2899
3165
|
const [unarchivingSessionIds, setUnarchivingSessionIds] = (0, react.useState)(() => /* @__PURE__ */ new Set());
|
|
2900
3166
|
const unarchivingSessionIdsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
3167
|
+
const [selectedSessionIds, setSelectedSessionIds] = (0, react.useState)([]);
|
|
2901
3168
|
const groups = (0, react.useMemo)(() => deriveArchivedGroups(sessions.byId, workspaceState.items, workspaceState.archivedSessionIds, t("group.ungrouped")), [sessions.byId, workspaceState, t]);
|
|
2902
3169
|
const sortedGroups = (0, react.useMemo)(() => sortArchivedGroups(groups, sortBy, createdAtById, t), [groups, sortBy, createdAtById, t]);
|
|
2903
3170
|
(0, react.useEffect)(() => {
|
|
@@ -2926,8 +3193,21 @@ window.__ModuleLoader__.load({
|
|
|
2926
3193
|
sessions: group.sessions.filter((session) => normalizedQuery === "" || displayTitle(session, t).toLocaleLowerCase().includes(normalizedQuery))
|
|
2927
3194
|
})).filter((group) => group.sessions.length > 0);
|
|
2928
3195
|
}, [sortedGroups, project, query, t]);
|
|
3196
|
+
const visibleSessionIds = (0, react.useMemo)(() => archivedSessionIdsInGroups(filteredGroups), [filteredGroups]);
|
|
3197
|
+
const selectedSessionIdSet = (0, react.useMemo)(() => new Set(selectedSessionIds), [selectedSessionIds]);
|
|
3198
|
+
const selectedVisibleCount = visibleSessionIds.filter((sessionId) => selectedSessionIdSet.has(sessionId)).length;
|
|
3199
|
+
const allVisibleSelected = visibleSessionIds.length > 0 && selectedVisibleCount === visibleSessionIds.length;
|
|
2929
3200
|
const allBatchTarget = { scope: "all" };
|
|
2930
3201
|
const allBatchSessionIds = (0, react.useMemo)(() => deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, allBatchTarget), [workspaceState.archivedSessionIds, workspaceState.items]);
|
|
3202
|
+
(0, react.useEffect)(() => {
|
|
3203
|
+
setSelectedSessionIds((current) => pruneArchivedSelection(current, workspaceState.archivedSessionIds));
|
|
3204
|
+
}, [workspaceState.archivedSessionIds]);
|
|
3205
|
+
const toggleSessionSelection = (sessionId, checked) => {
|
|
3206
|
+
setSelectedSessionIds((current) => toggleArchivedSelection(current, [sessionId], checked));
|
|
3207
|
+
};
|
|
3208
|
+
const toggleVisibleSelection = (checked) => {
|
|
3209
|
+
setSelectedSessionIds((current) => toggleArchivedSelection(current, visibleSessionIds, checked));
|
|
3210
|
+
};
|
|
2931
3211
|
const onUnarchive = (sessionId) => {
|
|
2932
3212
|
if (busy || unarchivingSessionIdsRef.current.has(sessionId)) return;
|
|
2933
3213
|
unarchivingSessionIdsRef.current.add(sessionId);
|
|
@@ -2949,12 +3229,21 @@ window.__ModuleLoader__.load({
|
|
|
2949
3229
|
try {
|
|
2950
3230
|
const result = await unarchiveSessions(target);
|
|
2951
3231
|
setNotice(t("archives.restoreSuccess", { n: result.unarchivedSessionIds.length }));
|
|
3232
|
+
return result;
|
|
2952
3233
|
} catch (reason) {
|
|
2953
3234
|
setError(t("archives.restoreBatchFailed", { detail: reason instanceof Error ? reason.message : String(reason) }));
|
|
2954
3235
|
} finally {
|
|
2955
3236
|
setBusy(false);
|
|
2956
3237
|
}
|
|
2957
3238
|
};
|
|
3239
|
+
const onSelectedUnarchive = async () => {
|
|
3240
|
+
if (selectedSessionIds.length === 0) return;
|
|
3241
|
+
const result = await onBatchUnarchive({ scope: "sessions", sessionIds: selectedSessionIds });
|
|
3242
|
+
if (result !== void 0) {
|
|
3243
|
+
const restored = new Set(result.unarchivedSessionIds);
|
|
3244
|
+
setSelectedSessionIds((current) => current.filter((sessionId) => !restored.has(sessionId)));
|
|
3245
|
+
}
|
|
3246
|
+
};
|
|
2958
3247
|
const closeDelete = () => {
|
|
2959
3248
|
if (!busy) setDeleteTarget(null);
|
|
2960
3249
|
};
|
|
@@ -2981,6 +3270,10 @@ window.__ModuleLoader__.load({
|
|
|
2981
3270
|
const feedback = archivedDeleteFeedback(result, t);
|
|
2982
3271
|
if (feedback.kind === "error") setError(feedback.message);
|
|
2983
3272
|
else setNotice(feedback.message);
|
|
3273
|
+
if (deleteTarget.target.scope === "sessions") {
|
|
3274
|
+
const completed = new Set([...result.deletedSessionIds, ...result.skippedSessionIds]);
|
|
3275
|
+
setSelectedSessionIds((current) => current.filter((sessionId) => !completed.has(sessionId)));
|
|
3276
|
+
}
|
|
2984
3277
|
} else {
|
|
2985
3278
|
await deleteSession(deleteTarget.session.id);
|
|
2986
3279
|
}
|
|
@@ -2992,9 +3285,9 @@ window.__ModuleLoader__.load({
|
|
|
2992
3285
|
}
|
|
2993
3286
|
};
|
|
2994
3287
|
const batchScope = deleteTarget?.kind === "batch" ? deleteTarget.target.scope : null;
|
|
2995
|
-
const deleteDialogTitle = batchScope === "all" ? t("archives.deleteAllTitle") : batchScope === "ungrouped" ? t("archives.deleteUngroupedTitle") : batchScope === "workspace" ? t("archives.deleteProjectTitle", { name: deleteTarget.title }) : t("deleteSession.title");
|
|
2996
|
-
const deleteDialogDescription = deleteTarget === null ? void 0 : batchScope === "all" ? t("archives.deleteAllDesc", { n: deleteTarget.count }) : batchScope === "ungrouped" ? t("archives.deleteUngroupedDesc", { n: deleteTarget.count }) : batchScope === "workspace" ? t("archives.deleteProjectDesc", { name: deleteTarget.title, n: deleteTarget.count }) : t("deleteSession.desc", { name: displayTitle(deleteTarget.session, t) });
|
|
2997
|
-
const deleteConfirmLabel = batchScope === "all" ? t("archives.deleteAll") : batchScope === "ungrouped" ? t("archives.deleteUngroupedConfirm") : batchScope === "workspace" ? t("archives.deleteProjectConfirm") : t("deleteSession.title");
|
|
3288
|
+
const deleteDialogTitle = batchScope === "all" ? t("archives.deleteAllTitle") : batchScope === "ungrouped" ? t("archives.deleteUngroupedTitle") : batchScope === "workspace" ? t("archives.deleteProjectTitle", { name: deleteTarget.title }) : batchScope === "sessions" ? t("archives.deleteSelectedTitle") : t("deleteSession.title");
|
|
3289
|
+
const deleteDialogDescription = deleteTarget === null ? void 0 : batchScope === "all" ? t("archives.deleteAllDesc", { n: deleteTarget.count }) : batchScope === "ungrouped" ? t("archives.deleteUngroupedDesc", { n: deleteTarget.count }) : batchScope === "workspace" ? t("archives.deleteProjectDesc", { name: deleteTarget.title, n: deleteTarget.count }) : batchScope === "sessions" ? t("archives.deleteSelectedDesc", { n: deleteTarget.count }) : t("deleteSession.desc", { name: displayTitle(deleteTarget.session, t) });
|
|
3290
|
+
const deleteConfirmLabel = batchScope === "all" ? t("archives.deleteAll") : batchScope === "ungrouped" ? t("archives.deleteUngroupedConfirm") : batchScope === "workspace" ? t("archives.deleteProjectConfirm") : batchScope === "sessions" ? t("archives.deleteSelectedConfirm") : t("deleteSession.title");
|
|
2998
3291
|
return (0, react_jsx_runtime.jsxs)("section", {
|
|
2999
3292
|
className: "dsham_settings",
|
|
3000
3293
|
"aria-label": t("archives.title"),
|
|
@@ -3075,6 +3368,11 @@ window.__ModuleLoader__.load({
|
|
|
3075
3368
|
"archives.projectFilter": "按项目筛选",
|
|
3076
3369
|
"archives.allProjects": "所有项目",
|
|
3077
3370
|
"archives.sessionCount": "{n} 个聊天",
|
|
3371
|
+
"archives.selectAllFiltered": "全选当前筛选结果",
|
|
3372
|
+
"archives.selectSession": "选择会话“{name}”",
|
|
3373
|
+
"archives.selectedCount": "已选 {n} 个",
|
|
3374
|
+
"archives.restoreSelected": "恢复所选",
|
|
3375
|
+
"archives.deleteSelected": "删除所选",
|
|
3078
3376
|
"archives.timestamp": "{date},{time}",
|
|
3079
3377
|
"archives.restoreAll": "全部恢复",
|
|
3080
3378
|
"archives.restoreProject": "恢复该项目的全部聊天",
|
|
@@ -3088,6 +3386,9 @@ window.__ModuleLoader__.load({
|
|
|
3088
3386
|
"archives.deleteAll": "全部删除",
|
|
3089
3387
|
"archives.deleteAllTitle": "删除全部已归档聊天",
|
|
3090
3388
|
"archives.deleteAllDesc": "将永久删除全部 {n} 个已归档聊天及其子代理(含正在运行的)和记录,此操作不可恢复。",
|
|
3389
|
+
"archives.deleteSelectedTitle": "删除所选已归档聊天",
|
|
3390
|
+
"archives.deleteSelectedDesc": "将永久删除所选的 {n} 个已归档聊天及其子代理(含正在运行的)和记录。其他聊天不会受影响,此操作不可恢复。",
|
|
3391
|
+
"archives.deleteSelectedConfirm": "删除所选聊天",
|
|
3091
3392
|
"archives.deleteAllPending": "正在删除已归档聊天…",
|
|
3092
3393
|
"archives.deleteProjectTitle": "删除“{name}”中的已归档聊天",
|
|
3093
3394
|
"archives.deleteProjectDesc": "将永久删除“{name}”中的 {n} 个已归档聊天及其子代理和记录。项目目录和未归档聊天不会受影响,此操作不可恢复。",
|
|
@@ -3191,6 +3492,11 @@ window.__ModuleLoader__.load({
|
|
|
3191
3492
|
"archives.projectFilter": "Filter by project",
|
|
3192
3493
|
"archives.allProjects": "All projects",
|
|
3193
3494
|
"archives.sessionCount": "{n} chats",
|
|
3495
|
+
"archives.selectAllFiltered": "Select all results",
|
|
3496
|
+
"archives.selectSession": "Select chat {name}",
|
|
3497
|
+
"archives.selectedCount": "{n} selected",
|
|
3498
|
+
"archives.restoreSelected": "Restore selected",
|
|
3499
|
+
"archives.deleteSelected": "Delete selected",
|
|
3194
3500
|
"archives.timestamp": "{date}, {time}",
|
|
3195
3501
|
"archives.restoreAll": "Restore all",
|
|
3196
3502
|
"archives.restoreProject": "Restore all chats in this project",
|
|
@@ -3204,6 +3510,9 @@ window.__ModuleLoader__.load({
|
|
|
3204
3510
|
"archives.deleteAll": "Delete all",
|
|
3205
3511
|
"archives.deleteAllTitle": "Delete all archived chats",
|
|
3206
3512
|
"archives.deleteAllDesc": "This permanently deletes all {n} archived chats, their child agents (including any that are still running), and their records. This cannot be undone.",
|
|
3513
|
+
"archives.deleteSelectedTitle": "Delete selected archived chats",
|
|
3514
|
+
"archives.deleteSelectedDesc": "This permanently deletes the selected {n} archived chats, their child agents (including any that are still running), and their records. Other chats are not affected. This cannot be undone.",
|
|
3515
|
+
"archives.deleteSelectedConfirm": "Delete selected chats",
|
|
3207
3516
|
"archives.deleteAllPending": "Deleting archived chats…",
|
|
3208
3517
|
"archives.deleteProjectTitle": "Delete archived chats in {name}",
|
|
3209
3518
|
"archives.deleteProjectDesc": "This permanently deletes the {n} archived chats in {name}, their child agents, and their records. The project directory and unarchived chats are not affected. This cannot be undone.",
|
|
@@ -3482,11 +3791,20 @@ window.__ModuleLoader__.load({
|
|
|
3482
3791
|
});
|
|
3483
3792
|
const browserFlowSource = flowSource("sidebar.workspaces.directoryFlow");
|
|
3484
3793
|
const pickerFlowSource = flowSource("conversation.hero.workspace.directoryFlow");
|
|
3794
|
+
const refreshSessionList = async () => {
|
|
3795
|
+
if (typeof ctx.sessions.refresh !== "function") return;
|
|
3796
|
+
try {
|
|
3797
|
+
await ctx.sessions.refresh();
|
|
3798
|
+
} catch (error) {
|
|
3799
|
+
console.warn("archive-manager: restored archived sessions but session list refresh failed:", error);
|
|
3800
|
+
}
|
|
3801
|
+
};
|
|
3485
3802
|
const unarchiveSession = async (sessionId) => {
|
|
3486
3803
|
const registry = ctx.get("remote.workspaceRegistry");
|
|
3487
3804
|
if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
|
|
3488
3805
|
const result = await registry.unarchiveSession(sessionId);
|
|
3489
3806
|
if (!result.ok) throw new Error(result.error.message);
|
|
3807
|
+
await refreshSessionList();
|
|
3490
3808
|
};
|
|
3491
3809
|
const deleteSession = async (sessionId) => {
|
|
3492
3810
|
const registry = ctx.get("remote.workspaceRegistry");
|
|
@@ -3499,6 +3817,7 @@ window.__ModuleLoader__.load({
|
|
|
3499
3817
|
if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
|
|
3500
3818
|
const result = await registry.unarchiveSessions(target);
|
|
3501
3819
|
if (!result.ok) throw new Error(result.error.message);
|
|
3820
|
+
await refreshSessionList();
|
|
3502
3821
|
return result.value;
|
|
3503
3822
|
};
|
|
3504
3823
|
const deleteArchivedSessions = async (target) => {
|
|
@@ -3605,7 +3924,7 @@ window.__ModuleLoader__.load({
|
|
|
3605
3924
|
archivedSessionMetadata,
|
|
3606
3925
|
t: ctx.locale.bind(NS)
|
|
3607
3926
|
})
|
|
3608
|
-
},
|
|
3927
|
+
}, ArchivedSessionsSectionPrototype));
|
|
3609
3928
|
}
|
|
3610
3929
|
//#endregion
|
|
3611
3930
|
/** Pure derivation surface for dsh-archive-manager self-tests (no-op for the runtime). */
|
|
@@ -3620,6 +3939,9 @@ window.__ModuleLoader__.load({
|
|
|
3620
3939
|
deriveArchivedGroups,
|
|
3621
3940
|
sortArchivedGroups,
|
|
3622
3941
|
deriveArchivedBatchIds,
|
|
3942
|
+
archivedSessionIdsInGroups,
|
|
3943
|
+
pruneArchivedSelection,
|
|
3944
|
+
toggleArchivedSelection,
|
|
3623
3945
|
archivedDeleteFeedback,
|
|
3624
3946
|
createWorkspaceViewStore,
|
|
3625
3947
|
bindObservable,
|
package/lib/workspace.js
CHANGED
|
@@ -84,7 +84,8 @@ const archivedBatchTargetSchema = {
|
|
|
84
84
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("target must be an object");
|
|
85
85
|
if (value.scope === "all" || value.scope === "ungrouped") return value;
|
|
86
86
|
if (value.scope === "workspace" && typeof value.workspaceId === "string" && value.workspaceId.length > 0) return value;
|
|
87
|
-
|
|
87
|
+
if (value.scope === "sessions" && Array.isArray(value.sessionIds) && value.sessionIds.length > 0 && value.sessionIds.every((id) => typeof id === "string" && id.length > 0)) return value;
|
|
88
|
+
throw new TypeError("target.scope must be all, ungrouped, workspace with a non-empty workspaceId, or sessions with non-empty sessionIds");
|
|
88
89
|
}
|
|
89
90
|
};
|
|
90
91
|
const unarchivedBatchSchema = {
|
|
@@ -400,6 +401,10 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
|
|
|
400
401
|
const state = this.requireState();
|
|
401
402
|
const archivedSessionIds = [...new Set(state.archivedSessionIds)];
|
|
402
403
|
if (target.scope === "all") return archivedSessionIds;
|
|
404
|
+
if (target.scope === "sessions") {
|
|
405
|
+
const selected = new Set(target.sessionIds);
|
|
406
|
+
return archivedSessionIds.filter((id) => selected.has(id));
|
|
407
|
+
}
|
|
403
408
|
if (target.scope === "workspace") {
|
|
404
409
|
const workspace = this.requireTable().get(target.workspaceId);
|
|
405
410
|
if (workspace === void 0) throw new Error(`unknown workspace "${target.workspaceId}"`);
|