@tobycaimf/dsh-archived-sessions 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -2,18 +2,26 @@
2
2
 
3
3
  > English | [中文](./README.md)
4
4
 
5
- > Manage your **archived sessions** in DSH view, restore, and permanently delete them.
6
- > Archive logo, session dates, workspace tags, and **batch multi-select** (restore / delete selected).
5
+ > Manage **all your sessions** in DSH from one place **Session Manager**.
6
+ > Archive / restore / permanently delete / move to another workspace, with workspace tags and session dates, plus batch actions.
7
+ > Target directories can be an **existing workspace** or a **new path** (auto-created); the new-path mode also supports the **native OS directory picker**, and the settings nav gets a dedicated archive-box icon.
7
8
 
8
- A persistent DSH plugin (host + browser halves). Manage every session that has been archived (hidden from the sidebar) under **Settings 归档会话 (Archived Sessions)**.
9
+ A persistent DSH plugin (host + browser halves). Under **Settings 会话管理 (Session Manager)** unify all conversations: archive (hide from sidebar), restore, permanently delete, and relocate between workspaces.
9
10
 
10
11
  ## Features
11
12
 
12
- - **View**: list all archived sessions each row shows the title (falls back to the first user message), the **session date** (`createdAt`, localized), and a **workspace tag** (title or directory name; a dashed "原工作区已删除 / workspace deleted" tag when the original workspace is gone).
13
- - **Restore**: unarchive and put the session back into its group.
13
+ - **Unified panel**: a single Session Manager entry with an **All / Archived** filter; each row shows the title (falls back to the first user message), **archive status** (active / archived), the **session date**, and a **workspace tag** (title or directory name; a dashed "原工作区已删除 / workspace deleted" tag when the original workspace is gone).
14
+ - **Archive / Restore**: archive hides a session from the sidebar; restore unarchives and puts it back into its group.
14
15
  - **Permanently delete**: physically removes the session log file + detaches workspace accounting — **irreversible** (asks for confirmation first).
15
- - **Batch multi-select**: one-click batch mode select-all, restore selected, delete selected (single confirmation for batch delete).
16
- - **Adaptive UI**: follows the theme tokens (light/dark), stacks vertically on narrow screens; loading / empty / error / busy states, keyboard focus and `prefers-reduced-motion` support.
16
+ - **Move to a workspace**: pick an **existing workspace**, or enter a **new directory path** (auto-created); the new-path mode also lets you open the **native OS directory picker** with a **「浏览…」** button. The session's working directory and log are migrated, and it moves into the corresponding workspace group. Sessions that are currently open must be switched away from first.
17
+ - **Batch multi-select**: select-all / archive selected / restore selected / delete selected (single confirmation for batch delete).
18
+ - **Adaptive UI**: follows the theme tokens (light/dark), stacks vertically on narrow screens; loading / empty / error / busy states, keyboard focus and `prefers-reduced-motion` support; dedicated archive-box nav icon.
19
+
20
+ ## Screenshots
21
+
22
+ ![Archived sessions list](assets/screenshot-archived-sessions.png)
23
+
24
+ ![Batch multi-select](assets/screenshot-batch-mode.png)
17
25
 
18
26
  ## Install
19
27
 
@@ -29,7 +37,7 @@ git clone https://github.com/TOBYCAI/dsh-archived-sessions.git
29
37
  dsh plugin --profile desktop add link:/path/to/dsh-archived-sessions
30
38
  ```
31
39
 
32
- > After installing, **restart DSH** (or refresh the page to reload the bundle). Then Settings → Archived Sessions becomes available.
40
+ > After installing, **restart DSH** (or refresh the page to reload the bundle). Then Settings → 会话管理 (Session Manager) becomes available.
33
41
 
34
42
  ## Uninstall
35
43
 
@@ -57,10 +65,15 @@ lib/client.js pre-built client (ModuleLoader CJS handshake)
57
65
  | Method | Path | Description |
58
66
  |---|---|---|
59
67
  | POST | `/archived-sessions/list` | List archived sessions (skips deleted / non-existent ones) |
68
+ | POST | `/archived-sessions/archive` | Archive (hide) a single session |
69
+ | POST | `/archived-sessions/archive-many` | Archive many |
60
70
  | POST | `/archived-sessions/restore` | Unarchive a single session |
61
71
  | POST | `/archived-sessions/restore-many` | Unarchive many |
62
72
  | POST | `/archived-sessions/delete` | Permanently delete a single session (physically removes log, keeps hidden) |
63
73
  | POST | `/archived-sessions/delete-many` | Permanently delete many |
74
+ | POST | `/archived-sessions/sessions` | List all sessions (with archived flag) for the Session Manager panel |
75
+ | POST | `/archived-sessions/workspaces` | List available target workspaces |
76
+ | POST | `/archived-sessions/move` | Move a session to a target workspace `{ sessionId, targetPath }` |
64
77
 
65
78
  > On delete the session **stays archived** (is not unarchived), so it won't pop back into the sidebar / "ungrouped"; entries whose log is already gone are skipped.
66
79
  > DSH has no official session-delete API; this plugin implements "physically remove log + detach ownership + keep hidden".
package/README.md CHANGED
@@ -2,18 +2,26 @@
2
2
 
3
3
  > [English](./README.en.md) | 中文
4
4
 
5
- > DSH 设置面板「归档会话」管理器:查看、恢复、彻底删除已归档的 session。
6
- > 带归档 logo、会话日期、所属工作区标签,以及**批量多选**(恢复所选 / 删除所选)。
5
+ > DSH 设置面板**「会话管理」**:一个入口统一管理全部会话。
6
+ > 归档 / 恢复 / 彻底删除 / 移动到其他工作区,带工作区标签与会话日期,支持批量。
7
+ > 工作区目录可选「已有」或「新建」,新建支持**系统目录选择**;设置导航带专属归档盒图标。
7
8
 
8
- 一个 DSH 持久化插件(host + browser 双半)。在 **设置 → 归档会话** 里管理所有被归档(从侧栏隐藏)的会话。
9
+ 一个 DSH 持久化插件(host + browser 双半)。在 **设置 → 会话管理** 里统一管理所有会话:归档(从侧栏隐藏)、恢复、彻底删除、移动到其他工作区。
9
10
 
10
11
  ## 功能
11
12
 
12
- - **查看**:列出全部已归档会话,每行显示 —— 标题(缺失时回退到首条用户消息)、**会话日期**(createdAt,中文格式)、**所属工作区标签**(标题或目录名;原工作区已删除时显示虚线「工作区已删 · 目录名」)。
13
- - **恢复**:取消归档,放回原分组。
13
+ - **统一面板**:单一「会话管理」入口,顶部「全部 / 已归档」筛选;每行显示标题(缺失时回退首条用户消息)、**归档状态**(活动 / 已归档)、**会话日期**、**所属工作区标签**(标题或目录名;原工作区已删除时显示「工作区已删 · 目录名」)。
14
+ - **归档 / 恢复**:归档把会话从侧栏隐藏;恢复取消归档放回原分组。
14
15
  - **彻底删除**:物理删除会话日志文件 + 解除工作区归属,**不可恢复**(删除前有二次确认)。
15
- - **批量多选**:一键进入批量模式,全选 / 恢复所选 / 删除所选(批量删除一次二次确认)。
16
- - **自适应 UI**:深浅色跟随主题 token,窄屏自动纵向堆叠;加载/空/错误/操作中状态齐全;支持键盘焦点、`prefers-reduced-motion`。
16
+ - **移动到工作区**:任选**已有工作区**或**新建目录路径**(自动创建);新建目录支持直接输入路径或点击 **「浏览…」调用系统目录选择窗口**。会话的工作目录与日志会一起迁移,随后滑入对应工作区分组;处于打开状态的会话需先切走。
17
+ - **批量多选**:全选 / 批量归档 / 恢复所选 / 删除所选(批量删除一次二次确认)。
18
+ - **自适应 UI**:深浅色跟随主题 token,窄屏自动纵向堆叠;加载/空/错误/操作中状态齐全;支持键盘焦点、`prefers-reduced-motion`;设置导航左侧为归档盒专属图标。
19
+
20
+ ## 截图
21
+
22
+ ![归档会话列表](assets/screenshot-archived-sessions.png)
23
+
24
+ ![批量多选](assets/screenshot-batch-mode.png)
17
25
 
18
26
  ## 安装
19
27
 
@@ -29,7 +37,7 @@ git clone https://github.com/TOBYCAI/dsh-archived-sessions.git
29
37
  dsh plugin --profile desktop add link:/path/to/dsh-archived-sessions
30
38
  ```
31
39
 
32
- > 装完**重启 DSH**(或刷新页面重新加载 bundle)后,设置 → 归档会话 即可用。
40
+ > 装完**重启 DSH**(或刷新页面重新加载 bundle)后,设置 → 会话管理 即可用。
33
41
 
34
42
  ## 卸载
35
43
 
@@ -57,10 +65,15 @@ lib/client.js 预构建 client(ModuleLoader CJS handshake)
57
65
  | 方法 | 路径 | 说明 |
58
66
  |---|---|---|
59
67
  | POST | `/archived-sessions/list` | 列出归档会话(跳过已删除/不存在者) |
68
+ | POST | `/archived-sessions/archive` | 归档(隐藏)单个会话 |
69
+ | POST | `/archived-sessions/archive-many` | 批量归档 |
60
70
  | POST | `/archived-sessions/restore` | 取消归档单个 |
61
71
  | POST | `/archived-sessions/restore-many` | 批量取消归档 |
62
72
  | POST | `/archived-sessions/delete` | 彻底删除单个(物理删日志,保持隐藏) |
63
73
  | POST | `/archived-sessions/delete-many` | 批量彻底删除 |
74
+ | POST | `/archived-sessions/sessions` | 列出全部会话(含已归档标记),供「会话管理」面板 |
75
+ | POST | `/archived-sessions/workspaces` | 列出可选目标工作区 |
76
+ | POST | `/archived-sessions/move` | 把会话移动到目标工作区 `{ sessionId, targetPath }` |
64
77
 
65
78
  > 删除时会**保留归档位**(不取消归档),从而不会把会话放回侧栏/未分组;并跳过日志已不存在的条目。
66
79
  > DSH 本身没有官方 session 删除接口,本插件是「物理删日志 + 解除归属 + 保持隐藏」的实现。
Binary file
package/lib/client.js CHANGED
@@ -45,25 +45,36 @@ var CSS = `
45
45
  .archv-sub{font-size:12px;line-height:1.55;color:var(--dsw-alias-label-tertiary);margin:0 0 12px;max-width:64ch}
46
46
  .archv-err{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 12px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 40%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent);border-radius:10px;color:var(--dsw-alias-state-error-primary);font-size:12px;margin-bottom:10px}
47
47
  .archv-errretry{appearance:none;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,transparent);background:transparent;color:inherit;border-radius:8px;padding:4px 10px;font-size:11px;cursor:pointer;flex:none}
48
- .archv-errretry:focus-visible,.archv-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
48
+ .sess-filter{display:flex;align-items:center;gap:6px;margin:0 0 12px}
49
+ .sess-fbtn{appearance:none;min-height:30px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-fill-subtle);color:var(--dsw-alias-label-secondary);border-radius:999px;font-size:12px;font-weight:500;cursor:pointer}
50
+ .sess-fbtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
51
+ .sess-fbtn-on{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}
52
+ .sess-batch{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 2px 4px;margin-bottom:4px}
53
+ .sess-btntext{font-size:12px;color:var(--dsw-alias-label-tertiary);flex:none}
49
54
  .archv-list{display:flex;flex-direction:column;gap:8px}
50
- .archv-card{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-fill-elevated);transition:border-color .15s ease,background-color .15s ease}
55
+ .archv-card{display:flex;flex-direction:column;align-items:stretch;gap:0;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-fill-elevated);transition:border-color .15s ease,background-color .15s ease}
51
56
  .archv-card:hover{border-color:var(--dsw-alias-border-l4)}
57
+ .archv-card-exp{border-color:var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-1)}
58
+ .archv-row{display:flex;align-items:center;gap:14px;width:100%;min-width:0}
52
59
  .archv-main{min-width:0;display:flex;flex-direction:column;gap:4px}
53
60
  .archv-name{font-size:13px;font-weight:550;color:var(--dsw-alias-label-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
54
61
  .archv-meta{display:flex;align-items:center;gap:7px;flex-wrap:wrap;min-width:0}
55
62
  .archv-wtag{display:inline-flex;align-items:center;gap:4px;max-width:100%;font-size:11px;font-weight:500;line-height:1;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-subtle);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:3px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
56
63
  .archv-wgone{border-style:dashed;color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 55%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 7%,transparent)}
64
+ .archv-active{color:var(--dsw-alias-state-success-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary) 45%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 8%,transparent)}
57
65
  .archv-date{font-size:11px;color:var(--dsw-alias-label-tertiary);white-space:nowrap;flex:none}
58
66
  .archv-id{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10.5px;color:var(--dsw-alias-label-tertiary);flex:none}
59
67
  .archv-dot{color:var(--dsw-alias-border-l3);flex:none}
60
- .archv-actions{display:flex;gap:8px;flex:none}
68
+ .archv-check{width:15px;height:15px;accent-color:var(--dsw-alias-state-business-primary);flex:none;cursor:pointer}
69
+ .archv-body{flex:1;min-width:0;display:flex;align-items:center;gap:12px}
70
+ .archv-actions{display:flex;gap:8px;flex:none;flex-wrap:wrap;justify-content:flex-end}
61
71
  .archv-btn{appearance:none;min-height:32px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-fill-subtle);color:var(--dsw-alias-label-secondary);border-radius:9px;font-size:12px;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:background-color .15s ease,border-color .15s ease,color .15s ease}
62
72
  .archv-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
63
73
  .archv-btn:disabled{opacity:.5;cursor:default}
64
- .archv-restore{min-width:62px;justify-content:center}
65
74
  .archv-del{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,transparent)}
66
75
  .archv-del:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}
76
+ .archv-go{color:var(--dsw-alias-state-business-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 45%,transparent)}
77
+ .archv-go:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent);color:var(--dsw-alias-state-business-primary)}
67
78
  .archv-empty{display:flex;align-items:center;gap:10px;padding:20px 14px;border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
68
79
  .archv-skel{display:flex;flex-direction:column;gap:8px}
69
80
  .archv-skel-card{height:58px;border-radius:12px;background:var(--dsw-alias-fill-subtle);position:relative;overflow:hidden}
@@ -74,7 +85,24 @@ var CSS = `
74
85
  @keyframes archv-rot{to{transform:rotate(360deg)}}
75
86
  @keyframes archv-pop{from{opacity:0;transform:translateX(-50%) translateY(10px)}}
76
87
  @media (prefers-reduced-motion:reduce){.archv-skel-card::after{animation:none}.archv-card,.archv-btn{transition:none}.archv-status,.archv-spin{animation:none}}
77
- @media (max-width:560px){.archv-card{flex-direction:column;align-items:stretch;gap:10px}.archv-actions{justify-content:flex-end}}
88
+ @media (max-width:640px){.archv-card{flex-direction:column;align-items:stretch;gap:10px}.archv-actions{justify-content:flex-end}}
89
+ .mv-sheet{width:100%;box-sizing:border-box;display:flex;flex-direction:column;gap:12px;margin-top:12px;padding:14px;border:1px solid var(--dsw-alias-border-l2);border-left:3px solid var(--dsw-alias-state-business-primary);border-radius:10px;background:var(--dsw-alias-fill-subtle)}
90
+ .mv-sheet-head{display:flex;align-items:center;justify-content:space-between;gap:10px}
91
+ .mv-sheet-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin:0}
92
+ .mv-sheet-close{appearance:none;width:26px;height:26px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;border-radius:7px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1}
93
+ .mv-sheet-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
94
+ .mv-seg{display:flex;gap:2px;padding:2px;background:var(--dsw-alias-fill-elevated);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;width:100%}
95
+ .mv-segbtn{appearance:none;flex:1;min-height:30px;padding:0 12px;border:none;background:transparent;color:var(--dsw-alias-label-secondary);border-radius:8px;font-size:12px;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px}
96
+ .mv-segbtn:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
97
+ .mv-segbtn-on{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);box-shadow:0 1px 2px rgb(0 0 0/.08)}
98
+ .mv-field{display:flex;flex-direction:column;gap:6px}
99
+ .mv-field label.mv-field-label{font-size:12px;font-weight:500;color:var(--dsw-alias-label-secondary)}
100
+ .mv-field select,.mv-field input[type=text]{box-sizing:border-box;appearance:none;width:100%;min-height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-fill-elevated);color:var(--dsw-alias-label-primary);font-size:12px;font-family:inherit}
101
+ .mv-field select:focus-visible,.mv-field input[type=text]:focus-visible,.mv-sheet-close:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
102
+ .mv-browse-row{display:flex;align-items:center;gap:8px}
103
+ .mv-browse-row input[type=text]{flex:1;min-width:0}
104
+ .mv-foot{display:flex;justify-content:flex-end;align-items:center;gap:8px;margin-top:2px}
105
+ @media (max-width:640px){.archv-row{flex-wrap:wrap}.mv-sheet{padding:12px}}
78
106
  `;
79
107
  function fmtDate(iso) {
80
108
  if (!iso) return null;
@@ -101,12 +129,60 @@ async function postJSON(path, body) {
101
129
  if (!res.ok || !data) throw new Error(data && (data.error || data.message) || `request failed (${res.status})`);
102
130
  return data;
103
131
  }
104
- function ArchivedPanel() {
105
- const [items, setItems] = (0, import_react.useState)(null);
132
+ var NAV_CSS = `
133
+ [data-dsh-nav-sessions] > svg:first-child { display: none; }
134
+ [data-dsh-nav-sessions]::before {
135
+ content: ''; flex: none; width: 16px; height: 16px; background: currentColor;
136
+ -webkit-mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http://www.w3.org/2000/svg'%20width%3D'24'%20height%3D'24'%20viewBox%3D'0%200%2024%2024'%20fill%3D'none'%20stroke%3D'black'%20stroke-width%3D'2'%20stroke-linecap%3D'round'%20stroke-linejoin%3D'round'%3E%3Cpath%20d%3D'M21%208v13H3V8'/%3E%3Cpath%20d%3D'M1%203h22v5H1z'/%3E%3Cpath%20d%3D'M10%2012h4'/%3E%3C/svg%3E") center / contain no-repeat;
137
+ mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http://www.w3.org/2000/svg'%20width%3D'24'%20height%3D'24'%20viewBox%3D'0%200%2024%2024'%20fill%3D'none'%20stroke%3D'black'%20stroke-width%3D'2'%20stroke-linecap%3D'round'%20stroke-linejoin%3D'round'%3E%3Cpath%20d%3D'M21%208v13H3V8'/%3E%3Cpath%20d%3D'M1%203h22v5H1z'/%3E%3Cpath%20d%3D'M10%2012h4'/%3E%3C/svg%3E") center / contain no-repeat;
138
+ }
139
+ `;
140
+ function markSettingsNav() {
141
+ const LABEL = "\u4F1A\u8BDD\u7BA1\u7406";
142
+ let disposed = false;
143
+ const sync = () => {
144
+ if (disposed) return;
145
+ const buttons = document.querySelectorAll('[role="dialog"] nav button');
146
+ for (const b of buttons) {
147
+ const t = (b.textContent || "").trim();
148
+ if (t === LABEL) b.setAttribute("data-dsh-nav-sessions", "");
149
+ else b.removeAttribute("data-dsh-nav-sessions");
150
+ }
151
+ };
152
+ sync();
153
+ const obs = new MutationObserver(sync);
154
+ obs.observe(document.body, { childList: true, subtree: true, characterData: true });
155
+ return () => {
156
+ disposed = true;
157
+ obs.disconnect();
158
+ document.querySelectorAll("[data-dsh-nav-sessions]").forEach((e) => e.removeAttribute("data-dsh-nav-sessions"));
159
+ };
160
+ }
161
+ function installSettingsNavIcons(ctx) {
162
+ const styleEl = document.createElement("style");
163
+ styleEl.textContent = NAV_CSS;
164
+ document.head.appendChild(styleEl);
165
+ const dispose = markSettingsNav();
166
+ ctx.effect(() => () => {
167
+ dispose();
168
+ if (styleEl.parentNode) styleEl.parentNode.removeChild(styleEl);
169
+ });
170
+ }
171
+ function SessionPanel({ workspacesSvc }) {
172
+ const [sessions, setSessions] = (0, import_react.useState)(null);
173
+ const [workspaces, setWorkspaces] = (0, import_react.useState)([]);
174
+ const [filter, setFilter] = (0, import_react.useState)("all");
175
+ const [selected, setSelected] = (0, import_react.useState)({});
176
+ const [confirmDel, setConfirmDel] = (0, import_react.useState)(null);
177
+ const [confirmBatch, setConfirmBatch] = (0, import_react.useState)(false);
106
178
  const [busy, setBusy] = (0, import_react.useState)(null);
107
- const [confirmId, setConfirmId] = (0, import_react.useState)(null);
179
+ const [openMove, setOpenMove] = (0, import_react.useState)(null);
180
+ const [moveMode, setMoveMode] = (0, import_react.useState)("existing");
181
+ const [targetWs, setTargetWs] = (0, import_react.useState)("");
182
+ const [newPath, setNewPath] = (0, import_react.useState)("");
108
183
  const [error, setError] = (0, import_react.useState)(null);
109
184
  const [toast, setToast] = (0, import_react.useState)(null);
185
+ const [picking, setPicking] = (0, import_react.useState)(false);
110
186
  const timer = (0, import_react.useRef)(null);
111
187
  const showToast = (msg) => {
112
188
  if (timer.current) clearTimeout(timer.current);
@@ -115,7 +191,17 @@ function ArchivedPanel() {
115
191
  };
116
192
  const refresh = () => {
117
193
  setError(null);
118
- postJSON("/archived-sessions/list", {}).then((r) => setItems(r.items || [])).catch((e) => setError(String(e && e.message || e)));
194
+ Promise.all([
195
+ postJSON("/archived-sessions/sessions", {}),
196
+ postJSON("/archived-sessions/workspaces", {})
197
+ ]).then(([s, works]) => {
198
+ setSessions(s.items || []);
199
+ setWorkspaces(works.items || []);
200
+ setSelected({});
201
+ setConfirmDel(null);
202
+ setConfirmBatch(false);
203
+ if (!targetWs && works.items && works.items.length) setTargetWs(works.items[0].workspaceId);
204
+ }).catch((e) => setError(String(e && e.message || e)));
119
205
  };
120
206
  (0, import_react.useEffect)(() => {
121
207
  refresh();
@@ -123,29 +209,110 @@ function ArchivedPanel() {
123
209
  if (timer.current) clearTimeout(timer.current);
124
210
  };
125
211
  }, []);
212
+ const wsPath = (id) => {
213
+ const w = workspaces.find((x) => x.workspaceId === id);
214
+ return w ? w.path : "";
215
+ };
216
+ const archivedList = sessions ? sessions.filter((x) => x.archived) : [];
217
+ const activeList = sessions ? sessions.filter((x) => !x.archived) : [];
218
+ const list = filter === "archived" ? archivedList : sessions || [];
219
+ const selIds = Object.keys(selected).filter((k) => selected[k]);
220
+ const toggle = (id) => setSelected((s) => ({ ...s, [id]: !s[id] }));
221
+ const clearSel = () => setSelected({});
222
+ const selectAll = () => {
223
+ const o = {};
224
+ list.forEach((x) => {
225
+ o[x.sessionId] = true;
226
+ });
227
+ setSelected(o);
228
+ };
126
229
  const act = (action, it) => {
127
230
  if (busy) return;
128
231
  if (action === "delete") {
129
- if (confirmId !== it.sessionId) {
130
- setConfirmId(it.sessionId);
232
+ if (confirmDel !== it.sessionId) {
233
+ setConfirmDel(it.sessionId);
131
234
  return;
132
235
  }
133
- setConfirmId(null);
134
- } else {
135
- setConfirmId(null);
136
- setError(null);
236
+ setConfirmDel(null);
137
237
  }
138
238
  setBusy(it.sessionId);
139
239
  postJSON("/archived-sessions/" + action, { sessionId: it.sessionId }).then(() => {
140
240
  setBusy(null);
141
- showToast(action === "restore" ? `\u5DF2\u6062\u590D\u300C${it.title || it.sessionId}\u300D` : `\u5DF2\u5220\u9664 ${it.title || it.sessionId}`);
241
+ const n = it.title || it.sessionId;
242
+ showToast(action === "archive" ? `\u5DF2\u5F52\u6863\u300C${n}\u300D` : action === "restore" ? `\u5DF2\u6062\u590D\u300C${n}\u300D` : `\u5DF2\u5220\u9664 ${n}`);
142
243
  refresh();
143
244
  }).catch((e) => {
144
245
  setBusy(null);
145
- setConfirmId(null);
246
+ setConfirmDel(null);
146
247
  setError(String(e && e.message || e));
147
248
  });
148
249
  };
250
+ const doMove = (it) => {
251
+ const targetPath = moveMode === "new" ? newPath.trim() : wsPath(targetWs);
252
+ if (!targetPath) {
253
+ setError("\u8BF7\u9009\u62E9\u5DF2\u6709\u5DE5\u4F5C\u533A\u6216\u8F93\u5165\u65B0\u7684\u76EE\u6807\u76EE\u5F55\u8DEF\u5F84");
254
+ return;
255
+ }
256
+ setBusy(it.sessionId);
257
+ setError(null);
258
+ postJSON("/archived-sessions/move", { sessionId: it.sessionId, targetPath }).then((r) => {
259
+ setBusy(null);
260
+ setOpenMove(null);
261
+ setMoveMode("existing");
262
+ setNewPath("");
263
+ showToast(`\u5DF2\u628A\u300C${it.title || it.sessionId}\u300D\u79FB\u5230 ${r.workspaceTitle || targetPath}`);
264
+ refresh();
265
+ }).catch((e) => {
266
+ setBusy(null);
267
+ setError(String(e && e.message || e));
268
+ });
269
+ };
270
+ const doBatch = (action) => {
271
+ if (!selIds.length || busy) return;
272
+ if (action === "delete-many") {
273
+ if (!confirmBatch) {
274
+ setConfirmBatch(true);
275
+ return;
276
+ }
277
+ setConfirmBatch(false);
278
+ }
279
+ setBusy("__batch__");
280
+ postJSON("/archived-sessions/" + action, { sessionIds: selIds }).then((r) => {
281
+ setBusy(null);
282
+ const n = r && (r.archived || r.restored || r.deleted) || selIds.length;
283
+ showToast(`\u5DF2\u5904\u7406 ${n} \u4E2A\u4F1A\u8BDD`);
284
+ refresh();
285
+ }).catch((e) => {
286
+ setBusy(null);
287
+ setConfirmBatch(false);
288
+ setError(String(e && e.message || e));
289
+ });
290
+ };
291
+ const openMoveFor = (it) => {
292
+ if (openMove === it.sessionId) {
293
+ setOpenMove(null);
294
+ return;
295
+ }
296
+ setTargetWs(workspaces.length ? targetWs || workspaces[0].workspaceId : "");
297
+ setMoveMode("existing");
298
+ setNewPath("");
299
+ setOpenMove(it.sessionId);
300
+ };
301
+ const pickDirectory = async () => {
302
+ if (!workspacesSvc || picking) return;
303
+ setPicking(true);
304
+ try {
305
+ const p = await workspacesSvc.pickDirectory();
306
+ if (p) {
307
+ setNewPath(p);
308
+ setMoveMode("new");
309
+ }
310
+ } catch (e) {
311
+ setError(String(e && e.message || e));
312
+ } finally {
313
+ setPicking(false);
314
+ }
315
+ };
149
316
  const workspaceTag = (it) => {
150
317
  if (it.hasWorkspace && it.workspaceGone) {
151
318
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "archv-wtag archv-wgone", title: (it.workspacePath || "") + "\uFF08\u539F\u5DE5\u4F5C\u533A\u5DF2\u5220\u9664\uFF09", children: [
@@ -155,51 +322,163 @@ function ArchivedPanel() {
155
322
  }
156
323
  const wName = it.workspaceTitle || pathName(it.workspacePath);
157
324
  if (wName) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-wtag", title: it.workspacePath || "", children: wName });
158
- return null;
325
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-wtag", children: "\u672A\u5206\u7EC4" });
326
+ };
327
+ const rowActions = (it) => {
328
+ if (it.archived) {
329
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-actions", children: [
330
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-go", disabled: busy !== null, onClick: () => act("restore", it), children: "\u6062\u590D" }),
331
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-go", disabled: busy !== null, onClick: () => openMoveFor(it), children: openMove === it.sessionId ? "\u6536\u8D77" : "\u79FB\u52A8\u2026" }),
332
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-del", disabled: busy !== null, onClick: () => act("delete", it), children: confirmDel === it.sessionId ? "\u786E\u8BA4\u5220\u9664?" : "\u5220\u9664" })
333
+ ] });
334
+ }
335
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-actions", children: [
336
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: busy !== null, onClick: () => act("archive", it), children: "\u5F52\u6863" }),
337
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-go", disabled: busy !== null, onClick: () => openMoveFor(it), children: openMove === it.sessionId ? "\u6536\u8D77" : "\u79FB\u52A8\u2026" })
338
+ ] });
159
339
  };
160
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv", role: "region", "aria-label": "\u5F52\u6863\u4F1A\u8BDD", children: [
340
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv", role: "region", "aria-label": "\u4F1A\u8BDD\u7BA1\u7406", children: [
161
341
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: CSS }),
162
342
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-head", children: [
163
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "archv-title", children: "\u5F52\u6863\u4F1A\u8BDD" }),
164
- items !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-count", "aria-label": `${items.length} \u4E2A\u5F52\u6863\u4F1A\u8BDD`, children: items.length })
343
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "archv-title", children: "\u4F1A\u8BDD\u7BA1\u7406" }),
344
+ sessions !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-count", "aria-label": `${sessions.length} \u4E2A\u4F1A\u8BDD`, children: sessions.length })
165
345
  ] }),
166
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "archv-sub", children: "\u88AB\u5F52\u6863\uFF08\u4ECE\u4FA7\u680F\u9690\u85CF\uFF09\u7684\u4F1A\u8BDD\u90FD\u5728\u8FD9\u91CC\u3002\u6062\u590D\u4F1A\u628A\u4F1A\u8BDD\u653E\u56DE\u539F\u5206\u7EC4\uFF1B\u5220\u9664\u4F1A\u5F7B\u5E95\u79FB\u9664\u65E5\u5FD7\u6587\u4EF6\uFF0C\u4E0D\u53EF\u6062\u590D\u3002" }),
346
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "archv-sub", children: "\u7EDF\u4E00\u7BA1\u7406\u5168\u90E8\u4F1A\u8BDD\uFF1A\u5F52\u6863 / \u6062\u590D / \u5F7B\u5E95\u5220\u9664 / \u79FB\u52A8\u5230\u5176\u4ED6\u5DE5\u4F5C\u533A\uFF0C\u652F\u6301\u6279\u91CF\u3002\u5F52\u6863\u4F1A\u4ECE\u4FA7\u680F\u9690\u85CF\uFF1B\u5220\u9664\u4F1A\u5F7B\u5E95\u79FB\u9664\u65E5\u5FD7\uFF0C\u4E0D\u53EF\u6062\u590D\u3002" }),
167
347
  error && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-err", role: "alert", children: [
168
348
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: error }),
169
349
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-errretry", onClick: refresh, children: "\u91CD\u8BD5" })
170
350
  ] }),
171
- items === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-skel", "aria-label": "\u52A0\u8F7D\u4E2D", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-skel-card" }, i)) }) : items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-empty", children: "\u76EE\u524D\u6CA1\u6709\u5F52\u6863\u4F1A\u8BDD\u3002\u5728\u4FA7\u680F\u4EFB\u4E00\u4F1A\u8BDD\u7684\u83DC\u5355\u91CC\u9009\u62E9\u300C\u5F52\u6863\u300D\u5373\u53EF\u6536\u7EB3\u8FDB\u6765\u3002" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-list", role: "list", children: items.map((it) => {
172
- const isConfirm = confirmId === it.sessionId;
173
- const date = fmtDate(it.createdAt);
174
- const wtag = workspaceTag(it);
175
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-card", role: "listitem", children: [
176
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-main", children: [
177
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-name", title: it.title || "", children: it.title || "(\u65E0\u6807\u9898)" }),
178
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-meta", children: [
179
- date && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-date", children: date }),
180
- date && wtag && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-dot", children: "\xB7" }),
181
- wtag,
182
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-id", children: it.sessionId })
183
- ] })
351
+ sessions === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-skel", "aria-label": "\u52A0\u8F7D\u4E2D", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-skel-card" }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
352
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sess-filter", role: "tablist", "aria-label": "\u4F1A\u8BDD\u7B5B\u9009", children: [
353
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "sess-fbtn" + (filter === "all" ? " sess-fbtn-on" : ""), onClick: () => {
354
+ setFilter("all");
355
+ clearSel();
356
+ setConfirmBatch(false);
357
+ }, children: [
358
+ "\u5168\u90E8 (",
359
+ sessions.length,
360
+ ")"
184
361
  ] }),
185
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-actions", children: [
186
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "archv-btn archv-restore", disabled: busy !== null, "aria-label": "\u6062\u590D " + (it.title || it.sessionId), onClick: () => act("restore", it), children: [
187
- busy === it.sessionId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-spin", "aria-hidden": "true" }),
188
- "\u6062\u590D"
362
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "sess-fbtn" + (filter === "archived" ? " sess-fbtn-on" : ""), onClick: () => {
363
+ setFilter("archived");
364
+ clearSel();
365
+ setConfirmBatch(false);
366
+ }, children: [
367
+ "\u5DF2\u5F52\u6863 (",
368
+ archivedList.length,
369
+ ")"
370
+ ] })
371
+ ] }),
372
+ list.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sess-batch", children: [
373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sess-btntext", children: selIds.length ? `\u5DF2\u9009 ${selIds.length} \u9879` : filter === "archived" ? `\u5171 ${archivedList.length} \u4E2A\u5F52\u6863\u4F1A\u8BDD` : `\u5171 ${sessions.length} \u4E2A\u4F1A\u8BDD\uFF08\u6D3B\u52A8 ${activeList.length} / \u5DF2\u5F52\u6863 ${archivedList.length}\uFF09` }),
374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: list.length === 0, onClick: selectAll, children: "\u5168\u9009" }),
375
+ selIds.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
376
+ filter === "archived" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
377
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: busy !== null, onClick: () => doBatch("restore-many"), children: "\u6062\u590D\u6240\u9009" }),
378
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-del", disabled: busy !== null, onClick: () => doBatch("delete-many"), children: confirmBatch ? "\u786E\u8BA4\u5220\u9664\u6240\u9009?" : "\u5220\u9664\u6240\u9009" })
189
379
  ] }),
190
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-del", disabled: busy !== null, "aria-label": (isConfirm ? "\u786E\u8BA4\u5220\u9664 " : "\u5220\u9664 ") + (it.title || it.sessionId), onClick: () => act("delete", it), children: isConfirm ? "\u786E\u8BA4\u5220\u9664?" : "\u5220\u9664" })
380
+ filter === "all" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: busy !== null, onClick: () => doBatch("archive-many"), children: "\u5F52\u6863\u6240\u9009" }),
381
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", onClick: clearSel, children: "\u53D6\u6D88\u9009\u62E9" })
191
382
  ] })
192
- ] }, it.sessionId);
193
- }) }),
383
+ ] }),
384
+ list.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-empty", children: filter === "archived" ? "\u76EE\u524D\u6CA1\u6709\u5F52\u6863\u4F1A\u8BDD\u3002\u5728\u201C\u5168\u90E8\u201D\u91CC\u9009\u4E2D\u4F1A\u8BDD\u70B9\u201C\u5F52\u6863\u201D\u5373\u53EF\u6536\u7EB3\u8FDB\u6765\u3002" : "\u6682\u65E0\u53EF\u7BA1\u7406\u7684\u4F1A\u8BDD\u3002" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-list", role: "list", children: list.map((it) => {
385
+ const date = fmtDate(it.createdAt);
386
+ const expanded = openMove === it.sessionId;
387
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-card" + (expanded ? " archv-card-exp" : ""), role: "listitem", children: [
388
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-row", children: [
389
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
390
+ "input",
391
+ {
392
+ type: "checkbox",
393
+ className: "archv-check",
394
+ checked: !!selected[it.sessionId],
395
+ onChange: () => toggle(it.sessionId),
396
+ "aria-label": "\u9009\u62E9 " + (it.title || it.sessionId)
397
+ }
398
+ ),
399
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-body", children: [
400
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-main", children: [
401
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-name", title: it.title || "", children: it.title || "(\u65E0\u6807\u9898)" }),
402
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-meta", children: [
403
+ it.archived ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-wtag archv-wgone", children: "\u5DF2\u5F52\u6863" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-wtag archv-active", children: "\u6D3B\u52A8" }),
404
+ workspaceTag(it),
405
+ date && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
406
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-dot", children: "\xB7" }),
407
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-date", children: date })
408
+ ] }),
409
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-id", children: it.sessionId })
410
+ ] })
411
+ ] }),
412
+ rowActions(it)
413
+ ] })
414
+ ] }),
415
+ expanded && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-sheet", role: "region", "aria-label": "\u79FB\u52A8\u5230\u5DE5\u4F5C\u533A", children: [
416
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-sheet-head", children: [
417
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "mv-sheet-title", children: "\u79FB\u52A8\u5230\u5DE5\u4F5C\u533A" }),
418
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "mv-sheet-close", "aria-label": "\u5173\u95ED", onClick: () => setOpenMove(null), children: "\xD7" })
419
+ ] }),
420
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-seg", role: "tablist", children: [
421
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", role: "tab", "aria-selected": moveMode === "existing", className: "mv-segbtn" + (moveMode === "existing" ? " mv-segbtn-on" : ""), onClick: () => setMoveMode("existing"), children: "\u5DF2\u6709\u5DE5\u4F5C\u533A" }),
422
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", role: "tab", "aria-selected": moveMode === "new", className: "mv-segbtn" + (moveMode === "new" ? " mv-segbtn-on" : ""), onClick: () => setMoveMode("new"), children: "\u65B0\u5EFA\u76EE\u5F55" })
423
+ ] }),
424
+ moveMode === "existing" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-field", children: [
425
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "mv-field-label", htmlFor: "mv-target-ws", children: "\u76EE\u6807\u5DE5\u4F5C\u533A" }),
426
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { id: "mv-target-ws", value: targetWs, onChange: (e) => setTargetWs(e.target.value), children: [
427
+ workspaces.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\uFF08\u6682\u65E0\u5DE5\u4F5C\u533A\uFF09" }),
428
+ workspaces.map((w) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", { value: w.workspaceId, children: [
429
+ w.title,
430
+ " \xB7 ",
431
+ w.path
432
+ ] }, w.workspaceId))
433
+ ] })
434
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-field", children: [
435
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "mv-field-label", htmlFor: "mv-new-path", children: "\u65B0\u5DE5\u4F5C\u533A\u76EE\u5F55\u8DEF\u5F84" }),
436
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-browse-row", children: [
437
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
438
+ "input",
439
+ {
440
+ id: "mv-new-path",
441
+ type: "text",
442
+ value: newPath,
443
+ onChange: (e) => setNewPath(e.target.value),
444
+ placeholder: "\u4F8B\u5982 /Users/you/Projects/demo \u6216 ~/demo"
445
+ }
446
+ ),
447
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
448
+ "button",
449
+ {
450
+ type: "button",
451
+ className: "archv-btn",
452
+ onClick: pickDirectory,
453
+ disabled: busy !== null || picking || !workspacesSvc,
454
+ title: !workspacesSvc ? "\u5F53\u524D\u8FD0\u884C\u73AF\u5883\u4E0D\u652F\u6301\u7CFB\u7EDF\u76EE\u5F55\u9009\u62E9" : "\u6253\u5F00\u7CFB\u7EDF\u76EE\u5F55\u9009\u62E9\u7A97\u53E3",
455
+ children: picking ? "\u9009\u62E9\u4E2D\u2026" : "\u6D4F\u89C8\u2026"
456
+ }
457
+ )
458
+ ] })
459
+ ] }),
460
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-foot", children: [
461
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", onClick: () => setOpenMove(null), children: "\u53D6\u6D88" }),
462
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "archv-btn archv-go", disabled: busy !== null, onClick: () => doMove(it), children: [
463
+ busy === it.sessionId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-spin", "aria-hidden": "true" }),
464
+ "\u786E\u8BA4\u79FB\u52A8"
465
+ ] })
466
+ ] })
467
+ ] })
468
+ ] }, it.sessionId);
469
+ }) })
470
+ ] }),
194
471
  toast && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-status", role: "status", children: toast })
195
472
  ] });
196
473
  }
197
474
  function apply(ctx) {
475
+ installSettingsNavIcons(ctx);
476
+ const workspacesSvc = ctx.get("workspaces");
198
477
  ctx.slots.inject(
199
478
  "settings.section",
200
479
  () => ctx.slots.register(
201
- { name: "settings.section", id: "archived-sessions", order: 90, label: "\u5F52\u6863\u4F1A\u8BDD" },
202
- (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArchivedPanel, { ...props })
480
+ { name: "settings.section", id: "session-manager", order: 90, label: "\u4F1A\u8BDD\u7BA1\u7406" },
481
+ (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SessionPanel, { ...props, workspacesSvc })
203
482
  )
204
483
  );
205
484
  }