@klarkxy/dsh-session-manager 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/README.zh-CN.md +4 -2
- package/lib/client.inner.cjs +953 -85
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +953 -85
- package/lib/index.js +923 -104
- package/lib/index.js.map +1 -1
- package/locale/en.json +1 -1
- package/locale/zh.json +2 -2
- package/package.json +16 -4
package/lib/client.js
CHANGED
|
@@ -72,6 +72,9 @@ const text = {
|
|
|
72
72
|
creatingContinuation: "正在创建新会话",
|
|
73
73
|
continueNewHint: "在来源会话的工作区新建会话,将摘要和原会话 ID 填入草稿,不自动发送。",
|
|
74
74
|
compactNothing: "没有值得压缩的内容。",
|
|
75
|
+
settingsTitle: "会话管理",
|
|
76
|
+
settingsGroupTitle: "会话行为",
|
|
77
|
+
settingsGroupHint: "继续灰影、自动继续、交接与会话摘要",
|
|
75
78
|
settingsMode: "继续灰影",
|
|
76
79
|
modeFixed: "固定文本",
|
|
77
80
|
modeAuto: "模型生成",
|
|
@@ -80,7 +83,12 @@ const text = {
|
|
|
80
83
|
continueTextLabel: "固定文本内容",
|
|
81
84
|
continueTextPlaceholder: "留空使用默认「继续」",
|
|
82
85
|
ghostModelLabel: "生成模型",
|
|
83
|
-
ghostModelHint: "
|
|
86
|
+
ghostModelHint: "每轮结束后生成一次并缓存;不选择时使用默认辅助模型。",
|
|
87
|
+
judgeModelLabel: "判定模型",
|
|
88
|
+
judgeModelHint: "判断回复是否提前结束;不选择时使用默认辅助模型。",
|
|
89
|
+
handoffGroup: "交接摘要",
|
|
90
|
+
handoffModelLabel: "交接模型",
|
|
91
|
+
handoffModelHint: "工具请求会话交接摘要时使用;不选择时使用默认辅助模型。",
|
|
84
92
|
welcomeBackGroup: "回到会话时",
|
|
85
93
|
recapEnabledLabel: "显示会话摘要",
|
|
86
94
|
recapEnabledHint: "在对话中回顾上次进展。",
|
|
@@ -104,8 +112,8 @@ const text = {
|
|
|
104
112
|
autoContinueMaxInvalid: "请填写 1–10 的整数。",
|
|
105
113
|
minutesUnit: "分钟",
|
|
106
114
|
modelLabel: "摘要模型",
|
|
107
|
-
modelFollowOption: "
|
|
108
|
-
modelGroupHint: "
|
|
115
|
+
modelFollowOption: "默认辅助模型",
|
|
116
|
+
modelGroupHint: "只用于欢迎回来摘要;留空使用默认辅助模型。",
|
|
109
117
|
saveSettings: "保存",
|
|
110
118
|
settingsSaving: "正在保存",
|
|
111
119
|
settingsSaved: "已保存",
|
|
@@ -148,6 +156,9 @@ const text = {
|
|
|
148
156
|
creatingContinuation: "Creating new session",
|
|
149
157
|
continueNewHint: "Create a session in the source session's workspace and fill its draft with the recap and source session ID, without sending.",
|
|
150
158
|
compactNothing: "There is nothing useful to compact.",
|
|
159
|
+
settingsTitle: "Session Management",
|
|
160
|
+
settingsGroupTitle: "Session behavior",
|
|
161
|
+
settingsGroupHint: "Continue ghost, auto-continue, handoff and session recap",
|
|
151
162
|
settingsMode: "Continue ghost",
|
|
152
163
|
modeFixed: "Fixed text",
|
|
153
164
|
modeAuto: "Model-generated",
|
|
@@ -156,7 +167,12 @@ const text = {
|
|
|
156
167
|
continueTextLabel: "Fixed text",
|
|
157
168
|
continueTextPlaceholder: "Empty uses the default \"Continue\"",
|
|
158
169
|
ghostModelLabel: "Generation model",
|
|
159
|
-
ghostModelHint: "Generated once after each turn and cached. The default follows the
|
|
170
|
+
ghostModelHint: "Generated once after each turn and cached. The default follows the shared auxiliary model.",
|
|
171
|
+
judgeModelLabel: "Judge model",
|
|
172
|
+
judgeModelHint: "Decides whether a reply stopped early. The default follows the shared auxiliary model.",
|
|
173
|
+
handoffGroup: "Session handoff",
|
|
174
|
+
handoffModelLabel: "Handoff model",
|
|
175
|
+
handoffModelHint: "Used when an agent asks for a session handoff summary. The default follows the shared auxiliary model.",
|
|
160
176
|
welcomeBackGroup: "When you return",
|
|
161
177
|
recapEnabledLabel: "Show session recap",
|
|
162
178
|
recapEnabledHint: "Review your previous progress in the conversation.",
|
|
@@ -180,8 +196,8 @@ const text = {
|
|
|
180
196
|
autoContinueMaxInvalid: "Enter a whole number from 1 to 10.",
|
|
181
197
|
minutesUnit: "minutes",
|
|
182
198
|
modelLabel: "Recap model",
|
|
183
|
-
modelFollowOption: "
|
|
184
|
-
modelGroupHint: "Used only for the welcome-back recap; empty follows the
|
|
199
|
+
modelFollowOption: "Default auxiliary model",
|
|
200
|
+
modelGroupHint: "Used only for the welcome-back recap; empty follows the default auxiliary model.",
|
|
185
201
|
saveSettings: "Save",
|
|
186
202
|
settingsSaving: "Saving",
|
|
187
203
|
settingsSaved: "Saved",
|
|
@@ -241,6 +257,8 @@ const CONFIG_READ_ENDPOINT = "config.read";
|
|
|
241
257
|
const CONFIG_WRITE_ENDPOINT = "config.write";
|
|
242
258
|
const AUTO_CONTINUE_STATUS_ENDPOINT = "autocontinue.status";
|
|
243
259
|
const AUTO_CONTINUE_UPDATE_ENDPOINT = "autocontinue.update";
|
|
260
|
+
/** This plugin's own row inside the shared Session Management settings list. */
|
|
261
|
+
const SESSION_SETTINGS_ITEM_ID = "session-manager";
|
|
244
262
|
/** Card entry id in the chat flow; the recap renders at the latest completed turn's tail. */
|
|
245
263
|
const WELCOME_BACK_CARD_ID = "@klarkxy/dsh-session-manager.welcome-back";
|
|
246
264
|
const TURN_TAIL_SLOT = "conversation.chat.turnTail";
|
|
@@ -830,6 +848,15 @@ function parseModelMenuChoices(value, bound) {
|
|
|
830
848
|
});
|
|
831
849
|
return choices;
|
|
832
850
|
}
|
|
851
|
+
/** Reasoning-effort options for one choice; the saved value stays selectable. */
|
|
852
|
+
function modelMenuEffortOptions(choice, current) {
|
|
853
|
+
const listed = (choice?.efforts ?? []).map((item) => ({ ...item }));
|
|
854
|
+
if (current && !listed.some((item) => item.id === current)) listed.push({
|
|
855
|
+
id: current,
|
|
856
|
+
name: current
|
|
857
|
+
});
|
|
858
|
+
return listed;
|
|
859
|
+
}
|
|
833
860
|
//#endregion
|
|
834
861
|
//#region ../dsh-model-route/src/catalog-ui.tsx
|
|
835
862
|
/**
|
|
@@ -1821,8 +1848,48 @@ ${modelMenuCss}
|
|
|
1821
1848
|
font-size: 13px;
|
|
1822
1849
|
line-height: 18px;
|
|
1823
1850
|
}
|
|
1824
|
-
/*
|
|
1825
|
-
*
|
|
1851
|
+
/* General-section item: a named details group mirroring the host's native
|
|
1852
|
+
* item rows (16px vertical padding, a 0.5px l2 rule the shell drops on the
|
|
1853
|
+
* last item, 24px label-control gutter). The body mounts on first expand and
|
|
1854
|
+
* stays mounted while collapsed, so a fold never drops an unsaved draft. */
|
|
1855
|
+
.${FREE_CHAT_ROOT}.dsm-item {
|
|
1856
|
+
padding: 0;
|
|
1857
|
+
border-bottom: 0.5px solid var(--dsw-alias-border-l2);
|
|
1858
|
+
}
|
|
1859
|
+
.${FREE_CHAT_ROOT} .dsm-item-summary {
|
|
1860
|
+
display: flex;
|
|
1861
|
+
align-items: center;
|
|
1862
|
+
justify-content: space-between;
|
|
1863
|
+
gap: 24px;
|
|
1864
|
+
padding: 16px 0;
|
|
1865
|
+
list-style: none;
|
|
1866
|
+
cursor: pointer;
|
|
1867
|
+
}
|
|
1868
|
+
.${FREE_CHAT_ROOT} .dsm-item-summary::-webkit-details-marker { display: none; }
|
|
1869
|
+
.${FREE_CHAT_ROOT} .dsm-item-summary > span:first-child { min-width: 0; }
|
|
1870
|
+
.${FREE_CHAT_ROOT} .dsm-item-title {
|
|
1871
|
+
display: block;
|
|
1872
|
+
font-size: 14px;
|
|
1873
|
+
line-height: 20px;
|
|
1874
|
+
color: var(--dsw-alias-label-primary);
|
|
1875
|
+
}
|
|
1876
|
+
.${FREE_CHAT_ROOT} .dsm-item-desc {
|
|
1877
|
+
display: block;
|
|
1878
|
+
margin-top: 4px;
|
|
1879
|
+
font-size: 12px;
|
|
1880
|
+
line-height: 18px;
|
|
1881
|
+
color: var(--dsw-alias-label-secondary);
|
|
1882
|
+
}
|
|
1883
|
+
.${FREE_CHAT_ROOT} .dsm-item-chevron {
|
|
1884
|
+
width: 14px;
|
|
1885
|
+
height: 14px;
|
|
1886
|
+
color: var(--dsw-alias-label-secondary);
|
|
1887
|
+
transition: transform 0.15s ease;
|
|
1888
|
+
}
|
|
1889
|
+
.${FREE_CHAT_ROOT}.dsm-item[open] > .dsm-item-summary .dsm-item-chevron { transform: rotate(180deg); }
|
|
1890
|
+
.${FREE_CHAT_ROOT} .dsm-item-body { padding: 0 0 16px; }
|
|
1891
|
+
/* Settings group body: one quiet reading surface. Rules, not nested wells,
|
|
1892
|
+
* separate sections; dependent controls align with the feature they belong to. */
|
|
1826
1893
|
.${FREE_CHAT_ROOT} .dsm-settings {
|
|
1827
1894
|
display: flex;
|
|
1828
1895
|
flex-direction: column;
|
|
@@ -3189,7 +3256,7 @@ function createPresenceConfigModel(host, delay = (ms) => new Promise((resolve) =
|
|
|
3189
3256
|
function same(view, patch) {
|
|
3190
3257
|
if (view === void 0) return false;
|
|
3191
3258
|
return Object.entries(patch).every(([key, value]) => {
|
|
3192
|
-
if (key === "model" || key === "ghostModel") return sameRoute(view[key], value);
|
|
3259
|
+
if (key === "model" || key === "ghostModel" || key === "judgeModel" || key === "handoffModel") return sameRoute(view[key], value);
|
|
3193
3260
|
return view[key] === value;
|
|
3194
3261
|
});
|
|
3195
3262
|
}
|
|
@@ -3597,6 +3664,153 @@ function modelSettingsChangesIdentity(packageName) {
|
|
|
3597
3664
|
namespace: segment
|
|
3598
3665
|
};
|
|
3599
3666
|
}
|
|
3667
|
+
/**
|
|
3668
|
+
* Shared "Session Management" settings section id. The section is provided by
|
|
3669
|
+
* shared infrastructure (see `registerSessionSettingsItem` in `client-utils`),
|
|
3670
|
+
* not by any one business plugin: every participant registers only a row into
|
|
3671
|
+
* {@link SESSION_SETTINGS_ITEM_SLOT}, so any enabled subset yields the same page.
|
|
3672
|
+
*/
|
|
3673
|
+
const SESSION_SETTINGS_SECTION_ID = "session-manager";
|
|
3674
|
+
/**
|
|
3675
|
+
* Child list slot declared by the shared Session Management page. Neutral
|
|
3676
|
+
* naming on purpose: participants never import each other, they only
|
|
3677
|
+
* contribute `{id, order}` rows into this slot while the page is alive.
|
|
3678
|
+
*/
|
|
3679
|
+
const SESSION_SETTINGS_ITEM_SLOT = "dsh.session-settings.item";
|
|
3680
|
+
/**
|
|
3681
|
+
* Host-owned nav icon seat: a root-scoped list where each entry's `id` must
|
|
3682
|
+
* equal the `settings.section` id it decorates. Owner props are
|
|
3683
|
+
* `{size, active}`; icons stay `aria-hidden` so the section label remains the
|
|
3684
|
+
* accessible name. Contributors into a shared section may register the same
|
|
3685
|
+
* icon `id` with different priorities — the ledger keeps every entry while the
|
|
3686
|
+
* host renders one icon per section.
|
|
3687
|
+
*/
|
|
3688
|
+
const SETTINGS_SECTION_ICON_SLOT = "settings.section.icon";
|
|
3689
|
+
/** Authenticated host-RPC channel for get/set of the shared default. */
|
|
3690
|
+
const AUXILIARY_MODEL_CHANNEL = "/dsh-auxiliary-model";
|
|
3691
|
+
/** Model-settings namespace under which the shared default is published. */
|
|
3692
|
+
const AUXILIARY_MODEL_NS = "@klarkxy/dsh-plugin-kit/auxiliary-model";
|
|
3693
|
+
/**
|
|
3694
|
+
* Typert identity of the shared change stream. Mounted on the root context
|
|
3695
|
+
* with ownerNs filtering, so frames only carry {@link AUXILIARY_MODEL_NS}.
|
|
3696
|
+
*/
|
|
3697
|
+
const AUXILIARY_MODEL_CHANGES_IDENTITY = modelSettingsChangesIdentity("@klarkxy/dsh-plugin-kit");
|
|
3698
|
+
//#endregion
|
|
3699
|
+
//#region ../dsh-plugin-kit/src/client-utils.ts
|
|
3700
|
+
/** One `<svg>` with the shared icon chrome; children are plain elements. */
|
|
3701
|
+
function lineIcon(children) {
|
|
3702
|
+
return function SettingsSectionIcon(props) {
|
|
3703
|
+
return (0, react.createElement)("svg", {
|
|
3704
|
+
"aria-hidden": "true",
|
|
3705
|
+
width: props.size ?? 16,
|
|
3706
|
+
height: props.size ?? 16,
|
|
3707
|
+
viewBox: "0 0 16 16",
|
|
3708
|
+
fill: "none",
|
|
3709
|
+
stroke: "currentColor",
|
|
3710
|
+
strokeWidth: 1,
|
|
3711
|
+
strokeLinecap: "round",
|
|
3712
|
+
strokeLinejoin: "round"
|
|
3713
|
+
}, ...children.map(([tag, attrs], index) => (0, react.createElement)(tag, {
|
|
3714
|
+
key: index,
|
|
3715
|
+
...attrs
|
|
3716
|
+
})));
|
|
3717
|
+
};
|
|
3718
|
+
}
|
|
3719
|
+
/** Chat bubble for the shared Session Management section. */
|
|
3720
|
+
const IconSessionSettings = lineIcon([["path", { d: "M8.5 13a5.5 5.5 0 0 0 5.5-5.5A5.5 5.5 0 0 0 8.5 2 5.5 5.5 0 0 0 3 7.5a5.48 5.48 0 0 0 .9 3L2.8 12.6a.5.5 0 0 0 .62.62l2.9-.98a5.48 5.48 0 0 0 2.18.76Z" }]]);
|
|
3721
|
+
/** Private Cordis rendezvous; every bundled copy of this helper resolves the same root service. */
|
|
3722
|
+
const SESSION_SETTINGS_PAGE_SERVICE = "dshSessionSettingsPage";
|
|
3723
|
+
/** The page component renders only its declared child list: items paint their own titles and styles. */
|
|
3724
|
+
function SessionSettingsPageView(props) {
|
|
3725
|
+
return props.renderSlot(SESSION_SETTINGS_ITEM_SLOT, {});
|
|
3726
|
+
}
|
|
3727
|
+
/**
|
|
3728
|
+
* Acquire the root-owned page service, creating and providing it on first use.
|
|
3729
|
+
* One counter covers participants, the section wait and the provider: the last
|
|
3730
|
+
* release removes the wait and then the provider synchronously (Cordis deletes
|
|
3731
|
+
* the impl before its async disposer awaits), so a later acquire sees a clean
|
|
3732
|
+
* get() miss and can re-establish the page.
|
|
3733
|
+
*/
|
|
3734
|
+
function acquireSessionSettingsPage(root) {
|
|
3735
|
+
const existing = root.get(SESSION_SETTINGS_PAGE_SERVICE, false);
|
|
3736
|
+
if (existing) return existing;
|
|
3737
|
+
const slots = root.slots;
|
|
3738
|
+
let users = 0;
|
|
3739
|
+
let stopPage;
|
|
3740
|
+
let unprovide;
|
|
3741
|
+
const page = {
|
|
3742
|
+
retain() {
|
|
3743
|
+
users += 1;
|
|
3744
|
+
if (!stopPage) stopPage = slots.inject("settings.section", () => slots.register({
|
|
3745
|
+
name: "settings.section",
|
|
3746
|
+
id: SESSION_SETTINGS_SECTION_ID,
|
|
3747
|
+
order: 30,
|
|
3748
|
+
label: () => {
|
|
3749
|
+
const locale = root.get("locale", false);
|
|
3750
|
+
return String(locale?.getSnapshot?.().active ?? "").startsWith("zh") ? "会话管理" : "Session Management";
|
|
3751
|
+
},
|
|
3752
|
+
children: { [SESSION_SETTINGS_ITEM_SLOT]: {
|
|
3753
|
+
kind: "list",
|
|
3754
|
+
scope: "root"
|
|
3755
|
+
} }
|
|
3756
|
+
}, SessionSettingsPageView));
|
|
3757
|
+
},
|
|
3758
|
+
release() {
|
|
3759
|
+
if (users <= 0) return;
|
|
3760
|
+
users -= 1;
|
|
3761
|
+
if (users > 0) return;
|
|
3762
|
+
stopPage?.();
|
|
3763
|
+
stopPage = void 0;
|
|
3764
|
+
unprovide?.();
|
|
3765
|
+
}
|
|
3766
|
+
};
|
|
3767
|
+
unprovide = root.provide(SESSION_SETTINGS_PAGE_SERVICE, page);
|
|
3768
|
+
return page;
|
|
3769
|
+
}
|
|
3770
|
+
/**
|
|
3771
|
+
* Contribute one row to the shared Session Management settings page.
|
|
3772
|
+
*
|
|
3773
|
+
* `ctx` is the plugin's own context: the row's slot wait unloads with the
|
|
3774
|
+
* plugin, while the page itself is retained on the shared root context for as
|
|
3775
|
+
* long as any contributor lives. There is no fallback seat — when every
|
|
3776
|
+
* contributor is gone the section disappears with the page. Returns a disposer.
|
|
3777
|
+
* Shared root-owned infrastructure (the auxiliary-model card) calls this on
|
|
3778
|
+
* `ctx.root` so its row, icon and page share are owned by the root lifetime
|
|
3779
|
+
* and released by the returned disposer, not by any plugin fiber.
|
|
3780
|
+
*/
|
|
3781
|
+
function registerSessionSettingsItem(ctx, item, component) {
|
|
3782
|
+
const slots = ctx.slots;
|
|
3783
|
+
const root = ctx.root;
|
|
3784
|
+
const page = acquireSessionSettingsPage(root);
|
|
3785
|
+
let stopItem;
|
|
3786
|
+
let stopIcon;
|
|
3787
|
+
try {
|
|
3788
|
+
page.retain();
|
|
3789
|
+
stopItem = slots.inject(SESSION_SETTINGS_ITEM_SLOT, () => slots.register({
|
|
3790
|
+
name: SESSION_SETTINGS_ITEM_SLOT,
|
|
3791
|
+
...item
|
|
3792
|
+
}, component));
|
|
3793
|
+
stopIcon = slots.inject(SETTINGS_SECTION_ICON_SLOT, () => slots.register({
|
|
3794
|
+
name: SETTINGS_SECTION_ICON_SLOT,
|
|
3795
|
+
id: SESSION_SETTINGS_SECTION_ID,
|
|
3796
|
+
order: item.order,
|
|
3797
|
+
priority: item.order
|
|
3798
|
+
}, IconSessionSettings));
|
|
3799
|
+
} catch (error) {
|
|
3800
|
+
stopIcon?.();
|
|
3801
|
+
stopItem?.();
|
|
3802
|
+
page.release();
|
|
3803
|
+
throw error;
|
|
3804
|
+
}
|
|
3805
|
+
let disposed = false;
|
|
3806
|
+
return () => {
|
|
3807
|
+
if (disposed) return;
|
|
3808
|
+
disposed = true;
|
|
3809
|
+
stopIcon?.();
|
|
3810
|
+
stopItem?.();
|
|
3811
|
+
page.release();
|
|
3812
|
+
};
|
|
3813
|
+
}
|
|
3600
3814
|
//#endregion
|
|
3601
3815
|
//#region ../dsh-plugin-kit/src/model-settings-client.ts
|
|
3602
3816
|
/** This stream carries invalidations only; ready is never a settings snapshot. */
|
|
@@ -3766,6 +3980,488 @@ function createModelSettingsSubscriber(ctx, packageName, identity = modelSetting
|
|
|
3766
3980
|
} });
|
|
3767
3981
|
}
|
|
3768
3982
|
//#endregion
|
|
3983
|
+
//#region ../dsh-plugin-kit/src/auxiliary-model-client.tsx
|
|
3984
|
+
const AUXILIARY_MODEL_PACKAGE = "@klarkxy/dsh-plugin-kit";
|
|
3985
|
+
const AUXILIARY_MODEL_SEAT_ID = "auxiliary-model";
|
|
3986
|
+
/** Private Cordis rendezvous; every bundled copy resolves the same root service. */
|
|
3987
|
+
const AUXILIARY_MODEL_CARD_SERVICE = "dshAuxiliaryModelSettingsCard";
|
|
3988
|
+
/** Theme-token-only stylesheet: officialUiCss scope plus the shared ModelMenu recipe. */
|
|
3989
|
+
const auxiliaryModelCss = `${officialUiCss("ams-root")}${modelMenuCss}
|
|
3990
|
+
.ams-root .ams-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
|
|
3991
|
+
`;
|
|
3992
|
+
const auxiliaryCopy = {
|
|
3993
|
+
zh: {
|
|
3994
|
+
title: "默认辅助模型",
|
|
3995
|
+
help: "各辅助功能(如标题生成、摘要)没有单独指定模型时使用的模型;仅影响声明使用它的功能。",
|
|
3996
|
+
model: "辅助模型",
|
|
3997
|
+
modelDefault: "跟随会话或宿主默认",
|
|
3998
|
+
modelHint: "不选择模型时,各功能按自己的规则决定对话模型。",
|
|
3999
|
+
effortDefault: "跟随模型默认",
|
|
4000
|
+
loading: "正在读取设置…",
|
|
4001
|
+
failed: "无法读取模型设置。",
|
|
4002
|
+
stale: "设置已被其他窗口更新,请刷新后重试。",
|
|
4003
|
+
saveFailed: "保存失败,原设置未更改。请重试。",
|
|
4004
|
+
saved: "已保存。",
|
|
4005
|
+
retry: "重试",
|
|
4006
|
+
refresh: "刷新设置",
|
|
4007
|
+
syncUnavailable: "自动刷新不可用,请手动刷新设置。"
|
|
4008
|
+
},
|
|
4009
|
+
en: {
|
|
4010
|
+
title: "Default Auxiliary Model",
|
|
4011
|
+
help: "The model used by auxiliary features (such as title generation or summarizing) that do not name their own model; only features that opted in are affected.",
|
|
4012
|
+
model: "Auxiliary model",
|
|
4013
|
+
modelDefault: "Follow session or host default",
|
|
4014
|
+
modelHint: "With no model selected, each feature follows its own rules for the conversation model.",
|
|
4015
|
+
effortDefault: "Follow model default",
|
|
4016
|
+
loading: "Loading settings…",
|
|
4017
|
+
failed: "Could not load model settings.",
|
|
4018
|
+
stale: "Settings were changed elsewhere; refresh and retry.",
|
|
4019
|
+
saveFailed: "Save failed; the previous setting was not changed. Try again.",
|
|
4020
|
+
saved: "Saved.",
|
|
4021
|
+
retry: "Retry",
|
|
4022
|
+
refresh: "Refresh settings",
|
|
4023
|
+
syncUnavailable: "Automatic refresh unavailable; refresh settings manually."
|
|
4024
|
+
}
|
|
4025
|
+
};
|
|
4026
|
+
/** A failed RPC keeps its code so the card can translate known rejections. */
|
|
4027
|
+
var AuxiliaryRpcError = class extends Error {
|
|
4028
|
+
code;
|
|
4029
|
+
constructor(code, message) {
|
|
4030
|
+
super(message);
|
|
4031
|
+
this.code = code;
|
|
4032
|
+
this.name = "AuxiliaryRpcError";
|
|
4033
|
+
}
|
|
4034
|
+
};
|
|
4035
|
+
/** Host UI language, following live switches with the host's English fallback. */
|
|
4036
|
+
function useAuxiliaryLocale(client) {
|
|
4037
|
+
const subscribe = (0, react.useCallback)((listener) => client.locale?.subscribe(listener) ?? (() => {}), [client]);
|
|
4038
|
+
const snapshot = (0, react.useCallback)(() => client.locale?.getSnapshot().active ?? "en", [client]);
|
|
4039
|
+
return String((0, react.useSyncExternalStore)(subscribe, snapshot, snapshot)).startsWith("zh") ? "zh" : "en";
|
|
4040
|
+
}
|
|
4041
|
+
function isAbort(error) {
|
|
4042
|
+
return error instanceof Error && (error.name === "AbortError" || /abort|cancel/i.test(error.message));
|
|
4043
|
+
}
|
|
4044
|
+
function isStaleRejection(error) {
|
|
4045
|
+
if (!(error instanceof AuxiliaryRpcError)) return false;
|
|
4046
|
+
return error.code === "stale" || error.code === "auxiliary-model/conflict" || /changed|revision|reload|stale|conflict/i.test(error.message);
|
|
4047
|
+
}
|
|
4048
|
+
async function callAuxiliaryRpc(client, endpoint, payload = {}) {
|
|
4049
|
+
const result = await client.connection.rpc.call(AUXILIARY_MODEL_CHANNEL, endpoint, payload);
|
|
4050
|
+
if (!result || result.ok !== true) {
|
|
4051
|
+
const error = result && "error" in result ? result.error : void 0;
|
|
4052
|
+
throw new AuxiliaryRpcError(error?.code ?? "unknown", error?.message ?? `RPC ${endpoint} failed`);
|
|
4053
|
+
}
|
|
4054
|
+
return result.value;
|
|
4055
|
+
}
|
|
4056
|
+
/**
|
|
4057
|
+
* The shared card. All state comes from the shared host service over the
|
|
4058
|
+
* plugin-kit RPC channel; the card only renders the stored row.
|
|
4059
|
+
*/
|
|
4060
|
+
function AuxiliaryModelCard({ client, subscribe }) {
|
|
4061
|
+
const language = useAuxiliaryLocale(client);
|
|
4062
|
+
const text = auxiliaryCopy[language];
|
|
4063
|
+
const [settings, setSettings] = (0, react.useState)();
|
|
4064
|
+
const catalogState = useModelCatalog((0, react.useCallback)(() => readModelCatalog(client), [client]), { subscribe: (0, react.useCallback)((change) => client.connection.generation?.subscribe(change), [client]) });
|
|
4065
|
+
const { refresh: refreshCatalog } = catalogState;
|
|
4066
|
+
const choices = parseModelMenuChoices(catalogState.catalog, settings?.model ?? void 0);
|
|
4067
|
+
const [menuOpen, setMenuOpen] = (0, react.useState)(false);
|
|
4068
|
+
const [error, setError] = (0, react.useState)("");
|
|
4069
|
+
const [note, setNote] = (0, react.useState)(false);
|
|
4070
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
4071
|
+
const [syncError, setSyncError] = (0, react.useState)("");
|
|
4072
|
+
const operation = (0, react.useRef)({
|
|
4073
|
+
epoch: 0,
|
|
4074
|
+
active: false,
|
|
4075
|
+
reading: false,
|
|
4076
|
+
writing: false,
|
|
4077
|
+
pending: false
|
|
4078
|
+
});
|
|
4079
|
+
const refreshRef = (0, react.useRef)(() => {});
|
|
4080
|
+
const refresh = () => {
|
|
4081
|
+
const state = operation.current;
|
|
4082
|
+
if (!state.active) return;
|
|
4083
|
+
if (state.reading || state.writing) {
|
|
4084
|
+
state.pending = true;
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
4087
|
+
state.pending = false;
|
|
4088
|
+
state.reading = true;
|
|
4089
|
+
setBusy(true);
|
|
4090
|
+
const epoch = state.epoch;
|
|
4091
|
+
callAuxiliaryRpc(client, "defaults.get").then((next) => {
|
|
4092
|
+
if (state.active && state.epoch === epoch && !state.pending) {
|
|
4093
|
+
setSettings(next);
|
|
4094
|
+
setError("");
|
|
4095
|
+
}
|
|
4096
|
+
}).catch((reason) => {
|
|
4097
|
+
if (state.active && state.epoch === epoch && !state.pending && !isAbort(reason)) setError("failed");
|
|
4098
|
+
}).finally(() => {
|
|
4099
|
+
if (!state.active || state.epoch !== epoch) return;
|
|
4100
|
+
state.reading = false;
|
|
4101
|
+
setBusy(false);
|
|
4102
|
+
if (state.pending) refreshRef.current();
|
|
4103
|
+
});
|
|
4104
|
+
};
|
|
4105
|
+
refreshRef.current = refresh;
|
|
4106
|
+
(0, react.useEffect)(() => {
|
|
4107
|
+
const state = operation.current;
|
|
4108
|
+
state.epoch++;
|
|
4109
|
+
state.active = true;
|
|
4110
|
+
state.reading = false;
|
|
4111
|
+
state.writing = false;
|
|
4112
|
+
state.pending = false;
|
|
4113
|
+
setBusy(false);
|
|
4114
|
+
setSettings(void 0);
|
|
4115
|
+
setError("");
|
|
4116
|
+
setNote(false);
|
|
4117
|
+
refreshRef.current();
|
|
4118
|
+
return () => {
|
|
4119
|
+
state.active = false;
|
|
4120
|
+
state.epoch++;
|
|
4121
|
+
};
|
|
4122
|
+
}, [client]);
|
|
4123
|
+
(0, react.useEffect)(() => {
|
|
4124
|
+
setSyncError("");
|
|
4125
|
+
let live = true;
|
|
4126
|
+
let stop;
|
|
4127
|
+
try {
|
|
4128
|
+
stop = subscribe?.(() => {
|
|
4129
|
+
if (live) {
|
|
4130
|
+
setSyncError("");
|
|
4131
|
+
refreshCatalog();
|
|
4132
|
+
refreshRef.current();
|
|
4133
|
+
}
|
|
4134
|
+
}, (message) => {
|
|
4135
|
+
if (live) setSyncError(message);
|
|
4136
|
+
});
|
|
4137
|
+
} catch {
|
|
4138
|
+
if (live) setSyncError("Model settings sync service unavailable");
|
|
4139
|
+
}
|
|
4140
|
+
return () => {
|
|
4141
|
+
live = false;
|
|
4142
|
+
try {
|
|
4143
|
+
stop?.();
|
|
4144
|
+
} catch {}
|
|
4145
|
+
};
|
|
4146
|
+
}, [subscribe, refreshCatalog]);
|
|
4147
|
+
const save = (0, react.useCallback)((model) => {
|
|
4148
|
+
const state = operation.current;
|
|
4149
|
+
if (!settings || !state.active || state.writing || state.reading) return;
|
|
4150
|
+
state.writing = true;
|
|
4151
|
+
const epoch = state.epoch;
|
|
4152
|
+
setBusy(true);
|
|
4153
|
+
setNote(false);
|
|
4154
|
+
setError("");
|
|
4155
|
+
callAuxiliaryRpc(client, "defaults.set", {
|
|
4156
|
+
model: model ?? {
|
|
4157
|
+
provider: "",
|
|
4158
|
+
model: ""
|
|
4159
|
+
},
|
|
4160
|
+
expectedRevision: settings.revision
|
|
4161
|
+
}).then((next) => {
|
|
4162
|
+
if (state.active && state.epoch === epoch) {
|
|
4163
|
+
setSettings(next);
|
|
4164
|
+
setNote(true);
|
|
4165
|
+
refreshCatalog();
|
|
4166
|
+
}
|
|
4167
|
+
}).catch((reason) => {
|
|
4168
|
+
if (state.active && state.epoch === epoch) setError(isStaleRejection(reason) ? "stale" : "saveFailed");
|
|
4169
|
+
}).finally(() => {
|
|
4170
|
+
if (!state.active || state.epoch !== epoch) return;
|
|
4171
|
+
state.writing = false;
|
|
4172
|
+
setBusy(false);
|
|
4173
|
+
if (state.pending) refreshRef.current();
|
|
4174
|
+
});
|
|
4175
|
+
}, [
|
|
4176
|
+
client,
|
|
4177
|
+
settings,
|
|
4178
|
+
refreshCatalog
|
|
4179
|
+
]);
|
|
4180
|
+
const locked = busy;
|
|
4181
|
+
if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4182
|
+
className: "ams-root dsh-ui-card",
|
|
4183
|
+
"data-testid": "auxiliary-model-settings",
|
|
4184
|
+
children: [
|
|
4185
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4186
|
+
className: "dsh-ui-heading",
|
|
4187
|
+
children: text.title
|
|
4188
|
+
}),
|
|
4189
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4190
|
+
className: "dsh-ui-help",
|
|
4191
|
+
children: text.help
|
|
4192
|
+
}),
|
|
4193
|
+
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4194
|
+
role: "alert",
|
|
4195
|
+
className: "dsh-ui-error",
|
|
4196
|
+
children: text[error]
|
|
4197
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4198
|
+
role: "status",
|
|
4199
|
+
className: "dsh-ui-hint",
|
|
4200
|
+
children: text.loading
|
|
4201
|
+
}),
|
|
4202
|
+
syncError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4203
|
+
role: "status",
|
|
4204
|
+
className: "dsh-ui-hint",
|
|
4205
|
+
children: text.syncUnavailable
|
|
4206
|
+
}),
|
|
4207
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4208
|
+
className: "dsh-ui-actions",
|
|
4209
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4210
|
+
variant: "ghost",
|
|
4211
|
+
size: "sm",
|
|
4212
|
+
disabled: locked,
|
|
4213
|
+
onClick: () => refreshRef.current(),
|
|
4214
|
+
children: text.retry
|
|
4215
|
+
})
|
|
4216
|
+
})
|
|
4217
|
+
]
|
|
4218
|
+
});
|
|
4219
|
+
const current = settings.model ?? {
|
|
4220
|
+
provider: "",
|
|
4221
|
+
model: ""
|
|
4222
|
+
};
|
|
4223
|
+
const follow = !current.provider || !current.model;
|
|
4224
|
+
const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
4225
|
+
const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
|
|
4226
|
+
const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
|
|
4227
|
+
const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
|
|
4228
|
+
const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
|
|
4229
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4230
|
+
className: "ams-root dsh-ui-card",
|
|
4231
|
+
"data-testid": "auxiliary-model-settings",
|
|
4232
|
+
children: [
|
|
4233
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4234
|
+
className: "dsh-ui-heading",
|
|
4235
|
+
children: text.title
|
|
4236
|
+
}),
|
|
4237
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4238
|
+
className: "dsh-ui-help",
|
|
4239
|
+
children: text.help
|
|
4240
|
+
}),
|
|
4241
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4242
|
+
className: "dsh-ui-field",
|
|
4243
|
+
children: [
|
|
4244
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4245
|
+
className: "dsh-ui-label",
|
|
4246
|
+
children: text.model
|
|
4247
|
+
}),
|
|
4248
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
|
|
4249
|
+
state: catalogState,
|
|
4250
|
+
locale: language
|
|
4251
|
+
}),
|
|
4252
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
4253
|
+
locale: language,
|
|
4254
|
+
open: menuOpen && !locked,
|
|
4255
|
+
onOpenChange: (next) => setMenuOpen(next && !locked),
|
|
4256
|
+
listClassName: "ams-root ams-menu-portal",
|
|
4257
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4258
|
+
type: "button",
|
|
4259
|
+
className: "dsh-model-menu-trigger",
|
|
4260
|
+
disabled: locked,
|
|
4261
|
+
"aria-label": `${text.model}: ${currentLabel}`,
|
|
4262
|
+
children: [
|
|
4263
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4264
|
+
className: "dsh-model-menu-triggerLabel",
|
|
4265
|
+
children: currentLabel
|
|
4266
|
+
}),
|
|
4267
|
+
!follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4268
|
+
className: "dsh-model-menu-triggerEffort",
|
|
4269
|
+
children: effortName ?? text.effortDefault
|
|
4270
|
+
}) : null,
|
|
4271
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
|
|
4272
|
+
]
|
|
4273
|
+
}),
|
|
4274
|
+
choices,
|
|
4275
|
+
selected: follow ? void 0 : {
|
|
4276
|
+
provider: current.provider,
|
|
4277
|
+
model: current.model
|
|
4278
|
+
},
|
|
4279
|
+
onPick: (picked) => save({
|
|
4280
|
+
provider: picked.provider,
|
|
4281
|
+
model: picked.model
|
|
4282
|
+
}),
|
|
4283
|
+
leading: [{
|
|
4284
|
+
id: "default",
|
|
4285
|
+
label: text.modelDefault,
|
|
4286
|
+
selected: follow
|
|
4287
|
+
}],
|
|
4288
|
+
onPickLeading: () => save(null),
|
|
4289
|
+
efforts,
|
|
4290
|
+
selectedEffort: current.reasoningEffort || "",
|
|
4291
|
+
onPickEffort: (effort) => save(follow ? null : {
|
|
4292
|
+
provider: current.provider,
|
|
4293
|
+
model: current.model,
|
|
4294
|
+
...effort ? { reasoningEffort: effort } : {}
|
|
4295
|
+
}),
|
|
4296
|
+
modelLabel: text.model,
|
|
4297
|
+
modelValue: currentLabel,
|
|
4298
|
+
defaultEffortLabel: text.effortDefault
|
|
4299
|
+
}),
|
|
4300
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4301
|
+
className: "dsh-ui-hint",
|
|
4302
|
+
children: text.modelHint
|
|
4303
|
+
})
|
|
4304
|
+
]
|
|
4305
|
+
}),
|
|
4306
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4307
|
+
className: "ams-row",
|
|
4308
|
+
children: [
|
|
4309
|
+
syncError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4310
|
+
role: "status",
|
|
4311
|
+
className: "dsh-ui-hint",
|
|
4312
|
+
children: text.syncUnavailable
|
|
4313
|
+
}),
|
|
4314
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4315
|
+
role: "alert",
|
|
4316
|
+
className: "dsh-ui-error",
|
|
4317
|
+
children: text[error]
|
|
4318
|
+
}),
|
|
4319
|
+
note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4320
|
+
role: "status",
|
|
4321
|
+
className: "dsh-ui-notice",
|
|
4322
|
+
children: text.saved
|
|
4323
|
+
}),
|
|
4324
|
+
error || syncError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4325
|
+
variant: "ghost",
|
|
4326
|
+
size: "sm",
|
|
4327
|
+
disabled: locked,
|
|
4328
|
+
onClick: () => refreshRef.current(),
|
|
4329
|
+
children: text.refresh
|
|
4330
|
+
}) : null
|
|
4331
|
+
]
|
|
4332
|
+
})
|
|
4333
|
+
]
|
|
4334
|
+
});
|
|
4335
|
+
}
|
|
4336
|
+
const AUXILIARY_MODEL_WATCH_COORDINATOR = "dshAuxiliaryModelWatchCoordinator";
|
|
4337
|
+
function acquireWatchCoordinator(root) {
|
|
4338
|
+
const existing = root.get(AUXILIARY_MODEL_WATCH_COORDINATOR, false);
|
|
4339
|
+
if (existing) return existing;
|
|
4340
|
+
let unprovide;
|
|
4341
|
+
const coordinator = {
|
|
4342
|
+
tail: Promise.resolve(),
|
|
4343
|
+
active: 0,
|
|
4344
|
+
mount() {
|
|
4345
|
+
coordinator.active += 1;
|
|
4346
|
+
const previousTail = coordinator.tail;
|
|
4347
|
+
const lease = {
|
|
4348
|
+
closed: false,
|
|
4349
|
+
done() {
|
|
4350
|
+
if (lease.closed) return;
|
|
4351
|
+
lease.closed = true;
|
|
4352
|
+
coordinator.active -= 1;
|
|
4353
|
+
const owned = lease.fiber;
|
|
4354
|
+
lease.fiber = void 0;
|
|
4355
|
+
coordinator.tail = coordinator.tail.then(() => Promise.resolve(owned?.dispose()).catch(() => {}));
|
|
4356
|
+
coordinator.tail.then(() => {
|
|
4357
|
+
if (coordinator.active > 0) return;
|
|
4358
|
+
try {
|
|
4359
|
+
if (root.get(AUXILIARY_MODEL_WATCH_COORDINATOR, false) === coordinator) Promise.resolve(unprovide?.()).catch(() => {});
|
|
4360
|
+
} catch {}
|
|
4361
|
+
});
|
|
4362
|
+
},
|
|
4363
|
+
fiber: void 0
|
|
4364
|
+
};
|
|
4365
|
+
previousTail.then(() => {
|
|
4366
|
+
if (lease.closed) return;
|
|
4367
|
+
try {
|
|
4368
|
+
lease.fiber = root.inject(["remote"], (injected) => {
|
|
4369
|
+
const scope = injected;
|
|
4370
|
+
const mounting = scope.remote.$mount(modelSettingsChangesRemote(AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY));
|
|
4371
|
+
mounting.catch(() => {});
|
|
4372
|
+
scope.effect(() => async () => {
|
|
4373
|
+
await (await mounting.catch(() => void 0))?.();
|
|
4374
|
+
});
|
|
4375
|
+
});
|
|
4376
|
+
} catch {}
|
|
4377
|
+
});
|
|
4378
|
+
return lease;
|
|
4379
|
+
}
|
|
4380
|
+
};
|
|
4381
|
+
unprovide = root.provide(AUXILIARY_MODEL_WATCH_COORDINATOR, coordinator);
|
|
4382
|
+
return coordinator;
|
|
4383
|
+
}
|
|
4384
|
+
/**
|
|
4385
|
+
* Acquire the root-owned card service, creating it on first use. One counter
|
|
4386
|
+
* covers participants, the seat wait and the change-stream mount: the last
|
|
4387
|
+
* release removes the wait, then the watch contribution, then the provider
|
|
4388
|
+
* itself synchronously (Cordis deletes the impl before its async disposer
|
|
4389
|
+
* awaits), so a later acquire sees a clean get() miss and can re-establish.
|
|
4390
|
+
*/
|
|
4391
|
+
function acquireAuxiliaryModelCard(root, ctx) {
|
|
4392
|
+
const existing = root.get(AUXILIARY_MODEL_CARD_SERVICE, false);
|
|
4393
|
+
if (existing) return existing;
|
|
4394
|
+
let users = 0;
|
|
4395
|
+
let styleElement;
|
|
4396
|
+
let stopItem;
|
|
4397
|
+
let unprovide;
|
|
4398
|
+
const watchLease = acquireWatchCoordinator(root).mount();
|
|
4399
|
+
const subscriber = createModelSettingsSubscriber(ctx.root, AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY, (ns) => ns === AUXILIARY_MODEL_NS);
|
|
4400
|
+
const render = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuxiliaryModelCard, {
|
|
4401
|
+
client: root,
|
|
4402
|
+
subscribe: subscriber.subscribe
|
|
4403
|
+
});
|
|
4404
|
+
const service = {
|
|
4405
|
+
retain() {
|
|
4406
|
+
users += 1;
|
|
4407
|
+
if (styleElement === void 0 && typeof document !== "undefined") {
|
|
4408
|
+
styleElement = document.createElement("style");
|
|
4409
|
+
styleElement.setAttribute("data-plugin", "dsh-auxiliary-model-shared");
|
|
4410
|
+
styleElement.textContent = auxiliaryModelCss;
|
|
4411
|
+
document.head.appendChild(styleElement);
|
|
4412
|
+
}
|
|
4413
|
+
if (!stopItem) stopItem = registerSessionSettingsItem(ctx.root, {
|
|
4414
|
+
id: AUXILIARY_MODEL_SEAT_ID,
|
|
4415
|
+
order: 55
|
|
4416
|
+
}, render);
|
|
4417
|
+
},
|
|
4418
|
+
release() {
|
|
4419
|
+
if (users <= 0) return;
|
|
4420
|
+
users -= 1;
|
|
4421
|
+
if (users > 0) return;
|
|
4422
|
+
stopItem?.();
|
|
4423
|
+
stopItem = void 0;
|
|
4424
|
+
styleElement?.remove();
|
|
4425
|
+
styleElement = void 0;
|
|
4426
|
+
watchLease.done();
|
|
4427
|
+
try {
|
|
4428
|
+
Promise.resolve(unprovide?.()).catch(() => {});
|
|
4429
|
+
} catch {}
|
|
4430
|
+
unprovide = void 0;
|
|
4431
|
+
}
|
|
4432
|
+
};
|
|
4433
|
+
unprovide = root.provide(AUXILIARY_MODEL_CARD_SERVICE, service);
|
|
4434
|
+
return service;
|
|
4435
|
+
}
|
|
4436
|
+
/**
|
|
4437
|
+
* Show the shared "default auxiliary model" card while the calling plugin is
|
|
4438
|
+
* enabled.
|
|
4439
|
+
*
|
|
4440
|
+
* `ctx` is the plugin's own client context: the card itself is retained on the
|
|
4441
|
+
* shared root context for as long as any participant lives, so an arbitrary
|
|
4442
|
+
* subset of enabled plugins yields the same single card. The mount's slot wait
|
|
4443
|
+
* and the change-stream contribution are owned by the root service — the first
|
|
4444
|
+
* participant's unload cannot take the card down. Returns a disposer; it may
|
|
4445
|
+
* be invoked by both ctx.effect and an explicit caller and releases at most
|
|
4446
|
+
* once, so a double call cannot unbalance the shared refcount.
|
|
4447
|
+
*/
|
|
4448
|
+
function registerAuxiliaryModelSettings(ctx) {
|
|
4449
|
+
const root = ctx.root;
|
|
4450
|
+
const service = acquireAuxiliaryModelCard(root, ctx);
|
|
4451
|
+
try {
|
|
4452
|
+
service.retain();
|
|
4453
|
+
} catch (error) {
|
|
4454
|
+
service.release();
|
|
4455
|
+
throw error;
|
|
4456
|
+
}
|
|
4457
|
+
let disposed = false;
|
|
4458
|
+
return () => {
|
|
4459
|
+
if (disposed) return;
|
|
4460
|
+
disposed = true;
|
|
4461
|
+
service.release();
|
|
4462
|
+
};
|
|
4463
|
+
}
|
|
4464
|
+
//#endregion
|
|
3769
4465
|
//#region src/presence-panel.tsx
|
|
3770
4466
|
/**
|
|
3771
4467
|
* Grey inline-completion for an empty composer, IDE style: the shipped
|
|
@@ -4013,7 +4709,9 @@ function toDraft(values) {
|
|
|
4013
4709
|
autoContinueMode: values.autoContinueMode,
|
|
4014
4710
|
autoContinueMax: String(values.autoContinueMax),
|
|
4015
4711
|
model: values.model === null ? "" : modelRouteKey(values.model.provider, values.model.model),
|
|
4016
|
-
ghostModel: values.ghostModel === null ? "" : modelRouteKey(values.ghostModel.provider, values.ghostModel.model)
|
|
4712
|
+
ghostModel: values.ghostModel === null ? "" : modelRouteKey(values.ghostModel.provider, values.ghostModel.model),
|
|
4713
|
+
judgeModel: values.judgeModel === null ? "" : modelRouteKey(values.judgeModel.provider, values.judgeModel.model),
|
|
4714
|
+
handoffModel: values.handoffModel === null ? "" : modelRouteKey(values.handoffModel.provider, values.handoffModel.model)
|
|
4017
4715
|
};
|
|
4018
4716
|
}
|
|
4019
4717
|
/** Enabled thresholds are whole minutes of at least 1. 0 stays the stored "off" value and is not a valid enabled draft. */
|
|
@@ -4029,7 +4727,7 @@ function autoMaxOf(text) {
|
|
|
4029
4727
|
return Number.isSafeInteger(value) && value >= 1 && value <= 10 ? value : void 0;
|
|
4030
4728
|
}
|
|
4031
4729
|
function sameDraft(left, right) {
|
|
4032
|
-
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;
|
|
4730
|
+
return left.recapEnabled === right.recapEnabled && left.recapMinutes === right.recapMinutes && left.compactEnabled === right.compactEnabled && left.compactMinutes === right.compactMinutes && left.continueText === right.continueText && left.continueMode === right.continueMode && left.autoContinueMode === right.autoContinueMode && left.autoContinueMax === right.autoContinueMax && left.model === right.model && left.ghostModel === right.ghostModel && left.judgeModel === right.judgeModel && left.handoffModel === right.handoffModel;
|
|
4033
4731
|
}
|
|
4034
4732
|
const missingCatalog = async () => {
|
|
4035
4733
|
throw new Error("Model catalog service unavailable");
|
|
@@ -4041,6 +4739,8 @@ function SessionManagerSettings(props) {
|
|
|
4041
4739
|
const catalog = useModelCatalog(props.modelCatalog ?? missingCatalog, { subscribe: props.subscribeCatalog });
|
|
4042
4740
|
const [menuOpen, setMenuOpen] = (0, react.useState)(false);
|
|
4043
4741
|
const [ghostMenuOpen, setGhostMenuOpen] = (0, react.useState)(false);
|
|
4742
|
+
const [judgeMenuOpen, setJudgeMenuOpen] = (0, react.useState)(false);
|
|
4743
|
+
const [handoffMenuOpen, setHandoffMenuOpen] = (0, react.useState)(false);
|
|
4044
4744
|
const [externalChange, setExternalChange] = (0, react.useState)(false);
|
|
4045
4745
|
const dirty = (0, react.useRef)(false);
|
|
4046
4746
|
const baseline = (0, react.useRef)();
|
|
@@ -4083,9 +4783,25 @@ function SessionManagerSettings(props) {
|
|
|
4083
4783
|
});
|
|
4084
4784
|
const selectedRoute = parseModelRouteKey(draft.model);
|
|
4085
4785
|
const ghostRoute = parseModelRouteKey(draft.ghostModel);
|
|
4086
|
-
const
|
|
4087
|
-
const
|
|
4088
|
-
const
|
|
4786
|
+
const judgeRoute = parseModelRouteKey(draft.judgeModel);
|
|
4787
|
+
const handoffRoute = parseModelRouteKey(draft.handoffModel);
|
|
4788
|
+
const selectableChoices = parseModelMenuChoices(catalog.catalog);
|
|
4789
|
+
for (const bound of [
|
|
4790
|
+
selectedRoute,
|
|
4791
|
+
ghostRoute,
|
|
4792
|
+
judgeRoute,
|
|
4793
|
+
handoffRoute
|
|
4794
|
+
]) if (bound && !selectableChoices.some((choice) => choice.provider === bound.provider && choice.model === bound.model)) selectableChoices.push({
|
|
4795
|
+
provider: bound.provider,
|
|
4796
|
+
model: bound.model,
|
|
4797
|
+
label: `${bound.provider} / ${bound.model}`,
|
|
4798
|
+
efforts: []
|
|
4799
|
+
});
|
|
4800
|
+
const routeLabel = (key) => selectableChoices.find((choice) => modelRouteKey(choice.provider, choice.model) === key)?.label ?? copy("modelFollowOption");
|
|
4801
|
+
const selectedLabel = routeLabel(draft.model);
|
|
4802
|
+
const ghostLabel = routeLabel(draft.ghostModel);
|
|
4803
|
+
const judgeLabel = routeLabel(draft.judgeModel);
|
|
4804
|
+
const handoffLabel = routeLabel(draft.handoffModel);
|
|
4089
4805
|
const recapMinutes = draft.recapEnabled ? minutesOf(draft.recapMinutes) : 0;
|
|
4090
4806
|
const compactMinutes = draft.compactEnabled ? minutesOf(draft.compactMinutes) : 0;
|
|
4091
4807
|
const autoContinueMax = autoMaxOf(draft.autoContinueMax);
|
|
@@ -4097,8 +4813,14 @@ function SessionManagerSettings(props) {
|
|
|
4097
4813
|
const autoPanelId = `${tabsId}-auto-panel`;
|
|
4098
4814
|
const save = () => {
|
|
4099
4815
|
if (invalid) return;
|
|
4100
|
-
const
|
|
4101
|
-
|
|
4816
|
+
const routePatch = (key) => {
|
|
4817
|
+
if (draft[key] === baseline.current?.[key]) return {};
|
|
4818
|
+
const parsed = parseModelRouteKey(draft[key]);
|
|
4819
|
+
return { [key]: parsed === void 0 ? null : {
|
|
4820
|
+
provider: parsed.provider,
|
|
4821
|
+
model: parsed.model
|
|
4822
|
+
} };
|
|
4823
|
+
};
|
|
4102
4824
|
setExternalChange(false);
|
|
4103
4825
|
props.model.save({
|
|
4104
4826
|
recapMinutes,
|
|
@@ -4107,14 +4829,10 @@ function SessionManagerSettings(props) {
|
|
|
4107
4829
|
continueMode: draft.continueMode,
|
|
4108
4830
|
autoContinueMode: draft.autoContinueMode,
|
|
4109
4831
|
autoContinueMax,
|
|
4110
|
-
...
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
...draft.ghostModel === baseline.current?.ghostModel ? {} : { ghostModel: ghost === void 0 ? null : {
|
|
4115
|
-
provider: ghost.provider,
|
|
4116
|
-
model: ghost.model
|
|
4117
|
-
} }
|
|
4832
|
+
...routePatch("model"),
|
|
4833
|
+
...routePatch("ghostModel"),
|
|
4834
|
+
...routePatch("judgeModel"),
|
|
4835
|
+
...routePatch("handoffModel")
|
|
4118
4836
|
});
|
|
4119
4837
|
};
|
|
4120
4838
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -4251,72 +4969,181 @@ function SessionManagerSettings(props) {
|
|
|
4251
4969
|
})
|
|
4252
4970
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4253
4971
|
className: "dsm-feature-options",
|
|
4254
|
-
children: [
|
|
4255
|
-
|
|
4972
|
+
children: [
|
|
4973
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4974
|
+
className: "dsh-ui-field",
|
|
4975
|
+
children: [
|
|
4976
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4977
|
+
className: "dsh-ui-label",
|
|
4978
|
+
children: copy("autoContinueDefaultLabel")
|
|
4979
|
+
}),
|
|
4980
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
4981
|
+
className: "dsh-ui-select",
|
|
4982
|
+
value: draft.autoContinueMode,
|
|
4983
|
+
onChange: (event) => edit({
|
|
4984
|
+
...draft,
|
|
4985
|
+
autoContinueMode: event.target.value
|
|
4986
|
+
}),
|
|
4987
|
+
children: [
|
|
4988
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4989
|
+
value: "off",
|
|
4990
|
+
children: copy("autoContinueOff")
|
|
4991
|
+
}),
|
|
4992
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4993
|
+
value: "cautious",
|
|
4994
|
+
children: copy("autoContinueCautious")
|
|
4995
|
+
}),
|
|
4996
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4997
|
+
value: "bold",
|
|
4998
|
+
children: copy("autoContinueBold")
|
|
4999
|
+
})
|
|
5000
|
+
]
|
|
5001
|
+
}),
|
|
5002
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5003
|
+
className: "dsh-ui-hint",
|
|
5004
|
+
children: copy("autoContinueDefaultHint")
|
|
5005
|
+
})
|
|
5006
|
+
]
|
|
5007
|
+
}),
|
|
5008
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
5009
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
5010
|
+
className: "dsh-ui-field",
|
|
5011
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5012
|
+
className: "dsh-ui-label",
|
|
5013
|
+
children: copy("autoContinueMaxLabel")
|
|
5014
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5015
|
+
className: "dsm-minutes-control",
|
|
5016
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
5017
|
+
className: "dsm-minutes",
|
|
5018
|
+
value: draft.autoContinueMax,
|
|
5019
|
+
inputMode: "numeric",
|
|
5020
|
+
"aria-invalid": autoMaxInvalid || void 0,
|
|
5021
|
+
"aria-describedby": autoMaxInvalid ? `${tabsId}-auto-max-error` : void 0,
|
|
5022
|
+
onChange: (event) => edit({
|
|
5023
|
+
...draft,
|
|
5024
|
+
autoContinueMax: event.target.value
|
|
5025
|
+
})
|
|
5026
|
+
})
|
|
5027
|
+
})]
|
|
5028
|
+
}),
|
|
5029
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5030
|
+
className: "dsh-ui-hint",
|
|
5031
|
+
children: copy("autoContinueMaxHint")
|
|
5032
|
+
}),
|
|
5033
|
+
autoMaxInvalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5034
|
+
id: `${tabsId}-auto-max-error`,
|
|
5035
|
+
className: "dsh-ui-error",
|
|
5036
|
+
role: "alert",
|
|
5037
|
+
children: copy("autoContinueMaxInvalid")
|
|
5038
|
+
}) : null
|
|
5039
|
+
] }),
|
|
5040
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5041
|
+
className: "dsh-ui-field dsm-model-field",
|
|
5042
|
+
children: [
|
|
5043
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5044
|
+
className: "dsh-ui-label",
|
|
5045
|
+
children: copy("judgeModelLabel")
|
|
5046
|
+
}),
|
|
5047
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
5048
|
+
locale: props.locale,
|
|
5049
|
+
open: judgeMenuOpen,
|
|
5050
|
+
onOpenChange: setJudgeMenuOpen,
|
|
5051
|
+
listClassName: FREE_CHAT_ROOT,
|
|
5052
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5053
|
+
type: "button",
|
|
5054
|
+
className: "dsh-model-menu-trigger",
|
|
5055
|
+
"aria-label": `${copy("judgeModelLabel")}: ${judgeLabel}`,
|
|
5056
|
+
"aria-haspopup": "menu",
|
|
5057
|
+
"aria-expanded": judgeMenuOpen,
|
|
5058
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5059
|
+
className: "dsh-model-menu-triggerLabel",
|
|
5060
|
+
children: judgeLabel
|
|
5061
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${judgeMenuOpen ? " dsh-model-menu-chevronOpen" : ""}` })]
|
|
5062
|
+
}),
|
|
5063
|
+
choices: selectableChoices,
|
|
5064
|
+
selected: judgeRoute,
|
|
5065
|
+
onPick: (route) => edit({
|
|
5066
|
+
...draft,
|
|
5067
|
+
judgeModel: modelRouteKey(route.provider, route.model)
|
|
5068
|
+
}),
|
|
5069
|
+
leading: [{
|
|
5070
|
+
id: "follow",
|
|
5071
|
+
label: copy("modelFollowOption"),
|
|
5072
|
+
selected: !judgeRoute
|
|
5073
|
+
}],
|
|
5074
|
+
onPickLeading: () => edit({
|
|
5075
|
+
...draft,
|
|
5076
|
+
judgeModel: ""
|
|
5077
|
+
}),
|
|
5078
|
+
modelValue: judgeLabel
|
|
5079
|
+
}),
|
|
5080
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5081
|
+
className: "dsh-ui-hint",
|
|
5082
|
+
children: copy("judgeModelHint")
|
|
5083
|
+
})
|
|
5084
|
+
]
|
|
5085
|
+
})
|
|
5086
|
+
]
|
|
5087
|
+
})]
|
|
5088
|
+
}),
|
|
5089
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5090
|
+
className: "dsm-setting",
|
|
5091
|
+
"aria-labelledby": `${tabsId}-handoff-heading`,
|
|
5092
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
|
|
5093
|
+
className: "dsm-setting-header",
|
|
5094
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
5095
|
+
id: `${tabsId}-handoff-heading`,
|
|
5096
|
+
className: "dsh-ui-title",
|
|
5097
|
+
children: copy("handoffGroup")
|
|
5098
|
+
})
|
|
5099
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5100
|
+
className: "dsm-feature-options",
|
|
5101
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5102
|
+
className: "dsh-ui-field dsm-model-field",
|
|
4256
5103
|
children: [
|
|
4257
5104
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4258
5105
|
className: "dsh-ui-label",
|
|
4259
|
-
children: copy("
|
|
5106
|
+
children: copy("handoffModelLabel")
|
|
4260
5107
|
}),
|
|
4261
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
5108
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
|
|
5109
|
+
locale: props.locale,
|
|
5110
|
+
open: handoffMenuOpen,
|
|
5111
|
+
onOpenChange: setHandoffMenuOpen,
|
|
5112
|
+
listClassName: FREE_CHAT_ROOT,
|
|
5113
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5114
|
+
type: "button",
|
|
5115
|
+
className: "dsh-model-menu-trigger",
|
|
5116
|
+
"aria-label": `${copy("handoffModelLabel")}: ${handoffLabel}`,
|
|
5117
|
+
"aria-haspopup": "menu",
|
|
5118
|
+
"aria-expanded": handoffMenuOpen,
|
|
5119
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5120
|
+
className: "dsh-model-menu-triggerLabel",
|
|
5121
|
+
children: handoffLabel
|
|
5122
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${handoffMenuOpen ? " dsh-model-menu-chevronOpen" : ""}` })]
|
|
5123
|
+
}),
|
|
5124
|
+
choices: selectableChoices,
|
|
5125
|
+
selected: handoffRoute,
|
|
5126
|
+
onPick: (route) => edit({
|
|
4265
5127
|
...draft,
|
|
4266
|
-
|
|
5128
|
+
handoffModel: modelRouteKey(route.provider, route.model)
|
|
4267
5129
|
}),
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
value: "bold",
|
|
4279
|
-
children: copy("autoContinueBold")
|
|
4280
|
-
})
|
|
4281
|
-
]
|
|
5130
|
+
leading: [{
|
|
5131
|
+
id: "follow",
|
|
5132
|
+
label: copy("modelFollowOption"),
|
|
5133
|
+
selected: !handoffRoute
|
|
5134
|
+
}],
|
|
5135
|
+
onPickLeading: () => edit({
|
|
5136
|
+
...draft,
|
|
5137
|
+
handoffModel: ""
|
|
5138
|
+
}),
|
|
5139
|
+
modelValue: handoffLabel
|
|
4282
5140
|
}),
|
|
4283
5141
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4284
5142
|
className: "dsh-ui-hint",
|
|
4285
|
-
children: copy("
|
|
5143
|
+
children: copy("handoffModelHint")
|
|
4286
5144
|
})
|
|
4287
5145
|
]
|
|
4288
|
-
})
|
|
4289
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4290
|
-
className: "dsh-ui-field",
|
|
4291
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4292
|
-
className: "dsh-ui-label",
|
|
4293
|
-
children: copy("autoContinueMaxLabel")
|
|
4294
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4295
|
-
className: "dsm-minutes-control",
|
|
4296
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
4297
|
-
className: "dsm-minutes",
|
|
4298
|
-
value: draft.autoContinueMax,
|
|
4299
|
-
inputMode: "numeric",
|
|
4300
|
-
"aria-invalid": autoMaxInvalid || void 0,
|
|
4301
|
-
"aria-describedby": autoMaxInvalid ? `${tabsId}-auto-max-error` : void 0,
|
|
4302
|
-
onChange: (event) => edit({
|
|
4303
|
-
...draft,
|
|
4304
|
-
autoContinueMax: event.target.value
|
|
4305
|
-
})
|
|
4306
|
-
})
|
|
4307
|
-
})]
|
|
4308
|
-
}),
|
|
4309
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4310
|
-
className: "dsh-ui-hint",
|
|
4311
|
-
children: copy("autoContinueMaxHint")
|
|
4312
|
-
}),
|
|
4313
|
-
autoMaxInvalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4314
|
-
id: `${tabsId}-auto-max-error`,
|
|
4315
|
-
className: "dsh-ui-error",
|
|
4316
|
-
role: "alert",
|
|
4317
|
-
children: copy("autoContinueMaxInvalid")
|
|
4318
|
-
}) : null
|
|
4319
|
-
] })]
|
|
5146
|
+
})
|
|
4320
5147
|
})]
|
|
4321
5148
|
}),
|
|
4322
5149
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
@@ -5454,15 +6281,16 @@ function apply(ctx) {
|
|
|
5454
6281
|
locale: useLiveLocale(client.locale),
|
|
5455
6282
|
model: welcomeBack
|
|
5456
6283
|
}))));
|
|
5457
|
-
stops.push(
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
6284
|
+
stops.push(registerSessionSettingsItem(ctx, {
|
|
6285
|
+
id: SESSION_SETTINGS_ITEM_ID,
|
|
6286
|
+
order: 65
|
|
6287
|
+
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionManagerSettingsItem, {
|
|
5461
6288
|
model: presenceConfig,
|
|
5462
6289
|
locale: useLiveLocale(client.locale),
|
|
5463
6290
|
modelCatalog,
|
|
5464
|
-
subscribeCatalog
|
|
5465
|
-
|
|
6291
|
+
subscribeCatalog,
|
|
6292
|
+
subscribeSettings
|
|
6293
|
+
})));
|
|
5466
6294
|
stops.push(client.slots.inject("shell.overlay", () => client.slots.register({
|
|
5467
6295
|
name: "shell.overlay",
|
|
5468
6296
|
id: DELETE_DIALOG_ID,
|
|
@@ -5485,6 +6313,7 @@ function apply(ctx) {
|
|
|
5485
6313
|
})
|
|
5486
6314
|
] });
|
|
5487
6315
|
})));
|
|
6316
|
+
stops.push(registerAuxiliaryModelSettings(ctx));
|
|
5488
6317
|
const findCurrent = () => Object.values(client.sessions?.list.getSnapshot().byId ?? {}).find((row) => (row?.retainedBy?.mainView ?? 0) > 0)?.id ?? "";
|
|
5489
6318
|
let current = findCurrent();
|
|
5490
6319
|
const onReturn = () => {
|
|
@@ -5579,10 +6408,49 @@ function DeleteMenuItem(props) {
|
|
|
5579
6408
|
children: uiText(locale, "deleteChat")
|
|
5580
6409
|
});
|
|
5581
6410
|
}
|
|
6411
|
+
/**
|
|
6412
|
+
* Session Management item: a named native details group. The body mounts on
|
|
6413
|
+
* the first expand only — the settings page must not fire every plugin's
|
|
6414
|
+
* config and model reads — and stays mounted afterwards so collapsing never
|
|
6415
|
+
* drops an unsaved draft or the settings-change subscription.
|
|
6416
|
+
*/
|
|
6417
|
+
function SessionManagerSettingsItem(props) {
|
|
6418
|
+
const [visited, setVisited] = (0, react.useState)(false);
|
|
6419
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
6420
|
+
className: `${FREE_CHAT_ROOT} dsm-item`,
|
|
6421
|
+
"data-testid": "session-manager-settings-item",
|
|
6422
|
+
onToggle: (event) => {
|
|
6423
|
+
if (event.currentTarget.open) setVisited(true);
|
|
6424
|
+
},
|
|
6425
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
|
|
6426
|
+
className: "dsm-item-summary",
|
|
6427
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6428
|
+
className: "dsm-item-title",
|
|
6429
|
+
children: uiText(props.locale, "settingsGroupTitle")
|
|
6430
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6431
|
+
className: "dsm-item-desc",
|
|
6432
|
+
children: uiText(props.locale, "settingsGroupHint")
|
|
6433
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
|
|
6434
|
+
className: "dsm-item-chevron",
|
|
6435
|
+
"aria-hidden": "true"
|
|
6436
|
+
})]
|
|
6437
|
+
}), visited ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6438
|
+
className: "dsm-item-body",
|
|
6439
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionManagerSettings, {
|
|
6440
|
+
model: props.model,
|
|
6441
|
+
locale: props.locale,
|
|
6442
|
+
modelCatalog: props.modelCatalog,
|
|
6443
|
+
subscribeCatalog: props.subscribeCatalog,
|
|
6444
|
+
subscribeSettings: props.subscribeSettings
|
|
6445
|
+
})
|
|
6446
|
+
}) : null]
|
|
6447
|
+
});
|
|
6448
|
+
}
|
|
5582
6449
|
function useLiveLocale(locale) {
|
|
5583
6450
|
return copyLocale((0, react.useSyncExternalStore)((listener) => locale?.subscribe(listener) ?? (() => {}), () => locale?.getSnapshot().active ?? "en", () => locale?.getSnapshot().active ?? "en"));
|
|
5584
6451
|
}
|
|
5585
6452
|
//#endregion
|
|
6453
|
+
exports.SessionManagerSettingsItem = SessionManagerSettingsItem;
|
|
5586
6454
|
exports.apply = apply;
|
|
5587
6455
|
exports.inject = inject;
|
|
5588
6456
|
exports.name = name;
|