dsh-session-curator 0.1.2 → 0.3.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/README.md +8 -7
- package/README.zh-CN.md +7 -6
- package/lib/client.js +1356 -373
- package/lib/host/delete.js +77 -0
- package/lib/host/fs-utils.js +44 -17
- package/lib/host/open.js +98 -0
- package/lib/host/paths.js +5 -13
- package/lib/host/routes.js +8 -4
- package/lib/host/session-manager.js +186 -76
- package/lib/index.js +11 -11
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +16 -13
- package/lib/host/trash.js +0 -190
- package/scripts/activation.mjs +0 -358
- package/scripts/build.mjs +0 -96
- package/scripts/client.mjs +0 -691
- package/scripts/smoke.mjs +0 -382
package/lib/client.js
CHANGED
|
@@ -37,21 +37,37 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
37
37
|
// src/client/index.ts
|
|
38
38
|
var index_exports = {};
|
|
39
39
|
__export(index_exports, {
|
|
40
|
+
Dropdown: () => Dropdown,
|
|
40
41
|
GroupHeader: () => GroupHeader,
|
|
41
42
|
NS: () => NS,
|
|
42
43
|
SessionRow: () => SessionRow,
|
|
44
|
+
UsagePanel: () => UsagePanel,
|
|
43
45
|
apply: () => apply,
|
|
46
|
+
cachedSnapshot: () => cachedSnapshot,
|
|
44
47
|
collapseKey: () => collapseKey,
|
|
48
|
+
describeSession: () => describeSession,
|
|
49
|
+
dialogStore: () => dialogStore,
|
|
50
|
+
filterRows: () => filterRows,
|
|
51
|
+
forgetDetails: () => forgetDetails,
|
|
52
|
+
forgetSnapshot: () => forgetSnapshot,
|
|
45
53
|
groupKeyOf: () => groupKeyOf,
|
|
46
54
|
groupRows: () => groupRows,
|
|
47
55
|
inject: () => inject,
|
|
56
|
+
isFilterActive: () => isFilterActive,
|
|
48
57
|
isGroupCollapsed: () => isGroupCollapsed,
|
|
58
|
+
matchesFilter: () => matchesFilter,
|
|
59
|
+
matchesSearch: () => matchesSearch,
|
|
60
|
+
mutationStore: () => mutationStore,
|
|
49
61
|
sectionIdFor: () => sectionIdFor,
|
|
50
|
-
|
|
62
|
+
showNotice: () => showNotice,
|
|
63
|
+
sortRows: () => sortRows,
|
|
64
|
+
toastStore: () => toastStore,
|
|
65
|
+
toggledCollapse: () => toggledCollapse,
|
|
66
|
+
workspaceSummaries: () => workspaceSummaries
|
|
51
67
|
});
|
|
52
68
|
module.exports = __toCommonJS(index_exports);
|
|
53
|
-
var
|
|
54
|
-
var
|
|
69
|
+
var React11 = __toESM(require("react"), 1);
|
|
70
|
+
var import_dsh_client_ui_primitives9 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
55
71
|
|
|
56
72
|
// src/client/components/dialog-host.ts
|
|
57
73
|
var React2 = __toESM(require("react"), 1);
|
|
@@ -168,49 +184,38 @@ function useStore(store) {
|
|
|
168
184
|
// src/client/dialog.ts
|
|
169
185
|
var dialogStore = createStore(null);
|
|
170
186
|
var mutationStore = createStore(0);
|
|
171
|
-
function
|
|
172
|
-
dialogStore.set({ kind: "
|
|
173
|
-
}
|
|
174
|
-
function requestPurge(ids, title) {
|
|
175
|
-
dialogStore.set({ kind: "purge", ids, title });
|
|
187
|
+
function requestDelete(ids, title) {
|
|
188
|
+
dialogStore.set({ kind: "delete", ids, title });
|
|
176
189
|
}
|
|
177
190
|
|
|
178
191
|
// src/client/i18n.ts
|
|
179
192
|
var NS = "session-curator";
|
|
180
193
|
var zh = {
|
|
181
194
|
sectionLabel: "会话管理",
|
|
182
|
-
|
|
195
|
+
deleteSession: "删除会话",
|
|
183
196
|
titleArchived: "已归档",
|
|
184
197
|
titlePinned: "已置顶",
|
|
185
|
-
titleTrash: "回收站",
|
|
186
198
|
refresh: "刷新",
|
|
187
199
|
loading: "读取中…",
|
|
188
200
|
retry: "重试",
|
|
189
201
|
emptyArchived: "还没有归档的会话。在会话行的「⋯」菜单里归档后会出现在这里。",
|
|
190
202
|
emptyPinned: "没有置顶的会话。",
|
|
191
|
-
emptyTrash: "回收站是空的。",
|
|
192
203
|
unarchive: "取消归档",
|
|
193
204
|
pin: "置顶",
|
|
194
205
|
unpin: "取消置顶",
|
|
195
|
-
restore: "恢复",
|
|
196
|
-
purge: "永久删除",
|
|
197
|
-
moveToTrash: "移入回收站",
|
|
198
206
|
rowActions: "更多操作",
|
|
199
207
|
selectAll: "全选",
|
|
200
208
|
selectNone: "取消选择",
|
|
201
209
|
selected: "已选 {n} 项",
|
|
202
|
-
emptyTrashAll: "清空回收站",
|
|
203
210
|
total: "合计 {n} 个会话 · {size}",
|
|
204
211
|
working: "处理中…",
|
|
205
212
|
sessionRunning: "会话正在运行,先停止它再删除。",
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
confirmPurgeAction: "永久删除",
|
|
213
|
-
ackPurge: "我明白这无法撤销",
|
|
213
|
+
// 物理删除的确认弹窗(唯一一种弹窗):正文必须写清"不可恢复"并指路归档。
|
|
214
|
+
confirmDeleteTitle: "永久删除",
|
|
215
|
+
confirmDeleteBody: "「{title}」将被永久删除:日志文件会从磁盘上彻底移除,无法恢复。如果想以后还能找回,请改用「归档」。",
|
|
216
|
+
confirmDeleteManyBody: "选中的 {n} 个会话将被永久删除:日志文件会从磁盘上彻底移除,无法恢复。如果想以后还能找回,请改用「归档」。",
|
|
217
|
+
confirmDeleteAction: "永久删除",
|
|
218
|
+
ackDelete: "我明白这无法撤销",
|
|
214
219
|
cancel: "取消",
|
|
215
220
|
close: "关闭",
|
|
216
221
|
noProject: "(未知目录)",
|
|
@@ -226,6 +231,35 @@ var zh = {
|
|
|
226
231
|
clearShort: "取消",
|
|
227
232
|
collapse: "折叠",
|
|
228
233
|
expand: "展开",
|
|
234
|
+
// 跨页签的"整个工作区一起处理"(分组标题右侧的「⋯」)。
|
|
235
|
+
workspaceActions: "整个工作区",
|
|
236
|
+
workspaceMenu: "工作区操作",
|
|
237
|
+
archiveWorkspace: "整个工作区归档",
|
|
238
|
+
deleteWorkspace: "整个工作区删除",
|
|
239
|
+
workspaceRunningHint: "这个工作区里有会话正在运行,危险动作已禁用",
|
|
240
|
+
// 运行中徽标(宿主用官方的 workspace/session-activity 判定)。
|
|
241
|
+
running: "运行中",
|
|
242
|
+
runningHint: "会话正在运行,先停止它再操作",
|
|
243
|
+
// 导出 / 定位。
|
|
244
|
+
copyPath: "复制路径",
|
|
245
|
+
revealInFolder: "在资源管理器中显示",
|
|
246
|
+
pathCopied: "已复制路径:{path}",
|
|
247
|
+
pathCopyFailed: "复制失败,路径是:{path}",
|
|
248
|
+
revealFailed: "没能打开文件管理器({reason}),路径是:{path}",
|
|
249
|
+
// 磁盘占用视图。
|
|
250
|
+
titleUsage: "磁盘占用",
|
|
251
|
+
usageTitle: "磁盘占用",
|
|
252
|
+
usageTotal: "共 {n} 个会话 · {size}",
|
|
253
|
+
usageByWorkspace: "按工作区",
|
|
254
|
+
usageLargest: "最占地方的会话",
|
|
255
|
+
usageOldest: "最久没打开的会话",
|
|
256
|
+
usageLoading: "正在统计…",
|
|
257
|
+
usageArchived: "已归档",
|
|
258
|
+
usagePinned: "已置顶",
|
|
259
|
+
usageSuggestArchive: "建议归档",
|
|
260
|
+
usageArchive: "归档",
|
|
261
|
+
usageUnarchive: "取消归档",
|
|
262
|
+
usageNoData: "没有可统计的会话。",
|
|
229
263
|
titleConfig: "配置",
|
|
230
264
|
configTitle: "基础设置",
|
|
231
265
|
configTab: "默认页签",
|
|
@@ -243,10 +277,20 @@ var zh = {
|
|
|
243
277
|
configMeta: "行内显示体积与文件数",
|
|
244
278
|
configMetaHint: "关掉后每行只留标题(平铺时仍会显示文件夹)",
|
|
245
279
|
configOrder: "排序",
|
|
246
|
-
configOrderHint: "
|
|
280
|
+
configOrderHint: "原顺序就是宿主给的顺序",
|
|
247
281
|
orderHost: "原顺序",
|
|
248
282
|
orderTitle: "标题",
|
|
249
283
|
orderBytes: "体积",
|
|
284
|
+
orderCreatedAt: "创建时间",
|
|
285
|
+
// 搜索与过滤(只影响显示;批量操作与计数仍按整个页签算)。
|
|
286
|
+
searchLabel: "搜索会话",
|
|
287
|
+
searchPlaceholder: "搜索标题或目录…",
|
|
288
|
+
searchClear: "清除搜索",
|
|
289
|
+
timeFilterLabel: "时间",
|
|
290
|
+
timeAll: "全部",
|
|
291
|
+
timeRecent: "最近 7 天",
|
|
292
|
+
filtered: "已过滤出 {n} 个会话(共 {total} 个)",
|
|
293
|
+
emptyFiltered: "没有符合搜索条件的会话。",
|
|
250
294
|
configAutoUpdate: "自动检查更新",
|
|
251
295
|
configAutoUpdateHint: "打开这个页面时问一次 npm registry(10 分钟内有缓存)",
|
|
252
296
|
configReset: "恢复默认",
|
|
@@ -263,42 +307,39 @@ var zh = {
|
|
|
263
307
|
updateStepsTitle: "升级步骤",
|
|
264
308
|
updateStepOne: "在终端里升级包(按你自己的 profile 名替换 desktop):",
|
|
265
309
|
updateStepTwo: "重启 DSH 让宿主半区生效;只改了客户端半区的话刷新页面就够。",
|
|
266
|
-
updateOpen: "打开 npm 页面"
|
|
310
|
+
updateOpen: "打开 npm 页面",
|
|
311
|
+
// 会话行 hover 卡片(`sidebar.session.row.hover`)。
|
|
312
|
+
hoverStats: "{size} · {n} 个文件",
|
|
313
|
+
hoverCwd: "原目录",
|
|
314
|
+
hoverArchived: "已归档",
|
|
315
|
+
hoverPinned: "已置顶"
|
|
267
316
|
};
|
|
268
317
|
var en = {
|
|
269
318
|
sectionLabel: "Session manager",
|
|
270
|
-
|
|
319
|
+
deleteSession: "Delete session",
|
|
271
320
|
titleArchived: "Archived",
|
|
272
321
|
titlePinned: "Pinned",
|
|
273
|
-
titleTrash: "Trash",
|
|
274
322
|
refresh: "Refresh",
|
|
275
323
|
loading: "Loading…",
|
|
276
324
|
retry: "Retry",
|
|
277
325
|
emptyArchived: "No archived sessions yet. Archive one from its “⋯” menu and it shows up here.",
|
|
278
326
|
emptyPinned: "No pinned sessions.",
|
|
279
|
-
emptyTrash: "The trash is empty.",
|
|
280
327
|
unarchive: "Unarchive",
|
|
281
328
|
pin: "Pin",
|
|
282
329
|
unpin: "Unpin",
|
|
283
|
-
restore: "Restore",
|
|
284
|
-
purge: "Delete permanently",
|
|
285
|
-
moveToTrash: "Move to trash",
|
|
286
330
|
rowActions: "More actions",
|
|
287
331
|
selectAll: "Select all",
|
|
288
332
|
selectNone: "Clear selection",
|
|
289
333
|
selected: "{n} selected",
|
|
290
|
-
emptyTrashAll: "Empty trash",
|
|
291
334
|
total: "{n} sessions · {size}",
|
|
292
335
|
working: "Working…",
|
|
293
336
|
sessionRunning: "This session is running; stop it before deleting.",
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
confirmPurgeAction: "Delete permanently",
|
|
301
|
-
ackPurge: "I understand this cannot be undone",
|
|
337
|
+
// The one confirmation dialog: it must spell out "permanent" and point at archiving instead.
|
|
338
|
+
confirmDeleteTitle: "Delete permanently",
|
|
339
|
+
confirmDeleteBody: "“{title}” is deleted permanently: its log files are removed from disk for good and cannot be recovered. If you want to be able to bring it back later, use Archive instead.",
|
|
340
|
+
confirmDeleteManyBody: "{n} selected sessions are deleted permanently: their log files are removed from disk for good and cannot be recovered. If you want to be able to bring them back later, use Archive instead.",
|
|
341
|
+
confirmDeleteAction: "Delete permanently",
|
|
342
|
+
ackDelete: "I understand this cannot be undone",
|
|
302
343
|
cancel: "Cancel",
|
|
303
344
|
close: "Close",
|
|
304
345
|
noProject: "(unknown directory)",
|
|
@@ -314,6 +355,35 @@ var en = {
|
|
|
314
355
|
clearShort: "Clear",
|
|
315
356
|
collapse: "Collapse",
|
|
316
357
|
expand: "Expand",
|
|
358
|
+
// Cross-tab "handle the whole workspace" (the ⋯ on a folder header).
|
|
359
|
+
workspaceActions: "Whole workspace",
|
|
360
|
+
workspaceMenu: "Workspace actions",
|
|
361
|
+
archiveWorkspace: "Archive this whole workspace",
|
|
362
|
+
deleteWorkspace: "Delete this whole workspace",
|
|
363
|
+
workspaceRunningHint: "A session in this workspace is running, so the destructive actions are off",
|
|
364
|
+
// Running badge (the host asks the official workspace/session-activity hook).
|
|
365
|
+
running: "Running",
|
|
366
|
+
runningHint: "This session is running; stop it first",
|
|
367
|
+
// Export / locate.
|
|
368
|
+
copyPath: "Copy path",
|
|
369
|
+
revealInFolder: "Show in file explorer",
|
|
370
|
+
pathCopied: "Path copied: {path}",
|
|
371
|
+
pathCopyFailed: "Copy failed — the path is: {path}",
|
|
372
|
+
revealFailed: "Could not open the file explorer ({reason}); the path is: {path}",
|
|
373
|
+
// Disk usage view.
|
|
374
|
+
titleUsage: "Disk usage",
|
|
375
|
+
usageTitle: "Disk usage",
|
|
376
|
+
usageTotal: "{n} sessions · {size}",
|
|
377
|
+
usageByWorkspace: "By workspace",
|
|
378
|
+
usageLargest: "Largest sessions",
|
|
379
|
+
usageOldest: "Least recently opened",
|
|
380
|
+
usageLoading: "Measuring…",
|
|
381
|
+
usageArchived: "Archived",
|
|
382
|
+
usagePinned: "Pinned",
|
|
383
|
+
usageSuggestArchive: "Archive suggested",
|
|
384
|
+
usageArchive: "Archive",
|
|
385
|
+
usageUnarchive: "Unarchive",
|
|
386
|
+
usageNoData: "No sessions to measure.",
|
|
317
387
|
titleConfig: "Settings",
|
|
318
388
|
configTitle: "Basics",
|
|
319
389
|
configTab: "Default tab",
|
|
@@ -331,10 +401,20 @@ var en = {
|
|
|
331
401
|
configMeta: "Show size and file count per row",
|
|
332
402
|
configMetaHint: "Off leaves only the title (the folder still shows in the flat list)",
|
|
333
403
|
configOrder: "Order",
|
|
334
|
-
configOrderHint: "Host order is what the host sent
|
|
404
|
+
configOrderHint: "Host order is what the host sent",
|
|
335
405
|
orderHost: "Host",
|
|
336
406
|
orderTitle: "Title",
|
|
337
407
|
orderBytes: "Size",
|
|
408
|
+
orderCreatedAt: "Created",
|
|
409
|
+
// Search and filtering (display only; batch actions and counts still cover the whole tab).
|
|
410
|
+
searchLabel: "Search sessions",
|
|
411
|
+
searchPlaceholder: "Search title or folder…",
|
|
412
|
+
searchClear: "Clear search",
|
|
413
|
+
timeFilterLabel: "Time",
|
|
414
|
+
timeAll: "All",
|
|
415
|
+
timeRecent: "Last 7 days",
|
|
416
|
+
filtered: "{n} of {total} sessions match",
|
|
417
|
+
emptyFiltered: "No session matches the search.",
|
|
338
418
|
configAutoUpdate: "Check for updates automatically",
|
|
339
419
|
configAutoUpdateHint: "Asks the npm registry when this page opens (cached for 10 minutes)",
|
|
340
420
|
configReset: "Reset to defaults",
|
|
@@ -351,7 +431,12 @@ var en = {
|
|
|
351
431
|
updateStepsTitle: "How to upgrade",
|
|
352
432
|
updateStepOne: "Upgrade the package in a terminal (replace desktop with your profile name):",
|
|
353
433
|
updateStepTwo: "Restart DSH so the host half reloads; a client-half change only needs a page refresh.",
|
|
354
|
-
updateOpen: "Open the npm page"
|
|
434
|
+
updateOpen: "Open the npm page",
|
|
435
|
+
// Session-row hover card (`sidebar.session.row.hover`).
|
|
436
|
+
hoverStats: "{size} · {n} files",
|
|
437
|
+
hoverCwd: "Folder",
|
|
438
|
+
hoverArchived: "Archived",
|
|
439
|
+
hoverPinned: "Pinned"
|
|
355
440
|
};
|
|
356
441
|
var dictionaries = { zh, en };
|
|
357
442
|
function text(t, key, params) {
|
|
@@ -363,11 +448,36 @@ function text(t, key, params) {
|
|
|
363
448
|
}
|
|
364
449
|
var TAB_LABEL = {
|
|
365
450
|
archived: "titleArchived",
|
|
366
|
-
pinned: "titlePinned"
|
|
367
|
-
trash: "titleTrash"
|
|
451
|
+
pinned: "titlePinned"
|
|
368
452
|
};
|
|
369
453
|
|
|
454
|
+
// src/client/snapshot.ts
|
|
455
|
+
var last = null;
|
|
456
|
+
function cachedSnapshot() {
|
|
457
|
+
return last;
|
|
458
|
+
}
|
|
459
|
+
function rememberSnapshot(value) {
|
|
460
|
+
last = value;
|
|
461
|
+
}
|
|
462
|
+
function forgetSnapshot() {
|
|
463
|
+
last = null;
|
|
464
|
+
}
|
|
465
|
+
|
|
370
466
|
// src/client/styles.ts
|
|
467
|
+
var ROW_ICON_BUTTON = {
|
|
468
|
+
display: "inline-flex",
|
|
469
|
+
alignItems: "center",
|
|
470
|
+
justifyContent: "center",
|
|
471
|
+
width: "16px",
|
|
472
|
+
height: "16px",
|
|
473
|
+
padding: 0,
|
|
474
|
+
margin: 0,
|
|
475
|
+
border: "none",
|
|
476
|
+
background: "transparent",
|
|
477
|
+
borderRadius: "var(--dsw-radius-xs)",
|
|
478
|
+
cursor: "pointer",
|
|
479
|
+
flex: "none"
|
|
480
|
+
};
|
|
371
481
|
var S = {
|
|
372
482
|
column: { display: "flex", flexDirection: "column", gap: "10px", padding: "4px 0", minWidth: 0 },
|
|
373
483
|
rowLine: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" },
|
|
@@ -386,12 +496,20 @@ var S = {
|
|
|
386
496
|
borderTop: "1px solid var(--dsw-alias-border-l1)"
|
|
387
497
|
},
|
|
388
498
|
itemMain: { display: "flex", flexDirection: "column", gap: "2px", minWidth: 0, flex: "1 1 auto" },
|
|
499
|
+
/**
|
|
500
|
+
* 标题那一行:标题 + 状态胶囊(「运行中」)。
|
|
501
|
+
*
|
|
502
|
+
* 胶囊 `flex: 0 0 auto`、标题 `minWidth: 0` + 省略号 —— 长标题只挤自己那一段,
|
|
503
|
+
* 不会把胶囊压成两行(与分组标题那次"长路径挤坏按钮"同一条教训,见 groupAction)。
|
|
504
|
+
*/
|
|
505
|
+
itemTitleRow: { display: "flex", alignItems: "center", gap: "6px", minWidth: 0 },
|
|
389
506
|
itemTitle: {
|
|
390
507
|
color: "var(--dsw-alias-label-primary)",
|
|
391
508
|
fontSize: "13px",
|
|
392
509
|
overflow: "hidden",
|
|
393
510
|
textOverflow: "ellipsis",
|
|
394
|
-
whiteSpace: "nowrap"
|
|
511
|
+
whiteSpace: "nowrap",
|
|
512
|
+
minWidth: 0
|
|
395
513
|
},
|
|
396
514
|
itemMeta: {
|
|
397
515
|
color: "var(--dsw-alias-label-secondary)",
|
|
@@ -400,6 +518,26 @@ var S = {
|
|
|
400
518
|
textOverflow: "ellipsis",
|
|
401
519
|
whiteSpace: "nowrap"
|
|
402
520
|
},
|
|
521
|
+
/** 状态胶囊:与 hover 卡片那几个同款(带 alpha 的叠加色,深浅主题都有效)。 */
|
|
522
|
+
badge: {
|
|
523
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
524
|
+
fontSize: "10px",
|
|
525
|
+
lineHeight: "16px",
|
|
526
|
+
padding: "0 6px",
|
|
527
|
+
borderRadius: "8px",
|
|
528
|
+
background: "var(--dsw-alias-interactive-bg-active)",
|
|
529
|
+
flex: "0 0 auto"
|
|
530
|
+
},
|
|
531
|
+
/** 「运行中」:同款胶囊,但用业务蓝表示"它是活的",不是危险。 */
|
|
532
|
+
badgeRunning: {
|
|
533
|
+
color: "var(--dsw-alias-state-business-primary)",
|
|
534
|
+
fontSize: "10px",
|
|
535
|
+
lineHeight: "16px",
|
|
536
|
+
padding: "0 6px",
|
|
537
|
+
borderRadius: "8px",
|
|
538
|
+
background: "var(--dsw-alias-interactive-bg-active)",
|
|
539
|
+
flex: "0 0 auto"
|
|
540
|
+
},
|
|
403
541
|
itemActions: { display: "flex", alignItems: "center", gap: "6px", flex: "0 0 auto" },
|
|
404
542
|
/**
|
|
405
543
|
* 「⋯」触发器:图标按钮,弱化成次要前景色(跟侧边栏行的图标按钮一致)。
|
|
@@ -472,6 +610,48 @@ var S = {
|
|
|
472
610
|
empty: { color: "var(--dsw-alias-label-secondary)", fontSize: "12px", padding: "14px 10px", textAlign: "center" },
|
|
473
611
|
error: { color: "var(--dsw-alias-state-error-primary)", fontSize: "12px" },
|
|
474
612
|
footer: { color: "var(--dsw-alias-label-secondary)", fontSize: "11px", display: "flex", gap: "10px", flexWrap: "wrap" },
|
|
613
|
+
/**
|
|
614
|
+
* 数据页签上的过滤工具条:搜索框在左、时间档在右。
|
|
615
|
+
*
|
|
616
|
+
* `flexWrap` 是必需的:设置页本身可能很窄(侧边栏展开时尤其),搜索框的
|
|
617
|
+
* `minWidth` 兜住它不被压成一条线,挤不下时时间档换行,而不是把输入框挤没。
|
|
618
|
+
*/
|
|
619
|
+
filterBar: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" },
|
|
620
|
+
/** 搜索框自己撑开剩余宽度;给个下限,窄屏下也不至于只剩图标。 */
|
|
621
|
+
searchInput: { flex: "1 1 auto", minWidth: "160px" },
|
|
622
|
+
/** 把时间档推到右边(搜索框与它之间那段空白)。 */
|
|
623
|
+
filterSpacer: { flex: "1 1 auto" },
|
|
624
|
+
// ── 磁盘占用视图(配置页里的一块) ─────────────────────────────────────
|
|
625
|
+
usageBody: { display: "flex", flexDirection: "column", padding: "0 12px 12px", gap: "2px" },
|
|
626
|
+
usageHeading: {
|
|
627
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
628
|
+
fontSize: "11px",
|
|
629
|
+
marginTop: "8px",
|
|
630
|
+
marginBottom: "2px"
|
|
631
|
+
},
|
|
632
|
+
/** 一条工作区横条:轨道 + 名字 + 数字。名字可以很长,所以它自己省略。 */
|
|
633
|
+
usageBarRow: { display: "flex", alignItems: "center", gap: "8px", minWidth: 0 },
|
|
634
|
+
usageBarTrack: {
|
|
635
|
+
width: "70px",
|
|
636
|
+
height: "6px",
|
|
637
|
+
borderRadius: "3px",
|
|
638
|
+
background: "var(--dsw-alias-interactive-bg-active)",
|
|
639
|
+
flex: "0 0 auto",
|
|
640
|
+
overflow: "hidden"
|
|
641
|
+
},
|
|
642
|
+
usageBarFill: { display: "block", height: "100%", background: "var(--dsw-alias-state-business-primary)" },
|
|
643
|
+
usageBarName: {
|
|
644
|
+
color: "var(--dsw-alias-label-primary)",
|
|
645
|
+
fontSize: "12px",
|
|
646
|
+
overflow: "hidden",
|
|
647
|
+
textOverflow: "ellipsis",
|
|
648
|
+
whiteSpace: "nowrap",
|
|
649
|
+
minWidth: 0,
|
|
650
|
+
flex: "1 1 auto"
|
|
651
|
+
},
|
|
652
|
+
usageBarMeta: { color: "var(--dsw-alias-label-secondary)", fontSize: "11px", flex: "0 0 auto" },
|
|
653
|
+
/** 榜单里的一行:与列表行同一套几何,只是没有勾选框。 */
|
|
654
|
+
usageRow: { display: "flex", alignItems: "center", gap: "10px", padding: "6px 0", minWidth: 0 },
|
|
475
655
|
mono: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" },
|
|
476
656
|
ack: { display: "flex", alignItems: "center", gap: "8px", fontSize: "12px", color: "var(--dsw-alias-label-primary)" },
|
|
477
657
|
/** 顶部条:页签在左,动作靠 `marginInlineStart: auto` 推到右边。 */
|
|
@@ -490,6 +670,9 @@ var S = {
|
|
|
490
670
|
configLabel: { color: "var(--dsw-alias-label-primary)", fontSize: "13px" },
|
|
491
671
|
configHint: { color: "var(--dsw-alias-label-tertiary)", fontSize: "11px" },
|
|
492
672
|
configControl: { display: "flex", alignItems: "center", gap: "8px", flex: "0 0 auto" },
|
|
673
|
+
/** 下拉锚点:文字 + 双向箭头图标,别被挤窄(长标签在按钮里折行是这个仓库踩过的坑)。 */
|
|
674
|
+
dropdownAnchor: { flex: "0 0 auto" },
|
|
675
|
+
dropdownButton: { flex: "0 0 auto", whiteSpace: "nowrap" },
|
|
493
676
|
configFoot: {
|
|
494
677
|
display: "flex",
|
|
495
678
|
alignItems: "center",
|
|
@@ -525,7 +708,49 @@ var S = {
|
|
|
525
708
|
overflowX: "auto",
|
|
526
709
|
whiteSpace: "pre"
|
|
527
710
|
},
|
|
528
|
-
link: { color: "var(--dsw-alias-label-primary)", fontSize: "11px" }
|
|
711
|
+
link: { color: "var(--dsw-alias-label-primary)", fontSize: "11px" },
|
|
712
|
+
/**
|
|
713
|
+
* 会话行 hover 卡片里我们那一节。卡片自己的 `hoverContent` 已经是纵向 flex 且有间距,
|
|
714
|
+
* 这里只补一点点上边距,不加分隔线 —— 上面那行是官方画的相对时间。
|
|
715
|
+
*/
|
|
716
|
+
hoverBlock: { display: "flex", flexDirection: "column", gap: "3px", marginTop: "2px", minWidth: 0 },
|
|
717
|
+
hoverLine: { display: "flex", alignItems: "center", gap: "6px", minWidth: 0 },
|
|
718
|
+
hoverLabel: { color: "var(--dsw-alias-label-tertiary)", fontSize: "11px", flex: "0 0 auto" },
|
|
719
|
+
hoverMeta: { color: "var(--dsw-alias-label-secondary)", fontSize: "11px", flex: "0 0 auto" },
|
|
720
|
+
/** 原目录可能很长:让它自己省略,别把卡片撑宽。 */
|
|
721
|
+
hoverPath: {
|
|
722
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
723
|
+
fontSize: "11px",
|
|
724
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
725
|
+
overflow: "hidden",
|
|
726
|
+
textOverflow: "ellipsis",
|
|
727
|
+
whiteSpace: "nowrap",
|
|
728
|
+
minWidth: 0,
|
|
729
|
+
flex: "0 1 auto"
|
|
730
|
+
},
|
|
731
|
+
hoverBadges: { display: "flex", alignItems: "center", gap: "4px", flexWrap: "wrap" },
|
|
732
|
+
/** 状态胶囊:底色用带 alpha 的 `interactive-bg-active`(浅色主题里 layer 系列是同一个颜色)。 */
|
|
733
|
+
hoverBadge: {
|
|
734
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
735
|
+
fontSize: "10px",
|
|
736
|
+
lineHeight: "16px",
|
|
737
|
+
padding: "0 6px",
|
|
738
|
+
borderRadius: "8px",
|
|
739
|
+
background: "var(--dsw-alias-interactive-bg-active)",
|
|
740
|
+
flex: "0 0 auto"
|
|
741
|
+
},
|
|
742
|
+
/**
|
|
743
|
+
* 侧边栏会话行 hover 时那个「删除会话」图标按钮(见
|
|
744
|
+
* `components/delete-row-button.ts`):16×16 的朴素按钮,**两种状态都没有底色**,
|
|
745
|
+
* 只有前景色变深一档。
|
|
746
|
+
*
|
|
747
|
+
* `color-mix` 是 DSH 自己的产物里就在用的写法(例如 ui-deliverables 的
|
|
748
|
+
* `color-mix(in srgb, var(--dsw-alias-interactive-bg-hover) 50%, transparent)`)。
|
|
749
|
+
* 为什么不换 `state-error-secondary`:那个 token 在浅色主题里是 red-400,比 primary
|
|
750
|
+
* (red-600)**更浅**,方向正好相反。
|
|
751
|
+
*/
|
|
752
|
+
rowIconButton: { ...ROW_ICON_BUTTON, color: "var(--dsw-alias-state-error-primary)" },
|
|
753
|
+
rowIconButtonHover: { ...ROW_ICON_BUTTON, color: "color-mix(in srgb, var(--dsw-alias-state-error-primary) 78%, #000)" }
|
|
529
754
|
};
|
|
530
755
|
|
|
531
756
|
// src/client/components/dialog-host.ts
|
|
@@ -541,10 +766,9 @@ function DialogHost(props) {
|
|
|
541
766
|
setError(null);
|
|
542
767
|
}, [requestKey]);
|
|
543
768
|
if (request === null) return null;
|
|
544
|
-
const purge = request.kind === "purge";
|
|
545
769
|
const many = request.ids.length > 1;
|
|
546
|
-
const title =
|
|
547
|
-
const body =
|
|
770
|
+
const title = t("confirmDeleteTitle");
|
|
771
|
+
const body = many ? text(t, "confirmDeleteManyBody", { n: request.ids.length }) : text(t, "confirmDeleteBody", { title: request.title });
|
|
548
772
|
const close = () => {
|
|
549
773
|
if (busy) return;
|
|
550
774
|
dialogStore.set(null);
|
|
@@ -552,10 +776,11 @@ function DialogHost(props) {
|
|
|
552
776
|
const confirm = async () => {
|
|
553
777
|
setBusy(true);
|
|
554
778
|
try {
|
|
555
|
-
const value = await call(
|
|
779
|
+
const value = await call("delete", { ids: request.ids });
|
|
556
780
|
const failure = firstFailure(value.results);
|
|
557
781
|
if (failure === null) {
|
|
558
782
|
dialogStore.set(null);
|
|
783
|
+
rememberSnapshot(value);
|
|
559
784
|
mutationStore.set(mutationStore.get() + 1);
|
|
560
785
|
} else {
|
|
561
786
|
setError(failure);
|
|
@@ -578,15 +803,19 @@ function DialogHost(props) {
|
|
|
578
803
|
"div",
|
|
579
804
|
{ style: S.rowLine },
|
|
580
805
|
React2.createElement(import_dsh_client_ui_primitives.Button, { size: "md", variant: "ghost", disabled: busy, onClick: close }, t("cancel")),
|
|
581
|
-
React2.createElement(
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
806
|
+
React2.createElement(
|
|
807
|
+
import_dsh_client_ui_primitives.Button,
|
|
808
|
+
{
|
|
809
|
+
size: "md",
|
|
810
|
+
variant: "primary",
|
|
811
|
+
disabled: busy || !ack,
|
|
812
|
+
onClick: () => void confirm()
|
|
813
|
+
},
|
|
814
|
+
t("confirmDeleteAction")
|
|
815
|
+
)
|
|
587
816
|
)
|
|
588
817
|
},
|
|
589
|
-
|
|
818
|
+
React2.createElement(
|
|
590
819
|
"div",
|
|
591
820
|
{ style: S.column },
|
|
592
821
|
React2.createElement(
|
|
@@ -598,16 +827,44 @@ function DialogHost(props) {
|
|
|
598
827
|
disabled: busy,
|
|
599
828
|
onChange: (event) => setAck(Boolean(event.target.checked))
|
|
600
829
|
}),
|
|
601
|
-
t("
|
|
830
|
+
t("ackDelete")
|
|
602
831
|
),
|
|
603
832
|
error ? React2.createElement("div", { style: S.error }, error) : null
|
|
604
|
-
)
|
|
833
|
+
)
|
|
605
834
|
);
|
|
606
835
|
}
|
|
607
836
|
|
|
608
|
-
// src/client/components/
|
|
609
|
-
var
|
|
610
|
-
|
|
837
|
+
// src/client/components/session-hover.ts
|
|
838
|
+
var React3 = __toESM(require("react"), 1);
|
|
839
|
+
|
|
840
|
+
// src/client/detail.ts
|
|
841
|
+
var TTL_MS = 3e4;
|
|
842
|
+
var cache = /* @__PURE__ */ new Map();
|
|
843
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
844
|
+
var watching = false;
|
|
845
|
+
function forgetDetails() {
|
|
846
|
+
cache.clear();
|
|
847
|
+
}
|
|
848
|
+
function watchMutations() {
|
|
849
|
+
if (watching) return;
|
|
850
|
+
watching = true;
|
|
851
|
+
mutationStore.subscribe(forgetDetails);
|
|
852
|
+
}
|
|
853
|
+
async function describeSession(sessionId) {
|
|
854
|
+
watchMutations();
|
|
855
|
+
const cached = cache.get(sessionId);
|
|
856
|
+
if (cached !== void 0 && Date.now() - cached.at < TTL_MS) return cached.value;
|
|
857
|
+
const running = inflight.get(sessionId);
|
|
858
|
+
if (running !== void 0) return running;
|
|
859
|
+
const request = call("describe", { ids: [sessionId] }).then((value) => {
|
|
860
|
+
cache.set(sessionId, { at: Date.now(), value });
|
|
861
|
+
return value;
|
|
862
|
+
}).finally(() => {
|
|
863
|
+
inflight.delete(sessionId);
|
|
864
|
+
});
|
|
865
|
+
inflight.set(sessionId, request);
|
|
866
|
+
return request;
|
|
867
|
+
}
|
|
611
868
|
|
|
612
869
|
// src/client/format.ts
|
|
613
870
|
function formatBytes(bytes) {
|
|
@@ -635,7 +892,121 @@ function baseName(path) {
|
|
|
635
892
|
return parts.length > 0 ? parts[parts.length - 1] : path;
|
|
636
893
|
}
|
|
637
894
|
|
|
895
|
+
// src/client/components/session-hover.ts
|
|
896
|
+
function SessionHover(props) {
|
|
897
|
+
const t = props.t;
|
|
898
|
+
const [detail, setDetail] = React3.useState(null);
|
|
899
|
+
React3.useEffect(() => {
|
|
900
|
+
let live = true;
|
|
901
|
+
describeSession(props.sessionId).then(
|
|
902
|
+
(value) => {
|
|
903
|
+
if (live) setDetail(value);
|
|
904
|
+
},
|
|
905
|
+
() => {
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
return () => {
|
|
909
|
+
live = false;
|
|
910
|
+
};
|
|
911
|
+
}, [props.sessionId]);
|
|
912
|
+
if (detail === null) return null;
|
|
913
|
+
const badges = [];
|
|
914
|
+
if (detail.archived) badges.push({ key: "archived", label: t("hoverArchived") });
|
|
915
|
+
if (detail.pinned) badges.push({ key: "pinned", label: t("hoverPinned") });
|
|
916
|
+
const lines = [
|
|
917
|
+
React3.createElement(
|
|
918
|
+
"div",
|
|
919
|
+
{ key: "stats", style: S.hoverLine },
|
|
920
|
+
React3.createElement("span", { style: S.hoverMeta }, text(t, "hoverStats", { size: formatBytes(detail.bytes), n: detail.files }))
|
|
921
|
+
)
|
|
922
|
+
];
|
|
923
|
+
if (detail.cwd) {
|
|
924
|
+
lines.push(
|
|
925
|
+
React3.createElement(
|
|
926
|
+
"div",
|
|
927
|
+
{ key: "cwd", style: S.hoverLine },
|
|
928
|
+
React3.createElement("span", { style: S.hoverLabel }, t("hoverCwd")),
|
|
929
|
+
React3.createElement("span", { style: S.hoverPath, title: detail.cwd }, detail.cwd)
|
|
930
|
+
)
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
if (badges.length > 0) {
|
|
934
|
+
lines.push(
|
|
935
|
+
React3.createElement(
|
|
936
|
+
"div",
|
|
937
|
+
{ key: "badges", style: S.hoverBadges },
|
|
938
|
+
badges.map(
|
|
939
|
+
(badge) => React3.createElement(
|
|
940
|
+
"span",
|
|
941
|
+
{
|
|
942
|
+
key: badge.key,
|
|
943
|
+
style: S.hoverBadge
|
|
944
|
+
},
|
|
945
|
+
badge.label
|
|
946
|
+
)
|
|
947
|
+
)
|
|
948
|
+
)
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
return React3.createElement("div", { style: S.hoverBlock }, lines);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/client/components/settings-panel.ts
|
|
955
|
+
var React8 = __toESM(require("react"), 1);
|
|
956
|
+
var import_dsh_client_ui_primitives6 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
957
|
+
|
|
958
|
+
// src/client/export.ts
|
|
959
|
+
function openSessionPath(sessionId, reveal) {
|
|
960
|
+
return call("open", reveal === true ? { ids: [sessionId], target: "reveal" } : { ids: [sessionId], target: "path" });
|
|
961
|
+
}
|
|
962
|
+
async function copyText(value) {
|
|
963
|
+
const clipboard = globalThis.navigator?.clipboard;
|
|
964
|
+
if (clipboard !== void 0 && typeof clipboard.writeText === "function") {
|
|
965
|
+
try {
|
|
966
|
+
await clipboard.writeText(value);
|
|
967
|
+
return true;
|
|
968
|
+
} catch {
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
try {
|
|
972
|
+
const document = globalThis.document;
|
|
973
|
+
if (document === void 0) return false;
|
|
974
|
+
const area = document.createElement("textarea");
|
|
975
|
+
area.value = value;
|
|
976
|
+
area.style.position = "fixed";
|
|
977
|
+
area.style.top = "-1000px";
|
|
978
|
+
area.style.opacity = "0";
|
|
979
|
+
document.body.appendChild(area);
|
|
980
|
+
area.select();
|
|
981
|
+
const copied = document.execCommand("copy");
|
|
982
|
+
document.body.removeChild(area);
|
|
983
|
+
return copied;
|
|
984
|
+
} catch {
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
638
989
|
// src/client/groups.ts
|
|
990
|
+
function workspaceSummaries(archived, pinned) {
|
|
991
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
992
|
+
const bucketOf = (key) => {
|
|
993
|
+
let summary = summaries.get(key);
|
|
994
|
+
if (summary === void 0) {
|
|
995
|
+
summary = { key, archivableIds: [], deletableIds: [], activeIds: [] };
|
|
996
|
+
summaries.set(key, summary);
|
|
997
|
+
}
|
|
998
|
+
return summary;
|
|
999
|
+
};
|
|
1000
|
+
for (const rows of [archived, pinned]) {
|
|
1001
|
+
for (const row of rows) {
|
|
1002
|
+
const summary = bucketOf(groupKeyOf(row));
|
|
1003
|
+
summary.archivableIds.push(row.sessionId);
|
|
1004
|
+
summary.deletableIds.push(row.sessionId);
|
|
1005
|
+
if (row.active === true) summary.activeIds.push(row.sessionId);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return summaries;
|
|
1009
|
+
}
|
|
639
1010
|
function groupKeyOf(row) {
|
|
640
1011
|
if (typeof row.cwd === "string" && row.cwd.trim() !== "") {
|
|
641
1012
|
return `cwd:${row.cwd.trim().replace(/[\\/]+$/, "").replace(/\\/g, "/").toLowerCase()}`;
|
|
@@ -667,14 +1038,50 @@ function groupRows(rows, t) {
|
|
|
667
1038
|
return list;
|
|
668
1039
|
}
|
|
669
1040
|
function sortRows(rows, order) {
|
|
1041
|
+
const descending = (pick) => (left, right) => {
|
|
1042
|
+
const a = pick(left);
|
|
1043
|
+
const b = pick(right);
|
|
1044
|
+
const hasA = typeof a === "number" && Number.isFinite(a) && a > 0;
|
|
1045
|
+
const hasB = typeof b === "number" && Number.isFinite(b) && b > 0;
|
|
1046
|
+
if (!hasA && !hasB) return 0;
|
|
1047
|
+
if (!hasA) return 1;
|
|
1048
|
+
if (!hasB) return -1;
|
|
1049
|
+
return b - a;
|
|
1050
|
+
};
|
|
670
1051
|
if (order === "title") {
|
|
671
1052
|
return [...rows].sort((left, right) => String(left.title ?? "").localeCompare(String(right.title ?? "")));
|
|
672
1053
|
}
|
|
673
1054
|
if (order === "bytes") {
|
|
674
1055
|
return [...rows].sort((left, right) => (right.bytes ?? 0) - (left.bytes ?? 0));
|
|
675
1056
|
}
|
|
1057
|
+
if (order === "createdAt") return [...rows].sort(descending((row) => row.createdAt));
|
|
676
1058
|
return rows;
|
|
677
1059
|
}
|
|
1060
|
+
var RECENT_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1061
|
+
function matchesSearch(row, search) {
|
|
1062
|
+
const needle = search.trim().toLowerCase();
|
|
1063
|
+
if (needle === "") return true;
|
|
1064
|
+
const haystacks = [row.title, row.cwd, row.workspaceTitle];
|
|
1065
|
+
return haystacks.some((value) => typeof value === "string" && value.toLowerCase().includes(needle));
|
|
1066
|
+
}
|
|
1067
|
+
function timeStampOf(row) {
|
|
1068
|
+
if (typeof row.createdAt === "number" && row.createdAt > 0) return row.createdAt;
|
|
1069
|
+
return null;
|
|
1070
|
+
}
|
|
1071
|
+
function matchesFilter(row, filter) {
|
|
1072
|
+
if (!matchesSearch(row, filter.search)) return false;
|
|
1073
|
+
if (filter.timeFilter !== "recent") return true;
|
|
1074
|
+
const stamp = timeStampOf(row);
|
|
1075
|
+
if (stamp === null) return false;
|
|
1076
|
+
return (filter.now ?? Date.now()) - stamp <= RECENT_WINDOW_MS;
|
|
1077
|
+
}
|
|
1078
|
+
function filterRows(rows, filter) {
|
|
1079
|
+
if (filter.search.trim() === "" && filter.timeFilter === "all") return rows;
|
|
1080
|
+
return rows.filter((row) => matchesFilter(row, filter));
|
|
1081
|
+
}
|
|
1082
|
+
function isFilterActive(filter) {
|
|
1083
|
+
return filter.search.trim() !== "" || filter.timeFilter !== "all";
|
|
1084
|
+
}
|
|
678
1085
|
function collapseKey(tab, groupKey) {
|
|
679
1086
|
return `${tab}:${groupKey}`;
|
|
680
1087
|
}
|
|
@@ -698,8 +1105,11 @@ var DEFAULT_SETTINGS = {
|
|
|
698
1105
|
groupDefault: "expanded",
|
|
699
1106
|
showMeta: true,
|
|
700
1107
|
order: "host",
|
|
701
|
-
autoUpdate: true
|
|
1108
|
+
autoUpdate: true,
|
|
1109
|
+
search: "",
|
|
1110
|
+
timeFilter: "all"
|
|
702
1111
|
};
|
|
1112
|
+
var SEARCH_MAX_LENGTH = 200;
|
|
703
1113
|
var SETTINGS_KEY = "dsh-session-curator.settings";
|
|
704
1114
|
function readSettings() {
|
|
705
1115
|
try {
|
|
@@ -712,12 +1122,14 @@ function readSettings() {
|
|
|
712
1122
|
const value = validate(parsed[key]);
|
|
713
1123
|
if (value !== void 0) next[key] = value;
|
|
714
1124
|
};
|
|
715
|
-
pick("tab", (value) => value === "archived" || value === "pinned"
|
|
1125
|
+
pick("tab", (value) => value === "archived" || value === "pinned" ? value : void 0);
|
|
716
1126
|
pick("grouping", (value) => typeof value === "boolean" ? value : void 0);
|
|
717
1127
|
pick("groupDefault", (value) => value === "expanded" || value === "collapsed" ? value : void 0);
|
|
718
1128
|
pick("showMeta", (value) => typeof value === "boolean" ? value : void 0);
|
|
719
|
-
pick("order", (value) => value === "host" || value === "title" || value === "bytes" ? value : void 0);
|
|
1129
|
+
pick("order", (value) => value === "host" || value === "title" || value === "bytes" || value === "createdAt" ? value : void 0);
|
|
720
1130
|
pick("autoUpdate", (value) => typeof value === "boolean" ? value : void 0);
|
|
1131
|
+
pick("search", (value) => typeof value === "string" ? value.slice(0, SEARCH_MAX_LENGTH) : void 0);
|
|
1132
|
+
pick("timeFilter", (value) => value === "all" || value === "recent" ? value : void 0);
|
|
721
1133
|
return next;
|
|
722
1134
|
} catch {
|
|
723
1135
|
return { ...DEFAULT_SETTINGS };
|
|
@@ -726,6 +1138,7 @@ function readSettings() {
|
|
|
726
1138
|
var settingsStore = createStore(readSettings());
|
|
727
1139
|
function updateSettings(patch) {
|
|
728
1140
|
const next = { ...settingsStore.get(), ...patch };
|
|
1141
|
+
if (typeof next.search === "string" && next.search.length > SEARCH_MAX_LENGTH) next.search = next.search.slice(0, SEARCH_MAX_LENGTH);
|
|
729
1142
|
settingsStore.set(next);
|
|
730
1143
|
try {
|
|
731
1144
|
if (globalThis.localStorage) globalThis.localStorage.setItem(SETTINGS_KEY, JSON.stringify(next));
|
|
@@ -760,6 +1173,18 @@ function writeSidebarArchived(show) {
|
|
|
760
1173
|
}
|
|
761
1174
|
}
|
|
762
1175
|
|
|
1176
|
+
// src/client/toast.ts
|
|
1177
|
+
var toastStore = createStore(null);
|
|
1178
|
+
var sequence = 0;
|
|
1179
|
+
function showNotice(message) {
|
|
1180
|
+
if (message === "") return;
|
|
1181
|
+
sequence += 1;
|
|
1182
|
+
toastStore.set({ seq: sequence, message });
|
|
1183
|
+
}
|
|
1184
|
+
function dismissToast() {
|
|
1185
|
+
toastStore.set(null);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
763
1188
|
// src/client/update.ts
|
|
764
1189
|
var updateStore = createStore({ state: "idle", value: null, error: null });
|
|
765
1190
|
async function checkUpdate(force) {
|
|
@@ -772,19 +1197,64 @@ async function checkUpdate(force) {
|
|
|
772
1197
|
}
|
|
773
1198
|
}
|
|
774
1199
|
|
|
775
|
-
// src/client/components/
|
|
776
|
-
var
|
|
1200
|
+
// src/client/components/dropdown.ts
|
|
1201
|
+
var React4 = __toESM(require("react"), 1);
|
|
777
1202
|
var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
1203
|
+
function Dropdown(props) {
|
|
1204
|
+
const current2 = props.options.find((option) => option.value === props.value);
|
|
1205
|
+
const display = props.display ?? (current2 === void 0 ? String(props.value) : current2.label);
|
|
1206
|
+
const optionId = (value) => `${props.id}-${String(value)}`;
|
|
1207
|
+
return React4.createElement(import_dsh_client_ui_primitives2.Menu, {
|
|
1208
|
+
open: props.open,
|
|
1209
|
+
portal: true,
|
|
1210
|
+
side: "bottom",
|
|
1211
|
+
align: props.align ?? "end",
|
|
1212
|
+
items: props.options.map((option) => ({
|
|
1213
|
+
id: optionId(option.value),
|
|
1214
|
+
label: option.label,
|
|
1215
|
+
disabled: option.disabled === true
|
|
1216
|
+
})),
|
|
1217
|
+
// 命中当前档位时由 `Menu` 在行**尾**画对勾 —— 这是数据行选中态的唯一来源。
|
|
1218
|
+
selectedId: optionId(props.value),
|
|
1219
|
+
onSelect: (id) => {
|
|
1220
|
+
props.onToggle(false);
|
|
1221
|
+
const picked = props.options.find((option) => optionId(option.value) === id);
|
|
1222
|
+
if (picked !== void 0 && picked.value !== props.value) props.onChange(picked.value);
|
|
1223
|
+
},
|
|
1224
|
+
onClose: () => props.onToggle(false),
|
|
1225
|
+
className: S.dropdownAnchor,
|
|
1226
|
+
anchor: React4.createElement(
|
|
1227
|
+
import_dsh_client_ui_primitives2.Button,
|
|
1228
|
+
{
|
|
1229
|
+
size: "sm",
|
|
1230
|
+
variant: "outline",
|
|
1231
|
+
disabled: props.disabled === true,
|
|
1232
|
+
style: S.dropdownButton,
|
|
1233
|
+
"aria-label": props.label,
|
|
1234
|
+
"aria-haspopup": "menu",
|
|
1235
|
+
"aria-expanded": props.open,
|
|
1236
|
+
icon: React4.createElement(import_dsh_client_ui_primitives2.IconChevronsUpDownOutlineRegular, { size: 14 }),
|
|
1237
|
+
onClick: () => props.onToggle(!props.open)
|
|
1238
|
+
},
|
|
1239
|
+
display
|
|
1240
|
+
)
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// src/client/components/group-header.ts
|
|
1245
|
+
var React5 = __toESM(require("react"), 1);
|
|
1246
|
+
var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
778
1247
|
function GroupHeader(props) {
|
|
779
1248
|
const group = props.group;
|
|
780
1249
|
const t = props.t;
|
|
781
1250
|
const total = group.rows.length;
|
|
782
1251
|
const allSelected = total > 0 && group.rows.every((row) => props.selected.includes(row.sessionId));
|
|
783
1252
|
const action = allSelected ? t("clearShort") : t("selectAllShort");
|
|
784
|
-
|
|
1253
|
+
const workspaceActions = props.workspaceActions ?? [];
|
|
1254
|
+
return React5.createElement(
|
|
785
1255
|
"div",
|
|
786
1256
|
{ style: props.first ? { ...S.groupHead, borderTop: "none" } : S.groupHead },
|
|
787
|
-
|
|
1257
|
+
React5.createElement(
|
|
788
1258
|
"button",
|
|
789
1259
|
{
|
|
790
1260
|
type: "button",
|
|
@@ -792,28 +1262,78 @@ function GroupHeader(props) {
|
|
|
792
1262
|
title: props.collapsed ? t("expand") : t("collapse"),
|
|
793
1263
|
onClick: () => props.onToggleCollapse()
|
|
794
1264
|
},
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1265
|
+
React5.createElement(
|
|
1266
|
+
"span",
|
|
1267
|
+
{ style: S.groupCaret },
|
|
1268
|
+
props.collapsed ? React5.createElement(import_dsh_client_ui_primitives3.IconChevronRightOutlineRegular, { size: 12 }) : React5.createElement(import_dsh_client_ui_primitives3.IconChevronDownOutlineRegular, { size: 12 })
|
|
1269
|
+
),
|
|
1270
|
+
React5.createElement(
|
|
1271
|
+
"span",
|
|
1272
|
+
{ style: S.groupIcon },
|
|
1273
|
+
props.collapsed ? React5.createElement(import_dsh_client_ui_primitives3.IconFolderCloseRegular, { size: 14 }) : React5.createElement(import_dsh_client_ui_primitives3.IconFolderOpenRegular, { size: 14 })
|
|
1274
|
+
),
|
|
1275
|
+
React5.createElement("span", { style: S.groupTitle, title: group.cwd || group.title }, group.title),
|
|
1276
|
+
React5.createElement("span", { style: S.groupMeta }, text(t, "sessions", { n: total })),
|
|
1277
|
+
group.bytes > 0 ? React5.createElement("span", { style: S.groupMeta }, formatBytes(group.bytes)) : null,
|
|
1278
|
+
group.cwd !== null ? React5.createElement("span", { style: { ...S.groupPath, ...S.mono }, title: group.cwd }, group.cwd) : null
|
|
801
1279
|
),
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1280
|
+
// 工作区动作:先勾选(本页签),再「⋯」(跨页签)。两个都不参与 flex 收缩,
|
|
1281
|
+
// 长路径挤的只能是左边的折叠按钮(见 styles.ts 的 groupAction)。
|
|
1282
|
+
React5.createElement(
|
|
1283
|
+
import_dsh_client_ui_primitives3.Button,
|
|
1284
|
+
{
|
|
1285
|
+
size: "sm",
|
|
1286
|
+
variant: "ghost",
|
|
1287
|
+
disabled: props.busy,
|
|
1288
|
+
style: S.groupAction,
|
|
1289
|
+
title: `${group.title} — ${allSelected ? t("clearGroup") : t("selectGroup")}`,
|
|
1290
|
+
onClick: () => props.onToggleGroup()
|
|
1291
|
+
},
|
|
1292
|
+
action
|
|
1293
|
+
),
|
|
1294
|
+
workspaceActions.length === 0 ? null : React5.createElement(import_dsh_client_ui_primitives3.Menu, {
|
|
1295
|
+
open: props.workspaceMenuOpen === true,
|
|
1296
|
+
portal: true,
|
|
1297
|
+
side: "bottom",
|
|
1298
|
+
align: "end",
|
|
1299
|
+
closeOnPointerLeave: true,
|
|
1300
|
+
items: workspaceActions,
|
|
1301
|
+
onSelect: (id) => props.onRunWorkspaceAction?.(id),
|
|
1302
|
+
onClose: () => props.onCloseWorkspaceMenu?.(),
|
|
1303
|
+
anchor: React5.createElement(import_dsh_client_ui_primitives3.Button, {
|
|
1304
|
+
size: "sm",
|
|
1305
|
+
variant: "ghost",
|
|
1306
|
+
disabled: props.busy,
|
|
1307
|
+
style: S.rowMenuButton,
|
|
1308
|
+
icon: React5.createElement(import_dsh_client_ui_primitives3.IconEllipsisOutlineRegular, {}),
|
|
1309
|
+
"aria-label": `${group.title} — ${t("workspaceActions")}`,
|
|
1310
|
+
onClick: () => props.onToggleWorkspaceMenu?.()
|
|
1311
|
+
})
|
|
1312
|
+
})
|
|
811
1313
|
);
|
|
812
1314
|
}
|
|
1315
|
+
function workspaceActionsOf(t, summary) {
|
|
1316
|
+
return [
|
|
1317
|
+
{
|
|
1318
|
+
id: "archive",
|
|
1319
|
+
label: t("archiveWorkspace"),
|
|
1320
|
+
icon: React5.createElement(import_dsh_client_ui_primitives3.IconArchiveOutlineRegular, { size: 14 }),
|
|
1321
|
+
// 全都归档过了就没什么可归档的;有会话正在跑时也不给点(归档会先停掉它的工作)。
|
|
1322
|
+
disabled: summary.archivableIds.length === 0 || summary.activeIds.length > 0
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
id: "delete",
|
|
1326
|
+
label: t("deleteWorkspace"),
|
|
1327
|
+
icon: React5.createElement(import_dsh_client_ui_primitives3.IconTrashOutlineRegular, { size: 14 }),
|
|
1328
|
+
danger: true,
|
|
1329
|
+
disabled: summary.deletableIds.length === 0 || summary.activeIds.length > 0
|
|
1330
|
+
}
|
|
1331
|
+
];
|
|
1332
|
+
}
|
|
813
1333
|
|
|
814
1334
|
// src/client/components/session-row.ts
|
|
815
|
-
var
|
|
816
|
-
var
|
|
1335
|
+
var React6 = __toESM(require("react"), 1);
|
|
1336
|
+
var import_dsh_client_ui_primitives4 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
817
1337
|
function SessionRow(props) {
|
|
818
1338
|
const row = props.row;
|
|
819
1339
|
const t = props.t;
|
|
@@ -823,29 +1343,36 @@ function SessionRow(props) {
|
|
|
823
1343
|
if (row.bytes) meta.push(formatBytes(row.bytes));
|
|
824
1344
|
if (row.files) meta.push(text(t, "files", { n: row.files }));
|
|
825
1345
|
}
|
|
826
|
-
const time =
|
|
1346
|
+
const time = formatTime(row.createdAt);
|
|
827
1347
|
if (time !== "") meta.push(time);
|
|
828
1348
|
const menu = props.menu;
|
|
829
|
-
|
|
1349
|
+
const running = row.active === true;
|
|
1350
|
+
return React6.createElement(
|
|
830
1351
|
"div",
|
|
831
1352
|
{ style: props.first ? { ...S.item, borderTop: "none" } : S.item },
|
|
832
|
-
|
|
1353
|
+
React6.createElement("input", {
|
|
833
1354
|
type: "checkbox",
|
|
834
1355
|
checked: props.checked,
|
|
835
1356
|
disabled: props.busy,
|
|
836
1357
|
"aria-label": row.title,
|
|
837
1358
|
onChange: () => props.onToggle()
|
|
838
1359
|
}),
|
|
839
|
-
|
|
1360
|
+
React6.createElement(
|
|
840
1361
|
"div",
|
|
841
1362
|
{ style: S.itemMain },
|
|
842
|
-
|
|
843
|
-
|
|
1363
|
+
React6.createElement(
|
|
1364
|
+
"div",
|
|
1365
|
+
{ style: S.itemTitleRow },
|
|
1366
|
+
React6.createElement("div", { style: S.itemTitle, title: row.title }, row.title),
|
|
1367
|
+
// 胶囊:`flex: 0 0 auto`,所以长标题只会挤标题那一段(它有 minWidth: 0 + 省略号)。
|
|
1368
|
+
running ? React6.createElement("span", { style: S.badgeRunning, title: t("runningHint") }, t("running")) : null
|
|
1369
|
+
),
|
|
1370
|
+
React6.createElement("div", { style: S.itemMeta, title: row.cwd || "" }, meta.join(" · "))
|
|
844
1371
|
),
|
|
845
|
-
|
|
1372
|
+
React6.createElement(
|
|
846
1373
|
"div",
|
|
847
1374
|
{ style: S.itemActions },
|
|
848
|
-
|
|
1375
|
+
React6.createElement(import_dsh_client_ui_primitives4.Menu, {
|
|
849
1376
|
open: props.menuOpen,
|
|
850
1377
|
portal: true,
|
|
851
1378
|
side: "bottom",
|
|
@@ -854,12 +1381,12 @@ function SessionRow(props) {
|
|
|
854
1381
|
items: menu.items,
|
|
855
1382
|
onSelect: (id) => menu.run(id),
|
|
856
1383
|
onClose: () => props.onCloseMenu(),
|
|
857
|
-
anchor:
|
|
1384
|
+
anchor: React6.createElement(import_dsh_client_ui_primitives4.Button, {
|
|
858
1385
|
size: "sm",
|
|
859
1386
|
variant: "ghost",
|
|
860
1387
|
disabled: props.busy,
|
|
861
1388
|
style: S.rowMenuButton,
|
|
862
|
-
icon:
|
|
1389
|
+
icon: React6.createElement(import_dsh_client_ui_primitives4.IconEllipsisOutlineRegular, {}),
|
|
863
1390
|
"aria-label": `${row.title} — ${t("rowActions")}`,
|
|
864
1391
|
onClick: (event) => {
|
|
865
1392
|
event.stopPropagation();
|
|
@@ -871,27 +1398,144 @@ function SessionRow(props) {
|
|
|
871
1398
|
);
|
|
872
1399
|
}
|
|
873
1400
|
|
|
1401
|
+
// src/client/components/usage-panel.ts
|
|
1402
|
+
var React7 = __toESM(require("react"), 1);
|
|
1403
|
+
var import_dsh_client_ui_primitives5 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
1404
|
+
function usageRow(t, row, busy, onArchive, onUnarchive) {
|
|
1405
|
+
const badges = [];
|
|
1406
|
+
if (row.archived) badges.push({ key: "archived", label: t("usageArchived"), style: S.badge });
|
|
1407
|
+
else if (row.pinned) badges.push({ key: "pinned", label: t("usagePinned"), style: S.badge });
|
|
1408
|
+
if (row.active) badges.push({ key: "running", label: t("running"), style: S.badgeRunning });
|
|
1409
|
+
const action = row.archived ? { label: t("usageUnarchive"), run: () => onUnarchive(row.sessionId) } : row.active ? null : { label: t("usageArchive"), run: () => onArchive(row.sessionId) };
|
|
1410
|
+
const when = formatTime(row.createdAt);
|
|
1411
|
+
return React7.createElement(
|
|
1412
|
+
"div",
|
|
1413
|
+
{ key: row.sessionId, style: S.usageRow },
|
|
1414
|
+
React7.createElement(
|
|
1415
|
+
"div",
|
|
1416
|
+
{ style: S.itemMain },
|
|
1417
|
+
React7.createElement(
|
|
1418
|
+
"div",
|
|
1419
|
+
{ style: S.itemTitleRow },
|
|
1420
|
+
React7.createElement("div", { style: S.itemTitle, title: row.title }, row.title),
|
|
1421
|
+
badges.map((badge) => React7.createElement("span", { key: badge.key, style: badge.style }, badge.label))
|
|
1422
|
+
),
|
|
1423
|
+
React7.createElement(
|
|
1424
|
+
"div",
|
|
1425
|
+
{ style: S.itemMeta, title: row.cwd || "" },
|
|
1426
|
+
[formatBytes(row.bytes), text(t, "files", { n: row.files }), row.workspaceTitle ?? row.cwd ?? t("noProject"), when].filter((part) => part !== "").join(" · ")
|
|
1427
|
+
)
|
|
1428
|
+
),
|
|
1429
|
+
React7.createElement(
|
|
1430
|
+
"div",
|
|
1431
|
+
{ style: S.itemActions },
|
|
1432
|
+
action === null ? null : React7.createElement(
|
|
1433
|
+
import_dsh_client_ui_primitives5.Button,
|
|
1434
|
+
{
|
|
1435
|
+
size: "sm",
|
|
1436
|
+
variant: "outline",
|
|
1437
|
+
disabled: busy,
|
|
1438
|
+
onClick: action.run
|
|
1439
|
+
},
|
|
1440
|
+
action.label
|
|
1441
|
+
)
|
|
1442
|
+
)
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
function UsagePanel(props) {
|
|
1446
|
+
const t = props.t;
|
|
1447
|
+
const usage = props.usage;
|
|
1448
|
+
const maxBytes = usage === null || usage.groups.length === 0 ? 0 : Math.max(...usage.groups.map((group) => group.bytes), 1);
|
|
1449
|
+
return React7.createElement(
|
|
1450
|
+
"div",
|
|
1451
|
+
{ style: S.card },
|
|
1452
|
+
React7.createElement(
|
|
1453
|
+
"div",
|
|
1454
|
+
{ style: { ...S.configRow, borderTop: "none" } },
|
|
1455
|
+
React7.createElement(
|
|
1456
|
+
"div",
|
|
1457
|
+
{ style: S.configMain },
|
|
1458
|
+
React7.createElement("span", { style: S.configLabel }, t("usageTitle")),
|
|
1459
|
+
React7.createElement(
|
|
1460
|
+
"span",
|
|
1461
|
+
{ style: S.configHint },
|
|
1462
|
+
props.loading ? t("usageLoading") : usage === null ? props.error ?? "" : text(t, "usageTotal", {
|
|
1463
|
+
n: usage.totalSessions,
|
|
1464
|
+
size: formatBytes(usage.totalBytes)
|
|
1465
|
+
})
|
|
1466
|
+
)
|
|
1467
|
+
)
|
|
1468
|
+
),
|
|
1469
|
+
usage === null || usage.totalSessions === 0 ? React7.createElement("div", { style: S.empty }, props.loading ? t("usageLoading") : t("usageNoData")) : React7.createElement(
|
|
1470
|
+
"div",
|
|
1471
|
+
{ style: S.usageBody },
|
|
1472
|
+
// 按工作区:横条 + 名字 + 体积 / 条数。
|
|
1473
|
+
React7.createElement("span", { style: S.usageHeading }, t("usageByWorkspace")),
|
|
1474
|
+
usage.groups.map(
|
|
1475
|
+
(group) => React7.createElement(
|
|
1476
|
+
"div",
|
|
1477
|
+
{ key: group.key, style: S.usageBarRow },
|
|
1478
|
+
React7.createElement(
|
|
1479
|
+
"span",
|
|
1480
|
+
{ style: S.usageBarTrack },
|
|
1481
|
+
React7.createElement("span", {
|
|
1482
|
+
style: {
|
|
1483
|
+
...S.usageBarFill,
|
|
1484
|
+
// 占比:按最大组归一,所以最小的组也看得见(下限 2%)。
|
|
1485
|
+
width: `${Math.max(2, Math.round(group.bytes / maxBytes * 100))}%`
|
|
1486
|
+
}
|
|
1487
|
+
})
|
|
1488
|
+
),
|
|
1489
|
+
React7.createElement(
|
|
1490
|
+
"span",
|
|
1491
|
+
{ style: S.usageBarName, title: group.key },
|
|
1492
|
+
group.title === "" ? t("noProject") : group.title
|
|
1493
|
+
),
|
|
1494
|
+
React7.createElement(
|
|
1495
|
+
"span",
|
|
1496
|
+
{ style: S.usageBarMeta },
|
|
1497
|
+
`${formatBytes(group.bytes)} · ${text(t, "sessions", { n: group.count })}`
|
|
1498
|
+
)
|
|
1499
|
+
)
|
|
1500
|
+
),
|
|
1501
|
+
React7.createElement("span", { style: S.usageHeading }, t("usageLargest")),
|
|
1502
|
+
usage.largest.map((row) => usageRow(t, row, props.busy, props.onArchive, props.onUnarchive)),
|
|
1503
|
+
React7.createElement("span", { style: S.usageHeading }, t("usageOldest")),
|
|
1504
|
+
usage.oldest.map((row) => usageRow(t, row, props.busy, props.onArchive, props.onUnarchive))
|
|
1505
|
+
)
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
874
1509
|
// src/client/components/settings-panel.ts
|
|
875
1510
|
function SessionManagerPanel(props) {
|
|
876
1511
|
const t = props.t;
|
|
877
1512
|
const settings = useStore(settingsStore);
|
|
878
|
-
const [tab, setTab] =
|
|
879
|
-
const [snapshot, setSnapshot] =
|
|
880
|
-
const [error, setError] =
|
|
881
|
-
const [loading, setLoading] =
|
|
882
|
-
const [busy, setBusy] =
|
|
883
|
-
const [selected, setSelected] =
|
|
884
|
-
const [collapsedOverrides, setCollapsedOverrides] =
|
|
885
|
-
const [menuFor, setMenuFor] =
|
|
1513
|
+
const [tab, setTab] = React8.useState(settings.tab);
|
|
1514
|
+
const [snapshot, setSnapshot] = React8.useState(cachedSnapshot);
|
|
1515
|
+
const [error, setError] = React8.useState(null);
|
|
1516
|
+
const [loading, setLoading] = React8.useState(true);
|
|
1517
|
+
const [busy, setBusy] = React8.useState(false);
|
|
1518
|
+
const [selected, setSelected] = React8.useState([]);
|
|
1519
|
+
const [collapsedOverrides, setCollapsedOverrides] = React8.useState({});
|
|
1520
|
+
const [menuFor, setMenuFor] = React8.useState(null);
|
|
1521
|
+
const [workspaceMenuFor, setWorkspaceMenuFor] = React8.useState(null);
|
|
1522
|
+
const [usage, setUsage] = React8.useState(null);
|
|
1523
|
+
const [usageError, setUsageError] = React8.useState(null);
|
|
1524
|
+
const [usageBusy, setUsageBusy] = React8.useState(false);
|
|
1525
|
+
const [usageReload, setUsageReload] = React8.useState(0);
|
|
1526
|
+
const [openDropdown, setOpenDropdown] = React8.useState(null);
|
|
886
1527
|
const grouping = useStore(groupingStore);
|
|
887
1528
|
const revision = useStore(mutationStore);
|
|
888
1529
|
const update = useStore(updateStore);
|
|
889
|
-
const [sidebarArchived, setSidebarArchived] =
|
|
890
|
-
const [sidebarAtMount] =
|
|
1530
|
+
const [sidebarArchived, setSidebarArchived] = React8.useState(readSidebarArchived);
|
|
1531
|
+
const [sidebarAtMount] = React8.useState(readSidebarArchived);
|
|
891
1532
|
const applyResult = (value) => {
|
|
892
|
-
if (value && value.archived)
|
|
1533
|
+
if (value && value.archived) {
|
|
1534
|
+
rememberSnapshot(value);
|
|
1535
|
+
setSnapshot(value);
|
|
1536
|
+
}
|
|
893
1537
|
};
|
|
894
|
-
const refresh =
|
|
1538
|
+
const refresh = React8.useCallback(async () => {
|
|
895
1539
|
setLoading(true);
|
|
896
1540
|
try {
|
|
897
1541
|
applyResult(await call("snapshot", {}));
|
|
@@ -902,17 +1546,41 @@ function SessionManagerPanel(props) {
|
|
|
902
1546
|
setLoading(false);
|
|
903
1547
|
}
|
|
904
1548
|
}, []);
|
|
905
|
-
|
|
1549
|
+
React8.useEffect(() => {
|
|
906
1550
|
void refresh();
|
|
907
1551
|
}, [refresh, revision]);
|
|
908
|
-
|
|
1552
|
+
React8.useEffect(() => {
|
|
909
1553
|
if (tab !== "config" || !settings.autoUpdate || update.state !== "idle") return;
|
|
910
1554
|
void checkUpdate(false);
|
|
911
1555
|
}, [tab, settings.autoUpdate, update.state]);
|
|
912
|
-
|
|
1556
|
+
React8.useEffect(() => {
|
|
913
1557
|
setSelected([]);
|
|
914
1558
|
setMenuFor(null);
|
|
1559
|
+
setWorkspaceMenuFor(null);
|
|
915
1560
|
}, [tab]);
|
|
1561
|
+
React8.useEffect(() => {
|
|
1562
|
+
if (tab !== "usage") return;
|
|
1563
|
+
let cancelled = false;
|
|
1564
|
+
setUsageBusy(true);
|
|
1565
|
+
setUsageError(null);
|
|
1566
|
+
void call("usage", {}).then((value) => {
|
|
1567
|
+
if (!cancelled) setUsage(value);
|
|
1568
|
+
}).catch((failure) => {
|
|
1569
|
+
if (!cancelled) {
|
|
1570
|
+
setUsage(null);
|
|
1571
|
+
setUsageError(reportError(failure));
|
|
1572
|
+
}
|
|
1573
|
+
}).finally(() => {
|
|
1574
|
+
if (!cancelled) setUsageBusy(false);
|
|
1575
|
+
});
|
|
1576
|
+
return () => {
|
|
1577
|
+
cancelled = true;
|
|
1578
|
+
};
|
|
1579
|
+
}, [tab, revision, usageReload]);
|
|
1580
|
+
const refreshUsage = () => setUsageReload((current2) => current2 + 1);
|
|
1581
|
+
const runUsageAction = (endpoint, sessionId) => {
|
|
1582
|
+
void run(endpoint, [sessionId]).then(() => refreshUsage());
|
|
1583
|
+
};
|
|
916
1584
|
const run = async (endpoint, ids) => {
|
|
917
1585
|
if (ids.length === 0) return;
|
|
918
1586
|
setBusy(true);
|
|
@@ -940,354 +1608,616 @@ function SessionManagerPanel(props) {
|
|
|
940
1608
|
};
|
|
941
1609
|
const archived = snapshot ? snapshot.archived : [];
|
|
942
1610
|
const pinned = snapshot ? snapshot.pinned : [];
|
|
943
|
-
const
|
|
944
|
-
const
|
|
1611
|
+
const rows = sortRows(tab === "archived" ? archived : pinned, settings.order);
|
|
1612
|
+
const filter = React8.useMemo(
|
|
1613
|
+
() => ({ search: settings.search, timeFilter: settings.timeFilter }),
|
|
1614
|
+
[settings.search, settings.timeFilter]
|
|
1615
|
+
);
|
|
1616
|
+
const filtered = React8.useMemo(() => filterRows(rows, filter), [rows, filter]);
|
|
1617
|
+
const filtering = isFilterActive(filter);
|
|
945
1618
|
const selectedRows = rows.filter((row) => selected.includes(row.sessionId));
|
|
946
|
-
const groups = grouping ? groupRows(
|
|
1619
|
+
const groups = grouping ? groupRows(filtered, t) : [];
|
|
947
1620
|
const blockOf = (group) => {
|
|
948
1621
|
const key = collapseKey(tab, group.key);
|
|
949
1622
|
return { key, group, rows: isGroupCollapsed(key, collapsedOverrides, settings.groupDefault) ? [] : group.rows };
|
|
950
1623
|
};
|
|
951
|
-
const blocks = grouping ? groups.map(blockOf) : [{ key: `${tab}:flat`, group: null, rows }];
|
|
1624
|
+
const blocks = grouping ? groups.map(blockOf) : [{ key: `${tab}:flat`, group: null, rows: filtered }];
|
|
952
1625
|
const tabs = [
|
|
953
1626
|
{ id: "archived", label: t(TAB_LABEL.archived), count: archived.length },
|
|
954
1627
|
{ id: "pinned", label: t(TAB_LABEL.pinned), count: pinned.length },
|
|
955
|
-
{ id: "
|
|
1628
|
+
{ id: "usage", label: t("titleUsage"), count: 0 },
|
|
956
1629
|
{ id: "config", label: t("titleConfig"), count: 0 }
|
|
957
1630
|
];
|
|
1631
|
+
const orderOptions = [
|
|
1632
|
+
{ value: "host", label: t("orderHost") },
|
|
1633
|
+
{ value: "title", label: t("orderTitle") },
|
|
1634
|
+
{ value: "bytes", label: t("orderBytes") },
|
|
1635
|
+
{ value: "createdAt", label: t("orderCreatedAt") }
|
|
1636
|
+
];
|
|
1637
|
+
const summaries = React8.useMemo(() => workspaceSummaries(archived, pinned), [archived, pinned]);
|
|
1638
|
+
const copyRowPath = async (sessionId) => {
|
|
1639
|
+
try {
|
|
1640
|
+
const value = await openSessionPath(sessionId);
|
|
1641
|
+
const copied = await copyText(value.path);
|
|
1642
|
+
showNotice(copied ? text(t, "pathCopied", { path: value.path }) : text(t, "pathCopyFailed", { path: value.path }));
|
|
1643
|
+
} catch (failure) {
|
|
1644
|
+
showNotice(reportError(failure));
|
|
1645
|
+
}
|
|
1646
|
+
};
|
|
1647
|
+
const revealRow = async (sessionId) => {
|
|
1648
|
+
try {
|
|
1649
|
+
const value = await openSessionPath(sessionId, true);
|
|
1650
|
+
if (!value.revealed) {
|
|
1651
|
+
await copyText(value.path);
|
|
1652
|
+
showNotice(text(t, "revealFailed", { reason: value.reason ?? "?", path: value.path }));
|
|
1653
|
+
}
|
|
1654
|
+
} catch (failure) {
|
|
1655
|
+
showNotice(reportError(failure));
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
958
1658
|
const rowMenu = (row) => {
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1659
|
+
const running = row.active === true;
|
|
1660
|
+
const deleteSession = {
|
|
1661
|
+
id: "delete",
|
|
1662
|
+
label: t("deleteSession"),
|
|
1663
|
+
icon: React8.createElement(import_dsh_client_ui_primitives6.IconTrashOutlineRegular, { size: 14 }),
|
|
1664
|
+
danger: true,
|
|
1665
|
+
disabled: running
|
|
1666
|
+
};
|
|
1667
|
+
const locate = [
|
|
1668
|
+
{ id: "copy-path", label: t("copyPath"), icon: React8.createElement(import_dsh_client_ui_primitives6.IconCopyOutlineRegular, { size: 14 }) },
|
|
1669
|
+
{ id: "reveal", label: t("revealInFolder"), icon: React8.createElement(import_dsh_client_ui_primitives6.IconFolderOpenRegular, { size: 14 }) }
|
|
1670
|
+
];
|
|
1671
|
+
const handleLocate = (id) => {
|
|
1672
|
+
if (id === "copy-path") {
|
|
1673
|
+
void copyRowPath(row.sessionId);
|
|
1674
|
+
return true;
|
|
1675
|
+
}
|
|
1676
|
+
if (id === "reveal") {
|
|
1677
|
+
void revealRow(row.sessionId);
|
|
1678
|
+
return true;
|
|
1679
|
+
}
|
|
1680
|
+
return false;
|
|
964
1681
|
};
|
|
965
1682
|
if (tab === "archived") {
|
|
966
1683
|
return {
|
|
967
1684
|
items: [
|
|
968
|
-
{ id: "unarchive", label: t("unarchive"), icon:
|
|
969
|
-
{ id: "pin", label: t("pin"), icon:
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
run: (id) => {
|
|
973
|
-
setMenuFor(null);
|
|
974
|
-
if (id === "trash") requestTrash([row.sessionId], row.title);
|
|
975
|
-
else void run(id, [row.sessionId]);
|
|
976
|
-
}
|
|
977
|
-
};
|
|
978
|
-
}
|
|
979
|
-
if (tab === "pinned") {
|
|
980
|
-
return {
|
|
981
|
-
items: [
|
|
982
|
-
{ id: "unpin", label: t("unpin"), icon: React5.createElement(import_dsh_client_ui_primitives4.IconPinOutlineRegular, { size: 14 }) },
|
|
983
|
-
moveToTrash
|
|
1685
|
+
{ id: "unarchive", label: t("unarchive"), icon: React8.createElement(import_dsh_client_ui_primitives6.IconUnarchiveOutlineRegular, { size: 14 }) },
|
|
1686
|
+
{ id: "pin", label: t("pin"), icon: React8.createElement(import_dsh_client_ui_primitives6.IconPinOutlineRegular, { size: 14 }) },
|
|
1687
|
+
deleteSession,
|
|
1688
|
+
...locate
|
|
984
1689
|
],
|
|
985
1690
|
run: (id) => {
|
|
986
1691
|
setMenuFor(null);
|
|
987
|
-
if (id
|
|
1692
|
+
if (handleLocate(id)) return;
|
|
1693
|
+
if (id === "delete") requestDelete([row.sessionId], row.title);
|
|
988
1694
|
else void run(id, [row.sessionId]);
|
|
989
1695
|
}
|
|
990
1696
|
};
|
|
991
1697
|
}
|
|
992
1698
|
return {
|
|
993
1699
|
items: [
|
|
994
|
-
{ id: "
|
|
995
|
-
|
|
1700
|
+
{ id: "unpin", label: t("unpin"), icon: React8.createElement(import_dsh_client_ui_primitives6.IconPinOutlineRegular, { size: 14 }) },
|
|
1701
|
+
deleteSession,
|
|
1702
|
+
...locate
|
|
996
1703
|
],
|
|
997
1704
|
run: (id) => {
|
|
998
1705
|
setMenuFor(null);
|
|
999
|
-
if (id
|
|
1706
|
+
if (handleLocate(id)) return;
|
|
1707
|
+
if (id === "delete") requestDelete([row.sessionId], row.title);
|
|
1000
1708
|
else void run(id, [row.sessionId]);
|
|
1001
1709
|
}
|
|
1002
1710
|
};
|
|
1003
1711
|
};
|
|
1712
|
+
const runWorkspaceAction = (group, id) => {
|
|
1713
|
+
setWorkspaceMenuFor(null);
|
|
1714
|
+
const summary = summaries.get(group.key);
|
|
1715
|
+
if (summary === void 0) return;
|
|
1716
|
+
if (id === "archive") void run("archive", summary.archivableIds);
|
|
1717
|
+
if (id === "delete") requestDelete(summary.deletableIds, group.title);
|
|
1718
|
+
};
|
|
1004
1719
|
const batch = [];
|
|
1005
1720
|
if (tab === "archived" && selected.length > 0) {
|
|
1006
1721
|
batch.push({ label: t("unarchive"), run: () => void run("unarchive", selected) });
|
|
1007
|
-
batch.push({
|
|
1722
|
+
batch.push({
|
|
1723
|
+
label: t("deleteSession"),
|
|
1724
|
+
variant: "outline",
|
|
1725
|
+
run: () => requestDelete(selected, selectedRows[0] ? selectedRows[0].title : "")
|
|
1726
|
+
});
|
|
1008
1727
|
}
|
|
1009
1728
|
if (tab === "pinned" && selected.length > 0) {
|
|
1010
1729
|
batch.push({ label: t("unpin"), run: () => void run("unpin", selected) });
|
|
1011
|
-
batch.push({
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1730
|
+
batch.push({
|
|
1731
|
+
label: t("deleteSession"),
|
|
1732
|
+
variant: "outline",
|
|
1733
|
+
run: () => requestDelete(selected, selectedRows[0] ? selectedRows[0].title : "")
|
|
1734
|
+
});
|
|
1016
1735
|
}
|
|
1017
1736
|
const totals = snapshot ? snapshot.totals : null;
|
|
1018
|
-
const totalBytes = tab === "archived"
|
|
1737
|
+
const totalBytes = tab === "archived" && totals ? totals.archivedBytes : 0;
|
|
1019
1738
|
const nodes = [];
|
|
1020
1739
|
let first = true;
|
|
1021
1740
|
for (const block of blocks) {
|
|
1022
1741
|
if (block.group !== null) {
|
|
1023
1742
|
const group = block.group;
|
|
1024
1743
|
const key = block.key;
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1744
|
+
const summary = summaries.get(group.key);
|
|
1745
|
+
nodes.push(
|
|
1746
|
+
React8.createElement(GroupHeader, {
|
|
1747
|
+
key: `group:${key}`,
|
|
1748
|
+
group,
|
|
1749
|
+
t,
|
|
1750
|
+
first,
|
|
1751
|
+
busy,
|
|
1752
|
+
selected,
|
|
1753
|
+
collapsed: isGroupCollapsed(key, collapsedOverrides, settings.groupDefault),
|
|
1754
|
+
onToggleGroup: () => toggleGroup(group),
|
|
1755
|
+
onToggleCollapse: () => toggleCollapse(key),
|
|
1756
|
+
// 「整个工作区」:跨页签的目标集合由 `workspaceSummaries()` 算好。
|
|
1757
|
+
// 每个渲染出来的分组都至少有一行,所以 `summary` 一定有值;`workspaceActionsOf`
|
|
1758
|
+
// 再按"有没有在跑的会话"把危险项置 `disabled`。
|
|
1759
|
+
workspaceActions: summary === void 0 ? [] : workspaceActionsOf(t, summary),
|
|
1760
|
+
workspaceMenuOpen: workspaceMenuFor === group.key,
|
|
1761
|
+
onToggleWorkspaceMenu: () => setWorkspaceMenuFor(workspaceMenuFor === group.key ? null : group.key),
|
|
1762
|
+
onCloseWorkspaceMenu: () => setWorkspaceMenuFor(null),
|
|
1763
|
+
onRunWorkspaceAction: (id) => runWorkspaceAction(group, id)
|
|
1764
|
+
})
|
|
1765
|
+
);
|
|
1036
1766
|
first = false;
|
|
1037
1767
|
}
|
|
1038
1768
|
for (const row of block.rows) {
|
|
1039
|
-
nodes.push(
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1769
|
+
nodes.push(
|
|
1770
|
+
React8.createElement(SessionRow, {
|
|
1771
|
+
key: row.sessionId,
|
|
1772
|
+
row,
|
|
1773
|
+
t,
|
|
1774
|
+
first,
|
|
1775
|
+
busy,
|
|
1776
|
+
showCwd: block.group === null,
|
|
1777
|
+
showMeta: settings.showMeta,
|
|
1778
|
+
checked: selected.includes(row.sessionId),
|
|
1779
|
+
onToggle: () => toggle(row.sessionId),
|
|
1780
|
+
menu: rowMenu(row),
|
|
1781
|
+
menuOpen: menuFor === row.sessionId,
|
|
1782
|
+
onToggleMenu: () => setMenuFor(menuFor === row.sessionId ? null : row.sessionId),
|
|
1783
|
+
onCloseMenu: () => setMenuFor(null)
|
|
1784
|
+
})
|
|
1785
|
+
);
|
|
1055
1786
|
first = false;
|
|
1056
1787
|
}
|
|
1057
1788
|
}
|
|
1058
1789
|
const isConfig = tab === "config";
|
|
1059
|
-
const
|
|
1790
|
+
const isUsage = tab === "usage";
|
|
1791
|
+
const isDataTab = !isConfig && !isUsage;
|
|
1792
|
+
const configRow = (key, label, hint, control, firstRow) => React8.createElement(
|
|
1060
1793
|
"div",
|
|
1061
1794
|
{ key, style: firstRow ? { ...S.configRow, borderTop: "none" } : S.configRow },
|
|
1062
|
-
|
|
1795
|
+
React8.createElement(
|
|
1063
1796
|
"div",
|
|
1064
1797
|
{ style: S.configMain },
|
|
1065
|
-
|
|
1066
|
-
hint === null ? null :
|
|
1798
|
+
React8.createElement("span", { style: S.configLabel }, label),
|
|
1799
|
+
hint === null ? null : React8.createElement("span", { style: S.configHint }, hint)
|
|
1067
1800
|
),
|
|
1068
|
-
|
|
1801
|
+
React8.createElement("div", { style: S.configControl }, control)
|
|
1802
|
+
);
|
|
1803
|
+
const filterBar = React8.createElement(
|
|
1804
|
+
"div",
|
|
1805
|
+
{ style: S.filterBar },
|
|
1806
|
+
React8.createElement(import_dsh_client_ui_primitives6.Input, {
|
|
1807
|
+
value: settings.search,
|
|
1808
|
+
placeholder: t("searchPlaceholder"),
|
|
1809
|
+
"aria-label": t("searchLabel"),
|
|
1810
|
+
icon: React8.createElement(import_dsh_client_ui_primitives6.IconSearchOutlineRegular, {}),
|
|
1811
|
+
style: S.searchInput,
|
|
1812
|
+
onChange: (event) => void updateSettings({ search: event.target.value })
|
|
1813
|
+
}),
|
|
1814
|
+
filtering ? React8.createElement(
|
|
1815
|
+
import_dsh_client_ui_primitives6.Button,
|
|
1816
|
+
{
|
|
1817
|
+
size: "sm",
|
|
1818
|
+
variant: "ghost",
|
|
1819
|
+
onClick: () => void updateSettings({ search: "", timeFilter: "all" })
|
|
1820
|
+
},
|
|
1821
|
+
t("searchClear")
|
|
1822
|
+
) : null,
|
|
1823
|
+
React8.createElement("div", { style: S.filterSpacer }),
|
|
1824
|
+
React8.createElement(import_dsh_client_ui_primitives6.SegmentedControl, {
|
|
1825
|
+
id: "session-curator-time-filter",
|
|
1826
|
+
value: settings.timeFilter,
|
|
1827
|
+
options: [
|
|
1828
|
+
{ value: "all", label: t("timeAll") },
|
|
1829
|
+
{ value: "recent", label: t("timeRecent") }
|
|
1830
|
+
],
|
|
1831
|
+
label: t("timeFilterLabel"),
|
|
1832
|
+
onChange: (next) => void updateSettings({ timeFilter: next })
|
|
1833
|
+
})
|
|
1069
1834
|
);
|
|
1070
1835
|
const version = snapshot !== null && typeof snapshot.version === "string" ? snapshot.version : update.value !== null && typeof update.value.current === "string" ? update.value.current : null;
|
|
1071
1836
|
const outdated = update.state === "done" && update.value !== null && update.value.outdated === true;
|
|
1072
|
-
const updateLine = update.state === "checking" ?
|
|
1073
|
-
|
|
1837
|
+
const updateLine = update.state === "checking" ? React8.createElement("span", { style: S.configHint }, t("updateChecking")) : update.state === "failed" ? React8.createElement(
|
|
1838
|
+
"span",
|
|
1839
|
+
{ style: S.error },
|
|
1840
|
+
text(t, "updateFailed", { reason: update.error === null ? "?" : update.error })
|
|
1841
|
+
) : update.value !== null && typeof update.value.error === "string" ? React8.createElement("span", { style: S.configHint }, text(t, "updateFailed", { reason: update.value.error })) : update.state === "done" && !outdated ? React8.createElement("span", { style: S.configHint }, t("updateLatest")) : null;
|
|
1842
|
+
const configPage = React8.createElement(
|
|
1074
1843
|
"div",
|
|
1075
1844
|
{ style: S.column },
|
|
1076
|
-
|
|
1845
|
+
React8.createElement(
|
|
1077
1846
|
"div",
|
|
1078
1847
|
{ style: S.card },
|
|
1079
|
-
configRow(
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
configRow(
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1848
|
+
configRow(
|
|
1849
|
+
"config:tab",
|
|
1850
|
+
t("configTab"),
|
|
1851
|
+
t("configTabHint"),
|
|
1852
|
+
React8.createElement(import_dsh_client_ui_primitives6.SegmentedControl, {
|
|
1853
|
+
id: "session-curator-default-tab",
|
|
1854
|
+
value: settings.tab,
|
|
1855
|
+
options: [
|
|
1856
|
+
{ value: "archived", label: t(TAB_LABEL.archived) },
|
|
1857
|
+
{ value: "pinned", label: t(TAB_LABEL.pinned) }
|
|
1858
|
+
],
|
|
1859
|
+
label: t("configTab"),
|
|
1860
|
+
onChange: (next) => void updateSettings({ tab: next })
|
|
1861
|
+
}),
|
|
1862
|
+
true
|
|
1863
|
+
),
|
|
1864
|
+
configRow(
|
|
1865
|
+
"config:grouping",
|
|
1866
|
+
t("configGrouping"),
|
|
1867
|
+
t("configGroupingHint"),
|
|
1868
|
+
React8.createElement(import_dsh_client_ui_primitives6.Switch, {
|
|
1869
|
+
checked: settings.grouping,
|
|
1870
|
+
label: t("configGrouping"),
|
|
1871
|
+
onChange: (next) => {
|
|
1872
|
+
updateSettings({ grouping: next });
|
|
1873
|
+
groupingStore.set(next);
|
|
1874
|
+
}
|
|
1875
|
+
})
|
|
1876
|
+
),
|
|
1877
|
+
configRow(
|
|
1878
|
+
"config:group-default",
|
|
1879
|
+
t("configGroupDefault"),
|
|
1880
|
+
t("configGroupDefaultHint"),
|
|
1881
|
+
React8.createElement(import_dsh_client_ui_primitives6.SegmentedControl, {
|
|
1882
|
+
id: "session-curator-group-default",
|
|
1883
|
+
value: settings.groupDefault,
|
|
1884
|
+
options: [
|
|
1885
|
+
{ value: "expanded", label: t("groupExpanded") },
|
|
1886
|
+
{ value: "collapsed", label: t("groupCollapsed") }
|
|
1887
|
+
],
|
|
1888
|
+
label: t("configGroupDefault"),
|
|
1889
|
+
onChange: (next) => void updateSettings({ groupDefault: next })
|
|
1890
|
+
})
|
|
1891
|
+
),
|
|
1892
|
+
configRow(
|
|
1893
|
+
"config:sidebar-archived",
|
|
1894
|
+
t("configSidebarArchived"),
|
|
1895
|
+
t("configSidebarArchivedHint"),
|
|
1896
|
+
React8.createElement(import_dsh_client_ui_primitives6.Switch, {
|
|
1897
|
+
checked: sidebarArchived !== "default",
|
|
1898
|
+
label: t("configSidebarArchived"),
|
|
1899
|
+
onChange: (next) => {
|
|
1900
|
+
writeSidebarArchived(next);
|
|
1901
|
+
setSidebarArchived(next ? "show" : "default");
|
|
1902
|
+
}
|
|
1903
|
+
})
|
|
1904
|
+
),
|
|
1116
1905
|
// 官方 store 只在创建时读一次 localStorage,所以改完必须刷新页面。
|
|
1117
|
-
sidebarArchived === sidebarAtMount ? null :
|
|
1906
|
+
sidebarArchived === sidebarAtMount ? null : React8.createElement(
|
|
1118
1907
|
"div",
|
|
1119
1908
|
{ key: "config:sidebar-refresh", style: S.configFoot },
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1909
|
+
React8.createElement("span", { style: S.configHint }, t("configSidebarRefreshHint")),
|
|
1910
|
+
React8.createElement(
|
|
1911
|
+
import_dsh_client_ui_primitives6.Button,
|
|
1912
|
+
{
|
|
1913
|
+
size: "sm",
|
|
1914
|
+
variant: "outline",
|
|
1915
|
+
onClick: () => {
|
|
1916
|
+
try {
|
|
1917
|
+
globalThis.location.reload();
|
|
1918
|
+
} catch {
|
|
1919
|
+
}
|
|
1128
1920
|
}
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1921
|
+
},
|
|
1922
|
+
t("refreshPage")
|
|
1923
|
+
)
|
|
1131
1924
|
),
|
|
1132
|
-
configRow(
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1925
|
+
configRow(
|
|
1926
|
+
"config:meta",
|
|
1927
|
+
t("configMeta"),
|
|
1928
|
+
t("configMetaHint"),
|
|
1929
|
+
React8.createElement(import_dsh_client_ui_primitives6.Switch, {
|
|
1930
|
+
checked: settings.showMeta,
|
|
1931
|
+
label: t("configMeta"),
|
|
1932
|
+
onChange: (next) => void updateSettings({ showMeta: next })
|
|
1933
|
+
})
|
|
1934
|
+
),
|
|
1935
|
+
// 排序:5 档,所以是下拉(`Dropdown` 见 components/dropdown.ts)。
|
|
1936
|
+
configRow(
|
|
1937
|
+
"config:order",
|
|
1938
|
+
t("configOrder"),
|
|
1939
|
+
t("configOrderHint"),
|
|
1940
|
+
React8.createElement(Dropdown, {
|
|
1941
|
+
id: "session-curator-order",
|
|
1942
|
+
value: settings.order,
|
|
1943
|
+
options: orderOptions,
|
|
1944
|
+
label: t("configOrder"),
|
|
1945
|
+
open: openDropdown === "order",
|
|
1946
|
+
onToggle: (next) => setOpenDropdown(next ? "order" : null),
|
|
1947
|
+
onChange: (next) => void updateSettings({ order: next })
|
|
1948
|
+
})
|
|
1949
|
+
),
|
|
1950
|
+
configRow(
|
|
1951
|
+
"config:auto",
|
|
1952
|
+
t("configAutoUpdate"),
|
|
1953
|
+
t("configAutoUpdateHint"),
|
|
1954
|
+
React8.createElement(import_dsh_client_ui_primitives6.Switch, {
|
|
1955
|
+
checked: settings.autoUpdate,
|
|
1956
|
+
label: t("configAutoUpdate"),
|
|
1957
|
+
onChange: (next) => void updateSettings({ autoUpdate: next })
|
|
1958
|
+
})
|
|
1959
|
+
),
|
|
1960
|
+
React8.createElement(
|
|
1154
1961
|
"div",
|
|
1155
1962
|
{ key: "config:reset", style: { ...S.configFoot, justifyContent: "flex-end" } },
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1963
|
+
React8.createElement(
|
|
1964
|
+
import_dsh_client_ui_primitives6.Button,
|
|
1965
|
+
{
|
|
1966
|
+
size: "sm",
|
|
1967
|
+
variant: "outline",
|
|
1968
|
+
onClick: () => {
|
|
1969
|
+
updateSettings({ ...DEFAULT_SETTINGS });
|
|
1970
|
+
groupingStore.set(DEFAULT_SETTINGS.grouping);
|
|
1971
|
+
}
|
|
1972
|
+
},
|
|
1973
|
+
t("configReset")
|
|
1974
|
+
)
|
|
1164
1975
|
)
|
|
1165
1976
|
),
|
|
1166
|
-
|
|
1977
|
+
React8.createElement(
|
|
1167
1978
|
"div",
|
|
1168
1979
|
{ style: S.card },
|
|
1169
|
-
|
|
1980
|
+
React8.createElement(
|
|
1170
1981
|
"div",
|
|
1171
1982
|
{ style: { ...S.configRow, borderTop: "none" } },
|
|
1172
|
-
|
|
1983
|
+
React8.createElement(
|
|
1173
1984
|
"div",
|
|
1174
1985
|
{ style: S.configMain },
|
|
1175
|
-
|
|
1176
|
-
|
|
1986
|
+
React8.createElement("span", { style: S.configLabel }, t("aboutTitle")),
|
|
1987
|
+
React8.createElement("span", { style: S.configHint }, "dsh-session-curator")
|
|
1177
1988
|
),
|
|
1178
|
-
|
|
1989
|
+
React8.createElement(
|
|
1179
1990
|
"div",
|
|
1180
1991
|
{ style: S.configControl },
|
|
1181
1992
|
// 版本号:浅色小字。
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1993
|
+
React8.createElement("span", { style: S.version }, `${t("versionLabel")} ${version === null ? "—" : `v${version}`}`),
|
|
1994
|
+
React8.createElement(
|
|
1995
|
+
import_dsh_client_ui_primitives6.Button,
|
|
1996
|
+
{
|
|
1997
|
+
size: "sm",
|
|
1998
|
+
variant: "outline",
|
|
1999
|
+
disabled: update.state === "checking",
|
|
2000
|
+
onClick: () => void checkUpdate(true)
|
|
2001
|
+
},
|
|
2002
|
+
t("updateCheckNow")
|
|
2003
|
+
)
|
|
1189
2004
|
)
|
|
1190
2005
|
),
|
|
1191
|
-
updateLine === null ? null :
|
|
1192
|
-
outdated && update.value !== null ?
|
|
2006
|
+
updateLine === null ? null : React8.createElement("div", { key: "config:update", style: S.configFoot }, updateLine),
|
|
2007
|
+
outdated && update.value !== null ? React8.createElement(
|
|
1193
2008
|
"div",
|
|
1194
2009
|
{ key: "config:upgrade", style: S.updateBox },
|
|
1195
|
-
|
|
1196
|
-
|
|
2010
|
+
React8.createElement(
|
|
2011
|
+
"span",
|
|
2012
|
+
{ style: S.updateTitle },
|
|
2013
|
+
text(t, "updateAvailable", { latest: update.value.latest ?? "?", current: version === null ? "?" : version })
|
|
2014
|
+
),
|
|
2015
|
+
React8.createElement(
|
|
1197
2016
|
"span",
|
|
1198
2017
|
{ style: S.updateSteps },
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
2018
|
+
React8.createElement("span", null, t("updateStepsTitle")),
|
|
2019
|
+
React8.createElement("span", null, t("updateStepOne")),
|
|
2020
|
+
React8.createElement("code", { style: S.code }, "dsh plugin --profile desktop add dsh-session-curator@latest"),
|
|
2021
|
+
React8.createElement("span", null, t("updateStepTwo"))
|
|
1203
2022
|
),
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
2023
|
+
React8.createElement(
|
|
2024
|
+
"a",
|
|
2025
|
+
{
|
|
2026
|
+
style: S.link,
|
|
2027
|
+
href: "https://www.npmjs.com/package/dsh-session-curator",
|
|
2028
|
+
target: "_blank",
|
|
2029
|
+
rel: "noreferrer"
|
|
2030
|
+
},
|
|
2031
|
+
t("updateOpen")
|
|
2032
|
+
)
|
|
1210
2033
|
) : null
|
|
1211
2034
|
)
|
|
1212
2035
|
);
|
|
1213
|
-
|
|
2036
|
+
const usagePage = React8.createElement(
|
|
1214
2037
|
"div",
|
|
1215
2038
|
{ style: S.column },
|
|
1216
|
-
|
|
2039
|
+
React8.createElement(
|
|
2040
|
+
"div",
|
|
2041
|
+
{ style: S.rowLine },
|
|
2042
|
+
React8.createElement("div", { style: S.filterSpacer }),
|
|
2043
|
+
React8.createElement(
|
|
2044
|
+
import_dsh_client_ui_primitives6.Button,
|
|
2045
|
+
{
|
|
2046
|
+
size: "sm",
|
|
2047
|
+
variant: "outline",
|
|
2048
|
+
disabled: usageBusy,
|
|
2049
|
+
onClick: () => void refreshUsage()
|
|
2050
|
+
},
|
|
2051
|
+
t("refresh")
|
|
2052
|
+
)
|
|
2053
|
+
),
|
|
2054
|
+
React8.createElement(UsagePanel, {
|
|
2055
|
+
t,
|
|
2056
|
+
usage,
|
|
2057
|
+
loading: usageBusy,
|
|
2058
|
+
error: usageError,
|
|
2059
|
+
busy,
|
|
2060
|
+
// 榜单上的动作走与列表同一个端点(`run()` 会顺手刷新快照),但**完事之后还要
|
|
2061
|
+
// 重拉一次 `usage`**:`run()` 只换快照,`usage` 不在快照里 —— 不重拉的话
|
|
2062
|
+
// 刚点了「取消归档」的那一行仍然顶着「已归档」胶囊,看起来像没生效。
|
|
2063
|
+
onArchive: (sessionId) => runUsageAction("archive", sessionId),
|
|
2064
|
+
onUnarchive: (sessionId) => runUsageAction("unarchive", sessionId)
|
|
2065
|
+
})
|
|
2066
|
+
);
|
|
2067
|
+
return React8.createElement(
|
|
2068
|
+
"div",
|
|
2069
|
+
{ style: S.column },
|
|
2070
|
+
React8.createElement(
|
|
1217
2071
|
"div",
|
|
1218
2072
|
{ style: S.header },
|
|
1219
|
-
|
|
2073
|
+
React8.createElement(
|
|
1220
2074
|
"div",
|
|
1221
2075
|
{ style: S.tabBar },
|
|
1222
|
-
tabs.map(
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
2076
|
+
tabs.map(
|
|
2077
|
+
(entry) => React8.createElement(
|
|
2078
|
+
import_dsh_client_ui_primitives6.Button,
|
|
2079
|
+
{
|
|
2080
|
+
key: entry.id,
|
|
2081
|
+
size: "sm",
|
|
2082
|
+
variant: tab === entry.id ? "primary" : "ghost",
|
|
2083
|
+
onClick: () => setTab(entry.id)
|
|
2084
|
+
},
|
|
2085
|
+
`${entry.label}${entry.count > 0 ? ` (${entry.count})` : ""}`
|
|
2086
|
+
)
|
|
2087
|
+
)
|
|
1228
2088
|
),
|
|
1229
|
-
//
|
|
1230
|
-
|
|
2089
|
+
// 分组与刷新推到右边;只有三个**数据**页签上才有它们(配置页与磁盘占用页都没有
|
|
2090
|
+
// 可筛可选的列表,磁盘占用页自带一个只重拉 usage 的刷新按钮)。
|
|
2091
|
+
isDataTab ? React8.createElement(
|
|
1231
2092
|
"div",
|
|
1232
2093
|
{ style: S.headerActions },
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
2094
|
+
React8.createElement(
|
|
2095
|
+
import_dsh_client_ui_primitives6.Button,
|
|
2096
|
+
{
|
|
2097
|
+
size: "sm",
|
|
2098
|
+
variant: "outline",
|
|
2099
|
+
title: t("groupHint"),
|
|
2100
|
+
onClick: () => {
|
|
2101
|
+
const next = !grouping;
|
|
2102
|
+
groupingStore.set(next);
|
|
2103
|
+
updateSettings({ grouping: next });
|
|
2104
|
+
}
|
|
2105
|
+
},
|
|
2106
|
+
grouping ? t("groupingOn") : t("groupingOff")
|
|
2107
|
+
),
|
|
2108
|
+
React8.createElement(
|
|
2109
|
+
import_dsh_client_ui_primitives6.Button,
|
|
2110
|
+
{ size: "sm", variant: "outline", disabled: loading || busy, onClick: () => void refresh() },
|
|
2111
|
+
t("refresh")
|
|
2112
|
+
)
|
|
2113
|
+
) : null
|
|
1245
2114
|
),
|
|
1246
|
-
error ?
|
|
1247
|
-
|
|
2115
|
+
error ? React8.createElement("div", { style: S.error }, error) : null,
|
|
2116
|
+
// 过滤工具条:只有数据页签上有(配置页没有列表可筛,磁盘占用页也不是列表)。
|
|
2117
|
+
isDataTab ? filterBar : null,
|
|
2118
|
+
isDataTab ? React8.createElement(
|
|
1248
2119
|
"div",
|
|
1249
2120
|
{ style: S.card },
|
|
1250
|
-
loading && !snapshot ?
|
|
1251
|
-
|
|
1252
|
-
|
|
2121
|
+
loading && !snapshot ? React8.createElement("div", { style: S.empty }, t("loading")) : filtered.length === 0 ? React8.createElement(
|
|
2122
|
+
"div",
|
|
2123
|
+
{ style: S.empty },
|
|
2124
|
+
filtering ? t("emptyFiltered") : tab === "archived" ? t("emptyArchived") : t("emptyPinned")
|
|
2125
|
+
) : React8.createElement("div", { style: S.list }, nodes)
|
|
2126
|
+
) : null,
|
|
2127
|
+
isDataTab ? React8.createElement(
|
|
1253
2128
|
"div",
|
|
1254
2129
|
{ style: S.rowLine },
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
2130
|
+
React8.createElement(
|
|
2131
|
+
import_dsh_client_ui_primitives6.Button,
|
|
2132
|
+
{
|
|
2133
|
+
size: "sm",
|
|
2134
|
+
variant: "ghost",
|
|
2135
|
+
disabled: busy || rows.length === 0,
|
|
2136
|
+
onClick: () => setSelected(selected.length === rows.length ? [] : rows.map((row) => row.sessionId))
|
|
2137
|
+
},
|
|
2138
|
+
selected.length === rows.length && rows.length > 0 ? t("selectNone") : t("selectAll")
|
|
2139
|
+
),
|
|
2140
|
+
batch.map(
|
|
2141
|
+
(action) => React8.createElement(
|
|
2142
|
+
import_dsh_client_ui_primitives6.Button,
|
|
2143
|
+
{
|
|
2144
|
+
key: action.label,
|
|
2145
|
+
size: "sm",
|
|
2146
|
+
variant: action.variant || "ghost",
|
|
2147
|
+
disabled: busy || action.disabled === true,
|
|
2148
|
+
onClick: action.run
|
|
2149
|
+
},
|
|
2150
|
+
action.label
|
|
2151
|
+
)
|
|
2152
|
+
),
|
|
2153
|
+
busy ? React8.createElement("span", { style: S.footer }, t("working")) : null
|
|
2154
|
+
) : null,
|
|
2155
|
+
isUsage ? usagePage : null,
|
|
1276
2156
|
isConfig ? configPage : null,
|
|
1277
|
-
|
|
2157
|
+
isDataTab ? React8.createElement(
|
|
1278
2158
|
"div",
|
|
1279
2159
|
{ style: S.footer },
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
2160
|
+
React8.createElement("span", null, text(t, "total", { n: rows.length, size: formatBytes(totalBytes) })),
|
|
2161
|
+
// 过滤只改显示:合计那一行报的仍是整个页签的条数,这里再补一句"筛出来几条"。
|
|
2162
|
+
filtering ? React8.createElement("span", null, text(t, "filtered", { n: filtered.length, total: rows.length })) : null,
|
|
2163
|
+
grouping && groups.length > 0 ? React8.createElement("span", null, text(t, "workspaces", { n: groups.length })) : null,
|
|
2164
|
+
selected.length > 0 ? React8.createElement("span", null, text(t, "selected", { n: selected.length })) : null,
|
|
2165
|
+
snapshot ? React8.createElement("span", { style: S.mono, title: snapshot.sessionsDir }, snapshot.sessionsDir) : null
|
|
2166
|
+
) : null
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
// src/client/components/toast-host.ts
|
|
2171
|
+
var React9 = __toESM(require("react"), 1);
|
|
2172
|
+
var import_dsh_client_ui_primitives7 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
2173
|
+
var HOLD_MS = 5e3;
|
|
2174
|
+
function ToastHost(props) {
|
|
2175
|
+
const notice = useStore(toastStore);
|
|
2176
|
+
if (notice === null) return null;
|
|
2177
|
+
return React9.createElement(
|
|
2178
|
+
import_dsh_client_ui_primitives7.Toast,
|
|
2179
|
+
{
|
|
2180
|
+
text: notice.message,
|
|
2181
|
+
holdMs: HOLD_MS,
|
|
2182
|
+
onDone: dismissToast
|
|
2183
|
+
},
|
|
2184
|
+
`session-curator-toast-${notice.seq}`
|
|
1285
2185
|
);
|
|
1286
2186
|
}
|
|
1287
2187
|
|
|
2188
|
+
// src/client/components/delete-row-button.ts
|
|
2189
|
+
var React10 = __toESM(require("react"), 1);
|
|
2190
|
+
var import_dsh_client_ui_primitives8 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
2191
|
+
function DeleteRowButton(props) {
|
|
2192
|
+
const [hovered, setHovered] = React10.useState(false);
|
|
2193
|
+
const title = props.displayTitle || props.sessionId;
|
|
2194
|
+
const label = props.t("deleteSession");
|
|
2195
|
+
return React10.createElement(import_dsh_client_ui_primitives8.Tooltip, {
|
|
2196
|
+
label,
|
|
2197
|
+
side: "bottom",
|
|
2198
|
+
align: "end",
|
|
2199
|
+
delayMs: 500,
|
|
2200
|
+
children: React10.createElement(
|
|
2201
|
+
"button",
|
|
2202
|
+
{
|
|
2203
|
+
type: "button",
|
|
2204
|
+
style: hovered ? S.rowIconButtonHover : S.rowIconButton,
|
|
2205
|
+
"aria-label": `${title} — ${label}`,
|
|
2206
|
+
onPointerEnter: () => setHovered(true),
|
|
2207
|
+
onPointerLeave: () => setHovered(false),
|
|
2208
|
+
onClick: () => requestDelete([props.sessionId], title)
|
|
2209
|
+
},
|
|
2210
|
+
React10.createElement(import_dsh_client_ui_primitives8.IconTrashOutlineRegular, { size: 14 })
|
|
2211
|
+
)
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
|
|
1288
2215
|
// src/client/index.ts
|
|
1289
2216
|
var MENU_ID = "session-curator.delete";
|
|
1290
2217
|
var OVERLAY_ID = "session-curator.dialog";
|
|
2218
|
+
var TOAST_ID = "session-curator.toast";
|
|
2219
|
+
var HOVER_ID = "session-curator.hover";
|
|
2220
|
+
var ROW_ACTION_ID = "session-curator.delete-row";
|
|
1291
2221
|
var SECTION_ID = "session-curator";
|
|
1292
2222
|
var NAV_SECTION_ID = "archived-sessions";
|
|
1293
2223
|
function sectionIdFor(ctx) {
|
|
@@ -1311,36 +2241,89 @@ function apply(ctx) {
|
|
|
1311
2241
|
if (ctx.logger) ctx.logger.warn(`[session-curator] slot ${slot} 注册失败(${label})`);
|
|
1312
2242
|
}
|
|
1313
2243
|
};
|
|
1314
|
-
contribute(
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
2244
|
+
contribute(
|
|
2245
|
+
"sidebar.workspaces.session.menu.item",
|
|
2246
|
+
{
|
|
2247
|
+
name: "sidebar.workspaces.session.menu.item",
|
|
2248
|
+
id: MENU_ID,
|
|
2249
|
+
order: 500,
|
|
2250
|
+
label: () => t("deleteSession"),
|
|
2251
|
+
locale: NS
|
|
2252
|
+
},
|
|
2253
|
+
(props) => {
|
|
2254
|
+
const [, setMenuOpen] = props.useMenuOpenState();
|
|
2255
|
+
return React11.createElement(
|
|
2256
|
+
import_dsh_client_ui_primitives9.MenuItemButton,
|
|
2257
|
+
{
|
|
2258
|
+
danger: true,
|
|
2259
|
+
separatorBefore: true,
|
|
2260
|
+
onSelect: () => {
|
|
2261
|
+
setMenuOpen(false);
|
|
2262
|
+
requestDelete([props.sessionId], props.displayTitle || props.sessionId);
|
|
2263
|
+
}
|
|
2264
|
+
},
|
|
2265
|
+
t("deleteSession")
|
|
2266
|
+
);
|
|
2267
|
+
},
|
|
2268
|
+
"menu item"
|
|
2269
|
+
);
|
|
2270
|
+
contribute(
|
|
2271
|
+
"shell.overlay",
|
|
2272
|
+
{
|
|
2273
|
+
name: "shell.overlay",
|
|
2274
|
+
id: OVERLAY_ID,
|
|
2275
|
+
order: 60,
|
|
2276
|
+
locale: NS
|
|
2277
|
+
},
|
|
2278
|
+
DialogHost,
|
|
2279
|
+
"dialog host"
|
|
2280
|
+
);
|
|
2281
|
+
contribute(
|
|
2282
|
+
"settings.section",
|
|
2283
|
+
{
|
|
2284
|
+
name: "settings.section",
|
|
2285
|
+
id: sectionIdFor(ctx),
|
|
2286
|
+
order: 45,
|
|
2287
|
+
label: () => t("sectionLabel"),
|
|
2288
|
+
locale: NS
|
|
2289
|
+
},
|
|
2290
|
+
SessionManagerPanel,
|
|
2291
|
+
"settings section"
|
|
2292
|
+
);
|
|
2293
|
+
contribute(
|
|
2294
|
+
"sidebar.session.row.hover",
|
|
2295
|
+
{
|
|
2296
|
+
name: "sidebar.session.row.hover",
|
|
2297
|
+
id: HOVER_ID,
|
|
2298
|
+
// 官方 ui-schedule 的任务列表在这个槽位是 order 10;我们排在它后面。
|
|
2299
|
+
order: 50,
|
|
2300
|
+
locale: NS
|
|
2301
|
+
},
|
|
2302
|
+
SessionHover,
|
|
2303
|
+
"hover card"
|
|
2304
|
+
);
|
|
2305
|
+
contribute(
|
|
2306
|
+
"sidebar.workspaces.session.row.action",
|
|
2307
|
+
{
|
|
2308
|
+
name: "sidebar.workspaces.session.row.action",
|
|
2309
|
+
id: ROW_ACTION_ID,
|
|
2310
|
+
order: 500,
|
|
2311
|
+
locale: NS
|
|
2312
|
+
},
|
|
2313
|
+
DeleteRowButton,
|
|
2314
|
+
"row action"
|
|
2315
|
+
);
|
|
2316
|
+
contribute(
|
|
2317
|
+
"shell.overlay",
|
|
2318
|
+
{
|
|
2319
|
+
name: "shell.overlay",
|
|
2320
|
+
id: TOAST_ID,
|
|
2321
|
+
order: 70,
|
|
2322
|
+
locale: NS
|
|
2323
|
+
},
|
|
2324
|
+
ToastHost,
|
|
2325
|
+
"toast host"
|
|
2326
|
+
);
|
|
1344
2327
|
}
|
|
1345
2328
|
})(module, module.exports, require);
|
|
1346
2329
|
return module.exports;
|