@klarkxy/dsh-session-manager 0.2.1 → 0.3.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 +10 -11
- package/README.zh-CN.md +9 -10
- package/lib/client.inner.cjs +313 -415
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +313 -415
- package/lib/index.js +16 -5
- package/lib/index.js.map +1 -1
- package/locale/en.json +2 -4
- package/locale/zh.json +2 -4
- package/package.json +26 -21
package/lib/client.inner.cjs
CHANGED
|
@@ -1,13 +1,175 @@
|
|
|
1
1
|
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
|
-
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
4
|
let react_dom = require("react-dom");
|
|
5
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
//#region src/ui-copy.ts
|
|
7
|
+
function copyLocale(active) {
|
|
8
|
+
return active?.toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
9
|
+
}
|
|
10
|
+
const reasons = {
|
|
11
|
+
zh: {
|
|
12
|
+
modified: "后续操作可能涉及修改,暂时不能重试。",
|
|
13
|
+
unknown: "无法确认这个操作的结果。",
|
|
14
|
+
pending: "这条消息还在等待处理。",
|
|
15
|
+
stale: "这个聊天已经更新,请再试一次。",
|
|
16
|
+
running: "这个聊天仍在运行。请等待当前工作结束后再试。",
|
|
17
|
+
cleanup: "清理没有完成。",
|
|
18
|
+
unsupported: "当前版本不能完成这个操作。"
|
|
19
|
+
},
|
|
20
|
+
en: {
|
|
21
|
+
modified: "Later operations may have made changes. Retry is unavailable.",
|
|
22
|
+
unknown: "This action's result could not be confirmed.",
|
|
23
|
+
pending: "This message is still pending.",
|
|
24
|
+
stale: "This chat has changed. Try again.",
|
|
25
|
+
running: "This chat is still running. Wait for the current work to finish, then try again.",
|
|
26
|
+
cleanup: "Cleanup did not finish.",
|
|
27
|
+
unsupported: "This runtime cannot complete that action."
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const text = {
|
|
31
|
+
zh: {
|
|
32
|
+
tempWorkspace: "临时工作区",
|
|
33
|
+
createFailed: "无法创建临时工作区",
|
|
34
|
+
retryList: "重试",
|
|
35
|
+
deleteChat: "删除聊天",
|
|
36
|
+
deleting: "正在删除",
|
|
37
|
+
deleteFree: "将删除这个聊天及其拥有的文件。",
|
|
38
|
+
deleteOrdinary: "将删除这个聊天。项目文件会保留。",
|
|
39
|
+
cancel: "取消",
|
|
40
|
+
close: "关闭",
|
|
41
|
+
retry: "重试",
|
|
42
|
+
edit: "编辑并重发",
|
|
43
|
+
send: "发送",
|
|
44
|
+
editTitle: "编辑并重发",
|
|
45
|
+
draft: "消息文本",
|
|
46
|
+
continueDefault: "继续",
|
|
47
|
+
welcomeBack: "欢迎回来",
|
|
48
|
+
recapSummary: "在聊什么",
|
|
49
|
+
recapProgress: "目前进展",
|
|
50
|
+
recapNextStep: "建议下一步",
|
|
51
|
+
cacheNotice: "离开时间较久,模型的提示缓存可能已失效,下一次发送会重新处理完整上下文。可以压缩上下文,或在新会话中继续;也可以忽略提示直接发送。",
|
|
52
|
+
compactNow: "压缩上下文",
|
|
53
|
+
compacting: "正在压缩",
|
|
54
|
+
compactCommandSent: "已调用宿主压缩命令,结果请查看对话中的压缩记录。",
|
|
55
|
+
continueNew: "在新会话中继续",
|
|
56
|
+
creatingContinuation: "正在创建新会话",
|
|
57
|
+
continueNewHint: "在来源会话的工作区新建会话,将摘要和原会话 ID 填入草稿,不自动发送。",
|
|
58
|
+
compactNothing: "没有值得压缩的内容。",
|
|
59
|
+
settingsMode: "继续灰影",
|
|
60
|
+
modeFixed: "固定文本",
|
|
61
|
+
modeAuto: "模型生成",
|
|
62
|
+
settingsModeHint: "输入框为空时显示的灰色继续文本:Tab 填入,点击发送。",
|
|
63
|
+
continueTextLabel: "固定文本内容",
|
|
64
|
+
continueTextPlaceholder: "留空使用默认「继续」",
|
|
65
|
+
ghostModelLabel: "灰影模型",
|
|
66
|
+
ghostModelHint: "只在上一轮结束后生成一次,并缓存在这个会话中;留空跟随会话或宿主默认。",
|
|
67
|
+
welcomeBackGroup: "欢迎回来",
|
|
68
|
+
recapEnabledLabel: "欢迎回来摘要",
|
|
69
|
+
recapEnabledHint: "回到空闲的会话时,在对话顶部生成回顾卡片。",
|
|
70
|
+
recapMinutesLabel: "摘要阈值(分钟)",
|
|
71
|
+
compactEnabledLabel: "压缩建议",
|
|
72
|
+
compactEnabledHint: "空闲较久时,回到会话会建议先压缩上下文。",
|
|
73
|
+
compactMinutesLabel: "建议阈值(分钟)",
|
|
74
|
+
modelLabel: "摘要模型",
|
|
75
|
+
modelFollowOption: "当前会话 / 宿主默认模型",
|
|
76
|
+
modelGroupHint: "只用于欢迎回来摘要;留空跟随会话或宿主默认。",
|
|
77
|
+
saveSettings: "保存",
|
|
78
|
+
settingsSaving: "正在保存",
|
|
79
|
+
settingsSaved: "已保存",
|
|
80
|
+
settingsLoading: "正在加载",
|
|
81
|
+
settingsUnavailable: "当前宿主不支持在线修改配置",
|
|
82
|
+
minutesInvalid: "请填写大于等于 1 的整数分钟。",
|
|
83
|
+
externalChange: "配置已在别处修改。草稿已保留,请刷新页面后再保存。"
|
|
84
|
+
},
|
|
85
|
+
en: {
|
|
86
|
+
tempWorkspace: "Temporary Workspace",
|
|
87
|
+
createFailed: "Could not create the temporary workspace",
|
|
88
|
+
retryList: "Retry",
|
|
89
|
+
deleteChat: "Delete chat",
|
|
90
|
+
deleting: "Deleting",
|
|
91
|
+
deleteFree: "This deletes this chat and its owned files.",
|
|
92
|
+
deleteOrdinary: "This deletes this chat. Project files remain.",
|
|
93
|
+
cancel: "Cancel",
|
|
94
|
+
close: "Close",
|
|
95
|
+
retry: "Retry",
|
|
96
|
+
edit: "Edit and resend",
|
|
97
|
+
send: "Send",
|
|
98
|
+
editTitle: "Edit and resend",
|
|
99
|
+
draft: "Message text",
|
|
100
|
+
continueDefault: "Continue",
|
|
101
|
+
welcomeBack: "Welcome back",
|
|
102
|
+
recapSummary: "What you were discussing",
|
|
103
|
+
recapProgress: "Where things stand",
|
|
104
|
+
recapNextStep: "Suggested next step",
|
|
105
|
+
cacheNotice: "The model prompt cache may have expired, so the next message reprocesses the full context. Compact the context, continue in a new session, or ignore this notice and send normally.",
|
|
106
|
+
compactNow: "Compact context",
|
|
107
|
+
compacting: "Compacting",
|
|
108
|
+
compactCommandSent: "Host compaction command invoked. See the compaction entry in the conversation for its result.",
|
|
109
|
+
continueNew: "Continue in a new session",
|
|
110
|
+
creatingContinuation: "Creating new session",
|
|
111
|
+
continueNewHint: "Create a session in the source session's workspace and fill its draft with the recap and source session ID, without sending.",
|
|
112
|
+
compactNothing: "There is nothing useful to compact.",
|
|
113
|
+
settingsMode: "Continue ghost",
|
|
114
|
+
modeFixed: "Fixed text",
|
|
115
|
+
modeAuto: "Model-generated",
|
|
116
|
+
settingsModeHint: "The grey continuation shown on an empty composer: Tab fills it, clicking sends it.",
|
|
117
|
+
continueTextLabel: "Fixed text",
|
|
118
|
+
continueTextPlaceholder: "Empty uses the default \"Continue\"",
|
|
119
|
+
ghostModelLabel: "Ghost model",
|
|
120
|
+
ghostModelHint: "Generated once after the previous turn ends and cached for this session; empty follows the conversation or host default.",
|
|
121
|
+
welcomeBackGroup: "Welcome back",
|
|
122
|
+
recapEnabledLabel: "Welcome-back recap",
|
|
123
|
+
recapEnabledHint: "Shows a recap card at the top of the conversation when you return to an idle session.",
|
|
124
|
+
recapMinutesLabel: "Recap threshold (minutes)",
|
|
125
|
+
compactEnabledLabel: "Compaction suggestion",
|
|
126
|
+
compactEnabledHint: "Suggests compacting the context when you return after a longer idle stretch.",
|
|
127
|
+
compactMinutesLabel: "Suggestion threshold (minutes)",
|
|
128
|
+
modelLabel: "Recap model",
|
|
129
|
+
modelFollowOption: "Current conversation / host default",
|
|
130
|
+
modelGroupHint: "Used only for the welcome-back recap; empty follows the conversation or host default.",
|
|
131
|
+
saveSettings: "Save",
|
|
132
|
+
settingsSaving: "Saving",
|
|
133
|
+
settingsSaved: "Saved",
|
|
134
|
+
settingsLoading: "Loading",
|
|
135
|
+
settingsUnavailable: "This runtime cannot edit the plugin configuration",
|
|
136
|
+
minutesInvalid: "Enter a whole number of minutes of at least 1.",
|
|
137
|
+
externalChange: "Settings changed elsewhere. The draft was kept; reload this page before saving."
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
function uiText(locale, key) {
|
|
141
|
+
return text[locale][key];
|
|
142
|
+
}
|
|
143
|
+
function failureReason(failure) {
|
|
144
|
+
if (failure.code === "session/lifecycle-unavailable" || failure.code === "session/fork-unavailable" || failure.code === "session-manager/unavailable") return "unsupported";
|
|
145
|
+
if (failure.code === "session-manager/busy") return "running";
|
|
146
|
+
if (failure.code === "session/retry-revision" || failure.code === "session/branch-moved" || failure.code === "session/retry-input" || failure.code === "session/retry-conflict") return "stale";
|
|
147
|
+
if (failure.code === "session/delete-pending") return "cleanup";
|
|
148
|
+
if (failure.code === "session/agent-busy" && failure.reason === "free-session-work-active") return "running";
|
|
149
|
+
if (failure.reason === "modified" || failure.reason === "pending" || failure.reason === "running" || failure.reason === "stale") return failure.reason;
|
|
150
|
+
if (failure.reason === "stop-failed" || failure.reason === "not-quiet") return "running";
|
|
151
|
+
if (failure.reason === "not-human-prompt" || failure.reason === "missing-evidence") return "unknown";
|
|
152
|
+
return "unknown";
|
|
153
|
+
}
|
|
154
|
+
function failureText(locale, failure) {
|
|
155
|
+
return reasons[locale][failureReason(failure)];
|
|
156
|
+
}
|
|
157
|
+
function deleteBody(locale, classification) {
|
|
158
|
+
return classification === "free" ? text[locale].deleteFree : text[locale].deleteOrdinary;
|
|
159
|
+
}
|
|
160
|
+
/** Idle duration sentence on the welcome-back card. */
|
|
161
|
+
function idleText(locale, minutes) {
|
|
162
|
+
return locale === "zh" ? `距离上次响应已约 ${minutes} 分钟。` : `About ${minutes} minutes have passed since the last response.`;
|
|
163
|
+
}
|
|
164
|
+
/** Compaction outcome line; a null token count means the host did not report one. */
|
|
165
|
+
function compactedText(locale, tokens) {
|
|
166
|
+
if (locale === "zh") return tokens === null ? "上下文已压缩。" : `上下文已压缩,约 ${tokens} tokens 的历史被折叠。`;
|
|
167
|
+
return tokens === null ? "Context compacted." : `Context compacted; about ${tokens} tokens of history were folded.`;
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
6
170
|
//#region src/contracts.ts
|
|
7
171
|
/** Shared names and bounds for cross-session discovery. */
|
|
8
172
|
const PLUGIN_NAME = "@klarkxy/dsh-session-manager";
|
|
9
|
-
const COPY_THREAD_MENU_ID = "@klarkxy/dsh-session-manager.copy-thread-id";
|
|
10
|
-
const COPY_THREAD_SHORTCUT_ID = "session-manager.copy-thread-id";
|
|
11
173
|
const TEMP_WORKSPACE_CHANNEL = "/dsh-session-manager";
|
|
12
174
|
const TEMP_WORKSPACE_FLOW_SLOT = "session-manager.workspace.directoryFlow";
|
|
13
175
|
const USER_ACTIONS_SLOT = "conversation.chat.user-actions";
|
|
@@ -25,155 +187,46 @@ const WELCOME_BACK_CARD_SLOT = "conversation.chat.turnTail";
|
|
|
25
187
|
const CONTINUE_GHOST_ID = "@klarkxy/dsh-session-manager.continue-ghost";
|
|
26
188
|
const CONTINUE_GHOST_SLOT = "conversation.input.overlay";
|
|
27
189
|
//#endregion
|
|
28
|
-
//#region src/copy-action.ts
|
|
29
|
-
const COPY_THREAD_LABEL = {
|
|
30
|
-
zh: "复制线程 ID",
|
|
31
|
-
en: "Copy thread ID"
|
|
32
|
-
};
|
|
33
|
-
const COPY_THREAD_FAILED = {
|
|
34
|
-
zh: "无法复制线程 ID",
|
|
35
|
-
en: "Could not copy the thread ID"
|
|
36
|
-
};
|
|
37
|
-
const COPY_THREAD_COPIED = {
|
|
38
|
-
zh: "已复制线程 ID",
|
|
39
|
-
en: "Thread ID copied"
|
|
40
|
-
};
|
|
41
|
-
const COPY_THREAD_NO_SESSION = {
|
|
42
|
-
zh: "请先打开一个会话",
|
|
43
|
-
en: "Open a session first"
|
|
44
|
-
};
|
|
45
|
-
function copyLocale(active) {
|
|
46
|
-
return active?.toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
47
|
-
}
|
|
48
|
-
/** Copy one session id. Rejection stays with the caller so the menu can show it. */
|
|
49
|
-
async function copyThreadId(sessionId, writeText) {
|
|
50
|
-
await writeText(sessionId);
|
|
51
|
-
}
|
|
52
|
-
//#endregion
|
|
53
|
-
//#region src/copy-shortcut.tsx
|
|
54
|
-
async function writeClipboard(value) {
|
|
55
|
-
const clipboard = globalThis.navigator?.clipboard;
|
|
56
|
-
if (clipboard === void 0) throw new Error("clipboard unavailable");
|
|
57
|
-
await clipboard.writeText(value);
|
|
58
|
-
}
|
|
59
|
-
/** Uses the same mainView reference as ui-workspace/src/client/shortcuts.ts.
|
|
60
|
-
* The host registry owns key dispatch, remapping, conflicts, IME and repeat guards.
|
|
61
|
-
*/
|
|
62
|
-
function CopyThreadShortcutNotice({ shortcuts, list, locale, currentLocale, writeText = writeClipboard }) {
|
|
63
|
-
const [notice, setNotice] = (0, react.useState)(null);
|
|
64
|
-
const sequence = (0, react.useRef)(0);
|
|
65
|
-
(0, react.useEffect)(() => {
|
|
66
|
-
if (shortcuts === void 0) return;
|
|
67
|
-
let active = true;
|
|
68
|
-
const notify = (copied) => {
|
|
69
|
-
if (active) setNotice({
|
|
70
|
-
copied,
|
|
71
|
-
seq: ++sequence.current
|
|
72
|
-
});
|
|
73
|
-
};
|
|
74
|
-
const binding = {
|
|
75
|
-
code: "KeyC",
|
|
76
|
-
modifiers: ["primary", "alt"]
|
|
77
|
-
};
|
|
78
|
-
const stop = shortcuts.register({
|
|
79
|
-
id: COPY_THREAD_SHORTCUT_ID,
|
|
80
|
-
label: () => COPY_THREAD_LABEL[copyLocale(currentLocale())],
|
|
81
|
-
aliases: [
|
|
82
|
-
"copy thread id",
|
|
83
|
-
"copy session id",
|
|
84
|
-
"复制线程 ID"
|
|
85
|
-
],
|
|
86
|
-
defaults: {
|
|
87
|
-
"desktop:windows": binding,
|
|
88
|
-
"desktop:macos": binding,
|
|
89
|
-
"web:windows": binding,
|
|
90
|
-
"web:macos": binding
|
|
91
|
-
},
|
|
92
|
-
regions: ["page", "editable"],
|
|
93
|
-
modals: [],
|
|
94
|
-
resolve(context) {
|
|
95
|
-
const target = Object.values(list?.getSnapshot().byId ?? {}).find((row) => (row?.retainedBy?.mainView ?? 0) > 0);
|
|
96
|
-
if (target === void 0 || context.modal !== null || context.region === "terminal") return {
|
|
97
|
-
status: "blocked",
|
|
98
|
-
reason: COPY_THREAD_NO_SESSION[copyLocale(currentLocale())]
|
|
99
|
-
};
|
|
100
|
-
return {
|
|
101
|
-
status: "handled",
|
|
102
|
-
run() {
|
|
103
|
-
copyThreadId(target.id, writeText).then(() => notify(true), () => notify(false));
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
return () => {
|
|
109
|
-
active = false;
|
|
110
|
-
stop();
|
|
111
|
-
};
|
|
112
|
-
}, [
|
|
113
|
-
shortcuts,
|
|
114
|
-
list,
|
|
115
|
-
currentLocale,
|
|
116
|
-
writeText
|
|
117
|
-
]);
|
|
118
|
-
return notice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
119
|
-
text: (notice.copied ? COPY_THREAD_COPIED : COPY_THREAD_FAILED)[locale],
|
|
120
|
-
tone: notice.copied ? "success" : void 0,
|
|
121
|
-
onDone: () => setNotice(null)
|
|
122
|
-
}, notice.seq);
|
|
123
|
-
}
|
|
124
|
-
//#endregion
|
|
125
190
|
//#region src/context-menu.ts
|
|
126
191
|
/**
|
|
127
|
-
*
|
|
128
|
-
* session or workspace row clicks that row's native "..." trigger, so the
|
|
129
|
-
* host's own menu — including every `sidebar.workspaces.session.menu.item`
|
|
130
|
-
* slot contribution, this plugin's rows included — opens exactly as a
|
|
131
|
-
* left-click on the trigger would. The host owns the menu; this module only
|
|
132
|
-
* relays the gesture. No host code registers a contextmenu listener, so the
|
|
133
|
-
* capture-phase relay does not fight an existing handler.
|
|
192
|
+
* Relay sidebar right-clicks to the host's own session actions menu.
|
|
134
193
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* `span` whose CSS-module class ends in `_rowActions`. It stays mounted —
|
|
143
|
-
* CSS `display:none` until hover or menu-open — so a programmatic click
|
|
144
|
-
* toggles the menu. Blank new-session rows and the ungrouped bucket render
|
|
145
|
-
* no trigger; right-click does nothing there. (client.js:1061, 1314-1340,
|
|
146
|
-
* 1640-1660)
|
|
147
|
-
* - Class names carry a per-build hash prefix and the trigger's aria-label is
|
|
148
|
-
* localized; only `data-row-key` and the structural suffix are stable.
|
|
149
|
-
* - The opened menu portals to `document.body`, outside the tree, so a
|
|
150
|
-
* right-click on a menu row never re-enters this relay.
|
|
194
|
+
* dsh-client-ui-workspace SessionNodeItem renders a keyed treeitem, with a
|
|
195
|
+
* trailing rowActions span. Its first button is the Menu anchor; subsequent
|
|
196
|
+
* buttons are contributed row actions. The span stops click propagation, so
|
|
197
|
+
* clicking the anchor opens the menu without opening the session. Menu portals
|
|
198
|
+
* outside the tree and the row carries a *_menuOpen class while it is open.
|
|
199
|
+
* Blank rows, search results, and AnimatedRows exit clones have no keyed menu
|
|
200
|
+
* anchor and are left alone. CSS-module hash prefixes vary between host builds.
|
|
151
201
|
*/
|
|
152
|
-
const ROW_SELECTOR = "[
|
|
153
|
-
const
|
|
154
|
-
/** Resolve
|
|
155
|
-
function
|
|
202
|
+
const ROW_SELECTOR = "[role=\"treeitem\"][data-row-key^=\"session:\"]";
|
|
203
|
+
const ACTIONS_SELECTOR = "span[class*=\"_rowActions\"]";
|
|
204
|
+
/** Resolve a live session row in the sidebar tree. */
|
|
205
|
+
function findSessionRow(target) {
|
|
156
206
|
if (typeof target.closest !== "function") return null;
|
|
157
207
|
const row = target.closest(ROW_SELECTOR);
|
|
158
|
-
if (row === null || row.closest("[role=\"tree\"]") === null) return null;
|
|
159
|
-
|
|
160
|
-
if (trigger === null || typeof trigger.click !== "function") return null;
|
|
161
|
-
return trigger;
|
|
208
|
+
if (row === null || row.closest("[role=\"tree\"]") === null || row.closest("[inert]") !== null) return null;
|
|
209
|
+
return row;
|
|
162
210
|
}
|
|
163
|
-
/**
|
|
164
|
-
* Forward one `contextmenu` event to the row's native menu trigger.
|
|
165
|
-
* Returns true when the gesture was relayed (and the default suppressed).
|
|
166
|
-
*/
|
|
211
|
+
/** Forward a context-menu gesture only when the host provides a menu anchor. */
|
|
167
212
|
function forwardContextMenu(event) {
|
|
213
|
+
if (event.defaultPrevented) return false;
|
|
168
214
|
const target = event.target;
|
|
169
215
|
if (target === null || typeof target.closest !== "function") return false;
|
|
170
|
-
const
|
|
171
|
-
|
|
216
|
+
const element = target;
|
|
217
|
+
const row = findSessionRow(element);
|
|
218
|
+
if (row === null) return false;
|
|
219
|
+
const trigger = row.querySelector(ACTIONS_SELECTOR)?.querySelector("button");
|
|
220
|
+
if (trigger == null || trigger.disabled) return false;
|
|
221
|
+
if (element.closest(ACTIONS_SELECTOR) !== null && !trigger.contains(element)) return false;
|
|
172
222
|
event.preventDefault();
|
|
173
|
-
|
|
223
|
+
if (!Array.from(row.classList).some((name) => name.endsWith("_menuOpen"))) {
|
|
224
|
+
trigger.focus({ preventScroll: true });
|
|
225
|
+
trigger.click();
|
|
226
|
+
}
|
|
174
227
|
return true;
|
|
175
228
|
}
|
|
176
|
-
/** Capture-phase relay
|
|
229
|
+
/** Capture-phase relay; disposing the client removes the listener. */
|
|
177
230
|
function installContextMenuForwarding(doc) {
|
|
178
231
|
const listener = (event) => {
|
|
179
232
|
forwardContextMenu(event);
|
|
@@ -359,6 +412,17 @@ ${scope} {
|
|
|
359
412
|
/* --- Layout scaffolding. --- */
|
|
360
413
|
& .dsh-ui-stack { display: flex; flex-direction: column; gap: 12px; }
|
|
361
414
|
& .dsh-ui-stack-lg { display: flex; flex-direction: column; gap: 20px; }
|
|
415
|
+
/* Plugin detail pages share one reading column. These are layout recipes;
|
|
416
|
+
* controls keep their own primitive styles. Form spacing follows the host's
|
|
417
|
+
* settings-form/fields.module.css and SettingsForm.module.css. */
|
|
418
|
+
& .dsh-ui-page { display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 760px; min-width: 0; }
|
|
419
|
+
& .dsh-ui-form { display: flex; flex-direction: column; gap: 0; min-width: 0; }
|
|
420
|
+
&.dsh-ui-form > .dsh-ui-field, & .dsh-ui-form > .dsh-ui-field { padding: 12px 0; }
|
|
421
|
+
& .dsh-ui-form-footer { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding-top: 16px; min-width: 0; }
|
|
422
|
+
& .dsh-ui-fields-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 20px; min-width: 0; margin: 0; padding: 0; border: 0; }
|
|
423
|
+
@media (max-width: 560px) {
|
|
424
|
+
& .dsh-ui-fields-grid { grid-template-columns: minmax(0, 1fr); }
|
|
425
|
+
}
|
|
362
426
|
& .dsh-ui-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
363
427
|
& .dsh-ui-row-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
364
428
|
& .dsh-ui-spacer { flex: 1; min-width: 0; }
|
|
@@ -386,6 +450,9 @@ ${scope} {
|
|
|
386
450
|
* official primitive, so this only owns the vertical rhythm. --- */
|
|
387
451
|
& .dsh-ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
388
452
|
& .dsh-ui-field + .dsh-ui-field { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }
|
|
453
|
+
/* Grid fields share a row, so the stacked-field divider must not win later in
|
|
454
|
+
* the cascade. Reset it here, after the field recipe. */
|
|
455
|
+
& .dsh-ui-fields-grid > .dsh-ui-field { padding: 0; border: 0; }
|
|
389
456
|
& .dsh-ui-label { display: flex; align-items: center; gap: 6px; font-size: 13px; line-height: 20px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
390
457
|
& .dsh-ui-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
|
391
458
|
& .dsh-ui-help { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
|
@@ -576,7 +643,7 @@ ${scope} {
|
|
|
576
643
|
& *, & *::before, & *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
|
|
577
644
|
}
|
|
578
645
|
}
|
|
579
|
-
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(
|
|
646
|
+
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?=\s*\{)/gm, (_match, className) => `&${className}, & ${className}`);
|
|
580
647
|
}
|
|
581
648
|
//#endregion
|
|
582
649
|
//#region ../dsh-model-route/src/route.ts
|
|
@@ -808,7 +875,14 @@ function useModelCatalog(load, options = {}) {
|
|
|
808
875
|
refresh
|
|
809
876
|
};
|
|
810
877
|
}
|
|
811
|
-
/**
|
|
878
|
+
/**
|
|
879
|
+
* Also accepts a mapped state from a caller that owns broader async orchestration.
|
|
880
|
+
*
|
|
881
|
+
* The dsh-ui-* classes carry the styling and resolve through the consuming
|
|
882
|
+
* plugin's own officialUiCss scope (every consumer mounts its root class, so
|
|
883
|
+
* e.g. `.dsh-model-hub .dsh-ui-loading` paints this); dsh-model-catalog-status
|
|
884
|
+
* stays as a structural hook for tests and layout, never a colour source.
|
|
885
|
+
*/
|
|
812
886
|
function ModelCatalogStatus({ state, locale }) {
|
|
813
887
|
const zh = locale?.toLowerCase().startsWith("zh") === true;
|
|
814
888
|
const copy = zh ? {
|
|
@@ -824,15 +898,17 @@ function ModelCatalogStatus({ state, locale }) {
|
|
|
824
898
|
};
|
|
825
899
|
if (!state.loading && !state.error && state.failures.length === 0) return null;
|
|
826
900
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
827
|
-
className: "dsh-model-catalog-status",
|
|
901
|
+
className: "dsh-model-catalog-status dsh-ui-stack",
|
|
828
902
|
lang: zh ? "zh" : "en",
|
|
829
903
|
children: [
|
|
830
904
|
state.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
831
905
|
role: "status",
|
|
906
|
+
className: "dsh-ui-loading",
|
|
832
907
|
children: copy.loading
|
|
833
908
|
}) : null,
|
|
834
909
|
state.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
835
910
|
role: "alert",
|
|
911
|
+
className: "dsh-ui-error dsh-ui-wrap",
|
|
836
912
|
children: [
|
|
837
913
|
copy.error,
|
|
838
914
|
": ",
|
|
@@ -841,20 +917,27 @@ function ModelCatalogStatus({ state, locale }) {
|
|
|
841
917
|
}) : null,
|
|
842
918
|
state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
843
919
|
role: "status",
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
failure.
|
|
848
|
-
|
|
920
|
+
className: "dsh-ui-meta",
|
|
921
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: copy.partial }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
922
|
+
className: "dsh-ui-list",
|
|
923
|
+
children: state.failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
924
|
+
failure.name || failure.id,
|
|
925
|
+
": ",
|
|
926
|
+
failure.message
|
|
927
|
+
] }, `${failure.id}:${index}`))
|
|
928
|
+
})]
|
|
849
929
|
}) : null,
|
|
850
|
-
state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
state.
|
|
856
|
-
|
|
857
|
-
|
|
930
|
+
state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
931
|
+
className: "dsh-ui-actions",
|
|
932
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
933
|
+
type: "button",
|
|
934
|
+
size: "sm",
|
|
935
|
+
disabled: state.loading,
|
|
936
|
+
onClick: () => {
|
|
937
|
+
state.refresh();
|
|
938
|
+
},
|
|
939
|
+
children: copy.retry
|
|
940
|
+
})
|
|
858
941
|
}) : null
|
|
859
942
|
]
|
|
860
943
|
});
|
|
@@ -914,7 +997,8 @@ function groupModelMenuChoices(choices, query) {
|
|
|
914
997
|
* Sticky group headings, subsequence ranking, highlight walking and focus
|
|
915
998
|
* hand-back all match the host. SYNC POINT: when the host's ModelSelect or
|
|
916
999
|
* ModelSelect.module.css changes, this copy must be re-transcribed; styles
|
|
917
|
-
* below may only reference
|
|
1000
|
+
* below may only reference allowlisted theme tokens (the --dsw-* set plus the
|
|
1001
|
+
* host's --ds-* motion tokens), never literal colors.
|
|
918
1002
|
*
|
|
919
1003
|
* It statically imports the primitives, so only browser clients should load
|
|
920
1004
|
* this entry; host code and node tests use the core entry (`./index.ts`).
|
|
@@ -1422,7 +1506,8 @@ function ModelMenu(props) {
|
|
|
1422
1506
|
* sticky group headings (MenuGroup's own CSS), and the l1-stroke prominent
|
|
1423
1507
|
* elevation at portal z-index 1100. The trigger mirrors the composer's 28px
|
|
1424
1508
|
* model pill, chevron rotating open. MenuSurface paints fill and backdrop.
|
|
1425
|
-
* Only --dsw
|
|
1509
|
+
* Only allowlisted --dsw- and --ds- theme tokens may appear here — a literal
|
|
1510
|
+
* color is a bug.
|
|
1426
1511
|
*/
|
|
1427
1512
|
const modelMenuCss = `
|
|
1428
1513
|
.dsh-model-menu-root { min-width: 0; position: relative; display: inline-flex; }
|
|
@@ -1445,12 +1530,14 @@ const modelMenuCss = `
|
|
|
1445
1530
|
display: flex;
|
|
1446
1531
|
}
|
|
1447
1532
|
.dsh-model-menu-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
1448
|
-
.dsh-model-menu-trigger:focus-visible:not([data-selection-focus]) { box-shadow: 0 0 0
|
|
1533
|
+
.dsh-model-menu-trigger:focus-visible:not([data-selection-focus]) { box-shadow: 0 0 0 var(--dsw-focus-ring-width) var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
|
|
1449
1534
|
.dsh-model-menu-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
|
|
1450
1535
|
.dsh-model-menu-triggerLabel { text-overflow: ellipsis; white-space: nowrap; min-width: 0; overflow: hidden; }
|
|
1451
1536
|
.dsh-model-menu-triggerEffort { text-overflow: ellipsis; white-space: nowrap; min-width: 0; color: var(--dsw-alias-label-caption); flex-shrink: 1000; overflow: hidden; }
|
|
1452
|
-
.dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform
|
|
1537
|
+
.dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform var(--ds-transition-duration) var(--ds-ease-in-out); }
|
|
1453
1538
|
.dsh-model-menu-chevronOpen { transform: rotate(180deg); }
|
|
1539
|
+
/* Portal overlay: the host publishes no z-index token, so the card keeps the
|
|
1540
|
+
* native menu layer's 1100 verbatim. */
|
|
1454
1541
|
.dsh-model-menu-menu {
|
|
1455
1542
|
z-index: 1100;
|
|
1456
1543
|
--dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
|
|
@@ -1476,6 +1563,8 @@ const modelMenuCss = `
|
|
|
1476
1563
|
.dsh-model-menu-searchWithQuery { padding-right: 34px; }
|
|
1477
1564
|
.dsh-model-menu-searchRow .dsh-model-menu-search input { padding: 0; font-size: 12px; line-height: normal; }
|
|
1478
1565
|
.dsh-model-menu-searchRow .dsh-model-menu-search input::placeholder { color: var(--dsw-alias-label-caption); }
|
|
1566
|
+
/* The clear button is a circle by geometry (border-radius: 50%), a shape the
|
|
1567
|
+
* radius token scale does not describe. */
|
|
1479
1568
|
.dsh-model-menu-searchClear {
|
|
1480
1569
|
width: 24px;
|
|
1481
1570
|
height: 24px;
|
|
@@ -1643,182 +1732,7 @@ ${modelMenuCss}
|
|
|
1643
1732
|
.${FREE_CHAT_ROOT} .dsh-welcome-done { margin: 6px 0 0; }
|
|
1644
1733
|
`;
|
|
1645
1734
|
//#endregion
|
|
1646
|
-
//#region src/ui-copy.ts
|
|
1647
|
-
const reasons = {
|
|
1648
|
-
zh: {
|
|
1649
|
-
modified: "后续操作可能涉及修改,暂时不能重试。",
|
|
1650
|
-
unknown: "无法确认这个操作的结果。",
|
|
1651
|
-
pending: "这条消息还在等待处理。",
|
|
1652
|
-
stale: "这个聊天已经更新,请再试一次。",
|
|
1653
|
-
running: "这个聊天仍在运行。请等待当前工作结束后再试。",
|
|
1654
|
-
cleanup: "清理没有完成。",
|
|
1655
|
-
unsupported: "当前版本不能完成这个操作。"
|
|
1656
|
-
},
|
|
1657
|
-
en: {
|
|
1658
|
-
modified: "Later operations may have made changes. Retry is unavailable.",
|
|
1659
|
-
unknown: "This action's result could not be confirmed.",
|
|
1660
|
-
pending: "This message is still pending.",
|
|
1661
|
-
stale: "This chat has changed. Try again.",
|
|
1662
|
-
running: "This chat is still running. Wait for the current work to finish, then try again.",
|
|
1663
|
-
cleanup: "Cleanup did not finish.",
|
|
1664
|
-
unsupported: "This runtime cannot complete that action."
|
|
1665
|
-
}
|
|
1666
|
-
};
|
|
1667
|
-
const text = {
|
|
1668
|
-
zh: {
|
|
1669
|
-
tempWorkspace: "临时工作区",
|
|
1670
|
-
createFailed: "无法创建临时工作区",
|
|
1671
|
-
retryList: "重试",
|
|
1672
|
-
deleteChat: "删除聊天",
|
|
1673
|
-
deleting: "正在删除",
|
|
1674
|
-
deleteFree: "将删除这个聊天及其拥有的文件。",
|
|
1675
|
-
deleteOrdinary: "将删除这个聊天。项目文件会保留。",
|
|
1676
|
-
cancel: "取消",
|
|
1677
|
-
close: "关闭",
|
|
1678
|
-
retry: "重试",
|
|
1679
|
-
edit: "编辑并重发",
|
|
1680
|
-
send: "发送",
|
|
1681
|
-
editTitle: "编辑并重发",
|
|
1682
|
-
draft: "消息文本",
|
|
1683
|
-
continueDefault: "继续",
|
|
1684
|
-
welcomeBack: "欢迎回来",
|
|
1685
|
-
recapSummary: "在聊什么",
|
|
1686
|
-
recapProgress: "目前进展",
|
|
1687
|
-
recapNextStep: "建议下一步",
|
|
1688
|
-
cacheNotice: "离开时间较久,模型的提示缓存可能已失效,下一次发送会重新处理完整上下文。可以压缩上下文,或在新会话中继续;也可以忽略提示直接发送。",
|
|
1689
|
-
compactNow: "压缩上下文",
|
|
1690
|
-
compacting: "正在压缩",
|
|
1691
|
-
compactCommandSent: "已调用宿主压缩命令,结果请查看对话中的压缩记录。",
|
|
1692
|
-
continueNew: "在新会话中继续",
|
|
1693
|
-
creatingContinuation: "正在创建新会话",
|
|
1694
|
-
continueNewHint: "在来源会话的工作区新建会话,将摘要和原会话 ID 填入草稿,不自动发送。",
|
|
1695
|
-
compactNothing: "没有值得压缩的内容。",
|
|
1696
|
-
settingsMode: "继续灰影",
|
|
1697
|
-
modeFixed: "固定文本",
|
|
1698
|
-
modeAuto: "模型生成",
|
|
1699
|
-
settingsModeHint: "输入框为空时显示的灰色继续文本:Tab 填入,点击发送。",
|
|
1700
|
-
continueTextLabel: "固定文本内容",
|
|
1701
|
-
continueTextPlaceholder: "留空使用默认「继续」",
|
|
1702
|
-
ghostModelLabel: "灰影模型",
|
|
1703
|
-
ghostModelHint: "只在上一轮结束后生成一次,并缓存在这个会话中;留空跟随会话或宿主默认。",
|
|
1704
|
-
welcomeBackGroup: "欢迎回来",
|
|
1705
|
-
recapEnabledLabel: "欢迎回来摘要",
|
|
1706
|
-
recapEnabledHint: "回到空闲的会话时,在对话顶部生成回顾卡片。",
|
|
1707
|
-
recapMinutesLabel: "摘要阈值(分钟)",
|
|
1708
|
-
compactEnabledLabel: "压缩建议",
|
|
1709
|
-
compactEnabledHint: "空闲较久时,回到会话会建议先压缩上下文。",
|
|
1710
|
-
compactMinutesLabel: "建议阈值(分钟)",
|
|
1711
|
-
modelLabel: "摘要模型",
|
|
1712
|
-
modelFollowOption: "当前会话 / 宿主默认模型",
|
|
1713
|
-
modelGroupHint: "只用于欢迎回来摘要;留空跟随会话或宿主默认。",
|
|
1714
|
-
saveSettings: "保存",
|
|
1715
|
-
settingsSaving: "正在保存",
|
|
1716
|
-
settingsSaved: "已保存",
|
|
1717
|
-
settingsLoading: "正在加载",
|
|
1718
|
-
settingsUnavailable: "当前宿主不支持在线修改配置",
|
|
1719
|
-
minutesInvalid: "请填写大于等于 1 的整数分钟。",
|
|
1720
|
-
externalChange: "配置已在别处修改。草稿已保留,请刷新页面后再保存。"
|
|
1721
|
-
},
|
|
1722
|
-
en: {
|
|
1723
|
-
tempWorkspace: "Temporary Workspace",
|
|
1724
|
-
createFailed: "Could not create the temporary workspace",
|
|
1725
|
-
retryList: "Retry",
|
|
1726
|
-
deleteChat: "Delete chat",
|
|
1727
|
-
deleting: "Deleting",
|
|
1728
|
-
deleteFree: "This deletes this chat and its owned files.",
|
|
1729
|
-
deleteOrdinary: "This deletes this chat. Project files remain.",
|
|
1730
|
-
cancel: "Cancel",
|
|
1731
|
-
close: "Close",
|
|
1732
|
-
retry: "Retry",
|
|
1733
|
-
edit: "Edit and resend",
|
|
1734
|
-
send: "Send",
|
|
1735
|
-
editTitle: "Edit and resend",
|
|
1736
|
-
draft: "Message text",
|
|
1737
|
-
continueDefault: "Continue",
|
|
1738
|
-
welcomeBack: "Welcome back",
|
|
1739
|
-
recapSummary: "What you were discussing",
|
|
1740
|
-
recapProgress: "Where things stand",
|
|
1741
|
-
recapNextStep: "Suggested next step",
|
|
1742
|
-
cacheNotice: "The model prompt cache may have expired, so the next message reprocesses the full context. Compact the context, continue in a new session, or ignore this notice and send normally.",
|
|
1743
|
-
compactNow: "Compact context",
|
|
1744
|
-
compacting: "Compacting",
|
|
1745
|
-
compactCommandSent: "Host compaction command invoked. See the compaction entry in the conversation for its result.",
|
|
1746
|
-
continueNew: "Continue in a new session",
|
|
1747
|
-
creatingContinuation: "Creating new session",
|
|
1748
|
-
continueNewHint: "Create a session in the source session's workspace and fill its draft with the recap and source session ID, without sending.",
|
|
1749
|
-
compactNothing: "There is nothing useful to compact.",
|
|
1750
|
-
settingsMode: "Continue ghost",
|
|
1751
|
-
modeFixed: "Fixed text",
|
|
1752
|
-
modeAuto: "Model-generated",
|
|
1753
|
-
settingsModeHint: "The grey continuation shown on an empty composer: Tab fills it, clicking sends it.",
|
|
1754
|
-
continueTextLabel: "Fixed text",
|
|
1755
|
-
continueTextPlaceholder: "Empty uses the default \"Continue\"",
|
|
1756
|
-
ghostModelLabel: "Ghost model",
|
|
1757
|
-
ghostModelHint: "Generated once after the previous turn ends and cached for this session; empty follows the conversation or host default.",
|
|
1758
|
-
welcomeBackGroup: "Welcome back",
|
|
1759
|
-
recapEnabledLabel: "Welcome-back recap",
|
|
1760
|
-
recapEnabledHint: "Shows a recap card at the top of the conversation when you return to an idle session.",
|
|
1761
|
-
recapMinutesLabel: "Recap threshold (minutes)",
|
|
1762
|
-
compactEnabledLabel: "Compaction suggestion",
|
|
1763
|
-
compactEnabledHint: "Suggests compacting the context when you return after a longer idle stretch.",
|
|
1764
|
-
compactMinutesLabel: "Suggestion threshold (minutes)",
|
|
1765
|
-
modelLabel: "Recap model",
|
|
1766
|
-
modelFollowOption: "Current conversation / host default",
|
|
1767
|
-
modelGroupHint: "Used only for the welcome-back recap; empty follows the conversation or host default.",
|
|
1768
|
-
saveSettings: "Save",
|
|
1769
|
-
settingsSaving: "Saving",
|
|
1770
|
-
settingsSaved: "Saved",
|
|
1771
|
-
settingsLoading: "Loading",
|
|
1772
|
-
settingsUnavailable: "This runtime cannot edit the plugin configuration",
|
|
1773
|
-
minutesInvalid: "Enter a whole number of minutes of at least 1.",
|
|
1774
|
-
externalChange: "Settings changed elsewhere. The draft was kept; reload this page before saving."
|
|
1775
|
-
}
|
|
1776
|
-
};
|
|
1777
|
-
function uiText(locale, key) {
|
|
1778
|
-
return text[locale][key];
|
|
1779
|
-
}
|
|
1780
|
-
function failureReason(failure) {
|
|
1781
|
-
if (failure.code === "session/lifecycle-unavailable" || failure.code === "session/fork-unavailable" || failure.code === "session-manager/unavailable") return "unsupported";
|
|
1782
|
-
if (failure.code === "session-manager/busy") return "running";
|
|
1783
|
-
if (failure.code === "session/retry-revision" || failure.code === "session/branch-moved" || failure.code === "session/retry-input" || failure.code === "session/retry-conflict") return "stale";
|
|
1784
|
-
if (failure.code === "session/delete-pending") return "cleanup";
|
|
1785
|
-
if (failure.code === "session/agent-busy" && failure.reason === "free-session-work-active") return "running";
|
|
1786
|
-
if (failure.reason === "modified" || failure.reason === "pending" || failure.reason === "running" || failure.reason === "stale") return failure.reason;
|
|
1787
|
-
if (failure.reason === "stop-failed" || failure.reason === "not-quiet") return "running";
|
|
1788
|
-
if (failure.reason === "not-human-prompt" || failure.reason === "missing-evidence") return "unknown";
|
|
1789
|
-
return "unknown";
|
|
1790
|
-
}
|
|
1791
|
-
function failureText(locale, failure) {
|
|
1792
|
-
return reasons[locale][failureReason(failure)];
|
|
1793
|
-
}
|
|
1794
|
-
function deleteBody(locale, classification) {
|
|
1795
|
-
return classification === "free" ? text[locale].deleteFree : text[locale].deleteOrdinary;
|
|
1796
|
-
}
|
|
1797
|
-
/** Idle duration sentence on the welcome-back card. */
|
|
1798
|
-
function idleText(locale, minutes) {
|
|
1799
|
-
return locale === "zh" ? `距离上次响应已约 ${minutes} 分钟。` : `About ${minutes} minutes have passed since the last response.`;
|
|
1800
|
-
}
|
|
1801
|
-
/** Compaction outcome line; a null token count means the host did not report one. */
|
|
1802
|
-
function compactedText(locale, tokens) {
|
|
1803
|
-
if (locale === "zh") return tokens === null ? "上下文已压缩。" : `上下文已压缩,约 ${tokens} tokens 的历史被折叠。`;
|
|
1804
|
-
return tokens === null ? "Context compacted." : `Context compacted; about ${tokens} tokens of history were folded.`;
|
|
1805
|
-
}
|
|
1806
|
-
//#endregion
|
|
1807
1735
|
//#region src/free-panel.tsx
|
|
1808
|
-
function FreeChatIcon({ size }) {
|
|
1809
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1810
|
-
width: size,
|
|
1811
|
-
height: size,
|
|
1812
|
-
viewBox: "0 0 16 16",
|
|
1813
|
-
"aria-hidden": "true",
|
|
1814
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1815
|
-
d: "M2.5 3.5h11v7.5h-4.2L6 13.5V11H2.5z",
|
|
1816
|
-
fill: "none",
|
|
1817
|
-
stroke: "currentColor",
|
|
1818
|
-
strokeWidth: "1.2"
|
|
1819
|
-
})
|
|
1820
|
-
});
|
|
1821
|
-
}
|
|
1822
1736
|
/** Create failures used to surface inside the standalone panel. The panel is gone,
|
|
1823
1737
|
* so they get a small overlay instead; closing it dismisses the stored notice. */
|
|
1824
1738
|
function CreateNoticeDialog(props) {
|
|
@@ -2016,7 +1930,7 @@ function TemporaryWorkspacePicker(props) {
|
|
|
2016
1930
|
}] : [], {
|
|
2017
1931
|
id: "::temporary-workspace",
|
|
2018
1932
|
label: uiText(locale, "tempWorkspace"),
|
|
2019
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1933
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconNewChatOutlineRegular, { size: 16 }),
|
|
2020
1934
|
disabled: busy
|
|
2021
1935
|
}],
|
|
2022
1936
|
onSelect: (id) => {
|
|
@@ -2029,6 +1943,7 @@ function TemporaryWorkspacePicker(props) {
|
|
|
2029
1943
|
}),
|
|
2030
1944
|
props.open && workspaces.phase === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2031
1945
|
role: "status",
|
|
1946
|
+
className: `dsh-free-chat dsh-ui-loading`,
|
|
2032
1947
|
children: props.t("picker.loading")
|
|
2033
1948
|
}),
|
|
2034
1949
|
props.renderSlot(TEMP_WORKSPACE_FLOW_SLOT, {
|
|
@@ -2064,6 +1979,7 @@ function TemporaryWorkspacePicker(props) {
|
|
|
2064
1979
|
}),
|
|
2065
1980
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
2066
1981
|
open: failure !== void 0,
|
|
1982
|
+
className: FREE_CHAT_ROOT,
|
|
2067
1983
|
title: props.t("folderError.title"),
|
|
2068
1984
|
closeLabel: props.t("close"),
|
|
2069
1985
|
onClose: () => setFailure(void 0),
|
|
@@ -2088,6 +2004,7 @@ function TemporaryWorkspaceNotice({ model, locale }) {
|
|
|
2088
2004
|
const state = (0, react.useSyncExternalStore)(model.subscribe, model.getSnapshot, model.getSnapshot);
|
|
2089
2005
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
2090
2006
|
open: state.failure !== void 0,
|
|
2007
|
+
className: FREE_CHAT_ROOT,
|
|
2091
2008
|
title: uiText(locale, "createFailed"),
|
|
2092
2009
|
closeLabel: uiText(locale, "close"),
|
|
2093
2010
|
onClose: model.dismissFailure,
|
|
@@ -3201,25 +3118,18 @@ function createWelcomeBackModel(host, ids = () => crypto.randomUUID(), minInterv
|
|
|
3201
3118
|
}
|
|
3202
3119
|
//#endregion
|
|
3203
3120
|
//#region ../dsh-plugin-kit/src/contracts.ts
|
|
3204
|
-
/**
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
method: "watch",
|
|
3217
|
-
mode: "stream",
|
|
3218
|
-
invocation: { kind: "direct" },
|
|
3219
|
-
parameters: [],
|
|
3220
|
-
cancellation: { parameter: "signal" }
|
|
3221
|
-
}]
|
|
3222
|
-
};
|
|
3121
|
+
/** `@klarkxy/dsh-git-commit` becomes `KlarkxyDshGitCommitModelSettingsChanges`. */
|
|
3122
|
+
function modelSettingsChangesIdentity(packageName) {
|
|
3123
|
+
const scoped = /^@([^/]+)\/(.+)$/.exec(packageName);
|
|
3124
|
+
const parts = (scoped ? `${scoped[1]}/${scoped[2]}` : packageName).split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
3125
|
+
if (!parts.length) throw new Error("Model settings package name has no RPC segment");
|
|
3126
|
+
const segment = `${parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("")}ModelSettingsChanges`;
|
|
3127
|
+
if (!/^[A-Z][A-Za-z0-9]*$/.test(segment)) throw new Error(`Model settings sync name is not an RPC segment: ${segment}`);
|
|
3128
|
+
return {
|
|
3129
|
+
service: segment,
|
|
3130
|
+
namespace: segment
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3223
3133
|
//#endregion
|
|
3224
3134
|
//#region ../dsh-plugin-kit/src/model-settings-client.ts
|
|
3225
3135
|
/** This stream carries invalidations only; ready is never a settings snapshot. */
|
|
@@ -3234,17 +3144,44 @@ function parseModelSettingsChangeFrame(value) {
|
|
|
3234
3144
|
};
|
|
3235
3145
|
throw new Error("Invalid model settings change frame");
|
|
3236
3146
|
}
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3147
|
+
/** One owner's contribution. Each package mounts only its own endpoint. */
|
|
3148
|
+
function modelSettingsChangesRemote(packageName, identity = modelSettingsChangesIdentity(packageName)) {
|
|
3149
|
+
return {
|
|
3150
|
+
package: packageName,
|
|
3151
|
+
descriptors: [{
|
|
3152
|
+
id: `${identity.namespace}/watch`,
|
|
3153
|
+
service: identity.service,
|
|
3154
|
+
namespace: identity.namespace,
|
|
3155
|
+
method: "watch",
|
|
3156
|
+
mode: "stream",
|
|
3157
|
+
invocation: { kind: "direct" },
|
|
3158
|
+
parameters: [],
|
|
3159
|
+
cancellation: { parameter: "signal" },
|
|
3160
|
+
result: {
|
|
3161
|
+
mode: "strict",
|
|
3162
|
+
typeSymbol: "ModelSettings.ChangeFrame",
|
|
3163
|
+
create: () => ({ parse: parseModelSettingsChangeFrame })
|
|
3164
|
+
}
|
|
3165
|
+
}]
|
|
3166
|
+
};
|
|
3167
|
+
}
|
|
3168
|
+
/** Mount from a synchronous apply. Disposal waits for an in-flight mount and
|
|
3169
|
+
* then withdraws it, so the contribution cannot outlive the plugin. */
|
|
3170
|
+
function mountModelSettingsChanges(ctx, packageName) {
|
|
3171
|
+
const contribution = modelSettingsChangesRemote(packageName);
|
|
3172
|
+
ctx.inject(["remote"], (injected) => {
|
|
3173
|
+
const scope = injected;
|
|
3174
|
+
const mounting = scope.remote.$mount(contribution);
|
|
3175
|
+
mounting.catch(() => {});
|
|
3176
|
+
scope.effect(() => async () => {
|
|
3177
|
+
await (await mounting.catch(() => void 0))?.();
|
|
3178
|
+
});
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3245
3181
|
/** Native RemoteStream owns carrier reconnects; no timers or browser transport.
|
|
3246
3182
|
* A factory shares one logical stream while at least one subscriber remains. */
|
|
3247
|
-
function createModelSettingsSubscriber(ctx,
|
|
3183
|
+
function createModelSettingsSubscriber(ctx, packageName, identity = modelSettingsChangesIdentity(packageName), accept = (ns) => ns === packageName) {
|
|
3184
|
+
const endpoint = identity;
|
|
3248
3185
|
const listeners = /* @__PURE__ */ new Set();
|
|
3249
3186
|
let injection;
|
|
3250
3187
|
let current;
|
|
@@ -3270,12 +3207,18 @@ function createModelSettingsSubscriber(ctx, namespace) {
|
|
|
3270
3207
|
};
|
|
3271
3208
|
const start = () => {
|
|
3272
3209
|
const life = ++lifecycle;
|
|
3273
|
-
injection = ctx.inject(["remote",
|
|
3210
|
+
injection = ctx.inject(["remote", `remote.${endpoint.namespace}`], (injected) => {
|
|
3274
3211
|
if (life !== lifecycle || !listeners.size) return;
|
|
3275
3212
|
const scope = injected;
|
|
3213
|
+
const service = scope.remote[endpoint.namespace];
|
|
3214
|
+
if (!service || !("watch" in service) || !service.watch) {
|
|
3215
|
+
report("Model settings sync service unavailable");
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
const watch = service.watch;
|
|
3276
3219
|
const stream = scope.remote.$stream({
|
|
3277
3220
|
name: "Model settings sync",
|
|
3278
|
-
open: (signal) =>
|
|
3221
|
+
open: (signal) => watch(signal),
|
|
3279
3222
|
ended: (accepted) => /* @__PURE__ */ new Error(`Model settings sync stream ended ${accepted ? "after" : "before"} ready; refresh or reopen the page`)
|
|
3280
3223
|
});
|
|
3281
3224
|
const session = {
|
|
@@ -3310,7 +3253,7 @@ function createModelSettingsSubscriber(ctx, namespace) {
|
|
|
3310
3253
|
invalidate();
|
|
3311
3254
|
} else {
|
|
3312
3255
|
if (!ready) throw new Error("Model settings sync change arrived before ready");
|
|
3313
|
-
if (
|
|
3256
|
+
if (accept(frame.ns)) invalidate();
|
|
3314
3257
|
}
|
|
3315
3258
|
}
|
|
3316
3259
|
} catch (error) {
|
|
@@ -3694,7 +3637,7 @@ function SessionManagerSettings(props) {
|
|
|
3694
3637
|
});
|
|
3695
3638
|
};
|
|
3696
3639
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3697
|
-
className: `${FREE_CHAT_ROOT} dsh-ui-
|
|
3640
|
+
className: `${FREE_CHAT_ROOT} dsh-ui-page`,
|
|
3698
3641
|
children: [
|
|
3699
3642
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3700
3643
|
className: "dsh-ui-card",
|
|
@@ -3961,7 +3904,7 @@ function SessionManagerSettings(props) {
|
|
|
3961
3904
|
]
|
|
3962
3905
|
}),
|
|
3963
3906
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3964
|
-
className: "dsh-ui-
|
|
3907
|
+
className: "dsh-ui-form-footer",
|
|
3965
3908
|
children: [
|
|
3966
3909
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3967
3910
|
variant: "primary",
|
|
@@ -4092,58 +4035,31 @@ const inject = [
|
|
|
4092
4035
|
"locale",
|
|
4093
4036
|
"sessions",
|
|
4094
4037
|
"uiWorkspace",
|
|
4095
|
-
"shortcuts",
|
|
4096
4038
|
"connection",
|
|
4097
4039
|
"workspaces",
|
|
4098
4040
|
"layout",
|
|
4099
4041
|
"conversation",
|
|
4100
4042
|
...MODEL_CATALOG_INJECT
|
|
4101
4043
|
];
|
|
4102
|
-
function CopyThreadMenuItem(props) {
|
|
4103
|
-
const [failed, setFailed] = (0, react.useState)(false);
|
|
4104
|
-
const [, setMenuOpen] = props.useMenuOpenState();
|
|
4105
|
-
const label = failed ? COPY_THREAD_FAILED[props.locale] : COPY_THREAD_LABEL[props.locale];
|
|
4106
|
-
const onSelect = () => copyThreadId(props.sessionId, props.writeText ?? writeClipboard).then(() => {
|
|
4107
|
-
setFailed(false);
|
|
4108
|
-
setMenuOpen(false);
|
|
4109
|
-
}, () => {
|
|
4110
|
-
setFailed(true);
|
|
4111
|
-
});
|
|
4112
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
|
|
4113
|
-
separatorBefore: true,
|
|
4114
|
-
shortcut: props.shortcut,
|
|
4115
|
-
onSelect,
|
|
4116
|
-
children: label
|
|
4117
|
-
});
|
|
4118
|
-
}
|
|
4119
4044
|
function apply(ctx) {
|
|
4120
4045
|
const client = ctx;
|
|
4046
|
+
mountModelSettingsChanges(client, PLUGIN_NAME);
|
|
4121
4047
|
const host = bindHost(client);
|
|
4122
4048
|
const chats = createFreeChatModel(host);
|
|
4123
4049
|
const retries = createRetryModel(host);
|
|
4124
4050
|
const temporaryHost = bindTemporaryWorkspaceHost(client);
|
|
4125
4051
|
const temporary = createTemporaryWorkspaceModel(temporaryHost, void 0, () => uiText(copyLocale(client.locale?.getSnapshot().active), "tempWorkspace"));
|
|
4126
|
-
const currentLocale = () => client.locale?.getSnapshot().active;
|
|
4127
4052
|
const presenceHost = bindPresenceHost(client);
|
|
4128
4053
|
const continues = createContinueModel(presenceHost);
|
|
4129
4054
|
const welcomeBack = createWelcomeBackModel(presenceHost);
|
|
4130
4055
|
const presenceConfig = createPresenceConfigModel(presenceHost);
|
|
4131
|
-
const settingsSubscriber = createModelSettingsSubscriber(client, PLUGIN_NAME);
|
|
4056
|
+
const settingsSubscriber = createModelSettingsSubscriber(client, PLUGIN_NAME, modelSettingsChangesIdentity(PLUGIN_NAME));
|
|
4132
4057
|
const subscribeSettings = (onChange, onError) => settingsSubscriber.subscribe(onChange, onError);
|
|
4133
4058
|
const modelCatalog = () => readModelCatalog(client);
|
|
4134
4059
|
const subscribeCatalog = (onChange) => client.connection?.generation?.subscribe(onChange);
|
|
4135
4060
|
client.effect(() => {
|
|
4136
4061
|
const stops = [];
|
|
4137
4062
|
if (typeof document !== "undefined") stops.push(installContextMenuForwarding(document));
|
|
4138
|
-
stops.push(client.slots.inject("sidebar.workspaces.session.menu.item", () => client.slots.register({
|
|
4139
|
-
name: "sidebar.workspaces.session.menu.item",
|
|
4140
|
-
id: COPY_THREAD_MENU_ID,
|
|
4141
|
-
order: 500
|
|
4142
|
-
}, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyThreadSlot, {
|
|
4143
|
-
locale: client.locale,
|
|
4144
|
-
shortcuts: client.shortcuts,
|
|
4145
|
-
...props
|
|
4146
|
-
}))));
|
|
4147
4063
|
stops.push(client.slots.inject("sidebar.workspaces.session.menu.item", () => client.slots.register({
|
|
4148
4064
|
name: "sidebar.workspaces.session.menu.item",
|
|
4149
4065
|
id: DELETE_DIALOG_ID,
|
|
@@ -4208,12 +4124,6 @@ function apply(ctx) {
|
|
|
4208
4124
|
const locale = useLiveLocale(client.locale);
|
|
4209
4125
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4210
4126
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: freeChatCss }),
|
|
4211
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyThreadShortcutNotice, {
|
|
4212
|
-
shortcuts: client.shortcuts,
|
|
4213
|
-
list: client.sessions?.list,
|
|
4214
|
-
locale,
|
|
4215
|
-
currentLocale
|
|
4216
|
-
}),
|
|
4217
4127
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TemporaryWorkspaceNotice, {
|
|
4218
4128
|
model: temporary,
|
|
4219
4129
|
locale
|
|
@@ -4308,17 +4218,6 @@ function apply(ctx) {
|
|
|
4308
4218
|
};
|
|
4309
4219
|
});
|
|
4310
4220
|
}
|
|
4311
|
-
const EMPTY_SHORTCUTS = [];
|
|
4312
|
-
function CopyThreadSlot(props) {
|
|
4313
|
-
const locale = useLiveLocale(props.locale);
|
|
4314
|
-
const shortcuts = (0, react.useSyncExternalStore)((listener) => props.shortcuts?.catalog.subscribe(listener) ?? (() => {}), () => props.shortcuts?.catalog.getSnapshot() ?? EMPTY_SHORTCUTS, () => EMPTY_SHORTCUTS);
|
|
4315
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyThreadMenuItem, {
|
|
4316
|
-
sessionId: props.sessionId,
|
|
4317
|
-
useMenuOpenState: props.useMenuOpenState,
|
|
4318
|
-
locale,
|
|
4319
|
-
shortcut: shortcuts.find((row) => row.id === COPY_THREAD_SHORTCUT_ID)
|
|
4320
|
-
});
|
|
4321
|
-
}
|
|
4322
4221
|
function DeleteMenuItem(props) {
|
|
4323
4222
|
const locale = useLiveLocale(props.locale);
|
|
4324
4223
|
const snapshot = (0, react.useSyncExternalStore)(props.model.subscribe, props.model.getSnapshot, props.model.getSnapshot);
|
|
@@ -4337,7 +4236,6 @@ function useLiveLocale(locale) {
|
|
|
4337
4236
|
return copyLocale((0, react.useSyncExternalStore)((listener) => locale?.subscribe(listener) ?? (() => {}), () => locale?.getSnapshot().active ?? "en", () => locale?.getSnapshot().active ?? "en"));
|
|
4338
4237
|
}
|
|
4339
4238
|
//#endregion
|
|
4340
|
-
exports.CopyThreadMenuItem = CopyThreadMenuItem;
|
|
4341
4239
|
exports.apply = apply;
|
|
4342
4240
|
exports.inject = inject;
|
|
4343
4241
|
exports.name = name;
|