@deepseek-ai/dsh-client-ui-model-selection 0.1.3-alpha.2 → 0.1.5-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
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
- README.md: 0ad5d6ee21dd47757247617d0ca2579144ac9611
6
- README.zh.md: 9c0b72a1e83785fc0bdaa62ec9bd35df10353ce1
5
+ README.md: b050f1fa7e680b7669e208d88851754aa72f09e2
6
+ README.zh.md: 85b4d44a1beb45a5a11fa946d2f36d83f9201e29
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- This package provides model selection in the Web GUI: the `/model` popup command and the composer's model seat, both over one per-session directory of provider-grouped models. Choosing a model submits the complete selection provider, model, and reasoning effort which the Host snapshots at the next prompt-assembly boundary, so the following request uses it while a running step keeps its assembled selection. The composer seat shows a two-level Model/Effort menu: models stay provider-grouped, and the selected exact model supplies its adapter-owned effort names and default. When the Host reports that no adapter serves the session's route, the composer input goes inert until a route becomes available.
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 no adapter can serve the session's route, the composer remains disabled until routing becomes available.
13
13
 
14
14
  ## Table of Contents
15
15
 
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- 本包提供 Web GUI 的模型选择:`/model` 弹窗命令与 composer 模型位,两者共用一份按提供方分组的会话级目录。选择模型会提交完整选择——提供方、模型与推理强度——宿主在下一次提示词组装边界对其快照,因此后续请求采用该选择,而运行中的步骤保留已组装选择。composer 位显示两级 Model/Effort 菜单:模型按提供方分组,所选具体模型提供其适配器持有的推理强度名称与默认值。当宿主报告没有适配器服务该会话的路由时,composer 输入停用,直到路由恢复可用。
12
+ Web GUI 允许用户通过 `/model` 弹窗或 composer 模型控件切换既有会话使用的模型与推理强度。两个界面呈现同一组按提供方分组的选择;所选模型决定可用的推理强度名称与默认值。完整选择从下一次请求开始生效;运行中的步骤保留其启动时的模型与推理强度。如果没有适配器可以服务会话路由,composer 会保持停用,直至路由恢复可用。
13
13
 
14
14
  ## 目录
15
15
 
package/lib/client.js CHANGED
@@ -8,6 +8,7 @@ window.__ModuleLoader__.load({
8
8
  let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
10
  let react = require("react");
11
+ let react_dom = require("react-dom");
11
12
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
12
13
  //#region lib/types/client/catalog.js
13
14
  /** One Host-generation model catalog shared by every Session selector. */
@@ -341,7 +342,7 @@ window.__ModuleLoader__.load({
341
342
  }
342
343
  //#endregion
343
344
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-model-selection/src/client/ModelSelect.module.css.mjs
344
- 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;border-radius:24px;outline:none;align-items:center;gap:4px;padding:0 4px 0 8px;font-size:13px;font-weight:500;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-alias-border-l3)}._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{color:var(--dsw-alias-label-caption);flex:none}._7KE1Ra_chevron{color:var(--dsw-alias-label-caption);flex:none;transition:transform .12s}._7KE1Ra_chevronOpen{transform:rotate(180deg)}._7KE1Ra_menu{z-index:20;background:var(--dsw-specific-menu);--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;border-radius:20px;flex-direction:column;padding:4px;display:flex;position:absolute;bottom:calc(100% + 8px);right:0;overflow:hidden}._7KE1Ra_status,._7KE1Ra_empty{color:var(--dsw-alias-label-tertiary);padding:10px;font-size:13px;line-height:20px}._7KE1Ra_error,._7KE1Ra_warning{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);border-radius:8px;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:4px;padding:7px 8px;font-size:12px;line-height:18px;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:4px}._7KE1Ra_groupTitle{z-index:1;background:var(--dsw-specific-menu);color:var(--dsw-alias-label-tertiary);padding:5px 8px 3px;font-size:12px;font-weight:500;line-height:18px;position:sticky;top:0}._7KE1Ra_option{box-sizing:border-box;width:auto;min-width:100%;min-height:38px;color:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;outline:none;align-items:center;gap:8px;padding:6px 8px;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:14px;font-weight:500;line-height:20px;overflow:hidden}._7KE1Ra_check{color:var(--dsw-alias-label-primary);flex:0 0 18px;place-items:center;display:grid}._7KE1Ra_cell{box-sizing:border-box;width:auto;min-width:100%;height:40px;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;background:0 0;border:none;border-radius:10px;align-items:center;gap:8px;padding:0 10px;font-size:14px;line-height:22px;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{color:var(--dsw-alias-label-tertiary);flex:none}";
345
+ 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;border-radius:24px;outline:none;align-items:center;gap:4px;padding:0 4px 0 8px;font-size:13px;font-weight:500;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-alias-border-l3)}._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{flex:none;display:none}@container (width<=360px){._7KE1Ra_triggerIcon{display:block}._7KE1Ra_triggerLabel,._7KE1Ra_triggerEffort{display:none}}._7KE1Ra_chevron{color:var(--dsw-alias-label-caption);flex:none;transition:transform .12s}._7KE1Ra_chevronOpen{transform:rotate(180deg)}._7KE1Ra_menu{z-index:1100;background:var(--dsw-specific-menu);--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;border-radius:20px;flex-direction:column;padding:4px;display:flex;position:fixed;overflow:hidden}._7KE1Ra_status,._7KE1Ra_empty{color:var(--dsw-alias-label-tertiary);padding:10px;font-size:13px;line-height:20px}._7KE1Ra_error,._7KE1Ra_warning{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);border-radius:8px;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:4px;padding:7px 8px;font-size:12px;line-height:18px;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:4px}._7KE1Ra_groupTitle{z-index:1;background:var(--dsw-specific-menu);color:var(--dsw-alias-label-tertiary);padding:5px 8px 3px;font-size:12px;font-weight:500;line-height:18px;position:sticky;top:0}._7KE1Ra_option{box-sizing:border-box;width:auto;min-width:100%;min-height:38px;color:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;outline:none;align-items:center;gap:8px;padding:6px 8px;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:14px;font-weight:500;line-height:20px;overflow:hidden}._7KE1Ra_check{color:var(--dsw-alias-label-primary);flex:0 0 18px;place-items:center;display:grid}._7KE1Ra_cell{box-sizing:border-box;width:auto;min-width:100%;height:40px;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;background:0 0;border:none;border-radius:10px;align-items:center;gap:8px;padding:0 10px;font-size:14px;line-height:22px;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{color:var(--dsw-alias-label-tertiary);flex:none}";
345
346
  const tagId = "@deepseek-ai/dsh-client-ui-model-selection/ModelSelect.module.css";
346
347
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
347
348
  const tag = document.createElement("style");
@@ -373,6 +374,7 @@ window.__ModuleLoader__.load({
373
374
  "status": "_7KE1Ra_status",
374
375
  "trigger": "_7KE1Ra_trigger",
375
376
  "triggerEffort": "_7KE1Ra_triggerEffort",
377
+ "triggerIcon": "_7KE1Ra_triggerIcon",
376
378
  "triggerLabel": "_7KE1Ra_triggerLabel",
377
379
  "warning": "_7KE1Ra_warning"
378
380
  };
@@ -391,6 +393,12 @@ window.__ModuleLoader__.load({
391
393
  * announces through the shared transient Toast anchored to the composer
392
394
  * card; the in-menu strip with Retry remains the catalog-load surface.
393
395
  */
396
+ /** Unplaced portal card: hidden but laid out at a fixed origin so offsetWidth/offsetHeight are real (Menu primitive's measure pass). */
397
+ const MEASURE_STYLE = {
398
+ visibility: "hidden",
399
+ left: 0,
400
+ top: 0
401
+ };
394
402
  /**
395
403
  * Render the composer model seat.
396
404
  * @param props - owner share (locked) + injected face (shared directory
@@ -406,6 +414,8 @@ window.__ModuleLoader__.load({
406
414
  const toastSeq = (0, react.useRef)(0);
407
415
  const rootRef = (0, react.useRef)(null);
408
416
  const triggerRef = (0, react.useRef)(null);
417
+ const menuRef = (0, react.useRef)(null);
418
+ const [menuPos, setMenuPos] = (0, react.useState)(null);
409
419
  const itemRefs = (0, react.useRef)([]);
410
420
  const id = (0, react.useId)();
411
421
  const choices = (0, react.useMemo)(() => state.groups.flatMap((group) => group.models.map((model) => ({
@@ -438,13 +448,48 @@ window.__ModuleLoader__.load({
438
448
  (0, react.useEffect)(() => {
439
449
  if (!open) return;
440
450
  const closeOutside = (event) => {
441
- if (!rootRef.current?.contains(event.target)) setOpen(false);
451
+ if (rootRef.current?.contains(event.target) === true) return;
452
+ if (menuRef.current?.contains(event.target) === true) return;
453
+ setOpen(false);
442
454
  };
443
455
  document.addEventListener("mousedown", closeOutside);
444
456
  return () => {
445
457
  document.removeEventListener("mousedown", closeOutside);
446
458
  };
447
459
  }, [open]);
460
+ (0, react.useLayoutEffect)(() => {
461
+ if (!open) {
462
+ setMenuPos(null);
463
+ return;
464
+ }
465
+ const place = () => {
466
+ /* v8 ignore next 2 -- the trigger ref is attached whenever the menu is open. */
467
+ const rect = triggerRef.current?.getBoundingClientRect();
468
+ if (rect === void 0) return;
469
+ const MARGIN = 12;
470
+ const lw = menuRef.current?.offsetWidth ?? 0;
471
+ const lh = menuRef.current?.offsetHeight ?? 0;
472
+ let x = rect.right - lw;
473
+ let y = rect.top - 8 - lh;
474
+ if (lw > 0) x = Math.min(Math.max(x, MARGIN), window.innerWidth - lw - MARGIN);
475
+ if (lh > 0) y = Math.min(Math.max(y, MARGIN), window.innerHeight - lh - MARGIN);
476
+ setMenuPos({
477
+ left: x,
478
+ top: y
479
+ });
480
+ };
481
+ place();
482
+ window.addEventListener("scroll", place, true);
483
+ window.addEventListener("resize", place);
484
+ return () => {
485
+ window.removeEventListener("scroll", place, true);
486
+ window.removeEventListener("resize", place);
487
+ };
488
+ }, [
489
+ open,
490
+ pane,
491
+ state
492
+ ]);
448
493
  if (!available) return null;
449
494
  const show = () => {
450
495
  setPane("root");
@@ -478,7 +523,7 @@ window.__ModuleLoader__.load({
478
523
  }
479
524
  };
480
525
  const onBlur = (event) => {
481
- if (event.relatedTarget instanceof Node && rootRef.current?.contains(event.relatedTarget)) return;
526
+ if (event.relatedTarget instanceof Node && (rootRef.current?.contains(event.relatedTarget) === true || menuRef.current?.contains(event.relatedTarget) === true)) return;
482
527
  close();
483
528
  };
484
529
  const settleSelection = (accepted) => {
@@ -553,6 +598,10 @@ window.__ModuleLoader__.load({
553
598
  else show();
554
599
  },
555
600
  children: [
601
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDataOutline16, {
602
+ className: ModelSelect_module_css_default.triggerIcon,
603
+ size: 16
604
+ }),
556
605
  (0, react_jsx_runtime.jsx)("span", {
557
606
  className: ModelSelect_module_css_default.triggerLabel,
558
607
  children: modelLabel
@@ -564,9 +613,11 @@ window.__ModuleLoader__.load({
564
613
  (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: clsx(ModelSelect_module_css_default.chevron, open && ModelSelect_module_css_default.chevronOpen) })
565
614
  ]
566
615
  }),
567
- open && (0, react_jsx_runtime.jsxs)("div", {
616
+ open && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)("div", {
617
+ ref: menuRef,
568
618
  id: `${id}-menu`,
569
619
  className: ModelSelect_module_css_default.menu,
620
+ style: menuPos ?? MEASURE_STYLE,
570
621
  role: "menu",
571
622
  "aria-label": t("menu.aria"),
572
623
  "aria-busy": state.status === "loading" || busy,
@@ -717,7 +768,7 @@ window.__ModuleLoader__.load({
717
768
  })]
718
769
  }, level.key))] })
719
770
  ]
720
- }),
771
+ }), document.body),
721
772
  toast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
722
773
  text: toast.text,
723
774
  icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {}),
@@ -852,7 +903,7 @@ window.__ModuleLoader__.load({
852
903
  const sessions = scope.sessions;
853
904
  scope.effect(() => command.register({
854
905
  name: "model",
855
- description: t("command.description"),
906
+ description: () => t("command.description"),
856
907
  available: (session) => sessions.subagentAddress(session.sessionId) === void 0,
857
908
  ui: {
858
909
  kind: "popupSelect",
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.3-alpha.2",
4
+ "version": "0.1.5-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -43,21 +43,23 @@
43
43
  "devDependencies": {
44
44
  "@types/react": "~18.3.1",
45
45
  "react": "^18.2.0",
46
- "@deepseek-ai/dsh-api-remotes": "^0.1.3-alpha.2",
47
- "@deepseek-ai/dsh-api-session-controller": "^0.1.3-alpha.2",
48
- "@deepseek-ai/dsh-client-locale": "^0.1.3-alpha.2",
49
- "@deepseek-ai/dsh-client-test-runtime": "^0.1.3-alpha.2",
50
- "@deepseek-ai/dsh-client-ui-commands": "^0.1.3-alpha.2",
51
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.3-alpha.2",
52
- "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.3-alpha.2",
53
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.3-alpha.2",
54
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.3-alpha.2",
55
- "@deepseek-ai/dsh-typert-protocol": "^0.1.3-alpha.2",
46
+ "react-dom": "^18.2.0",
47
+ "@types/react-dom": "~18.3.0",
48
+ "@deepseek-ai/dsh-api-remotes": "^0.1.5-alpha.1",
49
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.5-alpha.1",
50
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-alpha.1",
51
+ "@deepseek-ai/dsh-client-test-runtime": "^0.1.5-alpha.1",
52
+ "@deepseek-ai/dsh-client-ui-commands": "^0.1.5-alpha.1",
53
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-alpha.1",
54
+ "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.5-alpha.1",
55
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-alpha.1",
56
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-alpha.1",
57
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.5-alpha.1",
56
58
  "@deepseek-ai/cordis": "^4.0.2",
57
- "@deepseek-ai/dsh-session": "^0.1.3-alpha.2",
58
- "@deepseek-ai/dsh-client-store": "^0.1.3-alpha.2",
59
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.3-alpha.2",
60
- "@deepseek-ai/dsh-client-ui-session": "^0.1.3-alpha.2"
59
+ "@deepseek-ai/dsh-client-store": "^0.1.5-alpha.1",
60
+ "@deepseek-ai/dsh-session": "^0.1.5-alpha.1",
61
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-alpha.1",
62
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.5-alpha.1"
61
63
  },
62
64
  "files": [
63
65
  "lib/index.js",