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