@huanlin/dsh-plugin-ya-workspace-sidebar 0.1.0

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/lib/client.js ADDED
@@ -0,0 +1,1142 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@huanlin/dsh-plugin-ya-workspace-sidebar",
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/locales.ts
11
+ /** Product copy for the replacement workspace browser. */
12
+ const zh = {
13
+ workspaces: "工作区",
14
+ sessions: "会话",
15
+ recent: "最近会话",
16
+ ungrouped: "未分组",
17
+ newSession: "新会话",
18
+ addWorkspace: "添加工作区",
19
+ addWorkspaceMenu: "添加工作区…",
20
+ search: "搜索会话",
21
+ searchPlaceholder: "搜索名称、关键词…",
22
+ clearSearch: "清除搜索",
23
+ searching: "正在搜索会话历史…",
24
+ searchUnavailable: "内容搜索暂不可用,仅显示名称匹配。",
25
+ noMatches: "无匹配会话",
26
+ noSessions: "暂无会话",
27
+ noWorkspaces: "暂无工作区",
28
+ loading: "正在加载工作区…",
29
+ rename: "重命名",
30
+ renameWorkspace: "重命名工作区",
31
+ renameSession: "重命名会话",
32
+ deleteWorkspace: "删除工作区",
33
+ deleteDescription: "将把“{name}”从工作区列表中移除。文件夹与会话记录会保留。",
34
+ fork: "分叉会话",
35
+ archive: "归档会话",
36
+ cancel: "取消",
37
+ confirm: "确认",
38
+ retry: "重新选择",
39
+ folderError: "无法打开文件夹",
40
+ workspaceName: "工作区名称",
41
+ sessionName: "会话名称",
42
+ count: "{n} 个会话",
43
+ now: "刚刚",
44
+ minutes: "{n}分钟",
45
+ hours: "{n}小时",
46
+ days: "{n}天",
47
+ months: "{n}个月",
48
+ years: "{n}年",
49
+ running: "进行中",
50
+ waiting: "等待交互",
51
+ completed: "已完成",
52
+ collapse: "折叠",
53
+ expand: "展开"
54
+ };
55
+ const en = {
56
+ workspaces: "Workspaces",
57
+ sessions: "Sessions",
58
+ recent: "Recent Sessions",
59
+ ungrouped: "Ungrouped",
60
+ newSession: "New Session",
61
+ addWorkspace: "Add workspace",
62
+ addWorkspaceMenu: "Add workspace…",
63
+ search: "Search sessions",
64
+ searchPlaceholder: "Search name, keywords...",
65
+ clearSearch: "Clear search",
66
+ searching: "Searching session history…",
67
+ searchUnavailable: "Content search is unavailable. Showing name matches.",
68
+ noMatches: "No matching sessions",
69
+ noSessions: "No sessions yet",
70
+ noWorkspaces: "No workspaces yet",
71
+ loading: "Loading workspaces…",
72
+ rename: "Rename",
73
+ renameWorkspace: "Rename workspace",
74
+ renameSession: "Rename session",
75
+ deleteWorkspace: "Delete workspace",
76
+ deleteDescription: "This removes “{name}” from the workspace list. The folder and session logs remain.",
77
+ fork: "Fork session",
78
+ archive: "Archive session",
79
+ cancel: "Cancel",
80
+ confirm: "Confirm",
81
+ retry: "Choose again",
82
+ folderError: "Couldn’t open folder",
83
+ workspaceName: "Workspace name",
84
+ sessionName: "Session name",
85
+ count: "{n} sessions",
86
+ now: "now",
87
+ minutes: "{n}min",
88
+ hours: "{n}h",
89
+ days: "{n}d",
90
+ months: "{n}mo",
91
+ years: "{n}y",
92
+ running: "Running",
93
+ waiting: "Waiting for interaction",
94
+ completed: "Completed",
95
+ collapse: "Collapse",
96
+ expand: "Expand"
97
+ };
98
+ const NS = "ya-workspace-sidebar";
99
+ //#endregion
100
+ //#region src/client/styles.ts
101
+ /** One scoped stylesheet injected for the lifetime of the client activation. */
102
+ const CSS = `
103
+ [data-ya-workspace-sidebar] { flex:1; min-height:0; display:flex; flex-direction:column; box-sizing:border-box; padding-right:var(--dsh-sidebar-inline-padding); color:var(--dsw-alias-label-primary); }
104
+ [data-ya-workspace-sidebar].ya-rail { padding-right:0; }
105
+ .ya-section-header { flex:none; height:36px; display:flex; align-items:center; justify-content:flex-end; gap:4px; padding-left:12px; margin-bottom:4px; box-sizing:border-box; color:var(--dsw-alias-label-tertiary); }
106
+ .ya-section-title { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:14px; }
107
+ .ya-icon-button { flex:none; width:28px; height:28px; border:0; border-radius:50%; padding:0; display:inline-flex; align-items:center; justify-content:center; color:var(--dsw-alias-label-secondary); background:transparent; cursor:pointer; }
108
+ .ya-icon-button:hover { background:var(--dsw-alias-interactive-bg-hover); }
109
+ .ya-search { flex:none; height:38px; margin:0 2px 10px; padding:0 14px; display:flex; align-items:center; gap:8px; box-sizing:border-box; border:1px solid var(--dsw-alias-border-l2); border-radius:24px; background:var(--dsw-static-neutral-bluish-75); color:var(--dsw-alias-label-caption); }
110
+ body[data-ds-dark-theme] .ya-search { background:var(--dsw-static-neutral-bluish-900); }
111
+ .ya-search-input { flex:1; min-width:0; border:0; outline:0; background:transparent; color:var(--dsw-alias-label-primary); font:inherit; font-size:14px; }
112
+ .ya-search-input::placeholder { color:var(--dsw-alias-label-tertiary); }
113
+ .ya-search-icon { flex:none; display:inline-flex; border:0; padding:0; color:inherit; background:transparent; }
114
+ .ya-body { flex:1; min-height:0; display:flex; flex-direction:column; overflow:hidden; margin-right:calc(-1 * var(--dsh-sidebar-inline-padding)); padding-right:var(--dsh-sidebar-inline-padding); }
115
+ .ya-recent { flex:none; padding-bottom:8px; border-bottom:1px solid var(--dsw-alias-border-l2); }
116
+ .ya-recent-collapsed { padding-bottom:0; border-bottom-color:transparent; }
117
+ .ya-recent-list-wrap { display:grid; grid-template-rows:1fr; transition:grid-template-rows 220ms ease-out; }
118
+ .ya-recent-collapsed .ya-recent-list-wrap { grid-template-rows:0fr; }
119
+ .ya-recent-list { display:flex; flex-direction:column; overflow:hidden; min-height:0; }
120
+ .ya-block-label { height:26px; display:flex; align-items:center; gap:2px; padding:0 8px; color:var(--dsw-alias-label-tertiary); font-size:12px; font-weight:600; letter-spacing:.02em; text-transform:uppercase; }
121
+ .ya-block-label-toggle { flex:none; width:20px; height:20px; margin-left:auto; border:0; border-radius:6px; padding:0; display:inline-flex; align-items:center; justify-content:center; background:transparent; color:var(--dsw-alias-label-tertiary); cursor:pointer; transition:transform 180ms ease-out; }
122
+ .ya-block-label-toggle:hover { background:var(--dsw-alias-interactive-bg-hover); color:var(--dsw-alias-label-secondary); }
123
+ .ya-block-label-toggle.ya-collapsed { transform:rotate(-90deg); }
124
+ .ya-breadcrumb { flex:none; height:34px; display:flex; align-items:center; gap:2px; padding:0 6px; color:var(--dsw-alias-label-tertiary); font-size:13px; }
125
+ .ya-crumb { border:0; padding:4px 3px; border-radius:6px; background:transparent; color:inherit; font:inherit; cursor:default; min-width:0; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
126
+ button.ya-crumb:hover { background:var(--dsw-alias-interactive-bg-hover); color:var(--dsw-alias-label-primary); cursor:pointer; }
127
+ .ya-scroll { flex:1; min-height:0; overflow-y:auto; padding-bottom:12px; }
128
+ .ya-row { position:relative; min-height:34px; display:flex; align-items:center; gap:6px; margin:1px 0; padding:0 7px; border-radius:9px; box-sizing:border-box; color:var(--dsw-alias-label-primary); cursor:pointer; user-select:none; }
129
+ .ya-row:hover, .ya-row.ya-menu-open { background:var(--dsw-alias-interactive-bg-hover); }
130
+ .ya-row.ya-selected { background:var(--dsw-alias-interactive-bg-selected); }
131
+ .ya-workspace-row { min-height:40px; }
132
+ .ya-row-main { flex:1; min-width:0; display:flex; flex-direction:column; justify-content:center; }
133
+ .ya-row-line { display:flex; align-items:center; min-width:0; gap:6px; }
134
+ .ya-row-title { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:13px; line-height:18px; }
135
+ .ya-row-meta { flex:none; color:var(--dsw-alias-label-tertiary); font-size:11px; white-space:nowrap; }
136
+ .ya-workspace-path { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--dsw-alias-label-tertiary); font-size:11px; line-height:15px; }
137
+ .ya-row-actions { flex:none; display:flex; align-items:center; gap:2px; opacity:0; pointer-events:none; transition:opacity 120ms ease-out; }
138
+ .ya-row:hover .ya-row-actions, .ya-menu-open .ya-row-actions { opacity:1; pointer-events:auto; }
139
+ .ya-status-slot { flex:none; width:16px; height:16px; display:inline-flex; align-items:center; justify-content:center; color:var(--dsw-alias-label-tertiary); }
140
+ .ya-recent .ya-row { min-height:31px; }
141
+ .ya-search-workspace { color:var(--dsw-alias-label-tertiary); font-size:11px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
142
+ .ya-empty, .ya-status { padding:18px 10px; color:var(--dsw-alias-label-tertiary); text-align:center; font-size:13px; }
143
+ .ya-warning { color:var(--dsw-alias-status-warning); }
144
+ .ya-rename-input { width:100%; height:38px; box-sizing:border-box; border:1px solid var(--dsw-alias-border-l2); border-radius:9px; padding:0 11px; background:transparent; color:var(--dsw-alias-label-primary); outline:none; }
145
+ .ya-error { margin-top:8px; color:var(--dsw-alias-status-error); font-size:12px; }
146
+ .ya-drop-before::before, .ya-drop-after::after { content:''; position:absolute; left:8px; right:8px; height:2px; border-radius:2px; background:var(--dsw-alias-label-link); }
147
+ .ya-drop-before::before { top:-2px; } .ya-drop-after::after { bottom:-2px; }
148
+ .ya-rail .ya-section-header { padding-left:0; margin-bottom:12px; }
149
+ .ya-rail .ya-icon-button, .ya-rail .ya-search { width:36px; height:36px; padding:0; margin:0 0 12px; border-color:transparent; background:transparent; }
150
+ .ya-rail .ya-search { justify-content:center; }
151
+ .ya-rail .ya-search-icon { cursor:pointer; color:var(--dsw-alias-label-primary); }
152
+ .ya-picker-error { color:var(--dsw-alias-status-error); white-space:pre-wrap; }
153
+ @keyframes ya-slide-in-forward { from { opacity:0; transform:translateX(10px); } to { opacity:1; transform:translateX(0); } }
154
+ @keyframes ya-slide-in-backward { from { opacity:0; transform:translateX(-10px); } to { opacity:1; transform:translateX(0); } }
155
+ .ya-level-enter-forward { animation:ya-slide-in-forward 180ms ease-out; }
156
+ .ya-level-enter-backward { animation:ya-slide-in-backward 180ms ease-out; }
157
+ `;
158
+ /** Install the stylesheet and return its disposer. */
159
+ function installStyles() {
160
+ const style = document.createElement("style");
161
+ style.setAttribute("data-ya-workspace-sidebar-style", "");
162
+ style.textContent = CSS;
163
+ document.head.appendChild(style);
164
+ return () => {
165
+ style.remove();
166
+ };
167
+ }
168
+ //#endregion
169
+ //#region src/client/WorkspacePicker.tsx
170
+ const ADD = "::ya-add-workspace";
171
+ /** Render the workspace target menu and directory picking conversation. */
172
+ function WorkspacePickFlow({ t, open, anchorRef, useWorkspaces, createWorkspace, useDirectoryFlow, renderDirectoryFlow, onPick, onClose, addOnly = false, side = "bottom", selectedId }) {
173
+ const snapshot = useWorkspaces((state) => state);
174
+ const flowAvailable = useDirectoryFlow((value) => value);
175
+ const [flowOpen, setFlowOpen] = (0, react.useState)(false);
176
+ const [busy, setBusy] = (0, react.useState)(false);
177
+ const [error, setError] = (0, react.useState)(null);
178
+ const getAnchorRect = (0, react.useCallback)(() => anchorRef?.current?.getBoundingClientRect() ?? null, [anchorRef]);
179
+ (0, react.useEffect)(() => {
180
+ if (flowOpen && !flowAvailable) setFlowOpen(false);
181
+ }, [flowAvailable, flowOpen]);
182
+ const openFlow = (0, react.useCallback)(() => {
183
+ onClose();
184
+ setError(null);
185
+ setFlowOpen(true);
186
+ }, [onClose]);
187
+ const addEntries = flowAvailable ? [{
188
+ id: ADD,
189
+ label: t("addWorkspaceMenu"),
190
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 16 }),
191
+ disabled: flowOpen || busy
192
+ }] : [];
193
+ const pinnedAdd = !addOnly && snapshot.items.length > 0;
194
+ const items = pinnedAdd ? snapshot.items.map((workspace) => ({
195
+ id: workspace.workspaceId,
196
+ label: workspace.title,
197
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 }),
198
+ disabled: flowOpen || busy
199
+ })) : addEntries;
200
+ const settled = addOnly || snapshot.phase === "ready";
201
+ const onlyAdd = !pinnedAdd && settled && addEntries.length === 1;
202
+ (0, react.useEffect)(() => {
203
+ if (open && onlyAdd && !flowOpen && !busy) openFlow();
204
+ }, [
205
+ busy,
206
+ flowOpen,
207
+ onlyAdd,
208
+ open,
209
+ openFlow
210
+ ]);
211
+ const owner = {
212
+ open: flowOpen,
213
+ busy,
214
+ onPicked: (path) => {
215
+ setBusy(true);
216
+ createWorkspace({ path }).then((workspace) => {
217
+ setFlowOpen(false);
218
+ onPick(workspace.workspaceId);
219
+ }).catch((reason) => {
220
+ setFlowOpen(false);
221
+ setError(reason instanceof Error ? reason.message : String(reason));
222
+ }).finally(() => {
223
+ setBusy(false);
224
+ });
225
+ },
226
+ onCancel: () => {
227
+ setFlowOpen(false);
228
+ },
229
+ onError: (message) => {
230
+ setFlowOpen(false);
231
+ setError(message);
232
+ }
233
+ };
234
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
236
+ open: open && !onlyAdd && items.length > 0,
237
+ anchor: null,
238
+ items,
239
+ ...pinnedAdd ? { footer: addEntries } : {},
240
+ selectedId,
241
+ onSelect: (id) => {
242
+ if (id === ADD) openFlow();
243
+ else onPick(id);
244
+ },
245
+ onClose,
246
+ side,
247
+ portal: true,
248
+ getAnchorRect
249
+ }),
250
+ open && !onlyAdd && snapshot.phase === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
251
+ className: "ya-status",
252
+ children: t("loading")
253
+ }),
254
+ renderDirectoryFlow(owner),
255
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
256
+ open: error !== null,
257
+ onClose: () => {
258
+ setError(null);
259
+ },
260
+ closeLabel: t("cancel"),
261
+ title: t("folderError"),
262
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
263
+ variant: "outline",
264
+ onClick: () => {
265
+ setError(null);
266
+ },
267
+ children: t("cancel")
268
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
269
+ variant: "primary",
270
+ disabled: !flowAvailable,
271
+ onClick: openFlow,
272
+ children: t("retry")
273
+ })] }),
274
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
275
+ className: "ya-picker-error",
276
+ role: "alert",
277
+ children: error
278
+ })
279
+ })
280
+ ] });
281
+ }
282
+ /** Fill the conversation hero's workspace picker seat. */
283
+ function WorkspacePicker({ open, anchorRef, useWorkspaces, selectedId, onPick, onClose, createWorkspace, useDirectoryFlow, renderSlot, t }) {
284
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspacePickFlow, {
285
+ t,
286
+ open,
287
+ anchorRef,
288
+ useWorkspaces,
289
+ selectedId,
290
+ onPick,
291
+ onClose,
292
+ createWorkspace,
293
+ useDirectoryFlow,
294
+ renderDirectoryFlow: (owner) => renderSlot("conversation.hero.workspace.directoryFlow", owner)
295
+ });
296
+ }
297
+ //#endregion
298
+ //#region src/client/model.ts
299
+ /** Navigation key for sessions not accounted to a real workspace. */
300
+ const UNGROUPED = "__ya_ungrouped__";
301
+ function visible(summary, current, archived) {
302
+ return summary.origin !== "subagent" && !archived.has(summary.id) && (!summary.blank || summary.id === current);
303
+ }
304
+ function rowOf(summary, workspaceKey, workspaceTitle) {
305
+ return {
306
+ id: summary.id,
307
+ title: summary.blank ? "New Session" : summary.displayTitle,
308
+ blank: summary.blank,
309
+ running: summary.running,
310
+ ...summary.pendingInteraction === void 0 ? {} : { pendingInteraction: summary.pendingInteraction },
311
+ completed: summary.completed === true,
312
+ updatedAt: summary.updatedAt,
313
+ workspaceKey,
314
+ workspaceTitle
315
+ };
316
+ }
317
+ function ownerIndex(workspaces) {
318
+ const result = /* @__PURE__ */ new Map();
319
+ for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) result.set(sessionId, workspace);
320
+ return result;
321
+ }
322
+ /** Resolve the first/second-level destination for one session. */
323
+ function workspaceKeyForSession(sessionId, workspaces) {
324
+ if (sessionId === void 0) return null;
325
+ return ownerIndex(workspaces).get(sessionId)?.workspaceId ?? "__ya_ungrouped__";
326
+ }
327
+ /** Derive global recent sessions, newest first. */
328
+ function deriveRecent(list, workspaces, archivedSessionIds, limit = 5) {
329
+ const archived = new Set(archivedSessionIds);
330
+ const owners = ownerIndex(workspaces);
331
+ return list.ids.map((id) => list.byId[id]).filter((summary) => summary !== void 0 && visible(summary, list.current, archived)).sort((a, b) => b.updatedAt - a.updatedAt || String(a.id).localeCompare(String(b.id))).slice(0, limit).map((summary) => {
332
+ const workspace = owners.get(summary.id);
333
+ return rowOf(summary, workspace?.workspaceId ?? "__ya_ungrouped__", workspace?.title ?? "Ungrouped");
334
+ });
335
+ }
336
+ /** Derive first-level real workspaces plus the virtual Ungrouped row. */
337
+ function deriveWorkspaces(list, workspaces, archivedSessionIds) {
338
+ const archived = new Set(archivedSessionIds);
339
+ const accounted = /* @__PURE__ */ new Set();
340
+ const result = workspaces.map((workspace) => {
341
+ let count = 0;
342
+ for (const id of workspace.sessionIds) {
343
+ accounted.add(id);
344
+ const summary = list.byId[id];
345
+ if (summary !== void 0 && visible(summary, list.current, archived)) count++;
346
+ }
347
+ return {
348
+ key: workspace.workspaceId,
349
+ title: workspace.title,
350
+ path: workspace.path,
351
+ createdAt: workspace.createdAt,
352
+ count,
353
+ real: true
354
+ };
355
+ });
356
+ let ungrouped = 0;
357
+ for (const id of list.ids) {
358
+ const summary = list.byId[id];
359
+ if (summary !== void 0 && !accounted.has(id) && visible(summary, list.current, archived)) ungrouped++;
360
+ }
361
+ result.push({
362
+ key: UNGROUPED,
363
+ title: "Ungrouped",
364
+ count: ungrouped,
365
+ real: false
366
+ });
367
+ return result;
368
+ }
369
+ /** Derive the selected workspace's sessions in its canonical order. */
370
+ function deriveWorkspaceSessions(key, list, workspaces, archivedSessionIds) {
371
+ const archived = new Set(archivedSessionIds);
372
+ if (key === "__ya_ungrouped__") {
373
+ const accounted = new Set(workspaces.flatMap((workspace) => workspace.sessionIds));
374
+ return list.ids.map((id) => list.byId[id]).filter((summary) => summary !== void 0 && !accounted.has(summary.id) && visible(summary, list.current, archived)).sort((a, b) => b.updatedAt - a.updatedAt || String(a.id).localeCompare(String(b.id))).map((summary) => rowOf(summary, UNGROUPED, "Ungrouped"));
375
+ }
376
+ const workspace = workspaces.find((item) => item.workspaceId === key);
377
+ if (workspace === void 0) return [];
378
+ return workspace.sessionIds.map((id) => list.byId[id]).filter((summary) => summary !== void 0 && visible(summary, list.current, archived)).map((summary) => rowOf(summary, workspace.workspaceId, workspace.title));
379
+ }
380
+ /** Case-insensitive local title/workspace matching used beside Host content search. */
381
+ function localMatches(rows, query) {
382
+ const normalized = query.trim().toLocaleLowerCase();
383
+ if (normalized === "") return [];
384
+ return rows.filter((row) => `${row.title}\n${row.workspaceTitle}`.toLocaleLowerCase().includes(normalized));
385
+ }
386
+ //#endregion
387
+ //#region src/client/WorkspaceSidebar.tsx
388
+ /** Two-level workspace/session browser with a persistent global recent block. */
389
+ const SEARCH_DEBOUNCE_MS = 250;
390
+ const SEARCH_MAX = 500;
391
+ function sanitized(value) {
392
+ return value.replaceAll("\0", "").slice(0, SEARCH_MAX);
393
+ }
394
+ function relativeTime(updatedAt, now, t) {
395
+ const diff = Math.max(0, now - updatedAt);
396
+ const minute = 6e4;
397
+ if (diff < minute) return t("now");
398
+ if (diff < 60 * minute) return t("minutes", { n: Math.floor(diff / minute) });
399
+ if (diff < 1440 * minute) return t("hours", { n: Math.floor(diff / (60 * minute)) });
400
+ if (diff < 43200 * minute) return t("days", { n: Math.floor(diff / (1440 * minute)) });
401
+ if (diff < 525600 * minute) return t("months", { n: Math.floor(diff / (43200 * minute)) });
402
+ return t("years", { n: Math.floor(diff / (525600 * minute)) });
403
+ }
404
+ function SessionStatus({ row }) {
405
+ if (row.pendingInteraction !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "warning" });
406
+ if (row.running) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "ongoing" });
407
+ if (row.completed) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "done" });
408
+ return null;
409
+ }
410
+ function rowHalf(event) {
411
+ const rect = event.currentTarget.getBoundingClientRect();
412
+ return event.clientY < rect.top + rect.height / 2 ? "before" : "after";
413
+ }
414
+ function SessionItem({ row, current, now, open, rename, fork, archive, t, context, drag }) {
415
+ const [menuOpen, setMenuOpen] = (0, react.useState)(false);
416
+ const title = row.blank ? t("newSession") : row.title;
417
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
418
+ className: `ya-row${row.id === current ? " ya-selected" : ""}${menuOpen ? " ya-menu-open" : ""}${drag?.marker === "before" ? " ya-drop-before" : ""}${drag?.marker === "after" ? " ya-drop-after" : ""}`,
419
+ role: "treeitem",
420
+ "aria-selected": row.id === current,
421
+ draggable: drag !== void 0,
422
+ onClick: () => {
423
+ open(row.id);
424
+ },
425
+ onDragStart: drag === void 0 ? void 0 : (event) => {
426
+ event.dataTransfer.effectAllowed = "move";
427
+ drag.start();
428
+ },
429
+ onDragEnd: drag?.end,
430
+ onDragOver: drag === void 0 ? void 0 : (event) => {
431
+ if (!drag.active) return;
432
+ event.preventDefault();
433
+ event.dataTransfer.dropEffect = "move";
434
+ drag.hover(rowHalf(event));
435
+ },
436
+ onDrop: drag === void 0 ? void 0 : (event) => {
437
+ if (!drag.active) return;
438
+ event.preventDefault();
439
+ drag.drop(rowHalf(event));
440
+ },
441
+ children: [
442
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
443
+ className: "ya-status-slot",
444
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionStatus, { row })
445
+ }),
446
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
447
+ className: "ya-row-main",
448
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
449
+ className: "ya-row-line",
450
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
451
+ className: "ya-row-title",
452
+ children: title
453
+ }), !row.blank && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
454
+ className: "ya-row-meta ya-row-time",
455
+ children: relativeTime(row.updatedAt, now, t)
456
+ })]
457
+ }), context === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
458
+ className: "ya-search-workspace",
459
+ children: row.workspaceTitle
460
+ })]
461
+ }),
462
+ !row.blank && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
463
+ className: "ya-row-actions",
464
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
465
+ open: menuOpen,
466
+ onClose: () => {
467
+ setMenuOpen(false);
468
+ },
469
+ items: [
470
+ {
471
+ id: "rename",
472
+ label: t("rename"),
473
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {})
474
+ },
475
+ {
476
+ id: "fork",
477
+ label: t("fork"),
478
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, {})
479
+ },
480
+ {
481
+ id: "archive",
482
+ label: t("archive"),
483
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })
484
+ }
485
+ ],
486
+ onSelect: (id) => {
487
+ setMenuOpen(false);
488
+ if (id === "rename") rename(row);
489
+ if (id === "fork") fork(row.id);
490
+ if (id === "archive") archive(row.id);
491
+ },
492
+ portal: true,
493
+ closeOnPointerLeave: true,
494
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
495
+ type: "button",
496
+ className: "ya-icon-button",
497
+ "aria-label": `${title} actions`,
498
+ onClick: (event) => {
499
+ event.stopPropagation();
500
+ setMenuOpen((value) => !value);
501
+ },
502
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, {})
503
+ })
504
+ })
505
+ })
506
+ ]
507
+ });
508
+ }
509
+ function WorkspaceItem({ row, enter, create, rename, remove, t }) {
510
+ const [menuOpen, setMenuOpen] = (0, react.useState)(false);
511
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
512
+ className: `ya-row ya-workspace-row${menuOpen ? " ya-menu-open" : ""}`,
513
+ role: "treeitem",
514
+ onClick: enter,
515
+ title: row.path,
516
+ children: [
517
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
518
+ className: "ya-status-slot",
519
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, {})
520
+ }),
521
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
522
+ className: "ya-row-main",
523
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
524
+ className: "ya-row-line",
525
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
526
+ className: "ya-row-title",
527
+ children: row.real ? row.title : t("ungrouped")
528
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
529
+ className: "ya-row-meta",
530
+ children: t("count", { n: row.count })
531
+ })]
532
+ }), row.path !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
533
+ className: "ya-workspace-path",
534
+ children: row.path
535
+ })]
536
+ }),
537
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
538
+ className: "ya-row-actions",
539
+ children: [row.real && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
540
+ open: menuOpen,
541
+ onClose: () => {
542
+ setMenuOpen(false);
543
+ },
544
+ items: [{
545
+ id: "rename",
546
+ label: t("rename"),
547
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {})
548
+ }, {
549
+ id: "delete",
550
+ label: t("deleteWorkspace"),
551
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}),
552
+ danger: true
553
+ }],
554
+ onSelect: (id) => {
555
+ setMenuOpen(false);
556
+ if (id === "rename") rename();
557
+ if (id === "delete") remove();
558
+ },
559
+ portal: true,
560
+ closeOnPointerLeave: true,
561
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
562
+ type: "button",
563
+ className: "ya-icon-button",
564
+ onClick: (event) => {
565
+ event.stopPropagation();
566
+ setMenuOpen((value) => !value);
567
+ },
568
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, {})
569
+ })
570
+ }), row.real && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
571
+ type: "button",
572
+ className: "ya-icon-button",
573
+ onClick: (event) => {
574
+ event.stopPropagation();
575
+ create();
576
+ },
577
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, {})
578
+ })]
579
+ }),
580
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {})
581
+ ]
582
+ });
583
+ }
584
+ /** Fill `sidebar.workspaces` with the replacement browser. */
585
+ function WorkspaceSidebar(props) {
586
+ const { wide, expandSidebar, useSessions, useWorkspaces, startSession, open, searchSessions, searchResultLimit, renameSession, forkSession, renameWorkspace, deleteWorkspace, archiveSession, insertSessionBefore, createWorkspace, useDirectoryFlow, renderSlot, t } = props;
587
+ const sessions = useSessions((state) => state);
588
+ const workspaceState = useWorkspaces((state) => state);
589
+ const workspaces = workspaceState.items;
590
+ const archived = workspaceState.archivedSessionIds;
591
+ const directoryFlowAvailable = useDirectoryFlow((value) => value);
592
+ const allRows = (0, react.useMemo)(() => deriveRecent(sessions, workspaces, archived, Number.MAX_SAFE_INTEGER), [
593
+ archived,
594
+ sessions,
595
+ workspaces
596
+ ]);
597
+ const recent = allRows.slice(0, 5);
598
+ const workspaceRows = (0, react.useMemo)(() => deriveWorkspaces(sessions, workspaces, archived), [
599
+ archived,
600
+ sessions,
601
+ workspaces
602
+ ]);
603
+ const [selectedKey, setSelectedKey] = (0, react.useState)(null);
604
+ const [direction, setDirection] = (0, react.useState)("forward");
605
+ const [hasMounted, setHasMounted] = (0, react.useState)(false);
606
+ (0, react.useEffect)(() => {
607
+ setHasMounted(true);
608
+ }, []);
609
+ const observedCurrent = (0, react.useRef)(void 0);
610
+ const initialized = (0, react.useRef)(false);
611
+ (0, react.useEffect)(() => {
612
+ if (initialized.current && observedCurrent.current === sessions.current) return;
613
+ initialized.current = true;
614
+ observedCurrent.current = sessions.current;
615
+ if (sessions.current !== void 0) {
616
+ setDirection("forward");
617
+ setSelectedKey(workspaceKeyForSession(sessions.current, workspaces));
618
+ }
619
+ }, [sessions.current, workspaces]);
620
+ (0, react.useEffect)(() => {
621
+ if (selectedKey !== null && selectedKey !== "__ya_ungrouped__" && !workspaces.some((workspace) => workspace.workspaceId === selectedKey)) setSelectedKey(UNGROUPED);
622
+ }, [selectedKey, workspaces]);
623
+ const selectedWorkspace = selectedKey === null || selectedKey === "__ya_ungrouped__" ? void 0 : workspaces.find((workspace) => workspace.workspaceId === selectedKey);
624
+ const levelRows = selectedKey === null ? [] : deriveWorkspaceSessions(selectedKey, sessions, workspaces, archived);
625
+ const now = Date.now();
626
+ const [query, setQuery] = (0, react.useState)("");
627
+ const normalizedQuery = sanitized(query).trim();
628
+ const [remote, setRemote] = (0, react.useState)({
629
+ query: "",
630
+ status: "idle",
631
+ items: [],
632
+ hasMore: false
633
+ });
634
+ (0, react.useEffect)(() => {
635
+ if (normalizedQuery === "") {
636
+ setRemote({
637
+ query: "",
638
+ status: "idle",
639
+ items: [],
640
+ hasMore: false
641
+ });
642
+ return;
643
+ }
644
+ const controller = new AbortController();
645
+ setRemote({
646
+ query: normalizedQuery,
647
+ status: "loading",
648
+ items: [],
649
+ hasMore: false
650
+ });
651
+ const timer = window.setTimeout(() => {
652
+ searchSessions(normalizedQuery, controller.signal).then((result) => {
653
+ if (!controller.signal.aborted) setRemote({
654
+ query: normalizedQuery,
655
+ status: "ready",
656
+ items: result.items,
657
+ hasMore: result.hasMore
658
+ });
659
+ }).catch(() => {
660
+ if (!controller.signal.aborted) setRemote({
661
+ query: normalizedQuery,
662
+ status: "error",
663
+ items: [],
664
+ hasMore: false
665
+ });
666
+ });
667
+ }, SEARCH_DEBOUNCE_MS);
668
+ return () => {
669
+ window.clearTimeout(timer);
670
+ controller.abort();
671
+ };
672
+ }, [normalizedQuery, searchSessions]);
673
+ const searchRows = (0, react.useMemo)(() => {
674
+ if (normalizedQuery === "") return [];
675
+ const byId = new Map(localMatches(allRows, normalizedQuery).map((row) => [row.id, row]));
676
+ if (remote.query === normalizedQuery) for (const item of remote.items) {
677
+ const row = allRows.find((candidate) => candidate.id === item.sessionId);
678
+ if (row !== void 0) byId.set(row.id, row);
679
+ }
680
+ return [...byId.values()].slice(0, searchResultLimit);
681
+ }, [
682
+ allRows,
683
+ normalizedQuery,
684
+ remote,
685
+ searchResultLimit
686
+ ]);
687
+ const [pickerOpen, setPickerOpen] = (0, react.useState)(false);
688
+ const pickerAnchor = (0, react.useRef)(null);
689
+ const [recentCollapsed, setRecentCollapsed] = (0, react.useState)(false);
690
+ const [workspaceRename, setWorkspaceRename] = (0, react.useState)(null);
691
+ const [sessionRename, setSessionRename] = (0, react.useState)(null);
692
+ const [renameDraft, setRenameDraft] = (0, react.useState)("");
693
+ const [renameError, setRenameError] = (0, react.useState)(null);
694
+ const [busy, setBusy] = (0, react.useState)(false);
695
+ const [deleteTarget, setDeleteTarget] = (0, react.useState)(null);
696
+ const [drag, setDrag] = (0, react.useState)(null);
697
+ const beginWorkspaceRename = (row) => {
698
+ setWorkspaceRename(row);
699
+ setRenameDraft(row.title);
700
+ setRenameError(null);
701
+ };
702
+ const beginSessionRename = (row) => {
703
+ setSessionRename(row);
704
+ setRenameDraft(row.title);
705
+ setRenameError(null);
706
+ };
707
+ const closeRename = () => {
708
+ if (!busy) {
709
+ setWorkspaceRename(null);
710
+ setSessionRename(null);
711
+ setRenameError(null);
712
+ }
713
+ };
714
+ const commitRename = () => {
715
+ const title = renameDraft.trim();
716
+ if (title === "" || busy) return;
717
+ setBusy(true);
718
+ (workspaceRename !== null && workspaceRename.key !== "__ya_ungrouped__" ? renameWorkspace(workspaceRename.key, title) : sessionRename !== null ? renameSession(sessionRename.id, title) : Promise.resolve()).then(() => {
719
+ setWorkspaceRename(null);
720
+ setSessionRename(null);
721
+ }).catch((reason) => {
722
+ setRenameError(reason instanceof Error ? reason.message : String(reason));
723
+ }).finally(() => {
724
+ setBusy(false);
725
+ });
726
+ };
727
+ const confirmDelete = () => {
728
+ if (deleteTarget === null || deleteTarget.key === "__ya_ungrouped__" || busy) return;
729
+ setBusy(true);
730
+ deleteWorkspace(deleteTarget.key).then(() => {
731
+ setDeleteTarget(null);
732
+ }).catch((reason) => {
733
+ setRenameError(reason instanceof Error ? reason.message : String(reason));
734
+ }).finally(() => {
735
+ setBusy(false);
736
+ });
737
+ };
738
+ const archive = (id) => {
739
+ archiveSession(id).catch((reason) => {
740
+ console.warn("session archive rejected:", reason);
741
+ });
742
+ };
743
+ const fork = (id) => {
744
+ forkSession(id);
745
+ };
746
+ const sessionItem = (row, context = false, index) => {
747
+ const draggable = selectedKey !== null && selectedKey !== "__ya_ungrouped__" && index !== void 0;
748
+ const marker = drag?.over?.id === row.id ? drag.over.half : null;
749
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionItem, {
750
+ row,
751
+ current: sessions.current,
752
+ now,
753
+ open,
754
+ rename: beginSessionRename,
755
+ fork,
756
+ archive,
757
+ t,
758
+ context,
759
+ ...draggable && selectedKey !== null && selectedKey !== "__ya_ungrouped__" ? { drag: {
760
+ active: drag !== null,
761
+ marker,
762
+ start: () => {
763
+ setDrag({
764
+ id: row.id,
765
+ over: null
766
+ });
767
+ },
768
+ hover: (half) => {
769
+ setDrag((value) => value === null ? value : {
770
+ ...value,
771
+ over: {
772
+ id: row.id,
773
+ half
774
+ }
775
+ });
776
+ },
777
+ drop: (half) => {
778
+ if (drag === null) return;
779
+ const anchor = half === "before" ? row.id : levelRows[(index ?? 0) + 1]?.id;
780
+ const sourceIndex = levelRows.findIndex((item) => item.id === drag.id);
781
+ const anchorIndex = anchor === void 0 ? levelRows.length : levelRows.findIndex((item) => item.id === anchor);
782
+ setDrag(null);
783
+ if (anchor === drag.id || sourceIndex === anchorIndex || sourceIndex + 1 === anchorIndex) return;
784
+ insertSessionBefore(selectedKey, drag.id, anchor).catch((reason) => {
785
+ console.warn("session reorder rejected:", reason);
786
+ });
787
+ },
788
+ end: () => {
789
+ setDrag(null);
790
+ }
791
+ } } : {}
792
+ }, row.id);
793
+ };
794
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
795
+ "data-ya-workspace-sidebar": true,
796
+ className: wide ? "" : "ya-rail",
797
+ children: [
798
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
799
+ className: "ya-section-header",
800
+ children: [
801
+ wide && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
802
+ className: "ya-section-title",
803
+ children: t("workspaces")
804
+ }),
805
+ directoryFlowAvailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
806
+ ref: pickerAnchor,
807
+ type: "button",
808
+ className: "ya-icon-button",
809
+ "aria-label": t("addWorkspace"),
810
+ onClick: () => {
811
+ setPickerOpen((value) => !value);
812
+ },
813
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconProjectAddOutline16, { size: wide ? 16 : 18 })
814
+ }),
815
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspacePickFlow, {
816
+ t,
817
+ open: pickerOpen,
818
+ anchorRef: pickerAnchor,
819
+ useWorkspaces,
820
+ createWorkspace,
821
+ useDirectoryFlow,
822
+ renderDirectoryFlow: (owner) => renderSlot("sidebar.workspaces.directoryFlow", owner),
823
+ addOnly: true,
824
+ side: "right",
825
+ onPick: (workspaceId) => {
826
+ setPickerOpen(false);
827
+ startSession(workspaceId);
828
+ },
829
+ onClose: () => {
830
+ setPickerOpen(false);
831
+ }
832
+ })
833
+ ]
834
+ }),
835
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
836
+ className: "ya-search",
837
+ onClick: () => {
838
+ if (!wide) expandSidebar();
839
+ },
840
+ children: [
841
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
842
+ type: "button",
843
+ className: "ya-search-icon",
844
+ "aria-label": t("search"),
845
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: wide ? 14 : 18 })
846
+ }),
847
+ wide && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
848
+ className: "ya-search-input",
849
+ value: query,
850
+ maxLength: SEARCH_MAX,
851
+ placeholder: t("searchPlaceholder"),
852
+ onChange: (event) => {
853
+ setQuery(sanitized(event.target.value));
854
+ }
855
+ }),
856
+ wide && query !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
857
+ type: "button",
858
+ className: "ya-icon-button",
859
+ "aria-label": t("clearSearch"),
860
+ onClick: () => {
861
+ setQuery("");
862
+ },
863
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseFill14, {})
864
+ })
865
+ ]
866
+ }),
867
+ wide && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
868
+ className: "ya-body",
869
+ children: normalizedQuery !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
870
+ className: "ya-scroll",
871
+ role: "tree",
872
+ "aria-label": t("search"),
873
+ children: [
874
+ searchRows.map((row) => sessionItem(row, true)),
875
+ remote.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
876
+ className: "ya-status",
877
+ children: t("searching")
878
+ }),
879
+ remote.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
880
+ className: "ya-status ya-warning",
881
+ children: t("searchUnavailable")
882
+ }),
883
+ remote.status !== "loading" && searchRows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
884
+ className: "ya-empty",
885
+ children: t("noMatches")
886
+ })
887
+ ]
888
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
889
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
890
+ className: `ya-recent${recentCollapsed ? " ya-recent-collapsed" : ""}`,
891
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
892
+ className: "ya-block-label",
893
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("recent") }), recent.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
894
+ type: "button",
895
+ className: `ya-block-label-toggle${recentCollapsed ? " ya-collapsed" : ""}`,
896
+ "aria-label": recentCollapsed ? t("expand") : t("collapse"),
897
+ "aria-expanded": !recentCollapsed,
898
+ onClick: (event) => {
899
+ event.stopPropagation();
900
+ setRecentCollapsed((value) => !value);
901
+ },
902
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {})
903
+ })]
904
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
905
+ className: "ya-recent-list-wrap",
906
+ children: recent.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
907
+ className: "ya-empty",
908
+ children: t("noSessions")
909
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
910
+ className: "ya-recent-list",
911
+ children: recent.map((row) => sessionItem(row, true))
912
+ })
913
+ })]
914
+ }),
915
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
916
+ className: "ya-breadcrumb",
917
+ children: selectedKey === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
918
+ className: "ya-crumb",
919
+ children: t("workspaces")
920
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
921
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
922
+ type: "button",
923
+ className: "ya-crumb",
924
+ onClick: () => {
925
+ setDirection("backward");
926
+ setSelectedKey(null);
927
+ },
928
+ children: t("workspaces")
929
+ }),
930
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {}),
931
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
932
+ className: "ya-crumb",
933
+ children: selectedKey === "__ya_ungrouped__" ? t("ungrouped") : selectedWorkspace?.title
934
+ }),
935
+ selectedKey !== "__ya_ungrouped__" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
936
+ type: "button",
937
+ className: "ya-icon-button",
938
+ "aria-label": t("newSession"),
939
+ onClick: () => {
940
+ startSession(selectedKey);
941
+ },
942
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, {})
943
+ })
944
+ ] })
945
+ }),
946
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
947
+ className: "ya-scroll",
948
+ role: "tree",
949
+ "aria-label": selectedKey === null ? t("workspaces") : t("sessions"),
950
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
951
+ className: hasMounted ? `ya-level-enter-${direction}` : void 0,
952
+ children: [
953
+ selectedKey === null ? workspaceRows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceItem, {
954
+ row,
955
+ enter: () => {
956
+ setDirection("forward");
957
+ setSelectedKey(row.key);
958
+ },
959
+ create: () => {
960
+ if (row.key !== "__ya_ungrouped__") startSession(row.key);
961
+ },
962
+ rename: () => {
963
+ beginWorkspaceRename(row);
964
+ },
965
+ remove: () => {
966
+ setDeleteTarget(row);
967
+ setRenameError(null);
968
+ },
969
+ t
970
+ }, row.key)) : levelRows.map((row, index) => sessionItem(row, false, index)),
971
+ selectedKey === null && workspaceRows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
972
+ className: "ya-empty",
973
+ children: t("noWorkspaces")
974
+ }),
975
+ selectedKey !== null && levelRows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
976
+ className: "ya-empty",
977
+ children: t("noSessions")
978
+ })
979
+ ]
980
+ }, selectedKey ?? "root")
981
+ })
982
+ ] })
983
+ }),
984
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
985
+ open: workspaceRename !== null || sessionRename !== null,
986
+ onClose: closeRename,
987
+ closeLabel: t("cancel"),
988
+ title: workspaceRename !== null ? t("renameWorkspace") : t("renameSession"),
989
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
990
+ variant: "outline",
991
+ disabled: busy,
992
+ onClick: closeRename,
993
+ children: t("cancel")
994
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
995
+ variant: "primary",
996
+ disabled: busy || renameDraft.trim() === "",
997
+ onClick: commitRename,
998
+ children: t("rename")
999
+ })] }),
1000
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1001
+ className: "ya-rename-input",
1002
+ value: renameDraft,
1003
+ autoFocus: true,
1004
+ disabled: busy,
1005
+ "aria-label": workspaceRename !== null ? t("workspaceName") : t("sessionName"),
1006
+ onChange: (event) => {
1007
+ setRenameDraft(event.target.value);
1008
+ setRenameError(null);
1009
+ }
1010
+ }), renameError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1011
+ className: "ya-error",
1012
+ role: "alert",
1013
+ children: renameError
1014
+ })]
1015
+ }),
1016
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1017
+ open: deleteTarget !== null,
1018
+ onClose: () => {
1019
+ if (!busy) setDeleteTarget(null);
1020
+ },
1021
+ closeLabel: t("cancel"),
1022
+ title: t("deleteWorkspace"),
1023
+ description: deleteTarget === null ? void 0 : t("deleteDescription", { name: deleteTarget.title }),
1024
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1025
+ variant: "outline",
1026
+ disabled: busy,
1027
+ onClick: () => {
1028
+ setDeleteTarget(null);
1029
+ },
1030
+ children: t("cancel")
1031
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1032
+ variant: "outline",
1033
+ disabled: busy,
1034
+ onClick: confirmDelete,
1035
+ children: t("deleteWorkspace")
1036
+ })] }),
1037
+ children: renameError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1038
+ className: "ya-error",
1039
+ role: "alert",
1040
+ children: renameError
1041
+ })
1042
+ })
1043
+ ]
1044
+ });
1045
+ }
1046
+ //#endregion
1047
+ //#region src/client/index.ts
1048
+ /** Services required by both replacement client entries. */
1049
+ const inject = [
1050
+ "slots",
1051
+ "sessions",
1052
+ "workspaces",
1053
+ "locale"
1054
+ ];
1055
+ /** Register the sidebar browser and conversation hero picker. */
1056
+ function apply(ctx) {
1057
+ ctx.effect(() => ctx.locale.register(NS, {
1058
+ zh,
1059
+ en
1060
+ }), "ya-workspace-sidebar: dictionaries");
1061
+ ctx.effect(installStyles, "ya-workspace-sidebar: styles");
1062
+ const flowSource = (name) => ({
1063
+ getSnapshot: () => ctx.slots.entries(name).length > 0,
1064
+ subscribe: (listener) => ctx.slots.subscribe(name, listener)
1065
+ });
1066
+ const sidebarFlow = flowSource("sidebar.workspaces.directoryFlow");
1067
+ const pickerFlow = flowSource("conversation.hero.workspace.directoryFlow");
1068
+ const createWorkspace = (input) => ctx.workspaces.create(input);
1069
+ const searchSessions = async (query, signal) => {
1070
+ const result = await ctx.sessions.search(query, signal);
1071
+ if (!result.ok) throw new Error(result.error.message);
1072
+ return result.value;
1073
+ };
1074
+ const sidebarInjected = () => ({
1075
+ startSession: (workspaceId) => {
1076
+ ctx.workspaces.startSession(workspaceId);
1077
+ },
1078
+ open: (sessionId) => {
1079
+ ctx.sessions.open(sessionId);
1080
+ },
1081
+ searchSessions,
1082
+ searchResultLimit: ctx.sessions.searchResultLimit,
1083
+ renameSession: async (sessionId, title) => {
1084
+ const session = ctx.sessions.binding(sessionId)?.session;
1085
+ if (session === void 0) throw new Error(`unknown session "${sessionId}"`);
1086
+ const result = await session.rename(title);
1087
+ if (!result.ok) throw new Error(result.error.message);
1088
+ },
1089
+ forkSession: (sessionId) => {
1090
+ ctx.sessions.fork({
1091
+ sessionId,
1092
+ increaseTitle: true
1093
+ }).then((childId) => {
1094
+ ctx.sessions.open(childId);
1095
+ }).catch(() => {});
1096
+ },
1097
+ renameWorkspace: async (workspaceId, title) => {
1098
+ await ctx.workspaces.rename(workspaceId, title);
1099
+ },
1100
+ deleteWorkspace: async (workspaceId) => {
1101
+ await ctx.workspaces.delete(workspaceId);
1102
+ },
1103
+ archiveSession: async (sessionId) => {
1104
+ await ctx.workspaces.archiveSession(sessionId);
1105
+ },
1106
+ insertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {
1107
+ await ctx.workspaces.insertSessionBefore(workspaceId, sessionId, beforeSessionId);
1108
+ },
1109
+ createWorkspace,
1110
+ hooks: { directoryFlow: sidebarFlow }
1111
+ });
1112
+ const pickerInjected = () => ({
1113
+ createWorkspace,
1114
+ hooks: { directoryFlow: pickerFlow }
1115
+ });
1116
+ ctx.slots.inject("sidebar.workspaces", () => ctx.slots.register({
1117
+ name: "sidebar.workspaces",
1118
+ children: { "sidebar.workspaces.directoryFlow": {
1119
+ kind: "single",
1120
+ scope: "root"
1121
+ } },
1122
+ inject: sidebarInjected,
1123
+ locale: NS
1124
+ }, WorkspaceSidebar));
1125
+ ctx.slots.inject("conversation.hero.workspace", () => ctx.slots.register({
1126
+ name: "conversation.hero.workspace",
1127
+ children: { "conversation.hero.workspace.directoryFlow": {
1128
+ kind: "single",
1129
+ scope: "root"
1130
+ } },
1131
+ inject: pickerInjected,
1132
+ locale: NS
1133
+ }, WorkspacePicker));
1134
+ }
1135
+ //#endregion
1136
+ exports.apply = apply;
1137
+ exports.inject = inject;
1138
+ return module.exports;
1139
+ }
1140
+ });
1141
+
1142
+ //# sourceMappingURL=client.js.map