dsh-archived-chats 1.5.0 → 1.5.1
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 +14 -0
- package/README.md +7 -7
- package/README.zh-CN.md +7 -7
- package/assets/screenshots/preview-01.png +0 -0
- package/assets/screenshots/preview-02.png +0 -0
- package/assets/screenshots/preview-03.png +0 -0
- package/assets/screenshots/preview-04.png +0 -0
- package/assets/screenshots/preview-05.png +0 -0
- package/assets/screenshots/preview-06.png +0 -0
- package/assets/screenshots/preview-07.png +0 -0
- package/assets/screenshots/preview-08.png +0 -0
- package/docs/ARCHITECTURE.en.md +7 -3
- package/docs/ARCHITECTURE.md +7 -3
- package/docs/USER_GUIDE.md +7 -6
- package/docs/USER_GUIDE.zh-CN.md +8 -7
- package/lib/client.js +143 -64
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
Entries describe behavior at each release, not necessarily current behavior. Consult the READMEs and user guides for current usage.
|
|
6
6
|
|
|
7
|
+
## 1.5.1 — 2026-10-08
|
|
8
|
+
|
|
9
|
+
- 残留会话整合为顶部最后一张统计卡片,进入空间与策略自动检查,点击「查看明细」后管理明细;离开取消请求,迟到结果不覆盖新检查。目录及恢复快照异常归入对应卡片提示;空间统计加载或失败不再隐藏独立的残留检查入口。
|
|
10
|
+
- Move residual chats into the final summary card and scan automatically on entry to Storage & Retention. View details opens the management dialog; cancelled late responses cannot replace newer results. Show measurement and recovery-snapshot issues in their respective cards. Keep residual access available while storage statistics load or fail.
|
|
11
|
+
|
|
12
|
+
- 全部八张市场功能截图更新至最新界面,移除预览页顶部的英文及本地会话管理说明,并展示最新选择操作。
|
|
13
|
+
- Refresh all eight marketplace screenshots from the latest UI, remove the extra preview header, and show the current selected actions.
|
|
14
|
+
|
|
15
|
+
- 顶部更多新增「全部移入回收站」,确认并处理所有工作区的已归档聊天,筛选不会缩小范围。
|
|
16
|
+
- Add header More → Move all to Recycle Bin, confirming the complete archive scope across workspaces regardless of filters.
|
|
17
|
+
|
|
18
|
+
- 已归档新增选择模式,支持当前结果全选、半选、隐藏已选项计数及取消清空。直接提供取消归档和永久删除;更多提供导出选中和移入回收站。选择模式行内保留预览,操作先确认精确选中范围。
|
|
19
|
+
- Add Archived selection mode with filtered Select all, indeterminate state, hidden-selection counts, and Cancel. Expose Unarchive and Delete permanently; More contains Export selected and Move to Recycle Bin. Rows retain Preview and actions confirm the exact selected scope.
|
|
20
|
+
|
|
7
21
|
## 1.5.0 — 2026-10-08
|
|
8
22
|
|
|
9
23
|
### 中文
|
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Archive Management gives DeepSeek Harness a place to find chats hidden from the
|
|
|
32
32
|
|
|
33
33
|
> The English name is **Archive Management**, and the Chinese name is **归档管理**. The installation package remains `dsh-archived-chats`.
|
|
34
34
|
>
|
|
35
|
-
> This document follows the `main` branch and targets 1.5.
|
|
35
|
+
> This document follows the `main` branch and targets 1.5.1. This release streamlines selected actions, adds global recycling, and checks residual chats automatically from the final storage summary card. See the [changelog](CHANGELOG.md) for release history.
|
|
36
36
|
|
|
37
37
|
## Quick start
|
|
38
38
|
|
|
@@ -60,9 +60,9 @@ dsh plugin --profile web update dsh-archived-chats
|
|
|
60
60
|
| Workspace archiving | Unarchived offers individual, workspace-wide, and global archiving with aggregate confirmation; blank, active, or unreadable chats are skipped. |
|
|
61
61
|
| Read-only preview | Conversations, reasoning, tool activity, Markdown, JSON, code, and readable stored images without unarchiving. |
|
|
62
62
|
| Backup | Export one workspace or the entire archive as JSON + Markdown ZIP; preview imports and skip conflicting IDs. |
|
|
63
|
-
| Recycle Bin | Move archived chats by workspace; restore one chat, a workspace, or the entire Recycle Bin back to Archived. |
|
|
63
|
+
| Recycle Bin | Move archived chats by workspace or selection; restore one chat, a workspace, or the entire Recycle Bin back to Archived. |
|
|
64
64
|
| Permanent deletion | Delete one archived chat, a workspace's archive, or all archived chats after confirmation. Recycle Bin deletion is separate. |
|
|
65
|
-
| Residual chat check |
|
|
65
|
+
| Residual chat check | Entering Storage & Retention automatically checks for subagents whose parent no longer exists; search, select, preview, back up and confirm permanent deletion of selected records only. Empty chats are excluded. |
|
|
66
66
|
| Storage and relationships | Storage accounting, optional automatic Recycle Bin cleanup, and read-only Origins & Branches. |
|
|
67
67
|
|
|
68
68
|
The six views are **Archived**, **Unarchived**, **Recycle Bin**, **Storage & Retention**, **Origins & Branches**, and **About**. Unarchived shares Archived’s search, filters, and workspace-group layout, offering read-only chat previews and individual, workspace-wide, and global archiving. Archiving does not create historical versions; Recycle Bin protection snapshots support recovery, not a browsable version-history library.
|
|
@@ -72,7 +72,7 @@ The six views are **Archived**, **Unarchived**, **Recycle Bin**, **Storage & Ret
|
|
|
72
72
|
These screenshots show the current interface with synthetic example chats; they contain no private conversations. The [marketplace screenshot list](screenshots.json) uses the same files in the same order.
|
|
73
73
|
|
|
74
74
|
1. [Archived chats](assets/screenshots/preview-01.png)
|
|
75
|
-
2. [Full-text search](assets/screenshots/preview-02.png)
|
|
75
|
+
2. [Full-text search and selected actions](assets/screenshots/preview-02.png)
|
|
76
76
|
3. [Unarchived and workspace archiving](assets/screenshots/preview-03.png)
|
|
77
77
|
4. [Read-only preview](assets/screenshots/preview-04.png)
|
|
78
78
|
5. [Recycle Bin](assets/screenshots/preview-05.png)
|
|
@@ -80,7 +80,7 @@ These screenshots show the current interface with synthetic example chats; they
|
|
|
80
80
|
7. [Storage & Retention](assets/screenshots/preview-07.png)
|
|
81
81
|
8. [Origins & Branches](assets/screenshots/preview-08.png)
|
|
82
82
|
|
|
83
|
-
The Archived header has **More**. More contains Import backup, Export all, Unarchive all, then Delete all after a separator. The Recycle Bin header directly shows **Restore all** and **Empty Recycle Bin** without a More menu. Recycle rows retain the preview icon and use compact **Restore** and **Delete** text buttons. Workspace and global restoration separately confirm scope, counts, and the Archived destination, skipping pending deletions. Empty Recycle Bin still requires irreversible-action confirmation. The header keeps consistent dimensions across tabs. Workspace actions and global export, unarchive, and deletion ask you to confirm the complete archive scope and chat count.
|
|
83
|
+
The Archived header has **Select** and **More**. More contains Import backup, Export all, Unarchive all, Move all to Recycle Bin, then Delete all after a separator. The Recycle Bin header directly shows **Restore all** and **Empty Recycle Bin** without a More menu. Recycle rows retain the preview icon and use compact **Restore** and **Delete** text buttons. Workspace and global restoration separately confirm scope, counts, and the Archived destination, skipping pending deletions. Empty Recycle Bin still requires irreversible-action confirmation. The header keeps consistent dimensions across tabs. Workspace actions and global export, unarchive, and deletion ask you to confirm the complete archive scope and chat count.
|
|
84
84
|
|
|
85
85
|
**About** shows the running version, author, license, guides, project and feedback links, and Check for updates. Opening the page checks public npm version metadata in the background, at most once every 12 hours during a running backend session; failures never block archive management. A newer version adds a **Get update** link beside the title, opening the plugin market without installing or restarting anything. Follow the host's restart guidance when convenient; a browser refresh alone may not load an updated backend. No chat or backup content is sent during update checks.
|
|
86
86
|
|
|
@@ -89,11 +89,11 @@ Preview keeps real user messages on the right and the assistant's work on the le
|
|
|
89
89
|
## Archive, recycle, or delete?
|
|
90
90
|
|
|
91
91
|
- **Archive:** retain a chat outside the main chat area. Unarchive returns it there.
|
|
92
|
-
- **Move to Recycle Bin:** available
|
|
92
|
+
- **Move to Recycle Bin:** available through workspace menus, header More → Move all to Recycle Bin, or More in selection mode. Recovery returns the chat to Archived; Unarchive then returns it to the main chat area.
|
|
93
93
|
- **Delete all on Archived:** permanently delete archived chats across all workspaces, not existing Recycle Bin entries.
|
|
94
94
|
- **Empty Recycle Bin:** permanently delete recycled chats and their associated protection data.
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
Choose Select to check one or more chats, then Unarchive or Delete permanently. More contains Export selected and Move to Recycle Bin. Select all checks only current search/filter results; hidden selections remain selected with a visible count. Cancel exits and clears the selection. Ordinary rows have no export or recycle buttons. Workspace and global actions include chats hidden by filters. Workspace actions do not delete workspace/project directories. See the [action scope table](docs/USER_GUIDE.md#actions-and-their-scope) before bulk operations.
|
|
97
97
|
|
|
98
98
|
Empty Recycle Bin acts only on the exact recycle-record incarnations shown at confirmation. Records added afterward are excluded; a changed target fails instead of widening the confirmed scope. A chat without a working directory remains in Archived because this Host cannot make it reachable from the main chat list; a missing workspace alone can still restore as ungrouped.
|
|
99
99
|
|
package/README.zh-CN.md
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
> 中文名称为「归档管理」,英文名称为 Archive Management;安装包名始终为 `dsh-archived-chats`。
|
|
32
32
|
>
|
|
33
|
-
> 本文跟随 `main` 分支维护,适用于 1.5.
|
|
33
|
+
> 本文跟随 `main` 分支维护,适用于 1.5.1。本版精简选择操作,补充全局回收入口,并将残留会话整合到空间统计卡片自动检查。版本变更见[更新日志](CHANGELOG.md)。
|
|
34
34
|
|
|
35
35
|
## 快速开始
|
|
36
36
|
|
|
@@ -58,9 +58,9 @@ dsh plugin --profile web update dsh-archived-chats
|
|
|
58
58
|
| 工作区批量归档 | 未归档视图支持单条、工作区和全局归档,汇总确认后执行;跳过空白、正在使用或无法确认内容的聊天。 |
|
|
59
59
|
| 只读预览 | 无需取消归档即可查看对话、思考、工具活动、Markdown、JSON、代码及可读取的已存储图片。 |
|
|
60
60
|
| 备份 | 支持按工作区或全部归档导出 JSON + Markdown ZIP;导入先预览,跳过冲突 ID。 |
|
|
61
|
-
| 回收站 |
|
|
61
|
+
| 回收站 | 按工作区或勾选聊天移入回收站;支持单条、工作区和整个回收站恢复到已归档。 |
|
|
62
62
|
| 永久删除 | 确认后删除单条归档、工作区归档或全部归档;回收站删除独立操作。 |
|
|
63
|
-
| 残留会话检查 |
|
|
63
|
+
| 残留会话检查 | 进入空间与策略时自动检查父会话已不存在的子代理,支持明细搜索、多选、预览、备份及确认后永久删除;只删除选中项,不清理空会话。 |
|
|
64
64
|
| 空间与关系 | 空间分账、可选的回收站自动清理,以及只读「来源与分支」。 |
|
|
65
65
|
|
|
66
66
|
六个视图为 **已归档**、**未归档**、**回收站**、**空间与策略**、**来源与分支** 和 **关于**。未归档复用已归档的搜索、筛选和工作区分组排版,提供只读预览、单条归档、工作区全部归档和全局全部归档。归档不会创建历史版本;回收站保护快照仅用于恢复,不是可浏览的历史版本库。
|
|
@@ -70,7 +70,7 @@ dsh plugin --profile web update dsh-archived-chats
|
|
|
70
70
|
以下截图使用虚构示例聊天展示当前界面,不含私人会话。[插件市场截图清单](screenshots.json)使用相同文件及顺序。
|
|
71
71
|
|
|
72
72
|
1. [已归档聊天](assets/screenshots/preview-01.png)
|
|
73
|
-
2. [
|
|
73
|
+
2. [全文搜索与选择操作](assets/screenshots/preview-02.png)
|
|
74
74
|
3. [未归档与工作区归档](assets/screenshots/preview-03.png)
|
|
75
75
|
4. [只读预览](assets/screenshots/preview-04.png)
|
|
76
76
|
5. [回收站](assets/screenshots/preview-05.png)
|
|
@@ -78,7 +78,7 @@ dsh plugin --profile web update dsh-archived-chats
|
|
|
78
78
|
7. [空间与策略](assets/screenshots/preview-07.png)
|
|
79
79
|
8. [来源与分支](assets/screenshots/preview-08.png)
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
已归档页顶部提供 **选择** 和 **更多**;未归档页顶部提供 **全部归档**,筛选行最右侧提供刷新图标;「更多」依次提供导入备份、全部导出、全部取消归档、全部移入回收站,分隔线后为全部删除。回收站顶部直接并列显示 **全部恢复** 和 **清空回收站**,不再设更多菜单;聊天行保留预览图标,使用紧凑的 **恢复** 和 **删除** 文字按钮。工作区恢复和全局恢复分别确认范围、数量及已归档去向,跳过正在永久删除的条目;清空仍须确认不可恢复的后果。各 Tab 顶部保持一致尺寸。工作区操作及全局导出、取消归档、删除均先确认完整归档范围和聊天数量。
|
|
82
82
|
|
|
83
83
|
「关于」展示当前运行版本、作者、许可证、指南、项目与反馈链接,提供「检查更新」。打开页面时后台查询公开 npm 版本信息,运行期间自动检查间隔至少 12 小时;失败不会阻断归档功能。有新版时标题旁显示「去更新」,打开插件市场,不自动安装或重启。更新后按宿主提示自行安排重启 DSH,仅刷新网页不一定能加载新版后端。
|
|
84
84
|
|
|
@@ -87,11 +87,11 @@ dsh plugin --profile web update dsh-archived-chats
|
|
|
87
87
|
## 归档、回收与删除的区别
|
|
88
88
|
|
|
89
89
|
- **归档:** 保留聊天,但从主页面会话区隐藏;取消归档后返回主页面。
|
|
90
|
-
- **移入回收站:**
|
|
90
|
+
- **移入回收站:** 通过工作区菜单、顶部「更多 → 全部移入回收站」或选择模式的「更多」操作。恢复先回到已归档,再取消归档返回主页面。
|
|
91
91
|
- **归档页「全部删除」:** 永久删除所有工作区的已归档聊天,不影响已经在回收站中的聊天。
|
|
92
92
|
- **「清空回收站」:** 永久删除回收聊天及其关联保护数据。
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
点击「选择」进入选择模式,可勾选一条或多条聊天后取消归档或永久删除;「更多」提供导出选中和移入回收站。「全选」只勾选当前搜索/筛选结果,隐藏的已选项保留并显示数量;「取消」退出并清空选择。普通列表不增加单条导出或移入回收站按钮。工作区和全部操作包含被筛选隐藏的聊天;工作区操作不会删除工作区或项目目录。批量操作前可查看[操作范围表](docs/USER_GUIDE.zh-CN.md#操作入口与范围)。
|
|
95
95
|
|
|
96
96
|
清空回收站只作用于确认时展示的精确回收记录实例。之后新增的记录不纳入本次操作;目标已变更时会失败,不会扩大已确认范围。没有工作目录的聊天会留在已归档,因为当前 Host 无法让它在主聊天列表中可达;仅缺少工作区不等同于没有工作目录,仍可以未分组方式恢复。
|
|
97
97
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/ARCHITECTURE.en.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English · [中文](ARCHITECTURE.md) · [User guide](USER_GUIDE.md)
|
|
4
4
|
|
|
5
|
-
This document follows `main` and covers 1.5.
|
|
5
|
+
This document follows `main` and covers 1.5.1 behavior. This release streamlines selected actions, adds global recycling, and checks residual chats automatically from the final storage summary card. See the [changelog](../CHANGELOG.md) for release history. Runtime code is the authority for interfaces and behavior; user-facing documentation should agree with it.
|
|
6
6
|
|
|
7
7
|
## Product boundary and modules
|
|
8
8
|
|
|
@@ -23,7 +23,7 @@ The plugin supplements archive management; it does not replace DSH's main chat a
|
|
|
23
23
|
| `lineage.js` | Read-only source, fork, and subagent projection |
|
|
24
24
|
| `session-graph.js` | Session-id normalization, subagent descendant resolution, orphan classification |
|
|
25
25
|
|
|
26
|
-
The UI moves chats to the Recycle Bin
|
|
26
|
+
The UI moves chats to the Recycle Bin through workspace actions, the global header menu, or selection mode. Rows can permanently delete or unarchive. Workspace export and export-all remain. A compatibility backend endpoint accepting one ID does not imply a row-level recycle action.
|
|
27
27
|
|
|
28
28
|
Standalone History, legacy-snapshot recovery copies, and cleanup-preview UI are retired. `history.js`, `history-restore.js`, and `legacy-recycle.js` are not current modules.
|
|
29
29
|
|
|
@@ -137,7 +137,9 @@ Turn projection retains recorded boundaries and process/final-response positions
|
|
|
137
137
|
|
|
138
138
|
Export download uses a guarded fetch, validates status, ZIP content type, and attachment disposition, then buffers the complete response before creating a Blob URL. It has a five-minute end-to-end timeout and a 320 MiB response-byte cap for declared and streamed bodies. The cap is not a peak-heap guarantee because chunks, the contiguous buffer, and Blob can coexist; the non-stream WebView fallback has no stronger universal memory ceiling. Completion says the download started, not that the browser saved it to disk.
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
Selection mode stores checked IDs on the client. Select all uses filtered `group.items`; workspace actions keep complete `group.sessionIds`. Hidden selections have a visible count. Cancel or navigation clears the set, and list updates prune missing IDs. Confirmation captures the exact ID snapshot and reuses existing export, unarchive, and deletion APIs. Selection mode hides row mutations and global/workspace menus, retaining preview; More contains Export selected and Move to Recycle Bin.
|
|
141
|
+
|
|
142
|
+
Archive row actions are preview, edit tags/note, Unarchive, and Delete. The header exposes More; Unarchived reuses `GroupSection`, with search above filters and a refresh icon at the right; rows offer a read-only preview icon and Archive, while workspace menus and the header offer Archive all; More contains Import backup, Export all, Unarchive all, Move all to Recycle Bin, a separator, and Delete all. Header geometry stays consistent across tabs. Workspace menus contain Unarchive all, Move all to Recycle Bin, Export all, a separator, and Delete all; every action confirms the full workspace name and complete archive count. Global export/unarchive/delete confirms all archived chats across workspaces, excluding trash. Filters do not narrow these scopes. Delete/Delete all labels lead to concise irreversible-action confirmation naming the chat, workspace, or global scope. Recycle rows retain the preview icon and use compact Restore/Delete text buttons matching archive rows. Recycle workspace actions are Restore all and Delete all; its header directly offers text-only Restore all and Empty Recycle Bin, without a More menu. Empty still requires irreversible-action confirmation. Primary buttons and selected tabs use neutral theme colors that invert in dark mode, while destructive actions remain red. Workspace/global restoration separately confirms the workspace name or global workspace count, eligible chat counts, and Archived destination, skipping purge-pending. A synchronous submission lock prevents duplicates; results retain actual success/failure counts and View Archived navigation. Both deleted and pending IDs leave actionable archive rows while failures remain explained and related state refreshes.
|
|
141
143
|
|
|
142
144
|
Plugin confirmation, preview, import, metadata, storage, and retention dialogs share the opaque `--dsw-alias-bg-layer-2` surface and Host prominent elevation, with a legacy shadow fallback. Menus and toasts use opaque theme layers. Masks and hover states retain their intentional transparency; frosted menu tokens are not used as dialog backgrounds.
|
|
143
145
|
|
|
@@ -239,3 +241,5 @@ The declared DSH `>=0.1.0-rc.7` range remains capability-based. Release automati
|
|
|
239
241
|
Permanent deletion quiesces live agents before the final descendant inventory. Optional `cascadeSessionIds` in the parent purge-pending record persist the complete targets until every descendant finishes, so startup can recover after the parent directory disappears. Forks and subtrees with independent recycle records remain outside the cascade. Do not downgrade to a version that does not understand this field while these records are pending.
|
|
240
242
|
|
|
241
243
|
`GET /unarchived` is read-only; `/unarchived/export` and preview `scope: "unarchived"` validate current workspace visibility. Normal conversations gain no direct permanent-delete route. Cleanup UI retains `/orphans` authority checks.
|
|
244
|
+
|
|
245
|
+
Storage & Retention combines four storage cards and one residual chat card. Entry triggers a subagent residual scan; navigation cancels it, and late responses cannot replace newer results. View details opens the management dialog explicitly. Measurement and recovery-snapshot issues appear only in their respective cards. Residual bytes are excluded from the archive/recycle measured total.
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](ARCHITECTURE.en.md) · 中文 · [用户指南](USER_GUIDE.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
本文跟随 `main` 分支,功能对应 1.5.
|
|
5
|
+
本文跟随 `main` 分支,功能对应 1.5.1。本版精简选择操作,补充全局回收入口,并将残留会话整合到空间统计卡片自动检查。版本变更见[更新日志](../CHANGELOG.md)。运行时代码是接口与行为的依据;用户操作说明与本文应保持一致。
|
|
6
6
|
|
|
7
7
|
## 产品边界与模块
|
|
8
8
|
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
| `lineage.js` | 只读来源、分叉与子代理关系投影 |
|
|
24
24
|
| `session-graph.js` | 会话 ID 归一化、子代理后代解析与孤儿分类 |
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
界面通过工作区操作、全局顶部菜单或选择模式移入回收站;选择一条或多条后可导出、取消归档及永久删除。普通行保留原有操作,不增加单条回收和导出按钮。保留完整工作区与全局批量操作。
|
|
27
27
|
|
|
28
28
|
独立 History、旧快照恢复副本和预览清理界面已移除。`history.js`、`history-restore.js`、`legacy-recycle.js` 不是当前模块。
|
|
29
29
|
|
|
@@ -137,7 +137,9 @@ apply 只接受凭据,不接受调用方增补会话 ID。每条执行前在
|
|
|
137
137
|
|
|
138
138
|
导出下载使用受保护 fetch,验证状态、ZIP 内容类型和附件 disposition,缓冲完整响应后才创建 Blob URL。端到端超时为五分钟,已声明及流式响应上限为 320 MiB 字节。该上限不是峰值堆内存保证,因为分块、连续缓冲区和 Blob 可能同时存在;非流式 WebView 回退也没有更强的通用内存上限。完成文案表示已开始下载,不表示浏览器已保存到磁盘。
|
|
139
139
|
|
|
140
|
-
归档行操作为预览、编辑标签备注、取消归档、删除。顶部提供更多;未归档复用 `GroupSection
|
|
140
|
+
归档行操作为预览、编辑标签备注、取消归档、删除。顶部提供更多;未归档复用 `GroupSection`,搜索在筛选行上方,刷新图标靠右;会话行提供只读预览图标和归档,工作区菜单和顶部提供全部归档;更多菜单依次为导入备份、全部导出、全部取消归档、全部移入回收站、分隔线、全部删除,各 Tab 顶部保持一致尺寸。工作区菜单依次为全部取消归档、全部移入回收站、全部导出、分隔线、全部删除,每项均确认完整工作区名称和全部归档聊天数。全局导出/取消归档/删除确认所有工作区归档数量,排除回收站;筛选不缩小这些范围。「删除/全部删除」进入简短的不可恢复确认,点明聊天、工作区或全局范围。回收站聊天行保留预览图标,恢复/删除使用与归档行一致的紧凑文字按钮。工作区操作为全部恢复和全部删除;顶部直接并列显示纯文字的全部恢复和清空回收站,不再设置更多菜单;清空仍须不可恢复确认。主按钮和选中 Tab 使用随深色模式反转的黑白主题色,危险操作保留红色。两级恢复确认分别说明工作区名称或全局工作区数、可恢复聊天数及已归档去向,跳过 purge-pending。同步提交锁避免重复请求;反馈保留实际成功/失败数量及查看已归档入口。永久删除响应中的 deleted 和 pending 都从可操作归档列表移除,同时保留失败说明并刷新相关状态。
|
|
141
|
+
|
|
142
|
+
选择模式在客户端保存已选 ID 集合。全选从筛选后的 `group.items` 获取范围;工作区批量操作仍使用完整 `group.sessionIds`。隐藏选择显示数量,切换页面或取消清空集合,列表更新后剔除已不存在的 ID。操作确认捕获 ID 快照并复用现有导出、取消归档和删除接口。选择模式隐藏行内修改及全局/工作区菜单,保留预览,更多提供导出选中和移入回收站。
|
|
141
143
|
|
|
142
144
|
所有插件确认、预览、导入、元数据、空间和保留策略弹窗共用不透明的 `--dsw-alias-bg-layer-2` 背景及宿主 prominent elevation,兼容旧版阴影。菜单和提示使用不透明主题层;遮罩与悬停状态保留各自的半透明用途,避免将磨砂菜单 token 直接用于弹窗主体。
|
|
143
145
|
|
|
@@ -239,3 +241,5 @@ git diff --check
|
|
|
239
241
|
永久删除在最终读取子代理关系前先取消并释放活动代理。完整后代集合以可选 `cascadeSessionIds` 保存到父会话的 `purge-pending` 记录;父目录消失后也保留该记录,直到所有子代理清理完成。启动重试据此恢复级联,不跨越分叉或已有独立回收记录的子树。待重试记录清空前不要降级到不支持此字段的版本。
|
|
240
242
|
|
|
241
243
|
`GET /unarchived` 为只读列表;`/unarchived/export` 和预览的 `scope: "unarchived"` 校验当前工作区可见性。普通会话没有新增直接永久删除路由;清理筛选继续使用 `/orphans` 的严格授权校验。
|
|
244
|
+
|
|
245
|
+
空间与策略顶部由四张空间统计卡片和一张残留会话卡片组成。进入该页时发起一次子代理残留检查,离开时取消请求,迟到响应不覆盖后续结果;查看后才打开明细。目录统计异常与恢复快照异常仅在对应卡片中提示,不再单列零值诊断卡。残留会话空间不并入归档/回收空间总量。
|
package/docs/USER_GUIDE.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English · [简体中文](USER_GUIDE.zh-CN.md) · [Back to README](../README.md)
|
|
4
4
|
|
|
5
|
-
Archive Management provides a place to browse and manage DSH's archived chats, plus workspace bulk archiving. This document follows the `main` branch and targets 1.5.
|
|
5
|
+
Archive Management provides a place to browse and manage DSH's archived chats, plus workspace bulk archiving. This document follows the `main` branch and targets 1.5.1. This release streamlines selected actions, adds global recycling, and checks residual chats automatically from the final storage summary card. See the [changelog](../CHANGELOG.md) for release history. For interfaces and data formats, see the [architecture](ARCHITECTURE.en.md).
|
|
6
6
|
|
|
7
7
|
## Understand the three locations
|
|
8
8
|
|
|
@@ -67,14 +67,15 @@ Tags and notes remain local. Unarchiving preserves them; completed permanent del
|
|
|
67
67
|
| Unarchived: chat row | Preview icon, Archive | One chat; archiving requires confirmation |
|
|
68
68
|
| Unarchived: workspace three-dot menu | Archive all | Every eligible chat in that workspace |
|
|
69
69
|
| Unarchived: header | Archive all | Eligible chats across all workspaces |
|
|
70
|
-
| Archived: header | More |
|
|
71
|
-
| Archived:
|
|
70
|
+
| Archived: header | Select, More | Enter selection mode or open the global action menu |
|
|
71
|
+
| Archived: selection mode | Select all, Unarchive, Delete permanently, More → Export selected / Move to Recycle Bin, Cancel | Checked chats only; Select all covers current results |
|
|
72
|
+
| Archived: header More menu | Import backup, Export all, Unarchive all, Move all to Recycle Bin; separator; Delete all | Export, unarchive, recycle, and delete cover archived chats across all workspaces; Import uses the selected ZIP |
|
|
72
73
|
| Recycle Bin: chat row | Preview icon, Restore and Delete text buttons | One recycle record; Delete is permanent after confirmation |
|
|
73
74
|
| Recycle Bin: workspace More menu | Restore all, Delete all | Recycle records in that workspace |
|
|
74
75
|
| Recycle Bin: header | Restore all | Restore eligible entries across all workspaces after confirmation |
|
|
75
76
|
| Recycle Bin: header | Empty Recycle Bin | Permanently delete recycle records across all workspaces after confirmation |
|
|
76
77
|
|
|
77
|
-
**
|
|
78
|
+
**Selection mode supports one or many chats.** Select shows checkboxes and an action bar; rows keep Preview only. Unarchive and Delete permanently are visible; More contains Export selected and Move to Recycle Bin. Select all checks current search/filter results and shows an indeterminate state for partial selection. Hidden selections remain checked with a visible count; clearing current results preserves them. Cancel or leaving Archived clears the selection and exits. Actions confirm the selected count, and failed items stay checked. Ordinary rows have no export or recycle buttons; Unarchived provides read-only previews and archiving. Search and filters do not narrow workspace or global bulk actions.
|
|
78
79
|
|
|
79
80
|
Every archive workspace action asks for confirmation with the full workspace name and its complete archive count. Global Export all, Unarchive all, and Delete all confirmations count archived chats across every workspace, excluding the Recycle Bin. Check these counts even when the list is filtered. The header keeps consistent dimensions when switching tabs.
|
|
80
81
|
|
|
@@ -104,11 +105,11 @@ Once deletion starts, the plugin saves a non-restorable deletion task, removes a
|
|
|
104
105
|
|
|
105
106
|
Missing required Host capabilities cause refusal before a deletion task is committed. Removing snapshot attachment copies does not guarantee immediate reclamation of matching bytes in Harness's global attachment store; other references and Host caching or garbage collection may retain them.
|
|
106
107
|
|
|
107
|
-
Permanent deletion also cleans up the chat's subagent descendants while preserving independent forks. Unarchived lists ordinary workspace chats. Storage & Retention offers an
|
|
108
|
+
Permanent deletion also cleans up the chat's subagent descendants while preserving independent forks. Unarchived lists ordinary workspace chats. Storage & Retention offers an automatic residual check, described below.
|
|
108
109
|
|
|
109
110
|
## Residual chat check
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
Every entry to **Storage & Retention** automatically runs a check. The final summary card, Residual chats, shows the count and measured storage. Choose View details to manage the listed chats; failed checks offer Retry. Checking never opens a dialog or deletes records. Only subagents with an explicit parent ID whose parent no longer exists are listed. Workspace, archived, recycled and pending-deletion records, empty chats, invalid parent relationships and independent forks are excluded.
|
|
112
113
|
|
|
113
114
|
Details include full IDs, parent IDs, working directories, dates, sizes and read-only preview icons. Search and select current results without clearing hidden selections. Backups include selected records. When records exceed the backup limit, they are marked and you are asked whether to deselect them and export the rest. Cancelling keeps the original selection.
|
|
114
115
|
|
package/docs/USER_GUIDE.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](USER_GUIDE.md) · 简体中文 · [返回 README](../README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
归档管理为 DSH 已归档聊天提供查看和管理页面,并扩展按工作区批量归档。本文跟随 `main` 分支维护,适用于 1.5.
|
|
5
|
+
归档管理为 DSH 已归档聊天提供查看和管理页面,并扩展按工作区批量归档。本文跟随 `main` 分支维护,适用于 1.5.1。本版精简选择操作,补充全局回收入口,并将残留会话整合到空间统计卡片自动检查。版本变更见[更新日志](../CHANGELOG.md),接口和数据格式见[架构说明](ARCHITECTURE.md)。
|
|
6
6
|
|
|
7
7
|
## 先理解三个位置
|
|
8
8
|
|
|
@@ -67,14 +67,15 @@ dsh plugin --profile web add dsh-archived-chats@latest
|
|
|
67
67
|
| 未归档:聊天行 | 预览图标、归档 | 单条聊天;归档需确认 |
|
|
68
68
|
| 未归档:工作区三个点菜单 | 全部归档 | 该工作区的所有可归档聊天 |
|
|
69
69
|
| 未归档:顶部 | 全部归档 | 所有工作区的可归档聊天 |
|
|
70
|
-
| 已归档:顶部 |
|
|
71
|
-
|
|
|
70
|
+
| 已归档:顶部 | 选择、更多 | 进入选择模式或打开全局操作菜单 |
|
|
71
|
+
| 已归档:选择模式 | 全选、取消归档、永久删除、更多 → 导出选中/移入回收站、取消 | 操作仅针对勾选聊天;全选仅针对当前结果 |
|
|
72
|
+
| 已归档:顶部「更多」 | 导入备份、全部导出、全部取消归档、全部移入回收站;分隔线;全部删除 | 导出、取消归档、移入回收站及删除覆盖所有工作区的已归档聊天;导入使用所选 ZIP |
|
|
72
73
|
| 回收站:单条聊天 | 预览图标、恢复和删除文字按钮 | 当前回收条目;删除需确认且不可恢复 |
|
|
73
74
|
| 回收站:工作区「更多」 | 全部恢复、全部删除 | 该工作区的回收条目 |
|
|
74
75
|
| 回收站:顶部 | 全部恢复 | 确认后恢复所有工作区的可恢复条目 |
|
|
75
76
|
| 回收站:顶部 | 清空回收站 | 确认后永久删除所有工作区的回收条目 |
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
**选择模式支持单条和多条操作。** 点击「选择」显示复选框和操作栏,行内仅保留预览;取消归档和永久删除直接展示,「更多」包含导出选中和移入回收站。「全选」勾选当前搜索/筛选结果,部分勾选时显示半选;隐藏的已选项保留并显示数量。取消当前结果的全选不会取消隐藏选择。「取消」或离开已归档会退出模式并清空选择。执行前确认选中数量,失败项保留勾选。普通列表不增加单条导出或回收按钮;未归档提供只读预览和归档操作。工作区和顶部批量操作不会因搜索、筛选隐藏了部分聊天而缩小范围。
|
|
78
79
|
|
|
79
80
|
归档工作区菜单中的每项操作都需确认,弹窗显示完整工作区名称及该工作区全部归档聊天数。全局「全部导出」「全部取消归档」「全部删除」确认统计所有工作区的归档聊天,不包含回收站;筛选列表后仍应核对完整数量。切换 Tab 时顶部操作区保持一致尺寸。
|
|
80
81
|
|
|
@@ -104,11 +105,11 @@ dsh plugin --profile web add dsh-archived-chats@latest
|
|
|
104
105
|
|
|
105
106
|
缺少必要 Host 能力时,操作会在提交删除任务前拒绝执行。删除快照附件副本不保证立即释放 Harness 全局附件库中的相同字节;其他会话引用和 Host 缓存/垃圾回收仍可能保留数据。
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
永久删除聊天时会连带清理其子代理后代,独立分叉会保留。未归档只显示工作区中的普通聊天;「空间与策略」提供残留会话检查,详见下节。
|
|
108
109
|
|
|
109
110
|
## 残留会话检查
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
每次进入 **空间与策略** 时自动检查,顶部最后一张「残留会话」统计卡片显示数量和已测量空间。点击「查看明细」打开管理弹窗;检查失败时可重试。自动检查不会打开弹窗或删除记录。它只列出有明确父会话 ID 且父会话已不存在的子代理,排除工作区、已归档、回收站、待删除中的会话,以及空会话、父关系异常的记录和独立分叉。
|
|
112
113
|
|
|
113
114
|
明细提供完整 ID、父会话、工作目录、日期、大小和只读预览图标。可搜索并选择当前结果;筛选不会清除已经选中的隐藏项。导出备份覆盖选中项,超出备份限制时标记记录并询问是否取消勾选后导出其余,取消会保留完整选择。
|
|
114
115
|
|
|
@@ -116,7 +117,7 @@ dsh plugin --profile web add dsh-archived-chats@latest
|
|
|
116
117
|
|
|
117
118
|
## 导出和导入
|
|
118
119
|
|
|
119
|
-
|
|
120
|
+
选择模式「更多 → 导出选中」备份勾选的聊天,可用于单条备份。顶部「更多 → 全部导出」导出全部已归档聊天;工作区菜单「全部导出」仅导出该工作区的归档聊天。两者都先确认完整范围和聊天数量,不包含回收站内容。需要备份回收聊天时,先恢复到已归档。「导入备份」也位于顶部「更多」菜单。
|
|
120
121
|
|
|
121
122
|
每个 ZIP 包含:
|
|
122
123
|
|
package/lib/client.js
CHANGED
|
@@ -203,7 +203,7 @@ window.__ModuleLoader__.load({
|
|
|
203
203
|
"trash.listUnverified": "回收站目录无法读取:下面的列表可能包含已删除的聊天,归档修改已暂停。",
|
|
204
204
|
"insights.loading": "正在分析空间…",
|
|
205
205
|
"insights.error": "空间分析暂不可用",
|
|
206
|
-
"insights.scopeNote": "
|
|
206
|
+
"insights.scopeNote": "这里只统计已归档会话和回收站当前引用的保护快照。归档列表为空时仍可能有保护快照,因为回收记录会保留快照以备恢复。未被回收记录引用的保护快照会在启动时自动清理。残留会话单独检查,不计入已统计总量。",
|
|
207
207
|
"insights.sessions": "会话目录",
|
|
208
208
|
"insights.snapshots": "保护快照",
|
|
209
209
|
"insights.total": "已统计总量",
|
|
@@ -319,7 +319,7 @@ window.__ModuleLoader__.load({
|
|
|
319
319
|
"import.titlePublicationDegraded": "聊天已安全恢复,但标题可能需要打开聊天后才会出现在主列表。",
|
|
320
320
|
"import.seededTitleLimitation": "聊天已安全恢复;当前宿主的冷启动列表无法发布分支标题,打开聊天后可正常显示。",
|
|
321
321
|
"export.all": "全部导出",
|
|
322
|
-
"export.selected": "
|
|
322
|
+
"export.selected": "导出选中",
|
|
323
323
|
"menu.row": "更多聊天操作",
|
|
324
324
|
"export.started": "已开始下载备份",
|
|
325
325
|
"export.failed": "备份导出失败,应用仍保持打开,请刷新列表后重试",
|
|
@@ -477,8 +477,16 @@ window.__ModuleLoader__.load({
|
|
|
477
477
|
"unarchived.archive": "归档",
|
|
478
478
|
"unarchived.busy": "正在运行",
|
|
479
479
|
"action.refresh": "刷新列表",
|
|
480
|
-
"residual.
|
|
481
|
-
|
|
480
|
+
"residual.cardTitle": "残留会话",
|
|
481
|
+
"residual.count": "{count} 个",
|
|
482
|
+
"residual.view": "查看明细",
|
|
483
|
+
"residual.open": "查看残留会话明细",
|
|
484
|
+
"residual.checking": "正在检查…",
|
|
485
|
+
"residual.failed": "检查失败",
|
|
486
|
+
"insights.sessionIssues": "{count} 个目录无法统计",
|
|
487
|
+
"insights.snapshotIssues": "{count} 个恢复快照异常",
|
|
488
|
+
"residual.title": "残留会话检查",
|
|
489
|
+
"residual.note": "进入空间与策略时自动检查父会话已不存在的子代理。查看明细后,可先备份再清理。",
|
|
482
490
|
"residual.check": "开始检查",
|
|
483
491
|
"residual.recheck": "重新检查",
|
|
484
492
|
"residual.summary": "{count} 个残留会话 · 已测量 {size}",
|
|
@@ -541,7 +549,7 @@ window.__ModuleLoader__.load({
|
|
|
541
549
|
"trash.listUnverified": "The Recycle Bin catalog is unreadable: this list may include deleted chats, and archive changes are paused.",
|
|
542
550
|
"insights.loading": "Analyzing storage…",
|
|
543
551
|
"insights.error": "Storage insights are unavailable",
|
|
544
|
-
"insights.scopeNote": "This page measures archived chats and protection snapshots currently referenced by Recycle Bin records. Protection snapshots can remain even when the archive list is empty, because recycle records retain snapshots for restoration. Unreferenced legacy snapshots are removed during startup.",
|
|
552
|
+
"insights.scopeNote": "This page measures archived chats and protection snapshots currently referenced by Recycle Bin records. Protection snapshots can remain even when the archive list is empty, because recycle records retain snapshots for restoration. Unreferenced legacy snapshots are removed during startup. Residual chats are checked separately and excluded from the measured total.",
|
|
545
553
|
"insights.sessions": "Session directories",
|
|
546
554
|
"insights.snapshots": "Protection snapshots",
|
|
547
555
|
"insights.total": "Measured total",
|
|
@@ -815,8 +823,16 @@ window.__ModuleLoader__.load({
|
|
|
815
823
|
"unarchived.archive": "Archive",
|
|
816
824
|
"unarchived.busy": "Running",
|
|
817
825
|
"action.refresh": "Refresh list",
|
|
818
|
-
"residual.
|
|
819
|
-
|
|
826
|
+
"residual.cardTitle": "Residual chats",
|
|
827
|
+
"residual.count": "{count} chats",
|
|
828
|
+
"residual.view": "View details",
|
|
829
|
+
"residual.open": "View residual chat details",
|
|
830
|
+
"residual.checking": "Checking…",
|
|
831
|
+
"residual.failed": "Check failed",
|
|
832
|
+
"insights.sessionIssues": "{count} directories could not be measured",
|
|
833
|
+
"insights.snapshotIssues": "{count} recovery snapshots have issues",
|
|
834
|
+
"residual.title": "Residual chat check",
|
|
835
|
+
"residual.note": "Automatically check for subagents whose parent chat no longer exists when entering Storage & Retention. Review details and back up before cleanup.",
|
|
820
836
|
"residual.check": "Start check",
|
|
821
837
|
"residual.recheck": "Check again",
|
|
822
838
|
"residual.summary": "{count} residual chat(s) · {size} measured",
|
|
@@ -865,40 +881,42 @@ window.__ModuleLoader__.load({
|
|
|
865
881
|
const zh = isZh(t);
|
|
866
882
|
const workspace = scope.kind === "workspace";
|
|
867
883
|
const all = scope.kind === "all";
|
|
884
|
+
const selection = scope.kind === "selection";
|
|
868
885
|
const workspaceCount = new Set(selected.map((row) => row.workspaceId ?? "ungrouped")).size;
|
|
869
886
|
const target = workspace
|
|
870
887
|
? (zh ? `工作区“${scope.title}”中的全部 ${count} 个已归档聊天` : `all ${count} archived chats in workspace “${scope.title}”`)
|
|
871
888
|
: all ? (zh ? `全部 ${workspaceCount} 个工作区中的 ${count} 个已归档聊天` : `all ${count} archived chats across ${workspaceCount} workspaces`)
|
|
889
|
+
: selection ? (zh ? `选中的 ${count} 个已归档聊天` : `the ${count} selected archived chats`)
|
|
872
890
|
: (zh ? `聊天“${selected[0]?.title || t("chat.untitled")}”` : `chat “${selected[0]?.title || t("chat.untitled")}”`);
|
|
873
891
|
const unaffected = workspace
|
|
874
892
|
? (zh ? "其他工作区和回收站不受影响。" : "Other workspaces and the Recycle Bin are unaffected.")
|
|
875
893
|
: (zh ? "回收站不受影响。" : "The Recycle Bin is unaffected.");
|
|
876
894
|
if (action === "permanent") return {
|
|
877
|
-
title: zh ? (workspace ? "删除该工作区的全部已归档聊天?" : all ? "删除全部已归档聊天?" : "删除聊天?")
|
|
878
|
-
: (workspace ? "Delete all archived chats in this workspace?" : all ? "Delete all archived chats?" : "Delete chat?"),
|
|
895
|
+
title: zh ? (workspace ? "删除该工作区的全部已归档聊天?" : all ? "删除全部已归档聊天?" : selection ? "永久删除选中的聊天?" : "删除聊天?")
|
|
896
|
+
: (workspace ? "Delete all archived chats in this workspace?" : all ? "Delete all archived chats?" : selection ? "Permanently delete selected chats?" : "Delete chat?"),
|
|
879
897
|
body: zh ? `这将永久删除${target}及其归档数据(标签、备注和关联快照)。${workspace || all ? unaffected : ""}此操作无法撤销。`
|
|
880
898
|
: `This permanently deletes ${target} and their archive data (tags, notes, and related snapshots). ${workspace || all ? `${unaffected} ` : ""}This cannot be undone.`,
|
|
881
|
-
label: t(workspace || all ? "action.deleteAll" : "chat.delete"), danger: true,
|
|
899
|
+
label: t(selection ? "confirm.permanent.confirm" : workspace || all ? "action.deleteAll" : "chat.delete"), danger: true,
|
|
882
900
|
};
|
|
883
901
|
if (action === "trash") return {
|
|
884
|
-
title: zh ? "将该工作区的全部已归档聊天移入回收站?" : "Move all archived chats in this workspace to the Recycle Bin?",
|
|
885
|
-
body: zh ? `这将把${target}
|
|
886
|
-
: `This moves ${target} to the Recycle Bin. Other workspaces are unaffected. Restore them to Archived first, then unarchive to return them to the chat list.`,
|
|
887
|
-
label: zh ? "移入回收站" : "Move to Recycle Bin", danger: false,
|
|
902
|
+
title: zh ? (all ? "将全部已归档聊天移入回收站?" : selection ? "将选中的聊天移入回收站?" : "将该工作区的全部已归档聊天移入回收站?") : (all ? "Move all archived chats to the Recycle Bin?" : selection ? "Move selected chats to the Recycle Bin?" : "Move all archived chats in this workspace to the Recycle Bin?"),
|
|
903
|
+
body: zh ? `这将把${target}移入回收站。${workspace ? "其他工作区不受影响。" : selection ? "未选中的聊天不受影响。" : ""}之后可从回收站恢复到“已归档”,再取消归档以返回聊天列表。`
|
|
904
|
+
: `This moves ${target} to the Recycle Bin. ${workspace ? "Other workspaces are unaffected. " : selection ? "Unselected chats are unaffected. " : ""}Restore them to Archived first, then unarchive to return them to the chat list.`,
|
|
905
|
+
label: selection ? (zh ? "移入回收站" : "Move to Recycle Bin") : t("menu.deleteAll"), danger: false,
|
|
888
906
|
};
|
|
889
907
|
if (action === "unarchive") return {
|
|
890
|
-
title: zh ? (workspace ? "取消归档该工作区的全部聊天?" : "取消归档全部聊天?")
|
|
891
|
-
: (workspace ? "Unarchive all chats in this workspace?" : "Unarchive all chats?"),
|
|
908
|
+
title: zh ? (workspace ? "取消归档该工作区的全部聊天?" : selection ? "取消归档选中的聊天?" : "取消归档全部聊天?")
|
|
909
|
+
: (workspace ? "Unarchive all chats in this workspace?" : selection ? "Unarchive selected chats?" : "Unarchive all chats?"),
|
|
892
910
|
body: zh ? `这将把${target}恢复到聊天列表。聊天内容不会被删除。${unaffected}`
|
|
893
911
|
: `This returns ${target} to the chat list without deleting their content. ${unaffected}`,
|
|
894
|
-
label: t("menu.unarchiveAll"), danger: false,
|
|
912
|
+
label: t(selection ? "chat.unarchive" : "menu.unarchiveAll"), danger: false,
|
|
895
913
|
};
|
|
896
914
|
return {
|
|
897
|
-
title: zh ? (workspace ? "导出该工作区的全部已归档聊天?" : "导出全部已归档聊天?")
|
|
898
|
-
: (workspace ? "Export all archived chats in this workspace?" : "Export all archived chats?"),
|
|
915
|
+
title: zh ? (workspace ? "导出该工作区的全部已归档聊天?" : selection ? "导出选中的聊天?" : "导出全部已归档聊天?")
|
|
916
|
+
: (workspace ? "Export all archived chats in this workspace?" : selection ? "Export selected chats?" : "Export all archived chats?"),
|
|
899
917
|
body: zh ? `这将把${target}导出为 ZIP 备份,不会改变聊天的归档状态。回收站内容不包含在内。备份可能包含敏感内容,请妥善保存。`
|
|
900
918
|
: `This exports ${target} as a ZIP backup without changing their archived state. Recycle Bin contents are excluded. Backups may contain sensitive content; store them securely.`,
|
|
901
|
-
label: t("export.all"), danger: false,
|
|
919
|
+
label: t(selection ? "export.selected" : "export.all"), danger: false,
|
|
902
920
|
};
|
|
903
921
|
}
|
|
904
922
|
const deleteAllBody = (t, n) => isZh(t)
|
|
@@ -1322,6 +1340,15 @@ window.__ModuleLoader__.load({
|
|
|
1322
1340
|
.dac-select-wrap-fill,.dac-select-fill{width:100%;min-width:0}
|
|
1323
1341
|
.dac-select:hover{border-color:var(--dsw-alias-border-l1,var(--dsw-alias-border-l2))}
|
|
1324
1342
|
.dac-chevron{position:absolute;right:10px;pointer-events:none;color:var(--dsw-alias-label-tertiary);display:inline-flex}
|
|
1343
|
+
.dac-selectionbar{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}
|
|
1344
|
+
.dac-selection-info,.dac-selection-actions,.dac-selection-all{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}
|
|
1345
|
+
.dac-selection-all{font-size:13px;cursor:pointer;white-space:nowrap}
|
|
1346
|
+
.dac-selection-count{display:flex;flex-direction:column;gap:2px;font-size:13px;color:var(--dsw-alias-label-primary)}
|
|
1347
|
+
.dac-selection-count small{font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
1348
|
+
.dac-selection-actions{gap:6px}.dac-selection-actions .dac-danger{color:var(--dsw-alias-state-error-primary)}
|
|
1349
|
+
.dac-selection-cancel{border-color:transparent;background:transparent;color:var(--dsw-alias-label-secondary)}
|
|
1350
|
+
.dac-selection-all .dac-checkbox:focus-visible{outline:2px solid var(--dsw-alias-interactive-primary);outline-offset:3px}
|
|
1351
|
+
@media(max-width:640px){.dac-selection-actions{width:100%}.dac-selectionbar{gap:10px}.dac-selection-actions .dac-action-trigger{font-size:12px;padding:5px 8px}}
|
|
1325
1352
|
.dac-checkbox{width:16px;height:16px;margin:0;accent-color:var(--dsw-alias-interactive-primary,#6e6ef7);cursor:pointer;flex:none}
|
|
1326
1353
|
.dac-checkbox{color-scheme:light}[data-ds-dark-theme] .dac-checkbox{color-scheme:dark}
|
|
1327
1354
|
.dac-checkbox:disabled{cursor:default;opacity:.45}
|
|
@@ -1516,7 +1543,7 @@ window.__ModuleLoader__.load({
|
|
|
1516
1543
|
.dac-import-row.dac-import-conflict{opacity:.58;cursor:default}
|
|
1517
1544
|
.dac-import-main{display:flex;flex-direction:column;gap:2px;min-width:0;color:var(--dsw-alias-label-primary);font-size:13px;line-height:18px}
|
|
1518
1545
|
.dac-context-note{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;padding:9px 12px}
|
|
1519
|
-
.dac-insights-grid{display:grid;grid-template-columns:repeat(
|
|
1546
|
+
.dac-insights-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px}.dac-insights-card{display:flex;align-items:center;flex-direction:column;gap:5px;text-align:center;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);padding:14px}.dac-insights-card span{color:var(--dsw-alias-label-tertiary);font-size:12px}.dac-insights-card strong{color:var(--dsw-alias-label-primary);font-size:18px}.dac-policy{display:flex;flex-direction:column;gap:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:14px}.dac-policy-row{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.dac-policy-controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.dac-policy-label{color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap}.dac-policy .dac-select{min-width:100px;border-radius:8px}.dac-policy input{width:88px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);padding:7px 9px}.dac-policy-actions{display:flex;justify-content:flex-end;gap:8px;margin-left:auto}.dac-policy-actions .dac-btn{white-space:nowrap}.dac-retention-dialog{width:min(620px,calc(100vw - 48px));max-height:min(720px,calc(100vh - 48px))}.dac-retention-list{display:flex;flex-direction:column;gap:8px;overflow:auto}.dac-retention-row{display:flex;gap:10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;padding:9px}.dac-retention-row span{display:flex;flex-direction:column}.dac-retention-row small{color:var(--dsw-alias-label-tertiary)}.dac-retention-row.dac-danger strong{color:var(--dsw-alias-state-error-primary)}
|
|
1520
1547
|
.dac-lineage-toolbar{display:block;min-width:0}
|
|
1521
1548
|
.dac-lineage-filter-row{display:flex;flex-direction:column;gap:8px;min-width:0}
|
|
1522
1549
|
.dac-lineage-filter-row .dac-search{min-width:0}
|
|
@@ -1581,7 +1608,7 @@ window.__ModuleLoader__.load({
|
|
|
1581
1608
|
.dac-import-main small{color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:16px}
|
|
1582
1609
|
.dac-snapshot-row .dac-row-main{gap:3px}.dac-snapshot-id{white-space:normal;overflow-wrap:anywhere;word-break:break-word}
|
|
1583
1610
|
.dac-insights-card{min-height:98px;box-sizing:border-box}.dac-insights-open{align-self:flex-start;margin-top:auto;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;line-height:16px;padding:2px 5px;cursor:pointer}.dac-insights-open:hover,.dac-insights-open:focus-visible{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary)}.dac-insights-open:disabled{cursor:default;opacity:.45;background:transparent}
|
|
1584
|
-
.dac-residual-
|
|
1611
|
+
.dac-residual-card small{font-size:11px;color:var(--dsw-alias-label-secondary)}.dac-residual-card .dac-insights-open{align-self:center}.dac-residual-card strong.dac-residual-state{font-size:13px;font-weight:500}.dac-insights-card .dac-insights-warning{font-size:11px;line-height:16px;color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-secondary))}.dac-residual-search{box-sizing:border-box;width:100%;min-height:36px;padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}.dac-residual-toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-size:12px;color:var(--dsw-alias-label-secondary)}.dac-residual-list{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;overflow:auto;min-height:0;max-height: min(430px,calc(100vh - 300px))}.dac-residual-row{display:flex;align-items:flex-start;gap:12px;padding:14px}.dac-residual-row+.dac-residual-row{border-top:1px solid var(--dsw-alias-border-l2)}.dac-residual-main{display:flex;flex:1;min-width:0;flex-direction:column;gap:5px;font-size:13px;color:var(--dsw-alias-label-primary)}.dac-residual-main code,.dac-residual-main small{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere}.dac-residual-path{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dac-residual-row>.dac-row-size{white-space:nowrap}.dac-residual-dialog{overflow:hidden}.dac-residual-dialog .dac-notice{margin:0}
|
|
1585
1612
|
|
|
1586
1613
|
.dac-storage-dialog{width:min(720px,calc(100vw - 48px));max-height:min(720px,calc(100vh - 48px))}.dac-storage-detail-list{display:flex;flex-direction:column;gap:8px;max-height:min(460px,calc(100vh - 250px));overflow:auto}.dac-storage-detail-id{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;white-space:normal;overflow-wrap:anywhere}.dac-storage-dialog .dac-search{margin:0}
|
|
1587
1614
|
.dac-import-result{display:flex;flex-direction:column;gap:8px;color:var(--dsw-alias-state-success-primary);font-size:13px;line-height:20px}.dac-import-result-counts{display:flex;gap:14px;flex-wrap:wrap}.dac-import-warning{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.dac-import-error{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
|
|
@@ -3546,7 +3573,7 @@ window.__ModuleLoader__.load({
|
|
|
3546
3573
|
});
|
|
3547
3574
|
}
|
|
3548
3575
|
|
|
3549
|
-
function GroupSection({ group, t, collapsed, onToggleCollapsed, menuOpen, onToggleMenu, onUnarchive, onDelete, onPermanentDelete, onExport, onPreview, contentHits, busy, stats, metadataStatus, onEditMetadata, onArchive }) {
|
|
3576
|
+
function GroupSection({ group, t, collapsed, onToggleCollapsed, menuOpen, onToggleMenu, onUnarchive, onDelete, onPermanentDelete, onExport, onPreview, contentHits, busy, stats, metadataStatus, onEditMetadata, onArchive, selectionMode = false, selectedIds, onSelect }) {
|
|
3550
3577
|
const wrapRef = _react.useRef(null);
|
|
3551
3578
|
const closeMenu = () => {
|
|
3552
3579
|
const trigger = wrapRef.current?.querySelector?.('[data-dac-workspace-menu]');
|
|
@@ -3590,7 +3617,7 @@ window.__ModuleLoader__.load({
|
|
|
3590
3617
|
className: "dac-group-side",
|
|
3591
3618
|
children: [
|
|
3592
3619
|
(0, jsx.jsx)("span", { className: "dac-count", children: chatsCount(t, group.items.length) }),
|
|
3593
|
-
(0, jsx.jsx)("button", {
|
|
3620
|
+
!selectionMode && (0, jsx.jsx)("button", {
|
|
3594
3621
|
type: "button",
|
|
3595
3622
|
className: "dac-iconbtn",
|
|
3596
3623
|
"aria-label": "…",
|
|
@@ -3601,7 +3628,7 @@ window.__ModuleLoader__.load({
|
|
|
3601
3628
|
})
|
|
3602
3629
|
]
|
|
3603
3630
|
}),
|
|
3604
|
-
menuOpen && (0, jsx.jsxs)("div", {
|
|
3631
|
+
!selectionMode && menuOpen && (0, jsx.jsxs)("div", {
|
|
3605
3632
|
className: "dac-menu",
|
|
3606
3633
|
role: "menu",
|
|
3607
3634
|
children: onArchive ? [(0, jsx.jsx)("button", {
|
|
@@ -3655,6 +3682,7 @@ window.__ModuleLoader__.load({
|
|
|
3655
3682
|
(0, jsx.jsxs)("div", {
|
|
3656
3683
|
className: "dac-row-select",
|
|
3657
3684
|
children: [
|
|
3685
|
+
selectionMode && (0, jsx.jsx)(SelectionCheckbox, { checked: selectedIds.has(session.id), disabled: busy[session.id] === true, ariaLabel: `${isZh(t) ? "选择" : "Select"}: ${session.title ?? t("chat.untitled")}`, "data-session-id": session.id, onChange: checked => onSelect(session.id, checked) }),
|
|
3658
3686
|
(0, jsx.jsxs)("div", {
|
|
3659
3687
|
className: "dac-row-main",
|
|
3660
3688
|
children: [
|
|
@@ -3700,7 +3728,7 @@ window.__ModuleLoader__.load({
|
|
|
3700
3728
|
onClick: () => onPreview(session),
|
|
3701
3729
|
children: (0, jsx.jsx)(IconEye, {})
|
|
3702
3730
|
}),
|
|
3703
|
-
(0, jsx.jsx)("button", {
|
|
3731
|
+
!selectionMode && (0, jsx.jsx)("button", {
|
|
3704
3732
|
type: "button",
|
|
3705
3733
|
className: "dac-iconbtn",
|
|
3706
3734
|
"aria-label": t("tag.edit"),
|
|
@@ -3709,12 +3737,12 @@ window.__ModuleLoader__.load({
|
|
|
3709
3737
|
onClick: () => onEditMetadata(session),
|
|
3710
3738
|
children: (0, jsx.jsx)(IconEdit, {})
|
|
3711
3739
|
}),
|
|
3712
|
-
(0, jsx.jsx)("button", {
|
|
3740
|
+
!selectionMode && (0, jsx.jsx)("button", {
|
|
3713
3741
|
type: "button", className: "dac-unarchive",
|
|
3714
3742
|
disabled: busy[session.id] === true,
|
|
3715
3743
|
onClick: () => onUnarchive([session.id]), children: t("chat.unarchive")
|
|
3716
3744
|
}),
|
|
3717
|
-
(0, jsx.jsx)("button", {
|
|
3745
|
+
!selectionMode && (0, jsx.jsx)("button", {
|
|
3718
3746
|
type: "button",
|
|
3719
3747
|
className: "dac-unarchive dac-danger",
|
|
3720
3748
|
"aria-label": t("confirm.permanent.confirm"),
|
|
@@ -3843,26 +3871,16 @@ window.__ModuleLoader__.load({
|
|
|
3843
3871
|
}
|
|
3844
3872
|
|
|
3845
3873
|
|
|
3846
|
-
function ResidualCheckCard({ state, t, busy, onCheck, onView
|
|
3847
|
-
const
|
|
3848
|
-
const
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
ready && count > 0 && (0, jsx.jsx)("button", { className: "dac-btn", disabled: busy, onClick: onView, children: t("insights.details") }),
|
|
3857
|
-
(0, jsx.jsx)("button", { className: ready ? "dac-iconbtn" : "dac-btn", disabled: busy || state.status === "loading", "aria-label": t(ready ? "residual.recheck" : "residual.check"), title: t("residual.recheck"), onClick: onCheck, children: ready ? (0, jsx.jsx)(IconRefresh, {}) : t(state.status === "loading" ? "orphans.loading" : "residual.check") }),
|
|
3858
|
-
] }),
|
|
3859
|
-
] }),
|
|
3860
|
-
ready && (0, jsx.jsxs)("div", { className: "dac-residual-result", role: "status", children: [
|
|
3861
|
-
(0, jsx.jsx)("span", { children: count === 0 ? t("residual.empty") : t("residual.summary").replace("{count}", String(count)).replace("{size}", formatBytes(state.summary?.bytes)) }),
|
|
3862
|
-
state.checkedAt && (0, jsx.jsx)("small", { children: t("residual.checked").replace("{time}", new Date(state.checkedAt).toLocaleTimeString()) }),
|
|
3863
|
-
] }),
|
|
3864
|
-
(state.status === "error" || state.trashStatus === "unavailable") && (0, jsx.jsx)("div", { role: "alert", className: "dac-confirm-body", children: t(state.status === "error" ? "orphans.error" : "orphans.unavailable") }),
|
|
3865
|
-
notice && (0, jsx.jsx)(NoticeBanner, { notice, t, onDismiss }),
|
|
3874
|
+
function ResidualCheckCard({ state, t, busy, onCheck, onView }) {
|
|
3875
|
+
const loading = state.status === "idle" || state.status === "loading";
|
|
3876
|
+
const failed = state.status === "error" || (state.status === "ready" && state.trashStatus !== "ready");
|
|
3877
|
+
const ready = state.status === "ready" && !failed;
|
|
3878
|
+
const measured = state.sessions.length === 0 || state.sessions.some(row => Number.isFinite(row.sizeBytes));
|
|
3879
|
+
return (0, jsx.jsxs)("section", { className: "dac-insights-card dac-residual-card", "aria-labelledby": "dac-residual-heading", "aria-busy": loading, children: [
|
|
3880
|
+
(0, jsx.jsx)("span", { id: "dac-residual-heading", title: t("residual.note"), children: t("residual.cardTitle") }),
|
|
3881
|
+
(0, jsx.jsx)("strong", { role: "status", className: loading || failed ? "dac-residual-state" : void 0, children: ready ? t("residual.count").replace("{count}", String(state.sessions.length)) : t(failed ? "residual.failed" : "residual.checking") }),
|
|
3882
|
+
ready && (0, jsx.jsx)("small", { title: t("residual.summary").replace("{count}", String(state.sessions.length)).replace("{size}", measured ? formatBytes(state.summary?.bytes) : "—"), children: measured ? formatBytes(state.summary?.bytes) : "—" }),
|
|
3883
|
+
(0, jsx.jsx)("button", { type: "button", className: "dac-insights-open", "aria-label": t(failed ? "state.retry" : "residual.open"), disabled: busy || loading, onClick: failed ? onCheck : onView, children: t(failed ? "state.retry" : "residual.view") })
|
|
3866
3884
|
] });
|
|
3867
3885
|
}
|
|
3868
3886
|
|
|
@@ -3928,7 +3946,7 @@ window.__ModuleLoader__.load({
|
|
|
3928
3946
|
}) });
|
|
3929
3947
|
}
|
|
3930
3948
|
|
|
3931
|
-
function StorageRetentionPanel({ t, fallbackFocusRef }) {
|
|
3949
|
+
function StorageRetentionPanel({ t, fallbackFocusRef, children }) {
|
|
3932
3950
|
const [state, setState] = _react.useState({ status: "loading", value: null, error: null });
|
|
3933
3951
|
const [draft, setDraft] = _react.useState(null);
|
|
3934
3952
|
const [policyPreview, setPolicyPreview] = _react.useState(null);
|
|
@@ -3999,19 +4017,19 @@ window.__ModuleLoader__.load({
|
|
|
3999
4017
|
["insights.sessions", formatBytes(value.summary.sessionBytes), "sessions", (value.sessions ?? []).length, "insights.openSessions"],
|
|
4000
4018
|
["insights.snapshots", formatBytes(value.summary.snapshotBytes), "snapshots", (value.snapshots ?? []).length, "insights.openSnapshots"],
|
|
4001
4019
|
["insights.total", formatBytes(value.summary.totalMeasuredBytes), null, 0, null],
|
|
4002
|
-
["insights.duplicate", formatBytes(value.summary.duplicateSnapshotBytes), null, 0, null]
|
|
4003
|
-
["insights.unavailable", `${value.summary.sessionUnavailableCount} / ${value.summary.degradedSnapshotCount}`, null, 0, null]
|
|
4020
|
+
["insights.duplicate", formatBytes(value.summary.duplicateSnapshotBytes), null, 0, null]
|
|
4004
4021
|
];
|
|
4005
4022
|
return (0, jsx.jsxs)("div", { className: "dac-mode-panel", children: [
|
|
4006
4023
|
state.status === "loading" && (0, jsx.jsx)("div", { className: "dac-center", children: t("insights.loading") }),
|
|
4007
4024
|
state.status === "error" && (0, jsx.jsxs)("div", { className: "dac-center", children: [(0, jsx.jsx)("span", { children: t("insights.error") }), (0, jsx.jsx)("button", { className: "dac-retry", onClick: () => load(), children: t("state.retry") })] }),
|
|
4008
|
-
state.status === "ready" && (0, jsx.jsxs)("div", { className: "dac-mode-panel", children: [
|
|
4009
4025
|
(0, jsx.jsx)("div", { className: "dac-context-note", role: "note", children: t("insights.scopeNote") }),
|
|
4010
|
-
(0, jsx.
|
|
4026
|
+
(0, jsx.jsxs)("div", { className: "dac-insights-grid", children: [...cards.map(([label, display, mode, count, actionLabel]) => (0, jsx.jsxs)("div", { className: "dac-insights-card", style: { textAlign: "center", alignItems: "center" }, children: [
|
|
4011
4027
|
(0, jsx.jsx)("span", { children: t(label) }),
|
|
4012
|
-
(0, jsx.jsx)("strong", { children: display }),
|
|
4013
|
-
|
|
4014
|
-
|
|
4028
|
+
(0, jsx.jsx)("strong", { children: state.status === "ready" ? display : "—" }),
|
|
4029
|
+
(mode === "sessions" ? value.summary.sessionUnavailableCount : mode === "snapshots" ? value.summary.degradedSnapshotCount : 0) > 0 && (0, jsx.jsx)("small", { className: "dac-insights-warning", children: t(mode === "sessions" ? "insights.sessionIssues" : "insights.snapshotIssues").replace("{count}", String(mode === "sessions" ? value.summary.sessionUnavailableCount : value.summary.degradedSnapshotCount)) }),
|
|
4030
|
+
mode && (0, jsx.jsx)("button", { type: "button", className: "dac-insights-open", style: { alignSelf: "center" }, "aria-label": t(actionLabel), disabled: state.status !== "ready" || count === 0, onClick: () => openDetails(mode), children: t("insights.details") })
|
|
4031
|
+
] }, label)), children] }),
|
|
4032
|
+
state.status === "ready" && (0, jsx.jsxs)("div", { className: "dac-mode-panel", children: [
|
|
4015
4033
|
message && (0, jsx.jsxs)("div", { ...messageInteraction, className: messageKind === "ok" ? "dac-toast" : "dac-notice", role: messageKind === "ok" ? "status" : "alert", children: [message,
|
|
4016
4034
|
(0, jsx.jsx)("button", { type: "button", "aria-label": t("notice.dismiss"), onClick: () => setMessage(null), children: (0, jsx.jsx)(IconClose, {}) }),
|
|
4017
4035
|
] }),
|
|
@@ -4279,7 +4297,7 @@ window.__ModuleLoader__.load({
|
|
|
4279
4297
|
const links = about?.links ?? {};
|
|
4280
4298
|
return (0, jsx.jsxs)("section", { className: "dac-about", "aria-label": t("tab.about"), children: [
|
|
4281
4299
|
(0, jsx.jsxs)("div", { children: [
|
|
4282
|
-
(0, jsx.jsx)("p", { children: zh ? "本插件用于查看和管理 DeepSeek Harness
|
|
4300
|
+
(0, jsx.jsx)("p", { children: zh ? "本插件用于查看和管理 DeepSeek Harness 的归档会话,支持未归档管理、按工作区批量归档、备份、回收站恢复及残留会话检查。" : "This plugin lets you view and manage archived chats in DeepSeek Harness, with Unarchived management, workspace archiving, backups, Recycle Bin recovery and residual chat checks." }),
|
|
4283
4301
|
(0, jsx.jsxs)("div", { className: "dac-about-meta", children: [
|
|
4284
4302
|
(0, jsx.jsx)("span", { children: `${zh ? "当前版本" : "Current version"} · ${about ? `v${about.version}` : loading ? (zh ? "读取中…" : "Loading…") : (zh ? "暂不可用" : "Unavailable")}` }),
|
|
4285
4303
|
about && (0, jsx.jsx)("span", { children: `${zh ? "作者" : "Author"} · ${about.author}` }),
|
|
@@ -4435,6 +4453,26 @@ window.__ModuleLoader__.load({
|
|
|
4435
4453
|
const [unarchivedQuery, setUnarchivedQuery] = _react.useState("");
|
|
4436
4454
|
const [unarchivedWorkspace, setUnarchivedWorkspace] = _react.useState("all");
|
|
4437
4455
|
const [residualOpen, setResidualOpen] = _react.useState(false);
|
|
4456
|
+
const [selectionMode, setSelectionMode] = _react.useState(false);
|
|
4457
|
+
const [selectedIds, setSelectedIds] = _react.useState(() => new Set());
|
|
4458
|
+
const selectionEntryRef = _react.useRef(null);
|
|
4459
|
+
_react.useEffect(() => {
|
|
4460
|
+
if (sessions === null) return;
|
|
4461
|
+
const available = new Set(sessions.map(row => row.id));
|
|
4462
|
+
setSelectedIds(current => {
|
|
4463
|
+
const next = new Set([...current].filter(id => available.has(id)));
|
|
4464
|
+
return next.size === current.size ? current : next;
|
|
4465
|
+
});
|
|
4466
|
+
}, [sessions]);
|
|
4467
|
+
const toggleSelection = (id, checked) => setSelectedIds(current => {
|
|
4468
|
+
const next = new Set(current);
|
|
4469
|
+
if (checked) next.add(id); else next.delete(id);
|
|
4470
|
+
return next;
|
|
4471
|
+
});
|
|
4472
|
+
const cancelSelection = () => {
|
|
4473
|
+
setSelectionMode(false); setSelectedIds(new Set());
|
|
4474
|
+
setTimeout(() => selectionEntryRef.current?.focus?.(), 0);
|
|
4475
|
+
};
|
|
4438
4476
|
const orphanRequestRef = _react.useRef(null);
|
|
4439
4477
|
const orphanActionRef = _react.useRef(false);
|
|
4440
4478
|
const residualReturnFocusRef = _react.useRef(null);
|
|
@@ -4558,6 +4596,7 @@ window.__ModuleLoader__.load({
|
|
|
4558
4596
|
const noticeInteraction = useSuccessNoticeTimer(notice, notice?.kind === "ok", () => setNotice((current) => current === notice ? null : current));
|
|
4559
4597
|
const changePageMode = (mode) => {
|
|
4560
4598
|
if (mode !== pageMode) setNotice(null);
|
|
4599
|
+
if (mode !== "archived") { setSelectionMode(false); setSelectedIds(new Set()); }
|
|
4561
4600
|
setPageMode(mode);
|
|
4562
4601
|
};
|
|
4563
4602
|
|
|
@@ -4669,11 +4708,16 @@ window.__ModuleLoader__.load({
|
|
|
4669
4708
|
}, []);
|
|
4670
4709
|
|
|
4671
4710
|
_react.useEffect(() => {
|
|
4711
|
+
if (pageMode === "insights") {
|
|
4712
|
+
setOrphanBlocked(new Set());
|
|
4713
|
+
setOrphanSelection(new Set());
|
|
4714
|
+
void loadOrphans();
|
|
4715
|
+
}
|
|
4672
4716
|
return () => {
|
|
4673
4717
|
orphanRequestRef.current?.abort();
|
|
4674
4718
|
setOrphans(current => current.status === "loading" ? { ...current, status: "idle" } : current);
|
|
4675
4719
|
};
|
|
4676
|
-
}, [pageMode]);
|
|
4720
|
+
}, [pageMode, loadOrphans]);
|
|
4677
4721
|
|
|
4678
4722
|
|
|
4679
4723
|
// Statistics load independently after the archive list and never
|
|
@@ -5153,7 +5197,10 @@ window.__ModuleLoader__.load({
|
|
|
5153
5197
|
|
|
5154
5198
|
const askArchiveAction = (action, ids, scope) => {
|
|
5155
5199
|
const requested = uniqueSessionIds(ids);
|
|
5156
|
-
|
|
5200
|
+
if (requested.length === 0 || confirmActionRef.current || requested.some(id => busy[id] === true)) return;
|
|
5201
|
+
if (requested.some(id => !(sessions ?? []).some(row => row.id === id))) {
|
|
5202
|
+
setNotice({ kind: "error", text: isZh(t) ? "聊天列表已变化,请刷新后重新选择。" : "The chat list changed. Refresh and select again." }); return;
|
|
5203
|
+
}
|
|
5157
5204
|
deleteReturnFocusRef.current = document.activeElement;
|
|
5158
5205
|
setConfirm({ ...archiveActionConfirmation(t, action, requested, sessions ?? [], scope), ids: requested, action });
|
|
5159
5206
|
};
|
|
@@ -5440,6 +5487,18 @@ window.__ModuleLoader__.load({
|
|
|
5440
5487
|
}
|
|
5441
5488
|
|
|
5442
5489
|
const allIds = (sessions ?? []).map((s) => s.id);
|
|
5490
|
+
const visibleIds = groups.flatMap(group => group.items.map(row => row.id));
|
|
5491
|
+
const selectableIds = visibleIds.filter(id => busy[id] !== true);
|
|
5492
|
+
const selected = allIds.filter(id => selectedIds.has(id));
|
|
5493
|
+
const visibleSelected = selectableIds.filter(id => selectedIds.has(id)).length;
|
|
5494
|
+
const hiddenSelected = selected.filter(id => !visibleIds.includes(id)).length;
|
|
5495
|
+
const selectionBusy = selected.length === 0 || selected.some(id => busy[id] === true) || confirmBusy;
|
|
5496
|
+
const selectCurrentResults = checked => setSelectedIds(current => {
|
|
5497
|
+
const next = new Set(current);
|
|
5498
|
+
for (const id of selectableIds) { if (checked) next.add(id); else next.delete(id); }
|
|
5499
|
+
return next;
|
|
5500
|
+
});
|
|
5501
|
+
const askSelectionAction = action => askArchiveAction(action, selected, { kind: "selection" });
|
|
5443
5502
|
const allBusy = allIds.some((id) => busy[id] === true);
|
|
5444
5503
|
const filtering = query.trim() !== "" || projectFilter !== "all" || typeFilter !== "all" || tagFilter !== "";
|
|
5445
5504
|
const trashGroups = groupTrashSessions(trash.sessions);
|
|
@@ -5463,7 +5522,8 @@ window.__ModuleLoader__.load({
|
|
|
5463
5522
|
] }),
|
|
5464
5523
|
(0, jsx.jsxs)("div", {
|
|
5465
5524
|
className: "dac-head-actions",
|
|
5466
|
-
children: [pageMode === "archived" && (0, jsx.jsxs)(jsx.Fragment, { children: [
|
|
5525
|
+
children: [pageMode === "archived" && !selectionMode && (0, jsx.jsxs)(jsx.Fragment, { children: [
|
|
5526
|
+
(0, jsx.jsx)("button", { ref: selectionEntryRef, type: "button", className: "dac-action-trigger", disabled: allIds.length === 0, onClick: () => { setSelectedIds(new Set()); setMenuFor(null); setSelectionMode(true); }, children: isZh(t) ? "选择" : "Select" }),
|
|
5467
5527
|
(0, jsx.jsxs)(ChatActionMenu, {
|
|
5468
5528
|
t, toolbar: true, triggerLabel: isZh(t) ? "更多归档操作" : "More archive actions",
|
|
5469
5529
|
trigger: [(0, jsx.jsx)("span", { children: isZh(t) ? "更多" : "More" }), (0, jsx.jsx)(IconChevron, { size: 14 })],
|
|
@@ -5471,6 +5531,7 @@ window.__ModuleLoader__.load({
|
|
|
5471
5531
|
(0, jsx.jsx)("button", { type: "button", className: "dac-menu-item", disabled: importBusy, onClick: () => { importReturnFocusRef.current = document.activeElement; importInputRef.current?.click?.(); }, children: t("action.import") }),
|
|
5472
5532
|
(0, jsx.jsx)("button", { type: "button", className: "dac-menu-item", disabled: allIds.length === 0 || allBusy, onClick: () => askArchiveAction("export", allIds, { kind: "all" }), children: t("action.export") }),
|
|
5473
5533
|
(0, jsx.jsx)("button", { type: "button", className: "dac-menu-item", disabled: allIds.length === 0 || allBusy, onClick: () => askArchiveAction("unarchive", allIds, { kind: "all" }), children: t("menu.unarchiveAll") }),
|
|
5534
|
+
(0, jsx.jsx)("button", { type: "button", className: "dac-menu-item", disabled: allIds.length === 0 || allBusy, onClick: () => askArchiveAction("trash", allIds, { kind: "all" }), children: t("menu.deleteAll") }),
|
|
5474
5535
|
(0, jsx.jsx)("div", { role: "separator", className: "dac-menu-separator" }),
|
|
5475
5536
|
(0, jsx.jsx)("button", { type: "button", className: "dac-menu-item dac-danger", disabled: allIds.length === 0 || allBusy, onClick: () => askPermanentDelete(allIds, { kind: "all" }), children: t("action.deleteAll") }),
|
|
5476
5537
|
]
|
|
@@ -5564,7 +5625,25 @@ window.__ModuleLoader__.load({
|
|
|
5564
5625
|
}),
|
|
5565
5626
|
]
|
|
5566
5627
|
}),
|
|
5567
|
-
(0, jsx.
|
|
5628
|
+
selectionMode && (0, jsx.jsxs)("div", { className: "dac-selectionbar", children: [
|
|
5629
|
+
(0, jsx.jsxs)("div", { className: "dac-selection-info", children: [
|
|
5630
|
+
(0, jsx.jsxs)("label", { className: "dac-selection-all", children: [
|
|
5631
|
+
(0, jsx.jsx)(SelectionCheckbox, { checked: selectableIds.length > 0 && visibleSelected === selectableIds.length, indeterminate: visibleSelected > 0 && visibleSelected < selectableIds.length, disabled: selectableIds.length === 0 || confirmBusy, ariaLabel: isZh(t) ? "全选当前结果" : "Select all current results", onChange: selectCurrentResults }),
|
|
5632
|
+
(0, jsx.jsx)("span", { children: isZh(t) ? "全选" : "Select all" })
|
|
5633
|
+
] }),
|
|
5634
|
+
(0, jsx.jsxs)("span", { className: "dac-selection-count", role: "status", children: [
|
|
5635
|
+
(0, jsx.jsx)("span", { children: isZh(t) ? `已选 ${selected.length} 项` : `${selected.length} selected` }),
|
|
5636
|
+
hiddenSelected > 0 && (0, jsx.jsx)("small", { children: isZh(t) ? `${hiddenSelected} 项不在当前结果中` : `${hiddenSelected} outside current results` })
|
|
5637
|
+
] })
|
|
5638
|
+
] }),
|
|
5639
|
+
(0, jsx.jsxs)("div", { className: "dac-selection-actions", children: [
|
|
5640
|
+
(0, jsx.jsx)("button", { type: "button", className: "dac-action-trigger", disabled: selectionBusy, onClick: () => askSelectionAction("unarchive"), children: t("chat.unarchive") }),
|
|
5641
|
+
(0, jsx.jsx)("button", { type: "button", className: "dac-action-trigger dac-danger", disabled: selectionBusy, onClick: () => askSelectionAction("permanent"), children: t("confirm.permanent.confirm") }),
|
|
5642
|
+
(0, jsx.jsxs)(ChatActionMenu, { t, toolbar: true, triggerLabel: isZh(t) ? "更多选中项操作" : "More selection actions", trigger: [(0, jsx.jsx)("span", { children: isZh(t) ? "更多" : "More" }), (0, jsx.jsx)(IconDots, { size: 14 })], children: [(0, jsx.jsx)("button", { type: "button", className: "dac-menu-item", disabled: selectionBusy, onClick: () => askSelectionAction("export"), children: t("export.selected") }), (0, jsx.jsx)("button", { type: "button", className: "dac-menu-item", disabled: selectionBusy, onClick: () => askSelectionAction("trash"), children: isZh(t) ? "移入回收站" : "Move to Recycle Bin" })] }),
|
|
5643
|
+
(0, jsx.jsx)("button", { type: "button", className: "dac-action-trigger dac-selection-cancel", disabled: confirmBusy, onClick: cancelSelection, children: t("confirm.cancel") })
|
|
5644
|
+
] })
|
|
5645
|
+
] }),
|
|
5646
|
+
(0, jsx.jsx)(NoticeBanner, {
|
|
5568
5647
|
t, notice, interaction: noticeInteraction, top: toastTop(),
|
|
5569
5648
|
onDismiss: () => setNotice(null),
|
|
5570
5649
|
onUndoTrash: () => undoTrashMove(notice.ids),
|
|
@@ -5594,7 +5673,7 @@ window.__ModuleLoader__.load({
|
|
|
5594
5673
|
busy,
|
|
5595
5674
|
stats,
|
|
5596
5675
|
metadataStatus,
|
|
5597
|
-
onEditMetadata: openMetadataEditor
|
|
5676
|
+
onEditMetadata: openMetadataEditor, selectionMode, selectedIds, onSelect: toggleSelection
|
|
5598
5677
|
}, group.key)),
|
|
5599
5678
|
] }),
|
|
5600
5679
|
pageMode === "trash" && (0, jsx.jsxs)("div", { id: "dac-panel-trash", role: "tabpanel", "aria-labelledby": "dac-tab-trash", className: "dac-mode-panel", children: [
|
|
@@ -5622,8 +5701,8 @@ window.__ModuleLoader__.load({
|
|
|
5622
5701
|
}, group.key)),
|
|
5623
5702
|
] }),
|
|
5624
5703
|
pageMode === "insights" && (0, jsx.jsxs)("div", { id: "dac-panel-insights", role: "tabpanel", "aria-labelledby": "dac-tab-insights", className: "dac-mode-panel", children: [
|
|
5625
|
-
(0, jsx.jsx)(StorageRetentionPanel, { key: `insights-${workspaceArchiveRefresh}`, t, fallbackFocusRef: pageHeadingRef }),
|
|
5626
|
-
(0, jsx.jsx)(
|
|
5704
|
+
(0, jsx.jsx)(StorageRetentionPanel, { key: `insights-${workspaceArchiveRefresh}`, t, fallbackFocusRef: pageHeadingRef, children: (0, jsx.jsx)(ResidualCheckCard, { state: orphans, t, busy: orphansBusy, onCheck: () => { setNotice(null); setOrphanBlocked(new Set()); return loadOrphans(); }, onView: event => { residualReturnFocusRef.current = event?.currentTarget; setResidualOpen(true); } }) }),
|
|
5705
|
+
!residualOpen && (0, jsx.jsx)(NoticeBanner, { t, notice, interaction: noticeInteraction, top: toastTop(), onDismiss: () => setNotice(null) }),
|
|
5627
5706
|
] }),
|
|
5628
5707
|
pageMode === "lineage" && (0, jsx.jsx)("div", { id: "dac-panel-lineage", role: "tabpanel", "aria-labelledby": "dac-tab-lineage", children: (0, jsx.jsx)(ArchiveRelationshipsPanel, { t }) }),
|
|
5629
5708
|
pageMode === "unarchived" && (0, jsx.jsxs)("div", { id: "dac-panel-unarchived", role: "tabpanel", "aria-labelledby": "dac-tab-unarchived", className: "dac-mode-panel", children: [
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-archived-chats",
|
|
3
|
-
"version": "1.5.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.5.1",
|
|
4
|
+
"description": "查看、搜索和恢复已归档会话,支持未归档管理、工作区批量归档、备份、回收站与残留会话检查。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ultronen",
|
|
7
7
|
"repository": {
|