@deepseek-ai/dsh-client-ui-model-selection 0.1.7-alpha.2 → 0.1.7-rc.2
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.i18n.yaml +45 -5
- package/README.md +14 -9
- package/README.zh.md +14 -9
- package/lib/client.js +97 -80
- package/lib/types/client/catalog.d.ts +8 -1
- package/lib/types/client/directory.d.ts +7 -10
- package/lib/types/client/locales.d.ts +2 -2
- package/lib/types/client/service.d.ts +1 -6
- package/package.json +16 -16
package/README.i18n.yaml
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
|
-
# Bilingual-pair consistency record (docs/i18n/README.md):
|
|
2
|
-
#
|
|
3
|
-
#
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/ui-model-selection/README.md
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/:
|
|
6
|
+
en: f39813648aeef5a0
|
|
7
|
+
zh: eea25094e8bfc031
|
|
8
|
+
/deepseek-ai-dsh-client-ui-model-selection:
|
|
9
|
+
en: 5e007d579a0e22f0
|
|
10
|
+
zh: edfff0d021712077
|
|
11
|
+
/deepseek-ai-dsh-client-ui-model-selection/summary:
|
|
12
|
+
en: 2fb361ef704528fb
|
|
13
|
+
zh: 2537d2afb4d2cb45
|
|
14
|
+
/deepseek-ai-dsh-client-ui-model-selection/table-of-contents:
|
|
15
|
+
en: d152484eb41ac6b4
|
|
16
|
+
zh: 09388d293f9be9cb
|
|
17
|
+
/deepseek-ai-dsh-client-ui-model-selection/use-this-package:
|
|
18
|
+
en: e5a1df3a4dfe5041
|
|
19
|
+
zh: 6269e73026b32a33
|
|
20
|
+
/deepseek-ai-dsh-client-ui-model-selection/use-this-package/model-and-effort:
|
|
21
|
+
en: e38d0195677a7b69
|
|
22
|
+
zh: 4eea8a9b1664afd9
|
|
23
|
+
/deepseek-ai-dsh-client-ui-model-selection/use-this-package/unroutable-sessions:
|
|
24
|
+
en: 7f20428c747ff052
|
|
25
|
+
zh: 975e6de728e02fa0
|
|
26
|
+
/deepseek-ai-dsh-client-ui-model-selection/use-this-package/selection-failures:
|
|
27
|
+
en: 261bbf42296243af
|
|
28
|
+
zh: 72779fb7b8fbc473
|
|
29
|
+
/deepseek-ai-dsh-client-ui-model-selection/understand-the-implementation:
|
|
30
|
+
en: 493088e8de6be91c
|
|
31
|
+
zh: 9ecee4605b4dbad4
|
|
32
|
+
/deepseek-ai-dsh-client-ui-model-selection/further-exploration:
|
|
33
|
+
en: 14acd4692e923480
|
|
34
|
+
zh: ff89aed852aa0da8
|
|
35
|
+
/deepseek-ai-dsh-client-ui-model-selection/model-experience:
|
|
36
|
+
en: 782f79ed910f426a
|
|
37
|
+
zh: 54a2e3e32a747544
|
|
38
|
+
/deepseek-ai-dsh-client-ui-model-selection/model-experience/kv-cache-effect:
|
|
39
|
+
en: 68e54c00412a40d1
|
|
40
|
+
zh: 0ac96f57fd194d96
|
|
41
|
+
/deepseek-ai-dsh-client-ui-model-selection/known-limitations-and-deferred-work:
|
|
42
|
+
en: 75b37954ceacd92a
|
|
43
|
+
zh: a49d0361486e35c4
|
|
44
|
+
/deepseek-ai-dsh-client-ui-model-selection/known-limitations-and-deferred-work/dev-note:
|
|
45
|
+
en: 83bfc3dae3365b49
|
|
46
|
+
zh: f7dc4d0654ec83e1
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
The Web GUI lets users switch the model and reasoning effort for an existing session through either the `/model` popup or the composer's model control. Both surfaces present the same provider-grouped choices, and the selected model determines the available effort names and default. A complete selection applies to the next request; a running step keeps the model and effort it started with. If
|
|
12
|
+
The Web GUI lets users switch the model and reasoning effort for an existing session through either the `/model` popup or the composer's model control. Both surfaces present the same provider-grouped choices, and the selected model determines the available effort names and default. A complete selection applies to the next request; a running step keeps the model and effort it started with. If the selected model is unavailable, the composer stays disabled until the user selects an available model or that exact model becomes available again.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -25,19 +25,23 @@ The Web GUI lets users switch the model and reasoning effort for an existing ses
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## Use this package
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
DeepSeek account and API-key routes appear as separate provider groups, each exposing the same configured model catalog.
|
|
29
|
+
|
|
30
|
+
The unselected model label uses the same regular weight as an available model name and retains the saved reasoning effort caption for existing and new sessions; effort editing requires an available model. Clicking the unselected trigger opens the model list directly; Escape closes it.
|
|
31
|
+
|
|
32
|
+
Mount this plugin alongside `ui-conversation` and the commands package; the composer then shows the model seat next to the pending indicator, and `/model` opens the same directory as a popup. While the seat's menu is open, `↑`/`↓` move focus across the rows of the shown pane, Tab settles the focused row, and Escape and `Shift+Tab` leave a drilled pane first and otherwise close back to the trigger. Drilling lands on the row of the value in use, and going back lands on the cell that opened the pane left. The composer shows the catalog name while the selected model is available, and its saved `provider/model` ID when the model or provider is removed, including account sign-out. The stored provider, model, and reasoning effort remain unchanged.
|
|
33
|
+
|
|
34
|
+
Mouse selection uses native browser clicks, including their cancellation behavior; a press alone never selects. Opening the menu focuses its trigger, and clicking the trigger again closes the menu and returns focus there. While a selection from either entry is pending, focus stays on the trigger, the trigger shows a spinner in place of its chevron, and each row whose value the selection carries shows one in place of its check; a rejected selection leaves the menu open, and Tab returns to the current row.
|
|
29
35
|
|
|
30
36
|
### Model and effort
|
|
31
37
|
|
|
32
|
-
Models stay grouped by provider. The composer menu shows model and effort names only. The `/model` popup shows provider names and catalog descriptions; it localizes the two built-in DeepSeek descriptions and leaves external provider descriptions verbatim. The popup applies the selected model's default effort; the composer can then choose any advertised effort. An adapter without reasoning metadata leaves the Effort row absent; there is no arbitrary effort input.
|
|
38
|
+
Models stay grouped by provider. The composer menu shows model and effort names only, with DeepSeek Account first and DeepSeek second; third-party providers retain their catalog order. Navigation chevrons use `--dsw-alias-menu-icon`. The `/model` popup shows provider names and catalog descriptions; it localizes the two built-in DeepSeek descriptions and leaves external provider descriptions verbatim. The popup applies the selected model's default effort; the composer can then choose any advertised effort. An adapter without reasoning metadata leaves the Effort row absent; there is no arbitrary effort input.
|
|
33
39
|
|
|
34
40
|
The composer replaces the model and effort text with the Models icon when the expanded controls cannot share one line, and restores the text when space permits. The full selection remains available in the trigger's accessible name, tooltip, and menu.
|
|
35
41
|
|
|
36
42
|
### Unroutable sessions
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
Only the current Client binding's directory can publish its composer block. Cleanup from an older binding preserves a replacement directory's block; without a replacement directory, cleanup removes the obsolete block.
|
|
44
|
+
Catalog availability does not block sending with a saved selection; request execution reports missing credentials or unavailable models. Refreshes and refresh failures retain the last displayed selection and groups. A Host reset clears that display. Sign-out hides the account provider from the picker while preserving the saved provider/model ID and reasoning effort. Signing in restores the catalog name when that model is available again. Existing session logs remain unchanged.
|
|
41
45
|
|
|
42
46
|
### Selection failures
|
|
43
47
|
|
|
@@ -48,10 +52,12 @@ When another writer owns the Session, model-selection failures tell the user to
|
|
|
48
52
|
<a id="understand-the-implementation"></a>
|
|
49
53
|
## Understand the implementation
|
|
50
54
|
|
|
55
|
+
Menus use the shared `MenuSurface` material, including the macOS backing for background blur; custom content follows the [menu rules](../../../docs/web-styling.md#component-rules).
|
|
56
|
+
|
|
51
57
|
<details>
|
|
52
58
|
<summary>Implementation internals — click to expand</summary>
|
|
53
59
|
|
|
54
|
-
Two entries over ONE per-session directory owned by `ModelDirectoryResolver` (`ctx.modelDirectories`): the `/model` popupSelect contribution (registered through `ctx.commandUi`) and the composer's named `conversation.input.model` seat both load the session's
|
|
60
|
+
Two entries over ONE per-session directory owned by `ModelDirectoryResolver` (`ctx.modelDirectories`): the `/model` popupSelect contribution (registered through `ctx.commandUi`) and the composer's named `conversation.input.model` seat both load the session's available directory through `session.models` and submit through `session.selectModel` via the same `ModelDirectory` instance, so a switch made in either entry is what the other shows next. Directory loads and selections share a generation counter so an older response never overwrites a newer one. The directory publishes the latest submitted selection as `pending` until it settles or a connection reset invalidates it; a connection reset drops every resident projection and repulls the Host-restored selection before display. Directories are per-session, resolved lazily, and disposed with the session scope; addressed subagent sessions expose neither entry. Every resident directory refetches directly on forwarded `llm/adapters-updated`, `settings/document-updated`, and credential update events.
|
|
55
61
|
|
|
56
62
|
</details>
|
|
57
63
|
|
|
@@ -63,7 +69,7 @@ Two entries over ONE per-session directory owned by `ModelDirectoryResolver` (`c
|
|
|
63
69
|
Read these pages when the model surface is not enough. They move from the browser surfaces to the command popup shell and the selection contract.
|
|
64
70
|
|
|
65
71
|
- [ui-commands](../ui-commands/README.md) — the popupSelect shell the `/model` contribution registers into.
|
|
66
|
-
- [ui-conversation](../ui-conversation/README.md) — declares the composer's `conversation.input.model` seat
|
|
72
|
+
- [ui-conversation](../ui-conversation/README.md) — declares the composer's `conversation.input.model` seat.
|
|
67
73
|
- [dsh-agent-default-model](../../core/agent-default-model/README.md) — the default-model service for sessions that never choose.
|
|
68
74
|
- [Client package map](../README.md) — adjacent browser UI packages.
|
|
69
75
|
|
|
@@ -82,7 +88,6 @@ Switching the route can reduce or invalidate provider-side cache reuse for subse
|
|
|
82
88
|
|
|
83
89
|
<a id="known-limitations-and-deferred-work"></a>
|
|
84
90
|
|
|
85
|
-
|
|
86
91
|
These limits define the current model surface. They are current package constraints, not a general model-router comparison or a task backlog.
|
|
87
92
|
|
|
88
93
|
- **No create-time or addressed-subagent selection** — both entries require an existing ordinary session's Agent; there is no draft-phase model choice to fold into session creation, and subagent continuation deliberately exposes no independent model-selection contract.
|
package/README.zh.md
CHANGED
|
@@ -9,7 +9,7 @@ kind: "package-reference"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
Web GUI 允许用户通过 `/model` 弹窗或 composer 模型控件切换既有会话使用的模型与推理(reasoning
|
|
12
|
+
Web GUI 允许用户通过 `/model` 弹窗或 composer 模型控件切换既有会话使用的模型与推理(reasoning)强度。两个界面呈现同一组按提供方分组的选择;所选模型决定可用的推理强度名称与默认值。完整选择从下一次请求开始生效;运行中的步骤保留其启动时的模型与推理强度。所选模型不可用时,composer 保持停用,直到用户选择可用模型或同一模型恢复可用。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -25,19 +25,23 @@ Web GUI 允许用户通过 `/model` 弹窗或 composer 模型控件切换既有
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## 使用本包
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
DeepSeek 账号和 API Key 路由显示为独立提供方分组,各自展示相同的已配置模型目录。
|
|
29
|
+
|
|
30
|
+
未选模型文案与可用模型名使用相同的常规字重,并在既有会话与新会话中保留已保存的推理强度显示;调整推理强度需要先选择可用模型。点击未选模型控件直接打开模型列表,Escape 关闭列表。
|
|
31
|
+
|
|
32
|
+
与 `ui-conversation` 及命令包一起挂载本插件;composer 随即在待处理指示器旁显示模型位,`/model` 则以弹窗打开同一份目录。模型位菜单打开期间,`↑`/`↓` 在所显示面板的行间移动焦点,`Tab` 选定聚焦行,Escape 与 `Shift+Tab` 先退出已下钻的面板,否则关闭并回到触发器。下钻落在正在使用的那一行,返回则落在打开该面板的格子上。所选模型可用时,composer 显示目录中的模型名称;模型或提供方被删除时(包括账号退登),改为显示已保存的 `provider/model` ID。保存的提供方、模型和推理强度均保持不变。
|
|
33
|
+
|
|
34
|
+
鼠标选择沿用浏览器原生点击及其取消行为;仅按下按钮不会选定。打开菜单时聚焦触发按钮,再次点击触发按钮会关闭菜单并把焦点还给它。等待任一入口发起的选择结果时,焦点停在触发按钮,触发按钮以加载图标代替下拉箭头,该选择包含的值所在行以加载图标代替勾选标记;选择被拒绝后菜单保持打开,Tab 可回到当前选中的行。
|
|
29
35
|
|
|
30
36
|
### 模型与推理强度
|
|
31
37
|
|
|
32
|
-
模型按提供方分组。composer
|
|
38
|
+
模型按提供方分组。composer 菜单只显示模型与推理强度名称,DeepSeek 账号排第一,DeepSeek 排第二,第三方提供方保持目录原有顺序。导航箭头使用 `--dsw-alias-menu-icon` 文本色。`/model` 弹窗显示提供方名称与目录说明;其中两个内置 DeepSeek 模型的说明使用当前语言,外部提供方说明保持原文。弹窗应用所选模型的默认推理强度;composer 随后可以选择任一已公布的推理强度。适配器没有推理元数据时不显示 Effort 行;不存在任意推理强度输入。
|
|
33
39
|
|
|
34
40
|
展开的控件无法排在同一行时,composer 将模型与推理强度文字替换为模型图标;空间足够后恢复文字。触发器的无障碍名称、提示和菜单仍提供完整选择。
|
|
35
41
|
|
|
36
42
|
### 不可路由的会话
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
只有当前 Client binding 的目录可以发布对应的 composer 阻塞块。旧 binding 的清理保留后继目录的阻塞块;若尚无后继目录,则移除已失效的阻塞块。
|
|
44
|
+
目录可用性不会阻止使用已保存选择发送消息;请求执行负责报告凭据缺失或模型不可用。刷新及刷新失败期间保留上次显示的选择和分组。Host 重置时清空这些显示。退登后选择器隐藏账号提供方,同时保留已保存的提供方/模型 ID 和推理强度。再次登录且该模型可用时恢复目录名称。既有会话日志保持不变。
|
|
41
45
|
|
|
42
46
|
### 选择失败
|
|
43
47
|
|
|
@@ -48,10 +52,12 @@ Web GUI 允许用户通过 `/model` 弹窗或 composer 模型控件切换既有
|
|
|
48
52
|
<a id="understand-the-implementation"></a>
|
|
49
53
|
## 理解实现
|
|
50
54
|
|
|
55
|
+
菜单采用共享 `MenuSurface` 材质,包括用于背景模糊的 macOS 底层;自定义内容遵循[菜单规则](../../../docs/web-styling.zh.md#component-rules)。
|
|
56
|
+
|
|
51
57
|
<details>
|
|
52
58
|
<summary>实现细节——点击展开</summary>
|
|
53
59
|
|
|
54
|
-
两个入口共用一份由 `ModelDirectoryResolver`(`ctx.modelDirectories`)持有的会话级目录:`/model` popupSelect 贡献项(经 `ctx.commandUi` 注册)与 composer 的具名 `conversation.input.model` 位都经 `session.models`
|
|
60
|
+
两个入口共用一份由 `ModelDirectoryResolver`(`ctx.modelDirectories`)持有的会话级目录:`/model` popupSelect 贡献项(经 `ctx.commandUi` 注册)与 composer 的具名 `conversation.input.model` 位都经 `session.models` 加载会话的可用目录、经 `session.selectModel` 通过同一个 `ModelDirectory` 实例提交,因此任一入口所做的切换正是另一个入口接下来显示的。目录加载与选择共享一个代次计数器,旧响应不会覆盖新结果。目录把最近一次提交的选择发布为 `pending`,直到它完成或被连接重置作废;连接重置丢弃所有常驻投影,并在显示前重新拉取 Host 恢复的选择。目录按会话惰性解析,随会话作用域一并 dispose(资源释放);已寻址 subagent 会话不公开任一入口。每份常驻目录都会直接在转发的 `llm/adapters-updated`、`settings/document-updated` 与凭据更新事件上重拉。
|
|
55
61
|
|
|
56
62
|
</details>
|
|
57
63
|
|
|
@@ -63,7 +69,7 @@ Web GUI 允许用户通过 `/model` 弹窗或 composer 模型控件切换既有
|
|
|
63
69
|
当仅了解模型界面还不够时,请阅读以下页面。这些页面从浏览器界面逐步深入到命令弹窗外壳与选择约定。
|
|
64
70
|
|
|
65
71
|
- [ui-commands](../ui-commands/README.zh.md)——`/model` 贡献项注册进的 popupSelect 外壳。
|
|
66
|
-
- [ui-conversation](../ui-conversation/README.zh.md)——声明 composer 的 `conversation.input.model`
|
|
72
|
+
- [ui-conversation](../ui-conversation/README.zh.md)——声明 composer 的 `conversation.input.model` 位。
|
|
67
73
|
- [dsh-agent-default-model](../../core/agent-default-model/README.zh.md)——为从未选择的会话提供默认模型的默认模型服务。
|
|
68
74
|
- [客户端包映射](../README.zh.md)——相邻的浏览器 UI 包。
|
|
69
75
|
|
|
@@ -82,7 +88,6 @@ Web GUI 允许用户通过 `/model` 弹窗或 composer 模型控件切换既有
|
|
|
82
88
|
|
|
83
89
|
<a id="known-limitations-and-deferred-work"></a>
|
|
84
90
|
|
|
85
|
-
|
|
86
91
|
这些限制界定了当前模型选择界面。它们是当前包约束,不是通用模型路由器对比或任务积压。
|
|
87
92
|
|
|
88
93
|
- **无创建期或已寻址 subagent 选择**——两个入口都要求既有普通会话的 agent(智能体);没有可纳入会话创建的草稿阶段模型选择,subagent 继续执行也有意不公开独立的模型选择约定。
|
package/lib/client.js
CHANGED
|
@@ -84,6 +84,15 @@ window.__ModuleLoader__.load({
|
|
|
84
84
|
status: "idle",
|
|
85
85
|
error: null
|
|
86
86
|
});
|
|
87
|
+
reasoning = /* @__PURE__ */ new Map();
|
|
88
|
+
/**
|
|
89
|
+
* Read the last advertised reasoning metadata, including unavailable models.
|
|
90
|
+
* @param selection - provider and model whose effort is displayed.
|
|
91
|
+
* @returns reasoning metadata observed during this Host generation.
|
|
92
|
+
*/
|
|
93
|
+
reasoningFor(selection) {
|
|
94
|
+
return this.reasoning.get(JSON.stringify([selection.provider, selection.model]));
|
|
95
|
+
}
|
|
87
96
|
generation = 0;
|
|
88
97
|
inflight;
|
|
89
98
|
/**
|
|
@@ -108,11 +117,14 @@ window.__ModuleLoader__.load({
|
|
|
108
117
|
});
|
|
109
118
|
const operation = this.ctx.remote.session.modelCatalog().then((response) => {
|
|
110
119
|
if (!response.ok) throw new Error(`${response.error.code}: ${response.error.message}`);
|
|
111
|
-
if (generation === this.generation)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
if (generation === this.generation) {
|
|
121
|
+
for (const group of response.value.groups) for (const model of group.models) this.reasoning.set(JSON.stringify([group.id, model.id]), model.reasoning);
|
|
122
|
+
this.store.set({
|
|
123
|
+
value: response.value,
|
|
124
|
+
status: "ready",
|
|
125
|
+
error: null
|
|
126
|
+
});
|
|
127
|
+
}
|
|
116
128
|
return response.value;
|
|
117
129
|
}).catch((error) => {
|
|
118
130
|
if (generation === this.generation) this.store.update((draft) => {
|
|
@@ -147,6 +159,7 @@ window.__ModuleLoader__.load({
|
|
|
147
159
|
}
|
|
148
160
|
/** Clear Host-specific values and load the replacement Host generation. */
|
|
149
161
|
resetGeneration() {
|
|
162
|
+
this.reasoning.clear();
|
|
150
163
|
this.invalidate(true);
|
|
151
164
|
this.load().catch(() => {});
|
|
152
165
|
}
|
|
@@ -167,12 +180,12 @@ window.__ModuleLoader__.load({
|
|
|
167
180
|
groups: [],
|
|
168
181
|
failures: [],
|
|
169
182
|
status: "idle",
|
|
183
|
+
pending: null,
|
|
170
184
|
error: null
|
|
171
185
|
});
|
|
172
186
|
/** Latest selection operation wins; an older response never overwrites a newer one. */
|
|
173
187
|
generation = 0;
|
|
174
188
|
disposed = false;
|
|
175
|
-
resolved = false;
|
|
176
189
|
unsubscribeCatalog;
|
|
177
190
|
unsubscribeSelection;
|
|
178
191
|
/**
|
|
@@ -197,7 +210,7 @@ window.__ModuleLoader__.load({
|
|
|
197
210
|
this.syncInputs();
|
|
198
211
|
}
|
|
199
212
|
/**
|
|
200
|
-
* Ensure the Host generation's shared
|
|
213
|
+
* Ensure the Host generation's shared available catalog is loaded.
|
|
201
214
|
* @returns the fresh directory value.
|
|
202
215
|
*/
|
|
203
216
|
async load() {
|
|
@@ -218,6 +231,7 @@ window.__ModuleLoader__.load({
|
|
|
218
231
|
const generation = ++this.generation;
|
|
219
232
|
this.store.update((s) => {
|
|
220
233
|
s.status = "selecting";
|
|
234
|
+
s.pending = selection;
|
|
221
235
|
s.error = null;
|
|
222
236
|
});
|
|
223
237
|
const result = await this.sessions.selectModel({
|
|
@@ -233,12 +247,14 @@ window.__ModuleLoader__.load({
|
|
|
233
247
|
if (!result.ok) {
|
|
234
248
|
this.store.update((s) => {
|
|
235
249
|
s.status = "error";
|
|
250
|
+
s.pending = null;
|
|
236
251
|
s.error = `${result.error.code}: ${result.error.message}`;
|
|
237
252
|
});
|
|
238
253
|
return result;
|
|
239
254
|
}
|
|
240
255
|
this.store.update((s) => {
|
|
241
256
|
s.status = "ready";
|
|
257
|
+
s.pending = null;
|
|
242
258
|
s.error = null;
|
|
243
259
|
});
|
|
244
260
|
this.syncInputs();
|
|
@@ -255,6 +271,7 @@ window.__ModuleLoader__.load({
|
|
|
255
271
|
++this.generation;
|
|
256
272
|
this.store.update((state) => {
|
|
257
273
|
if (state.status === "selecting") state.status = "idle";
|
|
274
|
+
state.pending = null;
|
|
258
275
|
state.error = null;
|
|
259
276
|
});
|
|
260
277
|
this.syncInputs();
|
|
@@ -272,32 +289,33 @@ window.__ModuleLoader__.load({
|
|
|
272
289
|
if (this.disposed) return;
|
|
273
290
|
const catalog = this.catalog.store.getSnapshot();
|
|
274
291
|
const projected = modelSelectionProjection(this.projected.getSnapshot());
|
|
292
|
+
const intended = projected?.next ?? catalog.value?.default;
|
|
293
|
+
const reasoning = intended === void 0 ? void 0 : this.catalog.reasoningFor(intended);
|
|
294
|
+
const effort = intended?.reasoningEffort ?? reasoning?.defaultEffort;
|
|
295
|
+
const retainedEffort = effort === void 0 ? void 0 : reasoning?.efforts.find((level) => level.id === effort)?.name ?? effort;
|
|
275
296
|
if (catalog.status !== "ready" || catalog.value === null || projected === void 0) {
|
|
276
|
-
if (this.resolved) {
|
|
277
|
-
if (catalog.status === "error") this.store.update((state) => {
|
|
278
|
-
state.status = "error";
|
|
279
|
-
state.error = catalog.error;
|
|
280
|
-
});
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
297
|
this.store.set({
|
|
284
|
-
current: null,
|
|
298
|
+
current: catalog.value === null ? null : this.store.getSnapshot().current,
|
|
299
|
+
...retainedEffort === void 0 ? {} : { retainedEffort },
|
|
285
300
|
routable: null,
|
|
286
|
-
groups: [],
|
|
287
|
-
failures: [],
|
|
301
|
+
groups: catalog.value?.groups ?? [],
|
|
302
|
+
failures: catalog.value?.failures ?? [],
|
|
288
303
|
status: catalog.status === "error" ? "error" : "loading",
|
|
304
|
+
pending: this.store.getSnapshot().pending,
|
|
289
305
|
error: catalog.error
|
|
290
306
|
});
|
|
291
307
|
return;
|
|
292
308
|
}
|
|
293
|
-
const
|
|
294
|
-
|
|
309
|
+
const selection = projected.next ?? catalog.value.default;
|
|
310
|
+
const routable = catalog.value.groups.some((group) => group.id === selection.provider && group.models.some((model) => model.id === selection.model));
|
|
295
311
|
this.store.set({
|
|
296
|
-
current,
|
|
297
|
-
|
|
312
|
+
current: selection,
|
|
313
|
+
...retainedEffort === void 0 ? {} : { retainedEffort },
|
|
314
|
+
routable,
|
|
298
315
|
groups: catalog.value.groups,
|
|
299
316
|
failures: catalog.value.failures,
|
|
300
317
|
status: this.store.getSnapshot().status === "selecting" ? "selecting" : "ready",
|
|
318
|
+
pending: this.store.getSnapshot().pending,
|
|
301
319
|
error: null
|
|
302
320
|
});
|
|
303
321
|
}
|
|
@@ -330,15 +348,11 @@ window.__ModuleLoader__.load({
|
|
|
330
348
|
];
|
|
331
349
|
live = { directories: new WeakMapWithValues() };
|
|
332
350
|
catalog;
|
|
333
|
-
/** Localized composer-block copy; this plugin owns the string it raises. */
|
|
334
|
-
blockReason;
|
|
335
351
|
/**
|
|
336
352
|
* @param ctx - owning root context (the service registers itself as `models`).
|
|
337
|
-
* @param config - the bound translator for this plugin's own dictionary.
|
|
338
353
|
*/
|
|
339
|
-
constructor(ctx
|
|
354
|
+
constructor(ctx) {
|
|
340
355
|
super(ctx, "modelDirectories");
|
|
341
|
-
this.blockReason = config.blockReason;
|
|
342
356
|
this.catalog = new ModelCatalogDirectory(ctx);
|
|
343
357
|
this.catalog.load().catch(() => {});
|
|
344
358
|
ctx.on("connection/reset", () => {
|
|
@@ -351,6 +365,9 @@ window.__ModuleLoader__.load({
|
|
|
351
365
|
ctx.remote.$on("settings/document-updated", () => {
|
|
352
366
|
this.catalog.refresh();
|
|
353
367
|
});
|
|
368
|
+
ctx.remote.$on("credentials/record-updated", () => {
|
|
369
|
+
this.catalog.refresh();
|
|
370
|
+
});
|
|
354
371
|
ctx.remote.$on("credentials/reference-updated", () => {
|
|
355
372
|
this.catalog.refresh();
|
|
356
373
|
});
|
|
@@ -372,23 +389,6 @@ window.__ModuleLoader__.load({
|
|
|
372
389
|
if (existing !== void 0) return existing;
|
|
373
390
|
const directory = new ModelDirectory(this.ctx.remote.session, sessionId, () => sessions.subagentAddress(sessionId) === void 0, this.catalog, binding.session.projections.faceOf("modelSelection"));
|
|
374
391
|
live.directories.set(binding, directory);
|
|
375
|
-
const conversation = this.ctx.get("conversation");
|
|
376
|
-
if (conversation !== void 0) {
|
|
377
|
-
const publish = () => {
|
|
378
|
-
if (sessions.binding(sessionId) !== binding) return;
|
|
379
|
-
conversation.blocks.set(sessionId, directory.store.getSnapshot().routable === false ? { reason: this.blockReason() } : void 0);
|
|
380
|
-
};
|
|
381
|
-
publish();
|
|
382
|
-
actx.effect(() => {
|
|
383
|
-
const stop = directory.store.subscribe(publish);
|
|
384
|
-
return () => {
|
|
385
|
-
stop();
|
|
386
|
-
const current = sessions.binding(sessionId);
|
|
387
|
-
if (current !== void 0 && current !== binding && live.directories.get(current) !== void 0) return;
|
|
388
|
-
conversation.blocks.set(sessionId, void 0);
|
|
389
|
-
};
|
|
390
|
-
}, "ui-model-selection: composer block");
|
|
391
|
-
}
|
|
392
392
|
actx.effect(() => () => {
|
|
393
393
|
directory.dispose();
|
|
394
394
|
live.directories.delete(binding);
|
|
@@ -413,7 +413,7 @@ window.__ModuleLoader__.load({
|
|
|
413
413
|
}
|
|
414
414
|
//#endregion
|
|
415
415
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-model-selection/src/client/ModelSelect.module.css.mjs
|
|
416
|
-
const css = "._7KE1Ra_root{min-width:0;position:relative}._7KE1Ra_trigger{min-width:0;max-width:min(360px,45cqw);height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;
|
|
416
|
+
const css = "._7KE1Ra_root{min-width:0;position:relative}._7KE1Ra_trigger{border-radius:var(--dsw-radius-sm);min-width:0;max-width:min(360px,45cqw);height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;outline:none;align-items:center;gap:4px;padding:0 4px 0 8px;font-size:13px;font-weight:400;line-height:20px;display:flex}._7KE1Ra_trigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7KE1Ra_trigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary))}._7KE1Ra_trigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}._7KE1Ra_triggerLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}._7KE1Ra_triggerEffort{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-caption);flex-shrink:1000;overflow:hidden}._7KE1Ra_triggerIcon{display:var(--dsh-composer-model-icon-display,none);flex:none}._7KE1Ra_triggerLabel,._7KE1Ra_triggerEffort{display:var(--dsh-composer-model-text-display,block)}._7KE1Ra_chevron{color:var(--dsw-alias-label-caption);flex:none;transition:transform .12s}._7KE1Ra_chevronOpen{transform:rotate(180deg)}._7KE1Ra_menu{z-index:1100;--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);width:max-content;min-width:min(240px,100vw - 32px);max-width:min(420px,100vw - 32px);max-height:min(360px,100vh - 96px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border:0;flex-direction:column;padding:4px;display:flex;position:fixed;overflow:hidden}._7KE1Ra_status,._7KE1Ra_empty{color:var(--dsw-alias-label-tertiary);padding:8px;font-size:12px;line-height:18px}._7KE1Ra_error,._7KE1Ra_warning{border-radius:var(--dsw-radius-md);background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);justify-content:space-between;align-items:flex-start;gap:6px;margin-bottom:3px;padding:6px 7px;font-size:11px;line-height:16px;display:flex}._7KE1Ra_warning{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-state-warn-label)}._7KE1Ra_retry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-weight:600}._7KE1Ra_groups{min-height:0;overflow-y:auto}._7KE1Ra_group+._7KE1Ra_group{margin-top:3px}._7KE1Ra_groupTitle{z-index:1;background:var(--dsw-specific-menu);color:var(--dsw-alias-label-tertiary);padding:4px 7px 2px;font-size:11px;font-weight:500;line-height:16px;position:sticky;top:0}._7KE1Ra_option{box-sizing:border-box;border-radius:var(--dsw-radius-md);width:auto;min-width:100%;min-height:34px;color:inherit;text-align:left;cursor:pointer;background:0 0;border:none;outline:none;align-items:center;gap:6px;padding:5px 7px;display:flex}._7KE1Ra_option:hover:not(:disabled),._7KE1Ra_option:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}._7KE1Ra_selected{background:0 0}._7KE1Ra_option:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}._7KE1Ra_optionCopy{flex-direction:column;flex:1;min-width:0;display:flex}._7KE1Ra_modelName{color:inherit;text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;line-height:18px;overflow:hidden}._7KE1Ra_check{color:var(--dsw-alias-label-primary);flex:0 0 14px;place-items:center;display:grid}._7KE1Ra_check svg{width:14px;height:14px}._7KE1Ra_cell{box-sizing:border-box;border-radius:var(--dsw-radius-md);width:auto;min-width:100%;height:34px;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;background:0 0;border:none;align-items:center;gap:6px;padding:0 8px;font-size:13px;line-height:20px;display:flex}._7KE1Ra_cell:hover{background:var(--dsw-alias-interactive-bg-hover)}._7KE1Ra_cellLabel{white-space:nowrap;flex:none}._7KE1Ra_cellValue{text-overflow:ellipsis;white-space:nowrap;text-align:right;min-width:0;color:var(--dsw-alias-label-tertiary);flex:auto;overflow:hidden}._7KE1Ra_cellChevron{width:12px;height:12px;color:var(--dsw-alias-menu-icon);flex:none}";
|
|
417
417
|
const tagId = "@deepseek-ai/dsh-client-ui-model-selection/ModelSelect.module.css";
|
|
418
418
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
419
419
|
const tag = document.createElement("style");
|
|
@@ -468,7 +468,9 @@ window.__ModuleLoader__.load({
|
|
|
468
468
|
* selected effort come from the Host rather than a client-owned vocabulary. A
|
|
469
469
|
* rejected selection announces through the shared transient Toast anchored to
|
|
470
470
|
* the composer card; the in-menu strip with Retry remains the catalog-load
|
|
471
|
-
* surface.
|
|
471
|
+
* surface. While the directory's pending selection is unsettled, the trigger
|
|
472
|
+
* shows a spinner in place of its chevron, and each row whose value that
|
|
473
|
+
* selection carries shows one in place of its check mark.
|
|
472
474
|
*/
|
|
473
475
|
/** Unplaced portal card: hidden but laid out at a fixed origin so offsetWidth/offsetHeight are real (Menu primitive's measure pass). */
|
|
474
476
|
const MEASURE_STYLE = {
|
|
@@ -495,7 +497,8 @@ window.__ModuleLoader__.load({
|
|
|
495
497
|
const [menuPos, setMenuPos] = (0, react.useState)(null);
|
|
496
498
|
const itemRefs = (0, react.useRef)([]);
|
|
497
499
|
const id = (0, react.useId)();
|
|
498
|
-
const
|
|
500
|
+
const groups = (0, react.useMemo)(() => state.groups.toSorted((left, right) => (left.id === "deepseek-account" ? 0 : left.id === "deepseek-official" ? 1 : 2) - (right.id === "deepseek-account" ? 0 : right.id === "deepseek-official" ? 1 : 2)), [state.groups]);
|
|
501
|
+
const choices = (0, react.useMemo)(() => groups.flatMap((group) => group.models.map((model) => ({
|
|
499
502
|
group,
|
|
500
503
|
model,
|
|
501
504
|
selection: {
|
|
@@ -503,11 +506,11 @@ window.__ModuleLoader__.load({
|
|
|
503
506
|
model: model.id,
|
|
504
507
|
...model.reasoning?.defaultEffort === void 0 ? {} : { reasoningEffort: model.reasoning.defaultEffort }
|
|
505
508
|
}
|
|
506
|
-
}))), [
|
|
509
|
+
}))), [groups]);
|
|
507
510
|
const currentChoice = choices[state.current === null ? -1 : choices.findIndex((c) => c.selection.provider === state.current?.provider && c.selection.model === state.current.model)];
|
|
508
511
|
const reasoning = currentChoice?.model.reasoning;
|
|
509
512
|
const effectiveEffort = state.current?.reasoningEffort ?? reasoning?.defaultEffort;
|
|
510
|
-
const effortLabel = reasoning === void 0 ?
|
|
513
|
+
const effortLabel = reasoning === void 0 ? state.retainedEffort : effectiveEffort === void 0 ? t("effort.providerDefault") : reasoning.efforts.find((level) => level.id === effectiveEffort)?.name ?? effectiveEffort;
|
|
511
514
|
const effortChoices = (0, react.useMemo)(() => reasoning === void 0 ? [] : [...reasoning.defaultEffort === void 0 ? [{
|
|
512
515
|
key: "provider-default",
|
|
513
516
|
effort: void 0,
|
|
@@ -517,7 +520,8 @@ window.__ModuleLoader__.load({
|
|
|
517
520
|
effort: effort.id,
|
|
518
521
|
label: effort.name
|
|
519
522
|
}))], [reasoning, t]);
|
|
520
|
-
const
|
|
523
|
+
const { pending } = state;
|
|
524
|
+
const busy = pending !== null;
|
|
521
525
|
const reload = () => {
|
|
522
526
|
lastActionRef.current = "load";
|
|
523
527
|
load();
|
|
@@ -581,7 +585,9 @@ window.__ModuleLoader__.load({
|
|
|
581
585
|
]);
|
|
582
586
|
if (!available) return null;
|
|
583
587
|
const show = () => {
|
|
584
|
-
|
|
588
|
+
triggerRef.current?.focus();
|
|
589
|
+
if (state.current === null) paneFocus.current = "drill";
|
|
590
|
+
setPane(state.current === null ? "model" : "root");
|
|
585
591
|
setOpen(true);
|
|
586
592
|
reload();
|
|
587
593
|
};
|
|
@@ -610,7 +616,7 @@ window.__ModuleLoader__.load({
|
|
|
610
616
|
const onRootKeyDown = (event) => {
|
|
611
617
|
if (event.key === "Escape" && open) {
|
|
612
618
|
event.preventDefault();
|
|
613
|
-
if (pane !== "root") back(pane);
|
|
619
|
+
if (pane !== "root" && state.current !== null) back(pane);
|
|
614
620
|
else close(true);
|
|
615
621
|
return;
|
|
616
622
|
}
|
|
@@ -618,7 +624,7 @@ window.__ModuleLoader__.load({
|
|
|
618
624
|
if (event.key === "Tab") {
|
|
619
625
|
if (event.shiftKey) {
|
|
620
626
|
event.preventDefault();
|
|
621
|
-
if (pane !== "root") back(pane);
|
|
627
|
+
if (pane !== "root" && state.current !== null) back(pane);
|
|
622
628
|
else close(true);
|
|
623
629
|
return;
|
|
624
630
|
}
|
|
@@ -656,13 +662,17 @@ window.__ModuleLoader__.load({
|
|
|
656
662
|
text: error.code === "session/writer-held" ? t("error.sessionInUse") : t("error.action", { message: `${error.code}: ${error.message}` })
|
|
657
663
|
});
|
|
658
664
|
};
|
|
665
|
+
const submit = (selection) => {
|
|
666
|
+
lastActionRef.current = "select";
|
|
667
|
+
triggerRef.current?.focus();
|
|
668
|
+
select(selection).then(settleSelection);
|
|
669
|
+
};
|
|
659
670
|
const choose = (selection) => {
|
|
660
671
|
if (state.current?.provider === selection.provider && state.current.model === selection.model) {
|
|
661
672
|
close(true);
|
|
662
673
|
return;
|
|
663
674
|
}
|
|
664
|
-
|
|
665
|
-
select(selection).then(settleSelection);
|
|
675
|
+
submit(selection);
|
|
666
676
|
};
|
|
667
677
|
const chooseEffort = (effort) => {
|
|
668
678
|
if (state.current === null) return;
|
|
@@ -670,13 +680,11 @@ window.__ModuleLoader__.load({
|
|
|
670
680
|
close(true);
|
|
671
681
|
return;
|
|
672
682
|
}
|
|
673
|
-
|
|
683
|
+
submit({
|
|
674
684
|
provider: state.current.provider,
|
|
675
685
|
model: state.current.model,
|
|
676
686
|
...effort === void 0 ? {} : { reasoningEffort: effort }
|
|
677
|
-
};
|
|
678
|
-
lastActionRef.current = "select";
|
|
679
|
-
select(selection).then(settleSelection);
|
|
687
|
+
});
|
|
680
688
|
};
|
|
681
689
|
const waiting = state.current === null && state.status === "loading";
|
|
682
690
|
const modelLabel = waiting ? t("trigger.loading") : currentChoice?.model.name ?? (state.current === null ? t("trigger.fallback") : `${state.current.provider}/${state.current.model}`);
|
|
@@ -698,6 +706,9 @@ window.__ModuleLoader__.load({
|
|
|
698
706
|
className: ModelSelect_module_css_default.root,
|
|
699
707
|
onKeyDown: onRootKeyDown,
|
|
700
708
|
onBlur,
|
|
709
|
+
onMouseDown: (event) => {
|
|
710
|
+
if (event.target instanceof Element && event.target.closest("button") !== null) event.preventDefault();
|
|
711
|
+
},
|
|
701
712
|
children: [
|
|
702
713
|
(0, react_jsx_runtime.jsxs)("button", {
|
|
703
714
|
ref: triggerRef,
|
|
@@ -708,9 +719,10 @@ window.__ModuleLoader__.load({
|
|
|
708
719
|
"aria-expanded": open,
|
|
709
720
|
"aria-controls": open ? `${id}-menu` : void 0,
|
|
710
721
|
title: triggerLabel,
|
|
722
|
+
"aria-busy": busy,
|
|
711
723
|
disabled: locked,
|
|
712
724
|
onClick: () => {
|
|
713
|
-
if (open) close();
|
|
725
|
+
if (open) close(true);
|
|
714
726
|
else show();
|
|
715
727
|
},
|
|
716
728
|
children: [
|
|
@@ -726,10 +738,10 @@ window.__ModuleLoader__.load({
|
|
|
726
738
|
className: ModelSelect_module_css_default.triggerEffort,
|
|
727
739
|
children: effortLabel
|
|
728
740
|
}),
|
|
729
|
-
(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: clsx(ModelSelect_module_css_default.chevron, open && ModelSelect_module_css_default.chevronOpen) })
|
|
741
|
+
busy ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "ongoing" }) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: clsx(ModelSelect_module_css_default.chevron, open && ModelSelect_module_css_default.chevronOpen) })
|
|
730
742
|
]
|
|
731
743
|
}),
|
|
732
|
-
open && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)(
|
|
744
|
+
open && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
|
|
733
745
|
ref: menuRef,
|
|
734
746
|
id: `${id}-menu`,
|
|
735
747
|
className: ModelSelect_module_css_default.menu,
|
|
@@ -794,7 +806,7 @@ window.__ModuleLoader__.load({
|
|
|
794
806
|
state.failures.map((failure) => (0, react_jsx_runtime.jsxs)("div", {
|
|
795
807
|
className: ModelSelect_module_css_default.warning,
|
|
796
808
|
children: [(0, react_jsx_runtime.jsx)("span", { children: t("warning.groupLoad", {
|
|
797
|
-
name: failure.name,
|
|
809
|
+
name: failure.id === "deepseek-account" ? t("provider.account") : failure.name,
|
|
798
810
|
message: failure.message
|
|
799
811
|
}) }), (0, react_jsx_runtime.jsx)("button", {
|
|
800
812
|
type: "button",
|
|
@@ -805,7 +817,7 @@ window.__ModuleLoader__.load({
|
|
|
805
817
|
}, failure.id)),
|
|
806
818
|
(0, react_jsx_runtime.jsx)("div", {
|
|
807
819
|
className: clsx(ModelSelect_module_css_default.groups, "scrollable"),
|
|
808
|
-
children:
|
|
820
|
+
children: groups.map((group) => {
|
|
809
821
|
const headingId = `${id}-${group.id}`;
|
|
810
822
|
return (0, react_jsx_runtime.jsxs)("section", {
|
|
811
823
|
role: "group",
|
|
@@ -814,7 +826,7 @@ window.__ModuleLoader__.load({
|
|
|
814
826
|
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
815
827
|
className: ModelSelect_module_css_default.groupTitle,
|
|
816
828
|
id: headingId,
|
|
817
|
-
children: group.name
|
|
829
|
+
children: group.id === "deepseek-account" ? t("provider.account") : group.name
|
|
818
830
|
}), group.models.map((model) => {
|
|
819
831
|
const selected = state.current?.provider === group.id && state.current.model === model.id;
|
|
820
832
|
return (0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -839,7 +851,7 @@ window.__ModuleLoader__.load({
|
|
|
839
851
|
})
|
|
840
852
|
}), (0, react_jsx_runtime.jsx)("span", {
|
|
841
853
|
className: ModelSelect_module_css_default.check,
|
|
842
|
-
children: selected ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : null
|
|
854
|
+
children: pending?.provider === group.id && pending.model === model.id ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "ongoing" }) : selected ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : null
|
|
843
855
|
})]
|
|
844
856
|
}, model.id);
|
|
845
857
|
})]
|
|
@@ -880,7 +892,7 @@ window.__ModuleLoader__.load({
|
|
|
880
892
|
})
|
|
881
893
|
}), (0, react_jsx_runtime.jsx)("span", {
|
|
882
894
|
className: ModelSelect_module_css_default.check,
|
|
883
|
-
children: effectiveEffort === level.effort ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : null
|
|
895
|
+
children: pending !== null && pending.provider === state.current?.provider && pending.model === state.current.model && pending.reasoningEffort === level.effort ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "ongoing" }) : effectiveEffort === level.effort ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : null
|
|
884
896
|
})]
|
|
885
897
|
}, level.key))] })
|
|
886
898
|
]
|
|
@@ -909,14 +921,15 @@ window.__ModuleLoader__.load({
|
|
|
909
921
|
*/
|
|
910
922
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
911
923
|
const zh = {
|
|
924
|
+
"provider.account": "DeepSeek 账号",
|
|
912
925
|
"command.label": "模型",
|
|
913
926
|
"command.description": "选择本会话使用的模型",
|
|
914
927
|
"option.loadError": "目录加载失败:{message}",
|
|
915
928
|
"option.deepseekV4Flash.description": "快速、高效且经济;适合目标明确、常规或并行任务。",
|
|
916
929
|
"option.deepseekV4Pro.description": "更强的自主编码、知识与复杂推理能力;适合复杂或质量优先的任务,但成本更高。",
|
|
917
|
-
"trigger.fallback": "
|
|
930
|
+
"trigger.fallback": "请选择模型",
|
|
918
931
|
"trigger.loading": "正在加载模型…",
|
|
919
|
-
"trigger.selectAria": "
|
|
932
|
+
"trigger.selectAria": "请选择模型",
|
|
920
933
|
"trigger.aria": "选择模型,当前 {model}",
|
|
921
934
|
"trigger.ariaEffort": "选择模型,当前 {model},推理等级 {effort}",
|
|
922
935
|
"menu.aria": "模型与推理等级",
|
|
@@ -929,11 +942,11 @@ window.__ModuleLoader__.load({
|
|
|
929
942
|
"action.reload": "重新加载",
|
|
930
943
|
"warning.groupLoad": "{name} 加载失败:{message}",
|
|
931
944
|
"empty.models": "没有可用的模型。",
|
|
932
|
-
"blocked.composer": "当前模型不可用,请先选择模型",
|
|
933
945
|
"empty.efforts": "当前模型未提供推理等级。"
|
|
934
946
|
};
|
|
935
947
|
/** English dictionary, checked complete against the zh key set. */
|
|
936
948
|
const en = {
|
|
949
|
+
"provider.account": "DeepSeek Account",
|
|
937
950
|
"command.label": "Model",
|
|
938
951
|
"command.description": "Select the model for this conversation",
|
|
939
952
|
"option.loadError": "Catalog failed to load: {message}",
|
|
@@ -954,7 +967,6 @@ window.__ModuleLoader__.load({
|
|
|
954
967
|
"action.reload": "Reload",
|
|
955
968
|
"warning.groupLoad": "{name} failed to load: {message}",
|
|
956
969
|
"empty.models": "No models available.",
|
|
957
|
-
"blocked.composer": "This model is unavailable — select one to continue",
|
|
958
970
|
"empty.efforts": "This model provides no reasoning effort levels."
|
|
959
971
|
};
|
|
960
972
|
//#endregion
|
|
@@ -964,6 +976,8 @@ window.__ModuleLoader__.load({
|
|
|
964
976
|
return `${providerId}/${modelId}`;
|
|
965
977
|
}
|
|
966
978
|
const BUILTIN_DESCRIPTION_KEYS = {
|
|
979
|
+
"deepseek-account/deepseek-v4-flash": "option.deepseekV4Flash.description",
|
|
980
|
+
"deepseek-account/deepseek-v4-pro": "option.deepseekV4Pro.description",
|
|
967
981
|
"deepseek-official/deepseek-v4-flash": "option.deepseekV4Flash.description",
|
|
968
982
|
"deepseek-official/deepseek-v4-pro": "option.deepseekV4Pro.description"
|
|
969
983
|
};
|
|
@@ -974,18 +988,21 @@ window.__ModuleLoader__.load({
|
|
|
974
988
|
/** Flatten the directory into popup rows; failure rows are listed for visibility but never selectable. */
|
|
975
989
|
function optionsOf(directory, t) {
|
|
976
990
|
const rows = [];
|
|
977
|
-
for (const group of directory.groups)
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
991
|
+
for (const group of directory.groups) {
|
|
992
|
+
const name = group.id === "deepseek-account" ? t("provider.account") : group.name;
|
|
993
|
+
for (const model of group.models) {
|
|
994
|
+
const description = descriptionOf(group.id, model, t);
|
|
995
|
+
rows.push({
|
|
996
|
+
id: rowId(group.id, model.id),
|
|
997
|
+
label: model.name,
|
|
998
|
+
detail: description !== void 0 ? `${name} · ${description}` : name,
|
|
999
|
+
...directory.current !== null && directory.current.provider === group.id && directory.current.model === model.id ? { active: true } : {}
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
985
1002
|
}
|
|
986
1003
|
for (const failure of directory.failures) rows.push({
|
|
987
1004
|
id: `failure/${failure.id}`,
|
|
988
|
-
label: failure.name,
|
|
1005
|
+
label: failure.id === "deepseek-account" ? t("provider.account") : failure.name,
|
|
989
1006
|
detail: t("option.loadError", { message: failure.message })
|
|
990
1007
|
});
|
|
991
1008
|
return rows;
|
|
@@ -1031,7 +1048,7 @@ window.__ModuleLoader__.load({
|
|
|
1031
1048
|
en
|
|
1032
1049
|
}), "ui-model-selection: dictionaries");
|
|
1033
1050
|
const t = ctx.locale.bind(NS);
|
|
1034
|
-
ctx.plugin(ModelDirectoryResolver
|
|
1051
|
+
ctx.plugin(ModelDirectoryResolver);
|
|
1035
1052
|
ctx.inject(["commandUi", "modelDirectories"], (scope) => {
|
|
1036
1053
|
const command = scope.get("commandUi");
|
|
1037
1054
|
const models = scope.modelDirectories;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** One Host-generation model catalog shared by every Session selector. */
|
|
2
2
|
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
3
|
-
import type { ModelCatalog } from '@deepseek-ai/dsh-api-remotes/client';
|
|
3
|
+
import type { ModelCatalog, ModelSelection, ModelProviderGroup } from '@deepseek-ai/dsh-api-remotes/client';
|
|
4
4
|
import { type SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
5
5
|
/** Observable lifecycle of the shared model catalog. */
|
|
6
6
|
export interface ModelCatalogState {
|
|
@@ -13,6 +13,13 @@ export declare class ModelCatalogDirectory {
|
|
|
13
13
|
private readonly ctx;
|
|
14
14
|
/** Current shared catalog value and load lifecycle. */
|
|
15
15
|
readonly store: SnapshotStore<ModelCatalogState>;
|
|
16
|
+
private readonly reasoning;
|
|
17
|
+
/**
|
|
18
|
+
* Read the last advertised reasoning metadata, including unavailable models.
|
|
19
|
+
* @param selection - provider and model whose effort is displayed.
|
|
20
|
+
* @returns reasoning metadata observed during this Host generation.
|
|
21
|
+
*/
|
|
22
|
+
reasoningFor(selection: ModelSelection): ModelProviderGroup['models'][number]['reasoning'];
|
|
16
23
|
private generation;
|
|
17
24
|
private inflight;
|
|
18
25
|
/**
|
|
@@ -11,15 +11,11 @@ import type { ObservableSnapshot, SnapshotStore } from '@deepseek-ai/dsh-client-
|
|
|
11
11
|
import type { ModelCatalogDirectory } from './catalog.ts';
|
|
12
12
|
/** Directory snapshot both entries render from. */
|
|
13
13
|
export interface ModelDirectoryState {
|
|
14
|
-
/**
|
|
14
|
+
/** Saved selection, retained even when its provider or model leaves the catalog. */
|
|
15
15
|
current: ModelSelection | null;
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* this rather than "current matches no group": catalog membership is
|
|
20
|
-
* advisory, so a route serving a model it stopped advertising is missing
|
|
21
|
-
* from the groups yet perfectly usable.
|
|
22
|
-
*/
|
|
16
|
+
/** Saved effort caption retained when the selected model is unavailable. */
|
|
17
|
+
retainedEffort?: string;
|
|
18
|
+
/** Whether the current selection is present in the available catalog; null while unresolved. */
|
|
23
19
|
routable: boolean | null;
|
|
24
20
|
/** Successfully loaded provider groups (last good load). */
|
|
25
21
|
groups: readonly ModelProviderGroup[];
|
|
@@ -27,6 +23,8 @@ export interface ModelDirectoryState {
|
|
|
27
23
|
failures: readonly ModelCatalogFailure[];
|
|
28
24
|
/** Lifecycle of the in-flight operation. */
|
|
29
25
|
status: 'idle' | 'loading' | 'ready' | 'selecting' | 'error';
|
|
26
|
+
/** Selection submitted by the latest `select` until it settles; null otherwise. */
|
|
27
|
+
pending: ModelSelection | null;
|
|
30
28
|
/** Whole-request or selection failure text; null when none. */
|
|
31
29
|
error: string | null;
|
|
32
30
|
}
|
|
@@ -42,7 +40,6 @@ export declare class ModelDirectory {
|
|
|
42
40
|
/** Latest selection operation wins; an older response never overwrites a newer one. */
|
|
43
41
|
private generation;
|
|
44
42
|
private disposed;
|
|
45
|
-
private resolved;
|
|
46
43
|
private readonly unsubscribeCatalog;
|
|
47
44
|
private readonly unsubscribeSelection;
|
|
48
45
|
/**
|
|
@@ -54,7 +51,7 @@ export declare class ModelDirectory {
|
|
|
54
51
|
*/
|
|
55
52
|
constructor(sessions: Pick<TypertClientRemote['session'], 'selectModel'>, sessionId: SessionId, available: () => boolean, catalog: ModelCatalogDirectory, projected: ObservableSnapshot<unknown>);
|
|
56
53
|
/**
|
|
57
|
-
* Ensure the Host generation's shared
|
|
54
|
+
* Ensure the Host generation's shared available catalog is loaded.
|
|
58
55
|
* @returns the fresh directory value.
|
|
59
56
|
*/
|
|
60
57
|
load(): Promise<ModelDirectoryState>;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
11
11
|
export declare const zh: {
|
|
12
|
+
'provider.account': string;
|
|
12
13
|
'command.label': string;
|
|
13
14
|
'command.description': string;
|
|
14
15
|
'option.loadError': string;
|
|
@@ -29,13 +30,13 @@ export declare const zh: {
|
|
|
29
30
|
'action.reload': string;
|
|
30
31
|
'warning.groupLoad': string;
|
|
31
32
|
'empty.models': string;
|
|
32
|
-
'blocked.composer': string;
|
|
33
33
|
'empty.efforts': string;
|
|
34
34
|
};
|
|
35
35
|
/** The model namespace key union. */
|
|
36
36
|
export type ModelKey = keyof typeof zh;
|
|
37
37
|
/** English dictionary, checked complete against the zh key set. */
|
|
38
38
|
export declare const en: {
|
|
39
|
+
'provider.account': string;
|
|
39
40
|
'command.label': string;
|
|
40
41
|
'command.description': string;
|
|
41
42
|
'option.loadError': string;
|
|
@@ -56,7 +57,6 @@ export declare const en: {
|
|
|
56
57
|
'action.reload': string;
|
|
57
58
|
'warning.groupLoad': string;
|
|
58
59
|
'empty.models': string;
|
|
59
|
-
'blocked.composer': string;
|
|
60
60
|
'empty.efforts': string;
|
|
61
61
|
};
|
|
62
62
|
//# sourceMappingURL=locales.d.ts.map
|
|
@@ -26,15 +26,10 @@ export declare class ModelDirectoryResolver extends Service {
|
|
|
26
26
|
static inject: string[];
|
|
27
27
|
private readonly live;
|
|
28
28
|
private readonly catalog;
|
|
29
|
-
/** Localized composer-block copy; this plugin owns the string it raises. */
|
|
30
|
-
private readonly blockReason;
|
|
31
29
|
/**
|
|
32
30
|
* @param ctx - owning root context (the service registers itself as `models`).
|
|
33
|
-
* @param config - the bound translator for this plugin's own dictionary.
|
|
34
31
|
*/
|
|
35
|
-
constructor(ctx: Context
|
|
36
|
-
blockReason: () => string;
|
|
37
|
-
});
|
|
32
|
+
constructor(ctx: Context);
|
|
38
33
|
/**
|
|
39
34
|
* Resolve the per-session shared directory (lazy; the scope disposer
|
|
40
35
|
* removes and disposes it). Unknown sessions fail loud.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-ui-model-selection",
|
|
3
3
|
"description": "Model selection over the shared model catalog, Session projection, and session.selectModel",
|
|
4
|
-
"version": "0.1.7-
|
|
4
|
+
"version": "0.1.7-rc.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -46,22 +46,22 @@
|
|
|
46
46
|
"react-dom": "^18.2.0",
|
|
47
47
|
"@types/react-dom": "~18.3.0",
|
|
48
48
|
"clsx": "^2.1.1",
|
|
49
|
-
"@deepseek-ai/dsh-api-remotes": "0.1.7-
|
|
50
|
-
"@deepseek-ai/dsh-api-session-controller": "0.1.7-
|
|
51
|
-
"@deepseek-ai/dsh-client-
|
|
52
|
-
"@deepseek-ai/dsh-client-
|
|
53
|
-
"@deepseek-ai/dsh-client-ui-
|
|
54
|
-
"@deepseek-ai/dsh-client-ui-
|
|
55
|
-
"@deepseek-ai/dsh-client-ui-
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.1.7-
|
|
58
|
-
"@deepseek-ai/dsh-
|
|
49
|
+
"@deepseek-ai/dsh-api-remotes": "0.1.7-rc.2",
|
|
50
|
+
"@deepseek-ai/dsh-api-session-controller": "0.1.7-rc.2",
|
|
51
|
+
"@deepseek-ai/dsh-client-locale": "0.1.7-rc.2",
|
|
52
|
+
"@deepseek-ai/dsh-client-test-runtime": "0.1.7-rc.2",
|
|
53
|
+
"@deepseek-ai/dsh-client-ui-commands": "0.1.7-rc.2",
|
|
54
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.2",
|
|
55
|
+
"@deepseek-ai/dsh-client-ui-input-trigger": "0.1.7-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.2",
|
|
59
59
|
"@deepseek-ai/cordis": "~4.0.4",
|
|
60
|
-
"@deepseek-ai/dsh-client-
|
|
61
|
-
"@deepseek-ai/dsh-session": "0.1.7-
|
|
62
|
-
"@deepseek-ai/dsh-util-values": "0.1.7-
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-
|
|
60
|
+
"@deepseek-ai/dsh-client-store": "0.1.7-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-session": "0.1.7-rc.2",
|
|
62
|
+
"@deepseek-ai/dsh-util-values": "0.1.7-rc.2",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.2",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-session": "0.1.7-rc.2"
|
|
65
65
|
},
|
|
66
66
|
"files": [
|
|
67
67
|
"lib/index.js",
|