dsh-archived-chats 1.4.0 → 1.4.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 +20 -0
- package/README.md +17 -35
- package/README.zh-CN.md +13 -31
- package/docs/ARCHITECTURE.en.md +2 -2
- package/docs/ARCHITECTURE.md +2 -2
- package/docs/USER_GUIDE.md +16 -4
- package/docs/USER_GUIDE.zh-CN.md +14 -2
- package/lib/client.js +138 -36
- package/lib/types/client/index.d.ts +7 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@
|
|
|
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.4.1 — 2026-09-21
|
|
8
|
+
|
|
9
|
+
### 中文
|
|
10
|
+
|
|
11
|
+
- 修复恢复成功提示一直停留的问题:成功提示在 4 秒后收起,悬停或键盘聚焦时暂停计时,切换标签即清除;改善「查看已归档」按钮的明暗主题悬停对比度。
|
|
12
|
+
- 来源范围与回收站自动清理说明改为提示图标,支持鼠标、键盘与触屏;永久删除确认保留完整说明。仅确认有新版时在「关于」标签显示红点。
|
|
13
|
+
|
|
14
|
+
- 英文界面、设置入口与当前文档统一为 Archive Management,对应中文「归档管理」;包名和数据位置不变。
|
|
15
|
+
- 核对中英文 README、用户指南、架构与支持说明,补齐市场安装步骤,明确分叉 ZIP v2 能力及已发布 1.4.0 的英文菜单名称差异。当前 README 不再展示旧名称横幅或已退役流程的历史截图目录。
|
|
16
|
+
- 修正客户端类型说明中「删除会进入回收站」的旧描述:只有工作区菜单能移入回收站,删除需确认且不可恢复。
|
|
17
|
+
|
|
18
|
+
### English
|
|
19
|
+
|
|
20
|
+
- Fix persistent restore-success notices: dismiss after four seconds, pause on hover or keyboard focus, and clear on tab changes. Improve View Archived button hover contrast in light and dark themes.
|
|
21
|
+
- Move relationship-scope and automatic-cleanup explanations into mouse-, keyboard-, and touch-accessible help controls, keeping full permanent-deletion confirmations. Show an About-tab red dot only for a confirmed available update.
|
|
22
|
+
|
|
23
|
+
- Align the English UI, settings entry, and current documentation on Archive Management, matching 归档管理. The package identifier and data location are unchanged.
|
|
24
|
+
- Audit both READMEs, user guides, architecture, and support notes; add marketplace installation steps, clarify fork ZIP v2 support, and distinguish the English menu label in published 1.4.0. Remove the old-name banner and historical screenshot catalog from current READMEs.
|
|
25
|
+
- Correct the client declaration's outdated claim that Delete moves chats to the Recycle Bin: only workspace actions recycle chats; deletion requires confirmation and is irreversible.
|
|
26
|
+
|
|
7
27
|
## 1.4.0 — 2026-09-21
|
|
8
28
|
|
|
9
29
|
### 中文
|
package/README.md
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
[](https://dsh-insights.com/p/Ultronen/dsh-archived-chats/)
|
|
2
|
-
<p align="center">
|
|
3
|
-
<img src="assets/brand/archive-management-banner.png" alt="Session Archive: local-first archived-chat center for DeepSeek Harness" width="100%">
|
|
4
|
-
</p>
|
|
5
|
-
|
|
6
2
|
<div align="center">
|
|
7
3
|
|
|
8
|
-
<h1>
|
|
4
|
+
<h1>Archive Management</h1>
|
|
9
5
|
|
|
10
6
|
<p><strong>A local-first archived-chat center for DeepSeek Harness</strong></p>
|
|
11
7
|
<p><code>dsh-archived-chats</code></p>
|
|
@@ -27,19 +23,23 @@
|
|
|
27
23
|
|
|
28
24
|
</div>
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
Archive Management gives DeepSeek Harness a place to find chats hidden from the main chat area after archiving. It keeps DSH's single-chat archive workflow and adds workspace bulk archiving, search, preview, backup, and a workspace-based Recycle Bin workflow.
|
|
31
27
|
|
|
32
|
-
> The English name
|
|
28
|
+
> The English name is **Archive Management**, and the Chinese name is **归档管理**. The installation package remains `dsh-archived-chats`.
|
|
33
29
|
>
|
|
34
|
-
>
|
|
30
|
+
> This document follows the `main` branch. This guide targets 1.4.1, including the English rename and feedback improvements. Installed users should check the version differences in the upgrade notes. See the [changelog](CHANGELOG.md) for release history.
|
|
35
31
|
|
|
36
32
|
## Quick start
|
|
37
33
|
|
|
34
|
+
Search for `dsh-archived-chats` in DSH's plugin market, verify the author is **Ultronen**, and choose **Install**. After installation, follow the host's restart instructions. The settings entry is **归档管理** in Chinese; in English it is **Archive Management**.
|
|
35
|
+
|
|
36
|
+
Alternatively, run this command on the computer running DSH:
|
|
37
|
+
|
|
38
38
|
```sh
|
|
39
39
|
dsh plugin --profile web add dsh-archived-chats@latest
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Restart DSH, then open **Settings →
|
|
42
|
+
Restart DSH, then open **Settings → Archive Management** (Chinese: **设置 → 归档管理**).
|
|
43
43
|
|
|
44
44
|
Before updating an older installation, read “Upgrading from older releases” below. To update:
|
|
45
45
|
|
|
@@ -55,11 +55,11 @@ dsh plugin --profile web update dsh-archived-chats
|
|
|
55
55
|
| Workspace archiving | A settings-owned workspace chooser supports one or more workspaces and one aggregate confirmation; blank, active, or unreadable chats are skipped. |
|
|
56
56
|
| Read-only preview | Conversations, reasoning, tool activity, Markdown, JSON, code, and readable stored images without unarchiving. |
|
|
57
57
|
| Backup | Export one workspace or the entire archive as JSON + Markdown ZIP; preview imports and skip conflicting IDs. |
|
|
58
|
-
| Recycle Bin | Move archived chats by workspace
|
|
58
|
+
| Recycle Bin | Move archived chats by workspace; restore one chat, a workspace, or the entire Recycle Bin back to Archived. |
|
|
59
59
|
| Permanent deletion | Delete one archived chat, a workspace's archive, or all archived chats after confirmation. Recycle Bin deletion is separate. |
|
|
60
60
|
| Storage and relationships | Storage accounting, optional automatic Recycle Bin cleanup, and read-only Origins & Branches. |
|
|
61
61
|
|
|
62
|
-
The five views are **Archived**, **Recycle Bin**, **Storage & Retention**, **Origins & Branches**, and **About**.
|
|
62
|
+
The five views are **Archived**, **Recycle Bin**, **Storage & Retention**, **Origins & Branches**, and **About**. Archiving does not create historical versions; Recycle Bin protection snapshots support recovery, not a browsable version-history library.
|
|
63
63
|
|
|
64
64
|
The Archived header has **Bulk archive** and **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.
|
|
65
65
|
|
|
@@ -80,7 +80,7 @@ Empty Recycle Bin acts only on the exact recycle-record incarnations shown at co
|
|
|
80
80
|
|
|
81
81
|
## Data safety and limits
|
|
82
82
|
|
|
83
|
-
- **Local only:** plugin state lives under `$DSH_HOME/plugin-data/archived-chats/`; the plugin does not upload or cloud-sync chats.
|
|
83
|
+
- **Local only:** plugin state lives under `$DSH_HOME/plugin-data/archived-chats/`; the plugin does not upload or cloud-sync chats. Update checks request only public npm version metadata, without chat or backup content.
|
|
84
84
|
- **Recovery before purge:** workspace Recycle Bin moves require a healthy protection snapshot. Direct permanent deletion does not create a recoverable copy.
|
|
85
85
|
- **No overwrite:** imports skip existing IDs. Recycle recovery prefers the original; snapshot fallback recreates an ordinary session ID only through an explicitly exclusive Host writer and otherwise retains the recovery record.
|
|
86
86
|
- **Durable deletion:** confirmed deletion tasks clean related snapshots before the original. Partial failures retain non-restorable tasks for retries while running or after restart.
|
|
@@ -91,6 +91,10 @@ Successful exports are validated against the same format and budgets as import b
|
|
|
91
91
|
|
|
92
92
|
## Upgrading from older releases
|
|
93
93
|
|
|
94
|
+
**Names and versions:** Version 1.4.1 uses Archive Management; 1.4.0 labels its English menu Session Archive. Both correspond to **归档管理** in Chinese and are the same plugin. The Chinese entry, package name, install command, and data location are unchanged. Update to 1.4.1 or later and restart DSH to load the renamed English entry.
|
|
95
|
+
|
|
96
|
+
The standalone History and cleanup-preview interfaces have been removed; do not use older screenshots to locate them.
|
|
97
|
+
|
|
94
98
|
**Startup recovery removes old snapshots that are not referenced by current recycle records, including plugin-owned attachment copies. They are not placed in the Recycle Bin. This cleanup is independent of the optional Recycle Bin retention setting.**
|
|
95
99
|
|
|
96
100
|
Referenced protection snapshots are kept. Removing an unreferenced snapshot does not delete its source chat; already-confirmed deletion retries and enabled retention cleanup are separate operations.
|
|
@@ -113,28 +117,6 @@ Recycle Bin preview still depends on the original session; a missing original ma
|
|
|
113
117
|
|
|
114
118
|
The declared range remains capability-based. Release automation passed Node.js 18 on Ubuntu and Node.js 24 on Ubuntu, macOS, and Windows; the Node.js 24 matrix required the official Host backend integration (5/5) and package checks. The installed 1.4.0 artifact also passed the official native round trip (5/5) against `@deepseek-ai/dsh-session@0.1.5-rc.2`.
|
|
115
119
|
|
|
116
|
-
## Historical screenshots
|
|
117
|
-
|
|
118
|
-
The screenshot assets were captured for **v1.3.1**, using synthetic conversations in a Simplified Chinese light-theme environment. They are historical examples, **not current-version acceptance evidence**. In particular, `preview-04.png` and `preview-05.png` show retired snapshot workflows. Current behavior is defined by the guide above.
|
|
119
|
-
|
|
120
|
-
<details>
|
|
121
|
-
<summary>View the historical screenshot set</summary>
|
|
122
|
-
|
|
123
|
-
| Scene | Historical image |
|
|
124
|
-
| --- | --- |
|
|
125
|
-
| Archive overview | [preview-01](assets/screenshots/preview-01.png) |
|
|
126
|
-
| Full-text search | [preview-02](assets/screenshots/preview-02.png) |
|
|
127
|
-
| Workspace chooser | [preview-03](assets/screenshots/preview-03.png) |
|
|
128
|
-
| Retired snapshot preview | [preview-04](assets/screenshots/preview-04.png) |
|
|
129
|
-
| Recycle Bin with retired legacy rows | [preview-05](assets/screenshots/preview-05.png) |
|
|
130
|
-
| Empty Recycle Bin confirmation | [preview-06](assets/screenshots/preview-06.png) |
|
|
131
|
-
| Automatic cleanup confirmation | [preview-07](assets/screenshots/preview-07.png) |
|
|
132
|
-
| Origins and Branches | [preview-08](assets/screenshots/preview-08.png) |
|
|
133
|
-
|
|
134
|
-
</details>
|
|
135
|
-
|
|
136
|
-
The existing market screenshot manifest is unchanged. Recapture affected scenes against the release build before refreshing marketplace assets.
|
|
137
|
-
|
|
138
120
|
## Documentation
|
|
139
121
|
|
|
140
122
|
| Resource | English | 简体中文 |
|
|
@@ -147,7 +129,7 @@ See also [Support](SUPPORT.md), [Security](SECURITY.md), [Contributing](CONTRIBU
|
|
|
147
129
|
|
|
148
130
|
## Project status
|
|
149
131
|
|
|
150
|
-
|
|
132
|
+
Archive Management is actively maintained. The latest stable npm release receives fixes and security updates; older releases should be upgraded before reporting a problem. Reproducible bug reports and focused pull requests are welcome. Accepted community tasks are marked [`help wanted`](https://github.com/Ultronen/dsh-archived-chats/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22); read the [claim workflow](CONTRIBUTING.md#community-proposals-and-claims) before starting substantial work. Maintenance is performed as availability permits, so no fixed response or release schedule is promised.
|
|
151
133
|
|
|
152
134
|
## Development
|
|
153
135
|
|
package/README.zh-CN.md
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img src="assets/brand/archive-management-banner.png" alt="归档管理:面向 DeepSeek Harness 的本地优先归档聊天中心" width="100%">
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
1
|
<div align="center">
|
|
6
2
|
|
|
7
3
|
<h1>归档管理</h1>
|
|
@@ -28,12 +24,16 @@
|
|
|
28
24
|
|
|
29
25
|
归档管理为 DeepSeek Harness 中归档后从主页面会话区隐藏的聊天提供查看和管理入口。保留 DSH 原有单条归档方式,并扩展工作区批量归档、搜索、预览、备份及按工作区移入回收站的流程。
|
|
30
26
|
|
|
31
|
-
>
|
|
27
|
+
> 中文名称为「归档管理」,英文名称为 Archive Management;安装包名始终为 `dsh-archived-chats`。
|
|
32
28
|
>
|
|
33
|
-
>
|
|
29
|
+
> 本文跟随 `main` 分支维护。本文适用于 1.4.1,包含英文名称统一与交互提示优化;已安装用户请参阅升级说明中的版本差异。版本变更见[更新日志](CHANGELOG.md)。
|
|
34
30
|
|
|
35
31
|
## 快速开始
|
|
36
32
|
|
|
33
|
+
在 DSH 插件市场搜索 `dsh-archived-chats`,核对作者为 **Ultronen** 后点击「安装」。安装完成后按宿主提示重启 DSH,打开 **设置 → 归档管理**。
|
|
34
|
+
|
|
35
|
+
也可以在运行 DSH 的电脑上使用命令行安装:
|
|
36
|
+
|
|
37
37
|
```sh
|
|
38
38
|
dsh plugin --profile web add dsh-archived-chats@latest
|
|
39
39
|
```
|
|
@@ -54,11 +54,11 @@ dsh plugin --profile web update dsh-archived-chats
|
|
|
54
54
|
| 工作区批量归档 | 设置页的工作区选择器支持选择一个或多个工作区,汇总确认后归档;跳过空白、正在使用或无法确认内容的聊天。 |
|
|
55
55
|
| 只读预览 | 无需取消归档即可查看对话、思考、工具活动、Markdown、JSON、代码及可读取的已存储图片。 |
|
|
56
56
|
| 备份 | 支持按工作区或全部归档导出 JSON + Markdown ZIP;导入先预览,跳过冲突 ID。 |
|
|
57
|
-
| 回收站 |
|
|
57
|
+
| 回收站 | 按工作区移入回收站;支持单条、工作区和整个回收站恢复到已归档。 |
|
|
58
58
|
| 永久删除 | 确认后删除单条归档、工作区归档或全部归档;回收站删除独立操作。 |
|
|
59
59
|
| 空间与关系 | 空间分账、可选的回收站自动清理,以及只读「来源与分支」。 |
|
|
60
60
|
|
|
61
|
-
五个视图为 **已归档**、**回收站**、**空间与策略**、**来源与分支** 和
|
|
61
|
+
五个视图为 **已归档**、**回收站**、**空间与策略**、**来源与分支** 和 **关于**。归档不会创建历史版本;回收站保护快照仅用于恢复,不是可浏览的历史版本库。
|
|
62
62
|
|
|
63
63
|
已归档页顶部仅保留 **批量归档** 和 **更多**;「更多」依次提供导入备份、全部导出、全部取消归档,分隔线后为全部删除。回收站顶部直接并列显示 **全部恢复** 和 **清空回收站**,不再设更多菜单;聊天行保留预览图标,使用紧凑的 **恢复** 和 **删除** 文字按钮。工作区恢复和全局恢复分别确认范围、数量及已归档去向,跳过正在永久删除的条目;清空仍须确认不可恢复的后果。各 Tab 顶部保持一致尺寸。工作区操作及全局导出、取消归档、删除均先确认完整归档范围和聊天数量。
|
|
64
64
|
|
|
@@ -90,6 +90,10 @@ dsh plugin --profile web update dsh-archived-chats
|
|
|
90
90
|
|
|
91
91
|
## 从旧版升级
|
|
92
92
|
|
|
93
|
+
**名称与版本:** 1.4.1 的英文名称为 Archive Management,1.4.0 英文菜单显示 Session Archive;两者都对应中文「归档管理」,不是不同插件。中文入口、包名、安装命令和数据位置不变。更新至 1.4.1 或更高版本并重启 DSH 后,英文入口名称生效。
|
|
94
|
+
|
|
95
|
+
独立「历史版本」和「预览清理」界面已移除,不应再按旧版截图寻找这些入口。
|
|
96
|
+
|
|
93
97
|
**启动恢复流程会清理未被当前回收记录引用的旧快照及其插件附件副本,不会将它们放进回收站。这项清理不受回收站自动清理开关控制。**
|
|
94
98
|
|
|
95
99
|
当前回收记录引用的保护快照会保留。清理未引用快照不删除来源聊天本身;已确认删除任务的重试和已启用的到期清理是另外的操作。
|
|
@@ -112,28 +116,6 @@ dsh plugin --profile web update dsh-archived-chats
|
|
|
112
116
|
|
|
113
117
|
声明的版本范围仍以 Host 公开能力为准。发布自动化已通过 Ubuntu 上的 Node.js 18,以及 Ubuntu、macOS 和 Windows 上的 Node.js 24;Node.js 24 矩阵强制运行官方 Host 后端集成(5/5)与打包检查。已安装的 1.4.0 产物也已在 `@deepseek-ai/dsh-session@0.1.5-rc.2` 上通过官方 native 闭环(5/5)。
|
|
114
118
|
|
|
115
|
-
## 历史演示截图
|
|
116
|
-
|
|
117
|
-
现有截图拍摄于 **v1.3.1**,使用简体中文浅色环境和合成会话,仅供历史参考,**不代表当前版本验收结果**。其中 `preview-04.png`、`preview-05.png` 展示了已退役的快照流程;当前行为以上述指南为准。
|
|
118
|
-
|
|
119
|
-
<details>
|
|
120
|
-
<summary>查看历史截图目录</summary>
|
|
121
|
-
|
|
122
|
-
| 场景 | 历史图片 |
|
|
123
|
-
| --- | --- |
|
|
124
|
-
| 归档总览 | [preview-01](assets/screenshots/preview-01.png) |
|
|
125
|
-
| 全文搜索 | [preview-02](assets/screenshots/preview-02.png) |
|
|
126
|
-
| 工作区选择器 | [preview-03](assets/screenshots/preview-03.png) |
|
|
127
|
-
| 已退役的快照预览 | [preview-04](assets/screenshots/preview-04.png) |
|
|
128
|
-
| 含已退役旧快照条目的回收站 | [preview-05](assets/screenshots/preview-05.png) |
|
|
129
|
-
| 清空回收站确认 | [preview-06](assets/screenshots/preview-06.png) |
|
|
130
|
-
| 自动清理确认 | [preview-07](assets/screenshots/preview-07.png) |
|
|
131
|
-
| 来源与分支 | [preview-08](assets/screenshots/preview-08.png) |
|
|
132
|
-
|
|
133
|
-
</details>
|
|
134
|
-
|
|
135
|
-
现有市场截图清单未改动。更新市场素材前,应基于发布构建重新拍摄受影响场景。
|
|
136
|
-
|
|
137
119
|
## 文档
|
|
138
120
|
|
|
139
121
|
| 资料 | English | 简体中文 |
|
|
@@ -146,7 +128,7 @@ dsh plugin --profile web update dsh-archived-chats
|
|
|
146
128
|
|
|
147
129
|
## 项目状态
|
|
148
130
|
|
|
149
|
-
|
|
131
|
+
归档管理目前处于积极维护状态。最新 npm 稳定版会接收缺陷修复与安全更新;报告问题前请先从旧版本升级。欢迎可复现的缺陷报告和目标集中的 Pull Request。已开放认领的社区任务会标记为 [`help wanted`](https://github.com/Ultronen/dsh-archived-chats/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22);开始较大改动前,请先阅读[需求认领流程](CONTRIBUTING.md#社区需求与认领)。项目按维护者的可用时间推进,不承诺固定响应或发布时间。
|
|
150
132
|
|
|
151
133
|
## 开发
|
|
152
134
|
|
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
|
|
5
|
+
This document follows `main`, covering 1.4.1 behavior including the English rename and feedback improvements; 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
|
|
|
@@ -86,7 +86,7 @@ Metadata and recycle writes are serialized and published through temporary files
|
|
|
86
86
|
|
|
87
87
|
## Workspace bulk archive
|
|
88
88
|
|
|
89
|
-
The client registers `settings.section` and `shell.overlay`. Its workspace chooser lives in **Settings →
|
|
89
|
+
The client registers `settings.section` and `shell.overlay`. Its workspace chooser lives in **Settings → Archive Management / 设置 → 归档管理**, without depending on a workspace-menu extension or shared client store.
|
|
90
90
|
|
|
91
91
|
1. List workspaces containing at least one eligible chat.
|
|
92
92
|
2. Prepare each selected workspace separately. A preview binds at most 2,000 ordered IDs to a five-minute, single-use token/nonce.
|
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
|
-
|
|
5
|
+
本文跟随 `main` 分支,功能对应 1.4.1,包含英文名称统一与交互提示优化;版本变更见[更新日志](../CHANGELOG.md)。运行时代码是接口与行为的依据;用户操作说明与本文应保持一致。
|
|
6
6
|
|
|
7
7
|
## 产品边界与模块
|
|
8
8
|
|
|
@@ -86,7 +86,7 @@ Host 的归档注册表决定归档归属;正常可见归档列表排除回收
|
|
|
86
86
|
|
|
87
87
|
## 工作区批量归档
|
|
88
88
|
|
|
89
|
-
客户端注册 `settings.section` 和 `shell.overlay`,在 **Settings →
|
|
89
|
+
客户端注册 `settings.section` 和 `shell.overlay`,在 **Settings → Archive Management / 设置 → 归档管理** 内维护工作区选择器,不依赖工作区菜单扩展或共享客户端 store。
|
|
90
90
|
|
|
91
91
|
1. 列出至少有一条符合条件聊天的工作区。
|
|
92
92
|
2. 浏览器为每个选中工作区分别请求 preview;每个 preview 最多绑定 2,000 个有序 ID,凭据 token/nonce 有效 5 分钟且仅用一次。
|
package/docs/USER_GUIDE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Archive Management user guide
|
|
2
2
|
|
|
3
3
|
English · [简体中文](USER_GUIDE.zh-CN.md) · [Back to README](../README.md)
|
|
4
4
|
|
|
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. This guide targets 1.4.1, including the English rename and feedback improvements. Installed users should check the version differences in the upgrade notes. 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
|
|
|
@@ -16,11 +16,15 @@ Archiving is not deletion and creates no historical version. Moving to the Recyc
|
|
|
16
16
|
|
|
17
17
|
## Install and open
|
|
18
18
|
|
|
19
|
+
Search for `dsh-archived-chats` in DSH's plugin market, verify the author is **Ultronen**, and choose **Install**. After installation, follow the host's restart instructions. The settings entry is **归档管理** in Chinese; in English it is **Archive Management**.
|
|
20
|
+
|
|
21
|
+
Alternatively, run this command on the computer running DSH:
|
|
22
|
+
|
|
19
23
|
```sh
|
|
20
24
|
dsh plugin --profile web add dsh-archived-chats@latest
|
|
21
25
|
```
|
|
22
26
|
|
|
23
|
-
Restart DSH and open **Settings →
|
|
27
|
+
Restart DSH and open **Settings → Archive Management** (Chinese: **设置 → 归档管理**). The views are **Archived**, **Recycle Bin**, **Storage & Retention**, **Origins & Branches**, and **About**.
|
|
24
28
|
|
|
25
29
|
Before updating an older installation, read “Upgrades, old data, and downgrades” below, especially the snapshot-cleanup warning.
|
|
26
30
|
|
|
@@ -39,7 +43,7 @@ Only workspaces with eligible chats are listed. Blank sessions, chats in use, an
|
|
|
39
43
|
|
|
40
44
|
Search Archived by title, workspace, tags, notes, messages, and tool results. Filter by type, workspace, and tag; sort by time or title. Content matches show excerpts. Long workspace titles wrap to remain fully visible. Click a workspace folder or title to expand or collapse its chats; the open or closed folder reflects the current state. Group collapse state is saved in the browser.
|
|
41
45
|
|
|
42
|
-
Forked chats use the last valid title in their own event stream, including renames after branching. The Host creates a new chat ID with history inherited through the fork point; parent and child develop independently afterwards. Modern Hosts support preview
|
|
46
|
+
Forked chats use the last valid title in their own event stream, including renames after branching. The Host creates a new chat ID with history inherited through the fork point; parent and child develop independently afterwards. Modern Hosts support preview, Recycle Bin moves, and ZIP v2 export/import that preserves inherited history, even when the parent is absent. ZIPs do not include attachment bytes; see “Compatibility and limits” for older backups and Recycle Bin snapshot fallback.
|
|
43
47
|
|
|
44
48
|
## Preview, tags, and notes
|
|
45
49
|
|
|
@@ -143,6 +147,8 @@ This is separate from the old-snapshot cleanup below: **disabling automatic Recy
|
|
|
143
147
|
|
|
144
148
|
## Origins and Branches
|
|
145
149
|
|
|
150
|
+
Version 1.4.1 puts the scope explanation behind the help icon beside Managed chats. Hover, keyboard focus, or a touch tap reveals it. Auto-empty Recycle Bin on the storage page uses the same help control; enabling cleanup still shows the full permanent-deletion confirmation.
|
|
151
|
+
|
|
146
152
|
This read-only view shows sources, forks, and subagent trees for archived and recycled chats, plus the active-session context needed to explain their relationships. Unrelated active chats are not listed.
|
|
147
153
|
|
|
148
154
|
Chats are grouped by their actual workspace, with counts in workspace headings and browser-persisted workspace folding. Compact rows use title-side buttons to expand branches; initially only starting chats are shown, not every descendant. Details disclose full titles, timestamps, sources, and session IDs with Copy ID. Ungrouped chats have their own group.
|
|
@@ -153,6 +159,10 @@ Search temporarily opens matching workspaces and branches; clearing it restores
|
|
|
153
159
|
|
|
154
160
|
## About and updates
|
|
155
161
|
|
|
162
|
+
From 1.4.1, a red dot appears on the About tab only when a newer version is confirmed. Unchecked, failed, and up-to-date checks show no dot. Visiting About does not dismiss an available update.
|
|
163
|
+
|
|
164
|
+
Success notices within Archive Management, including View Archived after restoration, dismiss after four seconds and pause while hovered or keyboard-focused. Switching tabs clears the current notice. Errors do not auto-dismiss and can be closed manually. The native archive-success notice on the main chat page still closes after about three seconds. These interaction improvements are available from 1.4.1.
|
|
165
|
+
|
|
156
166
|
About shows the running plugin version, author, license, and direct links to the project, language-appropriate guide, changelog, issue feedback, and plugin market. The title also displays the running version.
|
|
157
167
|
|
|
158
168
|
Current version is read dynamically from the running backend, never substituted with the registry's latest version. A failed online check keeps the loaded version, identity, and links. Local metadata failure shows Unavailable and Reload information rather than an update-check error. If newly installed endpoints have not loaded yet, reload the DSH backend and retry.
|
|
@@ -163,6 +173,8 @@ When a newer version is found, Get update beside the title opens the plugin mark
|
|
|
163
173
|
|
|
164
174
|
## Upgrades, old data, and downgrades
|
|
165
175
|
|
|
176
|
+
**Names and versions:** Version 1.4.1 uses Archive Management; 1.4.0 labels its English menu Session Archive. Both correspond to **归档管理** in Chinese and are the same plugin. The Chinese entry, package name, install command, and data location are unchanged. Update to 1.4.1 or later and restart DSH to load the renamed English entry.
|
|
177
|
+
|
|
166
178
|
The standalone History and cleanup-preview interfaces are retired. Archiving no longer creates versions. Recycle Bin protection snapshots support recovery; they are not a browsable version-history library.
|
|
167
179
|
|
|
168
180
|
**After upgrading from an older release and starting the new version, startup recovery automatically removes old snapshots not referenced by current recycle records, including their plugin-owned attachment copies. They do not become Recycle Bin entries and cannot be recovered through the plugin afterward.** Referenced protection snapshots are retained. This cleanup does not delete source chats; retrying confirmed deletion tasks and enabled expiry cleanup are separate mechanisms.
|
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
|
|
5
|
+
归档管理为 DSH 已归档聊天提供查看和管理页面,并扩展按工作区批量归档。本文跟随 `main` 分支维护。本文适用于 1.4.1,包含英文名称统一与交互提示优化;已安装用户请参阅升级说明中的版本差异。版本变更见[更新日志](../CHANGELOG.md)。接口和数据格式见[架构说明](ARCHITECTURE.md)。
|
|
6
6
|
|
|
7
7
|
## 先理解三个位置
|
|
8
8
|
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
## 安装与打开
|
|
18
18
|
|
|
19
|
+
在 DSH 插件市场搜索 `dsh-archived-chats`,核对作者为 **Ultronen** 后点击「安装」。安装完成后按宿主提示重启 DSH,打开 **设置 → 归档管理**。
|
|
20
|
+
|
|
21
|
+
也可以在运行 DSH 的电脑上使用命令行安装:
|
|
22
|
+
|
|
19
23
|
```sh
|
|
20
24
|
dsh plugin --profile web add dsh-archived-chats@latest
|
|
21
25
|
```
|
|
@@ -39,7 +43,7 @@ dsh plugin --profile web add dsh-archived-chats@latest
|
|
|
39
43
|
|
|
40
44
|
在「已归档」中可以搜索标题、工作区、标签、备注、消息和工具结果,按类型、工作区、标签筛选,并按时间或标题排序。正文命中会显示摘要;工作区长标题自动换行、完整显示。点击工作区文件夹或标题可展开/收起聊天,文件夹开合样式表示当前状态,分组折叠状态保存在浏览器中。
|
|
41
45
|
|
|
42
|
-
分叉会话读取自身事件流中最后一个有效标题,包括分叉后的重命名。官方分叉创建独立 ID 的新会话,继承分叉点之前的历史,之后独立发展,不会跟随父会话继续同步。新版 Host
|
|
46
|
+
分叉会话读取自身事件流中最后一个有效标题,包括分叉后的重命名。官方分叉创建独立 ID 的新会话,继承分叉点之前的历史,之后独立发展,不会跟随父会话继续同步。新版 Host 上支持预览、移入回收站,以及保留继承历史的 ZIP v2 导出和导入。父会话不存在时也可从 v2 备份恢复;附件文件本体不包含在 ZIP 中,旧版备份与回收站快照回退的限制见「兼容性与限制」。
|
|
43
47
|
|
|
44
48
|
## 预览、标签与备注
|
|
45
49
|
|
|
@@ -143,6 +147,8 @@ DSH 运行时约每分钟检查,关闭期间暂停,启动恢复完成后补
|
|
|
143
147
|
|
|
144
148
|
## 来源与分支
|
|
145
149
|
|
|
150
|
+
1.4.1 将管理范围说明收起到「已管理会话」旁的提示图标;将鼠标放上去、用键盘聚焦或触屏点击即可查看。存储页「回收站自动清理」旁也使用相同的说明入口,开启清理时的永久删除确认仍会完整显示。
|
|
151
|
+
|
|
146
152
|
只读展示归档和回收聊天的来源、分叉、子代理树,以及解释这些关系所需的活动会话上下文,不列出无关活动聊天。
|
|
147
153
|
|
|
148
154
|
会话按实际所属工作区分组,工作区标题显示数量,折叠状态保存在浏览器。每条使用紧凑关系行,标题左侧箭头展开分支;默认先展示起始会话,不自动铺开全部后代。「详情」按需显示完整标题、时间、来源和会话 ID,并保留复制 ID。未分组聊天有独立分组。
|
|
@@ -153,6 +159,10 @@ DSH 运行时约每分钟检查,关闭期间暂停,启动恢复完成后补
|
|
|
153
159
|
|
|
154
160
|
## 关于与更新
|
|
155
161
|
|
|
162
|
+
从 1.4.1 起,仅确认有新版本时,「关于」标签右上角显示红点;尚未检查、检查失败或已是最新版时不显示。打开「关于」不会清除仍可用的新版本提示。
|
|
163
|
+
|
|
164
|
+
归档管理页面内的操作成功提示(包括恢复后的「查看已归档」)在 4 秒后自动收起,鼠标悬停或键盘焦点停留时暂停计时。切换标签会清除当前提示;错误提示不会自动消失,可以手动关闭。主聊天页面的原生归档成功提示仍约 3 秒后关闭。这些交互改进自 1.4.1 起提供。
|
|
165
|
+
|
|
156
166
|
「关于」展示正在运行的插件版本、作者、许可证,并直达项目主页、对应语言的使用指南、更新日志、问题反馈和插件市场。页面标题旁也显示当前运行版本。
|
|
157
167
|
|
|
158
168
|
「当前版本」动态读取正在运行的后端,不使用网上最新版代替。联网检查失败不会清除已读取的版本、作者或链接;本地信息读取失败则显示「暂不可用」和「重新读取」,不误报为检查更新失败。若刚更新插件但新接口尚未加载,请重新加载 DSH 后端后重试。
|
|
@@ -163,6 +173,8 @@ DSH 运行时约每分钟检查,关闭期间暂停,启动恢复完成后补
|
|
|
163
173
|
|
|
164
174
|
## 升级、旧数据与降级
|
|
165
175
|
|
|
176
|
+
**名称与版本:** 1.4.1 的英文名称为 Archive Management,1.4.0 英文菜单显示 Session Archive;两者都对应中文「归档管理」,不是不同插件。中文入口、包名、安装命令和数据位置不变。更新至 1.4.1 或更高版本并重启 DSH 后,英文入口名称生效。
|
|
177
|
+
|
|
166
178
|
独立「历史版本」和「预览清理」界面已退役。归档不再创建历史版本;回收站保护快照只用于恢复,不是用户可以浏览的历史版本库。
|
|
167
179
|
|
|
168
180
|
**从旧版本升级并启动新版后,启动恢复流程会自动删除没有被当前回收记录引用的旧快照,包括相应的插件附件副本;这些快照不会转成回收站条目,也无法通过插件恢复。** 当前回收记录引用的保护快照会保留。此清理不删除来源聊天本身;已确认的待删除任务重试及已启用的到期清理是另外的机制。
|
package/lib/client.js
CHANGED
|
@@ -197,6 +197,8 @@ window.__ModuleLoader__.load({
|
|
|
197
197
|
"tab.insights": "空间与策略",
|
|
198
198
|
"tab.lineage": "来源与分支",
|
|
199
199
|
"tab.about": "关于",
|
|
200
|
+
"about.updateAvailable": "有新版本可用",
|
|
201
|
+
"help.label": "{label}说明",
|
|
200
202
|
"trash.viewArchived": "查看已归档",
|
|
201
203
|
"trash.listUnverified": "回收站目录无法读取:下面的列表可能包含已删除的聊天,归档修改已暂停。",
|
|
202
204
|
"insights.loading": "正在分析空间…",
|
|
@@ -468,7 +470,7 @@ window.__ModuleLoader__.load({
|
|
|
468
470
|
};
|
|
469
471
|
const en = {
|
|
470
472
|
"locale.intl": "en-US",
|
|
471
|
-
"nav": "
|
|
473
|
+
"nav": "Archive Management",
|
|
472
474
|
"archiveNotice.title": "Chat archived",
|
|
473
475
|
"archiveNotice.view": "View",
|
|
474
476
|
"archiveNotice.opening": "Opening…",
|
|
@@ -478,13 +480,15 @@ window.__ModuleLoader__.load({
|
|
|
478
480
|
"archiveNotice.close": "Close archive notice",
|
|
479
481
|
"archiveNotice.settings": "Settings",
|
|
480
482
|
"archiveNotice.undoError": "Undo failed. Try again",
|
|
481
|
-
"archiveNotice.viewError": "Could not open
|
|
482
|
-
"page.title": "
|
|
483
|
+
"archiveNotice.viewError": "Could not open Archive Management. Try again",
|
|
484
|
+
"page.title": "Archive Management",
|
|
483
485
|
"tab.archived": "Archived",
|
|
484
486
|
"tab.trash": "Recycle Bin",
|
|
485
487
|
"tab.insights": "Storage & Retention",
|
|
486
488
|
"tab.lineage": "Origins & Branches",
|
|
487
489
|
"tab.about": "About",
|
|
490
|
+
"about.updateAvailable": "Update available",
|
|
491
|
+
"help.label": "Help: {label}",
|
|
488
492
|
"trash.viewArchived": "View Archived",
|
|
489
493
|
"trash.listUnverified": "The Recycle Bin catalog is unreadable: this list may include deleted chats, and archive changes are paused.",
|
|
490
494
|
"insights.loading": "Analyzing storage…",
|
|
@@ -729,8 +733,8 @@ window.__ModuleLoader__.load({
|
|
|
729
733
|
"workspaceArchive.loading": "Preparing archive…",
|
|
730
734
|
"workspaceArchive.skipped": "Skipped chats",
|
|
731
735
|
"workspaceArchive.confirmTitle": "Archive {count} chats?",
|
|
732
|
-
"workspaceArchive.confirmBody": "This will archive chats in \"{workspace}\". You can find them later under Archived in
|
|
733
|
-
"workspaceArchive.confirmBodyMany": "This will archive chats in {count} workspaces. You can find them later under Archived in
|
|
736
|
+
"workspaceArchive.confirmBody": "This will archive chats in \"{workspace}\". You can find them later under Archived in Archive Management.",
|
|
737
|
+
"workspaceArchive.confirmBodyMany": "This will archive chats in {count} workspaces. You can find them later under Archived in Archive Management.",
|
|
734
738
|
"workspaceArchive.skippedLive": "Skipping {count} running chats or chats with unconfirmed status.",
|
|
735
739
|
"workspaceArchive.apply": "Archive all",
|
|
736
740
|
"workspaceArchive.applying": "Archiving…",
|
|
@@ -1191,6 +1195,13 @@ window.__ModuleLoader__.load({
|
|
|
1191
1195
|
.dac-tabs{display:flex;align-items:center;gap:4px;border-bottom:1px solid var(--dsw-alias-border-l2);overflow-x:auto}
|
|
1192
1196
|
.dac-tab{flex:none;border:0;border-bottom:2px solid transparent;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;line-height:20px;padding:8px 12px;cursor:pointer}
|
|
1193
1197
|
.dac-tab[aria-selected="true"]{border-bottom-color:var(--dsw-alias-label-primary,#171717);color:var(--dsw-alias-label-primary,#171717);font-weight:500}
|
|
1198
|
+
.dac-tab-label{position:relative;display:inline-block}
|
|
1199
|
+
.dac-update-dot{position:absolute;right:-7px;top:0;width:6px;height:6px;border-radius:50%;background:#dc2626;pointer-events:none}
|
|
1200
|
+
.dac-help-label{display:inline-flex;align-items:center;gap:5px}
|
|
1201
|
+
.dac-help{display:inline-flex;align-items:center;font-weight:400}
|
|
1202
|
+
.dac-help-button{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:4px;border:0;border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
|
|
1203
|
+
.dac-help-button:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
|
|
1204
|
+
.dac-help-button:focus-visible,.dac-toast button:focus-visible{outline:2px solid var(--dsw-alias-label-primary);outline-offset:2px}
|
|
1194
1205
|
.dac-mode-panel{display:flex;flex-direction:column;gap:14px}
|
|
1195
1206
|
.dac-action-wrap{position:relative;display:inline-flex}
|
|
1196
1207
|
.dac-action-trigger{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;padding:5px 11px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;white-space:nowrap;cursor:pointer}
|
|
@@ -1280,8 +1291,9 @@ window.__ModuleLoader__.load({
|
|
|
1280
1291
|
.dac-notice button{border:none;background:transparent;color:inherit;cursor:pointer;font:inherit;padding:0;display:inline-flex}
|
|
1281
1292
|
.dac-toast{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:1200;display:flex;align-items:center;gap:8px;border:1px solid var(--dsw-alias-state-success-secondary);border-radius:999px;background:var(--dsw-alias-state-success-tertiary);color:var(--dsw-alias-state-success-primary);font-size:13px;font-weight:500;line-height:20px;padding:7px 12px 7px 14px;box-shadow:var(--dsw-shadow-lv3);animation:dac-toast-in .28s cubic-bezier(.21,1.02,.55,1) both}
|
|
1282
1293
|
.dac-toast svg{flex:none}
|
|
1283
|
-
.dac-toast button{border:none;background:
|
|
1284
|
-
.dac-toast button
|
|
1294
|
+
.dac-toast button{border:none;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);opacity:1;cursor:pointer;font:inherit;padding:3px;margin-left:2px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px}
|
|
1295
|
+
.dac-toast button.dac-notice-action{padding:3px 9px;white-space:nowrap}
|
|
1296
|
+
.dac-toast button:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
|
|
1285
1297
|
@keyframes dac-toast-in{from{opacity:0;transform:translateX(-50%) translateY(-10px) scale(.96)}to{opacity:1;transform:translateX(-50%) translateY(0) scale(1)}}
|
|
1286
1298
|
@media (prefers-reduced-motion:reduce){.dac-toast{animation:none}}
|
|
1287
1299
|
.dac-archive-notice{position:absolute;top:20px;left:50%;z-index:30;display:flex;align-items:center;gap:8px;width:max-content;max-width:calc(100% - 24px);min-height:42px;box-sizing:border-box;transform:translateX(-50%);border:1px solid var(--dsw-alias-border-l2);border-radius:11px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);padding:5px 8px 5px 12px;box-shadow:var(--dsw-shadow-lv3);animation:dac-archive-notice-in .2s cubic-bezier(.21,1.02,.55,1) both}
|
|
@@ -1391,8 +1403,6 @@ window.__ModuleLoader__.load({
|
|
|
1391
1403
|
.dac-import-row.dac-import-conflict{opacity:.58;cursor:default}
|
|
1392
1404
|
.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}
|
|
1393
1405
|
.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}
|
|
1394
|
-
.dac-policy-bottom .dac-policy-note{flex:1;min-width:0}.dac-policy-bottom .dac-policy-actions{flex-shrink:0}.dac-policy-bottom{flex-wrap:nowrap}
|
|
1395
|
-
.dac-policy-note{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
|
|
1396
1406
|
.dac-insights-grid{display:grid;grid-template-columns:repeat(4,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)}
|
|
1397
1407
|
.dac-lineage-toolbar{display:block;min-width:0}
|
|
1398
1408
|
.dac-lineage-filter-row{display:flex;flex-direction:column;gap:8px;min-width:0}
|
|
@@ -1454,7 +1464,6 @@ window.__ModuleLoader__.load({
|
|
|
1454
1464
|
.dac-lineage-trash{background:var(--dsw-alias-interactive-bg-hover-danger,var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12)));color:var(--dsw-alias-state-error-primary)}
|
|
1455
1465
|
.dac-lineage-diagnostics{grid-column:1/-1;display:flex;flex-direction:column;gap:3px;margin-left:34px}
|
|
1456
1466
|
.dac-lineage-diagnostic{display:block;color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:17px;white-space:normal;overflow-wrap:anywhere;word-break:break-word}
|
|
1457
|
-
.dac-lineage-scopebar{border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px;padding:9px 11px}
|
|
1458
1467
|
.dac-import-main span{color:var(--dsw-alias-label-tertiary);font-size:11px}
|
|
1459
1468
|
.dac-import-main small{color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:16px}
|
|
1460
1469
|
.dac-snapshot-row .dac-row-main{gap:3px}.dac-snapshot-id{white-space:normal;overflow-wrap:anywhere;word-break:break-word}
|
|
@@ -1527,7 +1536,7 @@ window.__ModuleLoader__.load({
|
|
|
1527
1536
|
// button's icon in place — mutating the existing <svg> (never replacing
|
|
1528
1537
|
// React-managed nodes) and re-marking through dataset, so locale
|
|
1529
1538
|
// re-renders and dialog reopens simply get re-patched by the observer.
|
|
1530
|
-
const NAV_LABELS = ["归档管理", "
|
|
1539
|
+
const NAV_LABELS = ["归档管理", "Archive Management"];
|
|
1531
1540
|
const ARCHIVE_ICON_INNER = '<rect x="3" y="4" width="18" height="5" rx="1"></rect><path d="M5 9v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9"></path><path d="M10 13h4"></path>';
|
|
1532
1541
|
|
|
1533
1542
|
function safeQueryAll(root, selector) {
|
|
@@ -3238,6 +3247,101 @@ window.__ModuleLoader__.load({
|
|
|
3238
3247
|
});
|
|
3239
3248
|
}
|
|
3240
3249
|
|
|
3250
|
+
// Both pointer and keyboard interaction pause the remaining display time.
|
|
3251
|
+
function useSuccessNoticeTimer(value, enabled, onDismiss) {
|
|
3252
|
+
const [interaction, setInteraction] = _react.useState(null);
|
|
3253
|
+
const remaining = _react.useRef({ value: null, ms: 4000 });
|
|
3254
|
+
const paused = interaction?.value === value && (interaction.hover || interaction.focus);
|
|
3255
|
+
const latest = _react.useRef(null);
|
|
3256
|
+
latest.current = { value, enabled, paused };
|
|
3257
|
+
_react.useEffect(() => {
|
|
3258
|
+
if (remaining.current.value !== value) remaining.current = { value, ms: 4000 };
|
|
3259
|
+
if (!enabled || paused) return;
|
|
3260
|
+
const started = Date.now();
|
|
3261
|
+
let active = true;
|
|
3262
|
+
const timer = setTimeout(() => {
|
|
3263
|
+
if (active && latest.current.value === value && latest.current.enabled && !latest.current.paused) {
|
|
3264
|
+
active = false;
|
|
3265
|
+
onDismiss();
|
|
3266
|
+
}
|
|
3267
|
+
}, remaining.current.ms);
|
|
3268
|
+
return () => {
|
|
3269
|
+
active = false;
|
|
3270
|
+
clearTimeout(timer);
|
|
3271
|
+
remaining.current.ms = Math.max(0, remaining.current.ms - Math.max(0, Date.now() - started));
|
|
3272
|
+
};
|
|
3273
|
+
}, [value, enabled, paused]);
|
|
3274
|
+
const set = (key, next) => setInteraction((current) => ({
|
|
3275
|
+
...(current?.value === value ? current : { value, hover: false, focus: false }), [key]: next,
|
|
3276
|
+
}));
|
|
3277
|
+
return {
|
|
3278
|
+
onMouseEnter: () => set("hover", true), onMouseLeave: () => set("hover", false),
|
|
3279
|
+
onFocus: () => set("focus", true),
|
|
3280
|
+
onBlur: (event) => { if (!event.currentTarget.contains(event.relatedTarget)) set("focus", false); },
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
function HelpTooltip({ id, label, children }) {
|
|
3285
|
+
const [position, setPosition] = _react.useState(null);
|
|
3286
|
+
const wrapRef = _react.useRef(null);
|
|
3287
|
+
const hovered = _react.useRef(false);
|
|
3288
|
+
const focused = _react.useRef(false);
|
|
3289
|
+
const pinned = _react.useRef(false);
|
|
3290
|
+
const close = () => { pinned.current = false; setPosition(null); };
|
|
3291
|
+
const show = () => {
|
|
3292
|
+
const rect = wrapRef.current?.getBoundingClientRect();
|
|
3293
|
+
if (!rect) return;
|
|
3294
|
+
const viewportWidth = window.innerWidth || 1024;
|
|
3295
|
+
const viewportHeight = window.innerHeight || 768;
|
|
3296
|
+
const width = Math.min(360, viewportWidth - 24);
|
|
3297
|
+
const below = viewportHeight - rect.bottom - 12;
|
|
3298
|
+
const above = rect.top - 12;
|
|
3299
|
+
const placeBelow = below >= Math.min(200, above);
|
|
3300
|
+
setPosition({ width, boxSizing: "border-box", left: Math.max(12, Math.min(rect.left, viewportWidth - width - 12)),
|
|
3301
|
+
maxHeight: Math.max(40, Math.min(240, placeBelow ? below : above)),
|
|
3302
|
+
...(placeBelow ? { top: rect.bottom } : { bottom: viewportHeight - rect.top }) });
|
|
3303
|
+
};
|
|
3304
|
+
_react.useEffect(() => {
|
|
3305
|
+
if (position === null) return;
|
|
3306
|
+
const outside = (event) => { if (!wrapRef.current?.contains(event.target)) close(); };
|
|
3307
|
+
const escape = (event) => {
|
|
3308
|
+
if (event.key !== "Escape") return;
|
|
3309
|
+
event.preventDefault();
|
|
3310
|
+
event.stopPropagation();
|
|
3311
|
+
event.stopImmediatePropagation?.();
|
|
3312
|
+
close();
|
|
3313
|
+
};
|
|
3314
|
+
document.addEventListener("keydown", escape, true);
|
|
3315
|
+
document.addEventListener("pointerdown", outside);
|
|
3316
|
+
document.addEventListener("focusin", outside);
|
|
3317
|
+
window.addEventListener("scroll", outside, true);
|
|
3318
|
+
window.addEventListener("resize", close);
|
|
3319
|
+
return () => {
|
|
3320
|
+
document.removeEventListener("keydown", escape, true);
|
|
3321
|
+
document.removeEventListener("pointerdown", outside);
|
|
3322
|
+
document.removeEventListener("focusin", outside);
|
|
3323
|
+
window.removeEventListener("scroll", outside, true);
|
|
3324
|
+
window.removeEventListener("resize", close);
|
|
3325
|
+
};
|
|
3326
|
+
}, [position]);
|
|
3327
|
+
return (0, jsx.jsxs)("span", { className: "dac-help", ref: wrapRef,
|
|
3328
|
+
onMouseEnter: () => { hovered.current = true; show(); },
|
|
3329
|
+
onMouseLeave: () => { hovered.current = false; if (!focused.current && !pinned.current) close(); },
|
|
3330
|
+
onFocus: () => { focused.current = true; show(); },
|
|
3331
|
+
onBlur: (event) => { if (!event.currentTarget.contains(event.relatedTarget)) { focused.current = false; if (!hovered.current) close(); } },
|
|
3332
|
+
onKeyDown: (event) => { if (event.key === "Escape" && position !== null) { event.preventDefault(); event.stopPropagation(); close(); } },
|
|
3333
|
+
children: [
|
|
3334
|
+
(0, jsx.jsx)("button", { type: "button", className: "dac-help-button", "aria-label": label,
|
|
3335
|
+
"aria-describedby": position !== null ? id : void 0,
|
|
3336
|
+
onClick: () => { if (pinned.current) close(); else { pinned.current = true; show(); } },
|
|
3337
|
+
children: (0, jsx.jsxs)("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.8, "aria-hidden": true, children: [
|
|
3338
|
+
(0, jsx.jsx)("circle", { cx: 12, cy: 12, r: 9 }),
|
|
3339
|
+
(0, jsx.jsx)("path", { d: "M12 11v6M12 7v1", strokeLinecap: "round" }),
|
|
3340
|
+
] }) }),
|
|
3341
|
+
position !== null && (0, jsx.jsx)("span", { id, role: "tooltip", className: "dac-note-tooltip", style: position, children }),
|
|
3342
|
+
] });
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3241
3345
|
function NotePreview({ note, sessionId, t }) {
|
|
3242
3346
|
const [position, setPosition] = _react.useState(null);
|
|
3243
3347
|
const wrapRef = _react.useRef(null);
|
|
@@ -3590,11 +3694,7 @@ window.__ModuleLoader__.load({
|
|
|
3590
3694
|
} catch (error) { if (!signal?.aborted) setState({ status: "error", value: null, error }); }
|
|
3591
3695
|
}, []);
|
|
3592
3696
|
_react.useEffect(() => { const controller = new AbortController(); void load(controller.signal); return () => controller.abort(); }, [load]);
|
|
3593
|
-
|
|
3594
|
-
if (message === null || messageKind !== "ok") return undefined;
|
|
3595
|
-
const timer = setTimeout(() => setMessage(null), 3000);
|
|
3596
|
-
return () => clearTimeout(timer);
|
|
3597
|
-
}, [message, messageKind]);
|
|
3697
|
+
const messageInteraction = useSuccessNoticeTimer(message, message !== null && messageKind === "ok", () => setMessage(null));
|
|
3598
3698
|
const setNumber = (key, value, nullable) => setDraft((current) => ({ ...current, [key]: nullable && value === "" ? null : Number(value) }));
|
|
3599
3699
|
const retentionDays = draft?.recycleMaxAgeDays;
|
|
3600
3700
|
const customDays = customRetention || (retentionDays != null && ![7, 30, 90].includes(retentionDays));
|
|
@@ -3653,11 +3753,15 @@ window.__ModuleLoader__.load({
|
|
|
3653
3753
|
(0, jsx.jsx)("strong", { children: display }),
|
|
3654
3754
|
mode && (0, jsx.jsx)("button", { type: "button", className: "dac-insights-open", style: { alignSelf: "center" }, "aria-label": t(actionLabel), disabled: count === 0, onClick: () => openDetails(mode), children: t("insights.details") })
|
|
3655
3755
|
] }, label)) }),
|
|
3656
|
-
message && (0, jsx.
|
|
3756
|
+
message && (0, jsx.jsxs)("div", { ...messageInteraction, className: messageKind === "ok" ? "dac-toast" : "dac-notice", role: messageKind === "ok" ? "status" : "alert", children: [message,
|
|
3757
|
+
(0, jsx.jsx)("button", { type: "button", "aria-label": t("notice.dismiss"), onClick: () => setMessage(null), children: (0, jsx.jsx)(IconClose, {}) }),
|
|
3758
|
+
] }),
|
|
3657
3759
|
draft && (0, jsx.jsxs)("div", { className: "dac-policy", children: [
|
|
3658
3760
|
(0, jsx.jsxs)("div", { className: "dac-policy-row", children: [
|
|
3659
3761
|
(0, jsx.jsxs)("div", { className: "dac-policy-controls", children: [
|
|
3660
|
-
(0, jsx.
|
|
3762
|
+
(0, jsx.jsxs)("span", { className: "dac-policy-label dac-help-label", children: [t("retention.recycleAge"),
|
|
3763
|
+
(0, jsx.jsx)(HelpTooltip, { id: "dac-retention-help", label: t("help.label").replace("{label}", t("retention.recycleAge")), children: t("retention.note") }),
|
|
3764
|
+
] }),
|
|
3661
3765
|
(0, jsx.jsx)(Select, { disabled: busy, ariaLabel: t("retention.recycleAge"), value: customDays ? "custom" : String(retentionDays ?? ""), onChange: (choice) => {
|
|
3662
3766
|
setCustomRetention(choice === "custom");
|
|
3663
3767
|
setDraft((current) => ({ ...current, recycleAutoDelete: choice !== "", recycleMaxAgeDays: choice === "custom" ? current.recycleMaxAgeDays : choice === "" ? null : Number(choice) }));
|
|
@@ -3666,9 +3770,6 @@ window.__ModuleLoader__.load({
|
|
|
3666
3770
|
] }),
|
|
3667
3771
|
(0, jsx.jsx)("div", { className: "dac-policy-actions", children: (0, jsx.jsx)("button", { className: "dac-btn", disabled: busy || !validDays, onClick: save, children: t("retention.save") }) })
|
|
3668
3772
|
] }),
|
|
3669
|
-
(0, jsx.jsxs)("div", { className: "dac-policy-row dac-policy-bottom", children: [
|
|
3670
|
-
(0, jsx.jsx)("div", { className: "dac-policy-note", children: t("retention.note") }),
|
|
3671
|
-
] })
|
|
3672
3773
|
] }),
|
|
3673
3774
|
] }),
|
|
3674
3775
|
policyPreview && (0, jsx.jsx)(ConfirmDialog, {
|
|
@@ -3870,7 +3971,9 @@ window.__ModuleLoader__.load({
|
|
|
3870
3971
|
] }),
|
|
3871
3972
|
(0, jsx.jsxs)("div", { className: "dac-lineage-list-head", children: [
|
|
3872
3973
|
(0, jsx.jsxs)("div", { className: "dac-lineage-list-head-copy", children: [
|
|
3873
|
-
(0, jsx.
|
|
3974
|
+
(0, jsx.jsxs)("span", { className: "dac-help-label", children: [t("lineage.managedHeading"),
|
|
3975
|
+
(0, jsx.jsx)(HelpTooltip, { id: "dac-lineage-help", label: t("help.label").replace("{label}", t("lineage.managedHeading")), children: t("lineage.scopeNote") }),
|
|
3976
|
+
] }),
|
|
3874
3977
|
(0, jsx.jsx)("small", { children: managedCountText })
|
|
3875
3978
|
] }),
|
|
3876
3979
|
(0, jsx.jsxs)("div", { className: "dac-lineage-fold-actions", children: [
|
|
@@ -3891,7 +3994,6 @@ window.__ModuleLoader__.load({
|
|
|
3891
3994
|
open && (0, jsx.jsx)("div", { role: "list", "aria-label": lineageWorkspaceLabel(t, { workspace: group.workspace }), className: "dac-lineage-tree", children: (0, jsx.jsx)(LineageTreeNodes, { nodes: group.roots, diagnostics: state.value.diagnostics, collapsed: visibleCollapsed, copiedId, foldingDisabled: searchActive, t, onCopy: copyId, onToggle: (id) => setCollapsed((current) => setVisibleSelection(current, [id], !current.has(id))) }) })
|
|
3892
3995
|
] }, group.key);
|
|
3893
3996
|
}) }),
|
|
3894
|
-
(0, jsx.jsx)("div", { className: "dac-lineage-scopebar", role: "note", children: t("lineage.scopeNote") })
|
|
3895
3997
|
] });
|
|
3896
3998
|
}
|
|
3897
3999
|
|
|
@@ -3954,7 +4056,7 @@ window.__ModuleLoader__.load({
|
|
|
3954
4056
|
["lineage", "tab.lineage"],
|
|
3955
4057
|
["about", "tab.about"],
|
|
3956
4058
|
];
|
|
3957
|
-
function ArchiveTabs({ pageMode, onChange, t }) {
|
|
4059
|
+
function ArchiveTabs({ pageMode, onChange, t, updateStatus }) {
|
|
3958
4060
|
const move = (event, index) => {
|
|
3959
4061
|
let next = index;
|
|
3960
4062
|
if (event.key === "ArrowRight") next = (index + 1) % ARCHIVE_TABS.length;
|
|
@@ -3974,10 +4076,11 @@ window.__ModuleLoader__.load({
|
|
|
3974
4076
|
className: "dac-tab",
|
|
3975
4077
|
"aria-selected": pageMode === mode,
|
|
3976
4078
|
"aria-controls": `dac-panel-${mode}`,
|
|
4079
|
+
"aria-label": mode === "about" && updateStatus === "available" ? `${t(label)} · ${t("about.updateAvailable")}` : void 0,
|
|
3977
4080
|
tabIndex: pageMode === mode ? 0 : -1,
|
|
3978
4081
|
onKeyDown: (event) => move(event, index),
|
|
3979
4082
|
onClick: () => onChange(mode),
|
|
3980
|
-
children: t(label),
|
|
4083
|
+
children: (0, jsx.jsxs)("span", { className: "dac-tab-label", children: [t(label), mode === "about" && updateStatus === "available" && (0, jsx.jsx)("span", { className: "dac-update-dot", "aria-hidden": true })] }),
|
|
3981
4084
|
}, mode)) });
|
|
3982
4085
|
}
|
|
3983
4086
|
|
|
@@ -4130,15 +4233,11 @@ window.__ModuleLoader__.load({
|
|
|
4130
4233
|
return markArchiveDialog(pageRef.current);
|
|
4131
4234
|
}, [sessions, loadError]);
|
|
4132
4235
|
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
setNotice((current) => (current === notice ? null : current));
|
|
4139
|
-
}, 4000);
|
|
4140
|
-
return () => clearTimeout(timer);
|
|
4141
|
-
}, [notice]);
|
|
4236
|
+
const noticeInteraction = useSuccessNoticeTimer(notice, notice?.kind === "ok", () => setNotice((current) => current === notice ? null : current));
|
|
4237
|
+
const changePageMode = (mode) => {
|
|
4238
|
+
if (mode !== pageMode) setNotice(null);
|
|
4239
|
+
setPageMode(mode);
|
|
4240
|
+
};
|
|
4142
4241
|
|
|
4143
4242
|
const toggleCollapsed = (key) => {
|
|
4144
4243
|
setCollapsed((prev) => {
|
|
@@ -4868,7 +4967,7 @@ window.__ModuleLoader__.load({
|
|
|
4868
4967
|
})
|
|
4869
4968
|
]
|
|
4870
4969
|
}),
|
|
4871
|
-
(0, jsx.jsx)(ArchiveTabs, { pageMode, onChange:
|
|
4970
|
+
(0, jsx.jsx)(ArchiveTabs, { pageMode, onChange: changePageMode, t, updateStatus: about?.update?.status }),
|
|
4872
4971
|
pageMode === "archived" && (0, jsx.jsxs)("div", { id: "dac-panel-archived", role: "tabpanel", "aria-labelledby": "dac-tab-archived", className: "dac-mode-panel", children: [
|
|
4873
4972
|
metadataStatus === "unavailable" && (0, jsx.jsx)("div", {
|
|
4874
4973
|
className: "dac-warn",
|
|
@@ -4932,6 +5031,7 @@ window.__ModuleLoader__.load({
|
|
|
4932
5031
|
}),
|
|
4933
5032
|
notice !== null && notice.kind === "ok" && (0, jsx.jsxs)("div", {
|
|
4934
5033
|
className: "dac-toast",
|
|
5034
|
+
...noticeInteraction,
|
|
4935
5035
|
role: "status",
|
|
4936
5036
|
style: { top: `${toastTop()}px` },
|
|
4937
5037
|
children: [
|
|
@@ -4986,7 +5086,7 @@ window.__ModuleLoader__.load({
|
|
|
4986
5086
|
(0, jsx.jsx)("button", { type: "button", className: "dac-retry", onClick: loadTrash, children: t("state.retry") }),
|
|
4987
5087
|
] }),
|
|
4988
5088
|
trash.status === "ready" && trash.trashStatus !== "ready" && (0, jsx.jsx)("div", { className: "dac-warn", role: "status", children: t("trash.unavailable") }),
|
|
4989
|
-
notice !== null && notice.kind === "ok" && (0, jsx.jsxs)("div", { className: "dac-toast", role: "status", style: { top: `${toastTop()}px` }, children: [
|
|
5089
|
+
notice !== null && notice.kind === "ok" && (0, jsx.jsxs)("div", { ...noticeInteraction, className: "dac-toast", role: "status", style: { top: `${toastTop()}px` }, children: [
|
|
4990
5090
|
(0, jsx.jsx)(IconCheckCircle, {}), (0, jsx.jsx)("span", { children: notice.text }),
|
|
4991
5091
|
notice.action === "undo-trash" && (0, jsx.jsx)("button", { type: "button", className: "dac-notice-action", onClick: () => undoTrashMove(notice.ids), children: t("trash.undo") }),
|
|
4992
5092
|
notice.action === "view-archived" && (0, jsx.jsx)("button", { type: "button", className: "dac-notice-action", onClick: viewRestoredArchive, children: t("trash.viewArchived") }),
|
|
@@ -5139,6 +5239,8 @@ window.__ModuleLoader__.load({
|
|
|
5139
5239
|
exports.SETTINGS_NS = SETTINGS_NS;
|
|
5140
5240
|
exports.__test = { NotePreview, formatBytes, matchesArchivedSession, filterByTag, sortArchivedSessions, setVisibleSelection, uniqueSessionIds, createArchiveNoticeController, installArchiveNoticeInterceptor, openArchiveSettings, ArchiveNoticeOverlay, WorkspaceArchiveChooserDialog, WorkspaceArchiveDialog, MetadataDialog, ImportDialog, ArchiveTabs, defaultRetentionSelection, filterLineageForest, groupLineageWorkspaces, SnapshotInsightRows, LineageTreeNodes, StorageRetentionPanel, ArchiveRelationshipsPanel, groupTrashSessions, trashStatusLabel, markArchiveDialog, submitExport, submitImportFile, fetchTrash, fetchInsights, saveRetentionPolicy, previewRetention, applyRetention, fetchLineage, restoreTrash, purgeTrash, emptyTrash, fetchArchivePreview, fetchArchiveImage, fetchArchiveSearch, fetchWorkspaceArchiveWorkspaces, previewWorkspaceArchive, applyWorkspaceArchive, buildPreviewNodes, previewCopyText, groupPreviewSegments, resolvePreviewPrimitives, PreviewMarkdown, PreviewReasoning, PreviewDisclosure, PreviewMessage, PreviewActions, previewFocusables, PreviewImage, unarchiveFailureText, importWarningText, editIconSpec: EDIT_ICON_SPEC };
|
|
5141
5241
|
exports.__test.importFailureText = importFailureText;
|
|
5242
|
+
exports.__test.useSuccessNoticeTimer = useSuccessNoticeTimer;
|
|
5243
|
+
exports.__test.HelpTooltip = HelpTooltip;
|
|
5142
5244
|
exports.apply = apply;
|
|
5143
5245
|
exports.inject = inject;
|
|
5144
5246
|
return module.exports;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser client entry — the
|
|
2
|
+
* Browser client entry — the Archive Management settings section with a workspace
|
|
3
3
|
* chooser and one-confirmation bulk archive flow. The flow stays inside the
|
|
4
4
|
* plugin-owned Settings surface and requires no workspace-row extension slot.
|
|
5
5
|
* The searchable manager includes Archived, Recycle Bin, Storage & Retention,
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Origins & Branches, and About views. Only workspace actions move archived
|
|
7
|
+
* chats to the Recycle Bin and offer immediate Undo. Delete/Delete all instead
|
|
8
|
+
* require confirmation for irreversible permanent deletion. Recycle restore
|
|
9
|
+
* returns chats to Archived; Unarchive returns them to the main chat area.
|
|
10
|
+
* The recycle view provides original-backed read-only previews,
|
|
11
|
+
* capability-dependent original/snapshot restore, guarded
|
|
9
12
|
* permanent purge and empty operations, degraded-state warnings, responsive
|
|
10
13
|
* rows, and accessible confirmation dialogs, all localized in English and 中文.
|
|
11
14
|
*/
|