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