@klarkxy/dsh-session-manager 0.1.0-rc.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -6
- package/README.zh-CN.md +26 -6
- package/icon.svg +18 -5
- package/lib/client.inner.cjs +528 -426
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +528 -426
- package/lib/index.js +1119 -95
- package/lib/index.js.map +1 -1
- package/package.json +11 -4
package/lib/client.inner.cjs
CHANGED
|
@@ -2,6 +2,15 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
3
3
|
let react = require("react");
|
|
4
4
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
|
+
//#region src/contracts.ts
|
|
6
|
+
const COPY_THREAD_MENU_ID = "@klarkxy/dsh-session-manager.copy-thread-id";
|
|
7
|
+
const COPY_THREAD_SHORTCUT_ID = "session-manager.copy-thread-id";
|
|
8
|
+
const TEMP_WORKSPACE_CHANNEL = "/dsh-session-manager";
|
|
9
|
+
const TEMP_WORKSPACE_FLOW_SLOT = "session-manager.workspace.directoryFlow";
|
|
10
|
+
const USER_ACTIONS_SLOT = "conversation.chat.user-actions";
|
|
11
|
+
const USER_ACTIONS_ID = "@klarkxy/dsh-session-manager.user-actions";
|
|
12
|
+
const DELETE_DIALOG_ID = "@klarkxy/dsh-session-manager.delete-chat";
|
|
13
|
+
//#endregion
|
|
5
14
|
//#region src/copy-action.ts
|
|
6
15
|
const COPY_THREAD_LABEL = {
|
|
7
16
|
zh: "复制线程 ID",
|
|
@@ -11,6 +20,14 @@ const COPY_THREAD_FAILED = {
|
|
|
11
20
|
zh: "无法复制线程 ID",
|
|
12
21
|
en: "Could not copy the thread ID"
|
|
13
22
|
};
|
|
23
|
+
const COPY_THREAD_COPIED = {
|
|
24
|
+
zh: "已复制线程 ID",
|
|
25
|
+
en: "Thread ID copied"
|
|
26
|
+
};
|
|
27
|
+
const COPY_THREAD_NO_SESSION = {
|
|
28
|
+
zh: "请先打开一个会话",
|
|
29
|
+
en: "Open a session first"
|
|
30
|
+
};
|
|
14
31
|
function copyLocale(active) {
|
|
15
32
|
return active?.toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
16
33
|
}
|
|
@@ -19,49 +36,138 @@ async function copyThreadId(sessionId, writeText) {
|
|
|
19
36
|
await writeText(sessionId);
|
|
20
37
|
}
|
|
21
38
|
//#endregion
|
|
22
|
-
//#region src/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
//#region src/copy-shortcut.tsx
|
|
40
|
+
async function writeClipboard(value) {
|
|
41
|
+
const clipboard = globalThis.navigator?.clipboard;
|
|
42
|
+
if (clipboard === void 0) throw new Error("clipboard unavailable");
|
|
43
|
+
await clipboard.writeText(value);
|
|
44
|
+
}
|
|
45
|
+
/** Uses the same mainView reference as ui-workspace/src/client/shortcuts.ts.
|
|
46
|
+
* The host registry owns key dispatch, remapping, conflicts, IME and repeat guards.
|
|
47
|
+
*/
|
|
48
|
+
function CopyThreadShortcutNotice({ shortcuts, list, locale, currentLocale, writeText = writeClipboard }) {
|
|
49
|
+
const [notice, setNotice] = (0, react.useState)(null);
|
|
50
|
+
const sequence = (0, react.useRef)(0);
|
|
51
|
+
(0, react.useEffect)(() => {
|
|
52
|
+
if (shortcuts === void 0) return;
|
|
53
|
+
let active = true;
|
|
54
|
+
const notify = (copied) => {
|
|
55
|
+
if (active) setNotice({
|
|
56
|
+
copied,
|
|
57
|
+
seq: ++sequence.current
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
const binding = {
|
|
61
|
+
code: "KeyC",
|
|
62
|
+
modifiers: ["primary", "alt"]
|
|
63
|
+
};
|
|
64
|
+
const stop = shortcuts.register({
|
|
65
|
+
id: COPY_THREAD_SHORTCUT_ID,
|
|
66
|
+
label: () => COPY_THREAD_LABEL[copyLocale(currentLocale())],
|
|
67
|
+
aliases: [
|
|
68
|
+
"copy thread id",
|
|
69
|
+
"copy session id",
|
|
70
|
+
"复制线程 ID"
|
|
71
|
+
],
|
|
72
|
+
defaults: {
|
|
73
|
+
"desktop:windows": binding,
|
|
74
|
+
"desktop:macos": binding,
|
|
75
|
+
"web:windows": binding,
|
|
76
|
+
"web:macos": binding
|
|
77
|
+
},
|
|
78
|
+
regions: ["page", "editable"],
|
|
79
|
+
modals: [],
|
|
80
|
+
resolve(context) {
|
|
81
|
+
const target = Object.values(list?.getSnapshot().byId ?? {}).find((row) => (row?.retainedBy?.mainView ?? 0) > 0);
|
|
82
|
+
if (target === void 0 || context.modal !== null || context.region === "terminal") return {
|
|
83
|
+
status: "blocked",
|
|
84
|
+
reason: COPY_THREAD_NO_SESSION[copyLocale(currentLocale())]
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
status: "handled",
|
|
88
|
+
run() {
|
|
89
|
+
copyThreadId(target.id, writeText).then(() => notify(true), () => notify(false));
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return () => {
|
|
95
|
+
active = false;
|
|
96
|
+
stop();
|
|
97
|
+
};
|
|
98
|
+
}, [
|
|
99
|
+
shortcuts,
|
|
100
|
+
list,
|
|
101
|
+
currentLocale,
|
|
102
|
+
writeText
|
|
103
|
+
]);
|
|
104
|
+
return notice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
105
|
+
text: (notice.copied ? COPY_THREAD_COPIED : COPY_THREAD_FAILED)[locale],
|
|
106
|
+
tone: notice.copied ? "success" : void 0,
|
|
107
|
+
onDone: () => setNotice(null)
|
|
108
|
+
}, notice.seq);
|
|
109
|
+
}
|
|
29
110
|
//#endregion
|
|
30
|
-
//#region src/
|
|
111
|
+
//#region src/context-menu.ts
|
|
31
112
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
113
|
+
* Right-click forwarding for the sidebar browser: a `contextmenu` gesture on a
|
|
114
|
+
* session or workspace row clicks that row's native "..." trigger, so the
|
|
115
|
+
* host's own menu — including every `sidebar.workspaces.session.menu.item`
|
|
116
|
+
* slot contribution, this plugin's rows included — opens exactly as a
|
|
117
|
+
* left-click on the trigger would. The host owns the menu; this module only
|
|
118
|
+
* relays the gesture. No host code registers a contextmenu listener, so the
|
|
119
|
+
* capture-phase relay does not fight an existing handler.
|
|
120
|
+
*
|
|
121
|
+
* DOM contract (dsh-client-ui-workspace 0.2.0-rc.2, lib/client.js):
|
|
122
|
+
* - Rows are `div[role="treeitem"][data-row-key="session:<id>"]` or
|
|
123
|
+
* `data-row-key="workspace:<id>"` inside `div[role="tree"]`. Overflow and
|
|
124
|
+
* empty rows carry other prefixes, search rows carry no `data-row-key`, and
|
|
125
|
+
* AnimatedRows exit clones have the attribute removed — all are ignored by
|
|
126
|
+
* the prefix match. (client.js:1287, 1590-1596, 1729, 1842-1846)
|
|
127
|
+
* - The trigger is the first `button[type="button"]` inside the row's trailing
|
|
128
|
+
* `span` whose CSS-module class ends in `_rowActions`. It stays mounted —
|
|
129
|
+
* CSS `display:none` until hover or menu-open — so a programmatic click
|
|
130
|
+
* toggles the menu. Blank new-session rows and the ungrouped bucket render
|
|
131
|
+
* no trigger; right-click does nothing there. (client.js:1061, 1314-1340,
|
|
132
|
+
* 1640-1660)
|
|
133
|
+
* - Class names carry a per-build hash prefix and the trigger's aria-label is
|
|
134
|
+
* localized; only `data-row-key` and the structural suffix are stable.
|
|
135
|
+
* - The opened menu portals to `document.body`, outside the tree, so a
|
|
136
|
+
* right-click on a menu row never re-enters this relay.
|
|
34
137
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
138
|
+
const ROW_SELECTOR = "[data-row-key^=\"session:\"], [data-row-key^=\"workspace:\"]";
|
|
139
|
+
const TRIGGER_SELECTOR = "span[class*=\"rowActions\"] button[type=\"button\"]";
|
|
140
|
+
/** Resolve the native "..." trigger of the row containing `target`, if any. */
|
|
141
|
+
function findRowMenuTrigger(target) {
|
|
142
|
+
if (typeof target.closest !== "function") return null;
|
|
143
|
+
const row = target.closest(ROW_SELECTOR);
|
|
144
|
+
if (row === null || row.closest("[role=\"tree\"]") === null) return null;
|
|
145
|
+
const trigger = row.querySelector(TRIGGER_SELECTOR);
|
|
146
|
+
if (trigger === null || typeof trigger.click !== "function") return null;
|
|
147
|
+
return trigger;
|
|
43
148
|
}
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Forward one `contextmenu` event to the row's native menu trigger.
|
|
151
|
+
* Returns true when the gesture was relayed (and the default suppressed).
|
|
152
|
+
*/
|
|
153
|
+
function forwardContextMenu(event) {
|
|
154
|
+
const target = event.target;
|
|
155
|
+
if (target === null || typeof target.closest !== "function") return false;
|
|
156
|
+
const trigger = findRowMenuTrigger(target);
|
|
157
|
+
if (trigger === null) return false;
|
|
158
|
+
event.preventDefault();
|
|
159
|
+
trigger.click();
|
|
160
|
+
return true;
|
|
49
161
|
}
|
|
50
|
-
/**
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
minute: "2-digit"
|
|
60
|
-
}).format(date);
|
|
61
|
-
return new Intl.DateTimeFormat(tag, {
|
|
62
|
-
month: "short",
|
|
63
|
-
day: "numeric"
|
|
64
|
-
}).format(date);
|
|
162
|
+
/** Capture-phase relay on the document; the returned disposer uninstalls it. */
|
|
163
|
+
function installContextMenuForwarding(doc) {
|
|
164
|
+
const listener = (event) => {
|
|
165
|
+
forwardContextMenu(event);
|
|
166
|
+
};
|
|
167
|
+
doc.addEventListener("contextmenu", listener, true);
|
|
168
|
+
return () => {
|
|
169
|
+
doc.removeEventListener("contextmenu", listener, true);
|
|
170
|
+
};
|
|
65
171
|
}
|
|
66
172
|
Object.values({
|
|
67
173
|
/** Surfaces, from the page up to the topmost nested card. */
|
|
@@ -360,7 +466,7 @@ ${scope} {
|
|
|
360
466
|
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
361
467
|
border-radius: var(--dsw-radius-md);
|
|
362
468
|
background: var(--dsw-alias-bg-layer-1);
|
|
363
|
-
font-family: var(--ds-font-family-code
|
|
469
|
+
font-family: var(--ds-font-family-code);
|
|
364
470
|
font-size: 12px;
|
|
365
471
|
line-height: 20px;
|
|
366
472
|
color: var(--dsw-alias-label-secondary);
|
|
@@ -438,60 +544,9 @@ ${scope} {
|
|
|
438
544
|
//#endregion
|
|
439
545
|
//#region src/styles.ts
|
|
440
546
|
const FREE_CHAT_ROOT = "dsh-free-chat";
|
|
441
|
-
/** Host tokens and official scaffolding.
|
|
547
|
+
/** Host tokens and official scaffolding. Only the edit draft, the inline action row,
|
|
548
|
+
* and the alert line survive the panel removal; the modals reuse the root class. */
|
|
442
549
|
const freeChatCss = `${officialUiCss(FREE_CHAT_ROOT)}
|
|
443
|
-
.${FREE_CHAT_ROOT}.dsh-free-panel {
|
|
444
|
-
display: flex;
|
|
445
|
-
flex-direction: column;
|
|
446
|
-
gap: 12px;
|
|
447
|
-
min-width: 0;
|
|
448
|
-
height: 100%;
|
|
449
|
-
padding: 16px;
|
|
450
|
-
background: var(--dsw-alias-bg-base);
|
|
451
|
-
}
|
|
452
|
-
.${FREE_CHAT_ROOT} .dsh-free-head {
|
|
453
|
-
display: flex;
|
|
454
|
-
align-items: center;
|
|
455
|
-
gap: 12px;
|
|
456
|
-
min-width: 0;
|
|
457
|
-
}
|
|
458
|
-
.${FREE_CHAT_ROOT} .dsh-free-head .dsh-ui-title { flex: 1; min-width: 0; }
|
|
459
|
-
.${FREE_CHAT_ROOT} .dsh-free-search { width: 100%; }
|
|
460
|
-
.${FREE_CHAT_ROOT} .dsh-free-line {
|
|
461
|
-
display: flex;
|
|
462
|
-
align-items: center;
|
|
463
|
-
gap: 4px;
|
|
464
|
-
min-width: 0;
|
|
465
|
-
}
|
|
466
|
-
.${FREE_CHAT_ROOT} .dsh-free-open {
|
|
467
|
-
display: flex;
|
|
468
|
-
align-items: center;
|
|
469
|
-
gap: 8px;
|
|
470
|
-
flex: 1;
|
|
471
|
-
min-width: 0;
|
|
472
|
-
min-height: 36px;
|
|
473
|
-
padding: 4px 8px;
|
|
474
|
-
border: 0;
|
|
475
|
-
border-radius: var(--dsw-radius-sm);
|
|
476
|
-
background: none;
|
|
477
|
-
color: inherit;
|
|
478
|
-
font: inherit;
|
|
479
|
-
text-align: left;
|
|
480
|
-
}
|
|
481
|
-
.${FREE_CHAT_ROOT} .dsh-free-open:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
482
|
-
.${FREE_CHAT_ROOT} .dsh-free-title {
|
|
483
|
-
flex: 1;
|
|
484
|
-
min-width: 0;
|
|
485
|
-
overflow: hidden;
|
|
486
|
-
text-overflow: ellipsis;
|
|
487
|
-
white-space: nowrap;
|
|
488
|
-
}
|
|
489
|
-
.${FREE_CHAT_ROOT} .dsh-free-time {
|
|
490
|
-
flex: none;
|
|
491
|
-
color: var(--dsw-alias-label-tertiary);
|
|
492
|
-
font-size: 12px;
|
|
493
|
-
line-height: 18px;
|
|
494
|
-
}
|
|
495
550
|
.${FREE_CHAT_ROOT} .dsh-free-draft {
|
|
496
551
|
box-sizing: border-box;
|
|
497
552
|
width: 100%;
|
|
@@ -513,24 +568,6 @@ const freeChatCss = `${officialUiCss(FREE_CHAT_ROOT)}
|
|
|
513
568
|
min-width: 0;
|
|
514
569
|
}
|
|
515
570
|
.${FREE_CHAT_ROOT} .dsh-free-alert { margin: 0; }
|
|
516
|
-
.${FREE_CHAT_ROOT} .dsh-free-sr {
|
|
517
|
-
position: absolute;
|
|
518
|
-
width: 1px;
|
|
519
|
-
height: 1px;
|
|
520
|
-
padding: 0;
|
|
521
|
-
margin: -1px;
|
|
522
|
-
overflow: hidden;
|
|
523
|
-
clip: rect(0, 0, 0, 0);
|
|
524
|
-
white-space: nowrap;
|
|
525
|
-
border: 0;
|
|
526
|
-
}
|
|
527
|
-
@media (max-width: 640px) {
|
|
528
|
-
.${FREE_CHAT_ROOT}.dsh-free-panel { padding: 12px; }
|
|
529
|
-
.${FREE_CHAT_ROOT} .dsh-free-head { flex-wrap: wrap; }
|
|
530
|
-
}
|
|
531
|
-
@media (prefers-reduced-motion: reduce) {
|
|
532
|
-
.${FREE_CHAT_ROOT} .dsh-free-open { transition: none; }
|
|
533
|
-
}
|
|
534
571
|
`;
|
|
535
572
|
//#endregion
|
|
536
573
|
//#region src/ui-copy.ts
|
|
@@ -556,18 +593,9 @@ const reasons = {
|
|
|
556
593
|
};
|
|
557
594
|
const text = {
|
|
558
595
|
zh: {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
empty: "还没有自由聊天。",
|
|
562
|
-
loading: "正在读取聊天",
|
|
563
|
-
noMatch: "没有匹配的聊天。",
|
|
564
|
-
listError: "聊天列表没有读出来。",
|
|
596
|
+
tempWorkspace: "临时工作区",
|
|
597
|
+
createFailed: "无法创建临时工作区",
|
|
565
598
|
retryList: "重试",
|
|
566
|
-
search: "搜索聊天",
|
|
567
|
-
running: "运行中",
|
|
568
|
-
idle: "空闲",
|
|
569
|
-
menu: "聊天操作",
|
|
570
|
-
fork: "分叉",
|
|
571
599
|
deleteChat: "删除聊天",
|
|
572
600
|
deleting: "正在删除",
|
|
573
601
|
deleteFree: "将删除这个聊天及其拥有的文件。",
|
|
@@ -581,18 +609,9 @@ const text = {
|
|
|
581
609
|
draft: "消息文本"
|
|
582
610
|
},
|
|
583
611
|
en: {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
empty: "No free chats yet.",
|
|
587
|
-
loading: "Reading chats",
|
|
588
|
-
noMatch: "No matching chats.",
|
|
589
|
-
listError: "The chat list could not be read.",
|
|
612
|
+
tempWorkspace: "Temporary Workspace",
|
|
613
|
+
createFailed: "Could not create the temporary workspace",
|
|
590
614
|
retryList: "Retry",
|
|
591
|
-
search: "Search chats",
|
|
592
|
-
running: "Running",
|
|
593
|
-
idle: "Idle",
|
|
594
|
-
menu: "Chat actions",
|
|
595
|
-
fork: "Fork",
|
|
596
615
|
deleteChat: "Delete chat",
|
|
597
616
|
deleting: "Deleting",
|
|
598
617
|
deleteFree: "This deletes this chat and its owned files.",
|
|
@@ -610,7 +629,7 @@ function uiText(locale, key) {
|
|
|
610
629
|
return text[locale][key];
|
|
611
630
|
}
|
|
612
631
|
function failureReason(failure) {
|
|
613
|
-
if (failure.code === "session/lifecycle-unavailable") return "unsupported";
|
|
632
|
+
if (failure.code === "session/lifecycle-unavailable" || failure.code === "session/fork-unavailable") return "unsupported";
|
|
614
633
|
if (failure.code === "session/retry-revision" || failure.code === "session/branch-moved" || failure.code === "session/retry-input" || failure.code === "session/retry-conflict") return "stale";
|
|
615
634
|
if (failure.code === "session/delete-pending") return "cleanup";
|
|
616
635
|
if (failure.code === "session/agent-busy" && failure.reason === "free-session-work-active") return "running";
|
|
@@ -641,179 +660,33 @@ function FreeChatIcon({ size }) {
|
|
|
641
660
|
})
|
|
642
661
|
});
|
|
643
662
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
const
|
|
648
|
-
(0
|
|
649
|
-
if (snapshot.focusNonce === 0) return;
|
|
650
|
-
titleRef.current?.focus();
|
|
651
|
-
}, [snapshot.focusNonce]);
|
|
652
|
-
const copy = (key) => uiText(props.locale, key);
|
|
653
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
654
|
-
className: `${FREE_CHAT_ROOT} dsh-free-panel`,
|
|
655
|
-
children: [
|
|
656
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: freeChatCss }),
|
|
657
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
658
|
-
className: "dsh-free-head",
|
|
659
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
660
|
-
ref: titleRef,
|
|
661
|
-
tabIndex: -1,
|
|
662
|
-
className: "dsh-ui-title",
|
|
663
|
-
children: copy("panel")
|
|
664
|
-
}), snapshot.controls.create ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
665
|
-
variant: "primary",
|
|
666
|
-
size: "sm",
|
|
667
|
-
disabled: snapshot.creating,
|
|
668
|
-
onClick: () => {
|
|
669
|
-
props.model.create();
|
|
670
|
-
},
|
|
671
|
-
children: copy("newChat")
|
|
672
|
-
}) : null]
|
|
673
|
-
}),
|
|
674
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
675
|
-
id: searchId,
|
|
676
|
-
className: "dsh-free-search",
|
|
677
|
-
type: "search",
|
|
678
|
-
value: snapshot.query,
|
|
679
|
-
"aria-label": copy("search"),
|
|
680
|
-
onChange: (event) => {
|
|
681
|
-
props.model.setQuery(event.target.value);
|
|
682
|
-
}
|
|
683
|
-
}),
|
|
684
|
-
snapshot.notice ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
685
|
-
className: "dsh-ui-error dsh-free-alert",
|
|
686
|
-
role: "alert",
|
|
687
|
-
children: failureText(props.locale, snapshot.notice)
|
|
688
|
-
}) : null,
|
|
689
|
-
snapshot.listStatus === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
690
|
-
className: "dsh-ui-loading",
|
|
691
|
-
children: copy("loading")
|
|
692
|
-
}) : null,
|
|
693
|
-
snapshot.listStatus === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
694
|
-
className: "dsh-ui-empty",
|
|
695
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
696
|
-
role: "alert",
|
|
697
|
-
children: snapshot.listFailure?.reason && snapshot.listFailure.reason !== "unknown" ? snapshot.listFailure.reason : copy("listError")
|
|
698
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
699
|
-
size: "sm",
|
|
700
|
-
onClick: () => {
|
|
701
|
-
props.model.refreshList();
|
|
702
|
-
},
|
|
703
|
-
children: copy("retryList")
|
|
704
|
-
})]
|
|
705
|
-
}) : null,
|
|
706
|
-
snapshot.listStatus === "empty" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
707
|
-
className: "dsh-ui-empty",
|
|
708
|
-
children: copy("empty")
|
|
709
|
-
}) : null,
|
|
710
|
-
snapshot.listStatus === "nomatch" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
711
|
-
className: "dsh-ui-empty",
|
|
712
|
-
children: copy("noMatch")
|
|
713
|
-
}) : null,
|
|
714
|
-
snapshot.rows.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
715
|
-
className: "dsh-ui-list",
|
|
716
|
-
"aria-label": copy("panel"),
|
|
717
|
-
children: snapshot.rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FreeChatRow, {
|
|
718
|
-
model: props.model,
|
|
719
|
-
locale: props.locale,
|
|
720
|
-
row
|
|
721
|
-
}, row.id))
|
|
722
|
-
}) : null
|
|
723
|
-
]
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
function FreeChatRow(props) {
|
|
727
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
728
|
-
const [copyFailed, setCopyFailed] = (0, react.useState)(false);
|
|
729
|
-
const snapshot = (0, react.useSyncExternalStore)(props.model.subscribe, props.model.getSnapshot, props.model.getSnapshot);
|
|
663
|
+
/** Create failures used to surface inside the standalone panel. The panel is gone,
|
|
664
|
+
* so they get a small overlay instead; closing it dismisses the stored notice. */
|
|
665
|
+
function CreateNoticeDialog(props) {
|
|
666
|
+
const notice = (0, react.useSyncExternalStore)(props.model.subscribe, props.model.getSnapshot, props.model.getSnapshot).notice;
|
|
667
|
+
if (notice === void 0) return null;
|
|
730
668
|
const copy = (key) => uiText(props.locale, key);
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
setOpen(true);
|
|
753
|
-
});
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
setOpen(false);
|
|
757
|
-
if (id === "fork") props.model.fork(props.row.id);
|
|
758
|
-
if (id === "delete") props.model.requestDelete(props.row.id);
|
|
759
|
-
};
|
|
760
|
-
const state = props.row.running ? copy("running") : copy("idle");
|
|
761
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
762
|
-
className: "dsh-ui-list-row dsh-free-line",
|
|
763
|
-
children: [
|
|
764
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
765
|
-
type: "button",
|
|
766
|
-
className: "dsh-free-open",
|
|
767
|
-
onClick: () => {
|
|
768
|
-
props.model.open(props.row.id);
|
|
769
|
-
},
|
|
770
|
-
children: [
|
|
771
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
|
|
772
|
-
state: props.row.running ? "ongoing" : "idle",
|
|
773
|
-
size: 8
|
|
774
|
-
}),
|
|
775
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
776
|
-
className: "dsh-free-sr",
|
|
777
|
-
children: state
|
|
778
|
-
}),
|
|
779
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
780
|
-
className: "dsh-free-title",
|
|
781
|
-
children: props.row.displayTitle
|
|
782
|
-
}),
|
|
783
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
|
|
784
|
-
className: "dsh-free-time",
|
|
785
|
-
dateTime: new Date(props.row.updatedAt).toISOString(),
|
|
786
|
-
children: formatQuietTime(props.row.updatedAt, props.locale)
|
|
787
|
-
})
|
|
788
|
-
]
|
|
789
|
-
}),
|
|
790
|
-
copyFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
791
|
-
className: "dsh-ui-error dsh-free-alert",
|
|
792
|
-
role: "alert",
|
|
793
|
-
children: COPY_THREAD_FAILED[props.locale]
|
|
794
|
-
}) : null,
|
|
795
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
796
|
-
open,
|
|
797
|
-
onClose: () => {
|
|
798
|
-
setOpen(false);
|
|
799
|
-
},
|
|
800
|
-
portal: true,
|
|
801
|
-
listClassName: FREE_CHAT_ROOT,
|
|
802
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
803
|
-
size: "sm",
|
|
804
|
-
variant: "ghost",
|
|
805
|
-
"aria-label": copy("menu"),
|
|
806
|
-
"aria-haspopup": "menu",
|
|
807
|
-
"aria-expanded": open,
|
|
808
|
-
onClick: () => {
|
|
809
|
-
setOpen((value) => !value);
|
|
810
|
-
},
|
|
811
|
-
children: "···"
|
|
812
|
-
}),
|
|
813
|
-
items,
|
|
814
|
-
onSelect
|
|
815
|
-
})
|
|
816
|
-
]
|
|
669
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
670
|
+
open: true,
|
|
671
|
+
className: FREE_CHAT_ROOT,
|
|
672
|
+
title: copy("createFailed"),
|
|
673
|
+
closeLabel: copy("close"),
|
|
674
|
+
onClose: () => {
|
|
675
|
+
props.model.dismissNotice();
|
|
676
|
+
},
|
|
677
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
678
|
+
variant: "primary",
|
|
679
|
+
"data-modal-autofocus": true,
|
|
680
|
+
onClick: () => {
|
|
681
|
+
props.model.dismissNotice();
|
|
682
|
+
},
|
|
683
|
+
children: copy("close")
|
|
684
|
+
}),
|
|
685
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
686
|
+
className: "dsh-ui-error dsh-free-alert",
|
|
687
|
+
role: "alert",
|
|
688
|
+
children: failureText(props.locale, notice)
|
|
689
|
+
})
|
|
817
690
|
});
|
|
818
691
|
}
|
|
819
692
|
function DeleteChatDialog(props) {
|
|
@@ -858,10 +731,310 @@ function DeleteChatDialog(props) {
|
|
|
858
731
|
}) : null]
|
|
859
732
|
});
|
|
860
733
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
734
|
+
//#endregion
|
|
735
|
+
//#region src/temporary-picker.tsx
|
|
736
|
+
const PICKER_SLOT = "conversation.hero.workspace";
|
|
737
|
+
const DIRECTORY_SLOT = "conversation.hero.workspace.directoryFlow";
|
|
738
|
+
/** Keep the host's native/web picker implementation and its injected capabilities.
|
|
739
|
+
* Standard rc.2 offers no exported directory-flow factory. Only its simple root
|
|
740
|
+
* registration can be mirrored; more complex future registrations restore the host picker.
|
|
741
|
+
* Original entries and their ownership ledger are never changed.
|
|
742
|
+
*/
|
|
743
|
+
function installTemporaryWorkspacePicker(slots, model, createWorkspace, locale) {
|
|
744
|
+
if (typeof slots.entriesOfSlot !== "function" || typeof slots.spec !== "function" || typeof slots.subscribe !== "function") {
|
|
745
|
+
model.reportFailure(copyLocale(locale?.getSnapshot().active) === "zh" ? "当前版本无法扩展工作区菜单,已保留原菜单。" : "Could not extend the workspace menu. The original menu has been preserved.");
|
|
746
|
+
return () => {};
|
|
747
|
+
}
|
|
748
|
+
return slots.inject(PICKER_SLOT, () => {
|
|
749
|
+
let mirror, stopMirror = () => {}, restored = false;
|
|
750
|
+
const failureMessage = () => copyLocale(locale?.getSnapshot().active) === "zh" ? "当前版本无法扩展工作区菜单,已保留原菜单。" : "Could not extend the workspace menu. The original menu has been preserved.";
|
|
751
|
+
const renderPicker = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TemporaryWorkspacePicker, {
|
|
752
|
+
...props,
|
|
753
|
+
model,
|
|
754
|
+
createWorkspace,
|
|
755
|
+
locale,
|
|
756
|
+
getDirectoryFlow: () => mirror,
|
|
757
|
+
subscribeFlow: (listener) => slots.subscribe(DIRECTORY_SLOT, listener)
|
|
758
|
+
});
|
|
759
|
+
const stopPicker = slots.register({
|
|
760
|
+
name: PICKER_SLOT,
|
|
761
|
+
priority: -10,
|
|
762
|
+
locale: "workspace",
|
|
763
|
+
children: { [TEMP_WORKSPACE_FLOW_SLOT]: {
|
|
764
|
+
kind: "single",
|
|
765
|
+
scope: "root"
|
|
766
|
+
} }
|
|
767
|
+
}, renderPicker);
|
|
768
|
+
const stopErrors = slots.onEntryError?.((key, entry) => {
|
|
769
|
+
if (restored || key !== "session-manager.workspace.directoryFlow" && !(key === PICKER_SLOT && entry.component === renderPicker)) return;
|
|
770
|
+
restored = true;
|
|
771
|
+
stopMirror();
|
|
772
|
+
mirror = void 0;
|
|
773
|
+
stopPicker();
|
|
774
|
+
model.reportFailure(failureMessage());
|
|
775
|
+
}) ?? (() => {});
|
|
776
|
+
const sync = () => {
|
|
777
|
+
if (restored) return;
|
|
778
|
+
const next = slots.entriesOfSlot(DIRECTORY_SLOT)[0];
|
|
779
|
+
if (next === mirror) return;
|
|
780
|
+
stopMirror();
|
|
781
|
+
stopMirror = () => {};
|
|
782
|
+
mirror = void 0;
|
|
783
|
+
if (next === void 0) return;
|
|
784
|
+
const spec = slots.spec(DIRECTORY_SLOT);
|
|
785
|
+
if (spec?.kind !== "single" || spec.scope !== "root" || typeof next.component !== "function" || next.children !== void 0 || next.store !== void 0 || next.locale !== void 0 || next.select !== void 0) {
|
|
786
|
+
restored = true;
|
|
787
|
+
stopPicker();
|
|
788
|
+
model.reportFailure(failureMessage());
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
try {
|
|
792
|
+
stopMirror = slots.register({
|
|
793
|
+
name: TEMP_WORKSPACE_FLOW_SLOT,
|
|
794
|
+
...next.inject ? { inject: next.inject } : {}
|
|
795
|
+
}, next.component);
|
|
796
|
+
mirror = next;
|
|
797
|
+
} catch (error) {
|
|
798
|
+
restored = true;
|
|
799
|
+
stopPicker();
|
|
800
|
+
model.reportFailure(error instanceof Error ? error.message : "Could not extend the workspace picker");
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
const stopSource = slots.subscribe(DIRECTORY_SLOT, sync);
|
|
804
|
+
sync();
|
|
805
|
+
return () => {
|
|
806
|
+
restored = true;
|
|
807
|
+
stopErrors();
|
|
808
|
+
stopSource();
|
|
809
|
+
stopMirror();
|
|
810
|
+
stopPicker();
|
|
811
|
+
};
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
function TemporaryWorkspacePicker(props) {
|
|
815
|
+
const workspaces = props.useWorkspaces((snapshot) => snapshot);
|
|
816
|
+
const state = (0, react.useSyncExternalStore)(props.model.subscribe, props.model.getSnapshot, props.model.getSnapshot);
|
|
817
|
+
const source = (0, react.useSyncExternalStore)(props.subscribeFlow, props.getDirectoryFlow, props.getDirectoryFlow);
|
|
818
|
+
const available = source !== void 0;
|
|
819
|
+
const locale = copyLocale((0, react.useSyncExternalStore)((listener) => props.locale?.subscribe(listener) ?? (() => {}), () => props.locale?.getSnapshot().active ?? "en", () => "en"));
|
|
820
|
+
const [flowOpen, setFlowOpen] = (0, react.useState)(false), [adopting, setAdopting] = (0, react.useState)(false);
|
|
821
|
+
const [failure, setFailure] = (0, react.useState)();
|
|
822
|
+
const epoch = (0, react.useRef)(0);
|
|
823
|
+
(0, react.useEffect)(() => () => {
|
|
824
|
+
epoch.current++;
|
|
825
|
+
}, []);
|
|
826
|
+
(0, react.useEffect)(() => {
|
|
827
|
+
epoch.current++;
|
|
828
|
+
setFlowOpen(false);
|
|
829
|
+
setAdopting(false);
|
|
830
|
+
}, [source]);
|
|
831
|
+
const busy = flowOpen || adopting || state.creating;
|
|
832
|
+
const getAnchorRect = (0, react.useCallback)(() => props.anchorRef?.current?.getBoundingClientRect() ?? null, [props.anchorRef]);
|
|
833
|
+
const openDirectory = () => {
|
|
834
|
+
props.onClose();
|
|
835
|
+
setFailure(void 0);
|
|
836
|
+
setFlowOpen(true);
|
|
837
|
+
};
|
|
838
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
839
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
840
|
+
open: props.open,
|
|
841
|
+
anchor: null,
|
|
842
|
+
portal: true,
|
|
843
|
+
getAnchorRect,
|
|
844
|
+
selectedId: props.selectedId,
|
|
845
|
+
onClose: props.onClose,
|
|
846
|
+
items: workspaces.items.map((workspace) => ({
|
|
847
|
+
id: workspace.workspaceId,
|
|
848
|
+
label: workspace.title === "default-workspace" ? props.t("workspace.defaultName") : workspace.title,
|
|
849
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, { size: 16 }),
|
|
850
|
+
disabled: busy
|
|
851
|
+
})),
|
|
852
|
+
footer: [...available ? [{
|
|
853
|
+
id: "::add-workspace",
|
|
854
|
+
label: props.t("menu.addWorkspace"),
|
|
855
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutlineRegular, { size: 16 }),
|
|
856
|
+
disabled: busy
|
|
857
|
+
}] : [], {
|
|
858
|
+
id: "::temporary-workspace",
|
|
859
|
+
label: uiText(locale, "tempWorkspace"),
|
|
860
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FreeChatIcon, { size: 16 }),
|
|
861
|
+
disabled: busy
|
|
862
|
+
}],
|
|
863
|
+
onSelect: (id) => {
|
|
864
|
+
if (id === "::add-workspace") openDirectory();
|
|
865
|
+
else if (id === "::temporary-workspace") {
|
|
866
|
+
props.onClose();
|
|
867
|
+
props.model.create();
|
|
868
|
+
} else props.onPick(id);
|
|
869
|
+
}
|
|
870
|
+
}),
|
|
871
|
+
props.open && workspaces.phase === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
872
|
+
role: "status",
|
|
873
|
+
children: props.t("picker.loading")
|
|
874
|
+
}),
|
|
875
|
+
props.renderSlot(TEMP_WORKSPACE_FLOW_SLOT, {
|
|
876
|
+
open: flowOpen,
|
|
877
|
+
busy: adopting,
|
|
878
|
+
onCancel: () => {
|
|
879
|
+
epoch.current++;
|
|
880
|
+
setFlowOpen(false);
|
|
881
|
+
setAdopting(false);
|
|
882
|
+
},
|
|
883
|
+
onError: (message) => {
|
|
884
|
+
epoch.current++;
|
|
885
|
+
setFlowOpen(false);
|
|
886
|
+
setAdopting(false);
|
|
887
|
+
setFailure(message);
|
|
888
|
+
},
|
|
889
|
+
onPicked: (path) => {
|
|
890
|
+
if (adopting) return;
|
|
891
|
+
const ticket = ++epoch.current;
|
|
892
|
+
setAdopting(true);
|
|
893
|
+
props.createWorkspace(path).then((workspace) => {
|
|
894
|
+
if (ticket !== epoch.current) return;
|
|
895
|
+
setFlowOpen(false);
|
|
896
|
+
props.onPick(workspace.workspaceId);
|
|
897
|
+
}, (error) => {
|
|
898
|
+
if (ticket !== epoch.current) return;
|
|
899
|
+
setFlowOpen(false);
|
|
900
|
+
setFailure(error instanceof Error ? error.message : String(error));
|
|
901
|
+
}).finally(() => {
|
|
902
|
+
if (ticket === epoch.current) setAdopting(false);
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
}),
|
|
906
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
907
|
+
open: failure !== void 0,
|
|
908
|
+
title: props.t("folderError.title"),
|
|
909
|
+
closeLabel: props.t("close"),
|
|
910
|
+
onClose: () => setFailure(void 0),
|
|
911
|
+
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, {
|
|
912
|
+
variant: "outline",
|
|
913
|
+
onClick: () => setFailure(void 0),
|
|
914
|
+
children: props.t("cancel")
|
|
915
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
916
|
+
variant: "primary",
|
|
917
|
+
disabled: !available,
|
|
918
|
+
onClick: openDirectory,
|
|
919
|
+
children: props.t("folderError.retry")
|
|
920
|
+
})] }),
|
|
921
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
922
|
+
role: "alert",
|
|
923
|
+
children: failure
|
|
924
|
+
})
|
|
925
|
+
})
|
|
926
|
+
] });
|
|
927
|
+
}
|
|
928
|
+
function TemporaryWorkspaceNotice({ model, locale }) {
|
|
929
|
+
const state = (0, react.useSyncExternalStore)(model.subscribe, model.getSnapshot, model.getSnapshot);
|
|
930
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
931
|
+
open: state.failure !== void 0,
|
|
932
|
+
title: uiText(locale, "createFailed"),
|
|
933
|
+
closeLabel: uiText(locale, "close"),
|
|
934
|
+
onClose: model.dismissFailure,
|
|
935
|
+
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, {
|
|
936
|
+
variant: "outline",
|
|
937
|
+
onClick: model.dismissFailure,
|
|
938
|
+
children: uiText(locale, "close")
|
|
939
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
940
|
+
variant: "primary",
|
|
941
|
+
disabled: state.creating,
|
|
942
|
+
onClick: () => {
|
|
943
|
+
model.create();
|
|
944
|
+
},
|
|
945
|
+
children: uiText(locale, "retry")
|
|
946
|
+
})] }),
|
|
947
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
948
|
+
role: "alert",
|
|
949
|
+
children: state.failure
|
|
950
|
+
})
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
//#endregion
|
|
954
|
+
//#region src/temporary-workspace.ts
|
|
955
|
+
function createTemporaryWorkspaceModel(host, ids = () => crypto.randomUUID(), label = () => "Temporary Workspace") {
|
|
956
|
+
const lifetime = new AbortController(), listeners = /* @__PURE__ */ new Set();
|
|
957
|
+
let snapshot = { creating: false };
|
|
958
|
+
let pending;
|
|
959
|
+
const publish = (next) => {
|
|
960
|
+
if (lifetime.signal.aborted) return;
|
|
961
|
+
snapshot = next;
|
|
962
|
+
for (const listener of listeners) listener();
|
|
963
|
+
};
|
|
964
|
+
return {
|
|
965
|
+
getSnapshot: () => snapshot,
|
|
966
|
+
subscribe(listener) {
|
|
967
|
+
listeners.add(listener);
|
|
968
|
+
return () => {
|
|
969
|
+
listeners.delete(listener);
|
|
970
|
+
};
|
|
971
|
+
},
|
|
972
|
+
dismissFailure() {
|
|
973
|
+
publish({ creating: snapshot.creating });
|
|
974
|
+
},
|
|
975
|
+
reportFailure(failure) {
|
|
976
|
+
publish({
|
|
977
|
+
...snapshot,
|
|
978
|
+
failure
|
|
979
|
+
});
|
|
980
|
+
},
|
|
981
|
+
async create() {
|
|
982
|
+
if (snapshot.creating || lifetime.signal.aborted) return;
|
|
983
|
+
const signal = AbortSignal.any([host.beginNavigation(), lifetime.signal]);
|
|
984
|
+
const action = pending ??= {
|
|
985
|
+
requestId: ids(),
|
|
986
|
+
title: label()
|
|
987
|
+
};
|
|
988
|
+
let opening = false;
|
|
989
|
+
publish({ creating: true });
|
|
990
|
+
try {
|
|
991
|
+
signal.throwIfAborted();
|
|
992
|
+
if (action.path === void 0) {
|
|
993
|
+
const allocated = await host.allocate(action.requestId, signal);
|
|
994
|
+
if (allocated.requestId !== action.requestId || !allocated.path) throw new Error("Invalid temporary workspace response");
|
|
995
|
+
action.path = allocated.path;
|
|
996
|
+
}
|
|
997
|
+
signal.throwIfAborted();
|
|
998
|
+
if (action.workspaceId === void 0) action.workspaceId = (await host.createWorkspace(action.path)).workspaceId;
|
|
999
|
+
signal.throwIfAborted();
|
|
1000
|
+
if (!action.named) {
|
|
1001
|
+
await host.renameWorkspace(action.workspaceId, `${action.title} ${action.requestId.slice(0, 8)}`);
|
|
1002
|
+
action.named = true;
|
|
1003
|
+
}
|
|
1004
|
+
signal.throwIfAborted();
|
|
1005
|
+
opening = true;
|
|
1006
|
+
await host.openWorkspace(action.workspaceId, () => lifetime.signal.throwIfAborted());
|
|
1007
|
+
if (!lifetime.signal.aborted) pending = void 0;
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
if (!lifetime.signal.aborted && (opening ? !(error instanceof Error && error.name === "AbortError") : !signal.aborted)) publish({
|
|
1010
|
+
creating: false,
|
|
1011
|
+
failure: error instanceof Error ? error.message : "Could not create the temporary workspace"
|
|
1012
|
+
});
|
|
1013
|
+
} finally {
|
|
1014
|
+
publish({
|
|
1015
|
+
...snapshot,
|
|
1016
|
+
creating: false
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
},
|
|
1020
|
+
cancel() {
|
|
1021
|
+
lifetime.abort();
|
|
1022
|
+
listeners.clear();
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
function bindTemporaryWorkspaceHost(ctx) {
|
|
1027
|
+
return {
|
|
1028
|
+
beginNavigation: () => ctx.layout.beginNavigation(),
|
|
1029
|
+
async allocate(requestId, signal) {
|
|
1030
|
+
const response = await ctx.connection.rpc.call(TEMP_WORKSPACE_CHANNEL, "temporary.allocate", { requestId }, signal);
|
|
1031
|
+
if (response?.ok !== true || typeof response.value?.path !== "string" || typeof response.value.requestId !== "string") throw new Error(response?.error?.message ?? "Could not allocate the temporary workspace");
|
|
1032
|
+
return response.value;
|
|
1033
|
+
},
|
|
1034
|
+
createWorkspace: (path) => ctx.workspaces.create({ path }),
|
|
1035
|
+
renameWorkspace: (workspaceId, title) => ctx.workspaces.rename(workspaceId, title),
|
|
1036
|
+
openWorkspace: (workspaceId, beforeOpen) => ctx.uiWorkspace.openWorkspace(workspaceId, beforeOpen)
|
|
1037
|
+
};
|
|
865
1038
|
}
|
|
866
1039
|
//#endregion
|
|
867
1040
|
//#region src/session-actions.ts
|
|
@@ -942,7 +1115,6 @@ async function readCapabilities(host) {
|
|
|
942
1115
|
function controlsFor(capabilities, ready, host) {
|
|
943
1116
|
return {
|
|
944
1117
|
create: ready && capabilities.freeSession && typeof host.create === "function",
|
|
945
|
-
fork: ready && capabilities.freeSession && (typeof host.forkSession === "function" || typeof host.fork === "function"),
|
|
946
1118
|
delete: ready && capabilities.deleteSession && typeof host.deleteSession === "function",
|
|
947
1119
|
retry: ready && capabilities.retry && typeof host.previewRetry === "function" && typeof host.retry === "function"
|
|
948
1120
|
};
|
|
@@ -957,25 +1129,13 @@ async function createFreeSession(host) {
|
|
|
957
1129
|
if (typeof host.create !== "function") throw new HostOperationError("session/lifecycle-unavailable");
|
|
958
1130
|
return sessionIdOf(await unwrap(host.create({ kind: "free" })));
|
|
959
1131
|
}
|
|
960
|
-
/** Returns the child id. The caller opens it only while its generation is still current. */
|
|
961
|
-
async function forkOwnedChild(host, sessionId) {
|
|
962
|
-
if (typeof host.forkSession === "function") return sessionIdOf(await unwrap(host.forkSession(sessionId)));
|
|
963
|
-
if (typeof host.fork === "function") return sessionIdOf(await unwrap(host.fork({
|
|
964
|
-
sessionId,
|
|
965
|
-
increaseTitle: true
|
|
966
|
-
})));
|
|
967
|
-
throw new HostOperationError("session/fork-unavailable");
|
|
968
|
-
}
|
|
969
1132
|
async function deleteOwnedSession(host, sessionId, requestId) {
|
|
970
1133
|
if (typeof host.deleteSession !== "function") throw new HostOperationError("session/lifecycle-unavailable");
|
|
971
1134
|
const value = await unwrap(host.deleteSession(sessionId, requestId));
|
|
972
1135
|
if (typeof value !== "object" || value === null) throw new HostOperationError("session/delete-pending", "missing-outcome");
|
|
973
1136
|
const phase = value.phase;
|
|
974
1137
|
if (phase !== "deleted" && phase !== "cleanup-pending") throw new HostOperationError("session/delete-pending", "missing-outcome");
|
|
975
|
-
return {
|
|
976
|
-
phase,
|
|
977
|
-
releasedSelection: value.releasedSelection === true
|
|
978
|
-
};
|
|
1138
|
+
return { phase };
|
|
979
1139
|
}
|
|
980
1140
|
function directRetryRequest(input) {
|
|
981
1141
|
return {
|
|
@@ -1052,57 +1212,18 @@ function createFreeChatModel(host, ids = () => crypto.randomUUID()) {
|
|
|
1052
1212
|
const events = store();
|
|
1053
1213
|
let capabilities = UNSUPPORTED_CAPABILITIES;
|
|
1054
1214
|
let capabilitiesReady = false;
|
|
1055
|
-
let query = "";
|
|
1056
1215
|
let creating = false;
|
|
1057
1216
|
let notice;
|
|
1058
1217
|
let deleteRequest;
|
|
1059
|
-
let focusNonce = 0;
|
|
1060
|
-
let listFailure;
|
|
1061
1218
|
let generation = 0;
|
|
1062
1219
|
let snapshot = build();
|
|
1063
|
-
const stopList = host.list.subscribe(() => publish());
|
|
1064
|
-
function listError(listed) {
|
|
1065
|
-
const code = typeof listed.error?.code === "string" && listed.error.code !== "" ? listed.error.code : "transport";
|
|
1066
|
-
const message = listed.error?.message;
|
|
1067
|
-
return {
|
|
1068
|
-
code,
|
|
1069
|
-
...typeof message === "string" && message !== "" ? { reason: message } : {}
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
1220
|
function build() {
|
|
1073
|
-
let status = "ready";
|
|
1074
|
-
let rows = [];
|
|
1075
|
-
try {
|
|
1076
|
-
const listed = host.list.getSnapshot();
|
|
1077
|
-
if (listed.phase === "error") {
|
|
1078
|
-
status = "error";
|
|
1079
|
-
listFailure = listError(listed);
|
|
1080
|
-
rows = [];
|
|
1081
|
-
} else {
|
|
1082
|
-
listFailure = void 0;
|
|
1083
|
-
const matched = matchFreeRows(freeChatRows(listed), query);
|
|
1084
|
-
if (listed.phase === "pending" && matched.length === 0) status = "loading";
|
|
1085
|
-
else if (listed.phase === "ready" && matched.length === 0 && query.trim() === "") status = "empty";
|
|
1086
|
-
else if (matched.length === 0) status = "nomatch";
|
|
1087
|
-
else status = "ready";
|
|
1088
|
-
rows = matched;
|
|
1089
|
-
}
|
|
1090
|
-
} catch (error) {
|
|
1091
|
-
status = "error";
|
|
1092
|
-
listFailure = readFailure(error);
|
|
1093
|
-
rows = [];
|
|
1094
|
-
}
|
|
1095
1221
|
return {
|
|
1096
|
-
rows,
|
|
1097
|
-
listStatus: status,
|
|
1098
|
-
...listFailure === void 0 ? {} : { listFailure },
|
|
1099
|
-
query,
|
|
1100
1222
|
controls: controlsFor(capabilities, capabilitiesReady, host),
|
|
1101
1223
|
capabilitiesReady,
|
|
1102
1224
|
creating,
|
|
1103
1225
|
...notice === void 0 ? {} : { notice },
|
|
1104
|
-
...deleteRequest === void 0 ? {} : { deleteRequest }
|
|
1105
|
-
focusNonce
|
|
1226
|
+
...deleteRequest === void 0 ? {} : { deleteRequest }
|
|
1106
1227
|
};
|
|
1107
1228
|
}
|
|
1108
1229
|
function publish() {
|
|
@@ -1117,15 +1238,6 @@ function createFreeChatModel(host, ids = () => crypto.randomUUID()) {
|
|
|
1117
1238
|
capabilitiesReady = true;
|
|
1118
1239
|
publish();
|
|
1119
1240
|
},
|
|
1120
|
-
setQuery(next) {
|
|
1121
|
-
query = next;
|
|
1122
|
-
publish();
|
|
1123
|
-
},
|
|
1124
|
-
async refreshList() {
|
|
1125
|
-
if (typeof host.refresh !== "function") return;
|
|
1126
|
-
await unwrap(host.refresh());
|
|
1127
|
-
publish();
|
|
1128
|
-
},
|
|
1129
1241
|
async create() {
|
|
1130
1242
|
if (!controlsFor(capabilities, capabilitiesReady, host).create || creating) return;
|
|
1131
1243
|
const ticket = generation;
|
|
@@ -1147,22 +1259,10 @@ function createFreeChatModel(host, ids = () => crypto.randomUUID()) {
|
|
|
1147
1259
|
}
|
|
1148
1260
|
}
|
|
1149
1261
|
},
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
},
|
|
1153
|
-
async fork(sessionId) {
|
|
1154
|
-
const ticket = generation;
|
|
1262
|
+
dismissNotice() {
|
|
1263
|
+
if (notice === void 0) return;
|
|
1155
1264
|
notice = void 0;
|
|
1156
|
-
|
|
1157
|
-
const child = await forkOwnedChild(host, sessionId);
|
|
1158
|
-
if (ticket !== generation) return;
|
|
1159
|
-
if (typeof host.openSession !== "function") throw new HostOperationError("session/lifecycle-unavailable", "open-session");
|
|
1160
|
-
host.openSession(child);
|
|
1161
|
-
} catch (error) {
|
|
1162
|
-
if (ticket !== generation) return;
|
|
1163
|
-
notice = readFailure(error);
|
|
1164
|
-
}
|
|
1165
|
-
if (ticket === generation) publish();
|
|
1265
|
+
publish();
|
|
1166
1266
|
},
|
|
1167
1267
|
requestDelete(sessionId) {
|
|
1168
1268
|
if (!controlsFor(capabilities, capabilitiesReady, host).delete) return;
|
|
@@ -1207,10 +1307,6 @@ function createFreeChatModel(host, ids = () => crypto.randomUUID()) {
|
|
|
1207
1307
|
return;
|
|
1208
1308
|
}
|
|
1209
1309
|
deleteRequest = void 0;
|
|
1210
|
-
if (outcome.releasedSelection) {
|
|
1211
|
-
host.selectPanel?.(FREE_PANEL_ID);
|
|
1212
|
-
focusNonce += 1;
|
|
1213
|
-
}
|
|
1214
1310
|
} catch (error) {
|
|
1215
1311
|
if (ticket !== generation) return;
|
|
1216
1312
|
const failure = readFailure(error);
|
|
@@ -1227,7 +1323,6 @@ function createFreeChatModel(host, ids = () => crypto.randomUUID()) {
|
|
|
1227
1323
|
creating = false;
|
|
1228
1324
|
notice = void 0;
|
|
1229
1325
|
deleteRequest = void 0;
|
|
1230
|
-
stopList();
|
|
1231
1326
|
publish();
|
|
1232
1327
|
}
|
|
1233
1328
|
};
|
|
@@ -1472,14 +1567,10 @@ function bindHost(ctx) {
|
|
|
1472
1567
|
},
|
|
1473
1568
|
...typeof sessions?.capabilities === "function" ? { capabilities: () => sessions.capabilities() } : {},
|
|
1474
1569
|
...typeof sessions?.create === "function" ? { create: (opts) => sessions.create(opts) } : {},
|
|
1475
|
-
...typeof sessions?.fork === "function" ? { fork: (opts) => sessions.fork(opts) } : {},
|
|
1476
|
-
...typeof sessions?.refresh === "function" ? { refresh: () => sessions.refresh() } : {},
|
|
1477
1570
|
...typeof sessions?.previewRetry === "function" ? { previewRetry: (request) => sessions.previewRetry(request) } : {},
|
|
1478
1571
|
...typeof sessions?.retry === "function" ? { retry: (request) => sessions.retry(request) } : {},
|
|
1479
1572
|
...typeof workspace?.openSession === "function" ? { openSession: (sessionId) => workspace.openSession(sessionId) } : {},
|
|
1480
|
-
...typeof workspace?.
|
|
1481
|
-
...typeof workspace?.deleteSession === "function" ? { deleteSession: (sessionId, requestId) => workspace.deleteSession(sessionId, requestId) } : {},
|
|
1482
|
-
...typeof ctx.layout?.selectPanel === "function" ? { selectPanel: (id) => ctx.layout.selectPanel(id) } : {}
|
|
1573
|
+
...typeof workspace?.deleteSession === "function" ? { deleteSession: (sessionId, requestId) => workspace.deleteSession(sessionId, requestId) } : {}
|
|
1483
1574
|
};
|
|
1484
1575
|
}
|
|
1485
1576
|
//#endregion
|
|
@@ -1585,6 +1676,9 @@ const inject = [
|
|
|
1585
1676
|
"locale",
|
|
1586
1677
|
"sessions",
|
|
1587
1678
|
"uiWorkspace",
|
|
1679
|
+
"shortcuts",
|
|
1680
|
+
"connection",
|
|
1681
|
+
"workspaces",
|
|
1588
1682
|
"layout"
|
|
1589
1683
|
];
|
|
1590
1684
|
function CopyThreadMenuItem(props) {
|
|
@@ -1599,6 +1693,7 @@ function CopyThreadMenuItem(props) {
|
|
|
1599
1693
|
});
|
|
1600
1694
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
|
|
1601
1695
|
separatorBefore: true,
|
|
1696
|
+
shortcut: props.shortcut,
|
|
1602
1697
|
onSelect,
|
|
1603
1698
|
children: label
|
|
1604
1699
|
});
|
|
@@ -1608,19 +1703,19 @@ function apply(ctx) {
|
|
|
1608
1703
|
const host = bindHost(client);
|
|
1609
1704
|
const chats = createFreeChatModel(host);
|
|
1610
1705
|
const retries = createRetryModel(host);
|
|
1706
|
+
const temporaryHost = bindTemporaryWorkspaceHost(client);
|
|
1707
|
+
const temporary = createTemporaryWorkspaceModel(temporaryHost, void 0, () => uiText(copyLocale(client.locale?.getSnapshot().active), "tempWorkspace"));
|
|
1708
|
+
const currentLocale = () => client.locale?.getSnapshot().active;
|
|
1611
1709
|
client.effect(() => {
|
|
1612
1710
|
const stops = [];
|
|
1613
|
-
|
|
1614
|
-
zh: { panel: uiText("zh", "panel") },
|
|
1615
|
-
en: { panel: uiText("en", "panel") }
|
|
1616
|
-
});
|
|
1617
|
-
if (typeof localeStop === "function") stops.push(localeStop);
|
|
1711
|
+
if (typeof document !== "undefined") stops.push(installContextMenuForwarding(document));
|
|
1618
1712
|
stops.push(client.slots.inject("sidebar.workspaces.session.menu.item", () => client.slots.register({
|
|
1619
1713
|
name: "sidebar.workspaces.session.menu.item",
|
|
1620
1714
|
id: COPY_THREAD_MENU_ID,
|
|
1621
1715
|
order: 500
|
|
1622
1716
|
}, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyThreadSlot, {
|
|
1623
1717
|
locale: client.locale,
|
|
1718
|
+
shortcuts: client.shortcuts,
|
|
1624
1719
|
...props
|
|
1625
1720
|
}))));
|
|
1626
1721
|
stops.push(client.slots.inject("sidebar.workspaces.session.menu.item", () => client.slots.register({
|
|
@@ -1632,19 +1727,7 @@ function apply(ctx) {
|
|
|
1632
1727
|
model: chats,
|
|
1633
1728
|
...props
|
|
1634
1729
|
}))));
|
|
1635
|
-
stops.push(client.slots
|
|
1636
|
-
name: "main",
|
|
1637
|
-
key: FREE_PANEL_ID
|
|
1638
|
-
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FreeChatPanel, {
|
|
1639
|
-
model: chats,
|
|
1640
|
-
locale: useLiveLocale(client.locale)
|
|
1641
|
-
}))));
|
|
1642
|
-
stops.push(client.slots.inject("sidebar.panellist", () => client.slots.register({
|
|
1643
|
-
name: "sidebar.panellist",
|
|
1644
|
-
id: FREE_PANEL_ID,
|
|
1645
|
-
order: 20,
|
|
1646
|
-
label: () => uiText(copyLocale(client.locale?.getSnapshot().active), "panel")
|
|
1647
|
-
}, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FreeChatIcon, { size: props.size ?? 16 }))));
|
|
1730
|
+
stops.push(installTemporaryWorkspacePicker(client.slots, temporary, temporaryHost.createWorkspace, client.locale));
|
|
1648
1731
|
stops.push(client.slots.inject(USER_ACTIONS_SLOT, () => client.slots.register({
|
|
1649
1732
|
name: USER_ACTIONS_SLOT,
|
|
1650
1733
|
id: USER_ACTIONS_ID,
|
|
@@ -1661,10 +1744,30 @@ function apply(ctx) {
|
|
|
1661
1744
|
name: "shell.overlay",
|
|
1662
1745
|
id: DELETE_DIALOG_ID,
|
|
1663
1746
|
order: 40
|
|
1664
|
-
}, () =>
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1747
|
+
}, () => {
|
|
1748
|
+
const locale = useLiveLocale(client.locale);
|
|
1749
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1750
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: freeChatCss }),
|
|
1751
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyThreadShortcutNotice, {
|
|
1752
|
+
shortcuts: client.shortcuts,
|
|
1753
|
+
list: client.sessions?.list,
|
|
1754
|
+
locale,
|
|
1755
|
+
currentLocale
|
|
1756
|
+
}),
|
|
1757
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TemporaryWorkspaceNotice, {
|
|
1758
|
+
model: temporary,
|
|
1759
|
+
locale
|
|
1760
|
+
}),
|
|
1761
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeleteChatDialog, {
|
|
1762
|
+
model: chats,
|
|
1763
|
+
locale
|
|
1764
|
+
}),
|
|
1765
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreateNoticeDialog, {
|
|
1766
|
+
model: chats,
|
|
1767
|
+
locale
|
|
1768
|
+
})
|
|
1769
|
+
] });
|
|
1770
|
+
})));
|
|
1668
1771
|
const reset = client.on?.("connection/reset", () => {
|
|
1669
1772
|
chats.reloadCapabilities();
|
|
1670
1773
|
retries.load();
|
|
@@ -1675,16 +1778,20 @@ function apply(ctx) {
|
|
|
1675
1778
|
return () => {
|
|
1676
1779
|
chats.cancel();
|
|
1677
1780
|
retries.cancel();
|
|
1781
|
+
temporary.cancel();
|
|
1678
1782
|
for (const stop of stops) stop();
|
|
1679
1783
|
};
|
|
1680
1784
|
});
|
|
1681
1785
|
}
|
|
1786
|
+
const EMPTY_SHORTCUTS = [];
|
|
1682
1787
|
function CopyThreadSlot(props) {
|
|
1683
1788
|
const locale = useLiveLocale(props.locale);
|
|
1789
|
+
const shortcuts = (0, react.useSyncExternalStore)((listener) => props.shortcuts?.catalog.subscribe(listener) ?? (() => {}), () => props.shortcuts?.catalog.getSnapshot() ?? EMPTY_SHORTCUTS, () => EMPTY_SHORTCUTS);
|
|
1684
1790
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyThreadMenuItem, {
|
|
1685
1791
|
sessionId: props.sessionId,
|
|
1686
1792
|
useMenuOpenState: props.useMenuOpenState,
|
|
1687
|
-
locale
|
|
1793
|
+
locale,
|
|
1794
|
+
shortcut: shortcuts.find((row) => row.id === COPY_THREAD_SHORTCUT_ID)
|
|
1688
1795
|
});
|
|
1689
1796
|
}
|
|
1690
1797
|
function DeleteMenuItem(props) {
|
|
@@ -1701,12 +1808,7 @@ function DeleteMenuItem(props) {
|
|
|
1701
1808
|
});
|
|
1702
1809
|
}
|
|
1703
1810
|
function useLiveLocale(locale) {
|
|
1704
|
-
return copyLocale((0, react.useSyncExternalStore)((listener) => locale?.subscribe(listener) ?? (() => {}), () => locale?.getSnapshot().active ?? "
|
|
1705
|
-
}
|
|
1706
|
-
async function writeClipboard(value) {
|
|
1707
|
-
const clipboard = globalThis.navigator?.clipboard;
|
|
1708
|
-
if (clipboard === void 0) throw new Error("clipboard unavailable");
|
|
1709
|
-
await clipboard.writeText(value);
|
|
1811
|
+
return copyLocale((0, react.useSyncExternalStore)((listener) => locale?.subscribe(listener) ?? (() => {}), () => locale?.getSnapshot().active ?? "en", () => locale?.getSnapshot().active ?? "en"));
|
|
1710
1812
|
}
|
|
1711
1813
|
//#endregion
|
|
1712
1814
|
exports.CopyThreadMenuItem = CopyThreadMenuItem;
|