@michengai/dsh-codex-ui 0.2.48

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/dist/client.js ADDED
@@ -0,0 +1,1966 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@michengai/dsh-codex-ui",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ //#region src/client/settings-navigation.ts
11
+ /**
12
+ * DSH 设置壳暂未提供按 section id 打开的公开 API。该兼容层只从本插件渲染
13
+ * 的设置入口触发,再按壳的可访问名称选择页面;宿主升级时可集中替换。
14
+ */
15
+ function openSettingsSection(root, label, onMissing) {
16
+ const trigger = root?.querySelector("[aria-haspopup=\"dialog\"]");
17
+ if (trigger === null || trigger === void 0) {
18
+ onMissing?.();
19
+ return;
20
+ }
21
+ trigger.click();
22
+ window.requestAnimationFrame(() => {
23
+ window.requestAnimationFrame(() => {
24
+ const buttons = document.querySelectorAll("[role=\"dialog\"] nav button");
25
+ for (const button of buttons) if (button.textContent?.trim() === label) {
26
+ button.click();
27
+ return;
28
+ }
29
+ console.warn(`[michengai-codex-ui] 未找到设置分区:${label}`);
30
+ onMissing?.();
31
+ });
32
+ });
33
+ }
34
+ //#endregion
35
+ //#region src/client/sidebar-search.ts
36
+ /** 按用户可见名称和辅助关键词筛选侧栏搜索结果。 */
37
+ function filterSidebarSearchItems(items, query) {
38
+ const needle = query.trim().toLocaleLowerCase();
39
+ if (needle === "") return [...items];
40
+ return items.filter((item) => `${item.label} ${item.keywords}`.toLocaleLowerCase().includes(needle));
41
+ }
42
+ //#endregion
43
+ //#region src/client/CodexSidebar.tsx
44
+ const stylesheet$4 = `
45
+ .dcu-root{--dcu-font:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei UI",sans-serif;--dcu-sidebar-primary:#2f3432;--dcu-sidebar-secondary:#57605c;--dcu-sidebar-tertiary:#747c78;--dcu-sidebar-icon:#4c5652;--dcu-sidebar-hover:#dfe8e5;--dcu-sidebar-border:rgba(37,46,41,.10);height:100%;min-width:0;box-sizing:border-box;display:flex;flex-direction:column;background:#eef7f5;color:var(--dcu-sidebar-primary);font:14px/20px var(--dcu-font)}body[data-ds-dark-theme] .dcu-root{background:#1c1f1e;--dcu-sidebar-primary:#d1d5d3;--dcu-sidebar-secondary:#9ca39f;--dcu-sidebar-tertiary:#747b77;--dcu-sidebar-icon:#b2b8b5;--dcu-sidebar-hover:#303432;--dcu-sidebar-border:rgba(255,255,255,.08)}
46
+ .dcu-root *{box-sizing:border-box}.dcu-head{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:8px;height:60px;padding:8px 10px 8px 16px}.dcu-brand{border:0;background:transparent;color:inherit;padding:0;display:flex;align-items:center;min-width:0;overflow:hidden}.dcu-brand svg{display:block;width:auto;max-width:100%;height:24px;min-width:0}.dcu-head-actions{display:grid;grid-auto-flow:column;grid-auto-columns:28px;align-items:center;column-gap:8px;height:28px}.dcu-fading>*{opacity:0;transition:opacity .15s ease-in-out}@media (prefers-reduced-motion:reduce){.dcu-fading>*{transition:none}}
47
+ .dcu-icon,.dcu-menu button,.dcu-footer-link{appearance:none;border:0;background:transparent;color:inherit;font:inherit;cursor:pointer}.dcu-icon{display:grid;place-items:center;width:36px;height:36px;border-radius:8px;color:var(--dcu-sidebar-icon)}.dcu-head .dcu-icon{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;margin:0;padding:0;border-radius:50%;line-height:0}.dcu-head .dcu-icon svg{display:block;width:16px;height:16px}
48
+ .dcu-icon:hover,.dcu-menu button:hover:not(:disabled),.dcu-footer-link:hover{background:var(--dcu-sidebar-hover);color:var(--dcu-sidebar-primary)}
49
+ .dcu-menu{padding:0 10px 10px;display:grid;gap:2px}.dcu-menu button,.dcu-footer-link{display:grid;grid-template-columns:20px minmax(0,1fr);column-gap:8px;align-items:center;width:100%;min-height:36px;padding:0 4px;border-radius:8px;color:var(--dcu-sidebar-primary);font-size:14px;line-height:20px;text-align:left;font-weight:500}
50
+ .dcu-menu-icon{display:grid;place-items:center start;width:20px;height:20px}.dcu-menu-icon svg,.dcu-footer-link svg{display:block;width:16px;height:16px;color:var(--dcu-sidebar-icon)}.dcu-menu button:disabled{color:var(--dcu-sidebar-secondary);cursor:default;opacity:1}.dcu-menu button:disabled svg{color:var(--dcu-sidebar-secondary)}
51
+ .dcu-extension-items{display:grid;gap:1px;margin:1px 0 4px 22px;padding-left:6px;border-left:1px solid var(--dcu-sidebar-border)}.dcu-extension-items button{color:var(--dcu-sidebar-primary);font-size:14px;font-weight:400}
52
+ .dcu-workspaces{display:flex;min-height:0;flex:1;flex-direction:column;margin-top:2px;padding-top:8px;border-top:1px solid var(--dcu-sidebar-border)}.dcu-native-workspaces{display:flex;min-height:0;flex:1}.dcu-native-workspaces>*{min-width:0;flex:1}.dcu-foot{display:grid;gap:4px;padding:8px 10px 12px;border-top:1px solid var(--dcu-sidebar-border)}.dcu-footer-actions:empty,.dcu-settings-seat:empty{display:none}.dcu-settings-seat>button{width:100%;min-height:36px;padding-left:4px!important;color:var(--dcu-sidebar-primary);font:14px/20px var(--dcu-font);font-weight:500}.dcu-compact{width:100%;align-items:center;overflow:hidden;padding:18px 0 6px}.dcu-compact .dcu-icon{width:36px;height:36px}.dcu-compact .dcu-workspaces{width:100%;flex:1}.dcu-compact .dcu-foot{width:36px;margin-top:auto;padding:8px 0;border-top:0}.dcu-compact .dcu-settings-seat{width:36px;overflow:hidden}.dcu-compact .dcu-footer-link{display:flex;justify-content:center;width:36px;padding:0;font-size:0}.dcu-compact .dcu-footer-link svg{width:16px;height:16px}
53
+ .dcu-dependency-notice{margin:0 10px 8px;border:1px solid var(--dcu-sidebar-border);border-radius:8px;padding:8px;color:var(--dcu-sidebar-secondary);font-size:12px;line-height:18px}
54
+ [role="dialog"][aria-labelledby]{width:min(1000px,calc(100vw - 48px));max-width:calc(100vw - 48px);height:min(800px,calc(100vh - 48px))}
55
+ .dcu-search-scrim{position:fixed;z-index:10020;inset:0;display:flex;justify-content:center;align-items:flex-start;padding:72px 20px;background:color-mix(in srgb,#000 48%,transparent)}.dcu-search-dialog{width:min(560px,100%);max-height:min(640px,calc(100vh - 120px));overflow:auto;border:1px solid var(--dcu-sidebar-border);border-radius:16px;padding:10px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv4)}.dcu-search-input{margin-bottom:8px}.dcu-search-section{padding:6px 0}.dcu-search-title{padding:0 8px 4px;color:var(--dcu-sidebar-tertiary);font-size:12px;font-weight:600}.dcu-search-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;width:100%;min-height:34px;border:0;border-radius:8px;padding:6px 8px;background:transparent;color:var(--dcu-sidebar-primary);font:inherit;text-align:left;cursor:pointer}.dcu-search-row:hover,.dcu-search-row[data-active=true]{background:var(--dcu-sidebar-hover)}.dcu-search-main{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dcu-search-detail{max-width:160px;overflow:hidden;color:var(--dcu-sidebar-tertiary);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.dcu-search-empty{padding:18px 8px;color:var(--dcu-sidebar-tertiary);font-size:13px}
56
+ [data-conversation-scroll]{--dsh-chat-content-width:800px;--dsh-composer-card-max-width:calc(var(--dsh-chat-content-width) + 32px);--dsh-composer-side-clearance:24px}[data-conversation-scroll] [data-chat-flow]{gap:20px}[data-conversation-scroll] [data-composer-card]{min-height:142px;padding-top:14px;border-radius:20px;box-shadow:var(--dsw-shadow-lv3);transition:border-color 180ms ease,box-shadow 180ms ease}[data-conversation-scroll] [data-composer-card]:focus-within{border-color:var(--dsw-alias-button-info-fill);box-shadow:0 0 0 2px var(--dsw-static-deepseek-50),var(--dsw-shadow-lv3)}[data-conversation-scroll] [data-input-mirror]{min-height:78px}@media (prefers-reduced-motion:reduce){[data-conversation-scroll] [data-composer-card]{transition:none}}
57
+ `;
58
+ function MenuIcon({ children }) {
59
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
60
+ className: "dcu-menu-icon",
61
+ children
62
+ });
63
+ }
64
+ /** Codex 风格的 DSH 侧栏,只替换导航外观,项目浏览和设置仍由 DSH 官方组件提供。 */
65
+ function CodexSidebar({ collapsed, width, openSession, startSession, toggleSidebar, renderSlot, t, useSessions, useWorkspaces }) {
66
+ const settingsSeat = (0, react.useRef)(null);
67
+ const [extensionsOpen, setExtensionsOpen] = (0, react.useState)(true);
68
+ const [searchOpen, setSearchOpen] = (0, react.useState)(false);
69
+ const [searchQuery, setSearchQuery] = (0, react.useState)("");
70
+ const [activeSearchIndex, setActiveSearchIndex] = (0, react.useState)(0);
71
+ const [settled, setSettled] = (0, react.useState)(collapsed);
72
+ const lastWideWidth = (0, react.useRef)(width);
73
+ if (!collapsed) lastWideWidth.current = width;
74
+ const sessions = useSessions((state) => state);
75
+ const workspaces = useWorkspaces((state) => state);
76
+ const selectSection = (label) => {
77
+ openSettingsSection(settingsSeat.current, label);
78
+ };
79
+ const selectExternalSection = (label) => {
80
+ openSettingsSection(settingsSeat.current, label, () => {
81
+ selectSection(t("about.nav"));
82
+ });
83
+ };
84
+ const openSettings = () => {
85
+ settingsSeat.current?.querySelector("[aria-haspopup=\"dialog\"]")?.click();
86
+ };
87
+ const closeSearch = () => {
88
+ setSearchOpen(false);
89
+ setSearchQuery("");
90
+ setActiveSearchIndex(0);
91
+ };
92
+ const searchEntries = (0, react.useMemo)(() => {
93
+ const archived = new Set(workspaces.archivedSessionIds);
94
+ const workspaceTitles = new Map(workspaces.items.flatMap((workspace) => workspace.sessionIds.map((sessionId) => [String(sessionId), workspace.title])));
95
+ const sessionEntries = sessions.ids.map((id) => sessions.byId[id]).filter((session) => session !== void 0 && session.origin !== "subagent" && !session.blank && !archived.has(session.id)).sort((left, right) => right.updatedAt - left.updatedAt).map((session) => ({
96
+ id: `session:${session.id}`,
97
+ group: "sessions",
98
+ label: session.displayTitle,
99
+ keywords: `${session.cwd ?? ""} ${session.id}`,
100
+ detail: workspaceTitles.get(String(session.id)) ?? session.cwd,
101
+ run: () => {
102
+ closeSearch();
103
+ openSession(session.id);
104
+ }
105
+ }));
106
+ const settingEntries = [
107
+ {
108
+ id: "settings:root",
109
+ group: "settings",
110
+ label: t("search.settings"),
111
+ keywords: t("search.settings"),
112
+ run: () => {
113
+ closeSearch();
114
+ openSettings();
115
+ }
116
+ },
117
+ {
118
+ id: "settings:experts",
119
+ group: "settings",
120
+ label: t("sidebar.experts"),
121
+ keywords: t("search.settings"),
122
+ run: () => {
123
+ closeSearch();
124
+ selectExternalSection(t("sidebar.experts"));
125
+ }
126
+ },
127
+ {
128
+ id: "settings:skills",
129
+ group: "settings",
130
+ label: t("sidebar.skills"),
131
+ keywords: t("search.settings"),
132
+ run: () => {
133
+ closeSearch();
134
+ selectExternalSection(t("sidebar.skills"));
135
+ }
136
+ },
137
+ {
138
+ id: "settings:plugins",
139
+ group: "settings",
140
+ label: t("sidebar.plugins"),
141
+ keywords: t("search.settings"),
142
+ run: () => {
143
+ closeSearch();
144
+ selectSection(t("sidebar.plugins"));
145
+ }
146
+ },
147
+ {
148
+ id: "settings:connectors",
149
+ group: "settings",
150
+ label: t("sidebar.connectors"),
151
+ keywords: t("search.settings"),
152
+ run: () => {
153
+ closeSearch();
154
+ selectSection(t("sidebar.connectors"));
155
+ }
156
+ },
157
+ {
158
+ id: "settings:about",
159
+ group: "settings",
160
+ label: t("about.nav"),
161
+ keywords: t("search.settings"),
162
+ run: () => {
163
+ closeSearch();
164
+ selectSection(t("about.nav"));
165
+ }
166
+ }
167
+ ];
168
+ return [
169
+ ...sessionEntries,
170
+ ...settingEntries,
171
+ {
172
+ id: "action:new",
173
+ group: "actions",
174
+ label: t("sidebar.newTask"),
175
+ keywords: t("search.actions"),
176
+ run: () => {
177
+ closeSearch();
178
+ startSession();
179
+ }
180
+ }
181
+ ];
182
+ }, [
183
+ openSession,
184
+ sessions,
185
+ startSession,
186
+ t,
187
+ workspaces
188
+ ]);
189
+ const searchResults = (0, react.useMemo)(() => filterSidebarSearchItems(searchEntries, searchQuery).slice(0, 12), [searchEntries, searchQuery]);
190
+ (0, react.useEffect)(() => {
191
+ setActiveSearchIndex(0);
192
+ }, [searchQuery, searchOpen]);
193
+ (0, react.useEffect)(() => {
194
+ if (!searchOpen) return;
195
+ const onKeyDown = (event) => {
196
+ if (event.key === "Escape") {
197
+ event.preventDefault();
198
+ closeSearch();
199
+ return;
200
+ }
201
+ if (event.key === "ArrowDown") {
202
+ event.preventDefault();
203
+ setActiveSearchIndex((index) => Math.min(index + 1, Math.max(0, searchResults.length - 1)));
204
+ return;
205
+ }
206
+ if (event.key === "ArrowUp") {
207
+ event.preventDefault();
208
+ setActiveSearchIndex((index) => Math.max(index - 1, 0));
209
+ return;
210
+ }
211
+ if (event.key === "Enter") {
212
+ const entry = searchResults[activeSearchIndex];
213
+ if (entry !== void 0) {
214
+ event.preventDefault();
215
+ entry.run();
216
+ }
217
+ }
218
+ };
219
+ window.addEventListener("keydown", onKeyDown);
220
+ return () => {
221
+ window.removeEventListener("keydown", onKeyDown);
222
+ };
223
+ }, [
224
+ activeSearchIndex,
225
+ searchOpen,
226
+ searchResults
227
+ ]);
228
+ (0, react.useEffect)(() => {
229
+ if (!collapsed) {
230
+ setSettled(false);
231
+ return;
232
+ }
233
+ const timer = window.setTimeout(() => {
234
+ setSettled(true);
235
+ }, 150);
236
+ return () => {
237
+ window.clearTimeout(timer);
238
+ };
239
+ }, [collapsed]);
240
+ if (!(!collapsed || !settled)) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
241
+ className: "dcu-root dcu-compact",
242
+ "aria-label": t("sidebar.label"),
243
+ children: [
244
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$4 }),
245
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
246
+ type: "button",
247
+ className: "dcu-icon",
248
+ "aria-label": t("sidebar.expand"),
249
+ onClick: toggleSidebar,
250
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPanelLeftOutline16, { size: 16 })
251
+ }),
252
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
253
+ className: "dcu-foot",
254
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
255
+ className: "dcu-footer-actions",
256
+ children: renderSlot("sidebar.footer.action", { wide: false })
257
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
258
+ ref: settingsSeat,
259
+ className: "dcu-settings-seat",
260
+ children: renderSlot("sidebar.settings", { wide: false })
261
+ })]
262
+ })
263
+ ]
264
+ });
265
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
266
+ className: collapsed ? "dcu-root dcu-fading" : "dcu-root",
267
+ style: collapsed ? { width: lastWideWidth.current } : void 0,
268
+ "aria-label": t("sidebar.label"),
269
+ children: [
270
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$4 }),
271
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
272
+ className: "dcu-head",
273
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
274
+ type: "button",
275
+ className: "dcu-brand",
276
+ "aria-label": t("sidebar.newTask"),
277
+ onClick: () => {
278
+ startSession();
279
+ },
280
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.BrandWordmark, { size: 24 })
281
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
282
+ className: "dcu-head-actions",
283
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
284
+ type: "button",
285
+ className: "dcu-icon",
286
+ "aria-label": t("sidebar.collapse"),
287
+ onClick: toggleSidebar,
288
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPanelLeftOutline16, { size: 16 })
289
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
290
+ type: "button",
291
+ className: "dcu-icon",
292
+ "aria-label": t("sidebar.search"),
293
+ onClick: () => {
294
+ setSearchOpen(true);
295
+ },
296
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 16 })
297
+ })]
298
+ })]
299
+ }),
300
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
301
+ className: "dcu-menu",
302
+ "aria-label": t("sidebar.mainMenu"),
303
+ children: [
304
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
305
+ type: "button",
306
+ onClick: () => {
307
+ startSession();
308
+ },
309
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconNewChatOutline16, { size: 16 }) }), t("sidebar.newTask")]
310
+ }),
311
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
312
+ type: "button",
313
+ "aria-expanded": extensionsOpen,
314
+ onClick: () => {
315
+ setExtensionsOpen((open) => !open);
316
+ },
317
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEnhanceOutline16, { size: 16 }) }), t("sidebar.extensions")]
318
+ }),
319
+ extensionsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
320
+ className: "dcu-extension-items",
321
+ children: [
322
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
323
+ type: "button",
324
+ onClick: () => {
325
+ selectExternalSection(t("sidebar.experts"));
326
+ },
327
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconUserOutline16, { size: 16 }) }), t("sidebar.experts")]
328
+ }),
329
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
330
+ type: "button",
331
+ onClick: () => {
332
+ selectExternalSection(t("sidebar.skills"));
333
+ },
334
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutline16, { size: 16 }) }), t("sidebar.skills")]
335
+ }),
336
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
337
+ type: "button",
338
+ onClick: () => {
339
+ selectSection(t("sidebar.plugins"));
340
+ },
341
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPersonalizationOutline16, { size: 16 }) }), t("sidebar.plugins")]
342
+ }),
343
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
344
+ type: "button",
345
+ onClick: () => {
346
+ selectSection(t("sidebar.connectors"));
347
+ },
348
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLinkOutline16, { size: 16 }) }), t("sidebar.connectors")]
349
+ })
350
+ ]
351
+ }),
352
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
353
+ type: "button",
354
+ disabled: true,
355
+ title: t("sidebar.scheduleUnavailable"),
356
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGoalOutline16, { size: 16 }) }), t("sidebar.schedule")]
357
+ }),
358
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
359
+ type: "button",
360
+ disabled: true,
361
+ title: t("sidebar.assistantUnavailable"),
362
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconAgentPresetOutline16, { size: 16 }) }), t("sidebar.assistant")]
363
+ })
364
+ ]
365
+ }),
366
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
367
+ className: "dcu-workspaces",
368
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
369
+ className: "dcu-native-workspaces",
370
+ children: renderSlot("sidebar.workspaces", {
371
+ wide: true,
372
+ expandSidebar: toggleSidebar
373
+ })
374
+ })
375
+ }),
376
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
377
+ className: "dcu-foot",
378
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
379
+ className: "dcu-footer-actions",
380
+ children: renderSlot("sidebar.footer.action", { wide: true })
381
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
382
+ ref: settingsSeat,
383
+ className: "dcu-settings-seat",
384
+ children: renderSlot("sidebar.settings", { wide: true })
385
+ })]
386
+ }),
387
+ searchOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
388
+ className: "dcu-search-scrim",
389
+ onMouseDown: (event) => {
390
+ if (event.target === event.currentTarget) closeSearch();
391
+ },
392
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
393
+ className: "dcu-search-dialog",
394
+ role: "dialog",
395
+ "aria-modal": "true",
396
+ "aria-label": t("sidebar.search"),
397
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
398
+ className: "dcu-search-input",
399
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
400
+ autoFocus: true,
401
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 16 }),
402
+ value: searchQuery,
403
+ placeholder: t("search.placeholder"),
404
+ onChange: (event) => {
405
+ setSearchQuery(event.target.value);
406
+ }
407
+ })
408
+ }), searchResults.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
409
+ className: "dcu-search-empty",
410
+ children: t("search.empty")
411
+ }) : [
412
+ "sessions",
413
+ "settings",
414
+ "actions"
415
+ ].map((group) => {
416
+ const entries = searchResults.filter((entry) => entry.group === group);
417
+ if (entries.length === 0) return null;
418
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
419
+ className: "dcu-search-section",
420
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
421
+ className: "dcu-search-title",
422
+ children: t(`search.${group}`)
423
+ }), entries.map((entry) => {
424
+ const index = searchResults.indexOf(entry);
425
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
426
+ type: "button",
427
+ className: "dcu-search-row",
428
+ "data-active": index === activeSearchIndex,
429
+ onMouseEnter: () => {
430
+ setActiveSearchIndex(index);
431
+ },
432
+ onClick: entry.run,
433
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
434
+ className: "dcu-search-main",
435
+ children: entry.label
436
+ }), entry.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
437
+ className: "dcu-search-detail",
438
+ children: entry.detail
439
+ })]
440
+ }, entry.id);
441
+ })]
442
+ }, group);
443
+ })]
444
+ })
445
+ })
446
+ ]
447
+ });
448
+ }
449
+ //#endregion
450
+ //#region src/client/AboutSection.tsx
451
+ const endpoint = "/api/michengai/codex-ui/dependencies";
452
+ const stylesheet$3 = `
453
+ .dcu-about{color:var(--dsw-alias-label-primary)}.dcu-about h2{margin:0;font-size:20px;line-height:28px}.dcu-about-intro{margin:6px 0 22px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}.dcu-about h3{margin:24px 0 8px;font-size:14px;line-height:20px}.dcu-about-features{margin:0;padding-left:20px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:24px}.dcu-about-dependencies{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px}.dcu-about-dependency{display:flex;align-items:center;gap:12px;min-height:64px;padding:12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dcu-about-dependency:last-child{border-bottom:0}.dcu-about-copy{min-width:0;flex:1}.dcu-about-name{font-size:13px;font-weight:600}.dcu-about-package{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.dcu-about-status{display:flex;align-items:center;gap:5px;font-size:12px}.dcu-about-status[data-installed=true]{color:var(--dsw-alias-state-success-primary)}.dcu-about-status[data-installed=false]{color:var(--dsw-alias-state-error-primary)}.dcu-about-status[data-update=true]{color:var(--dsw-alias-state-warning-primary)}.dcu-about-install{display:inline-flex;align-items:center;gap:5px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;padding:6px 9px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}.dcu-about-install:hover:not(:disabled){background:var(--dsw-specific-menu-item-hover)}.dcu-about-install:disabled{cursor:wait;opacity:.65}.dcu-about-message{margin:10px 0 0;border-radius:8px;padding:9px 10px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,transparent);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.dcu-about-message[data-error=true]{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);color:var(--dsw-alias-state-error-primary)}
454
+ `;
455
+ function isDependencyStatus(value) {
456
+ if (value === null || typeof value !== "object") return false;
457
+ const status = value;
458
+ return (status.id === "experts" || status.id === "skills" || status.id === "archive") && typeof status.packageName === "string" && typeof status.installed === "boolean" && typeof status.updateAvailable === "boolean";
459
+ }
460
+ /** Codex UI 的功能说明、配套管理插件状态与受限安装入口。 */
461
+ function AboutSection({ t }) {
462
+ const [dependencies, setDependencies] = (0, react.useState)([]);
463
+ const [state, setState] = (0, react.useState)("loading");
464
+ const [installing, setInstalling] = (0, react.useState)();
465
+ const [message, setMessage] = (0, react.useState)();
466
+ const load = (0, react.useCallback)(async () => {
467
+ setState("loading");
468
+ try {
469
+ const response = await fetch(endpoint, { cache: "no-store" });
470
+ const payload = await response.json();
471
+ if (!response.ok || !Array.isArray(payload.dependencies) || !payload.dependencies.every(isDependencyStatus)) throw new Error();
472
+ setDependencies(payload.dependencies);
473
+ setState("ready");
474
+ } catch {
475
+ setState("failed");
476
+ }
477
+ }, []);
478
+ (0, react.useEffect)(() => {
479
+ load();
480
+ }, [load]);
481
+ const install = async (id) => {
482
+ setInstalling(id);
483
+ setMessage(void 0);
484
+ try {
485
+ const response = await fetch(`${endpoint}?dependency=${encodeURIComponent(id)}`, { method: "POST" });
486
+ const payload = await response.json();
487
+ if (!response.ok || !Array.isArray(payload.dependencies) || !payload.dependencies.every(isDependencyStatus)) throw new Error(typeof payload.error === "string" ? payload.error : t("about.installFailed"));
488
+ setDependencies(payload.dependencies);
489
+ setState("ready");
490
+ setMessage({
491
+ error: false,
492
+ text: t("about.restartRequired")
493
+ });
494
+ } catch (error) {
495
+ setMessage({
496
+ error: true,
497
+ text: error instanceof Error ? error.message : t("about.installFailed")
498
+ });
499
+ } finally {
500
+ setInstalling(void 0);
501
+ }
502
+ };
503
+ const title = (id) => t(`about.dependency.${id}`);
504
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
505
+ className: "dcu-about",
506
+ "aria-label": t("about.nav"),
507
+ children: [
508
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$3 }),
509
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: t("about.title") }),
510
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
511
+ className: "dcu-about-intro",
512
+ children: t("about.description")
513
+ }),
514
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("about.features") }),
515
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
516
+ className: "dcu-about-features",
517
+ children: [
518
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("about.feature.sidebar") }),
519
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("about.feature.search") }),
520
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("about.feature.workspace") }),
521
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("about.feature.sessions") }),
522
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("about.feature.conversation") }),
523
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("about.feature.navigator") })
524
+ ]
525
+ }),
526
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("about.dependencies") }),
527
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
528
+ className: "dcu-about-intro",
529
+ children: t("about.dependenciesDescription")
530
+ }),
531
+ state === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
532
+ className: "dcu-about-message",
533
+ children: t("about.loading")
534
+ }) : state === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
535
+ className: "dcu-about-message",
536
+ "data-error": "true",
537
+ children: t("about.statusFailed")
538
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
539
+ className: "dcu-about-dependencies",
540
+ children: dependencies.map((dependency) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
541
+ className: "dcu-about-dependency",
542
+ children: [
543
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
544
+ className: "dcu-about-copy",
545
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
546
+ className: "dcu-about-name",
547
+ children: title(dependency.id)
548
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
549
+ className: "dcu-about-package",
550
+ children: [
551
+ dependency.packageName,
552
+ dependency.version === void 0 ? "" : ` · ${dependency.version}`,
553
+ dependency.updateAvailable && dependency.latestVersion !== void 0 ? ` → ${dependency.latestVersion}` : ""
554
+ ]
555
+ })]
556
+ }),
557
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
558
+ className: "dcu-about-status",
559
+ "data-installed": dependency.installed,
560
+ "data-update": dependency.updateAvailable,
561
+ children: [dependency.installed && !dependency.updateAvailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 }), dependency.updateAvailable ? t("about.updateAvailable") : dependency.installed ? t("about.installed") : t("about.missing")]
562
+ }),
563
+ (!dependency.installed || dependency.updateAvailable) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
564
+ className: "dcu-about-install",
565
+ type: "button",
566
+ disabled: installing === dependency.id,
567
+ onClick: () => {
568
+ install(dependency.id);
569
+ },
570
+ children: [installing === dependency.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { size: 14 }), installing === dependency.id ? t("about.installing") : dependency.updateAvailable ? t("about.update") : t("about.install")]
571
+ })
572
+ ]
573
+ }, dependency.id))
574
+ }),
575
+ message !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
576
+ className: "dcu-about-message",
577
+ "data-error": message.error,
578
+ children: message.text
579
+ })
580
+ ]
581
+ });
582
+ }
583
+ //#endregion
584
+ //#region src/client/pinned-workspaces.ts
585
+ /** 浏览器本地持久化键;置顶只影响本插件中的工作区分区。 */
586
+ const PINNED_WORKSPACES_STORAGE_KEY = "dsh-codex-ui.pinned-workspace-ids";
587
+ /** 清理无效或重复的工作区标识。 */
588
+ function normalizePinnedWorkspaceIds(ids) {
589
+ return [...new Set(ids.filter((id) => id.trim() !== ""))];
590
+ }
591
+ /** 在置顶列表中切换一个工作区。 */
592
+ function togglePinnedWorkspace(ids, workspaceId) {
593
+ return ids.includes(workspaceId) ? ids.filter((id) => id !== workspaceId) : [...ids, workspaceId];
594
+ }
595
+ /** 从浏览器本地存储读取置顶工作区;损坏数据按空列表处理。 */
596
+ function readPinnedWorkspaceIds(storage) {
597
+ if (storage === void 0) return [];
598
+ try {
599
+ const value = JSON.parse(storage.getItem("dsh-codex-ui.pinned-workspace-ids") ?? "[]");
600
+ return Array.isArray(value) && value.every((id) => typeof id === "string") ? normalizePinnedWorkspaceIds(value) : [];
601
+ } catch {
602
+ return [];
603
+ }
604
+ }
605
+ /** 保存置顶工作区;存储不可用时不影响导航。 */
606
+ function savePinnedWorkspaceIds(storage, ids) {
607
+ if (storage === void 0) return;
608
+ try {
609
+ storage.setItem(PINNED_WORKSPACES_STORAGE_KEY, JSON.stringify(normalizePinnedWorkspaceIds(ids)));
610
+ } catch {}
611
+ }
612
+ //#endregion
613
+ //#region src/client/session-manager.ts
614
+ const SESSION_PINS_STORAGE_KEY = "dsh.session-pins.v1";
615
+ const SESSION_UNREAD_STORAGE_KEY = "dsh.session-unread.v1";
616
+ function normalizeSessionIds(ids) {
617
+ return [...new Set(ids.filter((id) => id.trim() !== ""))];
618
+ }
619
+ function toggleSessionId(ids, sessionId) {
620
+ return ids.includes(sessionId) ? ids.filter((id) => id !== sessionId) : [sessionId, ...ids.filter((id) => id !== sessionId)];
621
+ }
622
+ function readSessionIds(storage, key) {
623
+ if (storage === void 0) return [];
624
+ try {
625
+ const value = JSON.parse(storage.getItem(key) ?? "{\"version\":1,\"ids\":[]}");
626
+ if (value === null || typeof value !== "object") return [];
627
+ const ids = value.ids;
628
+ return Array.isArray(ids) && ids.every((id) => typeof id === "string") ? normalizeSessionIds(ids) : [];
629
+ } catch {
630
+ return [];
631
+ }
632
+ }
633
+ function writeSessionIds(storage, key, ids) {
634
+ if (storage === void 0) return;
635
+ try {
636
+ storage.setItem(key, JSON.stringify({
637
+ version: 1,
638
+ ids: normalizeSessionIds(ids)
639
+ }));
640
+ } catch {}
641
+ }
642
+ function sessionDeepLink(base, sessionId) {
643
+ const url = new URL(base);
644
+ url.searchParams.set("session", sessionId);
645
+ return url.toString();
646
+ }
647
+ //#endregion
648
+ //#region src/client/workspace-browser.ts
649
+ /** 过滤不应出现在工作区树中的会话。 */
650
+ function visibleSessionIds(ids, byId, archivedIds) {
651
+ const archived = new Set(archivedIds);
652
+ return ids.filter((id) => {
653
+ const session = byId[id];
654
+ return session !== void 0 && session.origin !== "subagent" && !session.blank && !archived.has(id);
655
+ });
656
+ }
657
+ /** 将一个项目或会话移到指定项之前;省略锚点时追加到末尾。 */
658
+ function moveBefore(ids, id, beforeId) {
659
+ if (!ids.includes(id) || beforeId === id) return [...ids];
660
+ const next = ids.filter((item) => item !== id);
661
+ const index = beforeId === void 0 ? next.length : next.indexOf(beforeId);
662
+ next.splice(index < 0 ? next.length : index, 0, id);
663
+ return next;
664
+ }
665
+ //#endregion
666
+ //#region src/client/CodexWorkspaceBrowser.tsx
667
+ const stylesheet$2 = `
668
+ .dcu-wb{--dcu-wb-inset:10px;display:flex;flex:1;min-height:0;flex-direction:column;padding:4px var(--dcu-wb-inset) 8px;color:var(--dcu-sidebar-primary)}
669
+ .dcu-wb *{box-sizing:border-box}
670
+ .dcu-wb-tree{flex:1;min-height:0;overflow-y:auto;padding-bottom:16px;scrollbar-gutter:stable;user-select:none;-webkit-user-select:none}
671
+ .dcu-wb-section+.dcu-wb-section{margin-top:10px}
672
+ .dcu-wb-section-label{display:flex;align-items:center;min-height:28px;padding:0 6px;color:var(--dcu-sidebar-secondary);font-size:12px;font-weight:650}
673
+ .dcu-wb-project{position:relative}
674
+ .dcu-wb-project-head,.dcu-wb-session{display:flex;align-items:center;gap:6px;width:100%;border-radius:8px;padding:0 8px;color:var(--dcu-sidebar-primary);cursor:pointer}
675
+ .dcu-wb-project-head{height:34px;background:transparent;font:inherit;text-align:left}
676
+ .dcu-wb-project-head:hover,.dcu-wb-project-head.dcu-wb-menu-open,.dcu-wb-session:hover,.dcu-wb-session.dcu-wb-selected,.dcu-wb-session.dcu-wb-menu-open{background:var(--dcu-sidebar-hover)}
677
+ .dcu-wb-project-head[draggable=true],.dcu-wb-session[draggable=true]{cursor:grab}
678
+ .dcu-wb-project-head[draggable=true]:active,.dcu-wb-session[draggable=true]:active{cursor:grabbing}
679
+ .dcu-wb-project-head.dcu-wb-drop,.dcu-wb-session.dcu-wb-drop{box-shadow:inset 0 2px var(--dsw-alias-state-business-primary)}.dcu-wb-project-head:focus,.dcu-wb-session:focus{outline:0}.dcu-wb-drag-ghost{position:fixed;top:-120px;left:-240px;z-index:10040;max-width:220px;height:32px;padding:0 10px;border:1px solid var(--dcu-sidebar-border);border-radius:8px;background:var(--dcu-sidebar-hover);color:var(--dcu-sidebar-primary);font:13px/32px var(--dcu-font,inherit);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none}
680
+ .dcu-wb-folder{display:grid;place-items:center;flex:none;width:16px;height:20px;color:var(--dcu-sidebar-icon)}
681
+ .dcu-wb-project-current .dcu-wb-folder{color:var(--dsw-alias-state-business-primary)}
682
+ .dcu-wb-project-title,.dcu-wb-session-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px;line-height:20px}
683
+ .dcu-wb-project-title{flex:1;font-weight:550}
684
+ .dcu-wb-session{position:relative;min-height:32px;gap:0;padding-left:32px}
685
+ .dcu-wb-session-title{flex:1;margin-left:0}
686
+ .dcu-wb-pin{display:grid;place-items:center;flex:none;width:16px;height:20px;margin-left:6px;color:var(--dcu-sidebar-tertiary)}
687
+ .dcu-wb-pin svg,.dcu-wb-quick-pin svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.4}
688
+ .dcu-wb-time{flex:none;color:var(--dcu-sidebar-tertiary);font-size:12px;line-height:20px}
689
+ .dcu-wb-actions{display:none;align-items:center;gap:8px;flex:none}
690
+ .dcu-wb-quick-actions{display:none;align-items:center;gap:2px;flex:none}
691
+ .dcu-wb-project-head:hover .dcu-wb-actions,.dcu-wb-project-head.dcu-wb-menu-open .dcu-wb-actions,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-actions{display:flex}
692
+ .dcu-wb-session:hover .dcu-wb-quick-actions,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-quick-actions{display:flex}
693
+ .dcu-wb-session:hover .dcu-wb-time,.dcu-wb-session:hover .dcu-wb-unread,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-time,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-unread{display:none}
694
+ .dcu-wb-more{display:grid;place-items:center;width:20px;height:20px;border:0;border-radius:4px;padding:0;background:transparent;color:var(--dcu-sidebar-tertiary);cursor:pointer}
695
+ .dcu-wb-more:hover{color:var(--dcu-sidebar-primary)}
696
+ .dcu-wb-context-anchor{opacity:0;pointer-events:none}
697
+ .dcu-wb-unread{flex:none;width:7px;height:7px;margin-left:8px;margin-right:8px;border-radius:50%;background:var(--dsw-alias-state-business-primary)}
698
+ .dcu-wb-empty{padding:14px 8px;color:var(--dcu-sidebar-tertiary);font-size:13px}
699
+ .dcu-wb-error{margin:4px 0;padding:6px 8px;border-radius:6px;background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);color:var(--dsw-alias-state-error-primary);font-size:12px}
700
+ .dcu-wb-rail{display:none}
701
+ .dcu-wb-rename-actions{display:flex;justify-content:flex-end;gap:8px}
702
+ .dcu-wb-delete-button{color:var(--dsw-alias-state-error-primary)!important}
703
+ .dcu-wb-delete-copy{margin:0;color:var(--dcu-sidebar-secondary);font-size:13px;line-height:20px}
704
+ .dcu-wb-rename-input{width:100%;height:44px;border:1px solid var(--dcu-sidebar-border);border-radius:22px;padding:7px 14px;outline:0;background:transparent;color:var(--dcu-sidebar-primary);font:inherit}
705
+ .dcu-wb-rename-input:focus{border-color:var(--dsw-alias-button-info-fill)}
706
+ `;
707
+ const runningStyles = `.dcu-wb-running{flex:none;width:12px;height:12px;border:2px solid color-mix(in srgb,var(--dcu-sidebar-secondary) 25%,transparent);border-top-color:var(--dcu-sidebar-secondary);border-radius:50%;animation:dcu-wb-spin .8s linear infinite}@keyframes dcu-wb-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.dcu-wb-running{animation:none}}`;
708
+ const typographyStyles = `.dcu-wb{font:14px/20px var(--dcu-font,var(--dsw-font-family))}.dcu-wb-section-label{color:var(--dcu-sidebar-tertiary);font-size:12px;font-weight:500}.dcu-wb-project-title{font-weight:500}.dcu-wb-session-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-primary)}.dcu-wb-time{color:var(--dcu-sidebar-secondary);font-size:12px;line-height:20px}.dcu-wb-empty{color:var(--dcu-sidebar-tertiary);font-size:12px;line-height:18px}`;
709
+ function PinIcon() {
710
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
711
+ viewBox: "0 0 16 16",
712
+ width: 16,
713
+ height: 16,
714
+ "aria-hidden": "true",
715
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
716
+ d: "M5.6 9.7 3.2 14M10.9 2.4c.9.9 1.1 2.2.5 3.3L10 7.6l2.3 2.3c.3.3.3.8 0 1.1l-.5.5c-.3.3-.8.3-1.1 0L8.4 9.2 6.5 10.6c-1.1.6-2.4.4-3.3-.5",
717
+ fill: "none",
718
+ stroke: "currentColor",
719
+ strokeWidth: "1.35",
720
+ strokeLinecap: "round",
721
+ strokeLinejoin: "round"
722
+ })
723
+ });
724
+ }
725
+ function storage() {
726
+ return typeof window === "undefined" ? void 0 : window.localStorage;
727
+ }
728
+ function browserBase() {
729
+ return typeof window === "undefined" || window.location.origin === "null" ? "http://dsh.internal/" : `${window.location.origin}/`;
730
+ }
731
+ function relativeTime(updatedAt) {
732
+ const seconds = Math.max(0, Math.round((Date.now() - updatedAt) / 1e3));
733
+ if (seconds < 60) return "刚刚";
734
+ if (seconds < 3600) return `${Math.floor(seconds / 60)} 分钟前`;
735
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)} 小时前`;
736
+ return `${Math.floor(seconds / 86400)} 天前`;
737
+ }
738
+ /** 插件自有的工作区树:复刻原生层级和拖拽行为,并在每个会话菜单中增加管理操作。 */
739
+ function CodexWorkspaceBrowser({ wide, useSessions, useWorkspaces, t, archiveSession, deleteSession, deleteWorkspace, forkSession, insertSessionBefore, insertWorkspaceBefore, openPath, openSession, renameSession, renameWorkspace, startSession }) {
740
+ const sessions = useSessions((state) => state);
741
+ const workspaces = useWorkspaces((state) => state);
742
+ const [expanded, setExpanded] = (0, react.useState)({});
743
+ const [pinnedWorkspaceIds, setPinnedWorkspaceIds] = (0, react.useState)(() => readPinnedWorkspaceIds(storage()));
744
+ const [pinnedSessionIds, setPinnedSessionIds] = (0, react.useState)(() => readSessionIds(storage(), SESSION_PINS_STORAGE_KEY));
745
+ const [unreadSessionIds, setUnreadSessionIds] = (0, react.useState)(() => readSessionIds(storage(), SESSION_UNREAD_STORAGE_KEY));
746
+ const [menu, setMenu] = (0, react.useState)();
747
+ const [renameTarget, setRenameTarget] = (0, react.useState)();
748
+ const [deleteTarget, setDeleteTarget] = (0, react.useState)();
749
+ const [renameDraft, setRenameDraft] = (0, react.useState)("");
750
+ const [busy, setBusy] = (0, react.useState)();
751
+ const [error, setError] = (0, react.useState)();
752
+ const [workspaceDragId, setWorkspaceDragId] = (0, react.useState)();
753
+ const [workspaceDropId, setWorkspaceDropId] = (0, react.useState)();
754
+ const [sessionDrag, setSessionDrag] = (0, react.useState)();
755
+ const [sessionDropId, setSessionDropId] = (0, react.useState)();
756
+ const pendingPinRef = (0, react.useRef)();
757
+ (0, react.useEffect)(() => {
758
+ savePinnedWorkspaceIds(storage(), pinnedWorkspaceIds);
759
+ }, [pinnedWorkspaceIds]);
760
+ (0, react.useEffect)(() => {
761
+ writeSessionIds(storage(), SESSION_PINS_STORAGE_KEY, pinnedSessionIds);
762
+ }, [pinnedSessionIds]);
763
+ (0, react.useEffect)(() => {
764
+ writeSessionIds(storage(), SESSION_UNREAD_STORAGE_KEY, unreadSessionIds);
765
+ }, [unreadSessionIds]);
766
+ (0, react.useEffect)(() => {
767
+ const valid = new Set(workspaces.items.map((workspace) => String(workspace.workspaceId)));
768
+ setPinnedWorkspaceIds((ids) => ids.filter((id) => valid.has(id)));
769
+ }, [workspaces.items]);
770
+ (0, react.useEffect)(() => {
771
+ const current = sessions.current;
772
+ if (current !== void 0) setUnreadSessionIds((ids) => ids.filter((id) => id !== current));
773
+ }, [sessions.current]);
774
+ const groups = (0, react.useMemo)(() => {
775
+ const archived = workspaces.archivedSessionIds;
776
+ const visible = (ids) => visibleSessionIds(ids, sessions.byId, archived).sort((left, right) => {
777
+ const leftPinned = pinnedSessionIds.indexOf(left);
778
+ const rightPinned = pinnedSessionIds.indexOf(right);
779
+ if (leftPinned !== -1 || rightPinned !== -1) return (leftPinned === -1 ? Number.MAX_SAFE_INTEGER : leftPinned) - (rightPinned === -1 ? Number.MAX_SAFE_INTEGER : rightPinned);
780
+ return 0;
781
+ });
782
+ return { items: workspaces.items.map((workspace) => ({
783
+ ...workspace,
784
+ visibleIds: visible(workspace.sessionIds)
785
+ })) };
786
+ }, [
787
+ pinnedSessionIds,
788
+ sessions.byId,
789
+ sessions.ids,
790
+ workspaces.archivedSessionIds,
791
+ workspaces.items
792
+ ]);
793
+ const projectPinned = (id) => pinnedWorkspaceIds.includes(String(id));
794
+ const pinnedGroups = groups.items.filter((workspace) => projectPinned(workspace.workspaceId));
795
+ const regularGroups = groups.items.filter((workspace) => !projectPinned(workspace.workspaceId));
796
+ const run = async (key, action) => {
797
+ setBusy(key);
798
+ setError(void 0);
799
+ try {
800
+ await action();
801
+ setMenu(void 0);
802
+ } catch (reason) {
803
+ setError(reason instanceof Error ? reason.message : String(reason));
804
+ } finally {
805
+ setBusy(void 0);
806
+ }
807
+ };
808
+ const beginRename = (kind, id, title) => {
809
+ setRenameTarget({
810
+ kind,
811
+ id,
812
+ title
813
+ });
814
+ setRenameDraft(title);
815
+ setMenu(void 0);
816
+ };
817
+ const submitRename = () => {
818
+ if (renameTarget === void 0 || renameDraft.trim() === "") return;
819
+ run("rename", async () => {
820
+ if (renameTarget.kind === "workspace") await renameWorkspace(renameTarget.id, renameDraft.trim());
821
+ else await renameSession(renameTarget.id, renameDraft.trim());
822
+ setRenameTarget(void 0);
823
+ });
824
+ };
825
+ const submitDelete = () => {
826
+ if (deleteTarget === void 0) return;
827
+ const target = deleteTarget;
828
+ const operation = target.kind === "session" ? "delete-session" : "delete-workspace";
829
+ run(operation, async () => {
830
+ if (target.kind === "session") {
831
+ await deleteSession(target.id);
832
+ setPinnedSessionIds((ids) => ids.filter((id) => id !== target.id));
833
+ setUnreadSessionIds((ids) => ids.filter((id) => id !== target.id));
834
+ } else {
835
+ await deleteWorkspace(target.id);
836
+ setPinnedWorkspaceIds((ids) => ids.filter((id) => id !== target.id));
837
+ }
838
+ setDeleteTarget(void 0);
839
+ });
840
+ };
841
+ const copy = (value) => {
842
+ if (value === void 0 || value === "") return;
843
+ run("copy", async () => {
844
+ await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(value);
845
+ });
846
+ };
847
+ const toggleGroup = (workspaceId) => {
848
+ setExpanded((current) => ({
849
+ ...current,
850
+ [workspaceId]: !(current[workspaceId] ?? true)
851
+ }));
852
+ };
853
+ const projectMenu = (workspace) => [
854
+ {
855
+ id: "new",
856
+ label: t("workspace.newSession"),
857
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 16 })
858
+ },
859
+ {
860
+ id: "rename",
861
+ label: t("workspace.rename"),
862
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 16 })
863
+ },
864
+ {
865
+ id: "pin",
866
+ label: t(projectPinned(workspace.workspaceId) ? "workspace.unpin" : "workspace.pin"),
867
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinIcon, {})
868
+ },
869
+ {
870
+ id: "openPath",
871
+ label: t("workspace.openPath"),
872
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, { size: 16 })
873
+ },
874
+ {
875
+ type: "separator",
876
+ id: "project-separator"
877
+ },
878
+ {
879
+ id: "delete",
880
+ label: t("workspace.delete"),
881
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 16 }),
882
+ danger: true
883
+ }
884
+ ];
885
+ const sessionMenu = (sessionId, title, path) => {
886
+ const pinned = pinnedSessionIds.includes(sessionId);
887
+ const unread = unreadSessionIds.includes(sessionId);
888
+ return [
889
+ {
890
+ id: "rename",
891
+ label: t("sessions.rename"),
892
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 16 })
893
+ },
894
+ {
895
+ id: "pin",
896
+ label: t(pinned ? "sessions.unpin" : "sessions.pin"),
897
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinIcon, {})
898
+ },
899
+ {
900
+ id: "unread",
901
+ label: t(unread ? "sessions.markRead" : "sessions.markUnread"),
902
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dcu-wb-unread" })
903
+ },
904
+ {
905
+ id: "archive",
906
+ label: t("sessions.archive"),
907
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })
908
+ },
909
+ {
910
+ type: "separator",
911
+ id: "main-separator"
912
+ },
913
+ {
914
+ id: "fork",
915
+ label: t("sessions.fork"),
916
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, { size: 16 })
917
+ },
918
+ {
919
+ type: "separator",
920
+ id: "copy-separator"
921
+ },
922
+ {
923
+ id: "openPath",
924
+ label: t("sessions.openPath"),
925
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, { size: 16 }),
926
+ disabled: path === void 0
927
+ },
928
+ {
929
+ id: "copyPath",
930
+ label: t("sessions.copyPath"),
931
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 16 }),
932
+ disabled: path === void 0
933
+ },
934
+ {
935
+ id: "copyTitle",
936
+ label: t("sessions.copyTitle"),
937
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 16 })
938
+ },
939
+ {
940
+ id: "copyId",
941
+ label: t("sessions.copyId"),
942
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLinkOutline16, { size: 16 })
943
+ },
944
+ {
945
+ id: "copyLink",
946
+ label: t("sessions.copyLink"),
947
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconShareOutline16, { size: 16 })
948
+ },
949
+ {
950
+ type: "separator",
951
+ id: "delete-separator"
952
+ },
953
+ {
954
+ id: "delete",
955
+ label: t("sessions.delete"),
956
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 16 }),
957
+ danger: true
958
+ }
959
+ ];
960
+ };
961
+ const renderGroup = (workspace) => {
962
+ const isExpanded = expanded[workspace.workspaceId] ?? true;
963
+ const menuOpen = menu?.type === "workspace" && menu.id === workspace.workspaceId;
964
+ const isCurrentWorkspace = sessions.current !== void 0 && workspace.visibleIds.includes(sessions.current);
965
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
966
+ className: `dcu-wb-project${isCurrentWorkspace ? " dcu-wb-project-current" : ""}`,
967
+ onDragOver: (event) => {
968
+ if (workspaceDragId === void 0) return;
969
+ event.preventDefault();
970
+ setWorkspaceDropId(workspace.workspaceId);
971
+ },
972
+ onDrop: (event) => {
973
+ event.preventDefault();
974
+ const dragged = workspaceDragId;
975
+ setWorkspaceDragId(void 0);
976
+ setWorkspaceDropId(void 0);
977
+ if (dragged !== void 0 && dragged !== workspace.workspaceId && moveBefore(groups.items.map((item) => String(item.workspaceId)), dragged, String(workspace.workspaceId)).join() !== groups.items.map((item) => String(item.workspaceId)).join()) run("workspace-order", () => insertWorkspaceBefore(dragged, workspace.workspaceId));
978
+ },
979
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
980
+ className: `dcu-wb-project-head${menuOpen ? " dcu-wb-menu-open" : ""}${workspaceDropId === workspace.workspaceId ? " dcu-wb-drop" : ""}`,
981
+ role: "treeitem",
982
+ "aria-expanded": isExpanded,
983
+ tabIndex: 0,
984
+ draggable: true,
985
+ onDragStart: (event) => {
986
+ event.dataTransfer.effectAllowed = "move";
987
+ event.dataTransfer.setData("text/plain", workspace.title);
988
+ const preview = document.createElement("div");
989
+ preview.className = "dcu-wb-drag-ghost";
990
+ preview.textContent = workspace.title;
991
+ document.body.appendChild(preview);
992
+ event.dataTransfer.setDragImage(preview, 16, 18);
993
+ window.requestAnimationFrame(() => {
994
+ preview.remove();
995
+ });
996
+ setWorkspaceDragId(workspace.workspaceId);
997
+ },
998
+ onDragEnd: () => {
999
+ const pin = pendingPinRef.current;
1000
+ pendingPinRef.current = void 0;
1001
+ setWorkspaceDragId(void 0);
1002
+ setWorkspaceDropId(void 0);
1003
+ if (pin !== void 0) setPinnedWorkspaceIds((ids) => ids.includes(pin) ? ids : [pin, ...ids]);
1004
+ },
1005
+ onClick: () => {
1006
+ toggleGroup(workspace.workspaceId);
1007
+ },
1008
+ onContextMenu: (event) => {
1009
+ event.preventDefault();
1010
+ event.stopPropagation();
1011
+ setMenu({
1012
+ id: workspace.workspaceId,
1013
+ type: "workspace"
1014
+ });
1015
+ },
1016
+ onKeyDown: (event) => {
1017
+ if (event.key === "Enter" || event.key === " ") {
1018
+ event.preventDefault();
1019
+ toggleGroup(workspace.workspaceId);
1020
+ }
1021
+ },
1022
+ children: [
1023
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1024
+ className: "dcu-wb-folder",
1025
+ children: isExpanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, { size: 16 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 })
1026
+ }),
1027
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1028
+ className: "dcu-wb-project-title",
1029
+ title: workspace.path,
1030
+ children: workspace.title
1031
+ }),
1032
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1033
+ className: "dcu-wb-actions",
1034
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1035
+ open: menuOpen,
1036
+ onClose: () => {
1037
+ setMenu(void 0);
1038
+ },
1039
+ items: projectMenu(workspace),
1040
+ onSelect: (id) => {
1041
+ if (busy !== void 0) return;
1042
+ if (id === "new") {
1043
+ startSession(workspace.workspaceId);
1044
+ setMenu(void 0);
1045
+ }
1046
+ if (id === "rename") beginRename("workspace", workspace.workspaceId, workspace.title);
1047
+ if (id === "pin") {
1048
+ setPinnedWorkspaceIds((ids) => togglePinnedWorkspace(ids, workspace.workspaceId));
1049
+ setMenu(void 0);
1050
+ }
1051
+ if (id === "openPath") run("open-path", () => openPath(workspace.path));
1052
+ if (id === "delete") {
1053
+ setDeleteTarget({
1054
+ id: workspace.workspaceId,
1055
+ kind: "workspace",
1056
+ title: workspace.title
1057
+ });
1058
+ setError(void 0);
1059
+ setMenu(void 0);
1060
+ }
1061
+ },
1062
+ portal: true,
1063
+ dense: true,
1064
+ compact: true,
1065
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1066
+ type: "button",
1067
+ className: "dcu-wb-more",
1068
+ "aria-label": t("workspace.actions", { name: workspace.title }),
1069
+ onClick: (event) => {
1070
+ event.stopPropagation();
1071
+ setMenu((current) => current?.id === workspace.workspaceId && current.type === "workspace" ? void 0 : {
1072
+ id: workspace.workspaceId,
1073
+ type: "workspace"
1074
+ });
1075
+ },
1076
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, { size: 16 })
1077
+ })
1078
+ })
1079
+ }),
1080
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1081
+ className: "dcu-wb-actions",
1082
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1083
+ type: "button",
1084
+ className: "dcu-wb-more",
1085
+ "aria-label": t("workspace.newSession"),
1086
+ onClick: (event) => {
1087
+ event.stopPropagation();
1088
+ startSession(workspace.workspaceId);
1089
+ },
1090
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 16 })
1091
+ })
1092
+ })
1093
+ ]
1094
+ }), isExpanded && workspace.visibleIds.map((id) => {
1095
+ const session = sessions.byId[id];
1096
+ if (session === void 0) return null;
1097
+ const path = session.cwd ?? workspace.path;
1098
+ const selected = sessions.current === id;
1099
+ const sessionMenuOpen = menu?.type === "session" && menu.id === id;
1100
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1101
+ className: `dcu-wb-session${selected ? " dcu-wb-selected" : ""}${sessionMenuOpen ? " dcu-wb-menu-open" : ""}${sessionDropId === id ? " dcu-wb-drop" : ""}`,
1102
+ role: "treeitem",
1103
+ "aria-selected": selected,
1104
+ draggable: true,
1105
+ onDragStart: (event) => {
1106
+ event.stopPropagation();
1107
+ event.dataTransfer.effectAllowed = "move";
1108
+ setSessionDrag({
1109
+ sessionId: id,
1110
+ workspaceId: workspace.workspaceId
1111
+ });
1112
+ },
1113
+ onDragEnd: () => {
1114
+ setSessionDrag(void 0);
1115
+ setSessionDropId(void 0);
1116
+ },
1117
+ onDragOver: (event) => {
1118
+ if (sessionDrag?.workspaceId !== workspace.workspaceId) return;
1119
+ event.preventDefault();
1120
+ setSessionDropId(id);
1121
+ },
1122
+ onDrop: (event) => {
1123
+ event.preventDefault();
1124
+ const drag = sessionDrag;
1125
+ setSessionDrag(void 0);
1126
+ setSessionDropId(void 0);
1127
+ if (drag !== void 0 && drag.sessionId !== id && moveBefore(workspace.visibleIds, drag.sessionId, id).join() !== workspace.visibleIds.join()) run("session-order", () => insertSessionBefore(workspace.workspaceId, drag.sessionId, id));
1128
+ },
1129
+ onClick: () => {
1130
+ setUnreadSessionIds((ids) => ids.filter((item) => item !== id));
1131
+ openSession(id);
1132
+ },
1133
+ onContextMenu: (event) => {
1134
+ event.preventDefault();
1135
+ event.stopPropagation();
1136
+ setMenu({
1137
+ id,
1138
+ type: "session"
1139
+ });
1140
+ },
1141
+ children: [
1142
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1143
+ className: "dcu-wb-session-title",
1144
+ children: session.displayTitle
1145
+ }),
1146
+ pinnedSessionIds.includes(id) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1147
+ className: "dcu-wb-pin",
1148
+ "aria-label": t("sessions.pinned"),
1149
+ title: t("sessions.pinned"),
1150
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1151
+ viewBox: "0 0 16 16",
1152
+ "aria-hidden": "true",
1153
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5 2.5h6M5 2.5v2L3.75 6v1h8.5V6L11 4.5v-2M8 7v6" })
1154
+ })
1155
+ }),
1156
+ unreadSessionIds.includes(id) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1157
+ className: "dcu-wb-unread",
1158
+ "aria-label": t("sessions.unread")
1159
+ }) : session.running ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1160
+ className: "dcu-wb-running",
1161
+ "aria-hidden": "true"
1162
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1163
+ className: "dcu-wb-time",
1164
+ children: relativeTime(session.updatedAt)
1165
+ }),
1166
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1167
+ className: "dcu-wb-quick-actions",
1168
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1169
+ type: "button",
1170
+ className: "dcu-wb-more dcu-wb-quick-pin",
1171
+ "aria-label": t(pinnedSessionIds.includes(id) ? "sessions.unpin" : "sessions.pin"),
1172
+ title: t(pinnedSessionIds.includes(id) ? "sessions.unpin" : "sessions.pin"),
1173
+ onClick: (event) => {
1174
+ event.stopPropagation();
1175
+ setPinnedSessionIds((ids) => toggleSessionId(ids, id));
1176
+ },
1177
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1178
+ viewBox: "0 0 16 16",
1179
+ "aria-hidden": "true",
1180
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5 2.5h6M5 2.5v2L3.75 6v1h8.5V6L11 4.5v-2M8 7v6" })
1181
+ })
1182
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1183
+ type: "button",
1184
+ className: "dcu-wb-more",
1185
+ "aria-label": t("sessions.archive"),
1186
+ title: t("sessions.archive"),
1187
+ onClick: (event) => {
1188
+ event.stopPropagation();
1189
+ run("archive", () => archiveSession(id));
1190
+ },
1191
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })
1192
+ })]
1193
+ }),
1194
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1195
+ className: "dcu-wb-actions",
1196
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1197
+ open: sessionMenuOpen,
1198
+ onClose: () => {
1199
+ setMenu(void 0);
1200
+ },
1201
+ items: sessionMenu(id, session.displayTitle, path),
1202
+ onSelect: (action) => {
1203
+ if (busy !== void 0) return;
1204
+ const link = sessionDeepLink(browserBase(), id);
1205
+ if (action === "rename") beginRename("session", id, session.displayTitle);
1206
+ if (action === "pin") {
1207
+ setPinnedSessionIds((ids) => toggleSessionId(ids, id));
1208
+ setMenu(void 0);
1209
+ }
1210
+ if (action === "unread") {
1211
+ setUnreadSessionIds((ids) => toggleSessionId(ids, id));
1212
+ setMenu(void 0);
1213
+ }
1214
+ if (action === "archive") run("archive", () => archiveSession(id));
1215
+ if (action === "delete") {
1216
+ setDeleteTarget({
1217
+ id,
1218
+ kind: "session",
1219
+ title: session.displayTitle
1220
+ });
1221
+ setError(void 0);
1222
+ setMenu(void 0);
1223
+ }
1224
+ if (action === "fork") run("fork", () => forkSession(id));
1225
+ if (action === "openPath" && path !== void 0) run("open-path", () => openPath(path));
1226
+ if (action === "copyPath") copy(path);
1227
+ if (action === "copyTitle") copy(session.displayTitle);
1228
+ if (action === "copyId") copy(id);
1229
+ if (action === "copyLink") copy(link);
1230
+ },
1231
+ portal: true,
1232
+ dense: true,
1233
+ compact: true,
1234
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1235
+ type: "button",
1236
+ className: "dcu-wb-more dcu-wb-context-anchor",
1237
+ "aria-label": t("sessions.actions", { name: session.displayTitle }),
1238
+ onClick: (event) => {
1239
+ event.stopPropagation();
1240
+ setMenu((current) => current?.id === id && current.type === "session" ? void 0 : {
1241
+ id,
1242
+ type: "session"
1243
+ });
1244
+ },
1245
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, { size: 16 })
1246
+ })
1247
+ })
1248
+ })
1249
+ ]
1250
+ }, id);
1251
+ })]
1252
+ }, workspace.workspaceId);
1253
+ };
1254
+ if (!wide) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1255
+ className: "dcu-wb dcu-wb-rail",
1256
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$2 })
1257
+ });
1258
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1259
+ className: "dcu-wb",
1260
+ "aria-label": t("workspace.label"),
1261
+ children: [
1262
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("style", { children: [
1263
+ stylesheet$2,
1264
+ runningStyles,
1265
+ typographyStyles
1266
+ ] }),
1267
+ error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1268
+ className: "dcu-wb-error",
1269
+ role: "alert",
1270
+ children: t("sessions.failed", { message: error })
1271
+ }),
1272
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1273
+ className: "dcu-wb-tree",
1274
+ role: "tree",
1275
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1276
+ className: "dcu-wb-section",
1277
+ "aria-label": t("workspace.pinned"),
1278
+ onDragOver: (event) => {
1279
+ if (workspaceDragId !== void 0) event.preventDefault();
1280
+ },
1281
+ onDrop: (event) => {
1282
+ event.preventDefault();
1283
+ pendingPinRef.current = workspaceDragId;
1284
+ setWorkspaceDropId(void 0);
1285
+ if (event.currentTarget instanceof HTMLElement) event.currentTarget.blur();
1286
+ },
1287
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1288
+ className: "dcu-wb-section-label",
1289
+ children: t("workspace.pinned")
1290
+ }), pinnedGroups.length > 0 ? pinnedGroups.map(renderGroup) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1291
+ className: "dcu-wb-empty",
1292
+ children: t("workspace.pinnedEmpty")
1293
+ })]
1294
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1295
+ className: "dcu-wb-section",
1296
+ "aria-label": t("workspace.projects"),
1297
+ children: [
1298
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1299
+ className: "dcu-wb-section-label",
1300
+ children: t("workspace.projects")
1301
+ }),
1302
+ regularGroups.map(renderGroup),
1303
+ regularGroups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1304
+ className: "dcu-wb-empty",
1305
+ children: t("workspace.empty")
1306
+ })
1307
+ ]
1308
+ })]
1309
+ }),
1310
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1311
+ open: renameTarget !== void 0,
1312
+ onClose: () => {
1313
+ setRenameTarget(void 0);
1314
+ setError(void 0);
1315
+ },
1316
+ closeLabel: t("sessions.close"),
1317
+ title: renameTarget?.kind === "workspace" ? t("workspace.rename") : t("sessions.rename"),
1318
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1319
+ className: "dcu-wb-rename-actions",
1320
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1321
+ variant: "outline",
1322
+ onClick: () => {
1323
+ setRenameTarget(void 0);
1324
+ },
1325
+ children: t("sessions.cancel")
1326
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1327
+ variant: "primary",
1328
+ disabled: busy !== void 0 || renameDraft.trim() === "",
1329
+ onClick: submitRename,
1330
+ children: t("sessions.save")
1331
+ })]
1332
+ }),
1333
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1334
+ className: "dcu-wb-rename-input",
1335
+ value: renameDraft,
1336
+ autoFocus: true,
1337
+ onFocus: (event) => {
1338
+ event.target.select();
1339
+ },
1340
+ onChange: (event) => {
1341
+ setRenameDraft(event.target.value);
1342
+ setError(void 0);
1343
+ },
1344
+ onKeyDown: (event) => {
1345
+ if (event.key === "Enter") submitRename();
1346
+ }
1347
+ }), error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1348
+ className: "dcu-wb-error",
1349
+ role: "alert",
1350
+ children: t("sessions.failed", { message: error })
1351
+ })]
1352
+ }),
1353
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1354
+ open: deleteTarget !== void 0,
1355
+ onClose: () => {
1356
+ if (busy !== "delete-workspace" && busy !== "delete-session") {
1357
+ setDeleteTarget(void 0);
1358
+ setError(void 0);
1359
+ }
1360
+ },
1361
+ closeLabel: t("sessions.close"),
1362
+ title: deleteTarget?.kind === "session" ? t("sessions.delete") : t("workspace.delete"),
1363
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1364
+ className: "dcu-wb-rename-actions",
1365
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1366
+ variant: "outline",
1367
+ disabled: busy === "delete-workspace" || busy === "delete-session",
1368
+ onClick: () => {
1369
+ setDeleteTarget(void 0);
1370
+ setError(void 0);
1371
+ },
1372
+ children: t("sessions.cancel")
1373
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1374
+ variant: "outline",
1375
+ className: "dcu-wb-delete-button",
1376
+ disabled: busy === "delete-workspace" || busy === "delete-session",
1377
+ onClick: submitDelete,
1378
+ children: deleteTarget?.kind === "session" ? t("sessions.delete") : t("workspace.delete")
1379
+ })]
1380
+ }),
1381
+ children: [
1382
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1383
+ className: "dcu-wb-delete-copy",
1384
+ children: deleteTarget === void 0 ? "" : deleteTarget.kind === "session" ? t("sessions.deleteDescription", { name: deleteTarget.title }) : t("workspace.deleteDescription", { name: deleteTarget.title })
1385
+ }),
1386
+ busy === "delete-workspace" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1387
+ className: "dcu-wb-error",
1388
+ role: "status",
1389
+ children: t("workspace.deletePending")
1390
+ }),
1391
+ busy === "delete-session" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1392
+ className: "dcu-wb-error",
1393
+ role: "status",
1394
+ children: t("sessions.deletePending")
1395
+ }),
1396
+ error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1397
+ className: "dcu-wb-error",
1398
+ role: "alert",
1399
+ children: t("sessions.failed", { message: error })
1400
+ })
1401
+ ]
1402
+ })
1403
+ ]
1404
+ });
1405
+ }
1406
+ //#endregion
1407
+ //#region src/client/ConnectorsSection.tsx
1408
+ const stylesheet$1 = `
1409
+ .dcu-connectors{color:var(--dsw-alias-label-primary)}.dcu-connectors h2{margin:0;font-size:18px}.dcu-connectors p{margin:6px 0 18px;color:var(--dsw-alias-label-secondary);font-size:12px}.dcu-connector-list{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:9px}.dcu-connector{padding:12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dcu-connector:last-child{border-bottom:0}.dcu-connector-head{display:flex;align-items:center;gap:8px;font-weight:650}.dcu-connector-meta{margin:3px 0 8px;color:var(--dsw-alias-label-tertiary);font-size:11px}.dcu-connector-tool{padding:5px 0 0 24px;color:var(--dsw-alias-label-secondary);font-size:12px}.dcu-connector-tool span{display:block;margin-top:1px;color:var(--dsw-alias-label-tertiary);font-size:11px}.dcu-connector-empty{padding:20px 8px;color:var(--dsw-alias-label-secondary);text-align:center}
1410
+ `;
1411
+ function isConnector(value) {
1412
+ if (value === null || typeof value !== "object") return false;
1413
+ const item = value;
1414
+ return typeof item.name === "string" && Array.isArray(item.tools) && item.tools.every((tool) => tool !== null && typeof tool === "object" && typeof tool.name === "string" && typeof tool.description === "string");
1415
+ }
1416
+ /** 设置内的 MCP 连接器目录,只暴露名称、工具数和工具说明。 */
1417
+ function ConnectorsSection({ sessionStore, t }) {
1418
+ const sessionId = (0, react.useSyncExternalStore)(sessionStore.subscribe, () => sessionStore.getSnapshot().current);
1419
+ const [connectors, setConnectors] = (0, react.useState)([]);
1420
+ const [state, setState] = (0, react.useState)("loading");
1421
+ (0, react.useEffect)(() => {
1422
+ if (sessionId === void 0) {
1423
+ setConnectors([]);
1424
+ setState("ready");
1425
+ return;
1426
+ }
1427
+ const controller = new AbortController();
1428
+ setState("loading");
1429
+ fetch(`/api/michengai/codex-ui/connectors?sessionId=${encodeURIComponent(sessionId)}`, { signal: controller.signal }).then(async (response) => {
1430
+ if (!response.ok) throw new Error("连接器目录暂不可用。");
1431
+ const payload = await response.json();
1432
+ if (!Array.isArray(payload.connectors) || !payload.connectors.every(isConnector)) throw new Error("连接器目录返回格式无效。");
1433
+ if (!controller.signal.aborted) setConnectors(payload.connectors);
1434
+ }).then(() => {
1435
+ if (!controller.signal.aborted) setState("ready");
1436
+ }).catch(() => {
1437
+ if (!controller.signal.aborted) setState("failed");
1438
+ });
1439
+ return () => {
1440
+ controller.abort();
1441
+ };
1442
+ }, [sessionId]);
1443
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1444
+ className: "dcu-connectors",
1445
+ "aria-label": t("connectors.title"),
1446
+ children: [
1447
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$1 }),
1448
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: t("connectors.title") }),
1449
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("connectors.description") }),
1450
+ sessionId === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1451
+ className: "dcu-connector-empty",
1452
+ children: t("connectors.openSession")
1453
+ }) : state === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1454
+ className: "dcu-connector-empty",
1455
+ children: t("connectors.loading")
1456
+ }) : state === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1457
+ className: "dcu-connector-empty",
1458
+ children: t("connectors.failed")
1459
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1460
+ className: "dcu-connector-list",
1461
+ children: [connectors.map((connector) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
1462
+ className: "dcu-connector",
1463
+ children: [
1464
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1465
+ className: "dcu-connector-head",
1466
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLinkOutline16, { size: 16 }), connector.name]
1467
+ }),
1468
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1469
+ className: "dcu-connector-meta",
1470
+ children: t("connectors.toolCount", { count: connector.tools.length })
1471
+ }),
1472
+ connector.tools.map((tool) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1473
+ className: "dcu-connector-tool",
1474
+ children: [tool.name, tool.description !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tool.description })]
1475
+ }, tool.name))
1476
+ ]
1477
+ }, connector.name)), connectors.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1478
+ className: "dcu-connector-empty",
1479
+ children: t("connectors.empty")
1480
+ })]
1481
+ })
1482
+ ]
1483
+ });
1484
+ }
1485
+ //#endregion
1486
+ //#region src/client/locales.ts
1487
+ /** 本插件独占的界面文案命名空间。 */
1488
+ const NS = "michengai.codexUi";
1489
+ const zh = {
1490
+ "sidebar.label": "DSH 导航",
1491
+ "sidebar.expand": "展开侧边栏",
1492
+ "sidebar.collapse": "收缩侧边栏",
1493
+ "sidebar.newTask": "新建任务",
1494
+ "sidebar.search": "搜索会话",
1495
+ "sidebar.mainMenu": "主菜单",
1496
+ "sidebar.extensions": "扩展管理",
1497
+ "sidebar.experts": "专家",
1498
+ "sidebar.skills": "技能",
1499
+ "sidebar.plugins": "插件",
1500
+ "sidebar.connectors": "连接器",
1501
+ "sidebar.schedule": "定时任务",
1502
+ "sidebar.scheduleUnavailable": "定时任务暂未开放",
1503
+ "sidebar.assistant": "个人助理",
1504
+ "sidebar.assistantUnavailable": "个人助理暂未开放",
1505
+ "sidebar.agentPresets": "Agent 预设",
1506
+ "search.placeholder": "搜索会话、设置和操作",
1507
+ "search.empty": "没有匹配的结果。",
1508
+ "search.sessions": "会话",
1509
+ "search.settings": "设置",
1510
+ "search.actions": "快捷操作",
1511
+ "pinned.label": "置顶项目",
1512
+ "pinned.title": "置顶",
1513
+ "pinned.manage": "管理",
1514
+ "pinned.empty": "暂无置顶项目",
1515
+ "pinned.dialog": "管理置顶项目",
1516
+ "workspace.label": "工作区会话",
1517
+ "workspace.search": "搜索会话",
1518
+ "workspace.pinned": "置顶",
1519
+ "workspace.pinnedEmpty": "拖动项目到此处置顶",
1520
+ "workspace.projects": "项目",
1521
+ "workspace.empty": "暂无会话",
1522
+ "workspace.newSession": "新建会话",
1523
+ "workspace.rename": "编辑项目",
1524
+ "workspace.pin": "置顶项目",
1525
+ "workspace.unpin": "取消置顶",
1526
+ "workspace.openPath": "在资源管理器中打开",
1527
+ "workspace.delete": "删除项目",
1528
+ "workspace.deleteDescription": "将把“{name}”从项目列表中移除。文件夹和会话记录会保留。",
1529
+ "workspace.deletePending": "正在删除项目…",
1530
+ "workspace.actions": "{name} 的项目操作",
1531
+ "sessions.manager": "会话管理",
1532
+ "sessions.description": "集中管理所有工作区会话,不改变原生会话列表和菜单。",
1533
+ "sessions.search": "搜索会话名称、目录或 ID",
1534
+ "sessions.close": "关闭",
1535
+ "sessions.cancel": "取消",
1536
+ "sessions.save": "保存",
1537
+ "sessions.empty": "没有匹配的会话。",
1538
+ "sessions.failed": "操作失败:{message}",
1539
+ "sessions.unknown": "找不到该会话。",
1540
+ "sessions.actions": "{name} 的会话操作",
1541
+ "sessions.pin": "置顶会话",
1542
+ "sessions.unpin": "取消置顶",
1543
+ "sessions.pinned": "已置顶",
1544
+ "sessions.markUnread": "标记为未读",
1545
+ "sessions.markRead": "标记为已读",
1546
+ "sessions.unread": "未读",
1547
+ "sessions.rename": "重命名会话",
1548
+ "sessions.renameDescription": "设置一个便于识别的简短名称。",
1549
+ "sessions.archive": "归档会话",
1550
+ "sessions.delete": "删除会话",
1551
+ "sessions.deleteDescription": "将永久删除会话“{name}”及其全部记录,此操作不可恢复。",
1552
+ "sessions.deletePending": "正在删除会话…",
1553
+ "sessions.deleteUnavailable": "未安装或无法连接归档管理插件。",
1554
+ "sessions.fork": "在新会话中继续",
1555
+ "sessions.openPath": "在资源管理器中打开",
1556
+ "sessions.copyPath": "复制工作目录",
1557
+ "sessions.copyTitle": "复制会话标题",
1558
+ "sessions.copyId": "复制会话 ID",
1559
+ "sessions.copyLink": "复制会话链接",
1560
+ "sessions.noWorkspace": "未关联工作区",
1561
+ "connectors.title": "连接器",
1562
+ "connectors.description": "显示当前会话可用的 MCP 连接器及其工具,不显示地址、命令或凭证。",
1563
+ "connectors.openSession": "请先打开一个会话以读取连接器。",
1564
+ "connectors.loading": "正在读取连接器…",
1565
+ "connectors.failed": "连接器目录暂不可用,请稍后重试。",
1566
+ "connectors.toolCount": "{count} 个工具",
1567
+ "connectors.empty": "当前会话没有可用的 MCP 连接器。",
1568
+ "about.nav": "关于",
1569
+ "about.title": "Codex UI",
1570
+ "about.description": "为 DSH 提供 Codex 风格的侧栏、工作区会话管理、搜索与会话轮次导航。",
1571
+ "about.features": "基本功能",
1572
+ "about.feature.sidebar": "Codex 风格侧栏、全局会话搜索与设置入口",
1573
+ "about.feature.search": "通过顶部搜索快速定位会话、设置页面和新建任务等快捷操作",
1574
+ "about.feature.workspace": "项目文件夹可展开、折叠、拖拽排序和置顶,并保留原生工作区能力",
1575
+ "about.feature.sessions": "会话支持重命名、置顶、未读标记、归档、删除及右键快捷操作",
1576
+ "about.feature.conversation": "保留原生消息、工具调用、输入、权限和模型选择,仅调整容器视觉",
1577
+ "about.feature.navigator": "当前会话提供轮次缩略导航,可快速跳转至每一次用户提问",
1578
+ "about.dependencies": "配套管理插件",
1579
+ "about.dependenciesDescription": "安装以下插件可获得专家、技能和已归档会话的完整管理体验;点击安装会从 npm 下载并写入当前 Web profile。",
1580
+ "about.loading": "正在读取依赖安装状态…",
1581
+ "about.statusFailed": "暂时无法读取依赖安装状态。",
1582
+ "about.installed": "已安装",
1583
+ "about.missing": "未安装",
1584
+ "about.updateAvailable": "可更新",
1585
+ "about.install": "安装",
1586
+ "about.update": "更新",
1587
+ "about.installing": "安装中",
1588
+ "about.installFailed": "依赖安装失败,请稍后重试。",
1589
+ "about.restartRequired": "依赖安装完成。请重启 DSH Web 服务以加载新插件。",
1590
+ "about.dependency.experts": "专家管理",
1591
+ "about.dependency.skills": "技能管理",
1592
+ "about.dependency.archive": "归档会话管理",
1593
+ "turns.label": "当前会话轮次导航",
1594
+ "turns.untitled": "未命名提问",
1595
+ "turns.jump": "跳转到第 {index} 轮:{summary}"
1596
+ };
1597
+ const en = {
1598
+ "sidebar.label": "DSH navigation",
1599
+ "sidebar.expand": "Expand sidebar",
1600
+ "sidebar.collapse": "Collapse sidebar",
1601
+ "sidebar.newTask": "New task",
1602
+ "sidebar.search": "Search conversations",
1603
+ "sidebar.mainMenu": "Main menu",
1604
+ "sidebar.extensions": "Extensions",
1605
+ "sidebar.experts": "Experts",
1606
+ "sidebar.skills": "Skills",
1607
+ "sidebar.plugins": "Plugins",
1608
+ "sidebar.connectors": "Connectors",
1609
+ "sidebar.schedule": "Scheduled tasks",
1610
+ "sidebar.scheduleUnavailable": "Scheduled tasks are not available yet",
1611
+ "sidebar.assistant": "Personal assistant",
1612
+ "sidebar.assistantUnavailable": "Personal assistant is not available yet",
1613
+ "sidebar.agentPresets": "Agent presets",
1614
+ "search.placeholder": "Search conversations, settings, and actions",
1615
+ "search.empty": "No matching results.",
1616
+ "search.sessions": "Conversations",
1617
+ "search.settings": "Settings",
1618
+ "search.actions": "Quick actions",
1619
+ "pinned.label": "Pinned projects",
1620
+ "pinned.title": "Pinned",
1621
+ "pinned.manage": "Manage",
1622
+ "pinned.empty": "No pinned projects",
1623
+ "pinned.dialog": "Manage pinned projects",
1624
+ "workspace.label": "Workspace conversations",
1625
+ "workspace.search": "Search conversations",
1626
+ "workspace.projects": "Projects",
1627
+ "workspace.empty": "No conversations",
1628
+ "workspace.newSession": "New conversation",
1629
+ "workspace.rename": "Edit project",
1630
+ "workspace.pin": "Pin project",
1631
+ "workspace.unpin": "Unpin",
1632
+ "workspace.pinned": "Pinned",
1633
+ "workspace.pinnedEmpty": "Drag a project here to pin it",
1634
+ "workspace.openPath": "Open in file explorer",
1635
+ "workspace.delete": "Delete project",
1636
+ "workspace.deleteDescription": "This removes “{name}” from the project list. The folder and conversation records remain.",
1637
+ "workspace.deletePending": "Deleting project…",
1638
+ "workspace.actions": "Project actions for {name}",
1639
+ "sessions.manager": "Session manager",
1640
+ "sessions.description": "Manage conversations across all workspaces without replacing the native conversation list or menus.",
1641
+ "sessions.search": "Search titles, directories, or IDs",
1642
+ "sessions.close": "Close",
1643
+ "sessions.cancel": "Cancel",
1644
+ "sessions.save": "Save",
1645
+ "sessions.empty": "No matching conversations.",
1646
+ "sessions.failed": "Action failed: {message}",
1647
+ "sessions.unknown": "Conversation not found.",
1648
+ "sessions.actions": "Conversation actions for {name}",
1649
+ "sessions.pin": "Pin conversation",
1650
+ "sessions.unpin": "Unpin",
1651
+ "sessions.pinned": "Pinned",
1652
+ "sessions.markUnread": "Mark as unread",
1653
+ "sessions.markRead": "Mark as read",
1654
+ "sessions.unread": "Unread",
1655
+ "sessions.rename": "Rename conversation",
1656
+ "sessions.renameDescription": "Use a short name that is easy to recognize.",
1657
+ "sessions.archive": "Archive conversation",
1658
+ "sessions.delete": "Delete conversation",
1659
+ "sessions.deleteDescription": "This permanently deletes “{name}” and all of its records. This cannot be undone.",
1660
+ "sessions.deletePending": "Deleting conversation…",
1661
+ "sessions.deleteUnavailable": "The archive manager plugin is not installed or unavailable.",
1662
+ "sessions.fork": "Continue in new conversation",
1663
+ "sessions.openPath": "Open in file explorer",
1664
+ "sessions.copyPath": "Copy working directory",
1665
+ "sessions.copyTitle": "Copy conversation title",
1666
+ "sessions.copyId": "Copy conversation ID",
1667
+ "sessions.copyLink": "Copy conversation link",
1668
+ "sessions.noWorkspace": "No workspace",
1669
+ "connectors.title": "Connectors",
1670
+ "connectors.description": "Shows MCP connectors and tools available to this session without exposing addresses, commands, or credentials.",
1671
+ "connectors.openSession": "Open a conversation to load connectors.",
1672
+ "connectors.loading": "Loading connectors…",
1673
+ "connectors.failed": "Connectors are unavailable. Try again shortly.",
1674
+ "connectors.toolCount": "{count} tools",
1675
+ "connectors.empty": "This conversation has no available MCP connectors.",
1676
+ "about.nav": "About",
1677
+ "about.title": "Codex UI",
1678
+ "about.description": "Adds a Codex-style sidebar, workspace conversation management, search, and turn navigation to DSH.",
1679
+ "about.features": "Core features",
1680
+ "about.feature.sidebar": "Codex-style sidebar, global conversation search, and settings entry points",
1681
+ "about.feature.search": "Find conversations, settings pages, and quick actions from the top search panel",
1682
+ "about.feature.workspace": "Expand, collapse, reorder, and pin workspace folders while retaining native workspace behavior",
1683
+ "about.feature.sessions": "Rename, pin, mark unread, archive, delete, or use context actions for conversations",
1684
+ "about.feature.conversation": "Keeps native messages, tool calls, composer, permissions, and model selection while refining the container visuals",
1685
+ "about.feature.navigator": "Navigate directly to each user prompt with the current conversation turn navigator",
1686
+ "about.dependencies": "Companion management plugins",
1687
+ "about.dependenciesDescription": "Install these plugins for the full experts, skills, and archived-conversation management experience. Installation downloads from npm and writes to the current Web profile.",
1688
+ "about.loading": "Loading dependency status…",
1689
+ "about.statusFailed": "Dependency status is temporarily unavailable.",
1690
+ "about.installed": "Installed",
1691
+ "about.missing": "Not installed",
1692
+ "about.updateAvailable": "Update available",
1693
+ "about.install": "Install",
1694
+ "about.update": "Update",
1695
+ "about.installing": "Installing",
1696
+ "about.installFailed": "Dependency installation failed. Try again later.",
1697
+ "about.restartRequired": "Installation is complete. Restart DSH Web to load the new plugin.",
1698
+ "about.dependency.experts": "Expert management",
1699
+ "about.dependency.skills": "Skill management",
1700
+ "about.dependency.archive": "Archived conversation management",
1701
+ "turns.label": "Conversation turn navigation",
1702
+ "turns.untitled": "Untitled prompt",
1703
+ "turns.jump": "Jump to turn {index}: {summary}"
1704
+ };
1705
+ //#endregion
1706
+ //#region src/client/conversation-dom.ts
1707
+ /**
1708
+ * DSH 尚未公开会话滚动容器和消息锚点服务;把兼容读取集中在这里,方便
1709
+ * 宿主提供正式 API 后只替换这一处。所有调用均可失败,不改写会话数据。
1710
+ */
1711
+ function conversationScrollRoot() {
1712
+ return document.querySelector("[data-conversation-scroll]");
1713
+ }
1714
+ function conversationAnchor(root, key) {
1715
+ for (const anchor of root.querySelectorAll("[data-chat-anchor-key]")) if (anchor.dataset.chatAnchorKey === key) return anchor;
1716
+ return null;
1717
+ }
1718
+ //#endregion
1719
+ //#region src/client/TurnNavigator.tsx
1720
+ const stylesheet = `
1721
+ .dcu-turn-navigator{position:fixed;z-index:8;top:50%;left:var(--dcu-turn-left,288px);transform:translateY(-50%);max-height:calc(100vh - 120px);overflow-y:auto;padding:0;scrollbar-width:none}.dcu-turn-navigator::-webkit-scrollbar{display:none}.dcu-turn-list{display:grid;gap:2px;margin:0;padding:0;list-style:none}.dcu-turn-link{display:flex;align-items:center;width:16px;height:8px;overflow:hidden;padding:0;border:0;border-radius:4px;background:transparent;color:var(--dsw-alias-label-tertiary);font:12px/16px var(--dsw-font-family);text-align:left;cursor:pointer;transition:width 180ms ease,height 180ms ease,padding 180ms ease,background-color 180ms ease,color 180ms ease}.dcu-turn-link::before{width:7px;height:1px;flex:0 0 7px;border-radius:1px;background:currentcolor;content:''}.dcu-turn-summary{min-width:0;margin-left:8px;overflow:hidden;opacity:0;text-overflow:ellipsis;white-space:nowrap;transition:opacity 140ms ease}.dcu-turn-link:hover,.dcu-turn-link:focus-visible{width:min(280px,calc(100vw - 64px));height:32px;padding:0 12px;background:var(--dsw-alias-button-floating-hover);color:var(--dsw-alias-label-primary);outline:0}.dcu-turn-link:hover .dcu-turn-summary,.dcu-turn-link:focus-visible .dcu-turn-summary{opacity:1}.dcu-turn-link[aria-current=true]{color:var(--dsw-alias-label-primary)}.dcu-turn-link[aria-current=true]::before{width:12px;flex-basis:12px;height:2px}.dcu-turn-link:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-button-info-fill)}@media (prefers-reduced-motion:reduce){.dcu-turn-link,.dcu-turn-summary{transition:none}}@media (max-width:760px){.dcu-turn-link:hover,.dcu-turn-link:focus-visible{width:min(220px,calc(100vw - 40px))}}
1722
+ `;
1723
+ function textSummary(content, fallback) {
1724
+ const text = content.flatMap((block) => {
1725
+ if (typeof block !== "object" || block === null) return [];
1726
+ const value = block;
1727
+ return value.type === "text" && typeof value.text === "string" ? [value.text] : [];
1728
+ }).join("").replace(/\s+/g, " ").trim();
1729
+ return text === "" ? fallback : text.slice(0, 72);
1730
+ }
1731
+ function userContent(data) {
1732
+ if (typeof data !== "object" || data === null) return [];
1733
+ const content = data.content;
1734
+ return Array.isArray(content) ? content : [];
1735
+ }
1736
+ function turnLinks(snapshot, fallback) {
1737
+ const links = [];
1738
+ for (const key of snapshot.chat.order) {
1739
+ const node = snapshot.chat.nodes.get(key);
1740
+ if (node?.kind !== "user") continue;
1741
+ links.push({
1742
+ key: node.key,
1743
+ summary: textSummary(userContent(node.data), fallback)
1744
+ });
1745
+ }
1746
+ return links;
1747
+ }
1748
+ function equalTurns(left, right) {
1749
+ return left.length === right.length && left.every((turn, index) => turn.key === right[index]?.key && turn.summary === right[index]?.summary);
1750
+ }
1751
+ /** 当前会话的轮次导航;只读取原生聊天锚点并滚动,不改写会话数据或消息视图。 */
1752
+ function TurnNavigator({ useSession, t }) {
1753
+ const turns = useSession((snapshot) => turnLinks(snapshot, t("turns.untitled")), equalTurns);
1754
+ const [current, setCurrent] = (0, react.useState)(turns[0]?.key ?? null);
1755
+ const [sidebarRight, setSidebarRight] = (0, react.useState)(288);
1756
+ const turnKeys = (0, react.useMemo)(() => turns.map((turn) => turn.key).join("|"), [turns]);
1757
+ (0, react.useEffect)(() => {
1758
+ const sidebar = document.querySelector(".dcu-root");
1759
+ if (sidebar === null) return;
1760
+ const update = () => {
1761
+ const next = Math.round(sidebar.getBoundingClientRect().right) + 16;
1762
+ setSidebarRight((previous) => previous === next ? previous : next);
1763
+ };
1764
+ const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(update);
1765
+ observer?.observe(sidebar);
1766
+ window.addEventListener("resize", update);
1767
+ update();
1768
+ return () => {
1769
+ observer?.disconnect();
1770
+ window.removeEventListener("resize", update);
1771
+ };
1772
+ }, []);
1773
+ (0, react.useEffect)(() => {
1774
+ const host = conversationScrollRoot();
1775
+ if (host === null || turns.length === 0) return;
1776
+ let frame = null;
1777
+ const update = () => {
1778
+ frame = null;
1779
+ const threshold = host.getBoundingClientRect().top + Math.min(180, host.clientHeight * .35);
1780
+ let next = turns[0]?.key ?? null;
1781
+ for (const turn of turns) {
1782
+ const anchor = conversationAnchor(host, turn.key);
1783
+ if (anchor !== null && anchor.getBoundingClientRect().top <= threshold) next = turn.key;
1784
+ }
1785
+ setCurrent((previous) => previous === next ? previous : next);
1786
+ };
1787
+ const schedule = () => {
1788
+ if (frame === null) frame = window.requestAnimationFrame(update);
1789
+ };
1790
+ host.addEventListener("scroll", schedule, { passive: true });
1791
+ window.addEventListener("resize", schedule);
1792
+ schedule();
1793
+ return () => {
1794
+ host.removeEventListener("scroll", schedule);
1795
+ window.removeEventListener("resize", schedule);
1796
+ if (frame !== null) window.cancelAnimationFrame(frame);
1797
+ };
1798
+ }, [turnKeys, turns]);
1799
+ if (turns.length === 0) return null;
1800
+ const style = { "--dcu-turn-left": `${sidebarRight}px` };
1801
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
1802
+ className: "dcu-turn-navigator",
1803
+ "aria-label": t("turns.label"),
1804
+ style,
1805
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
1806
+ className: "dcu-turn-list",
1807
+ children: turns.map((turn, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1808
+ type: "button",
1809
+ className: "dcu-turn-link",
1810
+ "aria-current": current === turn.key || void 0,
1811
+ "aria-label": t("turns.jump", {
1812
+ index: index + 1,
1813
+ summary: turn.summary
1814
+ }),
1815
+ title: turn.summary,
1816
+ onClick: () => {
1817
+ const host = conversationScrollRoot();
1818
+ const anchor = host === null ? null : conversationAnchor(host, turn.key);
1819
+ if (host === null || anchor === null) return;
1820
+ const reduceMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
1821
+ host.scrollTo({
1822
+ top: host.scrollTop + anchor.getBoundingClientRect().top - host.getBoundingClientRect().top - 12,
1823
+ behavior: reduceMotion ? "auto" : "smooth"
1824
+ });
1825
+ },
1826
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1827
+ className: "dcu-turn-summary",
1828
+ children: turn.summary
1829
+ })
1830
+ }) }, turn.key))
1831
+ })]
1832
+ });
1833
+ }
1834
+ //#endregion
1835
+ //#region src/client/index.ts
1836
+ const inject = [
1837
+ "slots",
1838
+ "sessions",
1839
+ "workspaces",
1840
+ "layout",
1841
+ "locale"
1842
+ ];
1843
+ function hasDeleteSession(value) {
1844
+ return value !== null && typeof value === "object" && "deleteSession" in value && typeof value.deleteSession === "function";
1845
+ }
1846
+ /** 替换 DSH 的官方 sidebar 插槽,不修改 DSH 源码或会话数据。 */
1847
+ function apply(ctx) {
1848
+ ctx.effect(() => ctx.locale.register(NS, {
1849
+ zh,
1850
+ en
1851
+ }), "michengai-codex-ui: dictionaries");
1852
+ const t = ctx.locale.bind(NS);
1853
+ ctx.slots.inject("sidebar", () => ctx.slots.register({
1854
+ name: "sidebar",
1855
+ locale: NS,
1856
+ children: {
1857
+ "sidebar.workspaces": {
1858
+ kind: "single",
1859
+ scope: "root"
1860
+ },
1861
+ "sidebar.settings": {
1862
+ kind: "single",
1863
+ scope: "root"
1864
+ },
1865
+ "sidebar.footer.action": {
1866
+ kind: "list",
1867
+ scope: "root"
1868
+ }
1869
+ },
1870
+ inject: () => ({
1871
+ openSession: (sessionId) => {
1872
+ ctx.sessions.open(sessionId);
1873
+ },
1874
+ startSession: (workspaceId) => {
1875
+ ctx.workspaces.startSession(workspaceId);
1876
+ },
1877
+ toggleSidebar: () => {
1878
+ ctx.layout.toggleSidebar();
1879
+ }
1880
+ })
1881
+ }, CodexSidebar));
1882
+ ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
1883
+ name: "conversation.session.header.utilities",
1884
+ id: "turn-navigator",
1885
+ order: 100,
1886
+ locale: NS
1887
+ }, TurnNavigator));
1888
+ const forkSession = async (sessionId) => {
1889
+ const childId = await ctx.sessions.fork({
1890
+ sessionId,
1891
+ increaseTitle: true
1892
+ });
1893
+ ctx.sessions.open(childId);
1894
+ };
1895
+ const renameSession = async (sessionId, title) => {
1896
+ const session = ctx.sessions.binding(sessionId)?.session;
1897
+ if (session === void 0) throw new Error(t("sessions.unknown"));
1898
+ const result = await session.rename(title);
1899
+ if (!result.ok) throw new Error(result.error.message);
1900
+ };
1901
+ const deleteSession = async (sessionId) => {
1902
+ const registry = ctx.get("remote.workspaceRegistry");
1903
+ if (!hasDeleteSession(registry)) throw new Error(t("sessions.deleteUnavailable"));
1904
+ const result = await registry.deleteSession(sessionId);
1905
+ if (!result.ok) throw new Error(result.error?.message ?? t("sessions.deleteUnavailable"));
1906
+ };
1907
+ ctx.slots.inject("sidebar.workspaces", () => ctx.slots.register({
1908
+ name: "sidebar.workspaces",
1909
+ priority: -1,
1910
+ locale: NS,
1911
+ inject: () => ({
1912
+ archiveSession: (sessionId) => ctx.workspaces.archiveSession(sessionId),
1913
+ deleteSession,
1914
+ deleteWorkspace: (workspaceId) => ctx.workspaces.delete(workspaceId),
1915
+ forkSession,
1916
+ openPath: (path) => ctx.workspaces.openPath(path),
1917
+ openSession: (sessionId) => {
1918
+ ctx.sessions.open(sessionId);
1919
+ },
1920
+ renameSession,
1921
+ renameWorkspace: (workspaceId, title) => ctx.workspaces.rename(workspaceId, title),
1922
+ insertWorkspaceBefore: (workspaceId, beforeWorkspaceId) => ctx.workspaces.insertBefore(workspaceId, beforeWorkspaceId),
1923
+ insertSessionBefore: (workspaceId, sessionId, beforeSessionId) => ctx.workspaces.insertSessionBefore(workspaceId, sessionId, beforeSessionId),
1924
+ startSession: (workspaceId) => {
1925
+ ctx.workspaces.startSession(workspaceId);
1926
+ }
1927
+ })
1928
+ }, CodexWorkspaceBrowser));
1929
+ ctx.effect(() => {
1930
+ if (typeof window === "undefined") return () => {};
1931
+ const sessionId = new URL(window.location.href).searchParams.get("session");
1932
+ if (sessionId === null || sessionId === "") return () => {};
1933
+ let opened = false;
1934
+ const openDeepLink = () => {
1935
+ if (opened || ctx.sessions.list.getSnapshot().byId[sessionId] === void 0) return;
1936
+ opened = true;
1937
+ ctx.sessions.open(sessionId);
1938
+ };
1939
+ openDeepLink();
1940
+ return ctx.sessions.list.subscribe(openDeepLink);
1941
+ }, "michengai-codex-ui: session deep link");
1942
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
1943
+ name: "settings.section",
1944
+ id: "connectors",
1945
+ order: 17,
1946
+ label: () => t("sidebar.connectors"),
1947
+ icon: "connector",
1948
+ inject: () => ({
1949
+ sessionStore: ctx.sessions.list,
1950
+ t
1951
+ })
1952
+ }, ConnectorsSection));
1953
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
1954
+ name: "settings.section",
1955
+ id: "about",
1956
+ order: 100,
1957
+ label: () => t("about.nav"),
1958
+ locale: NS
1959
+ }, AboutSection));
1960
+ }
1961
+ //#endregion
1962
+ exports.apply = apply;
1963
+ exports.inject = inject;
1964
+ return module.exports;
1965
+ }
1966
+ });