@deepseek-ai/dsh-client-ui-model-selection 0.2.0-rc.1 → 0.2.0-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 +6 -6
- package/README.md +9 -3
- package/README.zh.md +9 -3
- package/lib/client.js +216 -63
- package/lib/types/client/locales.d.ts +6 -4
- package/lib/types/client/provider-order.d.ts +10 -0
- package/package.json +17 -17
package/README.i18n.yaml
CHANGED
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
en: d152484eb41ac6b4
|
|
16
16
|
zh: 09388d293f9be9cb
|
|
17
17
|
/deepseek-ai-dsh-client-ui-model-selection/use-this-package:
|
|
18
|
-
en:
|
|
19
|
-
zh:
|
|
18
|
+
en: 62235f8c8c9e1e7e
|
|
19
|
+
zh: c18dc9c38ef9d4f9
|
|
20
20
|
/deepseek-ai-dsh-client-ui-model-selection/use-this-package/model-and-effort:
|
|
21
|
-
en:
|
|
22
|
-
zh:
|
|
21
|
+
en: 48f8157ffdfe6e69
|
|
22
|
+
zh: 64ec9a533e2aa8f4
|
|
23
23
|
/deepseek-ai-dsh-client-ui-model-selection/use-this-package/unroutable-sessions:
|
|
24
24
|
en: 7f20428c747ff052
|
|
25
25
|
zh: 975e6de728e02fa0
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
en: 261bbf42296243af
|
|
28
28
|
zh: 72779fb7b8fbc473
|
|
29
29
|
/deepseek-ai-dsh-client-ui-model-selection/understand-the-implementation:
|
|
30
|
-
en:
|
|
31
|
-
zh:
|
|
30
|
+
en: f3d38594439eab3d
|
|
31
|
+
zh: f255974e61696dca
|
|
32
32
|
/deepseek-ai-dsh-client-ui-model-selection/further-exploration:
|
|
33
33
|
en: 14acd4692e923480
|
|
34
34
|
zh: ff89aed852aa0da8
|
package/README.md
CHANGED
|
@@ -31,13 +31,17 @@ DeepSeek account and API-key routes appear as separate provider groups, each exp
|
|
|
31
31
|
|
|
32
32
|
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.
|
|
33
33
|
|
|
34
|
-
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
|
|
34
|
+
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 through root and effort rows, or move the model-list highlight without leaving search. Enter and Tab settle the highlighted model or focused row; Escape and `Shift+Tab` leave a drilled pane first and otherwise close back to the trigger. Drilling focuses model search when shown, otherwise the current model or effort row, 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.
|
|
35
35
|
|
|
36
|
-
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
|
|
36
|
+
Mouse selection uses native browser clicks, including their cancellation behavior; a press alone never selects. Opening the root menu focuses its trigger, and clicking the trigger again closes the menu and returns focus there. Root rows and the search-clear button show the same fill for keyboard focus and hover, without a native outline. Selecting a model or effort returns focus to the trigger without a focus ring; leaving the trigger or reopening the menu restores its normal focus indication. 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. Tab returns to model search with the current model highlighted when search is shown, otherwise to the current row.
|
|
37
37
|
|
|
38
38
|
### Model and effort
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
The button's model menu shows search only when its full catalog contains more than four models; smaller catalogs focus the current model or first row and support keyboard navigation directly. The `/model` command always keeps its search field. Both search fields match model names case-insensitively, including nonconsecutive characters in order; leading and trailing spaces are ignored. Within each provider, prefix matches come first, followed by alignment score and then catalog order. Empty groups are hidden. Empty catalogs and queries without matches are announced through a status region. Search keeps focus while arrow keys cycle the highlighted result across provider groups; Enter or Tab selects it. Left and right arrows keep their native caret behavior. Opening highlights the current model or the first available row, and editing the query resets the highlight to the first result. Reopening the model pane clears the query.
|
|
41
|
+
|
|
42
|
+
Model and reasoning-effort names use weight 400 (regular) in the composer menu, including the selected item. The search field follows the command popup's compact treatment with transparent background and border in both palettes, with no leading icon and a caption-tone placeholder. The clear button appears for a nonempty query and restores the full list with focus in the search field.
|
|
43
|
+
|
|
44
|
+
Both entries group models by provider, with DeepSeek Account first and DeepSeek second; third-party providers retain their catalog order. Both use the shared, asynchronously observed sticky headings from [ui-primitives](../ui-primitives/README.md#understand-the-implementation): transparent at rest, with the theme's 94%-opaque fill only while pinned, and `md` corners outside macOS Desktop. The composer menu shows model and effort names only. Navigation chevrons use `--dsw-alias-menu-icon`. The `/model` popup shows provider names as group headings and model names as rows, without repeating the provider on each row or showing catalog descriptions. Its search placeholder, no-match text, and empty-catalog text use the same localized labels as the composer model menu. 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.
|
|
41
45
|
|
|
42
46
|
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.
|
|
43
47
|
|
|
@@ -59,6 +63,8 @@ Menus use the shared `MenuSurface` material, including the macOS backing for bac
|
|
|
59
63
|
<details>
|
|
60
64
|
<summary>Implementation internals — click to expand</summary>
|
|
61
65
|
|
|
66
|
+
The composer `ModelSelect` and `/model` option builder share [provider ordering](src/client/provider-order.ts), while both searches use `rankByName` within each provider. The command supplies optional groups and `searchMode: 'fuzzy-label'` through the [popupSelect API](../ui-commands/README.md#use-this-package); both entries use `MenuGroup` and rebuild its sticky observer when the rendered groups change. The command popup fills the composer overlay, while the button retains its compact menu.
|
|
67
|
+
|
|
62
68
|
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.
|
|
63
69
|
|
|
64
70
|
</details>
|
package/README.zh.md
CHANGED
|
@@ -31,13 +31,17 @@ DeepSeek 账号和 API Key 路由显示为独立提供方分组,各自展示
|
|
|
31
31
|
|
|
32
32
|
未选模型文案与可用模型名使用相同的常规字重,并在既有会话与新会话中保留已保存的推理强度显示;调整推理强度需要先选择可用模型。点击未选模型控件直接打开模型列表,Escape 关闭列表。
|
|
33
33
|
|
|
34
|
-
与 `ui-conversation` 及命令包一起挂载本插件;composer 随即在待处理指示器旁显示模型位,`/model` 则以弹窗打开同一份目录。模型位菜单打开期间,`↑`/`↓`
|
|
34
|
+
与 `ui-conversation` 及命令包一起挂载本插件;composer 随即在待处理指示器旁显示模型位,`/model` 则以弹窗打开同一份目录。模型位菜单打开期间,`↑`/`↓` 在根菜单和推理等级行间移动焦点,或在模型列表中移动高亮并让焦点保留在搜索框。Enter 和 Tab 选定高亮模型或聚焦行;Escape 与 `Shift+Tab` 先退出已下钻的面板,否则关闭并回到触发器。下钻时若显示模型搜索框则聚焦搜索框,否则聚焦当前模型或推理强度行,返回则落在打开该面板的格子上。所选模型可用时,composer 显示目录中的模型名称;模型或提供方被删除时(包括账号退登),改为显示已保存的 `provider/model` ID。保存的提供方、模型和推理强度均保持不变。
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
鼠标选择沿用浏览器原生点击及其取消行为;仅按下按钮不会选定。打开根菜单时聚焦触发按钮,再次点击触发按钮会关闭菜单并把焦点还给它。根菜单行和搜索清除按钮的键盘焦点与悬停使用相同底色,不显示原生焦点轮廓。选定模型或推理等级后,焦点返回触发按钮但不显示焦点环;焦点离开按钮或重新打开菜单后恢复正常焦点提示。等待任一入口发起的选择结果时,焦点停在触发按钮,触发按钮以加载图标代替下拉箭头,该选择包含的值所在行以加载图标代替勾选标记;选择被拒绝后菜单保持打开。显示模型搜索框时,Tab 返回搜索框并高亮当前模型,否则返回当前选中行。
|
|
37
37
|
|
|
38
38
|
### 模型与推理强度
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
按钮模型菜单仅在完整目录超过四个模型时显示搜索框;较小目录聚焦当前模型或首行,直接支持键盘导航。`/model` 命令始终保留搜索框。两个搜索框均按模型名称进行不区分大小写的匹配,支持按顺序输入不连续字符,并忽略首尾空格。每个提供方内的结果优先按前缀匹配、其次按匹配得分、最后按目录顺序排列。没有匹配项的分组会隐藏。空目录和无匹配结果均通过状态区域通知屏幕阅读器。方向键在各提供方分组的结果间循环移动高亮,焦点保留在搜索框;Enter 或 Tab 选定高亮项。左右方向键仍用于移动输入光标。打开时高亮当前模型或首个可用选项,修改搜索内容后高亮首个结果。重新打开模型面板会清空搜索内容。
|
|
41
|
+
|
|
42
|
+
composer 菜单中的模型名称和推理等级均使用 400(regular)字重,包括选中项。搜索框沿用 command 弹窗的紧凑样式,在浅/深色主题下均保持背景和边框透明,不显示前置图标,占位文字使用 caption 色。搜索内容非空时显示清除按钮;点击后恢复完整列表,并将焦点留在搜索框。
|
|
43
|
+
|
|
44
|
+
两个入口均按提供方分组,DeepSeek 账号排第一,DeepSeek 排第二,第三方提供方保持目录原有顺序。两者采用 [ui-primitives](../ui-primitives/README.zh.md#understand-the-implementation) 异步观察的共享吸顶标题:原位透明,仅吸顶时使用主题的 94% 不透明填充,macOS 桌面端以外使用 `md` 圆角。composer 菜单只显示模型与推理强度名称。导航箭头使用 `--dsw-alias-menu-icon` 文本色。`/model` 弹窗以提供方名称作为分组标题、模型名称作为选项行,不在每行重复提供方,也不显示目录说明。搜索占位文字、无匹配提示和空目录提示与 composer 模型菜单共用同一组本地化文案。弹窗应用所选模型的默认推理强度;composer 随后可以选择任一已公布的推理强度。适配器没有推理元数据时不显示 Effort 行;不存在任意推理强度输入。
|
|
41
45
|
|
|
42
46
|
展开的控件无法排在同一行时,composer 将模型与推理强度文字替换为模型图标;空间足够后恢复文字。触发器的无障碍名称、提示和菜单仍提供完整选择。
|
|
43
47
|
|
|
@@ -59,6 +63,8 @@ DeepSeek 账号和 API Key 路由显示为独立提供方分组,各自展示
|
|
|
59
63
|
<details>
|
|
60
64
|
<summary>实现细节——点击展开</summary>
|
|
61
65
|
|
|
66
|
+
composer 的 `ModelSelect` 与 `/model` 选项构建器共用[提供方排序](src/client/provider-order.ts),两个搜索框均在每个提供方内使用 `rankByName`。命令通过 [popupSelect API](../ui-commands/README.zh.md#use-this-package) 提供可选分组与 `searchMode: 'fuzzy-label'`;两个入口均使用 `MenuGroup`,并在渲染分组变化时重建其吸顶观察器。命令弹窗撑满 composer 浮层,按钮则保留紧凑菜单。
|
|
67
|
+
|
|
62
68
|
两个入口共用一份由 `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` 与凭据更新事件上重拉。
|
|
63
69
|
|
|
64
70
|
</details>
|
package/lib/client.js
CHANGED
|
@@ -423,7 +423,7 @@ window.__ModuleLoader__.load({
|
|
|
423
423
|
}
|
|
424
424
|
//#endregion
|
|
425
425
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-model-selection/src/client/ModelSelect.module.css.mjs
|
|
426
|
-
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}.
|
|
426
|
+
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:not([data-selection-focus]){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_searchRow{flex-shrink:0;margin:2px 0 3px;position:relative}._7KE1Ra_searchRow ._7KE1Ra_search{border-radius:var(--dsw-radius-md);background:0 0;border:0 solid #0000;height:auto;padding:5px 7px;display:flex}._7KE1Ra_searchRow ._7KE1Ra_searchWithQuery{padding-right:34px}._7KE1Ra_searchClear{corner-shape:round;width:24px;height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:50%;justify-content:center;align-items:center;padding:0;display:inline-flex;position:absolute;top:50%;right:4px;transform:translateY(-50%)}._7KE1Ra_searchClear:hover,._7KE1Ra_searchClear:focus-visible{background:var(--dsw-alias-interactive-bg-hover);outline:none}._7KE1Ra_searchRow ._7KE1Ra_search:focus-within{border-color:#0000}._7KE1Ra_searchRow ._7KE1Ra_search input{padding:0;font-size:12px;line-height:normal}._7KE1Ra_searchRow ._7KE1Ra_search input::placeholder{color:var(--dsw-alias-label-caption)}._7KE1Ra_groups{min-height:0;overflow-y:auto}._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:not(._7KE1Ra_modelOption):hover:not(:disabled),._7KE1Ra_option:focus-visible,._7KE1Ra_optionActive:not(:disabled){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:400;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;outline:none;align-items:center;gap:6px;padding:0 8px;font-size:13px;line-height:20px;display:flex}._7KE1Ra_cell:hover,._7KE1Ra_cell:focus-visible{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}";
|
|
427
427
|
const tagId = "@deepseek-ai/dsh-client-ui-model-selection/ModelSelect.module.css";
|
|
428
428
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
429
429
|
const tag = document.createElement("style");
|
|
@@ -442,15 +442,19 @@ window.__ModuleLoader__.load({
|
|
|
442
442
|
"chevronOpen": "_7KE1Ra_chevronOpen",
|
|
443
443
|
"empty": "_7KE1Ra_empty",
|
|
444
444
|
"error": "_7KE1Ra_error",
|
|
445
|
-
"group": "_7KE1Ra_group",
|
|
446
|
-
"groupTitle": "_7KE1Ra_groupTitle",
|
|
447
445
|
"groups": "_7KE1Ra_groups",
|
|
448
446
|
"menu": "_7KE1Ra_menu",
|
|
449
447
|
"modelName": "_7KE1Ra_modelName",
|
|
448
|
+
"modelOption": "_7KE1Ra_modelOption",
|
|
450
449
|
"option": "_7KE1Ra_option",
|
|
450
|
+
"optionActive": "_7KE1Ra_optionActive",
|
|
451
451
|
"optionCopy": "_7KE1Ra_optionCopy",
|
|
452
452
|
"retry": "_7KE1Ra_retry",
|
|
453
453
|
"root": "_7KE1Ra_root",
|
|
454
|
+
"search": "_7KE1Ra_search",
|
|
455
|
+
"searchClear": "_7KE1Ra_searchClear",
|
|
456
|
+
"searchRow": "_7KE1Ra_searchRow",
|
|
457
|
+
"searchWithQuery": "_7KE1Ra_searchWithQuery",
|
|
454
458
|
"selected": "_7KE1Ra_selected",
|
|
455
459
|
"status": "_7KE1Ra_status",
|
|
456
460
|
"trigger": "_7KE1Ra_trigger",
|
|
@@ -460,6 +464,17 @@ window.__ModuleLoader__.load({
|
|
|
460
464
|
"warning": "_7KE1Ra_warning"
|
|
461
465
|
};
|
|
462
466
|
//#endregion
|
|
467
|
+
//#region lib/types/client/provider-order.js
|
|
468
|
+
/** Shared provider display order for the composer and command model pickers. */
|
|
469
|
+
/**
|
|
470
|
+
* Put the account and official providers first, preserving every other relative order.
|
|
471
|
+
* @param groups - Provider groups in catalog order.
|
|
472
|
+
* @returns a sorted copy; model order within each group is unchanged.
|
|
473
|
+
*/
|
|
474
|
+
function orderModelProviders(groups) {
|
|
475
|
+
return 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));
|
|
476
|
+
}
|
|
477
|
+
//#endregion
|
|
463
478
|
//#region lib/types/client/ModelSelect.js
|
|
464
479
|
/**
|
|
465
480
|
* ModelSelect: the composer's named model seat (`conversation.input.model`).
|
|
@@ -468,19 +483,25 @@ window.__ModuleLoader__.load({
|
|
|
468
483
|
* each drilling into its own list — the provider-grouped model list over
|
|
469
484
|
* the shared directory, and the effort levels. The trigger (313:14108's
|
|
470
485
|
* ToggleButton) shows both: model name + effort in the caption tone.
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
486
|
+
* Model catalogs above four entries show search, which retains focus while
|
|
487
|
+
* ↑/↓ cycle the highlighted result; Enter and Tab accept it. Smaller model
|
|
488
|
+
* catalogs, root panes, and effort panes move focus between rows. Escape and Shift+Tab leave a drilled pane first and otherwise close
|
|
489
|
+
* back to the trigger. A drilled pane focuses the current effort or model
|
|
490
|
+
* search field. Provider headings paint their background only while pinned
|
|
491
|
+
* by scrolling. Clearing a query restores the full list and search focus.
|
|
492
|
+
* Selecting restores trigger focus without a ring until the trigger loses focus
|
|
493
|
+
* or the menu reopens. Model names match a case-insensitive ordered subsequence
|
|
494
|
+
* within each provider group, ranked by
|
|
495
|
+
* prefix, alignment score, then catalog order. Returning to the root pane
|
|
496
|
+
* hands focus back to the cell that opened it. Data and submission ride the
|
|
497
|
+
* same per-session ModelDirectory as the /model popup; exact-model reasoning
|
|
498
|
+
* metadata and the selected effort come from the Host rather than a
|
|
499
|
+
* client-owned vocabulary. A rejected selection announces through the shared
|
|
500
|
+
* transient Toast anchored to the composer card; the in-menu strip with
|
|
501
|
+
* Retry remains the catalog-load surface. While the directory's pending
|
|
502
|
+
* selection is unsettled, the trigger shows a spinner in place of its
|
|
503
|
+
* chevron, and each row whose value that selection carries shows one in place
|
|
504
|
+
* of its check mark.
|
|
484
505
|
*/
|
|
485
506
|
/** Unplaced portal card: hidden but laid out at a fixed origin so offsetWidth/offsetHeight are real (Menu primitive's measure pass). */
|
|
486
507
|
const MEASURE_STYLE = {
|
|
@@ -498,16 +519,21 @@ window.__ModuleLoader__.load({
|
|
|
498
519
|
const state = (0, react.useSyncExternalStore)((fn) => directory.subscribe(fn), () => directory.getSnapshot());
|
|
499
520
|
const [open, setOpen] = (0, react.useState)(false);
|
|
500
521
|
const [pane, setPane] = (0, react.useState)("root");
|
|
522
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
523
|
+
const [highlightedIndex, setHighlightedIndex] = (0, react.useState)(null);
|
|
524
|
+
const [selectionFocus, setSelectionFocus] = (0, react.useState)(false);
|
|
501
525
|
const lastActionRef = (0, react.useRef)("load");
|
|
502
526
|
const [toast, setToast] = (0, react.useState)(null);
|
|
503
527
|
const toastSeq = (0, react.useRef)(0);
|
|
504
528
|
const rootRef = (0, react.useRef)(null);
|
|
505
529
|
const triggerRef = (0, react.useRef)(null);
|
|
530
|
+
const searchRef = (0, react.useRef)(null);
|
|
506
531
|
const menuRef = (0, react.useRef)(null);
|
|
532
|
+
const groupsRef = (0, react.useRef)(null);
|
|
507
533
|
const [menuPos, setMenuPos] = (0, react.useState)(null);
|
|
508
534
|
const itemRefs = (0, react.useRef)([]);
|
|
509
535
|
const id = (0, react.useId)();
|
|
510
|
-
const groups = (0, react.useMemo)(() => state.groups
|
|
536
|
+
const groups = (0, react.useMemo)(() => orderModelProviders(state.groups), [state.groups]);
|
|
511
537
|
const choices = (0, react.useMemo)(() => groups.flatMap((group) => group.models.map((model) => ({
|
|
512
538
|
group,
|
|
513
539
|
model,
|
|
@@ -517,6 +543,21 @@ window.__ModuleLoader__.load({
|
|
|
517
543
|
...model.reasoning?.defaultEffort === void 0 ? {} : { reasoningEffort: model.reasoning.defaultEffort }
|
|
518
544
|
}
|
|
519
545
|
}))), [groups]);
|
|
546
|
+
const showSearch = choices.length > 4;
|
|
547
|
+
const filteredGroups = (0, react.useMemo)(() => groups.map((group) => ({
|
|
548
|
+
...group,
|
|
549
|
+
models: (0, _deepseek_ai_dsh_client_ui_primitives.rankByName)(group.models, showSearch ? query.trim() : "")
|
|
550
|
+
})).filter((group) => group.models.length > 0), [
|
|
551
|
+
groups,
|
|
552
|
+
query,
|
|
553
|
+
showSearch
|
|
554
|
+
]);
|
|
555
|
+
const visibleModels = (0, react.useMemo)(() => filteredGroups.flatMap((group) => group.models.map((model) => ({
|
|
556
|
+
provider: group.id,
|
|
557
|
+
model: model.id
|
|
558
|
+
}))), [filteredGroups]);
|
|
559
|
+
const currentVisibleIndex = visibleModels.findIndex((model) => model.provider === state.current?.provider && model.model === state.current.model);
|
|
560
|
+
const activeModelIndex = Math.min(highlightedIndex ?? Math.max(0, currentVisibleIndex), visibleModels.length - 1);
|
|
520
561
|
const currentChoice = choices[state.current === null ? -1 : choices.findIndex((c) => c.selection.provider === state.current?.provider && c.selection.model === state.current.model)];
|
|
521
562
|
const reasoning = currentChoice?.model.reasoning;
|
|
522
563
|
const effectiveEffort = state.current?.reasoningEffort ?? reasoning?.defaultEffort;
|
|
@@ -548,18 +589,53 @@ window.__ModuleLoader__.load({
|
|
|
548
589
|
document.removeEventListener("mousedown", closeOutside);
|
|
549
590
|
};
|
|
550
591
|
}, [open]);
|
|
592
|
+
(0, react.useLayoutEffect)(() => {
|
|
593
|
+
if (!showSearch) {
|
|
594
|
+
setQuery("");
|
|
595
|
+
setHighlightedIndex(null);
|
|
596
|
+
}
|
|
597
|
+
}, [showSearch]);
|
|
551
598
|
const paneFocus = (0, react.useRef)(null);
|
|
599
|
+
const previousShowSearch = (0, react.useRef)(showSearch);
|
|
552
600
|
(0, react.useEffect)(() => {
|
|
553
|
-
const
|
|
601
|
+
const changedSearchMode = previousShowSearch.current !== showSearch;
|
|
602
|
+
previousShowSearch.current = showSearch;
|
|
603
|
+
const intent = paneFocus.current ?? (changedSearchMode && pane === "model" ? "drill" : null);
|
|
554
604
|
paneFocus.current = null;
|
|
555
605
|
if (!open || intent === null) return;
|
|
556
606
|
if (intent === "drill") {
|
|
607
|
+
if (pane === "model" && showSearch) {
|
|
608
|
+
searchRef.current?.focus();
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
557
611
|
(menuRef.current?.querySelector("[role=\"menuitemradio\"][aria-checked=\"true\"]:not([disabled])") ?? itemRefs.current.find((item) => item !== null && !item.disabled) ?? triggerRef.current)?.focus();
|
|
558
612
|
return;
|
|
559
613
|
}
|
|
560
614
|
const cell = itemRefs.current[intent === "effort" ? 1 : 0];
|
|
561
615
|
(cell !== null && cell !== void 0 && !cell.disabled ? cell : triggerRef.current)?.focus();
|
|
562
|
-
}, [
|
|
616
|
+
}, [
|
|
617
|
+
open,
|
|
618
|
+
pane,
|
|
619
|
+
showSearch
|
|
620
|
+
]);
|
|
621
|
+
(0, react.useEffect)(() => {
|
|
622
|
+
const viewport = groupsRef.current;
|
|
623
|
+
if (viewport === null) return;
|
|
624
|
+
return (0, _deepseek_ai_dsh_client_ui_primitives.observeStickyMenuGroups)(viewport);
|
|
625
|
+
}, [
|
|
626
|
+
available,
|
|
627
|
+
open,
|
|
628
|
+
pane,
|
|
629
|
+
filteredGroups
|
|
630
|
+
]);
|
|
631
|
+
(0, react.useLayoutEffect)(() => {
|
|
632
|
+
if (open && pane === "model" && activeModelIndex >= 0) itemRefs.current[activeModelIndex]?.scrollIntoView({ block: "nearest" });
|
|
633
|
+
}, [
|
|
634
|
+
open,
|
|
635
|
+
pane,
|
|
636
|
+
activeModelIndex,
|
|
637
|
+
visibleModels
|
|
638
|
+
]);
|
|
563
639
|
(0, react.useLayoutEffect)(() => {
|
|
564
640
|
if (!open) {
|
|
565
641
|
setMenuPos(null);
|
|
@@ -591,16 +667,24 @@ window.__ModuleLoader__.load({
|
|
|
591
667
|
}, [
|
|
592
668
|
open,
|
|
593
669
|
pane,
|
|
594
|
-
state
|
|
670
|
+
state,
|
|
671
|
+
query
|
|
595
672
|
]);
|
|
596
673
|
if (!available) return null;
|
|
597
674
|
const show = () => {
|
|
675
|
+
setSelectionFocus(false);
|
|
598
676
|
triggerRef.current?.focus();
|
|
677
|
+
setQuery("");
|
|
678
|
+
setHighlightedIndex(null);
|
|
599
679
|
if (state.current === null) paneFocus.current = "drill";
|
|
600
680
|
setPane(state.current === null ? "model" : "root");
|
|
601
681
|
setOpen(true);
|
|
602
682
|
reload();
|
|
603
683
|
};
|
|
684
|
+
const changeQuery = (next) => {
|
|
685
|
+
setQuery(next);
|
|
686
|
+
setHighlightedIndex(0);
|
|
687
|
+
};
|
|
604
688
|
const close = (restoreFocus = false) => {
|
|
605
689
|
setOpen(false);
|
|
606
690
|
setPane("root");
|
|
@@ -608,7 +692,13 @@ window.__ModuleLoader__.load({
|
|
|
608
692
|
triggerRef.current?.focus();
|
|
609
693
|
});
|
|
610
694
|
};
|
|
695
|
+
const closeAfterSelection = () => {
|
|
696
|
+
setSelectionFocus(true);
|
|
697
|
+
close(true);
|
|
698
|
+
};
|
|
611
699
|
const drill = (next) => {
|
|
700
|
+
setQuery("");
|
|
701
|
+
setHighlightedIndex(null);
|
|
612
702
|
paneFocus.current = "drill";
|
|
613
703
|
setPane(next);
|
|
614
704
|
};
|
|
@@ -624,6 +714,7 @@ window.__ModuleLoader__.load({
|
|
|
624
714
|
items[active === -1 ? offset > 0 ? 0 : items.length - 1 : (active + offset + items.length) % items.length]?.focus();
|
|
625
715
|
};
|
|
626
716
|
const onRootKeyDown = (event) => {
|
|
717
|
+
if (event.nativeEvent.isComposing) return;
|
|
627
718
|
if (event.key === "Escape" && open) {
|
|
628
719
|
event.preventDefault();
|
|
629
720
|
if (pane !== "root" && state.current !== null) back(pane);
|
|
@@ -631,6 +722,21 @@ window.__ModuleLoader__.load({
|
|
|
631
722
|
return;
|
|
632
723
|
}
|
|
633
724
|
if (!open) return;
|
|
725
|
+
if (pane === "model" && showSearch && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
|
|
726
|
+
event.preventDefault();
|
|
727
|
+
if (!busy && visibleModels.length > 0) {
|
|
728
|
+
setHighlightedIndex((activeModelIndex + (event.key === "ArrowDown" ? 1 : -1) + visibleModels.length) % visibleModels.length);
|
|
729
|
+
searchRef.current?.focus();
|
|
730
|
+
}
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if (pane === "model" && showSearch && event.target instanceof HTMLInputElement && (event.key === "Enter" || event.key === "Tab" && !event.shiftKey)) {
|
|
734
|
+
if (event.key === "Tab" && visibleModels.length === 0) return;
|
|
735
|
+
event.preventDefault();
|
|
736
|
+
const highlighted = visibleModels[activeModelIndex];
|
|
737
|
+
if (!busy && highlighted !== void 0) choose(highlighted);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
634
740
|
if (event.key === "Tab") {
|
|
635
741
|
if (event.shiftKey) {
|
|
636
742
|
event.preventDefault();
|
|
@@ -647,6 +753,11 @@ window.__ModuleLoader__.load({
|
|
|
647
753
|
}
|
|
648
754
|
if (focused !== triggerRef.current) return;
|
|
649
755
|
event.preventDefault();
|
|
756
|
+
if (pane === "model" && showSearch) {
|
|
757
|
+
setHighlightedIndex(null);
|
|
758
|
+
searchRef.current?.focus();
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
650
761
|
(menuRef.current?.querySelector("[role=\"menuitemradio\"][aria-checked=\"true\"]:not([disabled])") ?? rows.find((item) => !item.disabled))?.focus();
|
|
651
762
|
return;
|
|
652
763
|
}
|
|
@@ -662,7 +773,7 @@ window.__ModuleLoader__.load({
|
|
|
662
773
|
const settleSelection = (result) => {
|
|
663
774
|
if (result === void 0) return;
|
|
664
775
|
if (result.ok) {
|
|
665
|
-
if (rootRef.current !== null)
|
|
776
|
+
if (rootRef.current !== null) closeAfterSelection();
|
|
666
777
|
return;
|
|
667
778
|
}
|
|
668
779
|
const { error } = result;
|
|
@@ -674,12 +785,13 @@ window.__ModuleLoader__.load({
|
|
|
674
785
|
};
|
|
675
786
|
const submit = (selection) => {
|
|
676
787
|
lastActionRef.current = "select";
|
|
788
|
+
setSelectionFocus(true);
|
|
677
789
|
triggerRef.current?.focus();
|
|
678
790
|
select(selection).then(settleSelection);
|
|
679
791
|
};
|
|
680
792
|
const choose = (selection) => {
|
|
681
793
|
if (state.current?.provider === selection.provider && state.current.model === selection.model) {
|
|
682
|
-
|
|
794
|
+
closeAfterSelection();
|
|
683
795
|
return;
|
|
684
796
|
}
|
|
685
797
|
submit(selection);
|
|
@@ -687,7 +799,7 @@ window.__ModuleLoader__.load({
|
|
|
687
799
|
const chooseEffort = (effort) => {
|
|
688
800
|
if (state.current === null) return;
|
|
689
801
|
if (effectiveEffort === effort) {
|
|
690
|
-
|
|
802
|
+
closeAfterSelection();
|
|
691
803
|
return;
|
|
692
804
|
}
|
|
693
805
|
submit({
|
|
@@ -705,6 +817,7 @@ window.__ModuleLoader__.load({
|
|
|
705
817
|
});
|
|
706
818
|
itemRefs.current = [];
|
|
707
819
|
let itemIndex = 0;
|
|
820
|
+
let modelIndex = 0;
|
|
708
821
|
const itemRef = () => {
|
|
709
822
|
const at = itemIndex++;
|
|
710
823
|
return (node) => {
|
|
@@ -730,6 +843,10 @@ window.__ModuleLoader__.load({
|
|
|
730
843
|
"aria-controls": open ? `${id}-menu` : void 0,
|
|
731
844
|
title: triggerLabel,
|
|
732
845
|
"aria-busy": busy,
|
|
846
|
+
"data-selection-focus": selectionFocus ? "" : void 0,
|
|
847
|
+
onBlur: () => {
|
|
848
|
+
setSelectionFocus(false);
|
|
849
|
+
},
|
|
733
850
|
disabled: locked,
|
|
734
851
|
onClick: () => {
|
|
735
852
|
if (open) close(true);
|
|
@@ -756,7 +873,7 @@ window.__ModuleLoader__.load({
|
|
|
756
873
|
id: `${id}-menu`,
|
|
757
874
|
className: ModelSelect_module_css_default.menu,
|
|
758
875
|
style: menuPos ?? MEASURE_STYLE,
|
|
759
|
-
role: "menu",
|
|
876
|
+
role: pane === "model" ? "group" : "menu",
|
|
760
877
|
"aria-label": t("menu.aria"),
|
|
761
878
|
"aria-busy": state.status === "loading" || busy,
|
|
762
879
|
children: [
|
|
@@ -800,6 +917,34 @@ window.__ModuleLoader__.load({
|
|
|
800
917
|
]
|
|
801
918
|
})] }),
|
|
802
919
|
pane === "model" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
920
|
+
showSearch && (0, react_jsx_runtime.jsxs)("div", {
|
|
921
|
+
className: ModelSelect_module_css_default.searchRow,
|
|
922
|
+
children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
923
|
+
ref: searchRef,
|
|
924
|
+
className: clsx(ModelSelect_module_css_default.search, query !== "" && ModelSelect_module_css_default.searchWithQuery),
|
|
925
|
+
type: "text",
|
|
926
|
+
role: "searchbox",
|
|
927
|
+
"aria-label": t("search.placeholder"),
|
|
928
|
+
"aria-controls": `${id}-models`,
|
|
929
|
+
"aria-activedescendant": activeModelIndex < 0 ? void 0 : `${id}-model-${activeModelIndex}`,
|
|
930
|
+
placeholder: t("search.placeholder"),
|
|
931
|
+
value: query,
|
|
932
|
+
readOnly: busy,
|
|
933
|
+
onChange: (event) => {
|
|
934
|
+
changeQuery(event.target.value);
|
|
935
|
+
}
|
|
936
|
+
}), query !== "" && (0, react_jsx_runtime.jsx)("button", {
|
|
937
|
+
type: "button",
|
|
938
|
+
className: ModelSelect_module_css_default.searchClear,
|
|
939
|
+
"aria-label": t("search.clear"),
|
|
940
|
+
disabled: busy,
|
|
941
|
+
onClick: () => {
|
|
942
|
+
changeQuery("");
|
|
943
|
+
searchRef.current?.focus();
|
|
944
|
+
},
|
|
945
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseFillRegular, {})
|
|
946
|
+
})]
|
|
947
|
+
}),
|
|
803
948
|
state.status === "loading" && (0, react_jsx_runtime.jsx)("div", {
|
|
804
949
|
className: ModelSelect_module_css_default.status,
|
|
805
950
|
children: t("status.loading")
|
|
@@ -826,25 +971,34 @@ window.__ModuleLoader__.load({
|
|
|
826
971
|
})]
|
|
827
972
|
}, failure.id)),
|
|
828
973
|
(0, react_jsx_runtime.jsx)("div", {
|
|
974
|
+
ref: groupsRef,
|
|
975
|
+
id: `${id}-models`,
|
|
829
976
|
className: clsx(ModelSelect_module_css_default.groups, "scrollable"),
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
children:
|
|
837
|
-
|
|
838
|
-
id: headingId,
|
|
839
|
-
children: group.id === "deepseek-account" ? t("provider.account") : group.name
|
|
840
|
-
}), group.models.map((model) => {
|
|
977
|
+
role: "menu",
|
|
978
|
+
"aria-label": t("menu.model"),
|
|
979
|
+
hidden: filteredGroups.length === 0,
|
|
980
|
+
children: filteredGroups.map((group) => {
|
|
981
|
+
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuGroup, {
|
|
982
|
+
label: group.id === "deepseek-account" ? t("provider.account") : group.name,
|
|
983
|
+
children: group.models.map((model) => {
|
|
984
|
+
const index = modelIndex++;
|
|
841
985
|
const selected = state.current?.provider === group.id && state.current.model === model.id;
|
|
842
986
|
return (0, react_jsx_runtime.jsxs)("button", {
|
|
843
987
|
ref: itemRef(),
|
|
844
988
|
type: "button",
|
|
845
989
|
role: "menuitemradio",
|
|
846
990
|
"aria-checked": selected,
|
|
847
|
-
|
|
991
|
+
id: `${id}-model-${index}`,
|
|
992
|
+
tabIndex: showSearch ? -1 : 0,
|
|
993
|
+
onFocus: () => {
|
|
994
|
+
setHighlightedIndex(index);
|
|
995
|
+
},
|
|
996
|
+
"data-highlighted": index === activeModelIndex ? "" : void 0,
|
|
997
|
+
className: clsx(ModelSelect_module_css_default.option, ModelSelect_module_css_default.modelOption, selected && ModelSelect_module_css_default.selected, index === activeModelIndex && ModelSelect_module_css_default.optionActive),
|
|
998
|
+
onMouseMove: busy || index === activeModelIndex ? void 0 : () => {
|
|
999
|
+
if (showSearch) setHighlightedIndex(index);
|
|
1000
|
+
else itemRefs.current[index]?.focus();
|
|
1001
|
+
},
|
|
848
1002
|
title: model.name,
|
|
849
1003
|
disabled: busy,
|
|
850
1004
|
onClick: () => {
|
|
@@ -864,13 +1018,14 @@ window.__ModuleLoader__.load({
|
|
|
864
1018
|
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
|
|
865
1019
|
})]
|
|
866
1020
|
}, model.id);
|
|
867
|
-
})
|
|
1021
|
+
})
|
|
868
1022
|
}, group.id);
|
|
869
1023
|
})
|
|
870
1024
|
}),
|
|
871
|
-
state.status === "ready" &&
|
|
1025
|
+
state.status === "ready" && filteredGroups.length === 0 && (0, react_jsx_runtime.jsx)("div", {
|
|
872
1026
|
className: ModelSelect_module_css_default.empty,
|
|
873
|
-
|
|
1027
|
+
role: "status",
|
|
1028
|
+
children: t(choices.length === 0 ? "empty.models" : "search.empty")
|
|
874
1029
|
})
|
|
875
1030
|
] }),
|
|
876
1031
|
pane === "effort" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [state.error !== null && lastActionRef.current === "load" && (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -935,8 +1090,6 @@ window.__ModuleLoader__.load({
|
|
|
935
1090
|
"command.label": "模型",
|
|
936
1091
|
"command.description": "选择本会话使用的模型",
|
|
937
1092
|
"option.loadError": "目录加载失败:{message}",
|
|
938
|
-
"option.deepseekV4Flash.description": "快速、高效且经济;适合目标明确、常规或并行任务。",
|
|
939
|
-
"option.deepseekV4Pro.description": "更强的自主编码、知识与复杂推理能力;适合复杂或质量优先的任务,但成本更高。",
|
|
940
1093
|
"trigger.fallback": "请选择模型",
|
|
941
1094
|
"trigger.loading": "正在加载模型…",
|
|
942
1095
|
"trigger.selectAria": "请选择模型",
|
|
@@ -951,6 +1104,9 @@ window.__ModuleLoader__.load({
|
|
|
951
1104
|
"error.sessionInUse": "当前会话已被占用,可能是其他正在运行的 DSH 导致的(如其他 dsh web、桌面端),请退出其他正在运行的 DSH 后重试。",
|
|
952
1105
|
"action.reload": "重新加载",
|
|
953
1106
|
"warning.groupLoad": "{name} 加载失败:{message}",
|
|
1107
|
+
"search.placeholder": "搜索模型…",
|
|
1108
|
+
"search.clear": "清除搜索",
|
|
1109
|
+
"search.empty": "没有匹配的模型。",
|
|
954
1110
|
"empty.models": "没有可用的模型。",
|
|
955
1111
|
"empty.efforts": "当前模型未提供推理等级。"
|
|
956
1112
|
};
|
|
@@ -960,8 +1116,6 @@ window.__ModuleLoader__.load({
|
|
|
960
1116
|
"command.label": "Model",
|
|
961
1117
|
"command.description": "Select the model for this conversation",
|
|
962
1118
|
"option.loadError": "Catalog failed to load: {message}",
|
|
963
|
-
"option.deepseekV4Flash.description": "Fast, efficient, and economical; suited to focused, routine, or parallel tasks.",
|
|
964
|
-
"option.deepseekV4Pro.description": "Stronger agentic coding, knowledge, and difficult reasoning; suited to complex or quality-critical tasks at higher cost.",
|
|
965
1119
|
"trigger.fallback": "Select model",
|
|
966
1120
|
"trigger.loading": "Loading models…",
|
|
967
1121
|
"trigger.selectAria": "Select model",
|
|
@@ -976,6 +1130,9 @@ window.__ModuleLoader__.load({
|
|
|
976
1130
|
"error.sessionInUse": "This session is already in use, possibly by another running DSH instance (such as dsh web or the desktop app). Quit other running DSH instances and try again.",
|
|
977
1131
|
"action.reload": "Reload",
|
|
978
1132
|
"warning.groupLoad": "{name} failed to load: {message}",
|
|
1133
|
+
"search.placeholder": "Search models…",
|
|
1134
|
+
"search.clear": "Clear search",
|
|
1135
|
+
"search.empty": "No matching models.",
|
|
979
1136
|
"empty.models": "No models available.",
|
|
980
1137
|
"empty.efforts": "This model provides no reasoning effort levels."
|
|
981
1138
|
};
|
|
@@ -985,30 +1142,20 @@ window.__ModuleLoader__.load({
|
|
|
985
1142
|
function rowId(providerId, modelId) {
|
|
986
1143
|
return `${providerId}/${modelId}`;
|
|
987
1144
|
}
|
|
988
|
-
const BUILTIN_DESCRIPTION_KEYS = {
|
|
989
|
-
"deepseek-account/deepseek-v4-flash": "option.deepseekV4Flash.description",
|
|
990
|
-
"deepseek-account/deepseek-v4-pro": "option.deepseekV4Pro.description",
|
|
991
|
-
"deepseek-official/deepseek-v4-flash": "option.deepseekV4Flash.description",
|
|
992
|
-
"deepseek-official/deepseek-v4-pro": "option.deepseekV4Pro.description"
|
|
993
|
-
};
|
|
994
|
-
function descriptionOf(providerId, model, t) {
|
|
995
|
-
const key = BUILTIN_DESCRIPTION_KEYS[rowId(providerId, model.id)];
|
|
996
|
-
return key !== void 0 && model.description === en[key] ? t(key) : model.description;
|
|
997
|
-
}
|
|
998
1145
|
/** Flatten the directory into popup rows; failure rows are listed for visibility but never selectable. */
|
|
999
1146
|
function optionsOf(directory, t) {
|
|
1000
1147
|
const rows = [];
|
|
1001
|
-
for (const group of directory.groups) {
|
|
1148
|
+
for (const group of orderModelProviders(directory.groups)) {
|
|
1002
1149
|
const name = group.id === "deepseek-account" ? t("provider.account") : group.name;
|
|
1003
|
-
for (const model of group.models) {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1150
|
+
for (const model of group.models) rows.push({
|
|
1151
|
+
id: rowId(group.id, model.id),
|
|
1152
|
+
label: model.name,
|
|
1153
|
+
group: {
|
|
1154
|
+
name: group.id,
|
|
1155
|
+
label: name
|
|
1156
|
+
},
|
|
1157
|
+
...directory.current !== null && directory.current.provider === group.id && directory.current.model === model.id ? { active: true } : {}
|
|
1158
|
+
});
|
|
1012
1159
|
}
|
|
1013
1160
|
for (const failure of directory.failures) rows.push({
|
|
1014
1161
|
id: `failure/${failure.id}`,
|
|
@@ -1071,6 +1218,12 @@ window.__ModuleLoader__.load({
|
|
|
1071
1218
|
available: (session) => sessions.subagentAddress(session.sessionId) === void 0,
|
|
1072
1219
|
ui: {
|
|
1073
1220
|
kind: "popupSelect",
|
|
1221
|
+
searchMode: "fuzzy-label",
|
|
1222
|
+
searchLabels: () => ({
|
|
1223
|
+
placeholder: t("search.placeholder"),
|
|
1224
|
+
empty: t("empty.models"),
|
|
1225
|
+
noResults: t("search.empty")
|
|
1226
|
+
}),
|
|
1074
1227
|
options: async (session) => {
|
|
1075
1228
|
if (sessions.subagentAddress(session.sessionId) !== void 0) throw new Error("model selection is unavailable for addressed subagent sessions");
|
|
1076
1229
|
return optionsOf(await models.directoryFor(session.sessionId).load(), t);
|
|
@@ -13,8 +13,6 @@ export declare const zh: {
|
|
|
13
13
|
'command.label': string;
|
|
14
14
|
'command.description': string;
|
|
15
15
|
'option.loadError': string;
|
|
16
|
-
'option.deepseekV4Flash.description': string;
|
|
17
|
-
'option.deepseekV4Pro.description': string;
|
|
18
16
|
'trigger.fallback': string;
|
|
19
17
|
'trigger.loading': string;
|
|
20
18
|
'trigger.selectAria': string;
|
|
@@ -29,6 +27,9 @@ export declare const zh: {
|
|
|
29
27
|
'error.sessionInUse': string;
|
|
30
28
|
'action.reload': string;
|
|
31
29
|
'warning.groupLoad': string;
|
|
30
|
+
'search.placeholder': string;
|
|
31
|
+
'search.clear': string;
|
|
32
|
+
'search.empty': string;
|
|
32
33
|
'empty.models': string;
|
|
33
34
|
'empty.efforts': string;
|
|
34
35
|
};
|
|
@@ -40,8 +41,6 @@ export declare const en: {
|
|
|
40
41
|
'command.label': string;
|
|
41
42
|
'command.description': string;
|
|
42
43
|
'option.loadError': string;
|
|
43
|
-
'option.deepseekV4Flash.description': string;
|
|
44
|
-
'option.deepseekV4Pro.description': string;
|
|
45
44
|
'trigger.fallback': string;
|
|
46
45
|
'trigger.loading': string;
|
|
47
46
|
'trigger.selectAria': string;
|
|
@@ -56,6 +55,9 @@ export declare const en: {
|
|
|
56
55
|
'error.sessionInUse': string;
|
|
57
56
|
'action.reload': string;
|
|
58
57
|
'warning.groupLoad': string;
|
|
58
|
+
'search.placeholder': string;
|
|
59
|
+
'search.clear': string;
|
|
60
|
+
'search.empty': string;
|
|
59
61
|
'empty.models': string;
|
|
60
62
|
'empty.efforts': string;
|
|
61
63
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Shared provider display order for the composer and command model pickers. */
|
|
2
|
+
/**
|
|
3
|
+
* Put the account and official providers first, preserving every other relative order.
|
|
4
|
+
* @param groups - Provider groups in catalog order.
|
|
5
|
+
* @returns a sorted copy; model order within each group is unchanged.
|
|
6
|
+
*/
|
|
7
|
+
export declare function orderModelProviders<T extends {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
}>(groups: readonly T[]): T[];
|
|
10
|
+
//# sourceMappingURL=provider-order.d.ts.map
|
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.2.0-rc.
|
|
4
|
+
"version": "0.2.0-rc.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -46,23 +46,23 @@
|
|
|
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-
|
|
50
|
-
"@deepseek-ai/dsh-
|
|
51
|
-
"@deepseek-ai/dsh-
|
|
52
|
-
"@deepseek-ai/dsh-client-
|
|
53
|
-
"@deepseek-ai/dsh-client-
|
|
54
|
-
"@deepseek-ai/dsh-client-ui-
|
|
55
|
-
"@deepseek-ai/dsh-client-ui-
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.1",
|
|
49
|
+
"@deepseek-ai/dsh-api-session-controller": "0.2.0-rc.2",
|
|
50
|
+
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
|
|
51
|
+
"@deepseek-ai/dsh-api-remotes": "0.2.0-rc.2",
|
|
52
|
+
"@deepseek-ai/dsh-client-ui-commands": "0.2.0-rc.2",
|
|
53
|
+
"@deepseek-ai/dsh-client-test-runtime": "0.2.0-rc.2",
|
|
54
|
+
"@deepseek-ai/dsh-client-ui-input-trigger": "0.2.0-rc.2",
|
|
55
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2",
|
|
58
57
|
"@deepseek-ai/cordis": "~4.0.4",
|
|
59
|
-
"@deepseek-ai/dsh-client-store": "0.2.0-rc.
|
|
60
|
-
"@deepseek-ai/dsh-
|
|
61
|
-
"@deepseek-ai/dsh-
|
|
62
|
-
"@deepseek-ai/dsh-
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-
|
|
65
|
-
"@deepseek-ai/dsh-client-
|
|
58
|
+
"@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-session": "0.2.0-rc.2",
|
|
62
|
+
"@deepseek-ai/dsh-util-values": "0.2.0-rc.2",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-session": "0.2.0-rc.2",
|
|
65
|
+
"@deepseek-ai/dsh-client-product-analytics": "0.2.0-rc.2"
|
|
66
66
|
},
|
|
67
67
|
"files": [
|
|
68
68
|
"lib/index.js",
|