@klarkxy/dsh-session-manager 0.4.0 → 0.5.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 +11 -7
- package/README.zh-CN.md +11 -7
- package/lib/client.inner.cjs +1306 -835
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +1306 -835
- package/lib/index.d.ts +6 -0
- package/lib/index.js +553 -38
- package/lib/index.js.map +1 -1
- package/locale/en.json +1 -1
- package/locale/zh.json +2 -2
- package/package.json +32 -20
package/lib/client.js
CHANGED
|
@@ -73,29 +73,29 @@ const text = {
|
|
|
73
73
|
continueNewHint: "在来源会话的工作区新建会话,将摘要和原会话 ID 填入草稿,不自动发送。",
|
|
74
74
|
compactNothing: "没有值得压缩的内容。",
|
|
75
75
|
settingsTitle: "会话管理",
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
settingsMode: "继续灰影",
|
|
76
|
+
composerGroupTitle: "输入框建议",
|
|
77
|
+
composerGroupHint: "输入框为空时显示的下一句建议",
|
|
79
78
|
modeFixed: "固定文本",
|
|
80
79
|
modeAuto: "模型生成",
|
|
81
|
-
|
|
80
|
+
composerUsageHint: "按 Tab 填入建议,点击建议直接发送。",
|
|
81
|
+
ghostModelLabel: "生成模型",
|
|
82
82
|
continueSourceLabel: "内容来源",
|
|
83
83
|
continueTextLabel: "固定文本内容",
|
|
84
84
|
continueTextPlaceholder: "留空使用默认「继续」",
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
judgeModelHint: "判断回复是否提前结束;不选择时使用默认辅助模型。",
|
|
89
|
-
handoffGroup: "交接摘要",
|
|
90
|
-
handoffModelLabel: "交接模型",
|
|
91
|
-
handoffModelHint: "工具请求会话交接摘要时使用;不选择时使用默认辅助模型。",
|
|
92
|
-
welcomeBackGroup: "回到会话时",
|
|
85
|
+
returnGroupTitle: "回到会话时",
|
|
86
|
+
returnGroupHint: "长时间离开后显示摘要或建议压缩",
|
|
87
|
+
awayMinutesLabel: "离开超过",
|
|
93
88
|
recapEnabledLabel: "显示会话摘要",
|
|
94
89
|
recapEnabledHint: "在对话中回顾上次进展。",
|
|
95
|
-
recapMinutesLabel: "离开多久后显示摘要",
|
|
96
90
|
compactEnabledLabel: "建议压缩上下文",
|
|
97
91
|
compactEnabledHint: "长时间离开后,建议先压缩上下文。",
|
|
98
|
-
|
|
92
|
+
recapModelLabel: "摘要模型",
|
|
93
|
+
judgeModelLabel: "判断模型",
|
|
94
|
+
judgeModelHint: "判断回复是否提前结束,并写出继续消息。",
|
|
95
|
+
handoffGroupTitle: "会话交接",
|
|
96
|
+
handoffGroupHint: "智能体读取其他会话时生成的交接摘要",
|
|
97
|
+
handoffModelLabel: "摘要模型",
|
|
98
|
+
handoffModelHint: "智能体请求会话交接摘要时使用。",
|
|
99
99
|
autoContinue: "自动继续",
|
|
100
100
|
autoContinueOff: "关闭",
|
|
101
101
|
autoContinueCautious: "谨慎",
|
|
@@ -105,22 +105,24 @@ const text = {
|
|
|
105
105
|
autoContinueStale: "状态已在别处更改,已刷新",
|
|
106
106
|
autoContinueFailed: "切换失败",
|
|
107
107
|
autoContinueGroup: "自动继续",
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
autoContinueGroupHint: "回复提前结束时自动发送继续",
|
|
109
|
+
autoContinueModeHint: "开启方式(关闭/谨慎/奔放)在创建会话时按会话选择。",
|
|
110
110
|
autoContinueMaxLabel: "续跑次数上限",
|
|
111
111
|
autoContinueMaxHint: "你每发一条消息之后,最多自动继续的次数(1–10)。",
|
|
112
112
|
autoContinueMaxInvalid: "请填写 1–10 的整数。",
|
|
113
113
|
minutesUnit: "分钟",
|
|
114
|
-
|
|
114
|
+
agentActionsGroup: "代理操作",
|
|
115
|
+
agentActionsGroupHint: "允许 agent 自主操作会话",
|
|
116
|
+
allowSelfForkLabel: "允许自我 fork",
|
|
117
|
+
allowSelfForkHint: "允许 agent 自行 fork 当前会话,创建独立分支。",
|
|
118
|
+
allowSendMessageLabel: "允许发送消息",
|
|
119
|
+
allowSendMessageHint: "允许 agent 向其他未归档会话发送消息;空闲会话会被唤醒,运行中的会话会排队接收。",
|
|
115
120
|
modelFollowOption: "默认辅助模型",
|
|
116
|
-
modelGroupHint: "只用于欢迎回来摘要;留空使用默认辅助模型。",
|
|
117
|
-
saveSettings: "保存",
|
|
118
121
|
settingsSaving: "正在保存",
|
|
119
122
|
settingsSaved: "已保存",
|
|
120
123
|
settingsLoading: "正在加载",
|
|
121
124
|
settingsUnavailable: "当前宿主不支持在线修改配置",
|
|
122
|
-
minutesInvalid: "请填写大于等于 1 的整数分钟。"
|
|
123
|
-
externalChange: "配置已在别处修改。草稿已保留,请刷新页面后再保存。"
|
|
125
|
+
minutesInvalid: "请填写大于等于 1 的整数分钟。"
|
|
124
126
|
},
|
|
125
127
|
en: {
|
|
126
128
|
tempWorkspace: "Temporary Workspace",
|
|
@@ -157,29 +159,29 @@ const text = {
|
|
|
157
159
|
continueNewHint: "Create a session in the source session's workspace and fill its draft with the recap and source session ID, without sending.",
|
|
158
160
|
compactNothing: "There is nothing useful to compact.",
|
|
159
161
|
settingsTitle: "Session Management",
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
settingsMode: "Continue ghost",
|
|
162
|
+
composerGroupTitle: "Composer suggestion",
|
|
163
|
+
composerGroupHint: "The suggested next message shown in an empty composer",
|
|
163
164
|
modeFixed: "Fixed text",
|
|
164
165
|
modeAuto: "Model-generated",
|
|
165
|
-
|
|
166
|
+
composerUsageHint: "Press Tab to fill the suggestion, or click it to send.",
|
|
167
|
+
ghostModelLabel: "Generation model",
|
|
166
168
|
continueSourceLabel: "Source",
|
|
167
169
|
continueTextLabel: "Fixed text",
|
|
168
170
|
continueTextPlaceholder: "Empty uses the default \"Continue\"",
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
judgeModelHint: "Decides whether a reply stopped early. The default follows the shared auxiliary model.",
|
|
173
|
-
handoffGroup: "Session handoff",
|
|
174
|
-
handoffModelLabel: "Handoff model",
|
|
175
|
-
handoffModelHint: "Used when an agent asks for a session handoff summary. The default follows the shared auxiliary model.",
|
|
176
|
-
welcomeBackGroup: "When you return",
|
|
171
|
+
returnGroupTitle: "When you return",
|
|
172
|
+
returnGroupHint: "Show a recap or suggest compaction after time away",
|
|
173
|
+
awayMinutesLabel: "Away for more than",
|
|
177
174
|
recapEnabledLabel: "Show session recap",
|
|
178
175
|
recapEnabledHint: "Review your previous progress in the conversation.",
|
|
179
|
-
recapMinutesLabel: "Show recap after",
|
|
180
176
|
compactEnabledLabel: "Suggest context compaction",
|
|
181
177
|
compactEnabledHint: "Suggest compacting the context after a long time away.",
|
|
182
|
-
|
|
178
|
+
recapModelLabel: "Recap model",
|
|
179
|
+
judgeModelLabel: "Judge model",
|
|
180
|
+
judgeModelHint: "Decides whether a reply stopped early and writes the follow-up.",
|
|
181
|
+
handoffGroupTitle: "Session handoff",
|
|
182
|
+
handoffGroupHint: "Handoff summaries an agent generates when reading another session",
|
|
183
|
+
handoffModelLabel: "Summary model",
|
|
184
|
+
handoffModelHint: "Used when an agent asks for a session handoff summary.",
|
|
183
185
|
autoContinue: "Auto-continue",
|
|
184
186
|
autoContinueOff: "Off",
|
|
185
187
|
autoContinueCautious: "Cautious",
|
|
@@ -189,22 +191,24 @@ const text = {
|
|
|
189
191
|
autoContinueStale: "State changed elsewhere; refreshed",
|
|
190
192
|
autoContinueFailed: "Could not change Auto-continue",
|
|
191
193
|
autoContinueGroup: "Auto-continue",
|
|
192
|
-
|
|
193
|
-
|
|
194
|
+
autoContinueGroupHint: "Send a follow-up when a reply stops early",
|
|
195
|
+
autoContinueModeHint: "The mode (Off / Cautious / Bold) is chosen per session when creating it.",
|
|
194
196
|
autoContinueMaxLabel: "Maximum continuations",
|
|
195
197
|
autoContinueMaxHint: "How many times it may continue automatically after each message you send (1–10).",
|
|
196
198
|
autoContinueMaxInvalid: "Enter a whole number from 1 to 10.",
|
|
197
199
|
minutesUnit: "minutes",
|
|
198
|
-
|
|
200
|
+
agentActionsGroup: "Agent actions",
|
|
201
|
+
agentActionsGroupHint: "Let the agent act on sessions on its own",
|
|
202
|
+
allowSelfForkLabel: "Allow self-fork",
|
|
203
|
+
allowSelfForkHint: "Let the agent fork the current session into an independent branch.",
|
|
204
|
+
allowSendMessageLabel: "Allow sending messages",
|
|
205
|
+
allowSendMessageHint: "Let the agent message other unarchived sessions; idle sessions are woken, running sessions queue the message.",
|
|
199
206
|
modelFollowOption: "Default auxiliary model",
|
|
200
|
-
modelGroupHint: "Used only for the welcome-back recap; empty follows the default auxiliary model.",
|
|
201
|
-
saveSettings: "Save",
|
|
202
207
|
settingsSaving: "Saving",
|
|
203
208
|
settingsSaved: "Saved",
|
|
204
209
|
settingsLoading: "Loading",
|
|
205
210
|
settingsUnavailable: "This runtime cannot edit the plugin configuration",
|
|
206
|
-
minutesInvalid: "Enter a whole number of minutes of at least 1."
|
|
207
|
-
externalChange: "Settings changed elsewhere. The draft was kept; reload this page before saving."
|
|
211
|
+
minutesInvalid: "Enter a whole number of minutes of at least 1."
|
|
208
212
|
}
|
|
209
213
|
};
|
|
210
214
|
function uiText(locale, key) {
|
|
@@ -257,8 +261,29 @@ const CONFIG_READ_ENDPOINT = "config.read";
|
|
|
257
261
|
const CONFIG_WRITE_ENDPOINT = "config.write";
|
|
258
262
|
const AUTO_CONTINUE_STATUS_ENDPOINT = "autocontinue.status";
|
|
259
263
|
const AUTO_CONTINUE_UPDATE_ENDPOINT = "autocontinue.update";
|
|
260
|
-
/** This plugin's
|
|
261
|
-
const
|
|
264
|
+
/** This plugin's rows inside the shared Session Management settings list. */
|
|
265
|
+
const SESSION_SETTINGS_ITEMS = [
|
|
266
|
+
{
|
|
267
|
+
id: "session-manager.composer",
|
|
268
|
+
order: 60
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: "session-manager.auto-continue",
|
|
272
|
+
order: 62
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
id: "session-manager.return",
|
|
276
|
+
order: 64
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: "session-manager.handoff",
|
|
280
|
+
order: 66
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: "session-manager.agent-actions",
|
|
284
|
+
order: 68
|
|
285
|
+
}
|
|
286
|
+
];
|
|
262
287
|
/** Card entry id in the chat flow; the recap renders at the latest completed turn's tail. */
|
|
263
288
|
const WELCOME_BACK_CARD_ID = "@klarkxy/dsh-session-manager.welcome-back";
|
|
264
289
|
const TURN_TAIL_SLOT = "conversation.chat.turnTail";
|
|
@@ -403,7 +428,9 @@ Object.values({
|
|
|
403
428
|
"--dsh-scrollbar-thumb-hover",
|
|
404
429
|
"--dsh-content-font-size-secondary",
|
|
405
430
|
"--dsh-content-font-delta",
|
|
406
|
-
"--dsh-frame-top-clearance"
|
|
431
|
+
"--dsh-frame-top-clearance",
|
|
432
|
+
"--dsh-composer-model-icon-display",
|
|
433
|
+
"--dsh-composer-model-text-display"
|
|
407
434
|
]
|
|
408
435
|
}).flat();
|
|
409
436
|
/**
|
|
@@ -730,18 +757,6 @@ const KEY_SEPARATOR = "";
|
|
|
730
757
|
function modelRouteKey(provider, model) {
|
|
731
758
|
return provider && model ? `${provider}${KEY_SEPARATOR}${model}` : "";
|
|
732
759
|
}
|
|
733
|
-
/** Inverse of modelRouteKey; undefined for malformed keys. */
|
|
734
|
-
function parseModelRouteKey(value) {
|
|
735
|
-
const index = value.indexOf(KEY_SEPARATOR);
|
|
736
|
-
if (index <= 0) return void 0;
|
|
737
|
-
const provider = value.slice(0, index);
|
|
738
|
-
const model = value.slice(index + 1);
|
|
739
|
-
if (!provider || !model) return void 0;
|
|
740
|
-
return {
|
|
741
|
-
provider,
|
|
742
|
-
model
|
|
743
|
-
};
|
|
744
|
-
}
|
|
745
760
|
//#endregion
|
|
746
761
|
//#region ../dsh-model-route/src/catalog.ts
|
|
747
762
|
/** Both services are required: Cordis checks the nested namespace separately. */
|
|
@@ -836,6 +851,7 @@ function parseModelMenuChoices(value, bound) {
|
|
|
836
851
|
provider,
|
|
837
852
|
model,
|
|
838
853
|
label: `${providerName} / ${name}`,
|
|
854
|
+
providerLabel: providerName,
|
|
839
855
|
efforts
|
|
840
856
|
});
|
|
841
857
|
}
|
|
@@ -1054,11 +1070,11 @@ function modelMenuShortName(choice) {
|
|
|
1054
1070
|
/** Group catalog choices by provider and filter them by a case-insensitive query. */
|
|
1055
1071
|
function groupModelMenuChoices(choices, query) {
|
|
1056
1072
|
const needle = query.trim().toLowerCase();
|
|
1057
|
-
const matched = needle ? choices.filter((choice) => choice.label.toLowerCase().includes(needle) || choice.model.toLowerCase().includes(needle) || choice.provider.toLowerCase().includes(needle)) : choices;
|
|
1073
|
+
const matched = needle ? choices.filter((choice) => choice.label.toLowerCase().includes(needle) || choice.model.toLowerCase().includes(needle) || choice.provider.toLowerCase().includes(needle) || (choice.providerLabel?.toLowerCase().includes(needle) ?? false)) : choices;
|
|
1058
1074
|
const groups = [];
|
|
1059
1075
|
for (const choice of matched) {
|
|
1060
1076
|
const sep = choice.label.indexOf(" / ");
|
|
1061
|
-
const name = sep > 0 ? choice.label.slice(0, sep) : choice.provider;
|
|
1077
|
+
const name = choice.providerLabel ?? (sep > 0 ? choice.label.slice(0, sep) : choice.provider);
|
|
1062
1078
|
const last = groups.at(-1);
|
|
1063
1079
|
if (last && last.provider === choice.provider) last.items.push(choice);
|
|
1064
1080
|
else groups.push({
|
|
@@ -1113,7 +1129,7 @@ function ModelMenu(props) {
|
|
|
1113
1129
|
empty: "No matching models",
|
|
1114
1130
|
clear: "Clear search"
|
|
1115
1131
|
};
|
|
1116
|
-
const { open, onOpenChange, anchor, choices, selected, onPick, leading = [], onPickLeading, efforts = [], selectedEffort, onPickEffort, modelLabel = copy.model, modelValue = "—", effortLabel = copy.effort, effortValue, defaultEffortLabel = copy.default, searchPlaceholder = copy.search, emptyLabel = copy.empty, clearSearchLabel = copy.clear, className, listClassName } = props;
|
|
1132
|
+
const { open, onOpenChange, anchor, choices, selected, onPick, leading = [], onPickLeading, efforts = [], selectedEffort, onPickEffort, modelLabel = copy.model, modelValue = "—", effortLabel = copy.effort, effortValue, defaultEffortLabel = copy.default, searchPlaceholder = copy.search, emptyLabel = copy.empty, clearSearchLabel = copy.clear, initialPane = "root", alwaysShowSearch = false, reasoningSlider = false, menuHeader, disabled = false, busy = false, closeOnPick = true, closeOnDisable = true, className, listClassName } = props;
|
|
1117
1133
|
const rootRef = (0, react.useRef)(null);
|
|
1118
1134
|
const openRef = (0, react.useRef)(open);
|
|
1119
1135
|
openRef.current = open;
|
|
@@ -1128,9 +1144,10 @@ function ModelMenu(props) {
|
|
|
1128
1144
|
const [pane, setPane] = (0, react.useState)("root");
|
|
1129
1145
|
const [query, setQuery] = (0, react.useState)("");
|
|
1130
1146
|
const [highlightedIndex, setHighlightedIndex] = (0, react.useState)(null);
|
|
1147
|
+
const [keyNav, setKeyNav] = (0, react.useState)(0);
|
|
1131
1148
|
const [menuPos, setMenuPos] = (0, react.useState)(null);
|
|
1132
1149
|
const id = (0, react.useId)();
|
|
1133
|
-
const showSearch = choices.length > 4;
|
|
1150
|
+
const showSearch = alwaysShowSearch || choices.length > 4;
|
|
1134
1151
|
const effortRows = (0, react.useMemo)(() => [{
|
|
1135
1152
|
id: "",
|
|
1136
1153
|
name: defaultEffortLabel
|
|
@@ -1147,7 +1164,12 @@ function ModelMenu(props) {
|
|
|
1147
1164
|
items: (0, _deepseek_ai_dsh_client_ui_primitives.rankByName)(group.items.map((choice) => ({
|
|
1148
1165
|
choice,
|
|
1149
1166
|
name: modelMenuShortName(choice),
|
|
1150
|
-
label:
|
|
1167
|
+
label: [
|
|
1168
|
+
choice.provider,
|
|
1169
|
+
choice.providerLabel,
|
|
1170
|
+
choice.model,
|
|
1171
|
+
choice.label
|
|
1172
|
+
].filter(Boolean).join(" ")
|
|
1151
1173
|
})), showSearch ? query.trim() : "")
|
|
1152
1174
|
})).filter((group) => group.items.length > 0), [
|
|
1153
1175
|
choices,
|
|
@@ -1179,10 +1201,12 @@ function ModelMenu(props) {
|
|
|
1179
1201
|
paneFocus.current = "drill";
|
|
1180
1202
|
setPane(next);
|
|
1181
1203
|
};
|
|
1182
|
-
/** Leave a drilled pane for the
|
|
1204
|
+
/** Leave a drilled pane for the entry pane, handing the keyboard back to it.
|
|
1205
|
+
* A root entry refocuses the cell that opened the pane; a search-first
|
|
1206
|
+
* (model) entry refocuses that pane's own intent. */
|
|
1183
1207
|
const back = (from) => {
|
|
1184
|
-
paneFocus.current = from;
|
|
1185
|
-
setPane(
|
|
1208
|
+
paneFocus.current = initialPane === "root" ? from : "drill";
|
|
1209
|
+
setPane(initialPane);
|
|
1186
1210
|
};
|
|
1187
1211
|
const moveFocus = (offset) => {
|
|
1188
1212
|
const items = itemRefs.current.filter((item) => item !== null && !item.disabled);
|
|
@@ -1222,10 +1246,18 @@ function ModelMenu(props) {
|
|
|
1222
1246
|
}, [requestedOpen, open]);
|
|
1223
1247
|
(0, react.useEffect)(() => {
|
|
1224
1248
|
if (!open) return;
|
|
1225
|
-
setPane(
|
|
1249
|
+
setPane(initialPane);
|
|
1250
|
+
if (initialPane === "model") paneFocus.current = "drill";
|
|
1226
1251
|
setQuery("");
|
|
1227
1252
|
setHighlightedIndex(null);
|
|
1228
|
-
}, [open]);
|
|
1253
|
+
}, [open, initialPane]);
|
|
1254
|
+
(0, react.useEffect)(() => {
|
|
1255
|
+
if (disabled && open && closeOnDisable) changeOpen(false);
|
|
1256
|
+
}, [
|
|
1257
|
+
disabled,
|
|
1258
|
+
open,
|
|
1259
|
+
closeOnDisable
|
|
1260
|
+
]);
|
|
1229
1261
|
(0, react.useEffect)(() => {
|
|
1230
1262
|
if (!open) return;
|
|
1231
1263
|
const closeOutside = (event) => {
|
|
@@ -1233,9 +1265,25 @@ function ModelMenu(props) {
|
|
|
1233
1265
|
if (rootRef.current?.contains(target) || menuRef.current?.contains(target)) return;
|
|
1234
1266
|
changeOpen(false);
|
|
1235
1267
|
};
|
|
1268
|
+
const escapeOutside = (event) => {
|
|
1269
|
+
if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
|
|
1270
|
+
if (!openRef.current) return;
|
|
1271
|
+
event.preventDefault();
|
|
1272
|
+
if (pane !== initialPane) back(pane);
|
|
1273
|
+
else close(true);
|
|
1274
|
+
};
|
|
1236
1275
|
document.addEventListener("mousedown", closeOutside);
|
|
1237
|
-
|
|
1238
|
-
|
|
1276
|
+
document.addEventListener("keydown", escapeOutside);
|
|
1277
|
+
return () => {
|
|
1278
|
+
document.removeEventListener("mousedown", closeOutside);
|
|
1279
|
+
document.removeEventListener("keydown", escapeOutside);
|
|
1280
|
+
};
|
|
1281
|
+
}, [
|
|
1282
|
+
open,
|
|
1283
|
+
onOpenChange,
|
|
1284
|
+
pane,
|
|
1285
|
+
initialPane
|
|
1286
|
+
]);
|
|
1239
1287
|
(0, react.useEffect)(() => {
|
|
1240
1288
|
const intent = paneFocus.current;
|
|
1241
1289
|
if (!open || menuPos === null || intent === null) return;
|
|
@@ -1265,13 +1313,21 @@ function ModelMenu(props) {
|
|
|
1265
1313
|
pane,
|
|
1266
1314
|
filteredGroups
|
|
1267
1315
|
]);
|
|
1316
|
+
const modelPaneOpen = open && pane === "model";
|
|
1317
|
+
const scrollSignal = !modelPaneOpen ? "closed" : keyNav === 0 ? "open" : `key:${keyNav}`;
|
|
1318
|
+
const scrollSignalRef = (0, react.useRef)(null);
|
|
1268
1319
|
(0, react.useLayoutEffect)(() => {
|
|
1269
|
-
if (
|
|
1320
|
+
if (!modelPaneOpen || activeModelIndex < 0) {
|
|
1321
|
+
if (!modelPaneOpen) scrollSignalRef.current = null;
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (scrollSignalRef.current === scrollSignal) return;
|
|
1325
|
+
scrollSignalRef.current = scrollSignal;
|
|
1326
|
+
itemRefs.current[leading.length + activeModelIndex]?.scrollIntoView({ block: "nearest" });
|
|
1270
1327
|
}, [
|
|
1271
|
-
|
|
1272
|
-
|
|
1328
|
+
modelPaneOpen,
|
|
1329
|
+
scrollSignal,
|
|
1273
1330
|
activeModelIndex,
|
|
1274
|
-
visibleModels,
|
|
1275
1331
|
leading.length
|
|
1276
1332
|
]);
|
|
1277
1333
|
(0, react.useLayoutEffect)(() => {
|
|
@@ -1286,7 +1342,7 @@ function ModelMenu(props) {
|
|
|
1286
1342
|
let x = rect.right - lw, y = rect.top - 8 - lh;
|
|
1287
1343
|
if (lw > 0) x = Math.min(Math.max(x, margin), window.innerWidth - lw - margin);
|
|
1288
1344
|
if (lh > 0) y = Math.min(Math.max(y, margin), window.innerHeight - lh - margin);
|
|
1289
|
-
setMenuPos({
|
|
1345
|
+
setMenuPos((prev) => prev && prev.left === x && prev.top === y ? prev : {
|
|
1290
1346
|
left: x,
|
|
1291
1347
|
top: y
|
|
1292
1348
|
});
|
|
@@ -1306,6 +1362,13 @@ function ModelMenu(props) {
|
|
|
1306
1362
|
]);
|
|
1307
1363
|
function onKeyDown(event) {
|
|
1308
1364
|
if (event.defaultPrevented || event.nativeEvent.isComposing) return;
|
|
1365
|
+
if (event.key === "Escape" && open) {
|
|
1366
|
+
event.preventDefault();
|
|
1367
|
+
if (pane !== initialPane) back(pane);
|
|
1368
|
+
else close(true);
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
if (disabled) return;
|
|
1309
1372
|
if (!open && [
|
|
1310
1373
|
"Enter",
|
|
1311
1374
|
" ",
|
|
@@ -1319,17 +1382,12 @@ function ModelMenu(props) {
|
|
|
1319
1382
|
requestOpen();
|
|
1320
1383
|
return;
|
|
1321
1384
|
}
|
|
1322
|
-
if (event.key === "Escape" && open) {
|
|
1323
|
-
event.preventDefault();
|
|
1324
|
-
if (pane !== "root") back(pane);
|
|
1325
|
-
else close(true);
|
|
1326
|
-
return;
|
|
1327
|
-
}
|
|
1328
1385
|
if (!open) return;
|
|
1329
1386
|
if (pane === "model" && showSearch && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
|
|
1330
1387
|
event.preventDefault();
|
|
1331
1388
|
if (visibleModels.length > 0) {
|
|
1332
1389
|
setHighlightedIndex((activeModelIndex + (event.key === "ArrowDown" ? 1 : -1) + visibleModels.length) % visibleModels.length);
|
|
1390
|
+
setKeyNav((n) => n + 1);
|
|
1333
1391
|
searchRef.current?.focus();
|
|
1334
1392
|
}
|
|
1335
1393
|
return;
|
|
@@ -1338,19 +1396,19 @@ function ModelMenu(props) {
|
|
|
1338
1396
|
if (event.key === "Tab" && visibleModels.length === 0) return;
|
|
1339
1397
|
event.preventDefault();
|
|
1340
1398
|
const highlighted = visibleModels[activeModelIndex];
|
|
1341
|
-
if (highlighted) {
|
|
1399
|
+
if (highlighted && !busy) {
|
|
1342
1400
|
onPick({
|
|
1343
1401
|
provider: highlighted.provider,
|
|
1344
1402
|
model: highlighted.model
|
|
1345
1403
|
});
|
|
1346
|
-
close(true);
|
|
1404
|
+
if (closeOnPick) close(true);
|
|
1347
1405
|
}
|
|
1348
1406
|
return;
|
|
1349
1407
|
}
|
|
1350
1408
|
if (event.key === "Tab") {
|
|
1351
1409
|
if (event.shiftKey) {
|
|
1352
1410
|
event.preventDefault();
|
|
1353
|
-
if (pane !==
|
|
1411
|
+
if (pane !== initialPane) back(pane);
|
|
1354
1412
|
else close(true);
|
|
1355
1413
|
return;
|
|
1356
1414
|
}
|
|
@@ -1374,7 +1432,10 @@ function ModelMenu(props) {
|
|
|
1374
1432
|
}
|
|
1375
1433
|
}
|
|
1376
1434
|
function onBlur(event) {
|
|
1377
|
-
|
|
1435
|
+
const related = event.relatedTarget;
|
|
1436
|
+
if (related instanceof Node && (rootRef.current?.contains(related) || menuRef.current?.contains(related) || !related.isConnected)) return;
|
|
1437
|
+
if (!closeOnPick && !(related instanceof Node)) return;
|
|
1438
|
+
if (busy && related == null) return;
|
|
1378
1439
|
if (open) changeOpen(false);
|
|
1379
1440
|
}
|
|
1380
1441
|
itemRefs.current = [];
|
|
@@ -1395,11 +1456,16 @@ function ModelMenu(props) {
|
|
|
1395
1456
|
"aria-label": modelLabel,
|
|
1396
1457
|
lang: language,
|
|
1397
1458
|
children: [
|
|
1459
|
+
menuHeader ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1460
|
+
className: "dsh-model-menu-header",
|
|
1461
|
+
children: menuHeader
|
|
1462
|
+
}) : null,
|
|
1398
1463
|
pane === "root" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1399
1464
|
ref: itemRef(),
|
|
1400
1465
|
type: "button",
|
|
1401
1466
|
role: "menuitem",
|
|
1402
1467
|
className: "dsh-model-menu-cell",
|
|
1468
|
+
disabled,
|
|
1403
1469
|
onClick: () => drill("model"),
|
|
1404
1470
|
children: [
|
|
1405
1471
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1412,11 +1478,12 @@ function ModelMenu(props) {
|
|
|
1412
1478
|
}),
|
|
1413
1479
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: "dsh-model-menu-cellChevron" })
|
|
1414
1480
|
]
|
|
1415
|
-
}), efforts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1481
|
+
}), efforts.length > 0 && !reasoningSlider ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1416
1482
|
ref: itemRef(),
|
|
1417
1483
|
type: "button",
|
|
1418
1484
|
role: "menuitem",
|
|
1419
1485
|
className: "dsh-model-menu-cell",
|
|
1486
|
+
disabled,
|
|
1420
1487
|
onClick: () => drill("effort"),
|
|
1421
1488
|
children: [
|
|
1422
1489
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1435,6 +1502,7 @@ function ModelMenu(props) {
|
|
|
1435
1502
|
className: "dsh-model-menu-searchRow",
|
|
1436
1503
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
1437
1504
|
ref: searchRef,
|
|
1505
|
+
disabled,
|
|
1438
1506
|
className: `dsh-model-menu-search${query !== "" ? " dsh-model-menu-searchWithQuery" : ""}`,
|
|
1439
1507
|
type: "text",
|
|
1440
1508
|
role: "searchbox",
|
|
@@ -1451,6 +1519,7 @@ function ModelMenu(props) {
|
|
|
1451
1519
|
type: "button",
|
|
1452
1520
|
className: "dsh-model-menu-searchClear",
|
|
1453
1521
|
"aria-label": clearSearchLabel,
|
|
1522
|
+
disabled,
|
|
1454
1523
|
onClick: () => {
|
|
1455
1524
|
setQuery("");
|
|
1456
1525
|
setHighlightedIndex(null);
|
|
@@ -1473,10 +1542,12 @@ function ModelMenu(props) {
|
|
|
1473
1542
|
type: "button",
|
|
1474
1543
|
role: "menuitemradio",
|
|
1475
1544
|
"aria-checked": item.selected === true,
|
|
1545
|
+
disabled: disabled || busy,
|
|
1476
1546
|
className: `dsh-model-menu-option${item.selected === true ? " dsh-model-menu-selected" : ""}`,
|
|
1477
1547
|
onClick: () => {
|
|
1548
|
+
if (disabled || busy) return;
|
|
1478
1549
|
onPickLeading?.(item.id);
|
|
1479
|
-
close(true);
|
|
1550
|
+
if (closeOnPick) close(true);
|
|
1480
1551
|
},
|
|
1481
1552
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1482
1553
|
className: "dsh-model-menu-optionCopy",
|
|
@@ -1502,6 +1573,7 @@ function ModelMenu(props) {
|
|
|
1502
1573
|
"aria-checked": checked,
|
|
1503
1574
|
id: `${id}-model-${index}`,
|
|
1504
1575
|
tabIndex: showSearch ? -1 : 0,
|
|
1576
|
+
disabled: disabled || busy,
|
|
1505
1577
|
onFocus: () => setHighlightedIndex(index),
|
|
1506
1578
|
"data-highlighted": index === activeModelIndex ? "" : void 0,
|
|
1507
1579
|
className: `dsh-model-menu-option dsh-model-menu-modelOption${checked ? " dsh-model-menu-selected" : ""}${index === activeModelIndex ? " dsh-model-menu-optionActive" : ""}`,
|
|
@@ -1511,11 +1583,12 @@ function ModelMenu(props) {
|
|
|
1511
1583
|
},
|
|
1512
1584
|
title: choice.label,
|
|
1513
1585
|
onClick: () => {
|
|
1586
|
+
if (disabled || busy) return;
|
|
1514
1587
|
onPick({
|
|
1515
1588
|
provider: choice.provider,
|
|
1516
1589
|
model: choice.model
|
|
1517
1590
|
});
|
|
1518
|
-
close(true);
|
|
1591
|
+
if (closeOnPick) close(true);
|
|
1519
1592
|
},
|
|
1520
1593
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1521
1594
|
className: "dsh-model-menu-optionCopy",
|
|
@@ -1531,6 +1604,14 @@ function ModelMenu(props) {
|
|
|
1531
1604
|
})
|
|
1532
1605
|
}, group.provider))]
|
|
1533
1606
|
}),
|
|
1607
|
+
reasoningSlider && efforts.length > 0 && filteredGroups.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EffortSlider, {
|
|
1608
|
+
label: effortLabel,
|
|
1609
|
+
rows: effortRows,
|
|
1610
|
+
selected: selectedEffort ?? "",
|
|
1611
|
+
disabled,
|
|
1612
|
+
busy,
|
|
1613
|
+
onPick: (value) => onPickEffort?.(value)
|
|
1614
|
+
}) : null,
|
|
1534
1615
|
filteredGroups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1535
1616
|
className: "dsh-model-menu-empty",
|
|
1536
1617
|
role: "status",
|
|
@@ -1544,10 +1625,12 @@ function ModelMenu(props) {
|
|
|
1544
1625
|
type: "button",
|
|
1545
1626
|
role: "menuitemradio",
|
|
1546
1627
|
"aria-checked": checked,
|
|
1628
|
+
disabled: disabled || busy,
|
|
1547
1629
|
className: `dsh-model-menu-option${checked ? " dsh-model-menu-selected" : ""}`,
|
|
1548
1630
|
onClick: () => {
|
|
1631
|
+
if (disabled || busy) return;
|
|
1549
1632
|
onPickEffort?.(row.id || void 0);
|
|
1550
|
-
close(true);
|
|
1633
|
+
if (closeOnPick) close(true);
|
|
1551
1634
|
},
|
|
1552
1635
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1553
1636
|
className: "dsh-model-menu-optionCopy",
|
|
@@ -1566,7 +1649,8 @@ function ModelMenu(props) {
|
|
|
1566
1649
|
const trigger = (0, react.isValidElement)(anchor) ? (0, react.cloneElement)(anchor, {
|
|
1567
1650
|
"aria-haspopup": "menu",
|
|
1568
1651
|
"aria-expanded": open,
|
|
1569
|
-
"aria-controls": open ? `${id}-menu` : void 0
|
|
1652
|
+
"aria-controls": open ? `${id}-menu` : void 0,
|
|
1653
|
+
"aria-disabled": disabled || void 0
|
|
1570
1654
|
}) : anchor;
|
|
1571
1655
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1572
1656
|
ref: rootRef,
|
|
@@ -1574,7 +1658,7 @@ function ModelMenu(props) {
|
|
|
1574
1658
|
onKeyDown,
|
|
1575
1659
|
onBlur,
|
|
1576
1660
|
onClick: (event) => {
|
|
1577
|
-
if (event.defaultPrevented) return;
|
|
1661
|
+
if (disabled || event.defaultPrevented) return;
|
|
1578
1662
|
const button = event.target instanceof Element ? event.target.closest("button") : null;
|
|
1579
1663
|
if (!(button instanceof HTMLButtonElement) || button.disabled || !rootRef.current?.contains(button)) return;
|
|
1580
1664
|
requestOpen();
|
|
@@ -1586,6 +1670,102 @@ function ModelMenu(props) {
|
|
|
1586
1670
|
});
|
|
1587
1671
|
}
|
|
1588
1672
|
/**
|
|
1673
|
+
* Discrete reasoning-effort slider shown under the model list in enhanced mode.
|
|
1674
|
+
*
|
|
1675
|
+
* A native range input (`step=1`) over exactly the rows the caller supplies —
|
|
1676
|
+
* the default row plus each effort the selected model advertises — so it can
|
|
1677
|
+
* never invent a level. It previews locally while dragging or arrowing and
|
|
1678
|
+
* commits only when the gesture ends (pointer up, key up, or blur), so a drag
|
|
1679
|
+
* does not write on every step. The menu stays open, so adjusting the effort
|
|
1680
|
+
* never loses the model selection or the search query.
|
|
1681
|
+
*
|
|
1682
|
+
* The input itself stays the entire interaction and accessibility surface —
|
|
1683
|
+
* keyboard, ARIA, disabled and the native step snapping all come from it —
|
|
1684
|
+
* while the track, gradient fill and thumb below are decorative layers painted
|
|
1685
|
+
* from the `--_p` percentage the preview position sets on the rail. Value
|
|
1686
|
+
* changes only update that custom property, so the CSS transitions animate
|
|
1687
|
+
* between levels without remounting nodes or restarting an animation.
|
|
1688
|
+
*/
|
|
1689
|
+
function EffortSlider(props) {
|
|
1690
|
+
const { label, rows, selected, disabled = false, busy = false, onPick } = props;
|
|
1691
|
+
const active = Math.max(0, rows.findIndex((row) => row.id === selected));
|
|
1692
|
+
const [position, setPosition] = (0, react.useState)(active);
|
|
1693
|
+
const positionRef = (0, react.useRef)(position);
|
|
1694
|
+
(0, react.useEffect)(() => {
|
|
1695
|
+
positionRef.current = active;
|
|
1696
|
+
setPosition(active);
|
|
1697
|
+
}, [active]);
|
|
1698
|
+
const preview = (next) => {
|
|
1699
|
+
positionRef.current = next;
|
|
1700
|
+
setPosition(next);
|
|
1701
|
+
};
|
|
1702
|
+
const commit = () => {
|
|
1703
|
+
if (disabled || busy) return;
|
|
1704
|
+
const row = rows[positionRef.current];
|
|
1705
|
+
if (row !== void 0) onPick(row.id === "" ? void 0 : row.id);
|
|
1706
|
+
};
|
|
1707
|
+
const max = Math.max(0, rows.length - 1);
|
|
1708
|
+
const percent = max > 0 ? Math.round(position / max * 100) : 0;
|
|
1709
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1710
|
+
className: "dsh-model-menu-sliderGroup",
|
|
1711
|
+
role: "group",
|
|
1712
|
+
"aria-label": label,
|
|
1713
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1714
|
+
className: "dsh-model-menu-sliderHead",
|
|
1715
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1716
|
+
className: "dsh-model-menu-sliderTitle",
|
|
1717
|
+
children: label
|
|
1718
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1719
|
+
className: "dsh-model-menu-sliderValue",
|
|
1720
|
+
children: rows[position]?.name
|
|
1721
|
+
})]
|
|
1722
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1723
|
+
className: "dsh-model-menu-sliderRail",
|
|
1724
|
+
style: { "--_p": percent },
|
|
1725
|
+
children: [
|
|
1726
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1727
|
+
type: "range",
|
|
1728
|
+
className: "dsh-model-menu-sliderInput",
|
|
1729
|
+
min: 0,
|
|
1730
|
+
max,
|
|
1731
|
+
step: 1,
|
|
1732
|
+
value: position,
|
|
1733
|
+
disabled,
|
|
1734
|
+
"aria-label": label,
|
|
1735
|
+
"aria-disabled": disabled || busy || void 0,
|
|
1736
|
+
"aria-valuetext": rows[position]?.name,
|
|
1737
|
+
onChange: (event) => {
|
|
1738
|
+
const value = Number(event.target.value);
|
|
1739
|
+
if (Number.isFinite(value)) preview(value);
|
|
1740
|
+
},
|
|
1741
|
+
onPointerUp: commit,
|
|
1742
|
+
onKeyUp: commit,
|
|
1743
|
+
onBlur: commit
|
|
1744
|
+
}),
|
|
1745
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1746
|
+
className: "dsh-model-menu-sliderTrack",
|
|
1747
|
+
"aria-hidden": "true",
|
|
1748
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1749
|
+
className: "dsh-model-menu-sliderFill",
|
|
1750
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsh-model-menu-sliderFillBar" })
|
|
1751
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1752
|
+
className: "dsh-model-menu-sliderNodes",
|
|
1753
|
+
children: rows.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1754
|
+
"data-covered": index <= position ? "" : void 0,
|
|
1755
|
+
className: "dsh-model-menu-sliderNode",
|
|
1756
|
+
style: { "--_n": max > 0 ? Math.round(index / max * 100) : 0 }
|
|
1757
|
+
}, row.id || "__default"))
|
|
1758
|
+
})]
|
|
1759
|
+
}),
|
|
1760
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1761
|
+
className: "dsh-model-menu-sliderThumb",
|
|
1762
|
+
"aria-hidden": "true"
|
|
1763
|
+
})
|
|
1764
|
+
]
|
|
1765
|
+
})]
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1589
1769
|
* ModelMenu styles, transcribed from the official ModelSelect.module.css (the
|
|
1590
1770
|
* published dsh-client-ui-model-selection bundle): card capped at
|
|
1591
1771
|
* min(360px, 100vh - 96px), 34px option cells at 13/18 with 5px 7px padding,
|
|
@@ -1644,6 +1824,7 @@ const modelMenuCss = `
|
|
|
1644
1824
|
overflow: hidden;
|
|
1645
1825
|
}
|
|
1646
1826
|
.dsh-model-menu-status, .dsh-model-menu-empty { color: var(--dsw-alias-label-tertiary); padding: 8px; font-size: 12px; line-height: 18px; }
|
|
1827
|
+
.dsh-model-menu-header { flex: none; padding: 2px 2px 4px; }
|
|
1647
1828
|
.dsh-model-menu-searchRow { flex-shrink: 0; margin: 2px 0 3px; position: relative; }
|
|
1648
1829
|
.dsh-model-menu-searchRow .dsh-model-menu-search { border-radius: var(--dsw-radius-md); background: none; border: 0 solid transparent; height: auto; padding: 5px 7px; display: flex; }
|
|
1649
1830
|
.dsh-model-menu-searchRow .dsh-model-menu-search:focus-within { border-color: transparent; }
|
|
@@ -1722,6 +1903,145 @@ const modelMenuCss = `
|
|
|
1722
1903
|
.dsh-model-menu-cellLabel { white-space: nowrap; flex: none; }
|
|
1723
1904
|
.dsh-model-menu-cellValue { text-overflow: ellipsis; white-space: nowrap; text-align: right; min-width: 0; color: var(--dsw-alias-label-tertiary); flex: auto; overflow: hidden; }
|
|
1724
1905
|
.dsh-model-menu-cellChevron { width: 12px; height: 12px; color: var(--dsw-alias-menu-icon); flex: none; }
|
|
1906
|
+
/* Discrete reasoning-effort slider under the model list (enhanced mode).
|
|
1907
|
+
* The painted control is layered over a fully functional native range input:
|
|
1908
|
+
* the input stays on top, transparent, and owns pointer/keyboard/ARIA, while
|
|
1909
|
+
* the decorative track, fill and thumb render beneath it at pointer-events:
|
|
1910
|
+
* none. Position lives in one custom property (--_p, 0–100) on the rail, so
|
|
1911
|
+
* the only animated work a level change triggers is a clip-path/left
|
|
1912
|
+
* transition — no keyframes, no element churn. */
|
|
1913
|
+
.dsh-model-menu-sliderGroup { border-top: 0.5px solid var(--dsw-alias-border-l2); flex: none; margin-top: 4px; padding: 6px 8px 8px; }
|
|
1914
|
+
.dsh-model-menu-sliderHead { align-items: baseline; justify-content: space-between; gap: 8px; margin: 0 2px 4px; display: flex; }
|
|
1915
|
+
.dsh-model-menu-sliderTitle { color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
|
|
1916
|
+
.dsh-model-menu-sliderValue { color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 600; line-height: 20px; transition: color var(--ds-transition-duration) var(--ds-ease-in-out); }
|
|
1917
|
+
.dsh-model-menu-sliderRail { position: relative; direction: ltr; height: 38px; margin-top: 2px; }
|
|
1918
|
+
.dsh-model-menu-sliderRail:has(:disabled) { opacity: 0.45; }
|
|
1919
|
+
.dsh-model-menu-sliderInput {
|
|
1920
|
+
appearance: none;
|
|
1921
|
+
-webkit-appearance: none;
|
|
1922
|
+
box-sizing: border-box;
|
|
1923
|
+
border: 0;
|
|
1924
|
+
cursor: pointer;
|
|
1925
|
+
direction: ltr;
|
|
1926
|
+
opacity: 0;
|
|
1927
|
+
margin: 0;
|
|
1928
|
+
padding: 0;
|
|
1929
|
+
width: 100%;
|
|
1930
|
+
height: 100%;
|
|
1931
|
+
position: absolute;
|
|
1932
|
+
inset: 0;
|
|
1933
|
+
z-index: 1;
|
|
1934
|
+
touch-action: pan-y;
|
|
1935
|
+
}
|
|
1936
|
+
.dsh-model-menu-sliderInput:disabled { cursor: default; }
|
|
1937
|
+
/* The transparent input must keep the painted thumb's geometry: an
|
|
1938
|
+
* appearance:none webkit thumb defaults to 16px, which would shift the
|
|
1939
|
+
* end-point positions 7px off the decorative 30px disc and misplace the
|
|
1940
|
+
* native drag hit target. Track gets the full rail height so the whole band
|
|
1941
|
+
* maps to values identically. */
|
|
1942
|
+
.dsh-model-menu-sliderInput::-webkit-slider-runnable-track { width: 100%; height: 100%; }
|
|
1943
|
+
.dsh-model-menu-sliderInput::-webkit-slider-thumb { appearance: none; -webkit-appearance: none; width: 30px; height: 30px; margin: 0; }
|
|
1944
|
+
.dsh-model-menu-sliderInput::-moz-range-track { width: 100%; height: 100%; }
|
|
1945
|
+
.dsh-model-menu-sliderInput::-moz-range-thumb { width: 30px; height: 30px; margin: 0; border: 0; }
|
|
1946
|
+
.dsh-model-menu-sliderTrack {
|
|
1947
|
+
pointer-events: none;
|
|
1948
|
+
border-radius: 999px;
|
|
1949
|
+
width: 100%;
|
|
1950
|
+
height: 26px;
|
|
1951
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1952
|
+
box-shadow: inset 0 0 0 0.5px var(--dsw-alias-border-l2);
|
|
1953
|
+
position: absolute;
|
|
1954
|
+
inset-inline: 0;
|
|
1955
|
+
top: 50%;
|
|
1956
|
+
transform: translateY(-50%);
|
|
1957
|
+
/* No overflow:hidden — it would clip the fill's drop-shadow glow; the bar's
|
|
1958
|
+
* own clip-path already supplies the rounded reveal shape. */
|
|
1959
|
+
}
|
|
1960
|
+
/* The accent ramp paints the full track width once and is revealed by an
|
|
1961
|
+
* animated clip-path, so raising the effort unwraps more of the spectrum
|
|
1962
|
+
* instead of squashing the same gradient into a narrower bar. The glow lives
|
|
1963
|
+
* on the un-clipped parent: a filter on the clipped element itself would be
|
|
1964
|
+
* clipped away with it. */
|
|
1965
|
+
.dsh-model-menu-sliderFill {
|
|
1966
|
+
display: block;
|
|
1967
|
+
height: 100%;
|
|
1968
|
+
filter: drop-shadow(0 0 5px var(--dsw-alias-state-business-primary));
|
|
1969
|
+
transition: filter var(--ds-transition-duration) var(--ds-ease-in-out);
|
|
1970
|
+
}
|
|
1971
|
+
.dsh-model-menu-sliderInput:hover:not(:disabled) ~ .dsh-model-menu-sliderTrack .dsh-model-menu-sliderFill,
|
|
1972
|
+
.dsh-model-menu-sliderInput:active:not(:disabled) ~ .dsh-model-menu-sliderTrack .dsh-model-menu-sliderFill {
|
|
1973
|
+
filter: drop-shadow(0 0 9px var(--dsw-alias-state-business-primary));
|
|
1974
|
+
}
|
|
1975
|
+
/* The fill stops at the thumb's center, not at the track edge — at a 30px
|
|
1976
|
+
* thumb the end stops sit 15px in from each side, matching the node dots. */
|
|
1977
|
+
.dsh-model-menu-sliderFillBar {
|
|
1978
|
+
display: block;
|
|
1979
|
+
height: 100%;
|
|
1980
|
+
background: linear-gradient(90deg,
|
|
1981
|
+
var(--dsw-alias-state-business-primary),
|
|
1982
|
+
var(--dsw-alias-state-success-primary),
|
|
1983
|
+
var(--dsw-alias-state-warn-primary),
|
|
1984
|
+
var(--dsw-alias-state-error-primary));
|
|
1985
|
+
clip-path: inset(0 calc(100% - 15px - (100% - 30px) * var(--_p, 0) / 100) 0 0 round 999px);
|
|
1986
|
+
transition: clip-path var(--ds-transition-duration) var(--ds-ease-in-out);
|
|
1987
|
+
}
|
|
1988
|
+
/* Stop dots: one per declared row (default + every advertised effort), evenly
|
|
1989
|
+
* spread across the band between the two thumb-center extremes (inset 15px,
|
|
1990
|
+
* i.e. half the 30px thumb). --_n is 0-100 along that band. A covered dot
|
|
1991
|
+
* reads as a pale pin through the gradient; an uncovered one stays a neutral
|
|
1992
|
+
* notch on the empty track. */
|
|
1993
|
+
.dsh-model-menu-sliderNodes {
|
|
1994
|
+
pointer-events: none;
|
|
1995
|
+
display: block;
|
|
1996
|
+
position: absolute;
|
|
1997
|
+
inset-inline: 15px;
|
|
1998
|
+
inset-block: 0;
|
|
1999
|
+
}
|
|
2000
|
+
.dsh-model-menu-sliderNode {
|
|
2001
|
+
pointer-events: none;
|
|
2002
|
+
box-sizing: border-box;
|
|
2003
|
+
width: 4px;
|
|
2004
|
+
height: 4px;
|
|
2005
|
+
border-radius: 50%;
|
|
2006
|
+
background: color-mix(in srgb, var(--dsw-alias-label-secondary) 35%, transparent);
|
|
2007
|
+
position: absolute;
|
|
2008
|
+
left: calc(var(--_n, 0) * 1%);
|
|
2009
|
+
top: 50%;
|
|
2010
|
+
transform: translate(-50%, -50%);
|
|
2011
|
+
transition: background-color var(--ds-transition-duration) var(--ds-ease-in-out),
|
|
2012
|
+
transform var(--ds-transition-duration) var(--ds-ease-in-out);
|
|
2013
|
+
}
|
|
2014
|
+
.dsh-model-menu-sliderNode[data-covered] { background: color-mix(in srgb, var(--dsw-alias-label-primary) 60%, transparent); }
|
|
2015
|
+
.dsh-model-menu-sliderThumb {
|
|
2016
|
+
pointer-events: none;
|
|
2017
|
+
box-sizing: border-box;
|
|
2018
|
+
border-radius: 50%;
|
|
2019
|
+
width: 30px;
|
|
2020
|
+
height: 30px;
|
|
2021
|
+
background: var(--dsw-alias-button-primary-fill);
|
|
2022
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
2023
|
+
box-shadow: 0 0 0 3px var(--dsw-alias-interactive-bg-hover), var(--dsw-elevation-soft);
|
|
2024
|
+
position: absolute;
|
|
2025
|
+
left: calc((100% - 30px) * var(--_p, 0) / 100);
|
|
2026
|
+
top: 50%;
|
|
2027
|
+
transform: translateY(-50%) scale(0.92);
|
|
2028
|
+
transition: left var(--ds-transition-duration) var(--ds-ease-in-out),
|
|
2029
|
+
transform var(--ds-transition-duration) var(--ds-ease-in-out),
|
|
2030
|
+
box-shadow var(--ds-transition-duration) var(--ds-ease-in-out);
|
|
2031
|
+
}
|
|
2032
|
+
.dsh-model-menu-sliderInput:hover:not(:disabled) ~ .dsh-model-menu-sliderThumb { transform: translateY(-50%) scale(1); }
|
|
2033
|
+
.dsh-model-menu-sliderInput:active:not(:disabled) ~ .dsh-model-menu-sliderThumb {
|
|
2034
|
+
transform: translateY(-50%) scale(1.1);
|
|
2035
|
+
box-shadow: 0 0 0 4px var(--dsw-alias-interactive-bg-hover), 0 0 12px var(--dsw-alias-state-business-primary);
|
|
2036
|
+
}
|
|
2037
|
+
.dsh-model-menu-sliderInput:focus-visible ~ .dsh-model-menu-sliderThumb {
|
|
2038
|
+
box-shadow: 0 0 0 3px var(--dsw-alias-interactive-bg-hover),
|
|
2039
|
+
0 0 0 calc(3px + var(--dsw-focus-ring-width)) var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
2040
|
+
}
|
|
2041
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2042
|
+
.dsh-model-menu-sliderFill, .dsh-model-menu-sliderFillBar, .dsh-model-menu-sliderThumb,
|
|
2043
|
+
.dsh-model-menu-sliderNode, .dsh-model-menu-sliderValue { transition: none; }
|
|
2044
|
+
}
|
|
1725
2045
|
`;
|
|
1726
2046
|
//#endregion
|
|
1727
2047
|
//#region src/styles.ts
|
|
@@ -1888,42 +2208,24 @@ ${modelMenuCss}
|
|
|
1888
2208
|
}
|
|
1889
2209
|
.${FREE_CHAT_ROOT}.dsm-item[open] > .dsm-item-summary .dsm-item-chevron { transform: rotate(180deg); }
|
|
1890
2210
|
.${FREE_CHAT_ROOT} .dsm-item-body { padding: 0 0 16px; }
|
|
1891
|
-
/* Settings
|
|
1892
|
-
*
|
|
2211
|
+
/* Settings row bodies: each collapsible row edits its own keys and saves on
|
|
2212
|
+
* change, so the body is one flat reading surface — a feature block per
|
|
2213
|
+
* switch/section, dependent controls indented underneath. */
|
|
1893
2214
|
.${FREE_CHAT_ROOT} .dsm-settings {
|
|
1894
|
-
display: flex;
|
|
1895
|
-
flex-direction: column;
|
|
1896
|
-
gap: 20px;
|
|
1897
|
-
min-width: 0;
|
|
1898
|
-
}
|
|
1899
|
-
.${FREE_CHAT_ROOT} .dsm-setting {
|
|
1900
2215
|
display: flex;
|
|
1901
2216
|
flex-direction: column;
|
|
1902
2217
|
gap: 12px;
|
|
1903
2218
|
min-width: 0;
|
|
1904
2219
|
}
|
|
1905
|
-
.${FREE_CHAT_ROOT} .dsm-
|
|
1906
|
-
padding-top: 20px;
|
|
1907
|
-
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
1908
|
-
}
|
|
1909
|
-
.${FREE_CHAT_ROOT} .dsm-setting-header {
|
|
2220
|
+
.${FREE_CHAT_ROOT} .dsm-feature {
|
|
1910
2221
|
display: flex;
|
|
1911
2222
|
flex-direction: column;
|
|
1912
|
-
gap:
|
|
1913
|
-
|
|
1914
|
-
.${FREE_CHAT_ROOT} .dsm-ghost-controls {
|
|
1915
|
-
display: grid;
|
|
1916
|
-
grid-template-columns: minmax(220px, 0.8fr) minmax(0, 1.2fr);
|
|
1917
|
-
align-items: start;
|
|
1918
|
-
gap: 16px;
|
|
1919
|
-
}
|
|
1920
|
-
/* SegmentedTabs is 42px tall; its adjacent text controls share that baseline. */
|
|
1921
|
-
.${FREE_CHAT_ROOT} .dsm-ghost-controls .dsh-model-menu-trigger,
|
|
1922
|
-
.${FREE_CHAT_ROOT} .dsm-ghost-controls .dsm-ghost-input {
|
|
1923
|
-
height: 42px;
|
|
2223
|
+
gap: 8px;
|
|
2224
|
+
min-width: 0;
|
|
1924
2225
|
}
|
|
1925
2226
|
.${FREE_CHAT_ROOT} .dsm-feature + .dsm-feature {
|
|
1926
2227
|
margin-top: 4px;
|
|
2228
|
+
padding-top: 4px;
|
|
1927
2229
|
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
1928
2230
|
}
|
|
1929
2231
|
.${FREE_CHAT_ROOT} .dsm-feature > .dsh-ui-toggle-row { padding: 8px 0; }
|
|
@@ -1931,20 +2233,44 @@ ${modelMenuCss}
|
|
|
1931
2233
|
display: flex;
|
|
1932
2234
|
flex-direction: column;
|
|
1933
2235
|
gap: 8px;
|
|
1934
|
-
padding:
|
|
2236
|
+
padding: 0 0 8px 24px;
|
|
1935
2237
|
}
|
|
1936
2238
|
.${FREE_CHAT_ROOT} .dsm-feature-options[data-disabled='true'] .dsh-ui-label,
|
|
1937
2239
|
.${FREE_CHAT_ROOT} .dsm-feature-options[data-disabled='true'] .dsh-ui-meta {
|
|
1938
2240
|
color: var(--dsw-alias-label-tertiary);
|
|
1939
2241
|
}
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
.${FREE_CHAT_ROOT} .dsm-model-field
|
|
1943
|
-
|
|
1944
|
-
|
|
2242
|
+
/* A feature's model picker: label on the left, the menu trigger on the right;
|
|
2243
|
+
* narrow widths wrap the trigger under the label. */
|
|
2244
|
+
.${FREE_CHAT_ROOT} .dsm-model-field {
|
|
2245
|
+
display: flex;
|
|
2246
|
+
flex-direction: column;
|
|
2247
|
+
gap: 4px;
|
|
2248
|
+
min-width: 0;
|
|
2249
|
+
}
|
|
2250
|
+
.${FREE_CHAT_ROOT} .dsm-model-choice .dsh-model-menu-trigger:disabled {
|
|
2251
|
+
color: var(--dsw-alias-label-tertiary);
|
|
2252
|
+
cursor: not-allowed;
|
|
2253
|
+
}
|
|
2254
|
+
.${FREE_CHAT_ROOT} .dsm-model-choice {
|
|
2255
|
+
display: flex;
|
|
2256
|
+
flex-wrap: wrap;
|
|
2257
|
+
align-items: center;
|
|
2258
|
+
justify-content: space-between;
|
|
2259
|
+
gap: 4px 16px;
|
|
1945
2260
|
}
|
|
1946
|
-
.${FREE_CHAT_ROOT} .dsm-model-
|
|
2261
|
+
.${FREE_CHAT_ROOT} .dsm-model-choice > .dsh-ui-label {
|
|
2262
|
+
flex: 1 1 120px;
|
|
2263
|
+
min-width: 120px;
|
|
2264
|
+
}
|
|
2265
|
+
.${FREE_CHAT_ROOT} .dsm-model-choice .dsh-model-menu-root {
|
|
2266
|
+
flex: 1 1 200px;
|
|
2267
|
+
min-width: min(200px, 100%);
|
|
2268
|
+
max-width: 280px;
|
|
2269
|
+
}
|
|
2270
|
+
.${FREE_CHAT_ROOT} .dsm-model-choice .dsh-model-menu-trigger {
|
|
2271
|
+
display: flex;
|
|
1947
2272
|
justify-content: space-between;
|
|
2273
|
+
width: 100%;
|
|
1948
2274
|
height: 32px;
|
|
1949
2275
|
padding: 0 10px;
|
|
1950
2276
|
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
@@ -1952,13 +2278,13 @@ ${modelMenuCss}
|
|
|
1952
2278
|
background: var(--dsw-alias-bg-layer-3);
|
|
1953
2279
|
color: var(--dsw-alias-label-primary);
|
|
1954
2280
|
}
|
|
1955
|
-
.${FREE_CHAT_ROOT} .dsm-model-
|
|
2281
|
+
.${FREE_CHAT_ROOT} .dsm-model-choice .dsh-model-menu-triggerLabel {
|
|
1956
2282
|
flex: 1;
|
|
1957
|
-
text-align: left;
|
|
1958
|
-
}
|
|
1959
|
-
.${FREE_CHAT_ROOT} .dsm-settings-footer .dsh-ui-notice--error {
|
|
1960
|
-
flex: 0 0 100%;
|
|
1961
2283
|
min-width: 0;
|
|
2284
|
+
overflow: hidden;
|
|
2285
|
+
text-align: left;
|
|
2286
|
+
text-overflow: ellipsis;
|
|
2287
|
+
white-space: nowrap;
|
|
1962
2288
|
}
|
|
1963
2289
|
.${FREE_CHAT_ROOT} .dsm-minutes-control {
|
|
1964
2290
|
display: flex;
|
|
@@ -1966,10 +2292,7 @@ ${modelMenuCss}
|
|
|
1966
2292
|
gap: 8px;
|
|
1967
2293
|
}
|
|
1968
2294
|
.${FREE_CHAT_ROOT} .dsm-minutes { width: 88px; }
|
|
1969
|
-
|
|
1970
|
-
.${FREE_CHAT_ROOT} .dsm-ghost-controls { grid-template-columns: minmax(0, 1fr); }
|
|
1971
|
-
.${FREE_CHAT_ROOT} .dsm-compact-options { max-width: none; }
|
|
1972
|
-
}
|
|
2295
|
+
.${FREE_CHAT_ROOT} .dsm-text-input { max-width: 320px; }
|
|
1973
2296
|
/* Welcome-back card: one flat full-width row at the latest turn's tail in the
|
|
1974
2297
|
* chat flow — a single bordered surface with the recap laid out inline, no
|
|
1975
2298
|
* nested box. */
|
|
@@ -3225,6 +3548,19 @@ async function callContinueSuggest(host, sessionId, knownSeq, signal) {
|
|
|
3225
3548
|
...value?.fallback === true ? { fallback: true } : {}
|
|
3226
3549
|
};
|
|
3227
3550
|
}
|
|
3551
|
+
/**
|
|
3552
|
+
* A host that predates the agent-action flags returns a config view without
|
|
3553
|
+
* them, and a malformed one may carry a non-boolean. Both normalize to
|
|
3554
|
+
* false: an absent or unusable flag must never read as granted. Only a real
|
|
3555
|
+
* boolean true keeps the permission on.
|
|
3556
|
+
*/
|
|
3557
|
+
function normalizeConfigView(view) {
|
|
3558
|
+
return {
|
|
3559
|
+
...view,
|
|
3560
|
+
allowSelfFork: view.allowSelfFork === true,
|
|
3561
|
+
allowSendMessage: view.allowSendMessage === true
|
|
3562
|
+
};
|
|
3563
|
+
}
|
|
3228
3564
|
function createPresenceConfigModel(host, delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
3229
3565
|
const events = store();
|
|
3230
3566
|
const lifetime = new AbortController();
|
|
@@ -3235,6 +3571,8 @@ function createPresenceConfigModel(host, delay = (ms) => new Promise((resolve) =
|
|
|
3235
3571
|
let failure;
|
|
3236
3572
|
let snapshot = build();
|
|
3237
3573
|
let readGeneration = 0;
|
|
3574
|
+
/** Several settings rows share this model; collapse concurrent reads into one call. */
|
|
3575
|
+
let inflightLoad;
|
|
3238
3576
|
function build() {
|
|
3239
3577
|
return {
|
|
3240
3578
|
phase,
|
|
@@ -3260,23 +3598,29 @@ function createPresenceConfigModel(host, delay = (ms) => new Promise((resolve) =
|
|
|
3260
3598
|
return view[key] === value;
|
|
3261
3599
|
});
|
|
3262
3600
|
}
|
|
3601
|
+
async function doLoad() {
|
|
3602
|
+
const generation = ++readGeneration;
|
|
3603
|
+
try {
|
|
3604
|
+
const view = await callPresence(host, CONFIG_READ_ENDPOINT, {}, lifetime.signal);
|
|
3605
|
+
if (lifetime.signal.aborted || generation !== readGeneration) return;
|
|
3606
|
+
values = normalizeConfigView(view);
|
|
3607
|
+
phase = "ready";
|
|
3608
|
+
failure = void 0;
|
|
3609
|
+
} catch (error) {
|
|
3610
|
+
if (lifetime.signal.aborted || generation !== readGeneration) return;
|
|
3611
|
+
phase = "unavailable";
|
|
3612
|
+
failure = readFailure(error);
|
|
3613
|
+
}
|
|
3614
|
+
publish();
|
|
3615
|
+
}
|
|
3263
3616
|
return {
|
|
3264
3617
|
subscribe: events.subscribe,
|
|
3265
3618
|
getSnapshot: () => snapshot,
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
values = view;
|
|
3272
|
-
phase = "ready";
|
|
3273
|
-
failure = void 0;
|
|
3274
|
-
} catch (error) {
|
|
3275
|
-
if (lifetime.signal.aborted || generation !== readGeneration) return;
|
|
3276
|
-
phase = "unavailable";
|
|
3277
|
-
failure = readFailure(error);
|
|
3278
|
-
}
|
|
3279
|
-
publish();
|
|
3619
|
+
load() {
|
|
3620
|
+
inflightLoad ??= doLoad().finally(() => {
|
|
3621
|
+
inflightLoad = void 0;
|
|
3622
|
+
});
|
|
3623
|
+
return inflightLoad;
|
|
3280
3624
|
},
|
|
3281
3625
|
async save(patch) {
|
|
3282
3626
|
if (saving || lifetime.signal.aborted) return;
|
|
@@ -3293,14 +3637,14 @@ function createPresenceConfigModel(host, delay = (ms) => new Promise((resolve) =
|
|
|
3293
3637
|
await delay(400);
|
|
3294
3638
|
let view;
|
|
3295
3639
|
try {
|
|
3296
|
-
view = await callPresence(host, CONFIG_READ_ENDPOINT, {}, lifetime.signal);
|
|
3640
|
+
view = normalizeConfigView(await callPresence(host, CONFIG_READ_ENDPOINT, {}, lifetime.signal));
|
|
3297
3641
|
} catch (error) {
|
|
3298
3642
|
if (lifetime.signal.aborted || attempt === 9) throw error;
|
|
3299
3643
|
continue;
|
|
3300
3644
|
}
|
|
3301
3645
|
if (same(view, patch)) {
|
|
3302
3646
|
++readGeneration;
|
|
3303
|
-
values = view;
|
|
3647
|
+
values = normalizeConfigView(view);
|
|
3304
3648
|
phase = "ready";
|
|
3305
3649
|
saved = true;
|
|
3306
3650
|
saving = false;
|
|
@@ -3812,6 +4156,95 @@ function registerSessionSettingsItem(ctx, item, component) {
|
|
|
3812
4156
|
};
|
|
3813
4157
|
}
|
|
3814
4158
|
//#endregion
|
|
4159
|
+
//#region ../dsh-plugin-kit/src/model-picker.tsx
|
|
4160
|
+
/**
|
|
4161
|
+
* Optional shared model picker (`klarkxy.model-picker` Factory).
|
|
4162
|
+
*
|
|
4163
|
+
* `@klarkxy/dsh-model-picker`, when installed, registers one root-scoped
|
|
4164
|
+
* Component Factory under `MODEL_PICKER_FACTORY`: an enhanced model menu with
|
|
4165
|
+
* a searchable provider-grouped list and a reasoning-effort slider, built on
|
|
4166
|
+
* the same ModelMenu recipe. Consumers keep their own ModelMenu as the
|
|
4167
|
+
* `fallback`; when no definition is registered the renderer resolves that
|
|
4168
|
+
* fallback verbatim, so the shared picker is strictly opt-in and every saved
|
|
4169
|
+
* value keeps its existing write path.
|
|
4170
|
+
*
|
|
4171
|
+
* `renderFactorySlot` is a framework-composed prop, never a context: the
|
|
4172
|
+
* component a plugin registers into any slot receives it automatically, but
|
|
4173
|
+
* the plugin must pass it down to whichever child renders the menu (or to the
|
|
4174
|
+
* render callback handed to registerSessionSettingsItem).
|
|
4175
|
+
*
|
|
4176
|
+
* The Factory manages its own open state; a consumer's `open`/`onOpenChange`
|
|
4177
|
+
* pair belongs to the fallback menu only and must not be fed to both.
|
|
4178
|
+
*/
|
|
4179
|
+
/**
|
|
4180
|
+
* The trigger every consumer used to hand its own ModelMenu, rebuilt as the
|
|
4181
|
+
* factory's `anchor` local component: identical classes and chevron, the menu
|
|
4182
|
+
* state coming from the occurrence's props instead of local open state.
|
|
4183
|
+
*
|
|
4184
|
+
* `ariaLabel` and `title` are the whole accessible name and tooltip (the
|
|
4185
|
+
* caller formats "field: current route" itself because the factory's resolved
|
|
4186
|
+
* caption may intentionally be a short name). The component identity stays
|
|
4187
|
+
* put across label changes — a new function would unmount the trigger, and
|
|
4188
|
+
* its cleanup would close a menu that `closeOnPick={false}` is keeping open.
|
|
4189
|
+
* The latest label, title, and `onClose` are read from refs.
|
|
4190
|
+
*/
|
|
4191
|
+
function useModelMenuAnchor(ariaLabel, title, onClose) {
|
|
4192
|
+
const closeRef = (0, react.useRef)(onClose);
|
|
4193
|
+
const labelRef = (0, react.useRef)(ariaLabel);
|
|
4194
|
+
const titleRef = (0, react.useRef)(title);
|
|
4195
|
+
closeRef.current = onClose;
|
|
4196
|
+
labelRef.current = ariaLabel;
|
|
4197
|
+
titleRef.current = title;
|
|
4198
|
+
return (0, react.useMemo)(() => {
|
|
4199
|
+
const ModelPickerTrigger = (props) => {
|
|
4200
|
+
(0, react.useEffect)(() => () => closeRef.current?.(), []);
|
|
4201
|
+
const record = props;
|
|
4202
|
+
const injected = {};
|
|
4203
|
+
for (const [key, value] of Object.entries(record)) if (key.startsWith("aria-")) injected[key] = value;
|
|
4204
|
+
const extraClass = typeof record.className === "string" ? ` ${record.className}` : "";
|
|
4205
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4206
|
+
type: "button",
|
|
4207
|
+
className: `dsh-model-menu-trigger${extraClass}`,
|
|
4208
|
+
disabled: props.disabled,
|
|
4209
|
+
"aria-haspopup": "menu",
|
|
4210
|
+
"aria-expanded": props.open,
|
|
4211
|
+
...injected,
|
|
4212
|
+
"aria-label": labelRef.current,
|
|
4213
|
+
title: titleRef.current,
|
|
4214
|
+
children: [
|
|
4215
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4216
|
+
className: "dsh-model-menu-triggerLabel",
|
|
4217
|
+
children: props.label
|
|
4218
|
+
}),
|
|
4219
|
+
props.effortLabel ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4220
|
+
className: "dsh-model-menu-triggerEffort",
|
|
4221
|
+
children: props.effortLabel
|
|
4222
|
+
}) : null,
|
|
4223
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${props.open ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
4224
|
+
]
|
|
4225
|
+
});
|
|
4226
|
+
};
|
|
4227
|
+
return ModelPickerTrigger;
|
|
4228
|
+
}, []);
|
|
4229
|
+
}
|
|
4230
|
+
/**
|
|
4231
|
+
* Render the shared model picker while it is registered; `options.fallback`
|
|
4232
|
+
* whenever it is not (plugin absent, disabled, or unloaded).
|
|
4233
|
+
*
|
|
4234
|
+
* `options.anchor` is optional: the factory ships a default trigger, so only
|
|
4235
|
+
* consumers that need their own chrome (custom aria-label, tooltip, class)
|
|
4236
|
+
* pass one built with {@link useModelMenuAnchor}. The returned node is never
|
|
4237
|
+
* assumed to be the factory's own output — a defensive `?? fallback` covers a
|
|
4238
|
+
* renderer that yields null for an unregistered definition.
|
|
4239
|
+
*/
|
|
4240
|
+
function renderModelPicker(renderFactorySlot, props, options) {
|
|
4241
|
+
if (typeof renderFactorySlot !== "function") return options.fallback;
|
|
4242
|
+
return renderFactorySlot("klarkxy.model-picker", props, {
|
|
4243
|
+
...options.anchor ? { slots: { ["anchor"]: options.anchor } } : {},
|
|
4244
|
+
fallback: options.fallback
|
|
4245
|
+
}) ?? options.fallback;
|
|
4246
|
+
}
|
|
4247
|
+
//#endregion
|
|
3815
4248
|
//#region ../dsh-plugin-kit/src/model-settings-client.ts
|
|
3816
4249
|
/** This stream carries invalidations only; ready is never a settings snapshot. */
|
|
3817
4250
|
function parseModelSettingsChangeFrame(value) {
|
|
@@ -4057,7 +4490,7 @@ async function callAuxiliaryRpc(client, endpoint, payload = {}) {
|
|
|
4057
4490
|
* The shared card. All state comes from the shared host service over the
|
|
4058
4491
|
* plugin-kit RPC channel; the card only renders the stored row.
|
|
4059
4492
|
*/
|
|
4060
|
-
function AuxiliaryModelCard({ client, subscribe }) {
|
|
4493
|
+
function AuxiliaryModelCard({ client, subscribe, renderFactorySlot }) {
|
|
4061
4494
|
const language = useAuxiliaryLocale(client);
|
|
4062
4495
|
const text = auxiliaryCopy[language];
|
|
4063
4496
|
const [settings, setSettings] = (0, react.useState)();
|
|
@@ -4178,6 +4611,17 @@ function AuxiliaryModelCard({ client, subscribe }) {
|
|
|
4178
4611
|
refreshCatalog
|
|
4179
4612
|
]);
|
|
4180
4613
|
const locked = busy;
|
|
4614
|
+
const current = settings?.model ?? {
|
|
4615
|
+
provider: "",
|
|
4616
|
+
model: ""
|
|
4617
|
+
};
|
|
4618
|
+
const follow = !current.provider || !current.model;
|
|
4619
|
+
const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
4620
|
+
const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
|
|
4621
|
+
const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
|
|
4622
|
+
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
4623
|
+
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
4624
|
+
const anchor = useModelMenuAnchor(`${text.model}: ${settings ? currentLabel : ""}`, void 0, () => setMenuOpen(false));
|
|
4181
4625
|
if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4182
4626
|
className: "ams-root dsh-ui-card",
|
|
4183
4627
|
"data-testid": "auxiliary-model-settings",
|
|
@@ -4216,16 +4660,6 @@ function AuxiliaryModelCard({ client, subscribe }) {
|
|
|
4216
4660
|
})
|
|
4217
4661
|
]
|
|
4218
4662
|
});
|
|
4219
|
-
const current = settings.model ?? {
|
|
4220
|
-
provider: "",
|
|
4221
|
-
model: ""
|
|
4222
|
-
};
|
|
4223
|
-
const follow = !current.provider || !current.model;
|
|
4224
|
-
const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
4225
|
-
const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
|
|
4226
|
-
const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
|
|
4227
|
-
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
4228
|
-
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
4229
4663
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4230
4664
|
className: "ams-root dsh-ui-card",
|
|
4231
4665
|
"data-testid": "auxiliary-model-settings",
|
|
@@ -4249,28 +4683,12 @@ function AuxiliaryModelCard({ client, subscribe }) {
|
|
|
4249
4683
|
state: catalogState,
|
|
4250
4684
|
locale: language
|
|
4251
4685
|
}),
|
|
4252
|
-
|
|
4686
|
+
renderModelPicker(renderFactorySlot, {
|
|
4253
4687
|
locale: language,
|
|
4254
|
-
|
|
4688
|
+
disabled: locked,
|
|
4689
|
+
busy: locked,
|
|
4255
4690
|
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
4256
4691
|
listClassName: "ams-root ams-menu-portal",
|
|
4257
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4258
|
-
type: "button",
|
|
4259
|
-
className: "dsh-model-menu-trigger",
|
|
4260
|
-
disabled: locked,
|
|
4261
|
-
"aria-label": `${text.model}: ${currentLabel}`,
|
|
4262
|
-
children: [
|
|
4263
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4264
|
-
className: "dsh-model-menu-triggerLabel",
|
|
4265
|
-
children: currentLabel
|
|
4266
|
-
}),
|
|
4267
|
-
!follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4268
|
-
className: "dsh-model-menu-triggerEffort",
|
|
4269
|
-
children: effortName ?? text.effortDefault
|
|
4270
|
-
}) : null,
|
|
4271
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
4272
|
-
]
|
|
4273
|
-
}),
|
|
4274
4692
|
choices,
|
|
4275
4693
|
selected: follow ? void 0 : {
|
|
4276
4694
|
provider: current.provider,
|
|
@@ -4293,9 +4711,61 @@ function AuxiliaryModelCard({ client, subscribe }) {
|
|
|
4293
4711
|
model: current.model,
|
|
4294
4712
|
...effort ? { reasoningEffort: effort } : {}
|
|
4295
4713
|
}),
|
|
4714
|
+
triggerLabel: currentLabel,
|
|
4715
|
+
triggerEffortLabel: !follow && efforts.length ? effortName ?? text.effortDefault : void 0,
|
|
4296
4716
|
modelLabel: text.model,
|
|
4297
4717
|
modelValue: currentLabel,
|
|
4298
4718
|
defaultEffortLabel: text.effortDefault
|
|
4719
|
+
}, {
|
|
4720
|
+
anchor,
|
|
4721
|
+
fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
4722
|
+
locale: language,
|
|
4723
|
+
open: menuOpen && !locked,
|
|
4724
|
+
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
4725
|
+
listClassName: "ams-root ams-menu-portal",
|
|
4726
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4727
|
+
type: "button",
|
|
4728
|
+
className: "dsh-model-menu-trigger",
|
|
4729
|
+
disabled: locked,
|
|
4730
|
+
"aria-label": `${text.model}: ${currentLabel}`,
|
|
4731
|
+
children: [
|
|
4732
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4733
|
+
className: "dsh-model-menu-triggerLabel",
|
|
4734
|
+
children: currentLabel
|
|
4735
|
+
}),
|
|
4736
|
+
!follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4737
|
+
className: "dsh-model-menu-triggerEffort",
|
|
4738
|
+
children: effortName ?? text.effortDefault
|
|
4739
|
+
}) : null,
|
|
4740
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
4741
|
+
]
|
|
4742
|
+
}),
|
|
4743
|
+
choices,
|
|
4744
|
+
selected: follow ? void 0 : {
|
|
4745
|
+
provider: current.provider,
|
|
4746
|
+
model: current.model
|
|
4747
|
+
},
|
|
4748
|
+
onPick: (picked) => save({
|
|
4749
|
+
provider: picked.provider,
|
|
4750
|
+
model: picked.model
|
|
4751
|
+
}),
|
|
4752
|
+
leading: [{
|
|
4753
|
+
id: "default",
|
|
4754
|
+
label: text.modelDefault,
|
|
4755
|
+
selected: follow
|
|
4756
|
+
}],
|
|
4757
|
+
onPickLeading: () => save(null),
|
|
4758
|
+
efforts,
|
|
4759
|
+
selectedEffort: current.reasoningEffort || "",
|
|
4760
|
+
onPickEffort: (effort) => save(follow ? null : {
|
|
4761
|
+
provider: current.provider,
|
|
4762
|
+
model: current.model,
|
|
4763
|
+
...effort ? { reasoningEffort: effort } : {}
|
|
4764
|
+
}),
|
|
4765
|
+
modelLabel: text.model,
|
|
4766
|
+
modelValue: currentLabel,
|
|
4767
|
+
defaultEffortLabel: text.effortDefault
|
|
4768
|
+
})
|
|
4299
4769
|
}),
|
|
4300
4770
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4301
4771
|
className: "dsh-ui-hint",
|
|
@@ -4397,9 +4867,10 @@ function acquireAuxiliaryModelCard(root, ctx) {
|
|
|
4397
4867
|
let unprovide;
|
|
4398
4868
|
const watchLease = acquireWatchCoordinator(root).mount();
|
|
4399
4869
|
const subscriber = createModelSettingsSubscriber(ctx.root, AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY, (ns) => ns === AUXILIARY_MODEL_NS);
|
|
4400
|
-
const render = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuxiliaryModelCard, {
|
|
4870
|
+
const render = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuxiliaryModelCard, {
|
|
4401
4871
|
client: root,
|
|
4402
|
-
subscribe: subscriber.subscribe
|
|
4872
|
+
subscribe: subscriber.subscribe,
|
|
4873
|
+
renderFactorySlot: props.renderFactorySlot
|
|
4403
4874
|
});
|
|
4404
4875
|
const service = {
|
|
4405
4876
|
retain() {
|
|
@@ -4696,24 +5167,6 @@ function WelcomeBackCard(props) {
|
|
|
4696
5167
|
}
|
|
4697
5168
|
//#endregion
|
|
4698
5169
|
//#region src/settings-panel.tsx
|
|
4699
|
-
function toDraft(values) {
|
|
4700
|
-
const recapEnabled = values.recapMinutes > 0;
|
|
4701
|
-
const compactEnabled = values.compactMinutes > 0;
|
|
4702
|
-
return {
|
|
4703
|
-
recapEnabled,
|
|
4704
|
-
recapMinutes: String(recapEnabled ? values.recapMinutes : 15),
|
|
4705
|
-
compactEnabled,
|
|
4706
|
-
compactMinutes: String(compactEnabled ? values.compactMinutes : 60),
|
|
4707
|
-
continueText: values.continueText,
|
|
4708
|
-
continueMode: values.continueMode,
|
|
4709
|
-
autoContinueMode: values.autoContinueMode,
|
|
4710
|
-
autoContinueMax: String(values.autoContinueMax),
|
|
4711
|
-
model: values.model === null ? "" : modelRouteKey(values.model.provider, values.model.model),
|
|
4712
|
-
ghostModel: values.ghostModel === null ? "" : modelRouteKey(values.ghostModel.provider, values.ghostModel.model),
|
|
4713
|
-
judgeModel: values.judgeModel === null ? "" : modelRouteKey(values.judgeModel.provider, values.judgeModel.model),
|
|
4714
|
-
handoffModel: values.handoffModel === null ? "" : modelRouteKey(values.handoffModel.provider, values.handoffModel.model)
|
|
4715
|
-
};
|
|
4716
|
-
}
|
|
4717
5170
|
/** Enabled thresholds are whole minutes of at least 1. 0 stays the stored "off" value and is not a valid enabled draft. */
|
|
4718
5171
|
function minutesOf(text) {
|
|
4719
5172
|
if (!/^[1-9]\d*$/.test(text.trim())) return void 0;
|
|
@@ -4726,624 +5179,649 @@ function autoMaxOf(text) {
|
|
|
4726
5179
|
const value = Number.parseInt(text.trim(), 10);
|
|
4727
5180
|
return Number.isSafeInteger(value) && value >= 1 && value <= 10 ? value : void 0;
|
|
4728
5181
|
}
|
|
4729
|
-
function sameDraft(left, right) {
|
|
4730
|
-
return left.recapEnabled === right.recapEnabled && left.recapMinutes === right.recapMinutes && left.compactEnabled === right.compactEnabled && left.compactMinutes === right.compactMinutes && left.continueText === right.continueText && left.continueMode === right.continueMode && left.autoContinueMode === right.autoContinueMode && left.autoContinueMax === right.autoContinueMax && left.model === right.model && left.ghostModel === right.ghostModel && left.judgeModel === right.judgeModel && left.handoffModel === right.handoffModel;
|
|
4731
|
-
}
|
|
4732
5182
|
const missingCatalog = async () => {
|
|
4733
5183
|
throw new Error("Model catalog service unavailable");
|
|
4734
5184
|
};
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
const
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
const copy = (key) => uiText(props.locale, key);
|
|
4749
|
-
const edit = (next) => {
|
|
4750
|
-
dirty.current = true;
|
|
4751
|
-
setDraft(next);
|
|
5185
|
+
function copy(locale, key) {
|
|
5186
|
+
return uiText(locale, key);
|
|
5187
|
+
}
|
|
5188
|
+
const writers = /* @__PURE__ */ new WeakMap();
|
|
5189
|
+
function writerFor(model) {
|
|
5190
|
+
let writer = writers.get(model);
|
|
5191
|
+
if (writer !== void 0) return writer;
|
|
5192
|
+
let queue;
|
|
5193
|
+
const flush = () => {
|
|
5194
|
+
if (queue === void 0 || model.getSnapshot().saving) return;
|
|
5195
|
+
const patch = queue;
|
|
5196
|
+
queue = void 0;
|
|
5197
|
+
model.save(patch).finally(flush);
|
|
4752
5198
|
};
|
|
5199
|
+
writer = {
|
|
5200
|
+
lastRow: void 0,
|
|
5201
|
+
send(patch, row) {
|
|
5202
|
+
writer.lastRow = row;
|
|
5203
|
+
queue = {
|
|
5204
|
+
...queue,
|
|
5205
|
+
...patch
|
|
5206
|
+
};
|
|
5207
|
+
flush();
|
|
5208
|
+
}
|
|
5209
|
+
};
|
|
5210
|
+
writers.set(model, writer);
|
|
5211
|
+
return writer;
|
|
5212
|
+
}
|
|
5213
|
+
function useConfigPatch(model) {
|
|
5214
|
+
const row = (0, react.useRef)(Symbol("row")).current;
|
|
5215
|
+
const writer = writerFor(model);
|
|
5216
|
+
return {
|
|
5217
|
+
send: (0, react.useCallback)((patch) => writer.send(patch, row), [writer, row]),
|
|
5218
|
+
owns: () => writer.lastRow === row
|
|
5219
|
+
};
|
|
5220
|
+
}
|
|
5221
|
+
/**
|
|
5222
|
+
* One controlled text/number input bound to a stored string. The draft follows
|
|
5223
|
+
* external writes only while untouched — a focused, edited draft is never
|
|
5224
|
+
* wiped by a settings reload. The commit runs on blur and Enter: valid input
|
|
5225
|
+
* normalizes and saves when it differs; invalid input keeps the draft and its
|
|
5226
|
+
* inline error and is never written.
|
|
5227
|
+
*/
|
|
5228
|
+
function useTextSetting(props) {
|
|
5229
|
+
const [draft, setDraft] = (0, react.useState)(props.stored);
|
|
5230
|
+
const seen = (0, react.useRef)(props.stored);
|
|
5231
|
+
const touched = (0, react.useRef)(false);
|
|
4753
5232
|
(0, react.useEffect)(() => {
|
|
4754
|
-
if (
|
|
4755
|
-
|
|
4756
|
-
(0, react.useEffect)(() => {
|
|
4757
|
-
if (snapshot.values === void 0) return;
|
|
4758
|
-
const next = toDraft(snapshot.values);
|
|
5233
|
+
if (props.stored === seen.current) return;
|
|
5234
|
+
seen.current = props.stored;
|
|
4759
5235
|
setDraft((current) => {
|
|
4760
|
-
if (
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
return next;
|
|
5236
|
+
if (touched.current && current !== props.stored) return current;
|
|
5237
|
+
touched.current = false;
|
|
5238
|
+
return props.stored;
|
|
4764
5239
|
});
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
5240
|
+
}, [props.stored]);
|
|
5241
|
+
const commit = (0, react.useCallback)(() => {
|
|
5242
|
+
const parsed = props.parse(draft);
|
|
5243
|
+
if (parsed === void 0) return;
|
|
5244
|
+
setDraft(parsed.draft);
|
|
5245
|
+
if (parsed.draft === seen.current) {
|
|
5246
|
+
touched.current = false;
|
|
5247
|
+
return;
|
|
5248
|
+
}
|
|
5249
|
+
props.send(parsed.value);
|
|
5250
|
+
}, [
|
|
5251
|
+
draft,
|
|
5252
|
+
props.parse,
|
|
5253
|
+
props.send
|
|
4774
5254
|
]);
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
5255
|
+
return {
|
|
5256
|
+
draft,
|
|
5257
|
+
invalid: props.parse(draft) === void 0,
|
|
5258
|
+
inputProps: {
|
|
5259
|
+
value: draft,
|
|
5260
|
+
onChange: (event) => {
|
|
5261
|
+
touched.current = true;
|
|
5262
|
+
setDraft(event.target.value);
|
|
5263
|
+
},
|
|
5264
|
+
onBlur: commit,
|
|
5265
|
+
onKeyDown: (event) => {
|
|
5266
|
+
if (event.key === "Enter") commit();
|
|
5267
|
+
}
|
|
5268
|
+
}
|
|
5269
|
+
};
|
|
5270
|
+
}
|
|
5271
|
+
/** Per-row write status: one line under the controls, only while it has something to say. */
|
|
5272
|
+
function SaveStatus(props) {
|
|
5273
|
+
const { snapshot, locale } = props;
|
|
5274
|
+
if (!props.owns()) return null;
|
|
5275
|
+
if (snapshot.saving) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5276
|
+
className: "dsh-ui-notice",
|
|
5277
|
+
role: "status",
|
|
5278
|
+
children: copy(locale, "settingsSaving")
|
|
4778
5279
|
});
|
|
4779
|
-
if (snapshot.
|
|
4780
|
-
className:
|
|
5280
|
+
if (snapshot.failure) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5281
|
+
className: "dsh-ui-notice dsh-ui-notice--error",
|
|
4781
5282
|
role: "alert",
|
|
4782
|
-
children:
|
|
5283
|
+
children: failureText(locale, snapshot.failure)
|
|
4783
5284
|
});
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
const selectableChoices = parseModelMenuChoices(catalog.catalog);
|
|
4789
|
-
for (const bound of [
|
|
4790
|
-
selectedRoute,
|
|
4791
|
-
ghostRoute,
|
|
4792
|
-
judgeRoute,
|
|
4793
|
-
handoffRoute
|
|
4794
|
-
]) if (bound && !selectableChoices.some((choice) => choice.provider === bound.provider && choice.model === bound.model)) selectableChoices.push({
|
|
4795
|
-
provider: bound.provider,
|
|
4796
|
-
model: bound.model,
|
|
4797
|
-
label: `${bound.provider} / ${bound.model}`,
|
|
4798
|
-
efforts: []
|
|
5285
|
+
if (snapshot.saved) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5286
|
+
className: "dsh-ui-notice",
|
|
5287
|
+
role: "status",
|
|
5288
|
+
children: copy(locale, "settingsSaved")
|
|
4799
5289
|
});
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
const
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
5290
|
+
return null;
|
|
5291
|
+
}
|
|
5292
|
+
/** Shared loading/unavailable body: every row reads the same config snapshot. */
|
|
5293
|
+
function useRowGate(props) {
|
|
5294
|
+
const snapshot = (0, react.useSyncExternalStore)(props.model.subscribe, props.model.getSnapshot, props.model.getSnapshot);
|
|
5295
|
+
(0, react.useEffect)(() => {
|
|
5296
|
+
if (snapshot.phase === "loading") props.model.load();
|
|
5297
|
+
}, [props.model, snapshot.phase]);
|
|
5298
|
+
if (snapshot.phase === "loading") return {
|
|
5299
|
+
snapshot,
|
|
5300
|
+
gate: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5301
|
+
className: "dsh-ui-loading",
|
|
5302
|
+
children: copy(props.locale, "settingsLoading")
|
|
5303
|
+
})
|
|
5304
|
+
};
|
|
5305
|
+
if (snapshot.phase === "unavailable" || snapshot.values === void 0) return {
|
|
5306
|
+
snapshot,
|
|
5307
|
+
gate: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5308
|
+
className: "dsh-ui-error",
|
|
5309
|
+
role: "alert",
|
|
5310
|
+
children: snapshot.failure ? failureText(props.locale, snapshot.failure) : copy(props.locale, "settingsUnavailable")
|
|
5311
|
+
})
|
|
5312
|
+
};
|
|
5313
|
+
return {
|
|
5314
|
+
snapshot,
|
|
5315
|
+
values: snapshot.values
|
|
5316
|
+
};
|
|
5317
|
+
}
|
|
5318
|
+
/** 输入框建议 / Composer suggestion row. */
|
|
5319
|
+
function ComposerSettings(props) {
|
|
5320
|
+
const { snapshot, values, gate } = useRowGate(props);
|
|
5321
|
+
const { send, owns } = useConfigPatch(props.model);
|
|
5322
|
+
const tabsId = (0, react.useId)();
|
|
5323
|
+
const field = useTextSetting({
|
|
5324
|
+
stored: values?.continueText ?? "",
|
|
5325
|
+
parse: (draft) => ({
|
|
5326
|
+
value: draft.trim(),
|
|
5327
|
+
draft: draft.trim()
|
|
5328
|
+
}),
|
|
5329
|
+
send: (0, react.useCallback)((value) => send({ continueText: value }), [send])
|
|
5330
|
+
});
|
|
5331
|
+
if (gate !== void 0 || values === void 0) return gate ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
|
|
5332
|
+
const fixedPanelId = `${tabsId}-fixed-panel`;
|
|
5333
|
+
const autoPanelId = `${tabsId}-auto-panel`;
|
|
5334
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5335
|
+
className: "dsm-settings",
|
|
5336
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5337
|
+
className: "dsm-feature",
|
|
4842
5338
|
children: [
|
|
4843
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
4844
|
-
className: "
|
|
4845
|
-
"
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
}
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
children: copy("continueSourceLabel")
|
|
4866
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
|
|
4867
|
-
label: copy("continueSourceLabel"),
|
|
4868
|
-
items: [{
|
|
4869
|
-
id: `${tabsId}-fixed`,
|
|
4870
|
-
panelId: fixedPanelId,
|
|
4871
|
-
value: "fixed",
|
|
4872
|
-
label: copy("modeFixed")
|
|
4873
|
-
}, {
|
|
4874
|
-
id: `${tabsId}-auto`,
|
|
4875
|
-
panelId: autoPanelId,
|
|
4876
|
-
value: "auto",
|
|
4877
|
-
label: copy("modeAuto")
|
|
4878
|
-
}],
|
|
4879
|
-
value: draft.continueMode,
|
|
4880
|
-
onChange: (value) => edit({
|
|
4881
|
-
...draft,
|
|
4882
|
-
continueMode: value
|
|
4883
|
-
})
|
|
4884
|
-
})]
|
|
4885
|
-
}),
|
|
4886
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4887
|
-
role: "tabpanel",
|
|
4888
|
-
id: fixedPanelId,
|
|
4889
|
-
"aria-labelledby": `${tabsId}-fixed`,
|
|
4890
|
-
hidden: draft.continueMode !== "fixed",
|
|
4891
|
-
children: draft.continueMode === "fixed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4892
|
-
className: "dsh-ui-field",
|
|
4893
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4894
|
-
className: "dsh-ui-label",
|
|
4895
|
-
children: copy("continueTextLabel")
|
|
4896
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
4897
|
-
className: "dsm-ghost-input",
|
|
4898
|
-
value: draft.continueText,
|
|
4899
|
-
placeholder: copy("continueTextPlaceholder"),
|
|
4900
|
-
onChange: (event) => edit({
|
|
4901
|
-
...draft,
|
|
4902
|
-
continueText: event.target.value
|
|
4903
|
-
})
|
|
4904
|
-
})]
|
|
4905
|
-
}) : null
|
|
4906
|
-
}),
|
|
4907
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4908
|
-
role: "tabpanel",
|
|
4909
|
-
id: autoPanelId,
|
|
4910
|
-
"aria-labelledby": `${tabsId}-auto`,
|
|
4911
|
-
hidden: draft.continueMode !== "auto",
|
|
4912
|
-
children: draft.continueMode === "auto" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4913
|
-
className: "dsh-ui-field dsm-model-field",
|
|
4914
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4915
|
-
className: "dsh-ui-label",
|
|
4916
|
-
children: copy("ghostModelLabel")
|
|
4917
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
4918
|
-
locale: props.locale,
|
|
4919
|
-
open: ghostMenuOpen,
|
|
4920
|
-
onOpenChange: setGhostMenuOpen,
|
|
4921
|
-
listClassName: FREE_CHAT_ROOT,
|
|
4922
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4923
|
-
type: "button",
|
|
4924
|
-
className: "dsh-model-menu-trigger",
|
|
4925
|
-
"aria-label": `${copy("ghostModelLabel")}: ${ghostLabel}`,
|
|
4926
|
-
"aria-haspopup": "menu",
|
|
4927
|
-
"aria-expanded": ghostMenuOpen,
|
|
4928
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4929
|
-
className: "dsh-model-menu-triggerLabel",
|
|
4930
|
-
children: ghostLabel
|
|
4931
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${ghostMenuOpen ? " dsh-model-menu-chevronOpen" : ""}` })]
|
|
4932
|
-
}),
|
|
4933
|
-
choices: selectableChoices,
|
|
4934
|
-
selected: ghostRoute,
|
|
4935
|
-
onPick: (route) => edit({
|
|
4936
|
-
...draft,
|
|
4937
|
-
ghostModel: modelRouteKey(route.provider, route.model)
|
|
4938
|
-
}),
|
|
4939
|
-
leading: [{
|
|
4940
|
-
id: "follow",
|
|
4941
|
-
label: copy("modelFollowOption"),
|
|
4942
|
-
selected: !ghostRoute
|
|
4943
|
-
}],
|
|
4944
|
-
onPickLeading: () => edit({
|
|
4945
|
-
...draft,
|
|
4946
|
-
ghostModel: ""
|
|
4947
|
-
}),
|
|
4948
|
-
modelValue: ghostLabel
|
|
4949
|
-
})]
|
|
4950
|
-
}) : null
|
|
4951
|
-
})
|
|
4952
|
-
]
|
|
4953
|
-
}),
|
|
4954
|
-
draft.continueMode === "auto" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4955
|
-
className: "dsh-ui-hint",
|
|
4956
|
-
children: copy("ghostModelHint")
|
|
4957
|
-
}) : null
|
|
4958
|
-
]
|
|
4959
|
-
}),
|
|
4960
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4961
|
-
className: "dsm-setting",
|
|
4962
|
-
"aria-labelledby": `${tabsId}-auto-continue-heading`,
|
|
4963
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
|
|
4964
|
-
className: "dsm-setting-header",
|
|
4965
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
4966
|
-
id: `${tabsId}-auto-continue-heading`,
|
|
4967
|
-
className: "dsh-ui-title",
|
|
4968
|
-
children: copy("autoContinueGroup")
|
|
4969
|
-
})
|
|
4970
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4971
|
-
className: "dsm-feature-options",
|
|
4972
|
-
children: [
|
|
4973
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4974
|
-
className: "dsh-ui-field",
|
|
4975
|
-
children: [
|
|
4976
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4977
|
-
className: "dsh-ui-label",
|
|
4978
|
-
children: copy("autoContinueDefaultLabel")
|
|
4979
|
-
}),
|
|
4980
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
4981
|
-
className: "dsh-ui-select",
|
|
4982
|
-
value: draft.autoContinueMode,
|
|
4983
|
-
onChange: (event) => edit({
|
|
4984
|
-
...draft,
|
|
4985
|
-
autoContinueMode: event.target.value
|
|
4986
|
-
}),
|
|
4987
|
-
children: [
|
|
4988
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4989
|
-
value: "off",
|
|
4990
|
-
children: copy("autoContinueOff")
|
|
4991
|
-
}),
|
|
4992
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4993
|
-
value: "cautious",
|
|
4994
|
-
children: copy("autoContinueCautious")
|
|
4995
|
-
}),
|
|
4996
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4997
|
-
value: "bold",
|
|
4998
|
-
children: copy("autoContinueBold")
|
|
4999
|
-
})
|
|
5000
|
-
]
|
|
5001
|
-
}),
|
|
5002
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5003
|
-
className: "dsh-ui-hint",
|
|
5004
|
-
children: copy("autoContinueDefaultHint")
|
|
5005
|
-
})
|
|
5006
|
-
]
|
|
5007
|
-
}),
|
|
5008
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
5009
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5010
|
-
className: "dsh-ui-field",
|
|
5011
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5012
|
-
className: "dsh-ui-label",
|
|
5013
|
-
children: copy("autoContinueMaxLabel")
|
|
5014
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5015
|
-
className: "dsm-minutes-control",
|
|
5016
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
5017
|
-
className: "dsm-minutes",
|
|
5018
|
-
value: draft.autoContinueMax,
|
|
5019
|
-
inputMode: "numeric",
|
|
5020
|
-
"aria-invalid": autoMaxInvalid || void 0,
|
|
5021
|
-
"aria-describedby": autoMaxInvalid ? `${tabsId}-auto-max-error` : void 0,
|
|
5022
|
-
onChange: (event) => edit({
|
|
5023
|
-
...draft,
|
|
5024
|
-
autoContinueMax: event.target.value
|
|
5025
|
-
})
|
|
5026
|
-
})
|
|
5027
|
-
})]
|
|
5028
|
-
}),
|
|
5029
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5030
|
-
className: "dsh-ui-hint",
|
|
5031
|
-
children: copy("autoContinueMaxHint")
|
|
5032
|
-
}),
|
|
5033
|
-
autoMaxInvalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5034
|
-
id: `${tabsId}-auto-max-error`,
|
|
5035
|
-
className: "dsh-ui-error",
|
|
5036
|
-
role: "alert",
|
|
5037
|
-
children: copy("autoContinueMaxInvalid")
|
|
5038
|
-
}) : null
|
|
5039
|
-
] }),
|
|
5040
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5041
|
-
className: "dsh-ui-field dsm-model-field",
|
|
5042
|
-
children: [
|
|
5043
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5044
|
-
className: "dsh-ui-label",
|
|
5045
|
-
children: copy("judgeModelLabel")
|
|
5046
|
-
}),
|
|
5047
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
5048
|
-
locale: props.locale,
|
|
5049
|
-
open: judgeMenuOpen,
|
|
5050
|
-
onOpenChange: setJudgeMenuOpen,
|
|
5051
|
-
listClassName: FREE_CHAT_ROOT,
|
|
5052
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5053
|
-
type: "button",
|
|
5054
|
-
className: "dsh-model-menu-trigger",
|
|
5055
|
-
"aria-label": `${copy("judgeModelLabel")}: ${judgeLabel}`,
|
|
5056
|
-
"aria-haspopup": "menu",
|
|
5057
|
-
"aria-expanded": judgeMenuOpen,
|
|
5058
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5059
|
-
className: "dsh-model-menu-triggerLabel",
|
|
5060
|
-
children: judgeLabel
|
|
5061
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${judgeMenuOpen ? " dsh-model-menu-chevronOpen" : ""}` })]
|
|
5062
|
-
}),
|
|
5063
|
-
choices: selectableChoices,
|
|
5064
|
-
selected: judgeRoute,
|
|
5065
|
-
onPick: (route) => edit({
|
|
5066
|
-
...draft,
|
|
5067
|
-
judgeModel: modelRouteKey(route.provider, route.model)
|
|
5068
|
-
}),
|
|
5069
|
-
leading: [{
|
|
5070
|
-
id: "follow",
|
|
5071
|
-
label: copy("modelFollowOption"),
|
|
5072
|
-
selected: !judgeRoute
|
|
5073
|
-
}],
|
|
5074
|
-
onPickLeading: () => edit({
|
|
5075
|
-
...draft,
|
|
5076
|
-
judgeModel: ""
|
|
5077
|
-
}),
|
|
5078
|
-
modelValue: judgeLabel
|
|
5079
|
-
}),
|
|
5080
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5081
|
-
className: "dsh-ui-hint",
|
|
5082
|
-
children: copy("judgeModelHint")
|
|
5083
|
-
})
|
|
5084
|
-
]
|
|
5085
|
-
})
|
|
5086
|
-
]
|
|
5339
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5340
|
+
className: "dsh-ui-field",
|
|
5341
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5342
|
+
className: "dsh-ui-label",
|
|
5343
|
+
children: copy(props.locale, "continueSourceLabel")
|
|
5344
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
|
|
5345
|
+
label: copy(props.locale, "continueSourceLabel"),
|
|
5346
|
+
items: [{
|
|
5347
|
+
id: `${tabsId}-fixed`,
|
|
5348
|
+
panelId: fixedPanelId,
|
|
5349
|
+
value: "fixed",
|
|
5350
|
+
label: copy(props.locale, "modeFixed")
|
|
5351
|
+
}, {
|
|
5352
|
+
id: `${tabsId}-auto`,
|
|
5353
|
+
panelId: autoPanelId,
|
|
5354
|
+
value: "auto",
|
|
5355
|
+
label: copy(props.locale, "modeAuto")
|
|
5356
|
+
}],
|
|
5357
|
+
value: values.continueMode,
|
|
5358
|
+
onChange: (value) => {
|
|
5359
|
+
if (value !== values.continueMode) send({ continueMode: value });
|
|
5360
|
+
}
|
|
5087
5361
|
})]
|
|
5088
5362
|
}),
|
|
5089
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5363
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5364
|
+
role: "tabpanel",
|
|
5365
|
+
id: fixedPanelId,
|
|
5366
|
+
"aria-labelledby": `${tabsId}-fixed`,
|
|
5367
|
+
hidden: values.continueMode !== "fixed",
|
|
5368
|
+
children: values.continueMode === "fixed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5369
|
+
className: "dsh-ui-field",
|
|
5370
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5371
|
+
className: "dsh-ui-label",
|
|
5372
|
+
children: copy(props.locale, "continueTextLabel")
|
|
5373
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
5374
|
+
className: "dsm-text-input",
|
|
5375
|
+
placeholder: copy(props.locale, "continueTextPlaceholder"),
|
|
5376
|
+
...field.inputProps
|
|
5377
|
+
})]
|
|
5378
|
+
}) : null
|
|
5379
|
+
}),
|
|
5380
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5381
|
+
role: "tabpanel",
|
|
5382
|
+
id: autoPanelId,
|
|
5383
|
+
"aria-labelledby": `${tabsId}-auto`,
|
|
5384
|
+
hidden: values.continueMode !== "auto",
|
|
5385
|
+
children: values.continueMode === "auto" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelField, {
|
|
5386
|
+
...props,
|
|
5387
|
+
field: "ghostModel",
|
|
5388
|
+
labelKey: "ghostModelLabel",
|
|
5389
|
+
route: values.ghostModel,
|
|
5390
|
+
send
|
|
5391
|
+
}) : null
|
|
5392
|
+
}),
|
|
5393
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5394
|
+
className: "dsh-ui-hint",
|
|
5395
|
+
children: copy(props.locale, "composerUsageHint")
|
|
5396
|
+
}),
|
|
5397
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SaveStatus, {
|
|
5398
|
+
snapshot,
|
|
5399
|
+
locale: props.locale,
|
|
5400
|
+
owns
|
|
5401
|
+
})
|
|
5402
|
+
]
|
|
5403
|
+
})
|
|
5404
|
+
});
|
|
5405
|
+
}
|
|
5406
|
+
/** 自动继续 / Auto-continue row. */
|
|
5407
|
+
function AutoContinueSettings(props) {
|
|
5408
|
+
const { snapshot, values, gate } = useRowGate(props);
|
|
5409
|
+
const { send, owns } = useConfigPatch(props.model);
|
|
5410
|
+
const errorId = (0, react.useId)();
|
|
5411
|
+
const field = useTextSetting({
|
|
5412
|
+
stored: String(values?.autoContinueMax ?? ""),
|
|
5413
|
+
parse: (draft) => {
|
|
5414
|
+
const value = autoMaxOf(draft);
|
|
5415
|
+
return value === void 0 ? void 0 : {
|
|
5416
|
+
value,
|
|
5417
|
+
draft: String(value)
|
|
5418
|
+
};
|
|
5419
|
+
},
|
|
5420
|
+
send: (0, react.useCallback)((value) => send({ autoContinueMax: value }), [send])
|
|
5421
|
+
});
|
|
5422
|
+
if (gate !== void 0 || values === void 0) return gate ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
|
|
5423
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5424
|
+
className: "dsm-settings",
|
|
5425
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5426
|
+
className: "dsm-feature",
|
|
5427
|
+
children: [
|
|
5428
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5429
|
+
className: "dsh-ui-field",
|
|
5430
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5431
|
+
className: "dsh-ui-label",
|
|
5432
|
+
children: copy(props.locale, "autoContinueMaxLabel")
|
|
5433
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
5434
|
+
className: "dsm-minutes",
|
|
5435
|
+
inputMode: "numeric",
|
|
5436
|
+
"aria-invalid": field.invalid || void 0,
|
|
5437
|
+
"aria-describedby": field.invalid ? `${errorId}-max-error` : void 0,
|
|
5438
|
+
...field.inputProps
|
|
5147
5439
|
})]
|
|
5148
5440
|
}),
|
|
5149
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
5150
|
-
className: "
|
|
5151
|
-
"
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
...draft,
|
|
5179
|
-
recapEnabled: next
|
|
5180
|
-
})
|
|
5181
|
-
})]
|
|
5182
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5183
|
-
className: "dsm-feature-options",
|
|
5184
|
-
"data-disabled": draft.recapEnabled ? void 0 : "true",
|
|
5185
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5186
|
-
className: "dsh-ui-fields-grid",
|
|
5187
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5188
|
-
className: "dsh-ui-field",
|
|
5189
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5190
|
-
className: "dsh-ui-label",
|
|
5191
|
-
children: copy("recapMinutesLabel")
|
|
5192
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5193
|
-
className: "dsm-minutes-control",
|
|
5194
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
5195
|
-
className: "dsm-minutes",
|
|
5196
|
-
value: draft.recapMinutes,
|
|
5197
|
-
inputMode: "numeric",
|
|
5198
|
-
disabled: !draft.recapEnabled,
|
|
5199
|
-
"aria-invalid": recapInvalid || void 0,
|
|
5200
|
-
"aria-describedby": recapInvalid ? `${tabsId}-recap-error` : void 0,
|
|
5201
|
-
onChange: (event) => edit({
|
|
5202
|
-
...draft,
|
|
5203
|
-
recapMinutes: event.target.value
|
|
5204
|
-
})
|
|
5205
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5206
|
-
className: "dsh-ui-meta",
|
|
5207
|
-
children: copy("minutesUnit")
|
|
5208
|
-
})]
|
|
5209
|
-
})]
|
|
5210
|
-
}), recapInvalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5211
|
-
id: `${tabsId}-recap-error`,
|
|
5212
|
-
className: "dsh-ui-error",
|
|
5213
|
-
role: "alert",
|
|
5214
|
-
children: copy("minutesInvalid")
|
|
5215
|
-
}) : null] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5216
|
-
className: "dsh-ui-field dsm-model-field",
|
|
5217
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5218
|
-
className: "dsh-ui-label",
|
|
5219
|
-
children: copy("modelLabel")
|
|
5220
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
5221
|
-
locale: props.locale,
|
|
5222
|
-
open: menuOpen,
|
|
5223
|
-
onOpenChange: setMenuOpen,
|
|
5224
|
-
listClassName: FREE_CHAT_ROOT,
|
|
5225
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5226
|
-
type: "button",
|
|
5227
|
-
className: "dsh-model-menu-trigger",
|
|
5228
|
-
disabled: !draft.recapEnabled,
|
|
5229
|
-
"aria-label": `${copy("modelLabel")}: ${selectedLabel}`,
|
|
5230
|
-
"aria-haspopup": "menu",
|
|
5231
|
-
"aria-expanded": menuOpen,
|
|
5232
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5233
|
-
className: "dsh-model-menu-triggerLabel",
|
|
5234
|
-
children: selectedLabel
|
|
5235
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen ? " dsh-model-menu-chevronOpen" : ""}` })]
|
|
5236
|
-
}),
|
|
5237
|
-
choices: selectableChoices,
|
|
5238
|
-
selected: selectedRoute,
|
|
5239
|
-
onPick: (route) => edit({
|
|
5240
|
-
...draft,
|
|
5241
|
-
model: modelRouteKey(route.provider, route.model)
|
|
5242
|
-
}),
|
|
5243
|
-
leading: [{
|
|
5244
|
-
id: "follow",
|
|
5245
|
-
label: copy("modelFollowOption"),
|
|
5246
|
-
selected: !selectedRoute
|
|
5247
|
-
}],
|
|
5248
|
-
onPickLeading: () => edit({
|
|
5249
|
-
...draft,
|
|
5250
|
-
model: ""
|
|
5251
|
-
}),
|
|
5252
|
-
modelValue: selectedLabel
|
|
5253
|
-
})]
|
|
5254
|
-
})]
|
|
5255
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
|
|
5256
|
-
state: catalog,
|
|
5257
|
-
locale: props.locale
|
|
5258
|
-
})]
|
|
5259
|
-
})]
|
|
5260
|
-
}),
|
|
5261
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5262
|
-
className: "dsm-feature",
|
|
5263
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5264
|
-
className: "dsh-ui-toggle-row",
|
|
5265
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5266
|
-
className: "dsh-ui-toggle-text",
|
|
5267
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5268
|
-
className: "dsh-ui-toggle-label",
|
|
5269
|
-
children: copy("compactEnabledLabel")
|
|
5270
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5271
|
-
className: "dsh-ui-hint",
|
|
5272
|
-
children: copy("compactEnabledHint")
|
|
5273
|
-
})]
|
|
5274
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
5275
|
-
label: copy("compactEnabledLabel"),
|
|
5276
|
-
checked: draft.compactEnabled,
|
|
5277
|
-
onChange: (next) => edit({
|
|
5278
|
-
...draft,
|
|
5279
|
-
compactEnabled: next
|
|
5280
|
-
})
|
|
5281
|
-
})]
|
|
5282
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5283
|
-
className: "dsm-feature-options dsm-compact-options",
|
|
5284
|
-
"data-disabled": draft.compactEnabled ? void 0 : "true",
|
|
5285
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5286
|
-
className: "dsh-ui-field",
|
|
5287
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5288
|
-
className: "dsh-ui-label",
|
|
5289
|
-
children: copy("compactMinutesLabel")
|
|
5290
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5291
|
-
className: "dsm-minutes-control",
|
|
5292
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
5293
|
-
className: "dsm-minutes",
|
|
5294
|
-
value: draft.compactMinutes,
|
|
5295
|
-
inputMode: "numeric",
|
|
5296
|
-
disabled: !draft.compactEnabled,
|
|
5297
|
-
"aria-invalid": compactInvalid || void 0,
|
|
5298
|
-
"aria-describedby": compactInvalid ? `${tabsId}-compact-error` : void 0,
|
|
5299
|
-
onChange: (event) => edit({
|
|
5300
|
-
...draft,
|
|
5301
|
-
compactMinutes: event.target.value
|
|
5302
|
-
})
|
|
5303
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5304
|
-
className: "dsh-ui-meta",
|
|
5305
|
-
children: copy("minutesUnit")
|
|
5306
|
-
})]
|
|
5307
|
-
})]
|
|
5308
|
-
}), compactInvalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5309
|
-
id: `${tabsId}-compact-error`,
|
|
5310
|
-
className: "dsh-ui-error",
|
|
5311
|
-
role: "alert",
|
|
5312
|
-
children: copy("minutesInvalid")
|
|
5313
|
-
}) : null] })
|
|
5314
|
-
})]
|
|
5315
|
-
})
|
|
5316
|
-
]
|
|
5441
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5442
|
+
className: "dsh-ui-hint",
|
|
5443
|
+
children: copy(props.locale, "autoContinueMaxHint")
|
|
5444
|
+
}),
|
|
5445
|
+
field.invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5446
|
+
id: `${errorId}-max-error`,
|
|
5447
|
+
className: "dsh-ui-error",
|
|
5448
|
+
role: "alert",
|
|
5449
|
+
children: copy(props.locale, "autoContinueMaxInvalid")
|
|
5450
|
+
}) : null,
|
|
5451
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelField, {
|
|
5452
|
+
...props,
|
|
5453
|
+
field: "judgeModel",
|
|
5454
|
+
labelKey: "judgeModelLabel",
|
|
5455
|
+
route: values.judgeModel,
|
|
5456
|
+
send
|
|
5457
|
+
}),
|
|
5458
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5459
|
+
className: "dsh-ui-hint",
|
|
5460
|
+
children: copy(props.locale, "judgeModelHint")
|
|
5461
|
+
}),
|
|
5462
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5463
|
+
className: "dsh-ui-hint",
|
|
5464
|
+
children: copy(props.locale, "autoContinueModeHint")
|
|
5465
|
+
}),
|
|
5466
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SaveStatus, {
|
|
5467
|
+
snapshot,
|
|
5468
|
+
locale: props.locale,
|
|
5469
|
+
owns
|
|
5317
5470
|
})
|
|
5318
5471
|
]
|
|
5472
|
+
})
|
|
5473
|
+
});
|
|
5474
|
+
}
|
|
5475
|
+
/** One return-feature block: a switch row and its gated "away for N minutes" input. */
|
|
5476
|
+
function ThresholdBlock(props) {
|
|
5477
|
+
const enabled = (props.minutes ?? 0) > 0;
|
|
5478
|
+
const errorId = (0, react.useId)();
|
|
5479
|
+
const input = useTextSetting({
|
|
5480
|
+
stored: String(enabled ? props.minutes : props.fallback),
|
|
5481
|
+
parse: (draft) => {
|
|
5482
|
+
const value = minutesOf(draft);
|
|
5483
|
+
return value === void 0 ? void 0 : {
|
|
5484
|
+
value,
|
|
5485
|
+
draft: String(value)
|
|
5486
|
+
};
|
|
5487
|
+
},
|
|
5488
|
+
send: (0, react.useCallback)((value) => props.send({ [props.field]: value }), [props.send, props.field])
|
|
5489
|
+
});
|
|
5490
|
+
const toggle = (next) => {
|
|
5491
|
+
if (next === enabled) return;
|
|
5492
|
+
if (!next) {
|
|
5493
|
+
props.send({ [props.field]: 0 });
|
|
5494
|
+
return;
|
|
5495
|
+
}
|
|
5496
|
+
props.send({ [props.field]: minutesOf(input.draft) ?? props.fallback });
|
|
5497
|
+
};
|
|
5498
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5499
|
+
className: "dsm-feature",
|
|
5500
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5501
|
+
className: "dsh-ui-toggle-row",
|
|
5502
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5503
|
+
className: "dsh-ui-toggle-text",
|
|
5504
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5505
|
+
className: "dsh-ui-toggle-label",
|
|
5506
|
+
children: copy(props.locale, props.titleKey)
|
|
5507
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5508
|
+
className: "dsh-ui-hint",
|
|
5509
|
+
children: copy(props.locale, props.hintKey)
|
|
5510
|
+
})]
|
|
5511
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
5512
|
+
label: copy(props.locale, props.titleKey),
|
|
5513
|
+
checked: enabled,
|
|
5514
|
+
onChange: toggle
|
|
5515
|
+
})]
|
|
5319
5516
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5320
|
-
className: "
|
|
5517
|
+
className: "dsm-feature-options",
|
|
5518
|
+
"data-disabled": enabled ? void 0 : "true",
|
|
5321
5519
|
children: [
|
|
5322
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5520
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
5521
|
+
className: "dsh-ui-field",
|
|
5522
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5523
|
+
className: "dsm-minutes-control",
|
|
5524
|
+
children: [
|
|
5525
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5526
|
+
className: "dsh-ui-label",
|
|
5527
|
+
children: copy(props.locale, "awayMinutesLabel")
|
|
5528
|
+
}),
|
|
5529
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
5530
|
+
className: "dsm-minutes",
|
|
5531
|
+
inputMode: "numeric",
|
|
5532
|
+
disabled: !enabled,
|
|
5533
|
+
"aria-label": `${copy(props.locale, "awayMinutesLabel")} ${copy(props.locale, "minutesUnit")}`,
|
|
5534
|
+
"aria-invalid": enabled && input.invalid ? true : void 0,
|
|
5535
|
+
"aria-describedby": enabled && input.invalid ? `${errorId}-minutes-error` : void 0,
|
|
5536
|
+
...input.inputProps
|
|
5537
|
+
}),
|
|
5538
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5539
|
+
className: "dsh-ui-meta",
|
|
5540
|
+
children: copy(props.locale, "minutesUnit")
|
|
5541
|
+
})
|
|
5542
|
+
]
|
|
5543
|
+
})
|
|
5327
5544
|
}),
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
children: copy("settingsSaved")
|
|
5332
|
-
}) : null,
|
|
5333
|
-
externalChange ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5334
|
-
className: "dsh-ui-notice dsh-ui-notice--error",
|
|
5545
|
+
enabled && input.invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5546
|
+
id: `${errorId}-minutes-error`,
|
|
5547
|
+
className: "dsh-ui-error",
|
|
5335
5548
|
role: "alert",
|
|
5336
|
-
children: copy("
|
|
5549
|
+
children: copy(props.locale, "minutesInvalid")
|
|
5337
5550
|
}) : null,
|
|
5338
|
-
|
|
5339
|
-
className: "dsh-ui-notice dsh-ui-notice--error",
|
|
5340
|
-
role: "alert",
|
|
5341
|
-
children: failureText(props.locale, snapshot.failure)
|
|
5342
|
-
}) : null
|
|
5551
|
+
props.extra?.(enabled)
|
|
5343
5552
|
]
|
|
5344
5553
|
})]
|
|
5345
5554
|
});
|
|
5346
5555
|
}
|
|
5556
|
+
/** 回到会话时 / When you return row. */
|
|
5557
|
+
function ReturnSettings(props) {
|
|
5558
|
+
const { snapshot, values, gate } = useRowGate(props);
|
|
5559
|
+
const { send, owns } = useConfigPatch(props.model);
|
|
5560
|
+
if (gate !== void 0 || values === void 0) return gate ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
|
|
5561
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5562
|
+
className: "dsm-settings",
|
|
5563
|
+
children: [
|
|
5564
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThresholdBlock, {
|
|
5565
|
+
...props,
|
|
5566
|
+
field: "recapMinutes",
|
|
5567
|
+
titleKey: "recapEnabledLabel",
|
|
5568
|
+
hintKey: "recapEnabledHint",
|
|
5569
|
+
minutes: values.recapMinutes,
|
|
5570
|
+
fallback: 15,
|
|
5571
|
+
send,
|
|
5572
|
+
extra: (enabled) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelField, {
|
|
5573
|
+
...props,
|
|
5574
|
+
field: "model",
|
|
5575
|
+
labelKey: "recapModelLabel",
|
|
5576
|
+
route: values.model,
|
|
5577
|
+
send,
|
|
5578
|
+
disabled: !enabled
|
|
5579
|
+
})
|
|
5580
|
+
}),
|
|
5581
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThresholdBlock, {
|
|
5582
|
+
...props,
|
|
5583
|
+
field: "compactMinutes",
|
|
5584
|
+
titleKey: "compactEnabledLabel",
|
|
5585
|
+
hintKey: "compactEnabledHint",
|
|
5586
|
+
minutes: values.compactMinutes,
|
|
5587
|
+
fallback: 60,
|
|
5588
|
+
send
|
|
5589
|
+
}),
|
|
5590
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SaveStatus, {
|
|
5591
|
+
snapshot,
|
|
5592
|
+
locale: props.locale,
|
|
5593
|
+
owns
|
|
5594
|
+
})
|
|
5595
|
+
]
|
|
5596
|
+
});
|
|
5597
|
+
}
|
|
5598
|
+
/**
|
|
5599
|
+
* One feature's model picker. The catalog loads with the row (first expand);
|
|
5600
|
+
* a stored route missing from the catalog stays selectable so a refresh or a
|
|
5601
|
+
* partial outage never hides the current choice.
|
|
5602
|
+
*/
|
|
5603
|
+
function ModelField(props) {
|
|
5604
|
+
const catalog = useModelCatalog(props.modelCatalog ?? missingCatalog, { subscribe: props.subscribeCatalog });
|
|
5605
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
5606
|
+
const label = copy(props.locale, props.labelKey);
|
|
5607
|
+
const selected = props.route === null ? void 0 : {
|
|
5608
|
+
provider: props.route.provider,
|
|
5609
|
+
model: props.route.model
|
|
5610
|
+
};
|
|
5611
|
+
const choices = parseModelMenuChoices(catalog.catalog);
|
|
5612
|
+
if (selected && !choices.some((choice) => choice.provider === selected.provider && choice.model === selected.model)) choices.push({
|
|
5613
|
+
provider: selected.provider,
|
|
5614
|
+
model: selected.model,
|
|
5615
|
+
label: `${selected.provider} / ${selected.model}`,
|
|
5616
|
+
efforts: []
|
|
5617
|
+
});
|
|
5618
|
+
const key = selected === void 0 ? "" : modelRouteKey(selected.provider, selected.model);
|
|
5619
|
+
const value = choices.find((choice) => modelRouteKey(choice.provider, choice.model) === key)?.label ?? copy(props.locale, "modelFollowOption");
|
|
5620
|
+
const anchor = useModelMenuAnchor(`${label}: ${value}`, void 0, () => setOpen(false));
|
|
5621
|
+
const pick = (route) => {
|
|
5622
|
+
if (selected && route.provider === selected.provider && route.model === selected.model) return;
|
|
5623
|
+
props.send({ [props.field]: {
|
|
5624
|
+
provider: route.provider,
|
|
5625
|
+
model: route.model
|
|
5626
|
+
} });
|
|
5627
|
+
};
|
|
5628
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5629
|
+
className: "dsm-model-field",
|
|
5630
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5631
|
+
className: "dsm-model-choice",
|
|
5632
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5633
|
+
className: "dsh-ui-label",
|
|
5634
|
+
children: label
|
|
5635
|
+
}), renderModelPicker(props.renderFactorySlot, {
|
|
5636
|
+
locale: props.locale,
|
|
5637
|
+
disabled: props.disabled,
|
|
5638
|
+
onOpenChange: (next) => setOpen(next && !props.disabled),
|
|
5639
|
+
listClassName: FREE_CHAT_ROOT,
|
|
5640
|
+
choices,
|
|
5641
|
+
selected,
|
|
5642
|
+
onPick: pick,
|
|
5643
|
+
leading: [{
|
|
5644
|
+
id: "follow",
|
|
5645
|
+
label: copy(props.locale, "modelFollowOption"),
|
|
5646
|
+
selected: selected === void 0
|
|
5647
|
+
}],
|
|
5648
|
+
onPickLeading: () => {
|
|
5649
|
+
if (props.route !== null) props.send({ [props.field]: null });
|
|
5650
|
+
},
|
|
5651
|
+
triggerLabel: value,
|
|
5652
|
+
modelValue: value
|
|
5653
|
+
}, {
|
|
5654
|
+
anchor,
|
|
5655
|
+
fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
5656
|
+
locale: props.locale,
|
|
5657
|
+
open: open && !props.disabled,
|
|
5658
|
+
onOpenChange: (next) => setOpen(next && !props.disabled),
|
|
5659
|
+
listClassName: FREE_CHAT_ROOT,
|
|
5660
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5661
|
+
type: "button",
|
|
5662
|
+
className: "dsh-model-menu-trigger",
|
|
5663
|
+
disabled: props.disabled,
|
|
5664
|
+
"aria-label": `${label}: ${value}`,
|
|
5665
|
+
"aria-haspopup": "menu",
|
|
5666
|
+
"aria-expanded": open,
|
|
5667
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5668
|
+
className: "dsh-model-menu-triggerLabel",
|
|
5669
|
+
children: value
|
|
5670
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${open ? " dsh-model-menu-chevronOpen" : ""}` })]
|
|
5671
|
+
}),
|
|
5672
|
+
choices,
|
|
5673
|
+
selected,
|
|
5674
|
+
onPick: pick,
|
|
5675
|
+
leading: [{
|
|
5676
|
+
id: "follow",
|
|
5677
|
+
label: copy(props.locale, "modelFollowOption"),
|
|
5678
|
+
selected: selected === void 0
|
|
5679
|
+
}],
|
|
5680
|
+
onPickLeading: () => {
|
|
5681
|
+
if (props.route !== null) props.send({ [props.field]: null });
|
|
5682
|
+
},
|
|
5683
|
+
modelValue: value
|
|
5684
|
+
})
|
|
5685
|
+
})]
|
|
5686
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
|
|
5687
|
+
state: catalog,
|
|
5688
|
+
locale: props.locale
|
|
5689
|
+
})]
|
|
5690
|
+
});
|
|
5691
|
+
}
|
|
5692
|
+
/** 会话交接 / Session handoff row: the model behind read_session handoff summaries. */
|
|
5693
|
+
function HandoffSettings(props) {
|
|
5694
|
+
const { snapshot, values, gate } = useRowGate(props);
|
|
5695
|
+
const { send, owns } = useConfigPatch(props.model);
|
|
5696
|
+
if (gate !== void 0 || values === void 0) return gate ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
|
|
5697
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5698
|
+
className: "dsm-settings",
|
|
5699
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5700
|
+
className: "dsm-feature",
|
|
5701
|
+
children: [
|
|
5702
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelField, {
|
|
5703
|
+
...props,
|
|
5704
|
+
field: "handoffModel",
|
|
5705
|
+
labelKey: "handoffModelLabel",
|
|
5706
|
+
route: values.handoffModel,
|
|
5707
|
+
send
|
|
5708
|
+
}),
|
|
5709
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5710
|
+
className: "dsh-ui-hint",
|
|
5711
|
+
children: copy(props.locale, "handoffModelHint")
|
|
5712
|
+
}),
|
|
5713
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SaveStatus, {
|
|
5714
|
+
snapshot,
|
|
5715
|
+
locale: props.locale,
|
|
5716
|
+
owns
|
|
5717
|
+
})
|
|
5718
|
+
]
|
|
5719
|
+
})
|
|
5720
|
+
});
|
|
5721
|
+
}
|
|
5722
|
+
/**
|
|
5723
|
+
* 代理操作 / Agent actions row.
|
|
5724
|
+
*
|
|
5725
|
+
* Two independent switches: each one saves only its own field, so toggling
|
|
5726
|
+
* one never rewrites the other. Both default to off, and the agent decides
|
|
5727
|
+
* on its own once a switch is on — there is no per-call approval here.
|
|
5728
|
+
*
|
|
5729
|
+
* The switches share one sparse writer: the config model accepts a single
|
|
5730
|
+
* write at a time, so a second click while a save is in flight must queue
|
|
5731
|
+
* rather than be dropped. The shared writer merges the pending patches and
|
|
5732
|
+
* flushes them once the current save settles.
|
|
5733
|
+
*/
|
|
5734
|
+
function AgentActionsSettings(props) {
|
|
5735
|
+
const { snapshot, values, gate } = useRowGate(props);
|
|
5736
|
+
const { send, owns } = useConfigPatch(props.model);
|
|
5737
|
+
if (gate !== void 0 || values === void 0) return gate ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
|
|
5738
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5739
|
+
className: "dsm-settings",
|
|
5740
|
+
children: [
|
|
5741
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5742
|
+
className: "dsm-feature",
|
|
5743
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5744
|
+
className: "dsh-ui-toggle-row",
|
|
5745
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5746
|
+
className: "dsh-ui-toggle-text",
|
|
5747
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5748
|
+
className: "dsh-ui-toggle-label",
|
|
5749
|
+
children: copy(props.locale, "allowSelfForkLabel")
|
|
5750
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5751
|
+
className: "dsh-ui-hint",
|
|
5752
|
+
children: copy(props.locale, "allowSelfForkHint")
|
|
5753
|
+
})]
|
|
5754
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
5755
|
+
label: copy(props.locale, "allowSelfForkLabel"),
|
|
5756
|
+
checked: values.allowSelfFork === true,
|
|
5757
|
+
onChange: (next) => {
|
|
5758
|
+
send({ allowSelfFork: next });
|
|
5759
|
+
}
|
|
5760
|
+
})]
|
|
5761
|
+
})
|
|
5762
|
+
}),
|
|
5763
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5764
|
+
className: "dsm-feature",
|
|
5765
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5766
|
+
className: "dsh-ui-toggle-row",
|
|
5767
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5768
|
+
className: "dsh-ui-toggle-text",
|
|
5769
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5770
|
+
className: "dsh-ui-toggle-label",
|
|
5771
|
+
children: copy(props.locale, "allowSendMessageLabel")
|
|
5772
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5773
|
+
className: "dsh-ui-hint",
|
|
5774
|
+
children: copy(props.locale, "allowSendMessageHint")
|
|
5775
|
+
})]
|
|
5776
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
5777
|
+
label: copy(props.locale, "allowSendMessageLabel"),
|
|
5778
|
+
checked: values.allowSendMessage === true,
|
|
5779
|
+
onChange: (next) => {
|
|
5780
|
+
send({ allowSendMessage: next });
|
|
5781
|
+
}
|
|
5782
|
+
})]
|
|
5783
|
+
})
|
|
5784
|
+
}),
|
|
5785
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SaveStatus, {
|
|
5786
|
+
snapshot,
|
|
5787
|
+
locale: props.locale,
|
|
5788
|
+
owns
|
|
5789
|
+
})
|
|
5790
|
+
]
|
|
5791
|
+
});
|
|
5792
|
+
}
|
|
5793
|
+
/**
|
|
5794
|
+
* One collapsible row in the shared Session Management section: a named
|
|
5795
|
+
* details group. The body mounts on the first expand only — opening the page
|
|
5796
|
+
* must not fire every feature's config and model reads — and stays mounted
|
|
5797
|
+
* afterwards so collapsing never drops an in-progress text draft.
|
|
5798
|
+
*/
|
|
5799
|
+
function SessionSettingsDetails(props) {
|
|
5800
|
+
const [visited, setVisited] = (0, react.useState)(false);
|
|
5801
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
5802
|
+
className: `${FREE_CHAT_ROOT} dsm-item`,
|
|
5803
|
+
"data-testid": props.testId,
|
|
5804
|
+
onToggle: (event) => {
|
|
5805
|
+
if (event.currentTarget.open) setVisited(true);
|
|
5806
|
+
},
|
|
5807
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
|
|
5808
|
+
className: "dsm-item-summary",
|
|
5809
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5810
|
+
className: "dsm-item-title",
|
|
5811
|
+
children: copy(props.locale, props.titleKey)
|
|
5812
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5813
|
+
className: "dsm-item-desc",
|
|
5814
|
+
children: copy(props.locale, props.descKey)
|
|
5815
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
|
|
5816
|
+
className: "dsm-item-chevron",
|
|
5817
|
+
"aria-hidden": "true"
|
|
5818
|
+
})]
|
|
5819
|
+
}), visited ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5820
|
+
className: "dsm-item-body",
|
|
5821
|
+
children: props.children
|
|
5822
|
+
}) : null]
|
|
5823
|
+
});
|
|
5824
|
+
}
|
|
5347
5825
|
//#endregion
|
|
5348
5826
|
//#region src/message-editor.tsx
|
|
5349
5827
|
function AttachmentPreview(props) {
|
|
@@ -6281,16 +6759,48 @@ function apply(ctx) {
|
|
|
6281
6759
|
locale: useLiveLocale(client.locale),
|
|
6282
6760
|
model: welcomeBack
|
|
6283
6761
|
}))));
|
|
6284
|
-
stops.push(registerSessionSettingsItem(ctx, {
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6762
|
+
const registerSettingsRow = (item, testId, titleKey, descKey, body) => stops.push(registerSessionSettingsItem(ctx, item, (slotProps) => {
|
|
6763
|
+
const locale = useLiveLocale(client.locale);
|
|
6764
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionSettingsDetails, {
|
|
6765
|
+
testId,
|
|
6766
|
+
titleKey,
|
|
6767
|
+
descKey,
|
|
6768
|
+
locale,
|
|
6769
|
+
children: body(locale, slotProps?.renderFactorySlot)
|
|
6770
|
+
});
|
|
6771
|
+
}));
|
|
6772
|
+
const catalogProps = {
|
|
6290
6773
|
modelCatalog,
|
|
6291
|
-
subscribeCatalog
|
|
6292
|
-
|
|
6293
|
-
|
|
6774
|
+
subscribeCatalog
|
|
6775
|
+
};
|
|
6776
|
+
registerSettingsRow(SESSION_SETTINGS_ITEMS[0], "session-manager-composer-settings-item", "composerGroupTitle", "composerGroupHint", (locale, renderFactorySlot) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerSettings, {
|
|
6777
|
+
model: presenceConfig,
|
|
6778
|
+
locale,
|
|
6779
|
+
renderFactorySlot,
|
|
6780
|
+
...catalogProps
|
|
6781
|
+
}));
|
|
6782
|
+
registerSettingsRow(SESSION_SETTINGS_ITEMS[1], "session-manager-auto-continue-settings-item", "autoContinueGroup", "autoContinueGroupHint", (locale, renderFactorySlot) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoContinueSettings, {
|
|
6783
|
+
model: presenceConfig,
|
|
6784
|
+
locale,
|
|
6785
|
+
renderFactorySlot,
|
|
6786
|
+
...catalogProps
|
|
6787
|
+
}));
|
|
6788
|
+
registerSettingsRow(SESSION_SETTINGS_ITEMS[2], "session-manager-return-settings-item", "returnGroupTitle", "returnGroupHint", (locale, renderFactorySlot) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReturnSettings, {
|
|
6789
|
+
model: presenceConfig,
|
|
6790
|
+
locale,
|
|
6791
|
+
renderFactorySlot,
|
|
6792
|
+
...catalogProps
|
|
6793
|
+
}));
|
|
6794
|
+
registerSettingsRow(SESSION_SETTINGS_ITEMS[3], "session-manager-handoff-settings-item", "handoffGroupTitle", "handoffGroupHint", (locale, renderFactorySlot) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HandoffSettings, {
|
|
6795
|
+
model: presenceConfig,
|
|
6796
|
+
locale,
|
|
6797
|
+
renderFactorySlot,
|
|
6798
|
+
...catalogProps
|
|
6799
|
+
}));
|
|
6800
|
+
registerSettingsRow(SESSION_SETTINGS_ITEMS[4], "session-manager-agent-actions-settings-item", "agentActionsGroup", "agentActionsGroupHint", (locale) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentActionsSettings, {
|
|
6801
|
+
model: presenceConfig,
|
|
6802
|
+
locale
|
|
6803
|
+
}));
|
|
6294
6804
|
stops.push(client.slots.inject("shell.overlay", () => client.slots.register({
|
|
6295
6805
|
name: "shell.overlay",
|
|
6296
6806
|
id: DELETE_DIALOG_ID,
|
|
@@ -6408,49 +6918,10 @@ function DeleteMenuItem(props) {
|
|
|
6408
6918
|
children: uiText(locale, "deleteChat")
|
|
6409
6919
|
});
|
|
6410
6920
|
}
|
|
6411
|
-
/**
|
|
6412
|
-
* Session Management item: a named native details group. The body mounts on
|
|
6413
|
-
* the first expand only — the settings page must not fire every plugin's
|
|
6414
|
-
* config and model reads — and stays mounted afterwards so collapsing never
|
|
6415
|
-
* drops an unsaved draft or the settings-change subscription.
|
|
6416
|
-
*/
|
|
6417
|
-
function SessionManagerSettingsItem(props) {
|
|
6418
|
-
const [visited, setVisited] = (0, react.useState)(false);
|
|
6419
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
6420
|
-
className: `${FREE_CHAT_ROOT} dsm-item`,
|
|
6421
|
-
"data-testid": "session-manager-settings-item",
|
|
6422
|
-
onToggle: (event) => {
|
|
6423
|
-
if (event.currentTarget.open) setVisited(true);
|
|
6424
|
-
},
|
|
6425
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
|
|
6426
|
-
className: "dsm-item-summary",
|
|
6427
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6428
|
-
className: "dsm-item-title",
|
|
6429
|
-
children: uiText(props.locale, "settingsGroupTitle")
|
|
6430
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6431
|
-
className: "dsm-item-desc",
|
|
6432
|
-
children: uiText(props.locale, "settingsGroupHint")
|
|
6433
|
-
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
|
|
6434
|
-
className: "dsm-item-chevron",
|
|
6435
|
-
"aria-hidden": "true"
|
|
6436
|
-
})]
|
|
6437
|
-
}), visited ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6438
|
-
className: "dsm-item-body",
|
|
6439
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionManagerSettings, {
|
|
6440
|
-
model: props.model,
|
|
6441
|
-
locale: props.locale,
|
|
6442
|
-
modelCatalog: props.modelCatalog,
|
|
6443
|
-
subscribeCatalog: props.subscribeCatalog,
|
|
6444
|
-
subscribeSettings: props.subscribeSettings
|
|
6445
|
-
})
|
|
6446
|
-
}) : null]
|
|
6447
|
-
});
|
|
6448
|
-
}
|
|
6449
6921
|
function useLiveLocale(locale) {
|
|
6450
6922
|
return copyLocale((0, react.useSyncExternalStore)((listener) => locale?.subscribe(listener) ?? (() => {}), () => locale?.getSnapshot().active ?? "en", () => locale?.getSnapshot().active ?? "en"));
|
|
6451
6923
|
}
|
|
6452
6924
|
//#endregion
|
|
6453
|
-
exports.SessionManagerSettingsItem = SessionManagerSettingsItem;
|
|
6454
6925
|
exports.apply = apply;
|
|
6455
6926
|
exports.inject = inject;
|
|
6456
6927
|
exports.name = name;
|