@bittersmilezzz/dsh-model-selector 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -760,9 +760,9 @@ window.__ModuleLoader__.load({
760
760
  * reused from @deepseek-ai/dsh-client-ui-primitives at runtime; colors
761
761
  * come from `--dsw-*` tokens in the injected stylesheet.
762
762
  */
763
- const IconChevronDown = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {});
764
- const IconCheck = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline14, {});
765
- const IconClear = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseFill14, {});
763
+ const IconChevronDown = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { size: 14 });
764
+ const IconCheck = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 14 });
765
+ const IconClear = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseFillRegular, { size: 14 });
766
766
  /**
767
767
  * How long a successfully loaded directory snapshot is trusted before the
768
768
  * menu re-fetches it over RPC. The snapshot lives in the per-session store,
@@ -877,7 +877,7 @@ window.__ModuleLoader__.load({
877
877
  const current = state.current;
878
878
  if (current === null) throw new Error(t("empty.efforts"));
879
879
  const modelKeyAtCommit = dmsRowKey(current.provider, current.model);
880
- if (!await new Promise((resolve, reject) => {
880
+ const result = await new Promise((resolve, reject) => {
881
881
  let settled = false;
882
882
  const timer = window.setTimeout(() => {
883
883
  if (settled) return;
@@ -890,11 +890,11 @@ window.__ModuleLoader__.load({
890
890
  provider: current.provider,
891
891
  model: current.model,
892
892
  reasoningEffort: next
893
- }).then((accepted) => {
893
+ }).then((result) => {
894
894
  if (settled) {
895
895
  const latest = stateRef.current.current;
896
896
  const sameModel = latest !== null && dmsRowKey(latest.provider, latest.model) === modelKeyAtCommit;
897
- if (accepted && mountedRef.current && dmsShouldAdoptLateSuccess(committedRef.current, previous, draggingRef.current, committingRef.current, commitEpochRef.current, epochAtCommit, sameModel)) {
897
+ if (result?.ok === true && mountedRef.current && dmsShouldAdoptLateSuccess(committedRef.current, previous, draggingRef.current, committingRef.current, commitEpochRef.current, epochAtCommit, sameModel)) {
898
898
  committedRef.current = next;
899
899
  previewRef.current = index;
900
900
  setEffort(next);
@@ -906,7 +906,7 @@ window.__ModuleLoader__.load({
906
906
  settled = true;
907
907
  window.clearTimeout(timer);
908
908
  commitTimerRef.current = null;
909
- resolve(accepted);
909
+ resolve(result);
910
910
  }, (cause) => {
911
911
  if (settled) return;
912
912
  settled = true;
@@ -914,7 +914,11 @@ window.__ModuleLoader__.load({
914
914
  commitTimerRef.current = null;
915
915
  reject(cause);
916
916
  });
917
- })) throw new Error(t("effort.failed"));
917
+ });
918
+ if (result === void 0 || !result.ok) {
919
+ if (result !== void 0) console.warn("[dsh-model-selector] effort select failed:", result.error);
920
+ throw new Error(t("effort.failed"));
921
+ }
918
922
  if (mountedRef.current) {
919
923
  committedRef.current = next;
920
924
  previewRef.current = index;
@@ -922,18 +926,22 @@ window.__ModuleLoader__.load({
922
926
  setPreview(index);
923
927
  }
924
928
  } catch (cause) {
925
- onSelectFailure();
926
- const restore = Math.max(0, dmsEffortIndex(levels, previous));
927
- committedRef.current = previous;
928
- previewRef.current = restore;
929
- if (mountedRef.current) {
930
- setEffort(previous);
931
- setPreview(restore);
932
- setLocalError(cause instanceof Error ? cause.message : String(cause));
929
+ if (commitEpochRef.current === epochAtCommit) {
930
+ onSelectFailure();
931
+ const restore = Math.max(0, dmsEffortIndex(levels, previous));
932
+ committedRef.current = previous;
933
+ previewRef.current = restore;
934
+ if (mountedRef.current) {
935
+ setEffort(previous);
936
+ setPreview(restore);
937
+ setLocalError(cause instanceof Error ? cause.message : String(cause));
938
+ }
933
939
  }
934
940
  } finally {
935
- committingRef.current = false;
936
- if (mountedRef.current) setCommitting(false);
941
+ if (commitEpochRef.current === epochAtCommit) {
942
+ committingRef.current = false;
943
+ if (mountedRef.current) setCommitting(false);
944
+ }
937
945
  }
938
946
  }, [
939
947
  levels,
@@ -1049,7 +1057,7 @@ window.__ModuleLoader__.load({
1049
1057
  * 但 100 条命中里绝大多数命中区间不变——按值比较 start/end,值相同即跳过
1050
1058
  * 重渲染,搜索才不卡顿。
1051
1059
  */
1052
- const ModelOption = react.memo(function ModelOption({ group, model, showProvider, selected, busy, rowKey, active, searchMode, onRowFocus, nameHit, t, onChoose }) {
1060
+ const ModelOption = react.memo(function ModelOption({ group, model, showProvider, selected, busy, rowKey, active, searchMode, onRowFocus, nameHit, setsize, posinset, t, onChoose }) {
1053
1061
  const hit = nameHit === void 0 || nameHit === null || nameHit.start === nameHit.end ? null : [
1054
1062
  model.name.slice(0, nameHit.start),
1055
1063
  model.name.slice(nameHit.start, nameHit.end),
@@ -1060,6 +1068,9 @@ window.__ModuleLoader__.load({
1060
1068
  role: searchMode ? "option" : "menuitemradio",
1061
1069
  "aria-checked": searchMode ? void 0 : selected,
1062
1070
  "aria-selected": searchMode ? selected : void 0,
1071
+ id: searchMode ? `dms-opt-${encodeURIComponent(rowKey)}` : void 0,
1072
+ "aria-setsize": searchMode && setsize !== void 0 && setsize > posinset ? setsize : void 0,
1073
+ "aria-posinset": searchMode && posinset !== void 0 ? posinset : void 0,
1063
1074
  "data-row-key": rowKey,
1064
1075
  tabIndex: active ? 0 : -1,
1065
1076
  className: `dms-model-option${selected ? " dms-model-optionSelected" : ""}`,
@@ -1118,6 +1129,8 @@ window.__ModuleLoader__.load({
1118
1129
  if (prev.onRowFocus !== next.onRowFocus) return false;
1119
1130
  if (prev.t !== next.t) return false;
1120
1131
  if (prev.onChoose !== next.onChoose) return false;
1132
+ if (prev.setsize !== next.setsize) return false;
1133
+ if (prev.posinset !== next.posinset) return false;
1121
1134
  if (prev.nameHit === next.nameHit) return true;
1122
1135
  if (prev.nameHit === null || prev.nameHit === void 0) return false;
1123
1136
  if (next.nameHit === null || next.nameHit === void 0) return false;
@@ -1363,26 +1376,27 @@ window.__ModuleLoader__.load({
1363
1376
  const autoName = plan.effort === void 0 ? "" : target?.model.reasoning?.efforts.find((level) => level.id === plan.effort)?.name ?? plan.effort;
1364
1377
  (async () => {
1365
1378
  try {
1366
- if (!await new Promise((resolve, reject) => {
1379
+ const result = await new Promise((resolve, reject) => {
1367
1380
  let settled = false;
1368
1381
  const timer = window.setTimeout(() => {
1369
1382
  if (settled) return;
1370
1383
  settled = true;
1371
1384
  reject(new Error(t("notice.selectFailed")));
1372
1385
  }, EFFORT_COMMIT_TIMEOUT_MS);
1373
- select(full).then((ok) => {
1386
+ select(full).then((settledResult) => {
1374
1387
  if (settled) return;
1375
1388
  settled = true;
1376
1389
  window.clearTimeout(timer);
1377
- resolve(ok);
1390
+ resolve(settledResult);
1378
1391
  }, (cause) => {
1379
1392
  if (settled) return;
1380
1393
  settled = true;
1381
1394
  window.clearTimeout(timer);
1382
1395
  reject(cause);
1383
1396
  });
1384
- })) {
1385
- const message = directory.getSnapshot().error;
1397
+ });
1398
+ if (result === void 0 || !result.ok) {
1399
+ const message = result !== void 0 ? `${result.error.code}: ${result.error.message}` : directory.getSnapshot().error;
1386
1400
  showToast(message !== null ? t("error.action", { message }) : t("notice.selectFailed"));
1387
1401
  return;
1388
1402
  }
@@ -1472,7 +1486,7 @@ window.__ModuleLoader__.load({
1472
1486
  close();
1473
1487
  return;
1474
1488
  }
1475
- if (!document.hasFocus()) close();
1489
+ if (document.visibilityState === "hidden") close();
1476
1490
  };
1477
1491
  const trigger = dmsTriggerCopy({
1478
1492
  current: state.current,
@@ -1583,7 +1597,7 @@ window.__ModuleLoader__.load({
1583
1597
  placeholder: t("search.placeholder"),
1584
1598
  "aria-label": t("search.placeholder"),
1585
1599
  role: "combobox",
1586
- "aria-expanded": hits !== null,
1600
+ "aria-expanded": hits !== null && hits.total > 0,
1587
1601
  "aria-autocomplete": "list",
1588
1602
  "aria-controls": emptyState === null ? `${id}-groups` : void 0,
1589
1603
  autoComplete: "off",
@@ -1635,7 +1649,7 @@ window.__ModuleLoader__.load({
1635
1649
  role: hits !== null ? "listbox" : "menu",
1636
1650
  "aria-busy": state.status === "loading" || busy,
1637
1651
  "aria-label": t("menu.aria"),
1638
- children: hits !== null ? hits.items.map((hit) => {
1652
+ children: hits !== null ? hits.items.map((hit, index) => {
1639
1653
  const rowKey = dmsRowKey(hit.group.id, hit.model.id);
1640
1654
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelOption, {
1641
1655
  group: hit.group,
@@ -1648,6 +1662,8 @@ window.__ModuleLoader__.load({
1648
1662
  searchMode: true,
1649
1663
  onRowFocus,
1650
1664
  nameHit: hit.nameHit,
1665
+ setsize: hits.total,
1666
+ posinset: index + 1,
1651
1667
  t,
1652
1668
  onChoose: choose
1653
1669
  }, rowKey);
@@ -1737,7 +1753,7 @@ window.__ModuleLoader__.load({
1737
1753
  }) : null,
1738
1754
  toast !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
1739
1755
  text: toast.text,
1740
- icon: toast.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {}) : void 0,
1756
+ icon: toast.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, { size: 14 }) : void 0,
1741
1757
  anchor: rootRef.current?.closest("[data-composer-card]") ?? null,
1742
1758
  onDone: dismissToast
1743
1759
  }, toast.seq)
@@ -1859,9 +1875,20 @@ window.__ModuleLoader__.load({
1859
1875
  animation-name: dms-menu-in-below;
1860
1876
  }
1861
1877
 
1878
+ /* 菜单头部的「非列表」栏(loading / 空态 / 错误条 / 失败条 / 截断提示):
1879
+ 一律允许收缩(flex-shrink 1)且各自限高,让 .dms-groups 始终保住可滚动
1880
+ 的最小可视高度。2026-09-21 修复的 P0:向上弹时 useAnchoredMaxHeight 会把
1881
+ maxHeight 钳到「trigger 底缘 - 12px」(底缘固定、向上生长的语义),trigger
1882
+ 靠近视口顶部时该值很小;原先这些栏是 flex: 0 0 auto 完全不收缩,头部
1883
+ 40-100px 全部占完后 .dms-groups 的可视高度趋近 0,而 .dms-menu 是
1884
+ overflow:hidden —— 用户看到错误条却看不到也滚不到任何模型行。
1885
+ 现在给它们统一的上限(相对菜单高度)+ overflow,最坏情况是栏内自己滚动。 */
1862
1886
  .dms-status,
1863
1887
  .dms-empty {
1864
- flex: 0 0 auto;
1888
+ flex: 0 1 auto;
1889
+ min-height: 0;
1890
+ max-height: 40%;
1891
+ overflow-y: auto;
1865
1892
  padding: 10px;
1866
1893
  color: var(--dsw-alias-label-tertiary);
1867
1894
  font-size: 13px;
@@ -1869,7 +1896,10 @@ window.__ModuleLoader__.load({
1869
1896
  }
1870
1897
 
1871
1898
  .dms-more {
1872
- flex: 0 0 auto;
1899
+ flex: 0 1 auto;
1900
+ min-height: 0;
1901
+ max-height: 20%;
1902
+ overflow-y: auto;
1873
1903
  padding: 8px 12px;
1874
1904
  border-top: 1px solid var(--dsw-alias-border-l1, transparent);
1875
1905
  color: var(--dsw-alias-label-tertiary);
@@ -1880,15 +1910,18 @@ window.__ModuleLoader__.load({
1880
1910
  /* Provider-load failure strip: capped and scrollable so a long list of failed
1881
1911
  groups can never squeeze the model list out of the menu (E4). */
1882
1912
  .dms-failures {
1883
- flex: 0 0 auto;
1884
- max-height: 96px;
1913
+ flex: 0 1 auto;
1914
+ max-height: min(96px, 30%);
1885
1915
  min-height: 0;
1886
1916
  overflow-y: auto;
1887
1917
  }
1888
1918
 
1889
1919
  .dms-error,
1890
1920
  .dms-warning {
1891
- flex: 0 0 auto;
1921
+ flex: 0 1 auto;
1922
+ min-height: 0;
1923
+ max-height: 40%;
1924
+ overflow-y: auto;
1892
1925
  display: flex;
1893
1926
  align-items: flex-start;
1894
1927
  justify-content: space-between;
@@ -1963,7 +1996,10 @@ window.__ModuleLoader__.load({
1963
1996
  /* 图标组件只收 size/className,装饰性 aria-hidden 只能包一层 span;contents 让它不占布局。 */
1964
1997
  .dms-icon-slot { display: contents; }
1965
1998
 
1966
- .dms-groups { flex: 1 1 auto; min-height: 0; overflow-y: auto; contain: content; }
1999
+ /* 列表区:菜单里唯一的弹性主体。max-height 被 useAnchoredMaxHeight 钳得很小
2000
+ (trigger 靠近视口顶 + 向上弹)时,32px 保底让「至少见到一行模型」始终成立
2001
+ —— 头部各栏此时按自己的 max-height 百分比上限收缩(见 .dms-status 等)。 */
2002
+ .dms-groups { flex: 1 1 auto; min-height: 32px; overflow-y: auto; contain: content; }
1967
2003
  /* 空态占位:空态播报节点在 role=menu 容器外,用与 .dms-groups 同款弹性布局
1968
2004
  补回列表区本来的位置(不挤压其余栏,消息也不会贴到菜单顶部)。 */
1969
2005
  .dms-groupsFill { flex: 1 1 auto; min-height: 0; overflow-y: auto; contain: content; }
@@ -2421,10 +2457,7 @@ body:not([data-ds-dark-theme]) .dms-effort.is-dragging .dms-effort-knob {
2421
2457
  console.warn("[dsh-model-selector] directory.load failed:", error);
2422
2458
  });
2423
2459
  },
2424
- select: (selection) => available ? directory.select(selection).then(() => true, (error) => {
2425
- console.warn("[dsh-model-selector] select failed:", error);
2426
- return false;
2427
- }) : Promise.resolve(false)
2460
+ select: (selection) => available ? directory.select(selection) : Promise.resolve(void 0)
2428
2461
  };
2429
2462
  }
2430
2463
  }, ModelSelect));
@@ -2435,7 +2468,7 @@ body:not([data-ds-dark-theme]) .dms-effort.is-dragging .dms-effort-knob {
2435
2468
  exports.apply = apply;
2436
2469
  exports.inject = inject;
2437
2470
  return module.exports;
2438
- /* __DMS_SRC_HASH__ = "eead5888f44285e2028692c6971ab7d6d8cfeb16272176f9c2c392186c440c04" */ }
2471
+ /* __DMS_SRC_HASH__ = "86fa33a484beb4717e3ba62f62d7834bd0be9438ebf41565218cd94a6da91e19" */ }
2439
2472
  });
2440
2473
 
2441
2474
  //# sourceMappingURL=client.js.map