@lynn123411/dsh-model-submenu 0.3.11 → 0.3.12

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
@@ -254,7 +254,7 @@ window.__ModuleLoader__.load({
254
254
  }
255
255
  //#endregion
256
256
  //#region \0dsh-css:/home/runner/work/my-dsh/my-dsh/plugins/dsh-model-submenu/src/client/ModelOptions.module.css.mjs
257
- const css = ".TcCbqa_pickerHost{display:contents}.TcCbqa_sliderCell{flex:auto;align-self:stretch;align-items:center;min-width:96px;display:flex}.TcCbqa_slider{cursor:pointer;touch-action:none;-webkit-user-select:none;user-select:none;outline:none;flex:1;align-items:center;min-width:0;height:100%;display:flex}.TcCbqa_slider[data-busy]{cursor:default}.TcCbqa_sliderTrack{corner-shape:round;background:var(--dsw-alias-border-l3);border-radius:999px;width:100%;height:16px;position:relative}.TcCbqa_sliderFill{corner-shape:round;pointer-events:none;border-radius:999px;transition:width .16s cubic-bezier(.2,0,0,1);position:absolute;top:0;bottom:0;left:0}.TcCbqa_sliderTick{corner-shape:round;background:var(--dsw-alias-label-tertiary);pointer-events:none;border-radius:50%;width:4px;height:4px;margin:-2px 0 0 -2px;position:absolute;top:50%}.TcCbqa_sliderTick[data-on]{background:#00000047}.TcCbqa_sliderKnob{box-sizing:border-box;corner-shape:round;background:var(--dsw-alias-label-primary-foreground);width:20px;height:20px;box-shadow:var(--dsw-elevation-soft);pointer-events:none;border-radius:50%;margin:-10px 0 0 -10px;transition:left .16s cubic-bezier(.2,0,0,1);position:absolute;top:50%}.TcCbqa_slider[data-dragging] .TcCbqa_sliderKnob,.TcCbqa_slider[data-dragging] .TcCbqa_sliderFill{transition:none}@media (prefers-reduced-motion:reduce){.TcCbqa_sliderKnob,.TcCbqa_sliderFill{transition:none}}.TcCbqa_slider[data-default] .TcCbqa_sliderKnob{background:var(--dsw-alias-bg-layer-1);border:1.5px solid var(--dsw-alias-label-tertiary);box-shadow:none}.TcCbqa_slider[data-busy] .TcCbqa_sliderKnob{opacity:.6}.TcCbqa_slider:focus-visible .TcCbqa_sliderTrack{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:3px}.TcCbqa_rowBadge{flex:none;margin-inline-start:6px}.TcCbqa_row{align-items:center;gap:2px;display:flex}.TcCbqa_rowPick{box-sizing:border-box;width:100%;min-width:0;min-height:34px;color:inherit;text-align:left;cursor:pointer;border-radius:var(--dsw-radius-md);padding-inline:6px;background:0 0;border:none;outline:none;flex:1;align-items:center;gap:4px;padding:5px 7px;display:flex}.TcCbqa_rowPick:hover:not(:disabled),.TcCbqa_rowPick:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.TcCbqa_rowPick:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.TcCbqa_rowCopy{flex-direction:column;flex:1;min-width:0;display:flex}.TcCbqa_rowName{color:inherit;text-overflow:ellipsis;white-space:nowrap;font-size:11.5px;font-weight:400;line-height:17px;overflow:hidden}.TcCbqa_rowMeta{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px;overflow:hidden}.TcCbqa_check{color:var(--dsw-alias-label-primary);flex:0 0 15px;place-items:center;display:grid}.TcCbqa_starButton{color:var(--dsw-alias-label-tertiary);cursor:pointer;border-radius:var(--dsw-radius-sm);opacity:0;background:0 0;border:none;outline:none;flex:none;place-items:center;width:26px;height:26px;display:grid}.TcCbqa_starButton:focus-visible{outline:2px solid var(--dsw-alias-border-l3)}.TcCbqa_row:hover .TcCbqa_starButton,.TcCbqa_row:focus-within .TcCbqa_starButton{opacity:1}.TcCbqa_starButton[aria-pressed=true]{color:var(--dsw-alias-state-warn-primary);opacity:1}@media (hover:none){.TcCbqa_starButton{opacity:1}}.TcCbqa_star{flex:none}.TcCbqa_notice{color:var(--dsw-alias-label-tertiary);justify-content:space-between;align-items:flex-start;gap:8px;padding:6px 10px;font-size:12px;line-height:18px;display:flex}.TcCbqa_retry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-weight:600;text-decoration:underline}.TcCbqa_pickerDialog{gap:0;width:min(680px,100%);height:min(640px,80vh);padding:0}.TcCbqa_pickerContent,.TcCbqa_pickerContent>:last-child{flex-direction:column;flex:1;min-height:0;display:flex}.TcCbqa_pickerSearch{padding:0 20px 12px}.TcCbqa_pickerSearchInput{width:100%}.TcCbqa_pickerTabs{border-bottom:.5px solid var(--dsw-alias-border-l2);overscroll-behavior-x:contain;scrollbar-width:none;flex-wrap:nowrap;gap:2px;padding:0 16px;display:flex;overflow-x:auto}.TcCbqa_pickerTabs::-webkit-scrollbar{display:none}.TcCbqa_pickerTab{color:var(--dsw-alias-label-secondary);cursor:pointer;border-radius:var(--dsw-radius-sm);white-space:nowrap;background:0 0;border:none;outline:none;flex:none;padding:6px 10px;font-size:13px;line-height:18px}.TcCbqa_pickerTab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.TcCbqa_pickerTab[aria-selected=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);font-weight:600}.TcCbqa_pickerTab:focus-visible{outline:2px solid var(--dsw-alias-border-l3)}.TcCbqa_pickerList{flex:1;min-height:0;padding:8px 12px;overflow-y:auto}.TcCbqa_pickerFooter{border-top:.5px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);align-items:center;gap:6px;padding:10px 20px;font-size:12px;line-height:18px;display:flex}.TcCbqa_footerHint{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.TcCbqa_footerDot{color:var(--dsw-alias-label-dimmed)}";
257
+ const css = ".TcCbqa_pickerHost{display:contents}.TcCbqa_sliderCell{flex:auto;align-self:stretch;align-items:center;min-width:96px;display:flex}.TcCbqa_slider{cursor:pointer;touch-action:none;-webkit-user-select:none;user-select:none;outline:none;flex:1;align-items:center;min-width:0;height:100%;display:flex}.TcCbqa_slider[data-busy]{cursor:default}.TcCbqa_sliderTrack{corner-shape:round;background:var(--dsw-alias-border-l3);border-radius:999px;width:100%;height:16px;position:relative}.TcCbqa_sliderFill{corner-shape:round;pointer-events:none;border-radius:999px;transition:width .16s cubic-bezier(.2,0,0,1);position:absolute;top:0;bottom:0;left:0}.TcCbqa_sliderTick{corner-shape:round;background:var(--dsw-alias-label-tertiary);pointer-events:none;border-radius:50%;width:4px;height:4px;margin:-2px 0 0 -2px;position:absolute;top:50%}.TcCbqa_sliderTick[data-on]{background:#00000047}.TcCbqa_sliderKnob{box-sizing:border-box;corner-shape:round;background:var(--dsw-alias-label-primary-foreground);width:20px;height:20px;box-shadow:var(--dsw-elevation-soft);pointer-events:none;border-radius:50%;margin:-10px 0 0 -10px;transition:left .16s cubic-bezier(.2,0,0,1);position:absolute;top:50%}.TcCbqa_slider[data-dragging] .TcCbqa_sliderKnob,.TcCbqa_slider[data-dragging] .TcCbqa_sliderFill{transition:none}@media (prefers-reduced-motion:reduce){.TcCbqa_sliderKnob,.TcCbqa_sliderFill{transition:none}}.TcCbqa_slider[data-default] .TcCbqa_sliderKnob{background:var(--dsw-alias-bg-layer-1);border:1.5px solid var(--dsw-alias-label-tertiary);box-shadow:none}.TcCbqa_slider[data-busy] .TcCbqa_sliderKnob{opacity:.6}.TcCbqa_slider:focus-visible .TcCbqa_sliderTrack{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:3px}.TcCbqa_rowBadge{flex:none;margin-inline-start:6px}.TcCbqa_row{align-items:center;gap:2px;display:flex}.TcCbqa_rowPick{box-sizing:border-box;width:100%;min-width:0;min-height:34px;color:inherit;text-align:left;cursor:pointer;border-radius:var(--dsw-radius-md);padding-inline:6px;background:0 0;border:none;outline:none;flex:1;align-items:center;gap:4px;padding:5px 7px;display:flex}.TcCbqa_rowPick:hover:not(:disabled),.TcCbqa_rowPick:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.TcCbqa_rowPick:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.TcCbqa_rowCopy{flex-direction:column;flex:1;min-width:0;display:flex}.TcCbqa_rowName{color:inherit;text-overflow:ellipsis;white-space:nowrap;font-size:11.5px;font-weight:400;line-height:17px;overflow:hidden}.TcCbqa_rowMeta{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px;overflow:hidden}.TcCbqa_check{color:var(--dsw-alias-label-primary);flex:0 0 15px;place-items:center;display:grid}.TcCbqa_starButton{color:var(--dsw-alias-label-tertiary);cursor:pointer;border-radius:var(--dsw-radius-sm);opacity:0;background:0 0;border:none;outline:none;flex:none;place-items:center;width:26px;height:26px;display:grid}.TcCbqa_starButton:focus-visible{outline:2px solid var(--dsw-alias-border-l3)}.TcCbqa_row:hover .TcCbqa_starButton,.TcCbqa_row:focus-within .TcCbqa_starButton{opacity:1}.TcCbqa_starButton[aria-pressed=true]{color:var(--dsw-alias-state-warn-primary);opacity:1}@media (hover:none){.TcCbqa_starButton{opacity:1}}.TcCbqa_star{flex:none}.TcCbqa_notice{color:var(--dsw-alias-label-tertiary);justify-content:space-between;align-items:flex-start;gap:8px;padding:6px 10px;font-size:12px;line-height:18px;display:flex}.TcCbqa_retry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-weight:600;text-decoration:underline}.TcCbqa_pickerDialog{gap:0;width:min(680px,100%);height:min(640px,80vh);padding:0}.TcCbqa_pickerContent,.TcCbqa_pickerContent>:last-child{flex-direction:column;flex:1;min-height:0;display:flex}.TcCbqa_pickerSearch{padding:0 20px 12px}.TcCbqa_pickerSearchInput{width:100%}.TcCbqa_pickerTabs{border-bottom:.5px solid var(--dsw-alias-border-l2);overscroll-behavior-x:contain;scrollbar-width:none;flex-wrap:nowrap;gap:2px;padding:0 16px;display:flex;overflow-x:auto}.TcCbqa_pickerTabs::-webkit-scrollbar{display:none}.TcCbqa_pickerTab{color:var(--dsw-alias-label-secondary);cursor:pointer;border-radius:var(--dsw-radius-sm);white-space:nowrap;background:0 0;border:none;outline:none;flex:none;padding:6px 10px;font-size:13px;line-height:18px}.TcCbqa_pickerTab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.TcCbqa_pickerTab[aria-selected=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);font-weight:600}.TcCbqa_pickerTab:focus-visible{outline:2px solid var(--dsw-alias-border-l3)}.TcCbqa_pickerList{flex:1;min-height:0;padding:8px 12px;overflow-y:auto}.TcCbqa_pickerFooter{border-top:.5px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);align-items:center;gap:6px;padding:10px 20px;font-size:12px;line-height:18px;display:flex}.TcCbqa_footerHint{flex-wrap:wrap;align-items:center;gap:6px;display:flex}";
258
258
  const tagId = "@lynn123411/dsh-model-submenu/ModelOptions.module.css";
259
259
  if (typeof document !== "undefined") {
260
260
  let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]");
@@ -268,7 +268,6 @@ window.__ModuleLoader__.load({
268
268
  }
269
269
  var ModelOptions_module_css_default = {
270
270
  "check": "TcCbqa_check",
271
- "footerDot": "TcCbqa_footerDot",
272
271
  "footerHint": "TcCbqa_footerHint",
273
272
  "notice": "TcCbqa_notice",
274
273
  "pickerContent": "TcCbqa_pickerContent",
@@ -643,7 +642,7 @@ window.__ModuleLoader__.load({
643
642
  * @param props - the row's content and behaviour.
644
643
  * @returns the row element.
645
644
  */
646
- function ModelRow({ name, badge, selected, busy, favorited, meta, starLabel, starTabStop = false, rowTabIndex, onPick, onFocusRow, onToggleFavorite, children }) {
645
+ function ModelRow({ name, badge, selected, busy, favorited, meta, starLabel, onPick, onToggleFavorite, children }) {
647
646
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
648
647
  className: ModelOptions_module_css_default.row,
649
648
  "data-selected": selected ? "" : void 0,
@@ -651,14 +650,11 @@ window.__ModuleLoader__.load({
651
650
  children: [
652
651
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
653
652
  type: "button",
654
- role: "menuitemradio",
655
- "aria-checked": selected,
656
- tabIndex: rowTabIndex,
653
+ "aria-current": selected ? "true" : void 0,
657
654
  className: ModelOptions_module_css_default.rowPick,
658
655
  title: name,
659
656
  disabled: busy,
660
657
  onClick: onPick,
661
- onFocus: onFocusRow,
662
658
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
663
659
  className: ModelOptions_module_css_default.rowCopy,
664
660
  children: [
@@ -679,7 +675,6 @@ window.__ModuleLoader__.load({
679
675
  className: ModelOptions_module_css_default.starButton,
680
676
  "aria-pressed": favorited,
681
677
  "aria-label": starLabel,
682
- tabIndex: starTabStop ? 0 : -1,
683
678
  disabled: busy,
684
679
  onClick: onToggleFavorite,
685
680
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StarIcon, {
@@ -978,8 +973,6 @@ window.__ModuleLoader__.load({
978
973
  const listRef = (0, react.useRef)(null);
979
974
  const [query, setQuery] = (0, react.useState)("");
980
975
  const [tab, setTab] = (0, react.useState)("*");
981
- const [highlight, setHighlight] = (0, react.useState)(0);
982
- const tabRefs = (0, react.useRef)(/* @__PURE__ */ new Map());
983
976
  const tabs = (0, react.useMemo)(() => pickerTabs(state.groups, t("picker.all")), [state.groups, t]);
984
977
  const reason = (0, react.useMemo)(() => reasoningOf(state.groups), [state.groups]);
985
978
  const rows = (0, react.useMemo)(() => orderWithFavorites(rowsForTab(flattenGroups(state.groups), tab), (row) => isFavorite(favorites, row.provider, row.model)), [
@@ -996,7 +989,6 @@ window.__ModuleLoader__.load({
996
989
  if (!open) return;
997
990
  setQuery("");
998
991
  setTab("*");
999
- setHighlight(0);
1000
992
  if (listRef.current !== null) listRef.current.scrollTop = 0;
1001
993
  const frame = requestAnimationFrame(() => {
1002
994
  if (!(0, _deepseek_ai_dsh_client_ui_primitives.isMobileDevice)()) searchRef.current?.focus();
@@ -1005,29 +997,7 @@ window.__ModuleLoader__.load({
1005
997
  cancelAnimationFrame(frame);
1006
998
  };
1007
999
  }, [open]);
1008
- (0, react.useEffect)(() => {
1009
- setHighlight((current) => visible.length === 0 ? 0 : Math.min(current, visible.length - 1));
1010
- }, [visible.length]);
1011
- (0, react.useEffect)(() => {
1012
- (listRef.current?.querySelector(`[data-row-index="${highlight}"]`))?.scrollIntoView({ block: "nearest" });
1013
- }, [highlight]);
1014
1000
  if (!open) return null;
1015
- const activeRow = visible[highlight];
1016
- const activeId = activeRow === void 0 ? void 0 : `${id}-row-${highlight}`;
1017
- const onSearchKeyDown = (event) => {
1018
- if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1019
- event.preventDefault();
1020
- if (visible.length === 0) return;
1021
- setHighlight((current) => {
1022
- return (current + (event.key === "ArrowDown" ? 1 : -1) + visible.length) % visible.length;
1023
- });
1024
- return;
1025
- }
1026
- if (event.key === "Enter" && activeRow !== void 0 && !busy) {
1027
- event.preventDefault();
1028
- pick(activeRow);
1029
- }
1030
- };
1031
1001
  const pick = (row) => {
1032
1002
  const selection = planModelCommit(live, row.provider, row.model, reason, rememberedOf?.(row.provider, row.model));
1033
1003
  if (selection === null) {
@@ -1036,26 +1006,6 @@ window.__ModuleLoader__.load({
1036
1006
  }
1037
1007
  onPick(selection);
1038
1008
  };
1039
- /** Move to another tab and hand it the keyboard, in one step. */
1040
- const goTab = (next) => {
1041
- if (next === void 0 || next.id === tab) return;
1042
- setTab(next.id);
1043
- setHighlight(0);
1044
- tabRefs.current.get(next.id)?.focus();
1045
- };
1046
- const onTabKeyDown = (event) => {
1047
- const index = tabs.findIndex((candidate) => candidate.id === tab);
1048
- if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
1049
- event.preventDefault();
1050
- const step = event.key === "ArrowRight" ? 1 : -1;
1051
- goTab(tabs[((index === -1 ? 0 : index) + step + tabs.length) % tabs.length]);
1052
- return;
1053
- }
1054
- if (event.key === "Home" || event.key === "End") {
1055
- event.preventDefault();
1056
- goTab(tabs[event.key === "Home" ? 0 : tabs.length - 1]);
1057
- }
1058
- };
1059
1009
  const failures = state.failures.filter((failure) => tab === "*" || failure.id === tab);
1060
1010
  const failed = state.error !== null && state.status === "error";
1061
1011
  const loading = state.status === "loading";
@@ -1076,7 +1026,6 @@ window.__ModuleLoader__.load({
1076
1026
  role: "searchbox",
1077
1027
  "aria-label": t("picker.search"),
1078
1028
  "aria-controls": `${id}-list`,
1079
- "aria-activedescendant": activeId,
1080
1029
  placeholder: t("picker.search"),
1081
1030
  icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutlineRegular, { size: 14 }),
1082
1031
  value: query,
@@ -1084,7 +1033,6 @@ window.__ModuleLoader__.load({
1084
1033
  onChange: (event) => {
1085
1034
  setQuery(event.target.value);
1086
1035
  },
1087
- onKeyDown: onSearchKeyDown,
1088
1036
  "data-modal-autofocus": ""
1089
1037
  })
1090
1038
  }),
@@ -1093,10 +1041,6 @@ window.__ModuleLoader__.load({
1093
1041
  role: "tablist",
1094
1042
  "aria-label": t("picker.title"),
1095
1043
  children: tabs.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1096
- ref: (node) => {
1097
- if (node === null) tabRefs.current.delete(entry.id);
1098
- else tabRefs.current.set(entry.id, node);
1099
- },
1100
1044
  type: "button",
1101
1045
  role: "tab",
1102
1046
  id: `${id}-tab-${entry.id}`,
@@ -1106,14 +1050,6 @@ window.__ModuleLoader__.load({
1106
1050
  className: ModelOptions_module_css_default.pickerTab,
1107
1051
  onClick: () => {
1108
1052
  setTab(entry.id);
1109
- setHighlight(0);
1110
- },
1111
- onKeyDown: onTabKeyDown,
1112
- onFocus: () => {
1113
- if (entry.id !== tab) {
1114
- setTab(entry.id);
1115
- setHighlight(0);
1116
- }
1117
1053
  },
1118
1054
  children: entry.label
1119
1055
  }, entry.id))
@@ -1122,8 +1058,6 @@ window.__ModuleLoader__.load({
1122
1058
  ref: listRef,
1123
1059
  id: `${id}-list`,
1124
1060
  className: ModelOptions_module_css_default.pickerList,
1125
- role: "menu",
1126
- "aria-label": t("picker.title"),
1127
1061
  children: [
1128
1062
  loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1129
1063
  className: ModelOptions_module_css_default.notice,
@@ -1154,63 +1088,39 @@ window.__ModuleLoader__.load({
1154
1088
  className: ModelOptions_module_css_default.notice,
1155
1089
  children: state.groups.length === 0 ? t("status.empty") : t("picker.searchEmpty")
1156
1090
  }),
1157
- visible.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1158
- "data-row-index": index,
1159
- id: `${id}-row-${index}`,
1160
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelRow, {
1161
- name: row.entry.name,
1162
- badge: row.entry.badge === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
1163
- tone: row.entry.badge.tone,
1164
- className: ModelOptions_module_css_default.rowBadge,
1165
- children: resolveText(row.entry.badge.text)
1166
- }),
1167
- selected: live?.provider === row.provider && live.model === row.model,
1168
- busy,
1169
- favorited: isFavorite(favorites, row.provider, row.model),
1170
- meta: metaLine({
1171
- row,
1172
- facts: facts(row.provider, row.model),
1173
- providerName: tab === "*" ? row.providerName : void 0,
1174
- thinks: row.entry.reasoning !== void 0,
1175
- effortLabel: t("meta.effort"),
1176
- imageLabel: t("meta.images")
1177
- }),
1178
- starLabel: isFavorite(favorites, row.provider, row.model) ? t("picker.unfavorite", { model: row.entry.name }) : t("picker.favorite", { model: row.entry.name }),
1179
- starTabStop: index === highlight,
1180
- rowTabIndex: -1,
1181
- onPick: () => {
1182
- pick(row);
1183
- },
1184
- onFocusRow: () => {
1185
- setHighlight(index);
1186
- },
1187
- onToggleFavorite: () => {
1188
- onToggleFavorite(row.provider, row.model);
1189
- }
1190
- })
1191
- }, `${row.provider}/${row.model}`))
1091
+ visible.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelRow, {
1092
+ name: row.entry.name,
1093
+ badge: row.entry.badge === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
1094
+ tone: row.entry.badge.tone,
1095
+ className: ModelOptions_module_css_default.rowBadge,
1096
+ children: resolveText(row.entry.badge.text)
1097
+ }),
1098
+ selected: live?.provider === row.provider && live.model === row.model,
1099
+ busy,
1100
+ favorited: isFavorite(favorites, row.provider, row.model),
1101
+ meta: metaLine({
1102
+ row,
1103
+ facts: facts(row.provider, row.model),
1104
+ providerName: tab === "*" ? row.providerName : void 0,
1105
+ thinks: row.entry.reasoning !== void 0,
1106
+ effortLabel: t("meta.effort"),
1107
+ imageLabel: t("meta.images")
1108
+ }),
1109
+ starLabel: isFavorite(favorites, row.provider, row.model) ? t("picker.unfavorite", { model: row.entry.name }) : t("picker.favorite", { model: row.entry.name }),
1110
+ onPick: () => {
1111
+ pick(row);
1112
+ },
1113
+ onToggleFavorite: () => {
1114
+ onToggleFavorite(row.provider, row.model);
1115
+ }
1116
+ }) }, `${row.provider}/${row.model}`))
1192
1117
  ]
1193
1118
  }),
1194
1119
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1195
1120
  className: ModelOptions_module_css_default.pickerFooter,
1196
1121
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1197
1122
  className: ModelOptions_module_css_default.footerHint,
1198
- children: [
1199
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.ShortcutKeys, { keys: ["↑", "↓"] }),
1200
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("picker.hintNavigate") }),
1201
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1202
- className: ModelOptions_module_css_default.footerDot,
1203
- children: "·"
1204
- }),
1205
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.ShortcutKeys, { keys: ["Enter"] }),
1206
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("picker.hintSelect") }),
1207
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1208
- className: ModelOptions_module_css_default.footerDot,
1209
- children: "·"
1210
- }),
1211
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.ShortcutKeys, { keys: ["Esc"] }),
1212
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("picker.hintClose") })
1213
- ]
1123
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.ShortcutKeys, { keys: ["Esc"] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("picker.hintClose") })]
1214
1124
  })
1215
1125
  })
1216
1126
  ]
@@ -1475,8 +1385,6 @@ window.__ModuleLoader__.load({
1475
1385
  "picker.all": "全部",
1476
1386
  "picker.favorite": "收藏 {model}",
1477
1387
  "picker.unfavorite": "取消收藏 {model}",
1478
- "picker.hintNavigate": "导航",
1479
- "picker.hintSelect": "选择",
1480
1388
  "picker.hintClose": "关闭",
1481
1389
  "meta.effort": "思考",
1482
1390
  "meta.images": "图片输入",
@@ -1500,8 +1408,6 @@ window.__ModuleLoader__.load({
1500
1408
  "picker.all": "All",
1501
1409
  "picker.favorite": "Favorite {model}",
1502
1410
  "picker.unfavorite": "Remove {model} from favorites",
1503
- "picker.hintNavigate": "navigate",
1504
- "picker.hintSelect": "select",
1505
1411
  "picker.hintClose": "close",
1506
1412
  "meta.effort": "Effort",
1507
1413
  "meta.images": "Images",
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["useRef","useState","useSyncExternalStore","useRef","useState","css","useRef","useState","useSyncExternalStore","useMemo","createPortal","css","css","IconCheckOutlineRegular","getItem","useId","useRef","useState","useMemo","rankByName","isMobileDevice","Modal","css","Input","IconSearchOutlineRegular","Tag","ShortcutKeys","useSyncExternalStore","useState","useRef","useMemo","css","Toast","IconWarningOutlineRegular"],"sources":["../src/client/level-color.ts","../src/client/selection.ts","../src/client/EffortTint.tsx","../src/client/slider.ts","../src/client/EffortSlider.tsx","../src/client/ModelMenuBridge.tsx","../src/client/icons.tsx","../src/client/ModelRow.tsx","../src/client/favorites.ts","../src/client/model-facts.ts","../src/client/rows.ts","../src/client/ModelPicker.tsx","../src/client/effort-memory.ts","../src/client/ModelPickerHost.tsx","../src/client/picker-state.ts","../src/client/facts-source.ts","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * The accent one reasoning level carries.\n *\n * \"Off\" carries no accent at all — the fill and the caption stay neutral, so\n * switching reasoning off reads as switching it off rather than as the first\n * shade of a ramp. Every level above it walks a blue-to-violet ramp, and the\n * highest level is the only one whose fill is a gradient.\n */\n\n/** Off: the dimmed label tone, the same neutral the Host uses for quiet text. */\nconst NEUTRAL = 'var(--dsw-alias-label-dimmed)';\n\n/** Lowest lit level: the blue the composer already uses for accents. */\nconst LOW = [77, 147, 248] as const;\n\n/** Highest level: violet. */\nconst HIGH = [147, 51, 234] as const;\n\n/**\n * @param t - a point on the ramp, 0 at the lowest lit level and 1 at the top.\n * @returns the ramp color there.\n */\nfunction ramp(t: number): string {\n const mix = (from: number, to: number): number => Math.round(from + (to - from) * t);\n return `rgb(${mix(LOW[0], HIGH[0])}, ${mix(LOW[1], HIGH[1])}, ${mix(LOW[2], HIGH[2])})`;\n}\n\n/**\n * @param index - the level's detent, 0 being \"off\".\n * @param count - number of detents.\n * @returns the level's color as a CSS color string.\n */\nexport function levelColor(index: number, count: number): string {\n if (index <= 0) return NEUTRAL;\n // The top level is always the violet end, even when it is the only lit one.\n if (isTopLevel(index, count)) return ramp(1);\n const span = Math.max(1, count - 2);\n return ramp(Math.min(Math.max((index - 1) / span, 0), 1));\n}\n\n/**\n * @param index - the level's detent.\n * @param count - number of detents.\n * @returns whether this level is the only one painted with a gradient.\n */\nexport function isTopLevel(index: number, count: number): boolean {\n return count > 1 && index === count - 1;\n}\n\n/** The top level's fill: the whole ramp, ending on the color its text takes. */\nexport const LEVEL_GRADIENT = `linear-gradient(90deg, ${ramp(0)}, ${ramp(1)})`;\n","/**\n * When the composer menu writes a selection.\n *\n * Both gestures are immediate: clicking a level submits it, clicking a model\n * submits it. The only quiet path is clicking the model already in use — that\n * closes the menu without a write. A model click also restores the level this\n * browser last used for that model, so switching away and back never silently\n * resets the effort.\n */\n\nimport type { ModelReasoning } from '@deepseek-ai/dsh-api-session-controller/types'\nimport type { ModelDirectoryState } from '@deepseek-ai/dsh-client-ui-model-selection/client'\n\n/** One complete selection, as the directory's `select` takes it. */\nexport interface Selection {\n readonly provider: string\n readonly model: string\n readonly reasoningEffort?: string\n}\n\n/** One segment of the reasoning row. */\nexport interface EffortChoice {\n readonly key: string\n readonly effort: string | undefined\n readonly label: string\n}\n\n/**\n * Build the slider's detents for one model.\n *\n * The provider default is deliberately absent: it is a state, not a level, and\n * giving it a detent would read as the weakest reasoning instead of \"let the\n * provider decide\". The label above the track switches to it.\n * @param reasoning - the catalog's reasoning metadata, undefined when the model has none.\n * @returns one detent per advertised level, in catalog order.\n */\nexport function effortChoices(reasoning: ModelReasoning | undefined): readonly EffortChoice[] {\n if (reasoning === undefined) return []\n return reasoning.efforts.map((level) => ({\n key: level.id,\n effort: level.id as string | undefined,\n label: level.name,\n }))\n}\n\n/**\n * The selection a level click submits.\n * @param live - the selection in force right now.\n * @param effort - the clicked level, undefined meaning the provider default.\n * @returns the selection to submit, or null when the level already is the live one.\n */\nexport function planEffortCommit(live: Selection, effort: string | undefined): Selection | null {\n if (sameEffort(live.reasoningEffort, effort)) return null\n return {\n provider: live.provider,\n model: live.model,\n ...effort === undefined ? {} : { reasoningEffort: effort },\n }\n}\n\n/**\n * The selection a model click submits.\n * @param live - the selection in force right now, null before a first selection resolves.\n * @param provider - the clicked model's provider.\n * @param model - the clicked model id.\n * @param reasoningOf - the catalog's reasoning metadata for one provider and model.\n * @param remembered - the level this browser last used for the target model, if any.\n * @returns the selection to submit, or null when the click changes nothing.\n */\nexport function planModelCommit(\n live: Selection | null,\n provider: string,\n model: string,\n reasoningOf: (provider: string, model: string) => ModelReasoning | undefined,\n remembered?: string | undefined,\n): Selection | null {\n if (live !== null && sameModel(live, provider, model)) return null\n const offered = reasoningOf(provider, model)\n // The target model's own memory is restored, so switching away and back never\n // silently resets the effort.\n const carried = offered !== undefined\n && remembered !== undefined\n && offered.efforts.some((level) => level.id === remembered)\n return {\n provider,\n model,\n ...carried ? { reasoningEffort: remembered } : {},\n }\n}\n\n/**\n * The reasoning metadata the catalog advertises for the model in force.\n * @param state - the session's directory snapshot.\n * @returns the metadata, or undefined when no model is live or it advertises none.\n */\nexport function liveReasoning(state: ModelDirectoryState): ModelReasoning | undefined {\n const current = state.current\n if (current === null) return undefined\n return state.groups\n .find((group) => group.id === current.provider)\n ?.models.find((model) => model.id === current.model)\n ?.reasoning\n}\n\n/**\n * The selection in force, or null while the Host has not resolved one.\n * @param state - the session's directory snapshot.\n * @returns the live selection, with the effort only when one is set.\n */\nexport function selectionOf(state: ModelDirectoryState): Selection | null {\n if (state.current === null) return null\n return state.current.reasoningEffort === undefined\n ? { provider: state.current.provider, model: state.current.model }\n : {\n provider: state.current.provider,\n model: state.current.model,\n reasoningEffort: state.current.reasoningEffort,\n }\n}\n\n/**\n * @param live - one selection.\n * @param provider - the provider to compare.\n * @param model - the model id to compare.\n * @returns whether both name the same model.\n */\nexport function sameModel(live: Selection, provider: string, model: string): boolean {\n return live.provider === provider && live.model === model\n}\n\n/**\n * @param liveEffort - the level in force, undefined meaning the provider default.\n * @param effort - the level in question.\n * @returns whether they name the same level.\n */\nexport function sameEffort(liveEffort: string | undefined, effort: string | undefined): boolean {\n return liveEffort === effort\n}\n","/**\n * Tint the composer's effort caption with the level's color.\n *\n * The slider's fill and the menu's value already carry the level's color; the\n * caption on the collapsed trigger is the third place the level shows, and it\n * lives outside the menu. This entry stays mounted for the whole session and\n * writes that one color, so all three read as one signal.\n *\n * The caption is recognized as the trigger's `span[class*='triggerEffort']`\n * child — the same class-substring contract the menu row uses. When the model\n * declares no levels the span does not exist and nothing is written.\n */\nimport { useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport type { ModelSelectInjected } from '@deepseek-ai/dsh-client-ui-model-selection/client';\nimport { levelColor } from './level-color.ts';\nimport { effortChoices, liveReasoning } from './selection.ts';\n\n/** Props of the tint: the shared catalog face. */\nexport type EffortTintProps = Pick<ModelSelectInjected, 'available' | 'directory'>;\n\n/**\n * Render nothing and keep the trigger caption colored.\n * @param props - the session's catalog face.\n * @returns an inert marker.\n */\nexport function EffortTint({ available, directory }: EffortTintProps) {\n const markerRef = useRef<HTMLSpanElement | null>(null);\n const [caption, setCaption] = useState<HTMLElement | null>(null);\n const state = useSyncExternalStore(\n (listener) => directory.subscribe(listener),\n () => directory.getSnapshot(),\n );\n const levels = effortChoices(liveReasoning(state));\n const effort = state.current?.reasoningEffort;\n const chosen = effort === undefined ? -1 : levels.findIndex((level) => level.effort === effort);\n // The provider default has no color of its own: the caption keeps the Host's.\n const color = available && chosen >= 0 && levels.length >= 2\n ? levelColor(chosen, levels.length)\n : null;\n\n // The Host rebuilds the trigger on every model change, so the caption is\n // re-read after each render; the identity guard keeps that from looping.\n useLayoutEffect(() => {\n const card = markerRef.current?.closest('[data-composer-card]');\n const next = card?.querySelector<HTMLElement>(\n '[data-slot=\"conversation.input.model\"] button[aria-haspopup=\"menu\"] span[class*=\"triggerEffort\"]',\n ) ?? null;\n if (next !== caption) setCaption(next);\n });\n\n useLayoutEffect(() => {\n if (caption === null || color === null) return;\n const saved = caption.style.color;\n caption.style.color = color;\n return () => {\n caption.style.color = saved;\n };\n }, [caption, color]);\n\n return <span ref={markerRef} hidden />;\n}\n","/**\n * Geometry of the discrete effort slider.\n *\n * The track is a capsule and the knob a slightly larger circle riding on it, so\n * the knob overhangs the track instead of merging with its rounded end. The\n * knob's centre travels between two points inset by the knob's radius, so the\n * knob never runs past either end. A pointer position becomes a position along\n * that travel; a position becomes the detent nearest it, or the CSS lengths that\n * put the knob and the fill there.\n */\n\n/**\n * Track height in CSS pixels; the stylesheet keeps the same value. Half of it is\n * the radius of the track's ends and of the fill's cap.\n */\nexport const TRACK_HEIGHT = 16;\n\n/**\n * Knob diameter in CSS pixels. It is larger than the track, so the knob reads as\n * a knob riding on the track rather than as the track's own end cap.\n */\nexport const KNOB_SIZE = 20;\n\n/** The knob's centre keeps its own radius away from either end of the track. */\nconst KNOB_RADIUS = KNOB_SIZE / 2;\n\n/**\n * @param index - detent index.\n * @param count - number of detents.\n * @returns the knob centre as a fraction of the track, 0 at the first detent and 1 at the last.\n */\nexport function offsetOf(index: number, count: number): number {\n if (count <= 1) return 0;\n const clamped = Math.min(Math.max(index, 0), count - 1);\n return clamped / (count - 1);\n}\n\n/**\n * Where a pointer sits along the knob's travel. The drag reads this rather than a\n * detent, so the knob follows the pointer instead of jumping a level at a time.\n * @param clientX - pointer position in viewport coordinates.\n * @param left - the track's left edge in viewport coordinates.\n * @param width - the track's width in pixels.\n * @returns the position as a fraction of the travel, clamped to 0 and 1.\n */\nexport function fractionAt(clientX: number, left: number, width: number): number {\n const usable = width - KNOB_SIZE;\n if (usable <= 0) return 0;\n const fraction = (clientX - left - KNOB_RADIUS) / usable;\n return Math.min(Math.max(fraction, 0), 1);\n}\n\n/**\n * @param fraction - a knob position between 0 and 1.\n * @param count - number of detents.\n * @returns the detent nearest that position.\n */\nexport function detentAt(fraction: number, count: number): number {\n if (count <= 1) return 0;\n const clamped = Math.min(Math.max(fraction, 0), 1);\n return Math.round(clamped * (count - 1));\n}\n\n/**\n * @param clientX - pointer position in viewport coordinates.\n * @param left - the track's left edge in viewport coordinates.\n * @param width - the track's width in pixels.\n * @param count - number of detents.\n * @returns the detent nearest the pointer, clamped to the two ends.\n */\nexport function stepAt(clientX: number, left: number, width: number, count: number): number {\n if (count <= 1) return 0;\n return detentAt(fractionAt(clientX, left, width), count);\n}\n\n/**\n * @param fraction - a knob position between 0 and 1.\n * @returns the CSS length that puts the knob centre there, inset at both ends.\n */\nexport function knobOffset(fraction: number): string {\n const clamped = Math.min(Math.max(fraction, 0), 1);\n return `calc(${KNOB_RADIUS}px + ${clamped} * (100% - ${KNOB_SIZE}px))`;\n}\n\n/**\n * Width of the colored fill for a knob position.\n *\n * The fill is the track's own shape cut at the knob: its right cap carries the\n * track's radius and ends under the knob, so the color runs into the knob\n * instead of stopping at a straight edge, and the last detent reaches the track's\n * end rather than the knob's centre.\n * @param fraction - a knob position between 0 and 1.\n * @returns the CSS width of the fill.\n */\nexport function fillWidth(fraction: number): string {\n const clamped = Math.min(Math.max(fraction, 0), 1);\n return `calc(${TRACK_HEIGHT}px + ${clamped} * (100% - ${TRACK_HEIGHT}px))`;\n}\n","/**\n * The reasoning-level slider that replaces the Host's effort drill.\n *\n * The Host's second menu cell is a button that drills into a list of levels.\n * This slider sits inside that same cell, between the Host's label and value, so\n * the levels are reachable without leaving the menu. The levels are discrete but\n * the drag is not: the knob follows the pointer continuously, the level it\n * reports is the detent nearest that position, and letting go eases the knob onto\n * that detent and commits once. Keyboard arrows step and commit immediately.\n *\n * The fill takes the level's color, so the track and the two captions read as\n * one signal: \"off\" is neutral, the levels above it walk a blue-to-violet ramp,\n * and only the top level fills with the whole gradient.\n *\n * \"Provider default\" is not a detent: it is a state, drawn as an empty track\n * with a hollow knob, so it never reads as the lowest level.\n */\nimport { useEffect, useRef, useState, type KeyboardEvent, type PointerEvent } from 'react';\nimport type { EffortChoice } from './selection.ts';\nimport { LEVEL_GRADIENT, isTopLevel, levelColor } from './level-color.ts';\nimport { detentAt, fillWidth, fractionAt, knobOffset, offsetOf } from './slider.ts';\nimport css from './ModelOptions.module.css';\n\n/** Props of the slider: the levels, the one in force, and the commit path. */\nexport interface EffortSliderProps {\n /** The levels in catalog order, lowest first. */\n readonly steps: readonly EffortChoice[];\n /** Index of the level in force, already clamped into `steps`. */\n readonly current: number;\n /** Whether the provider default is in force instead of a level. */\n readonly defaultActive: boolean;\n /** Where the knob rests while the provider default is in force. */\n readonly defaultIndex: number;\n /** Localized name of the provider default, read while it is in force. */\n readonly defaultLabel: string;\n /** Whether a selection is in flight; input is ignored while it is. */\n readonly busy: boolean;\n /** Accessible name, taken from the Host's own row label. */\n readonly label: string;\n /** @param index - the level the user settled on. */\n readonly onCommit: (index: number) => void;\n}\n\n/**\n * Render the slider.\n * @param props - levels, the level in force, and the commit path.\n * @returns one focusable slider over the discrete levels.\n */\nexport function EffortSlider({\n steps, current, defaultActive, defaultIndex, defaultLabel, busy, label, onCommit,\n}: EffortSliderProps) {\n const trackRef = useRef<HTMLDivElement | null>(null);\n // The drag position lives in a ref as well as in state: a press and a release\n // can arrive in one task, and the release must not read a render that never ran.\n const dragRef = useRef<number | null>(null);\n // While dragging this is where the pointer is along the travel, not a level:\n // the knob has to move with the pointer, or the drag reads as a series of jumps.\n const [drag, setDrag] = useState<number | null>(null);\n // After a release that submits, the knob waits on the detent it settled on\n // instead of springing back to the old level while the write is in flight.\n const [hold, setHold] = useState<number | null>(null);\n const dragging = drag !== null;\n const resting = defaultActive ? defaultIndex : current;\n const active = dragging ? detentAt(drag, steps.length) : resting;\n const knobFraction = drag ?? hold ?? offsetOf(resting, steps.length);\n // The color and the ticks follow the knob, not the level in force: during a\n // drag, and while a commit is in flight, the knob has already gone ahead.\n const shown = dragging || hold !== null ? detentAt(knobFraction, steps.length) : resting;\n const beginDrag = (fraction: number | null): void => {\n dragRef.current = fraction;\n setDrag(fraction);\n };\n\n // The hold ends as soon as the write does, whether it landed on the new level\n // or was rejected: the displayed level is the truth again either way.\n useEffect(() => {\n if (!busy) setHold(null);\n }, [busy]);\n useEffect(() => {\n setHold(null);\n }, [current, defaultActive]);\n\n const fractionAtPointer = (clientX: number): number => {\n const track = trackRef.current;\n if (track === null) return dragRef.current ?? knobFraction;\n const rect = track.getBoundingClientRect();\n return fractionAt(clientX, rect.left, rect.width);\n };\n\n const onPointerDown = (event: PointerEvent<HTMLDivElement>): void => {\n if (busy) return;\n // The Host's menu cell owns this subtree; without these the press would\n // also settle the cell and pull focus out of the slider.\n event.preventDefault();\n event.stopPropagation();\n setHold(null);\n beginDrag(fractionAtPointer(event.clientX));\n try {\n event.currentTarget.setPointerCapture(event.pointerId);\n } catch {\n // A synthetic pointer has no capture to take; the drag still tracks moves.\n }\n event.currentTarget.focus();\n };\n\n const onPointerMove = (event: PointerEvent<HTMLDivElement>): void => {\n if (dragRef.current === null || busy) return;\n beginDrag(fractionAtPointer(event.clientX));\n };\n\n const settle = (event: PointerEvent<HTMLDivElement>, commit: boolean): void => {\n if (dragRef.current === null) return;\n const next = detentAt(fractionAtPointer(event.clientX), steps.length);\n beginDrag(null);\n if (!commit) return;\n // Leaving the provider default is a change even when the knob lands where\n // the default already pointed.\n if (next !== current || defaultActive) {\n setHold(offsetOf(next, steps.length));\n onCommit(next);\n }\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {\n if (busy) return;\n const last = steps.length - 1;\n const next = event.key === 'ArrowRight' || event.key === 'ArrowUp'\n ? Math.min(active + 1, last)\n : event.key === 'ArrowLeft' || event.key === 'ArrowDown'\n ? Math.max(active - 1, 0)\n : event.key === 'Home'\n ? 0\n : event.key === 'End'\n ? last\n : null;\n if (next === null) return;\n // The menu walks rows with the arrow keys; a slider keeps them for itself.\n event.preventDefault();\n event.stopPropagation();\n if (next !== current || defaultActive) onCommit(next);\n };\n\n const offset = knobOffset(knobFraction);\n // The provider default fills nothing: the knob shows where it lands, the\n // track stays empty so it never reads as a chosen level.\n const idle = defaultActive && !dragging && hold === null;\n const filled = idle ? 0 : knobFraction;\n const fill = isTopLevel(shown, steps.length) ? LEVEL_GRADIENT : levelColor(shown, steps.length);\n return <div\n ref={trackRef}\n className={css.slider}\n role=\"slider\"\n tabIndex={0}\n aria-label={label}\n aria-valuemin={0}\n aria-valuemax={Math.max(0, steps.length - 1)}\n aria-valuenow={active}\n aria-valuetext={defaultActive && !dragging ? defaultLabel : steps[active]?.label}\n aria-disabled={busy || undefined}\n data-busy={busy ? '' : undefined}\n data-dragging={dragging ? '' : undefined}\n data-default={idle ? '' : undefined}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={(event) => { settle(event, true); }}\n onPointerCancel={(event) => { settle(event, false); }}\n onKeyDown={onKeyDown}\n onClick={(event) => { event.stopPropagation(); }}\n >\n <div className={css.sliderTrack} aria-hidden=\"true\">\n <div className={css.sliderFill} style={{ width: fillWidth(filled), background: fill }} />\n {steps.map((step, index) => <span\n key={step.key}\n className={css.sliderTick}\n data-on={index <= shown && !idle ? '' : undefined}\n style={{ left: knobOffset(offsetOf(index, steps.length)) }}\n />)}\n <div className={css.sliderKnob} style={{ left: offset }} />\n </div>\n </div>;\n}\n","/**\n * Bridge the Host's own model menu: the model cell opens the all-provider\n * picker, and the effort cell carries the reasoning-level slider.\n *\n * The Host draws the root menu's first two cells itself: the model cell drills\n * into the current provider's list, the effort cell into the levels. This plugin\n * wants the first to open its dialog and the second to stop drilling, and the\n * Host exposes no hook for either. This entry is mounted exactly while the root\n * pane is open, so it can take the click on the menu surface before the cells'\n * own handlers run and mount the slider into the effort cell.\n *\n * The cells are recognized as the menu surface's direct `role=\"menuitem\"`\n * children, which is the Host's root-menu shape; the options slot output sits in\n * its own wrapper, so other plugins' rows never join that list. When the second\n * cell is missing the model declares no reasoning levels and the row is left\n * alone; when the first is missing the click falls through to the Host.\n *\n * Clicking the cell's label switches to the provider default, which is a state\n * rather than a detent: the slider shows it as an empty track under a hollow\n * knob, so it never reads as the weakest level.\n */\nimport { useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { createPortal } from 'react-dom';\nimport type { ModelSelectInjected } from '@deepseek-ai/dsh-client-ui-model-selection/client';\nimport type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';\nimport { EffortSlider } from './EffortSlider.tsx';\nimport type { PickerStore } from './picker-state.ts';\nimport { effortChoices, liveReasoning, planEffortCommit, selectionOf, type Selection } from './selection.ts';\nimport css from './ModelOptions.module.css';\n\n/** Props of the bridge: menu state, the owning session, and the seat's injected face. */\nexport type ModelMenuBridgeProps = PropsRuntime<'conversation.input.model.options'>\n & PropsLocale<'modelSubmenu'>\n & Pick<ModelSelectInjected, 'available' | 'directory' | 'select'>\n & {\n /** Session whose catalog and picker this menu drives. */\n readonly sessionId: string;\n /** Carries the open request to the overlay view. */\n readonly picker: PickerStore;\n }\n\n/**\n * Render the marker, redirect the model cell, and mount the effort slider.\n * @param props - menu state, session, catalog face, and localized copy.\n * @returns an inert marker plus the slider while the effort cell is present.\n */\nexport function ModelMenuBridge({\n sessionId, picker, dismiss, reportFailure, available, directory, select, t,\n}: ModelMenuBridgeProps) {\n const markerRef = useRef<HTMLSpanElement | null>(null);\n const sliderRef = useRef<HTMLDivElement | null>(null);\n const [surface, setSurface] = useState<HTMLElement | null>(null);\n const [effortRow, setEffortRow] = useState<HTMLElement | null>(null);\n const [rowLabel, setRowLabel] = useState<string | null>(null);\n\n const state = useSyncExternalStore(\n (listener) => directory.subscribe(listener),\n () => directory.getSnapshot(),\n );\n const current = state.current;\n const reasoning = liveReasoning(state);\n const levels = useMemo(() => effortChoices(reasoning), [reasoning]);\n const declared = levels.findIndex((level) => level.effort === reasoning?.defaultEffort);\n const chosen = current?.reasoningEffort === undefined\n ? -1\n : levels.findIndex((level) => level.effort === current.reasoningEffort);\n // The provider default is a state, not a detent: it is in force whenever the\n // session carries no explicit level.\n const defaultActive = current !== null && current.reasoningEffort === undefined;\n const currentIndex = chosen >= 0 ? chosen : declared >= 0 ? declared : 0;\n const defaultIndex = declared >= 0 ? declared : 0;\n const canDefault = !defaultActive;\n const busy = state.pending !== null;\n // One detent is not a choice: leave the Host's row exactly as it is.\n const showSlider = available && levels.length >= 2 && effortRow !== null;\n\n // The Host rebuilds its cells on every render, so the surface and the effort\n // cell are re-read after each one; the identity guards keep that from looping.\n useLayoutEffect(() => {\n const foundSurface = markerRef.current?.closest('[data-menu-material]');\n const nextSurface = foundSurface instanceof HTMLElement ? foundSurface : null;\n if (nextSurface !== surface) setSurface(nextSurface);\n const cells = nextSurface === null\n ? []\n : [...nextSurface.querySelectorAll<HTMLElement>(':scope > button[role=\"menuitem\"]')];\n const nextRow = cells.length >= 2 ? cells[1] ?? null : null;\n if (nextRow !== effortRow) setEffortRow(nextRow);\n const nextLabel = nextRow?.querySelector(\"span[class*='cellLabel']\")?.textContent?.trim() ?? '';\n if (nextLabel !== (rowLabel ?? '')) setRowLabel(nextLabel);\n });\n\n const submit = (selection: Selection | null): void => {\n if (selection === null) return;\n void select(selection).then((result) => {\n if (result === undefined || result.ok) return;\n reportFailure(result.error.code === 'session/writer-held'\n ? t('error.sessionInUse')\n : t('error.action', { message: `${result.error.code}: ${result.error.message}` }));\n });\n };\n const commit = (index: number): void => {\n const live = selectionOf(state);\n const level = levels[index];\n if (live === null || level === undefined) return;\n submit(planEffortCommit(live, level.effort));\n };\n const commitDefault = (): void => {\n const live = selectionOf(state);\n if (live === null) return;\n submit(planEffortCommit(live, undefined));\n };\n\n // The Host rebuilds `dismiss` on every render, so the listener reads the\n // current one through a ref instead of being torn down and reinstalled.\n const latest = useRef({ sessionId, picker, dismiss, showSlider, canDefault, commitDefault });\n latest.current = { sessionId, picker, dismiss, showSlider, canDefault, commitDefault };\n\n useEffect(() => {\n if (surface === null) return;\n const redirect = (event: MouseEvent): void => {\n const target = event.target;\n if (!(target instanceof Node)) return;\n const cells = [...surface.querySelectorAll<HTMLElement>(':scope > button[role=\"menuitem\"]')];\n const modelCell = cells[0];\n if (modelCell !== undefined && modelCell.contains(target)) {\n // Capture on the surface runs before the React root's bubble listener, so\n // stopping here keeps the cell's own drill handler from ever running.\n event.preventDefault();\n event.stopPropagation();\n const request = latest.current;\n request.dismiss();\n request.picker.open(request.sessionId);\n return;\n }\n const row = cells[1];\n if (row === undefined || !row.contains(target) || !latest.current.showSlider) return;\n // The slider owns its own presses; only the label and the value are inert.\n if (sliderRef.current?.contains(target) === true) return;\n event.preventDefault();\n event.stopPropagation();\n const label = row.querySelector(\"span[class*='cellLabel']\");\n if (latest.current.canDefault && label !== null && label.contains(target)) {\n latest.current.commitDefault();\n return;\n }\n sliderRef.current?.querySelector<HTMLElement>('[role=\"slider\"]')?.focus();\n };\n surface.addEventListener('click', redirect, true);\n return () => {\n surface.removeEventListener('click', redirect, true);\n };\n }, [surface]);\n\n // The slider takes the whole slack beside the label: the value and the\n // chevron go, because the level is already written under the composer and the\n // row no longer drills anywhere. Every write is recorded and put back.\n useLayoutEffect(() => {\n if (effortRow === null || !showSlider) return;\n const label = effortRow.querySelector<HTMLElement>(\"span[class*='cellLabel']\");\n const value = effortRow.querySelector<HTMLElement>(\"span[class*='cellValue']\");\n const chevron = effortRow.querySelector<SVGElement>(\"svg[class*='cellChevron']\");\n const saved = {\n value: value?.style.display ?? '',\n chevron: chevron?.style.display ?? '',\n cursor: label?.style.cursor ?? '',\n title: label?.getAttribute('title') ?? null,\n };\n if (value !== null) value.style.display = 'none';\n if (chevron !== null) chevron.style.display = 'none';\n if (label !== null && canDefault) {\n label.style.cursor = 'pointer';\n label.title = t('effort.useDefault');\n }\n return () => {\n if (value !== null) value.style.display = saved.value;\n if (chevron !== null) chevron.style.display = saved.chevron;\n if (label !== null) {\n label.style.cursor = saved.cursor;\n if (saved.title === null) label.removeAttribute('title');\n else label.title = saved.title;\n }\n };\n }, [effortRow, showSlider, canDefault, t]);\n\n return <>\n <span ref={markerRef} hidden />\n {showSlider && effortRow !== null && createPortal(\n <div ref={sliderRef} className={css.sliderCell}>\n <EffortSlider\n steps={levels}\n current={currentIndex}\n defaultActive={defaultActive}\n defaultIndex={defaultIndex}\n defaultLabel={t('effort.providerDefault')}\n busy={busy}\n label={rowLabel !== null && rowLabel !== '' ? rowLabel : t('effort.label')}\n onCommit={commit}\n />\n </div>,\n effortRow,\n )}\n </>;\n}\n","/**\n * The one glyph this plugin draws itself.\n *\n * The shared icon library carries no star, so the favorite mark is defined here:\n * a five-point star whose outline and fill use the same path, which keeps the\n * hover-only outline and the standing filled state identical in geometry.\n */\n\n/**\n * Render the favorite star.\n * @param props.filled - draw the solid star; otherwise the outline.\n * @param props.size - square edge in pixels, matching the shared icon set's default.\n * @param props.className - extra class for colour and placement.\n * @returns the inline SVG.\n */\nexport function StarIcon({ filled, size = 14, className }: {\n readonly filled: boolean\n readonly size?: number | undefined\n readonly className?: string | undefined\n}) {\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill={filled ? 'currentColor' : 'none'}\n stroke=\"currentColor\"\n strokeWidth={filled ? 0 : 1.6}\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className={className}\n >\n <path d=\"M12 3.2l2.7 5.6 6.1.85-4.45 4.3 1.08 6.05L12 17.1l-5.43 2.9 1.08-6.05L3.2 9.65l6.1-.85z\" />\n </svg>\n )\n}\n","/**\n * One pickable model in the picker.\n *\n * The star is its own control, because favouriting there must not switch the\n * model: the row button picks, the star toggles, and the check marks the model\n * in use.\n */\n\nimport type { ReactNode } from 'react';\nimport { IconCheckOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives';\nimport { StarIcon } from './icons.tsx';\nimport css from './ModelOptions.module.css';\n\n/** Props of one row. */\nexport interface ModelRowProps {\n /** The model's catalog name. */\n readonly name: string;\n /** Optional catalog badge shown beside the name. */\n readonly badge?: ReactNode | undefined;\n /** Whether this row is the model in use. */\n readonly selected: boolean;\n /** Whether a selection is in flight, which locks the row. */\n readonly busy: boolean;\n /** Whether the model is favorited right now. */\n readonly favorited: boolean;\n /** The metadata line's text. */\n readonly meta?: string | undefined;\n /** Localized accessible name for the star control. */\n readonly starLabel?: string | undefined;\n /** Whether the star is a Tab stop right now (the picker's roving highlight). */\n readonly starTabStop?: boolean;\n /** Tab order of the row itself; the picker keeps rows out of it so ↑/↓ own the walk. */\n readonly rowTabIndex?: number;\n /** Pick this row. */\n readonly onPick: () => void;\n /** The row took the keyboard, so its star becomes reachable. */\n readonly onFocusRow?: (() => void) | undefined;\n /** Toggle this row's favorite. */\n readonly onToggleFavorite: () => void;\n /** Extra row content, such as the picker's disabled reason. */\n readonly children?: ReactNode;\n}\n\n/**\n * Render one model row.\n * @param props - the row's content and behaviour.\n * @returns the row element.\n */\nexport function ModelRow({\n name, badge, selected, busy, favorited, meta, starLabel,\n starTabStop = false, rowTabIndex, onPick, onFocusRow, onToggleFavorite, children,\n}: ModelRowProps) {\n return <div\n className={css.row}\n data-selected={selected ? '' : undefined}\n data-favorited={favorited ? '' : undefined}\n >\n <button\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={selected}\n tabIndex={rowTabIndex}\n className={css.rowPick}\n title={name}\n disabled={busy}\n onClick={onPick}\n onFocus={onFocusRow}\n >\n <span className={css.rowCopy}>\n <span className={css.rowName}>{name}</span>\n {badge}\n {meta !== undefined && meta !== '' && <span className={css.rowMeta}>{meta}</span>}\n </span>\n </button>\n <button\n type=\"button\"\n className={css.starButton}\n aria-pressed={favorited}\n aria-label={starLabel}\n tabIndex={starTabStop ? 0 : -1}\n disabled={busy}\n onClick={onToggleFavorite}\n >\n <StarIcon filled={favorited} className={css.star} />\n </button>\n <span className={css.check} aria-hidden={!selected}>{selected ? <IconCheckOutlineRegular /> : null}</span>\n {children}\n </div>;\n}\n","/**\n * The favorite-model table the picker keeps in the browser.\n *\n * One browser-wide key holds `{ provider, model }` rows: the provider route key\n * and the model id are the catalog's own identity, so a favorite survives a\n * display-name change and never collides across providers. A value that cannot\n * be read or does not parse reads as an empty table — a privacy-mode browser or\n * a hand-edited row must never keep the menu from opening. Rows whose provider\n * leaves the catalog are kept, so restoring the provider restores the star.\n */\n\nimport type { ModelRef } from './rows.ts'\n\n/** One favorited model, identified the way the catalog identifies it. */\nexport type FavoriteModel = ModelRef\n\n/** The single browser-wide storage key holding the whole table. */\nexport const FAVORITES_KEY = 'dsh-model-submenu.favorites'\n\n/** The part of `Storage` this module uses; absent or throwing reads mean \"none\". */\nexport type FavoritesStorage = Pick<Storage, 'getItem' | 'setItem'>\n\n/**\n * Read the table, keeping only rows that name both halves.\n * @param storage - browser storage, or undefined when the browser withholds it.\n * @returns the stored favorites in stored order; empty when unreadable.\n */\nexport function readFavorites(storage: FavoritesStorage | undefined): FavoriteModel[] {\n const raw = getItem(storage)\n if (raw === null) return []\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return []\n }\n if (!Array.isArray(parsed)) return []\n return parsed.filter((row): row is FavoriteModel => typeof row === 'object' && row !== null\n && typeof (row as FavoriteModel).provider === 'string'\n && typeof (row as FavoriteModel).model === 'string')\n}\n\n/**\n * Add or remove one model, writing the whole table back.\n * @param storage - browser storage, or undefined when writes are unavailable.\n * @param provider - the provider route key.\n * @param model - the model id inside that provider.\n * @returns the table after the change, whether or not the write landed.\n */\nexport function toggleFavorite(\n storage: FavoritesStorage | undefined,\n provider: string,\n model: string,\n): FavoriteModel[] {\n const current = readFavorites(storage)\n const next = isFavorite(current, provider, model)\n ? current.filter((row) => row.provider !== provider || row.model !== model)\n : [...current, { provider, model }]\n try {\n storage?.setItem(FAVORITES_KEY, JSON.stringify(next))\n } catch {\n // A full quota or a locked-down browser loses the star, not the menu.\n }\n return next\n}\n\n/**\n * @param favorites - the table to search.\n * @param provider - the provider route key.\n * @param model - the model id inside that provider.\n * @returns whether that exact pair is favorited.\n */\nexport function isFavorite(\n favorites: readonly FavoriteModel[],\n provider: string,\n model: string,\n): boolean {\n return favorites.some((row) => row.provider === provider && row.model === model)\n}\n\n/**\n * Stable-partition a catalog so favorited rows lead, each part keeping the\n * catalog's own order.\n * @param rows - the catalog in its display order.\n * @param favorited - predicate answering whether one row is favorited.\n * @returns a new list; the input order when nothing is favorited.\n */\nexport function orderWithFavorites<T>(\n rows: readonly T[],\n favorited: (row: T) => boolean,\n): readonly T[] {\n const starred = rows.filter(favorited)\n if (starred.length === 0) return rows\n return [...starred, ...rows.filter((row) => !favorited(row))]\n}\n\nfunction getItem(storage: FavoritesStorage | undefined): string | null {\n try {\n return storage?.getItem(FAVORITES_KEY) ?? null\n } catch {\n return null\n }\n}\n","/**\n * What the settings document knows about one model, for the picker's metadata\n * line.\n *\n * The Host's model catalog carries only a name and a reasoning list; context\n * window and accepted modalities live in each provider's settings profile,\n * which the settings mirror already holds in the browser. This module joins\n * the two by the identity both agree on: the catalog's provider route key and\n * model id are exactly the settings profile's provider key and model id.\n *\n * Every field is answered independently. A provider whose profile lists no\n * models (an OAuth route such as the Kimi subscription, or the account\n * provider) answers `unknown` rather than guessing the route default, because\n * the line's rule is to omit what cannot be asserted.\n */\n\n/** Accepted-modality answer for one model. */\nexport type ImageAnswer = 'yes' | 'no' | 'unknown'\n\n/**\n * The model knowledge a shipped adapter carries for its own account route.\n *\n * The account route has no `models` list on purpose: the account listing\n * supplies the catalog and settings only carry per-model overrides. The same\n * adapter family ships a default catalog for its API-key route, and the account\n * route serves the same models, so those entries answer what the account\n * profile cannot. Ids and facts mirror the adapter's own table.\n */\nconst FAMILY_DEFAULTS: Readonly<Record<string, readonly {\n readonly id: string\n readonly contextWindow: number\n readonly images: boolean\n}[]>> = {\n 'deepseek-account': [\n { id: 'deepseek-flash', contextWindow: 1_000_000, images: true },\n { id: 'deepseek-v4-pro', contextWindow: 1_000_000, images: false },\n ],\n}\n\n/** The metadata the settings document can answer for one model. */\nexport interface ModelFacts {\n /** Exact advertised capacity, or undefined when the model declares none. */\n readonly contextWindow: number | undefined\n /** Whether the model accepts images; `unknown` means the document is silent. */\n readonly images: ImageAnswer\n}\n\n/** One provider directory row, as the Host's LLM directory reports it. */\nexport interface ConfigurableProviderRow {\n readonly provider: string\n readonly settingsNs: string\n readonly settingsPath: readonly string[]\n}\n\n/** One settings namespace view, as the describe answer carries it. */\nexport interface NamespaceRow {\n readonly ns: string\n readonly value: unknown\n}\n\n/** What one provider's profile may carry, across both adapter families. */\ninterface ProfileView {\n readonly models?: readonly Record<string, unknown>[]\n readonly defaultContextWindow?: unknown\n readonly defaultInput?: unknown\n}\n\ntype Record_ = Record<string, unknown>\n\n/**\n * Build the fact lookup from the settings answer.\n * @param providers - provider directory rows giving each route's settings address.\n * @param namespaces - the describe answer's namespace views.\n * @returns a lookup answering both fields for one provider and model.\n */\nexport function buildModelFacts(\n providers: readonly ConfigurableProviderRow[],\n namespaces: readonly NamespaceRow[],\n): (provider: string, model: string) => ModelFacts {\n const byName = new Map(namespaces.map((row) => [row.ns, row.value]))\n const profiles = new Map<string, ProfileView>()\n for (const row of providers) {\n const section = byName.get(row.settingsNs)\n const profile = readPath(section, row.settingsPath)\n if (isRecord(profile)) profiles.set(row.provider, profile as ProfileView)\n }\n return (provider, model) => {\n const profile = profiles.get(provider)\n const entry = profile?.models === undefined\n ? undefined\n : profile.models.find((candidate) => candidate.id === model)\n if (profile !== undefined && entry !== undefined) {\n const window = typeof entry.contextWindow === 'number' && entry.contextWindow > 0\n ? entry.contextWindow\n : undefined\n return { contextWindow: window, images: imageAnswer(entry, profile) }\n }\n // A route with no model list of its own (the account route) still serves its\n // adapter family's models; a declared override there wins over the default.\n const override = readOverride(profile, model)\n if (override !== undefined) {\n const window = typeof override.contextWindow === 'number' && override.contextWindow > 0\n ? override.contextWindow\n : undefined\n return {\n contextWindow: window,\n images: Array.isArray(override.inputModalities) ? containsImage(override.inputModalities) : 'unknown',\n }\n }\n const family = FAMILY_DEFAULTS[provider]?.find((candidate) => candidate.id === model)\n return family === undefined\n ? { contextWindow: undefined, images: 'unknown' }\n : { contextWindow: family.contextWindow, images: family.images ? 'yes' : 'no' }\n }\n}\n\n/**\n * Read one model entry's accepted image modalities.\n *\n * The two adapter families disagree about an omission. The Messages family\n * names the field `inputModalities`, and leaving it out means text only. The\n * pi-ai family names it `input`, and an empty or absent list defers to the\n * route's `defaultInput`; with no route default the answer stays unknown.\n */\nfunction imageAnswer(entry: Record_, profile: ProfileView): ImageAnswer {\n if (Array.isArray(entry.inputModalities)) return containsImage(entry.inputModalities)\n const defaultInput = Array.isArray(profile.defaultInput) ? profile.defaultInput : undefined\n if (Array.isArray(entry.input)) {\n if (entry.input.length > 0) return containsImage(entry.input)\n return defaultInput === undefined ? 'unknown' : containsImage(defaultInput)\n }\n return defaultInput === undefined ? 'no' : containsImage(defaultInput)\n}\n\n/** One model's declared override, when the route keeps a `modelOverrides` table. */\nfunction readOverride(profile: ProfileView | undefined, model: string): Record_ | undefined {\n const overrides = (profile as Record_ | undefined)?.modelOverrides\n if (!isRecord(overrides)) return undefined\n const entry = overrides[model]\n return isRecord(entry) ? entry : undefined\n}\n\nfunction containsImage(modalities: readonly unknown[]): ImageAnswer {\n return modalities.includes('image') ? 'yes' : 'no'\n}\n\n/**\n * Abbreviate one capacity the way the metadata line shows it.\n * @param tokens - exact advertised capacity in tokens.\n * @returns e.g. `1M ctx`, `1.5M ctx`, `272k ctx`, or `512 ctx` below a thousand.\n */\nexport function formatContextWindow(tokens: number): string {\n if (tokens >= 1_000_000) return `${trimNumber(tokens / 1_000_000)}M ctx`\n if (tokens >= 1_000) return `${Math.round(tokens / 1_000)}k ctx`\n return `${tokens} ctx`\n}\n\n/**\n * Build the metadata line's segments.\n * @param input.providerName - the provider's display name, omitted inside that provider's own tab.\n * @param input.facts - what the settings document answered.\n * @param input.thinks - whether the catalog advertises reasoning levels for this model.\n * @param input.effortLabel - the localized word for the reasoning segment.\n * @param input.imageLabel - the localized word for the image segment.\n * @returns the segments to join; an empty list means show no line.\n */\nexport function metaSegments(input: {\n readonly providerName?: string\n readonly facts: ModelFacts\n readonly thinks: boolean\n readonly effortLabel: string\n readonly imageLabel: string\n}): readonly string[] {\n const segments: string[] = []\n if (input.providerName !== undefined) segments.push(input.providerName)\n if (input.facts.contextWindow !== undefined) segments.push(formatContextWindow(input.facts.contextWindow))\n if (input.thinks) segments.push(input.effortLabel)\n if (input.facts.images === 'yes') segments.push(input.imageLabel)\n return segments\n}\n\nfunction trimNumber(value: number): string {\n const rounded = Math.round(value * 10) / 10\n return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1)\n}\n\nfunction readPath(value: unknown, path: readonly string[]): unknown {\n let cursor = value\n for (const key of path) {\n if (!isRecord(cursor)) return undefined\n cursor = cursor[key]\n }\n return cursor\n}\n\nfunction isRecord(value: unknown): value is Record_ {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n","/**\n * Catalog flattening, tab derivation, and query matching for the picker.\n *\n * The model catalog groups models by provider; the picker shows one flat list\n * with a tab per provider, so both surfaces read from these rows. Matching\n * delegates to the shell's own ranker, which is why a query behaves the same\n * here as in the built-in menu: case-insensitive, ordered subsequence, prefix\n * hits first.\n */\n\nimport type { ModelDirectoryState } from '@deepseek-ai/dsh-client-ui-model-selection/client'\n\n/** One provider group of the shared catalog. */\nexport type ProviderGroup = ModelDirectoryState['groups'][number]\n\n/** One model inside a provider group. */\nexport type CatalogModel = ProviderGroup['models'][number]\n\n/** One flat catalog row, carrying the provider identity beside the model. */\nexport interface ModelRow extends ModelRef {\n readonly providerName: string\n /** The model id inside its provider, the other half of a selection. */\n readonly model: string\n /** The catalog entry itself, for name and reasoning metadata. */\n readonly entry: CatalogModel\n}\n\n/** A provider and one of its models: the pair every selection and favorite names. */\nexport interface ModelRef {\n readonly provider: string\n readonly model: string\n}\n\n/** One picker tab. */\nexport interface PickerTab {\n readonly id: string\n readonly label: string\n}\n\n/** The tab id standing for every provider. */\nexport const ALL_TAB = '*'\n\n/**\n * Flatten the catalog to one row per model.\n * @param groups - the shared directory's provider groups, in display order.\n * @returns the rows in catalog order.\n */\nexport function flattenGroups(groups: readonly ProviderGroup[]): readonly ModelRow[] {\n return groups.flatMap((group) => group.models.map((model) => ({\n provider: group.id,\n providerName: group.name,\n model: model.id,\n entry: model,\n })))\n}\n\n/**\n * Build the picker's tabs: the all-tab, then one per provider that has a model.\n * @param groups - the shared directory's provider groups.\n * @param allLabel - localized label for the all-tab.\n * @returns the tabs in catalog order.\n */\nexport function pickerTabs(groups: readonly ProviderGroup[], allLabel = 'All'): readonly PickerTab[] {\n return [\n { id: ALL_TAB, label: allLabel },\n ...groups.filter((group) => group.models.length > 0).map((group) => ({ id: group.id, label: group.name })),\n ]\n}\n\n/**\n * @param rows - the flat catalog.\n * @param tab - the tab id; the all-tab keeps every row.\n * @returns the rows the tab shows, in their current order.\n */\nexport function rowsForTab(rows: readonly ModelRow[], tab: string): readonly ModelRow[] {\n return tab === ALL_TAB ? rows : rows.filter((row) => row.provider === tab)\n}\n\n/** One row handed to the shell's ranker, carrying both searchable keys. */\nexport interface SearchCandidate {\n readonly name: string\n readonly label: string\n readonly row: ModelRow\n}\n\n/**\n * Shape rows as the shell's ranker candidates: the model name is the name, the\n * provider display name is the second key, so a query reaches either.\n * @param rows - the rows to search.\n * @returns candidates in row order.\n */\nexport function searchCandidates(rows: readonly ModelRow[]): readonly SearchCandidate[] {\n return rows.map((row) => ({ name: row.entry.name, label: row.providerName, row }))\n}\n\n/**\n * Read the reasoning metadata the catalog advertises for one model.\n * @param groups - the shared directory's provider groups.\n * @returns the metadata, or undefined when that model advertises none.\n */\nexport function reasoningOf(\n groups: readonly ProviderGroup[],\n): (provider: string, model: string) => CatalogModel['reasoning'] {\n return (provider, model) => groups\n .find((group) => group.id === provider)\n ?.models.find((candidate) => candidate.id === model)\n ?.reasoning\n}\n\n/**\n * Filter rows by a query over the model name and the provider name.\n * @param rows - the rows to search.\n * @param query - the typed text; an empty or blank query keeps every row.\n * @param rank - the shell's own ranker, so this search behaves like every other one.\n * @returns the matching rows.\n */\nexport function searchRows(\n rows: readonly ModelRow[],\n query: string,\n rank: (items: readonly SearchCandidate[], query: string) => readonly SearchCandidate[],\n): readonly ModelRow[] {\n const trimmed = query.trim()\n if (trimmed === '') return rows\n return rank(searchCandidates(rows), trimmed).map((match) => match.row)\n}\n","/**\n * The model picker: the centered dialog opened from the menu's last row.\n *\n * It is the one place every provider is visible at once — a search field, a tab\n * per provider with a model, and one flat list with each model's metadata line.\n * Favorites lead the list and can be toggled from a row's star without\n * selecting that row. A click commits at once; a rejected commit keeps the\n * dialog open so the user can retry or pick another model.\n */\n\nimport { useEffect, useId, useMemo, useRef, useState } from 'react'\nimport {\n IconSearchOutlineRegular,\n Input,\n Modal,\n ShortcutKeys,\n rankByName,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { ModelDirectoryState } from '@deepseek-ai/dsh-client-ui-model-selection/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport { ModelRow } from './ModelRow.tsx'\nimport { isFavorite, orderWithFavorites } from './favorites.ts'\nimport type { ModelFacts } from './model-facts.ts'\nimport { metaSegments } from './model-facts.ts'\nimport {\n ALL_TAB,\n flattenGroups,\n pickerTabs,\n reasoningOf,\n rowsForTab,\n searchRows,\n type CatalogModel,\n type ModelRow as CatalogRow,\n type PickerTab,\n} from './rows.ts'\nimport { Tag, isMobileDevice } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { ModelFactsLookup } from './facts-source.ts'\nimport type { Selection } from './selection.ts'\nimport { planModelCommit } from './selection.ts'\nimport css from './ModelOptions.module.css'\n\n/** Props the seat hands the picker. */\nexport interface ModelPickerProps {\n /** Whether the dialog is showing. */\n readonly open: boolean\n /** Resolve a catalog badge's localized text for the active language. */\n readonly resolveText: (text: NonNullable<CatalogModel['badge']>['text']) => string\n /** The shared directory snapshot. */\n readonly state: ModelDirectoryState\n /** The selection in force, null before a first selection resolves. */\n readonly live: Selection | null\n /** The metadata lookup for the current settings answer. */\n readonly facts: ModelFactsLookup\n /** The favorite table right now. */\n readonly favorites: readonly { readonly provider: string; readonly model: string }[]\n /** Whether a selection is in flight. */\n readonly busy: boolean\n /**\n * The reasoning level this browser last used for one model, if any.\n * @param provider - the provider route key.\n * @param model - the model id inside that provider.\n * @returns the remembered level, or undefined when none is remembered.\n */\n readonly rememberedOf?: (provider: string, model: string) => string | undefined\n /** Submit one selection. */\n readonly onPick: (selection: Selection) => void\n /** Toggle one model's favorite. */\n readonly onToggleFavorite: (provider: string, model: string) => void\n /** Reload the catalog after a failure. */\n readonly onReload: () => void\n /** Close the dialog, optionally returning focus. */\n readonly onClose: () => void\n}\n\n/**\n * Render the picker.\n * @param props - the catalog, the live selection, and the picker's verbs.\n * @returns the dialog; nothing while closed.\n */\nexport function ModelPicker({\n open, resolveText, state, live, facts, favorites, busy, rememberedOf, onPick, onToggleFavorite, onReload, onClose, t,\n}: ModelPickerProps & PropsLocale<'modelSubmenu'>) {\n const id = useId()\n const searchRef = useRef<HTMLInputElement | null>(null)\n const listRef = useRef<HTMLDivElement | null>(null)\n const [query, setQuery] = useState('')\n const [tab, setTab] = useState(ALL_TAB)\n const [highlight, setHighlight] = useState(0)\n const tabRefs = useRef(new Map<string, HTMLButtonElement>())\n\n const tabs = useMemo(() => pickerTabs(state.groups, t('picker.all')), [state.groups, t])\n const reason = useMemo(() => reasoningOf(state.groups), [state.groups])\n const rows = useMemo(() => orderWithFavorites(\n rowsForTab(flattenGroups(state.groups), tab),\n (row) => isFavorite(favorites, row.provider, row.model),\n ), [state.groups, tab, favorites])\n // Search runs inside the favorite partition, never across it: a query must not\n // pull an unstarred model above a starred one, because favorites lead the list\n // whether or not a query is typed.\n const visible = useMemo(() => orderWithFavorites(\n searchRows(rows, query, rankByName),\n (row) => isFavorite(favorites, row.provider, row.model),\n ), [rows, query, favorites])\n\n useEffect(() => {\n if (!open) return\n setQuery('')\n setTab(ALL_TAB)\n setHighlight(0)\n if (listRef.current !== null) listRef.current.scrollTop = 0\n // 粗指针设备不自动聚焦文本输入:移动端弹键盘会顶起布局,宿主座位也这样守。\n const frame = requestAnimationFrame(() => {\n if (!isMobileDevice()) searchRef.current?.focus()\n })\n return () => { cancelAnimationFrame(frame) }\n }, [open])\n\n useEffect(() => {\n setHighlight((current) => visible.length === 0 ? 0 : Math.min(current, visible.length - 1))\n }, [visible.length])\n\n useEffect(() => {\n const row = listRef.current?.querySelector<HTMLElement>(`[data-row-index=\"${highlight}\"]`)\n row?.scrollIntoView({ block: 'nearest' })\n }, [highlight])\n\n if (!open) return null\n\n const activeRow = visible[highlight]\n const activeId = activeRow === undefined ? undefined : `${id}-row-${highlight}`\n const onSearchKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault()\n if (visible.length === 0) return\n setHighlight((current) => {\n const next = current + (event.key === 'ArrowDown' ? 1 : -1)\n return (next + visible.length) % visible.length\n })\n return\n }\n if (event.key === 'Enter' && activeRow !== undefined && !busy) {\n event.preventDefault()\n pick(activeRow)\n }\n }\n const pick = (row: CatalogRow) => {\n const selection = planModelCommit(live, row.provider, row.model, reason, rememberedOf?.(row.provider, row.model))\n if (selection === null) {\n onClose()\n return\n }\n onPick(selection)\n }\n /** Move to another tab and hand it the keyboard, in one step. */\n const goTab = (next: PickerTab | undefined) => {\n if (next === undefined || next.id === tab) return\n setTab(next.id)\n setHighlight(0)\n tabRefs.current.get(next.id)?.focus()\n }\n const onTabKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {\n const index = tabs.findIndex((candidate) => candidate.id === tab)\n if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {\n event.preventDefault()\n const step = event.key === 'ArrowRight' ? 1 : -1\n goTab(tabs[((index === -1 ? 0 : index) + step + tabs.length) % tabs.length])\n return\n }\n if (event.key === 'Home' || event.key === 'End') {\n event.preventDefault()\n goTab(tabs[event.key === 'Home' ? 0 : tabs.length - 1])\n }\n }\n\n const failures = state.failures.filter((failure) => tab === ALL_TAB || failure.id === tab)\n const failed = state.error !== null && state.status === 'error'\n const loading = state.status === 'loading'\n return (\n <Modal\n open={open}\n onClose={onClose}\n title={t('picker.title')}\n closeLabel={t('picker.close')}\n className={css.pickerDialog ?? ''}\n contentClassName={css.pickerContent ?? ''}\n >\n <div className={css.pickerSearch}>\n <Input\n ref={searchRef}\n className={css.pickerSearchInput ?? ''}\n type=\"text\"\n role=\"searchbox\"\n aria-label={t('picker.search')}\n aria-controls={`${id}-list`}\n aria-activedescendant={activeId}\n placeholder={t('picker.search')}\n icon={<IconSearchOutlineRegular size={14} />}\n value={query}\n readOnly={busy}\n onChange={(event) => { setQuery(event.target.value) }}\n onKeyDown={onSearchKeyDown}\n data-modal-autofocus=\"\"\n />\n </div>\n <div className={css.pickerTabs} role=\"tablist\" aria-label={t('picker.title')}>\n {tabs.map((entry) => (\n <button\n key={entry.id}\n ref={(node) => {\n if (node === null) tabRefs.current.delete(entry.id)\n else tabRefs.current.set(entry.id, node)\n }}\n type=\"button\"\n role=\"tab\"\n id={`${id}-tab-${entry.id}`}\n aria-selected={entry.id === tab}\n aria-controls={`${id}-list`}\n tabIndex={0}\n className={css.pickerTab}\n onClick={() => {\n setTab(entry.id)\n setHighlight(0)\n }}\n onKeyDown={onTabKeyDown}\n onFocus={() => {\n if (entry.id !== tab) {\n setTab(entry.id)\n setHighlight(0)\n }\n }}\n >\n {entry.label}\n </button>\n ))}\n </div>\n <div\n ref={listRef}\n id={`${id}-list`}\n className={css.pickerList}\n role=\"menu\"\n aria-label={t('picker.title')}\n >\n {loading && <div className={css.notice}>{t('status.loading')}</div>}\n {failed && (\n <div className={css.notice}>\n <span>{t('status.failed', { message: state.error ?? '' })}</span>\n <button type=\"button\" className={css.retry} onClick={onReload}>{t('action.reload')}</button>\n </div>\n )}\n {failures.map((failure) => (\n <div className={css.notice} key={failure.id}>\n <span>{t('status.providerFailed', { name: failure.name, message: failure.message })}</span>\n <button type=\"button\" className={css.retry} onClick={onReload}>{t('action.reload')}</button>\n </div>\n ))}\n {visible.length === 0 && !loading && !failed && (\n <div className={css.notice}>{state.groups.length === 0 ? t('status.empty') : t('picker.searchEmpty')}</div>\n )}\n {visible.map((row, index) => (\n <div key={`${row.provider}/${row.model}`} data-row-index={index} id={`${id}-row-${index}`}>\n <ModelRow\n name={row.entry.name}\n badge={row.entry.badge === undefined\n ? undefined\n : <Tag tone={row.entry.badge.tone} className={css.rowBadge}>{resolveText(row.entry.badge.text)}</Tag>}\n selected={live?.provider === row.provider && live.model === row.model}\n busy={busy}\n favorited={isFavorite(favorites, row.provider, row.model)}\n meta={metaLine({\n row,\n facts: facts(row.provider, row.model),\n providerName: tab === ALL_TAB ? row.providerName : undefined,\n thinks: row.entry.reasoning !== undefined,\n effortLabel: t('meta.effort'),\n imageLabel: t('meta.images'),\n })}\n starLabel={isFavorite(favorites, row.provider, row.model)\n ? t('picker.unfavorite', { model: row.entry.name })\n : t('picker.favorite', { model: row.entry.name })}\n starTabStop={index === highlight}\n rowTabIndex={-1}\n onPick={() => { pick(row) }}\n onFocusRow={() => { setHighlight(index) }}\n onToggleFavorite={() => { onToggleFavorite(row.provider, row.model) }}\n />\n </div>\n ))}\n </div>\n <div className={css.pickerFooter}>\n <span className={css.footerHint}>\n <ShortcutKeys keys={['↑', '↓']} />\n <span>{t('picker.hintNavigate')}</span>\n <span className={css.footerDot}>·</span>\n <ShortcutKeys keys={['Enter']} />\n <span>{t('picker.hintSelect')}</span>\n <span className={css.footerDot}>·</span>\n <ShortcutKeys keys={['Esc']} />\n <span>{t('picker.hintClose')}</span>\n </span>\n </div>\n </Modal>\n )\n}\n\nfunction metaLine(input: {\n readonly row: CatalogRow\n readonly facts: ModelFacts\n readonly providerName: string | undefined\n readonly thinks: boolean\n readonly effortLabel: string\n readonly imageLabel: string\n}): string {\n return metaSegments({\n ...input.providerName === undefined ? {} : { providerName: input.providerName },\n facts: input.facts,\n thinks: input.thinks,\n effortLabel: input.effortLabel,\n imageLabel: input.imageLabel,\n }).join(' · ')\n}\n\n","/**\n * The per-model reasoning level this browser last chose.\n *\n * The Host stores one level per session, so switching models away and back\n * forgets what was picked for the first one. This module keeps that memory\n * beside the favorite table, keyed the same way: provider route key plus model\n * id. Remembering the provider default removes the row, so \"follow the\n * provider\" stays distinguishable from \"remembered a level\".\n */\n\n/** One remembered reasoning level. */\nexport interface RememberedEffort {\n readonly provider: string\n readonly model: string\n readonly effort: string\n}\n\n/** The single browser-wide storage key holding the whole memory. */\nexport const EFFORTS_KEY = 'dsh-model-submenu.efforts'\n\n/** The part of `Storage` this module uses; absent or throwing reads mean \"none\". */\nexport type EffortStorage = Pick<Storage, 'getItem' | 'setItem'>\n\n/**\n * Read the memory, keeping only rows naming both halves and a level.\n * @param storage - browser storage, or undefined when the browser withholds it.\n * @returns the remembered rows; empty when unreadable.\n */\nexport function readEfforts(storage: EffortStorage | undefined): RememberedEffort[] {\n const raw = getItem(storage)\n if (raw === null) return []\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return []\n }\n if (!Array.isArray(parsed)) return []\n return parsed.filter((row): row is RememberedEffort => typeof row === 'object' && row !== null\n && typeof (row as RememberedEffort).provider === 'string'\n && typeof (row as RememberedEffort).model === 'string'\n && typeof (row as RememberedEffort).effort === 'string'\n && (row as RememberedEffort).effort !== '')\n}\n\n/**\n * @param memory - the rows to search.\n * @param provider - the provider route key.\n * @param model - the model id inside that provider.\n * @returns the level last chosen for that exact model, or undefined.\n */\nexport function effortFor(\n memory: readonly RememberedEffort[],\n provider: string,\n model: string,\n): string | undefined {\n return memory.find((row) => row.provider === provider && row.model === model)?.effort\n}\n\n/**\n * Remember one model's level, or forget it when the choice is the provider default.\n * @param storage - browser storage, or undefined when writes are unavailable.\n * @param provider - the provider route key.\n * @param model - the model id inside that provider.\n * @param effort - the chosen level, undefined meaning the provider default.\n * @returns the memory after the change, whether or not the write landed.\n */\nexport function rememberEffort(\n storage: EffortStorage | undefined,\n provider: string,\n model: string,\n effort: string | undefined,\n): RememberedEffort[] {\n const rest = readEfforts(storage).filter((row) => row.provider !== provider || row.model !== model)\n const next = effort === undefined ? rest : [...rest, { provider, model, effort }]\n try {\n storage?.setItem(EFFORTS_KEY, JSON.stringify(next))\n } catch {\n // A full quota or a locked-down browser loses the memory, not the menu.\n }\n return next\n}\n\nfunction getItem(storage: EffortStorage | undefined): string | null {\n try {\n return storage?.getItem(EFFORTS_KEY) ?? null\n } catch {\n return null\n }\n}\n","/**\n * The composer overlay's picker view: the dialog, its catalog, and its verbs.\n *\n * It lives in `conversation.input.overlay` rather than beside the row, because\n * the Host unmounts the model menu as soon as a row is clicked. Everything the\n * dialog reads arrives through this entry's injected face.\n */\nimport { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { IconWarningOutlineRegular, Toast } from '@deepseek-ai/dsh-client-ui-primitives';\nimport type { ModelSelectInjected } from '@deepseek-ai/dsh-client-ui-model-selection/client';\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';\nimport { ModelPicker } from './ModelPicker.tsx';\nimport type { FactsSource } from './facts-source.ts';\nimport {\n readFavorites,\n toggleFavorite,\n type FavoriteModel,\n type FavoritesStorage,\n} from './favorites.ts';\nimport {\n effortFor,\n readEfforts,\n rememberEffort,\n type RememberedEffort,\n} from './effort-memory.ts';\nimport { selectionOf, type Selection } from './selection.ts';\nimport type { PickerStore } from './picker-state.ts';\nimport css from './ModelOptions.module.css';\n\n/** Props of the overlay view: the session, the store, and the seat's injected face. */\nexport type ModelPickerHostProps = PropsLocale<'modelSubmenu'>\n & ModelSelectInjected\n & {\n /** Session whose catalog this view renders. */\n readonly sessionId: string;\n /** The request written by the menu row. */\n readonly picker: PickerStore;\n /** The settings-derived metadata the picker's line reads. */\n readonly factsSource: FactsSource;\n }\n\n/**\n * Render the picker for this session.\n * @param props - session, store, catalog face, and localized copy.\n * @returns the dialog while the store names this session; otherwise nothing.\n */\nexport function ModelPickerHost({\n sessionId, picker, available, directory, load, select, resolveText, factsSource, t,\n}: ModelPickerHostProps) {\n const request = useSyncExternalStore(picker.subscribe, picker.getSnapshot);\n const state = useSyncExternalStore((listener) => directory.subscribe(listener), () => directory.getSnapshot());\n const [favorites, setFavorites] = useState<readonly FavoriteModel[]>(() => readFavorites(storageOf()));\n const [memory, setMemory] = useState<readonly RememberedEffort[]>(() => readEfforts(storageOf()));\n const [toast, setToast] = useState<{ seq: number; text: string } | null>(null);\n const toastSeq = useRef(0);\n const rootRef = useRef<HTMLDivElement | null>(null);\n\n const busy = state.status === 'selecting';\n const live = useMemo(() => selectionOf(state), [state]);\n const facts = useSyncExternalStore((listener) => factsSource.subscribe(listener), () => factsSource.getSnapshot());\n const open = request.sessionId === sessionId;\n // The injected face is cached per entry and session, but the read goes through\n // a ref anyway: a catalog load writes the store, so an unstable `load` in the\n // dependency list would reload on every render it caused.\n const loadRef = useRef(load);\n loadRef.current = load;\n\n // Opening reads the catalog again: another client may have changed it, and a\n // failed load has to be retryable from the same gesture.\n useEffect(() => {\n if (open) loadRef.current();\n }, [open]);\n\n // The Host's own menu changes the reasoning level without passing through this\n // plugin, so the memory is written from the directory snapshot too — a picker\n // click alone would only ever record levels chosen in the dialog. The key guard\n // keeps one write per distinct selection instead of one per snapshot.\n const current = state.current;\n const rememberedRef = useRef<string | null>(null);\n useEffect(() => {\n if (current === null) return;\n const key = `${current.provider}/${current.model}/${current.reasoningEffort ?? ''}`;\n if (rememberedRef.current === key) return;\n rememberedRef.current = key;\n setMemory(rememberEffort(storageOf(), current.provider, current.model, current.reasoningEffort));\n }, [current]);\n\n if (!available) return null;\n\n const raiseToast = (text: string): void => {\n toastSeq.current += 1;\n setToast({ seq: toastSeq.current, text });\n };\n const submit = (selection: Selection): void => {\n void select(selection).then((result) => {\n if (result === undefined || result.ok) {\n setMemory(rememberEffort(storageOf(), selection.provider, selection.model, selection.reasoningEffort));\n picker.close();\n return;\n }\n raiseToast(result.error.code === 'session/writer-held'\n ? t('error.sessionInUse')\n : t('error.action', { message: `${result.error.code}: ${result.error.message}` }));\n });\n };\n\n return <div ref={rootRef} className={css.pickerHost}>\n <ModelPicker\n open={open}\n resolveText={resolveText}\n state={state}\n live={live}\n facts={facts}\n favorites={favorites}\n busy={busy}\n t={t}\n rememberedOf={(provider, model) => effortFor(memory, provider, model)}\n onPick={submit}\n onToggleFavorite={(provider, model) => {\n setFavorites(toggleFavorite(storageOf(), provider, model));\n }}\n onReload={load}\n onClose={() => {\n picker.close();\n }}\n />\n {toast !== null && <Toast\n key={toast.seq}\n text={toast.text}\n icon={<IconWarningOutlineRegular />}\n anchor={rootRef.current?.closest('[data-composer-card]') ?? null}\n onDone={() => {\n setToast(null);\n }}\n />}\n </div>;\n}\n\n/** Isolate the browser's storage so a locked-down browser reads as \"no favorites\". */\nfunction storageOf(): FavoritesStorage | undefined {\n try {\n return globalThis.localStorage;\n } catch {\n return undefined;\n }\n}\n","/**\n * Which session's picker is open.\n *\n * The row that asks for the picker lives inside the composer model menu, and the\n * Host unmounts that menu the moment a row is clicked. The dialog lives in the\n * composer's overlay slot, which stays mounted. One plugin-wide store carries the\n * request across that unmount — the same split the Host's own command popups use\n * between their controller and their overlay view.\n */\n\n/** What the store publishes. */\nexport interface PickerSnapshot {\n /** Session whose picker is open, or null when none is. */\n readonly sessionId: string | null\n}\n\n/** Open and close verbs plus the observable the overlay view subscribes to. */\nexport interface PickerStore {\n /** @returns the current request. */\n readonly getSnapshot: () => PickerSnapshot\n /** @param listener - called after every change. @returns the unsubscribe. */\n readonly subscribe: (listener: () => void) => () => void\n /** @param sessionId - open the picker for one session, replacing any other. */\n readonly open: (sessionId: string) => void\n /** Close whatever is open. */\n readonly close: () => void\n}\n\n/**\n * @returns a store that starts closed.\n */\nexport function createPickerStore(): PickerStore {\n let snapshot: PickerSnapshot = { sessionId: null };\n const listeners = new Set<() => void>();\n const publish = (next: PickerSnapshot): void => {\n if (next.sessionId === snapshot.sessionId) return;\n snapshot = next;\n for (const listener of listeners) listener();\n };\n return {\n getSnapshot: () => snapshot,\n subscribe: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n open: (sessionId) => {\n publish({ sessionId });\n },\n close: () => {\n publish({ sessionId: null });\n },\n };\n}\n","/**\n * The browser's live answer to \"what does the settings document know about this\n * model?\".\n *\n * Two host answers meet here: the shared settings mirror (which the settings\n * page already keeps warm) and the LLM provider directory that says which\n * settings namespace holds each provider's profile. Both can change while the\n * page is open — a saved profile, a reconnected host — so the join is rebuilt\n * whenever the mirror or the directory reports a change, and readers subscribe\n * to the lookup itself.\n */\n\nimport type { ModelFacts } from './model-facts.ts'\nimport {\n buildModelFacts,\n type ConfigurableProviderRow,\n type NamespaceRow,\n} from './model-facts.ts'\n\n/** One lookup answering both metadata fields for a provider and model. */\nexport type ModelFactsLookup = (provider: string, model: string) => ModelFacts\n\n/** The mirror face this source reads; a subset of the settings provider's. */\nexport interface DescribeFace {\n getSnapshot(): { readonly view: { readonly namespaces: readonly NamespaceRow[] } | undefined }\n subscribe(listener: () => void): () => void\n ensure(): Promise<void>\n}\n\n/** One remote answer, as the gateway returns it. */\nexport interface DirectoryAnswer {\n readonly ok: boolean\n readonly value?: readonly ConfigurableProviderRow[]\n}\n\n/** A stable, subscribable lookup over the current settings answer. */\nexport interface FactsSource {\n /** @returns the lookup for the current answer; the identity changes on every rebuild. */\n getSnapshot(): ModelFactsLookup\n /** @param listener - called after each rebuild. @returns the disposer. */\n subscribe(listener: () => void): () => void\n}\n\nconst EMPTY: ModelFactsLookup = () => ({ contextWindow: undefined, images: 'unknown' })\n\n/**\n * Join the settings mirror with the provider directory.\n * @param deps.describe - the settings mirror's describe face.\n * @param deps.listProviders - reads the LLM provider directory once.\n * @returns the source, plus `start` which wires both reads and returns its disposer.\n */\nexport function createFactsSource(deps: {\n readonly describe: DescribeFace\n readonly listProviders: () => Promise<DirectoryAnswer>\n}): { readonly source: FactsSource; readonly start: () => () => void } {\n const listeners = new Set<() => void>()\n let providers: readonly ConfigurableProviderRow[] = []\n let lookup: ModelFactsLookup = EMPTY\n\n const rebuild = () => {\n const namespaces = deps.describe.getSnapshot().view?.namespaces ?? []\n lookup = providers.length === 0 || namespaces.length === 0\n ? EMPTY\n : buildModelFacts(providers, namespaces)\n for (const listener of [...listeners]) listener()\n }\n\n return {\n source: {\n getSnapshot: () => lookup,\n subscribe: (listener) => {\n listeners.add(listener)\n return () => { listeners.delete(listener) }\n },\n },\n start: () => {\n const off = deps.describe.subscribe(rebuild)\n void deps.describe.ensure().catch(() => { /* a failed read leaves the empty lookup in place */ })\n void deps.listProviders().then(\n (answer) => {\n providers = answer.ok && answer.value !== undefined ? answer.value : []\n rebuild()\n },\n () => { /* the directory being unavailable leaves every model's facts unknown */ },\n )\n rebuild()\n return () => {\n off()\n listeners.clear()\n }\n },\n }\n}\n","/** Locale bundles for the all-provider model picker. */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'modelSubmenu'\n\n/** Locale keys the picker renders. */\nexport type ModelSubmenuLocaleKey =\n | 'status.loading'\n | 'status.empty'\n | 'status.failed'\n | 'status.providerFailed'\n | 'action.reload'\n | 'error.action'\n | 'error.sessionInUse'\n | 'picker.title'\n | 'picker.close'\n | 'picker.search'\n | 'picker.searchEmpty'\n | 'picker.all'\n | 'picker.favorite'\n | 'picker.unfavorite'\n | 'picker.hintNavigate'\n | 'picker.hintSelect'\n | 'picker.hintClose'\n | 'meta.effort'\n | 'meta.images'\n | 'effort.label'\n | 'effort.providerDefault'\n | 'effort.useDefault'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The all-provider model picker's copy. */\n modelSubmenu: ModelSubmenuLocaleKey\n }\n}\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh: Record<ModelSubmenuLocaleKey, string> = {\n 'status.loading': '正在加载模型…',\n 'status.empty': '没有可用的模型。',\n 'status.failed': '模型列表加载失败:{message}',\n 'status.providerFailed': '{name} 加载失败:{message}',\n 'action.reload': '重新加载',\n 'error.action': '模型操作失败:{message}',\n 'error.sessionInUse': '当前会话已被占用,可能是其他正在运行的 DSH 导致的(如其他 dsh web、桌面端),请退出其他正在运行的 DSH 后重试。',\n 'picker.title': '切换模型',\n 'picker.close': '关闭',\n 'picker.search': '搜索模型或提供商…',\n 'picker.searchEmpty': '没有匹配的模型。',\n 'picker.all': '全部',\n 'picker.favorite': '收藏 {model}',\n 'picker.unfavorite': '取消收藏 {model}',\n 'picker.hintNavigate': '导航',\n 'picker.hintSelect': '选择',\n 'picker.hintClose': '关闭',\n 'meta.effort': '思考',\n 'meta.images': '图片输入',\n 'effort.label': '推理等级',\n 'effort.providerDefault': 'Default',\n 'effort.useDefault': '切换为默认档位',\n}\n\n/** English dictionary, checked complete against the zh key set. */\nexport const en: Record<ModelSubmenuLocaleKey, string> = {\n 'status.loading': 'Loading models…',\n 'status.empty': 'No models available.',\n 'status.failed': 'Model list failed to load: {message}',\n 'status.providerFailed': '{name} failed to load: {message}',\n 'action.reload': 'Reload',\n 'error.action': 'Model operation failed: {message}',\n '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.',\n 'picker.title': 'Switch model',\n 'picker.close': 'Close',\n 'picker.search': 'Search models or providers…',\n 'picker.searchEmpty': 'No matching models.',\n 'picker.all': 'All',\n 'picker.favorite': 'Favorite {model}',\n 'picker.unfavorite': 'Remove {model} from favorites',\n 'picker.hintNavigate': 'navigate',\n 'picker.hintSelect': 'select',\n 'picker.hintClose': 'close',\n 'meta.effort': 'Effort',\n 'meta.images': 'Images',\n 'effort.label': 'Reasoning effort',\n 'effort.providerDefault': 'Default',\n 'effort.useDefault': 'Switch to the provider default',\n}\n","import type { Context as ClientContext } from '@deepseek-ai/cordis'\n// Type-only: `slots` and its LocaleNamespaceMap merge come from the renderer and\n// the slot package; the model menu's options slot and the composer overlay from\n// ui-conversation; the `modelDirectories` service and the seat's injected face\n// from ui-model-selection's browser half; `configForms` from the settings\n// provider, whose shared mirror answers the metadata line.\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-model-selection/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { EffortTint } from './EffortTint.tsx'\nimport { ModelMenuBridge } from './ModelMenuBridge.tsx'\nimport { ModelPickerHost } from './ModelPickerHost.tsx'\nimport { createPickerStore } from './picker-state.ts'\nimport { createFactsSource, type DescribeFace } from './facts-source.ts'\nimport { NS, en, zh } from './locales.ts'\n\nexport { EffortTint } from './EffortTint.tsx'\nexport { ModelMenuBridge } from './ModelMenuBridge.tsx'\nexport { ModelPickerHost } from './ModelPickerHost.tsx'\nexport { createPickerStore } from './picker-state.ts'\n\n/**\n * Cordis services required by the browser half. `slots` is the contribution\n * registry, `sessions` tells whether a session may select a model,\n * `modelDirectories` owns the per-session catalog snapshot both entries render\n * from, and `configForms` carries the settings provider's shared describe\n * mirror, which answers each model's context window and accepted modalities.\n * `remote`, `remote.llm`, and `remote.session` are declared because a service\n * method runs on the calling context: `directoryFor` reads\n * `this.ctx.remote.session`, and the provider directory that names each\n * settings address is read through `this.ctx.remote.llm`.\n */\nexport const inject = [\n 'configForms',\n 'locale',\n 'modelDirectories',\n 'remote',\n 'remote.llm',\n 'remote.session',\n 'sessions',\n 'slots',\n]\n\n/**\n * Client plugin body: register this plugin's dictionaries, then contribute the\n * bridge that turns the Host's model cell into the picker's door and its effort\n * cell into the reasoning-level slider, the overlay that renders the picker, and\n * the tint that keeps the collapsed trigger's effort caption on the same color.\n *\n * The two are separate entries because the Host unmounts the model menu the\n * moment a row is clicked; the overlay slot stays mounted, so the dialog\n * survives the menu's teardown.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-model-submenu: dictionaries')\n ctx.inject(['slots', 'modelDirectories', 'configForms', 'remote'], (scope) => {\n const models = scope.modelDirectories\n const sessions = scope.sessions\n const picker = createPickerStore()\n const facts = createFactsSource({\n describe: scope.configForms.describe() as DescribeFace,\n listProviders: () => scope.remote.llm.listConfigurableProviders(),\n })\n ctx.effect(() => facts.start(), 'dsh-model-submenu: model metadata')\n /**\n * Build one entry's business face for a session.\n * @param sessionId - the slot's session scope.\n * @returns the catalog face plus the session and store both entries need.\n */\n const faceOf = (sessionId: string) => {\n // The slot's inject parameter is typed as a bare string; the service\n // faces carry the branded SessionId of that same runtime value.\n const key = sessionId as Parameters<typeof models.directoryFor>[0]\n const directory = models.directoryFor(key)\n const available = sessions.subagentAddress(key) === undefined\n return {\n sessionId,\n picker,\n available,\n directory: directory.store,\n factsSource: facts.source,\n load: () => {\n if (available) directory.load().catch(() => {})\n },\n resolveText: (text: Parameters<typeof ctx.locale.resolveText>[0]) => ctx.locale.resolveText(text),\n select: (selection: Parameters<typeof directory.select>[0]) => available\n ? directory.select(selection)\n : Promise.resolve(undefined),\n }\n }\n scope.slots.inject('conversation.input.model.options', () => scope.slots.register({\n name: 'conversation.input.model.options',\n id: 'model-menu-bridge',\n registrant: '@lynn123411/dsh-model-submenu',\n locale: NS,\n inject: faceOf,\n }, ModelMenuBridge))\n scope.slots.inject('conversation.input.overlay', () => scope.slots.register({\n name: 'conversation.input.overlay',\n id: 'model-picker',\n registrant: '@lynn123411/dsh-model-submenu',\n locale: NS,\n inject: faceOf,\n }, ModelPickerHost))\n // The trigger's effort caption lives outside the menu, so its tint needs an\n // entry that stays mounted for the whole session.\n scope.slots.inject('conversation.input.overlay', () => scope.slots.register({\n name: 'conversation.input.overlay',\n id: 'model-effort-tint',\n registrant: '@lynn123411/dsh-model-submenu',\n inject: faceOf,\n }, EffortTint))\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["useRef","useState","useSyncExternalStore","useRef","useState","css","useRef","useState","useSyncExternalStore","useMemo","createPortal","css","css","IconCheckOutlineRegular","getItem","useId","useRef","useState","useMemo","rankByName","isMobileDevice","Modal","css","Input","IconSearchOutlineRegular","Tag","ShortcutKeys","useSyncExternalStore","useState","useRef","useMemo","css","Toast","IconWarningOutlineRegular"],"sources":["../src/client/level-color.ts","../src/client/selection.ts","../src/client/EffortTint.tsx","../src/client/slider.ts","../src/client/EffortSlider.tsx","../src/client/ModelMenuBridge.tsx","../src/client/icons.tsx","../src/client/ModelRow.tsx","../src/client/favorites.ts","../src/client/model-facts.ts","../src/client/rows.ts","../src/client/ModelPicker.tsx","../src/client/effort-memory.ts","../src/client/ModelPickerHost.tsx","../src/client/picker-state.ts","../src/client/facts-source.ts","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * The accent one reasoning level carries.\n *\n * \"Off\" carries no accent at all — the fill and the caption stay neutral, so\n * switching reasoning off reads as switching it off rather than as the first\n * shade of a ramp. Every level above it walks a blue-to-violet ramp, and the\n * highest level is the only one whose fill is a gradient.\n */\n\n/** Off: the dimmed label tone, the same neutral the Host uses for quiet text. */\nconst NEUTRAL = 'var(--dsw-alias-label-dimmed)';\n\n/** Lowest lit level: the blue the composer already uses for accents. */\nconst LOW = [77, 147, 248] as const;\n\n/** Highest level: violet. */\nconst HIGH = [147, 51, 234] as const;\n\n/**\n * @param t - a point on the ramp, 0 at the lowest lit level and 1 at the top.\n * @returns the ramp color there.\n */\nfunction ramp(t: number): string {\n const mix = (from: number, to: number): number => Math.round(from + (to - from) * t);\n return `rgb(${mix(LOW[0], HIGH[0])}, ${mix(LOW[1], HIGH[1])}, ${mix(LOW[2], HIGH[2])})`;\n}\n\n/**\n * @param index - the level's detent, 0 being \"off\".\n * @param count - number of detents.\n * @returns the level's color as a CSS color string.\n */\nexport function levelColor(index: number, count: number): string {\n if (index <= 0) return NEUTRAL;\n // The top level is always the violet end, even when it is the only lit one.\n if (isTopLevel(index, count)) return ramp(1);\n const span = Math.max(1, count - 2);\n return ramp(Math.min(Math.max((index - 1) / span, 0), 1));\n}\n\n/**\n * @param index - the level's detent.\n * @param count - number of detents.\n * @returns whether this level is the only one painted with a gradient.\n */\nexport function isTopLevel(index: number, count: number): boolean {\n return count > 1 && index === count - 1;\n}\n\n/** The top level's fill: the whole ramp, ending on the color its text takes. */\nexport const LEVEL_GRADIENT = `linear-gradient(90deg, ${ramp(0)}, ${ramp(1)})`;\n","/**\n * When the composer menu writes a selection.\n *\n * Both gestures are immediate: clicking a level submits it, clicking a model\n * submits it. The only quiet path is clicking the model already in use — that\n * closes the menu without a write. A model click also restores the level this\n * browser last used for that model, so switching away and back never silently\n * resets the effort.\n */\n\nimport type { ModelReasoning } from '@deepseek-ai/dsh-api-session-controller/types'\nimport type { ModelDirectoryState } from '@deepseek-ai/dsh-client-ui-model-selection/client'\n\n/** One complete selection, as the directory's `select` takes it. */\nexport interface Selection {\n readonly provider: string\n readonly model: string\n readonly reasoningEffort?: string\n}\n\n/** One segment of the reasoning row. */\nexport interface EffortChoice {\n readonly key: string\n readonly effort: string | undefined\n readonly label: string\n}\n\n/**\n * Build the slider's detents for one model.\n *\n * The provider default is deliberately absent: it is a state, not a level, and\n * giving it a detent would read as the weakest reasoning instead of \"let the\n * provider decide\". The label above the track switches to it.\n * @param reasoning - the catalog's reasoning metadata, undefined when the model has none.\n * @returns one detent per advertised level, in catalog order.\n */\nexport function effortChoices(reasoning: ModelReasoning | undefined): readonly EffortChoice[] {\n if (reasoning === undefined) return []\n return reasoning.efforts.map((level) => ({\n key: level.id,\n effort: level.id as string | undefined,\n label: level.name,\n }))\n}\n\n/**\n * The selection a level click submits.\n * @param live - the selection in force right now.\n * @param effort - the clicked level, undefined meaning the provider default.\n * @returns the selection to submit, or null when the level already is the live one.\n */\nexport function planEffortCommit(live: Selection, effort: string | undefined): Selection | null {\n if (sameEffort(live.reasoningEffort, effort)) return null\n return {\n provider: live.provider,\n model: live.model,\n ...effort === undefined ? {} : { reasoningEffort: effort },\n }\n}\n\n/**\n * The selection a model click submits.\n * @param live - the selection in force right now, null before a first selection resolves.\n * @param provider - the clicked model's provider.\n * @param model - the clicked model id.\n * @param reasoningOf - the catalog's reasoning metadata for one provider and model.\n * @param remembered - the level this browser last used for the target model, if any.\n * @returns the selection to submit, or null when the click changes nothing.\n */\nexport function planModelCommit(\n live: Selection | null,\n provider: string,\n model: string,\n reasoningOf: (provider: string, model: string) => ModelReasoning | undefined,\n remembered?: string | undefined,\n): Selection | null {\n if (live !== null && sameModel(live, provider, model)) return null\n const offered = reasoningOf(provider, model)\n // The target model's own memory is restored, so switching away and back never\n // silently resets the effort.\n const carried = offered !== undefined\n && remembered !== undefined\n && offered.efforts.some((level) => level.id === remembered)\n return {\n provider,\n model,\n ...carried ? { reasoningEffort: remembered } : {},\n }\n}\n\n/**\n * The reasoning metadata the catalog advertises for the model in force.\n * @param state - the session's directory snapshot.\n * @returns the metadata, or undefined when no model is live or it advertises none.\n */\nexport function liveReasoning(state: ModelDirectoryState): ModelReasoning | undefined {\n const current = state.current\n if (current === null) return undefined\n return state.groups\n .find((group) => group.id === current.provider)\n ?.models.find((model) => model.id === current.model)\n ?.reasoning\n}\n\n/**\n * The selection in force, or null while the Host has not resolved one.\n * @param state - the session's directory snapshot.\n * @returns the live selection, with the effort only when one is set.\n */\nexport function selectionOf(state: ModelDirectoryState): Selection | null {\n if (state.current === null) return null\n return state.current.reasoningEffort === undefined\n ? { provider: state.current.provider, model: state.current.model }\n : {\n provider: state.current.provider,\n model: state.current.model,\n reasoningEffort: state.current.reasoningEffort,\n }\n}\n\n/**\n * @param live - one selection.\n * @param provider - the provider to compare.\n * @param model - the model id to compare.\n * @returns whether both name the same model.\n */\nexport function sameModel(live: Selection, provider: string, model: string): boolean {\n return live.provider === provider && live.model === model\n}\n\n/**\n * @param liveEffort - the level in force, undefined meaning the provider default.\n * @param effort - the level in question.\n * @returns whether they name the same level.\n */\nexport function sameEffort(liveEffort: string | undefined, effort: string | undefined): boolean {\n return liveEffort === effort\n}\n","/**\n * Tint the composer's effort caption with the level's color.\n *\n * The slider's fill and the menu's value already carry the level's color; the\n * caption on the collapsed trigger is the third place the level shows, and it\n * lives outside the menu. This entry stays mounted for the whole session and\n * writes that one color, so all three read as one signal.\n *\n * The caption is recognized as the trigger's `span[class*='triggerEffort']`\n * child — the same class-substring contract the menu row uses. When the model\n * declares no levels the span does not exist and nothing is written.\n */\nimport { useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport type { ModelSelectInjected } from '@deepseek-ai/dsh-client-ui-model-selection/client';\nimport { levelColor } from './level-color.ts';\nimport { effortChoices, liveReasoning } from './selection.ts';\n\n/** Props of the tint: the shared catalog face. */\nexport type EffortTintProps = Pick<ModelSelectInjected, 'available' | 'directory'>;\n\n/**\n * Render nothing and keep the trigger caption colored.\n * @param props - the session's catalog face.\n * @returns an inert marker.\n */\nexport function EffortTint({ available, directory }: EffortTintProps) {\n const markerRef = useRef<HTMLSpanElement | null>(null);\n const [caption, setCaption] = useState<HTMLElement | null>(null);\n const state = useSyncExternalStore(\n (listener) => directory.subscribe(listener),\n () => directory.getSnapshot(),\n );\n const levels = effortChoices(liveReasoning(state));\n const effort = state.current?.reasoningEffort;\n const chosen = effort === undefined ? -1 : levels.findIndex((level) => level.effort === effort);\n // The provider default has no color of its own: the caption keeps the Host's.\n const color = available && chosen >= 0 && levels.length >= 2\n ? levelColor(chosen, levels.length)\n : null;\n\n // The Host rebuilds the trigger on every model change, so the caption is\n // re-read after each render; the identity guard keeps that from looping.\n useLayoutEffect(() => {\n const card = markerRef.current?.closest('[data-composer-card]');\n const next = card?.querySelector<HTMLElement>(\n '[data-slot=\"conversation.input.model\"] button[aria-haspopup=\"menu\"] span[class*=\"triggerEffort\"]',\n ) ?? null;\n if (next !== caption) setCaption(next);\n });\n\n useLayoutEffect(() => {\n if (caption === null || color === null) return;\n const saved = caption.style.color;\n caption.style.color = color;\n return () => {\n caption.style.color = saved;\n };\n }, [caption, color]);\n\n return <span ref={markerRef} hidden />;\n}\n","/**\n * Geometry of the discrete effort slider.\n *\n * The track is a capsule and the knob a slightly larger circle riding on it, so\n * the knob overhangs the track instead of merging with its rounded end. The\n * knob's centre travels between two points inset by the knob's radius, so the\n * knob never runs past either end. A pointer position becomes a position along\n * that travel; a position becomes the detent nearest it, or the CSS lengths that\n * put the knob and the fill there.\n */\n\n/**\n * Track height in CSS pixels; the stylesheet keeps the same value. Half of it is\n * the radius of the track's ends and of the fill's cap.\n */\nexport const TRACK_HEIGHT = 16;\n\n/**\n * Knob diameter in CSS pixels. It is larger than the track, so the knob reads as\n * a knob riding on the track rather than as the track's own end cap.\n */\nexport const KNOB_SIZE = 20;\n\n/** The knob's centre keeps its own radius away from either end of the track. */\nconst KNOB_RADIUS = KNOB_SIZE / 2;\n\n/**\n * @param index - detent index.\n * @param count - number of detents.\n * @returns the knob centre as a fraction of the track, 0 at the first detent and 1 at the last.\n */\nexport function offsetOf(index: number, count: number): number {\n if (count <= 1) return 0;\n const clamped = Math.min(Math.max(index, 0), count - 1);\n return clamped / (count - 1);\n}\n\n/**\n * Where a pointer sits along the knob's travel. The drag reads this rather than a\n * detent, so the knob follows the pointer instead of jumping a level at a time.\n * @param clientX - pointer position in viewport coordinates.\n * @param left - the track's left edge in viewport coordinates.\n * @param width - the track's width in pixels.\n * @returns the position as a fraction of the travel, clamped to 0 and 1.\n */\nexport function fractionAt(clientX: number, left: number, width: number): number {\n const usable = width - KNOB_SIZE;\n if (usable <= 0) return 0;\n const fraction = (clientX - left - KNOB_RADIUS) / usable;\n return Math.min(Math.max(fraction, 0), 1);\n}\n\n/**\n * @param fraction - a knob position between 0 and 1.\n * @param count - number of detents.\n * @returns the detent nearest that position.\n */\nexport function detentAt(fraction: number, count: number): number {\n if (count <= 1) return 0;\n const clamped = Math.min(Math.max(fraction, 0), 1);\n return Math.round(clamped * (count - 1));\n}\n\n/**\n * @param clientX - pointer position in viewport coordinates.\n * @param left - the track's left edge in viewport coordinates.\n * @param width - the track's width in pixels.\n * @param count - number of detents.\n * @returns the detent nearest the pointer, clamped to the two ends.\n */\nexport function stepAt(clientX: number, left: number, width: number, count: number): number {\n if (count <= 1) return 0;\n return detentAt(fractionAt(clientX, left, width), count);\n}\n\n/**\n * @param fraction - a knob position between 0 and 1.\n * @returns the CSS length that puts the knob centre there, inset at both ends.\n */\nexport function knobOffset(fraction: number): string {\n const clamped = Math.min(Math.max(fraction, 0), 1);\n return `calc(${KNOB_RADIUS}px + ${clamped} * (100% - ${KNOB_SIZE}px))`;\n}\n\n/**\n * Width of the colored fill for a knob position.\n *\n * The fill is the track's own shape cut at the knob: its right cap carries the\n * track's radius and ends under the knob, so the color runs into the knob\n * instead of stopping at a straight edge, and the last detent reaches the track's\n * end rather than the knob's centre.\n * @param fraction - a knob position between 0 and 1.\n * @returns the CSS width of the fill.\n */\nexport function fillWidth(fraction: number): string {\n const clamped = Math.min(Math.max(fraction, 0), 1);\n return `calc(${TRACK_HEIGHT}px + ${clamped} * (100% - ${TRACK_HEIGHT}px))`;\n}\n","/**\n * The reasoning-level slider that replaces the Host's effort drill.\n *\n * The Host's second menu cell is a button that drills into a list of levels.\n * This slider sits inside that same cell, between the Host's label and value, so\n * the levels are reachable without leaving the menu. The levels are discrete but\n * the drag is not: the knob follows the pointer continuously, the level it\n * reports is the detent nearest that position, and letting go eases the knob onto\n * that detent and commits once. Keyboard arrows step and commit immediately.\n *\n * The fill takes the level's color, so the track and the two captions read as\n * one signal: \"off\" is neutral, the levels above it walk a blue-to-violet ramp,\n * and only the top level fills with the whole gradient.\n *\n * \"Provider default\" is not a detent: it is a state, drawn as an empty track\n * with a hollow knob, so it never reads as the lowest level.\n */\nimport { useEffect, useRef, useState, type KeyboardEvent, type PointerEvent } from 'react';\nimport type { EffortChoice } from './selection.ts';\nimport { LEVEL_GRADIENT, isTopLevel, levelColor } from './level-color.ts';\nimport { detentAt, fillWidth, fractionAt, knobOffset, offsetOf } from './slider.ts';\nimport css from './ModelOptions.module.css';\n\n/** Props of the slider: the levels, the one in force, and the commit path. */\nexport interface EffortSliderProps {\n /** The levels in catalog order, lowest first. */\n readonly steps: readonly EffortChoice[];\n /** Index of the level in force, already clamped into `steps`. */\n readonly current: number;\n /** Whether the provider default is in force instead of a level. */\n readonly defaultActive: boolean;\n /** Where the knob rests while the provider default is in force. */\n readonly defaultIndex: number;\n /** Localized name of the provider default, read while it is in force. */\n readonly defaultLabel: string;\n /** Whether a selection is in flight; input is ignored while it is. */\n readonly busy: boolean;\n /** Accessible name, taken from the Host's own row label. */\n readonly label: string;\n /** @param index - the level the user settled on. */\n readonly onCommit: (index: number) => void;\n}\n\n/**\n * Render the slider.\n * @param props - levels, the level in force, and the commit path.\n * @returns one focusable slider over the discrete levels.\n */\nexport function EffortSlider({\n steps, current, defaultActive, defaultIndex, defaultLabel, busy, label, onCommit,\n}: EffortSliderProps) {\n const trackRef = useRef<HTMLDivElement | null>(null);\n // The drag position lives in a ref as well as in state: a press and a release\n // can arrive in one task, and the release must not read a render that never ran.\n const dragRef = useRef<number | null>(null);\n // While dragging this is where the pointer is along the travel, not a level:\n // the knob has to move with the pointer, or the drag reads as a series of jumps.\n const [drag, setDrag] = useState<number | null>(null);\n // After a release that submits, the knob waits on the detent it settled on\n // instead of springing back to the old level while the write is in flight.\n const [hold, setHold] = useState<number | null>(null);\n const dragging = drag !== null;\n const resting = defaultActive ? defaultIndex : current;\n const active = dragging ? detentAt(drag, steps.length) : resting;\n const knobFraction = drag ?? hold ?? offsetOf(resting, steps.length);\n // The color and the ticks follow the knob, not the level in force: during a\n // drag, and while a commit is in flight, the knob has already gone ahead.\n const shown = dragging || hold !== null ? detentAt(knobFraction, steps.length) : resting;\n const beginDrag = (fraction: number | null): void => {\n dragRef.current = fraction;\n setDrag(fraction);\n };\n\n // The hold ends as soon as the write does, whether it landed on the new level\n // or was rejected: the displayed level is the truth again either way.\n useEffect(() => {\n if (!busy) setHold(null);\n }, [busy]);\n useEffect(() => {\n setHold(null);\n }, [current, defaultActive]);\n\n const fractionAtPointer = (clientX: number): number => {\n const track = trackRef.current;\n if (track === null) return dragRef.current ?? knobFraction;\n const rect = track.getBoundingClientRect();\n return fractionAt(clientX, rect.left, rect.width);\n };\n\n const onPointerDown = (event: PointerEvent<HTMLDivElement>): void => {\n if (busy) return;\n // The Host's menu cell owns this subtree; without these the press would\n // also settle the cell and pull focus out of the slider.\n event.preventDefault();\n event.stopPropagation();\n setHold(null);\n beginDrag(fractionAtPointer(event.clientX));\n try {\n event.currentTarget.setPointerCapture(event.pointerId);\n } catch {\n // A synthetic pointer has no capture to take; the drag still tracks moves.\n }\n event.currentTarget.focus();\n };\n\n const onPointerMove = (event: PointerEvent<HTMLDivElement>): void => {\n if (dragRef.current === null || busy) return;\n beginDrag(fractionAtPointer(event.clientX));\n };\n\n const settle = (event: PointerEvent<HTMLDivElement>, commit: boolean): void => {\n if (dragRef.current === null) return;\n const next = detentAt(fractionAtPointer(event.clientX), steps.length);\n beginDrag(null);\n if (!commit) return;\n // Leaving the provider default is a change even when the knob lands where\n // the default already pointed.\n if (next !== current || defaultActive) {\n setHold(offsetOf(next, steps.length));\n onCommit(next);\n }\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {\n if (busy) return;\n const last = steps.length - 1;\n const next = event.key === 'ArrowRight' || event.key === 'ArrowUp'\n ? Math.min(active + 1, last)\n : event.key === 'ArrowLeft' || event.key === 'ArrowDown'\n ? Math.max(active - 1, 0)\n : event.key === 'Home'\n ? 0\n : event.key === 'End'\n ? last\n : null;\n if (next === null) return;\n // The menu walks rows with the arrow keys; a slider keeps them for itself.\n event.preventDefault();\n event.stopPropagation();\n if (next !== current || defaultActive) onCommit(next);\n };\n\n const offset = knobOffset(knobFraction);\n // The provider default fills nothing: the knob shows where it lands, the\n // track stays empty so it never reads as a chosen level.\n const idle = defaultActive && !dragging && hold === null;\n const filled = idle ? 0 : knobFraction;\n const fill = isTopLevel(shown, steps.length) ? LEVEL_GRADIENT : levelColor(shown, steps.length);\n return <div\n ref={trackRef}\n className={css.slider}\n role=\"slider\"\n tabIndex={0}\n aria-label={label}\n aria-valuemin={0}\n aria-valuemax={Math.max(0, steps.length - 1)}\n aria-valuenow={active}\n aria-valuetext={defaultActive && !dragging ? defaultLabel : steps[active]?.label}\n aria-disabled={busy || undefined}\n data-busy={busy ? '' : undefined}\n data-dragging={dragging ? '' : undefined}\n data-default={idle ? '' : undefined}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={(event) => { settle(event, true); }}\n onPointerCancel={(event) => { settle(event, false); }}\n onKeyDown={onKeyDown}\n onClick={(event) => { event.stopPropagation(); }}\n >\n <div className={css.sliderTrack} aria-hidden=\"true\">\n <div className={css.sliderFill} style={{ width: fillWidth(filled), background: fill }} />\n {steps.map((step, index) => <span\n key={step.key}\n className={css.sliderTick}\n data-on={index <= shown && !idle ? '' : undefined}\n style={{ left: knobOffset(offsetOf(index, steps.length)) }}\n />)}\n <div className={css.sliderKnob} style={{ left: offset }} />\n </div>\n </div>;\n}\n","/**\n * Bridge the Host's own model menu: the model cell opens the all-provider\n * picker, and the effort cell carries the reasoning-level slider.\n *\n * The Host draws the root menu's first two cells itself: the model cell drills\n * into the current provider's list, the effort cell into the levels. This plugin\n * wants the first to open its dialog and the second to stop drilling, and the\n * Host exposes no hook for either. This entry is mounted exactly while the root\n * pane is open, so it can take the click on the menu surface before the cells'\n * own handlers run and mount the slider into the effort cell.\n *\n * The cells are recognized as the menu surface's direct `role=\"menuitem\"`\n * children, which is the Host's root-menu shape; the options slot output sits in\n * its own wrapper, so other plugins' rows never join that list. When the second\n * cell is missing the model declares no reasoning levels and the row is left\n * alone; when the first is missing the click falls through to the Host.\n *\n * Clicking the cell's label switches to the provider default, which is a state\n * rather than a detent: the slider shows it as an empty track under a hollow\n * knob, so it never reads as the weakest level.\n */\nimport { useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { createPortal } from 'react-dom';\nimport type { ModelSelectInjected } from '@deepseek-ai/dsh-client-ui-model-selection/client';\nimport type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';\nimport { EffortSlider } from './EffortSlider.tsx';\nimport type { PickerStore } from './picker-state.ts';\nimport { effortChoices, liveReasoning, planEffortCommit, selectionOf, type Selection } from './selection.ts';\nimport css from './ModelOptions.module.css';\n\n/** Props of the bridge: menu state, the owning session, and the seat's injected face. */\nexport type ModelMenuBridgeProps = PropsRuntime<'conversation.input.model.options'>\n & PropsLocale<'modelSubmenu'>\n & Pick<ModelSelectInjected, 'available' | 'directory' | 'select'>\n & {\n /** Session whose catalog and picker this menu drives. */\n readonly sessionId: string;\n /** Carries the open request to the overlay view. */\n readonly picker: PickerStore;\n }\n\n/**\n * Render the marker, redirect the model cell, and mount the effort slider.\n * @param props - menu state, session, catalog face, and localized copy.\n * @returns an inert marker plus the slider while the effort cell is present.\n */\nexport function ModelMenuBridge({\n sessionId, picker, dismiss, reportFailure, available, directory, select, t,\n}: ModelMenuBridgeProps) {\n const markerRef = useRef<HTMLSpanElement | null>(null);\n const sliderRef = useRef<HTMLDivElement | null>(null);\n const [surface, setSurface] = useState<HTMLElement | null>(null);\n const [effortRow, setEffortRow] = useState<HTMLElement | null>(null);\n const [rowLabel, setRowLabel] = useState<string | null>(null);\n\n const state = useSyncExternalStore(\n (listener) => directory.subscribe(listener),\n () => directory.getSnapshot(),\n );\n const current = state.current;\n const reasoning = liveReasoning(state);\n const levels = useMemo(() => effortChoices(reasoning), [reasoning]);\n const declared = levels.findIndex((level) => level.effort === reasoning?.defaultEffort);\n const chosen = current?.reasoningEffort === undefined\n ? -1\n : levels.findIndex((level) => level.effort === current.reasoningEffort);\n // The provider default is a state, not a detent: it is in force whenever the\n // session carries no explicit level.\n const defaultActive = current !== null && current.reasoningEffort === undefined;\n const currentIndex = chosen >= 0 ? chosen : declared >= 0 ? declared : 0;\n const defaultIndex = declared >= 0 ? declared : 0;\n const canDefault = !defaultActive;\n const busy = state.pending !== null;\n // One detent is not a choice: leave the Host's row exactly as it is.\n const showSlider = available && levels.length >= 2 && effortRow !== null;\n\n // The Host rebuilds its cells on every render, so the surface and the effort\n // cell are re-read after each one; the identity guards keep that from looping.\n useLayoutEffect(() => {\n const foundSurface = markerRef.current?.closest('[data-menu-material]');\n const nextSurface = foundSurface instanceof HTMLElement ? foundSurface : null;\n if (nextSurface !== surface) setSurface(nextSurface);\n const cells = nextSurface === null\n ? []\n : [...nextSurface.querySelectorAll<HTMLElement>(':scope > button[role=\"menuitem\"]')];\n const nextRow = cells.length >= 2 ? cells[1] ?? null : null;\n if (nextRow !== effortRow) setEffortRow(nextRow);\n const nextLabel = nextRow?.querySelector(\"span[class*='cellLabel']\")?.textContent?.trim() ?? '';\n if (nextLabel !== (rowLabel ?? '')) setRowLabel(nextLabel);\n });\n\n const submit = (selection: Selection | null): void => {\n if (selection === null) return;\n void select(selection).then((result) => {\n if (result === undefined || result.ok) return;\n reportFailure(result.error.code === 'session/writer-held'\n ? t('error.sessionInUse')\n : t('error.action', { message: `${result.error.code}: ${result.error.message}` }));\n });\n };\n const commit = (index: number): void => {\n const live = selectionOf(state);\n const level = levels[index];\n if (live === null || level === undefined) return;\n submit(planEffortCommit(live, level.effort));\n };\n const commitDefault = (): void => {\n const live = selectionOf(state);\n if (live === null) return;\n submit(planEffortCommit(live, undefined));\n };\n\n // The Host rebuilds `dismiss` on every render, so the listener reads the\n // current one through a ref instead of being torn down and reinstalled.\n const latest = useRef({ sessionId, picker, dismiss, showSlider, canDefault, commitDefault });\n latest.current = { sessionId, picker, dismiss, showSlider, canDefault, commitDefault };\n\n useEffect(() => {\n if (surface === null) return;\n const redirect = (event: MouseEvent): void => {\n const target = event.target;\n if (!(target instanceof Node)) return;\n const cells = [...surface.querySelectorAll<HTMLElement>(':scope > button[role=\"menuitem\"]')];\n const modelCell = cells[0];\n if (modelCell !== undefined && modelCell.contains(target)) {\n // Capture on the surface runs before the React root's bubble listener, so\n // stopping here keeps the cell's own drill handler from ever running.\n event.preventDefault();\n event.stopPropagation();\n const request = latest.current;\n request.dismiss();\n request.picker.open(request.sessionId);\n return;\n }\n const row = cells[1];\n if (row === undefined || !row.contains(target) || !latest.current.showSlider) return;\n // The slider owns its own presses; only the label and the value are inert.\n if (sliderRef.current?.contains(target) === true) return;\n event.preventDefault();\n event.stopPropagation();\n const label = row.querySelector(\"span[class*='cellLabel']\");\n if (latest.current.canDefault && label !== null && label.contains(target)) {\n latest.current.commitDefault();\n return;\n }\n sliderRef.current?.querySelector<HTMLElement>('[role=\"slider\"]')?.focus();\n };\n surface.addEventListener('click', redirect, true);\n return () => {\n surface.removeEventListener('click', redirect, true);\n };\n }, [surface]);\n\n // The slider takes the whole slack beside the label: the value and the\n // chevron go, because the level is already written under the composer and the\n // row no longer drills anywhere. Every write is recorded and put back.\n useLayoutEffect(() => {\n if (effortRow === null || !showSlider) return;\n const label = effortRow.querySelector<HTMLElement>(\"span[class*='cellLabel']\");\n const value = effortRow.querySelector<HTMLElement>(\"span[class*='cellValue']\");\n const chevron = effortRow.querySelector<SVGElement>(\"svg[class*='cellChevron']\");\n const saved = {\n value: value?.style.display ?? '',\n chevron: chevron?.style.display ?? '',\n cursor: label?.style.cursor ?? '',\n title: label?.getAttribute('title') ?? null,\n };\n if (value !== null) value.style.display = 'none';\n if (chevron !== null) chevron.style.display = 'none';\n if (label !== null && canDefault) {\n label.style.cursor = 'pointer';\n label.title = t('effort.useDefault');\n }\n return () => {\n if (value !== null) value.style.display = saved.value;\n if (chevron !== null) chevron.style.display = saved.chevron;\n if (label !== null) {\n label.style.cursor = saved.cursor;\n if (saved.title === null) label.removeAttribute('title');\n else label.title = saved.title;\n }\n };\n }, [effortRow, showSlider, canDefault, t]);\n\n return <>\n <span ref={markerRef} hidden />\n {showSlider && effortRow !== null && createPortal(\n <div ref={sliderRef} className={css.sliderCell}>\n <EffortSlider\n steps={levels}\n current={currentIndex}\n defaultActive={defaultActive}\n defaultIndex={defaultIndex}\n defaultLabel={t('effort.providerDefault')}\n busy={busy}\n label={rowLabel !== null && rowLabel !== '' ? rowLabel : t('effort.label')}\n onCommit={commit}\n />\n </div>,\n effortRow,\n )}\n </>;\n}\n","/**\n * The one glyph this plugin draws itself.\n *\n * The shared icon library carries no star, so the favorite mark is defined here:\n * a five-point star whose outline and fill use the same path, which keeps the\n * hover-only outline and the standing filled state identical in geometry.\n */\n\n/**\n * Render the favorite star.\n * @param props.filled - draw the solid star; otherwise the outline.\n * @param props.size - square edge in pixels, matching the shared icon set's default.\n * @param props.className - extra class for colour and placement.\n * @returns the inline SVG.\n */\nexport function StarIcon({ filled, size = 14, className }: {\n readonly filled: boolean\n readonly size?: number | undefined\n readonly className?: string | undefined\n}) {\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill={filled ? 'currentColor' : 'none'}\n stroke=\"currentColor\"\n strokeWidth={filled ? 0 : 1.6}\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className={className}\n >\n <path d=\"M12 3.2l2.7 5.6 6.1.85-4.45 4.3 1.08 6.05L12 17.1l-5.43 2.9 1.08-6.05L3.2 9.65l6.1-.85z\" />\n </svg>\n )\n}\n","/**\n * One pickable model in the picker.\n *\n * The star is its own control, because favouriting there must not switch the\n * model: the row button picks, the star toggles, and the check marks the model\n * in use.\n */\n\nimport type { ReactNode } from 'react';\nimport { IconCheckOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives';\nimport { StarIcon } from './icons.tsx';\nimport css from './ModelOptions.module.css';\n\n/** Props of one row. */\nexport interface ModelRowProps {\n /** The model's catalog name. */\n readonly name: string;\n /** Optional catalog badge shown beside the name. */\n readonly badge?: ReactNode | undefined;\n /** Whether this row is the model in use. */\n readonly selected: boolean;\n /** Whether a selection is in flight, which locks the row. */\n readonly busy: boolean;\n /** Whether the model is favorited right now. */\n readonly favorited: boolean;\n /** The metadata line's text. */\n readonly meta?: string | undefined;\n /** Localized accessible name for the star control. */\n readonly starLabel?: string | undefined;\n /** Pick this row. */\n readonly onPick: () => void;\n /** Toggle this row's favorite. */\n readonly onToggleFavorite: () => void;\n /** Extra row content, such as the picker's disabled reason. */\n readonly children?: ReactNode;\n}\n\n/**\n * Render one model row.\n * @param props - the row's content and behaviour.\n * @returns the row element.\n */\nexport function ModelRow({\n name, badge, selected, busy, favorited, meta, starLabel,\n onPick, onToggleFavorite, children,\n}: ModelRowProps) {\n return <div\n className={css.row}\n data-selected={selected ? '' : undefined}\n data-favorited={favorited ? '' : undefined}\n >\n <button\n type=\"button\"\n aria-current={selected ? 'true' : undefined}\n className={css.rowPick}\n title={name}\n disabled={busy}\n onClick={onPick}\n >\n <span className={css.rowCopy}>\n <span className={css.rowName}>{name}</span>\n {badge}\n {meta !== undefined && meta !== '' && <span className={css.rowMeta}>{meta}</span>}\n </span>\n </button>\n <button\n type=\"button\"\n className={css.starButton}\n aria-pressed={favorited}\n aria-label={starLabel}\n disabled={busy}\n onClick={onToggleFavorite}\n >\n <StarIcon filled={favorited} className={css.star} />\n </button>\n <span className={css.check} aria-hidden={!selected}>{selected ? <IconCheckOutlineRegular /> : null}</span>\n {children}\n </div>;\n}\n","/**\n * The favorite-model table the picker keeps in the browser.\n *\n * One browser-wide key holds `{ provider, model }` rows: the provider route key\n * and the model id are the catalog's own identity, so a favorite survives a\n * display-name change and never collides across providers. A value that cannot\n * be read or does not parse reads as an empty table — a privacy-mode browser or\n * a hand-edited row must never keep the menu from opening. Rows whose provider\n * leaves the catalog are kept, so restoring the provider restores the star.\n */\n\nimport type { ModelRef } from './rows.ts'\n\n/** One favorited model, identified the way the catalog identifies it. */\nexport type FavoriteModel = ModelRef\n\n/** The single browser-wide storage key holding the whole table. */\nexport const FAVORITES_KEY = 'dsh-model-submenu.favorites'\n\n/** The part of `Storage` this module uses; absent or throwing reads mean \"none\". */\nexport type FavoritesStorage = Pick<Storage, 'getItem' | 'setItem'>\n\n/**\n * Read the table, keeping only rows that name both halves.\n * @param storage - browser storage, or undefined when the browser withholds it.\n * @returns the stored favorites in stored order; empty when unreadable.\n */\nexport function readFavorites(storage: FavoritesStorage | undefined): FavoriteModel[] {\n const raw = getItem(storage)\n if (raw === null) return []\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return []\n }\n if (!Array.isArray(parsed)) return []\n return parsed.filter((row): row is FavoriteModel => typeof row === 'object' && row !== null\n && typeof (row as FavoriteModel).provider === 'string'\n && typeof (row as FavoriteModel).model === 'string')\n}\n\n/**\n * Add or remove one model, writing the whole table back.\n * @param storage - browser storage, or undefined when writes are unavailable.\n * @param provider - the provider route key.\n * @param model - the model id inside that provider.\n * @returns the table after the change, whether or not the write landed.\n */\nexport function toggleFavorite(\n storage: FavoritesStorage | undefined,\n provider: string,\n model: string,\n): FavoriteModel[] {\n const current = readFavorites(storage)\n const next = isFavorite(current, provider, model)\n ? current.filter((row) => row.provider !== provider || row.model !== model)\n : [...current, { provider, model }]\n try {\n storage?.setItem(FAVORITES_KEY, JSON.stringify(next))\n } catch {\n // A full quota or a locked-down browser loses the star, not the menu.\n }\n return next\n}\n\n/**\n * @param favorites - the table to search.\n * @param provider - the provider route key.\n * @param model - the model id inside that provider.\n * @returns whether that exact pair is favorited.\n */\nexport function isFavorite(\n favorites: readonly FavoriteModel[],\n provider: string,\n model: string,\n): boolean {\n return favorites.some((row) => row.provider === provider && row.model === model)\n}\n\n/**\n * Stable-partition a catalog so favorited rows lead, each part keeping the\n * catalog's own order.\n * @param rows - the catalog in its display order.\n * @param favorited - predicate answering whether one row is favorited.\n * @returns a new list; the input order when nothing is favorited.\n */\nexport function orderWithFavorites<T>(\n rows: readonly T[],\n favorited: (row: T) => boolean,\n): readonly T[] {\n const starred = rows.filter(favorited)\n if (starred.length === 0) return rows\n return [...starred, ...rows.filter((row) => !favorited(row))]\n}\n\nfunction getItem(storage: FavoritesStorage | undefined): string | null {\n try {\n return storage?.getItem(FAVORITES_KEY) ?? null\n } catch {\n return null\n }\n}\n","/**\n * What the settings document knows about one model, for the picker's metadata\n * line.\n *\n * The Host's model catalog carries only a name and a reasoning list; context\n * window and accepted modalities live in each provider's settings profile,\n * which the settings mirror already holds in the browser. This module joins\n * the two by the identity both agree on: the catalog's provider route key and\n * model id are exactly the settings profile's provider key and model id.\n *\n * Every field is answered independently. A provider whose profile lists no\n * models (an OAuth route such as the Kimi subscription, or the account\n * provider) answers `unknown` rather than guessing the route default, because\n * the line's rule is to omit what cannot be asserted.\n */\n\n/** Accepted-modality answer for one model. */\nexport type ImageAnswer = 'yes' | 'no' | 'unknown'\n\n/**\n * The model knowledge a shipped adapter carries for its own account route.\n *\n * The account route has no `models` list on purpose: the account listing\n * supplies the catalog and settings only carry per-model overrides. The same\n * adapter family ships a default catalog for its API-key route, and the account\n * route serves the same models, so those entries answer what the account\n * profile cannot. Ids and facts mirror the adapter's own table.\n */\nconst FAMILY_DEFAULTS: Readonly<Record<string, readonly {\n readonly id: string\n readonly contextWindow: number\n readonly images: boolean\n}[]>> = {\n 'deepseek-account': [\n { id: 'deepseek-flash', contextWindow: 1_000_000, images: true },\n { id: 'deepseek-v4-pro', contextWindow: 1_000_000, images: false },\n ],\n}\n\n/** The metadata the settings document can answer for one model. */\nexport interface ModelFacts {\n /** Exact advertised capacity, or undefined when the model declares none. */\n readonly contextWindow: number | undefined\n /** Whether the model accepts images; `unknown` means the document is silent. */\n readonly images: ImageAnswer\n}\n\n/** One provider directory row, as the Host's LLM directory reports it. */\nexport interface ConfigurableProviderRow {\n readonly provider: string\n readonly settingsNs: string\n readonly settingsPath: readonly string[]\n}\n\n/** One settings namespace view, as the describe answer carries it. */\nexport interface NamespaceRow {\n readonly ns: string\n readonly value: unknown\n}\n\n/** What one provider's profile may carry, across both adapter families. */\ninterface ProfileView {\n readonly models?: readonly Record<string, unknown>[]\n readonly defaultContextWindow?: unknown\n readonly defaultInput?: unknown\n}\n\ntype Record_ = Record<string, unknown>\n\n/**\n * Build the fact lookup from the settings answer.\n * @param providers - provider directory rows giving each route's settings address.\n * @param namespaces - the describe answer's namespace views.\n * @returns a lookup answering both fields for one provider and model.\n */\nexport function buildModelFacts(\n providers: readonly ConfigurableProviderRow[],\n namespaces: readonly NamespaceRow[],\n): (provider: string, model: string) => ModelFacts {\n const byName = new Map(namespaces.map((row) => [row.ns, row.value]))\n const profiles = new Map<string, ProfileView>()\n for (const row of providers) {\n const section = byName.get(row.settingsNs)\n const profile = readPath(section, row.settingsPath)\n if (isRecord(profile)) profiles.set(row.provider, profile as ProfileView)\n }\n return (provider, model) => {\n const profile = profiles.get(provider)\n const entry = profile?.models === undefined\n ? undefined\n : profile.models.find((candidate) => candidate.id === model)\n if (profile !== undefined && entry !== undefined) {\n const window = typeof entry.contextWindow === 'number' && entry.contextWindow > 0\n ? entry.contextWindow\n : undefined\n return { contextWindow: window, images: imageAnswer(entry, profile) }\n }\n // A route with no model list of its own (the account route) still serves its\n // adapter family's models; a declared override there wins over the default.\n const override = readOverride(profile, model)\n if (override !== undefined) {\n const window = typeof override.contextWindow === 'number' && override.contextWindow > 0\n ? override.contextWindow\n : undefined\n return {\n contextWindow: window,\n images: Array.isArray(override.inputModalities) ? containsImage(override.inputModalities) : 'unknown',\n }\n }\n const family = FAMILY_DEFAULTS[provider]?.find((candidate) => candidate.id === model)\n return family === undefined\n ? { contextWindow: undefined, images: 'unknown' }\n : { contextWindow: family.contextWindow, images: family.images ? 'yes' : 'no' }\n }\n}\n\n/**\n * Read one model entry's accepted image modalities.\n *\n * The two adapter families disagree about an omission. The Messages family\n * names the field `inputModalities`, and leaving it out means text only. The\n * pi-ai family names it `input`, and an empty or absent list defers to the\n * route's `defaultInput`; with no route default the answer stays unknown.\n */\nfunction imageAnswer(entry: Record_, profile: ProfileView): ImageAnswer {\n if (Array.isArray(entry.inputModalities)) return containsImage(entry.inputModalities)\n const defaultInput = Array.isArray(profile.defaultInput) ? profile.defaultInput : undefined\n if (Array.isArray(entry.input)) {\n if (entry.input.length > 0) return containsImage(entry.input)\n return defaultInput === undefined ? 'unknown' : containsImage(defaultInput)\n }\n return defaultInput === undefined ? 'no' : containsImage(defaultInput)\n}\n\n/** One model's declared override, when the route keeps a `modelOverrides` table. */\nfunction readOverride(profile: ProfileView | undefined, model: string): Record_ | undefined {\n const overrides = (profile as Record_ | undefined)?.modelOverrides\n if (!isRecord(overrides)) return undefined\n const entry = overrides[model]\n return isRecord(entry) ? entry : undefined\n}\n\nfunction containsImage(modalities: readonly unknown[]): ImageAnswer {\n return modalities.includes('image') ? 'yes' : 'no'\n}\n\n/**\n * Abbreviate one capacity the way the metadata line shows it.\n * @param tokens - exact advertised capacity in tokens.\n * @returns e.g. `1M ctx`, `1.5M ctx`, `272k ctx`, or `512 ctx` below a thousand.\n */\nexport function formatContextWindow(tokens: number): string {\n if (tokens >= 1_000_000) return `${trimNumber(tokens / 1_000_000)}M ctx`\n if (tokens >= 1_000) return `${Math.round(tokens / 1_000)}k ctx`\n return `${tokens} ctx`\n}\n\n/**\n * Build the metadata line's segments.\n * @param input.providerName - the provider's display name, omitted inside that provider's own tab.\n * @param input.facts - what the settings document answered.\n * @param input.thinks - whether the catalog advertises reasoning levels for this model.\n * @param input.effortLabel - the localized word for the reasoning segment.\n * @param input.imageLabel - the localized word for the image segment.\n * @returns the segments to join; an empty list means show no line.\n */\nexport function metaSegments(input: {\n readonly providerName?: string\n readonly facts: ModelFacts\n readonly thinks: boolean\n readonly effortLabel: string\n readonly imageLabel: string\n}): readonly string[] {\n const segments: string[] = []\n if (input.providerName !== undefined) segments.push(input.providerName)\n if (input.facts.contextWindow !== undefined) segments.push(formatContextWindow(input.facts.contextWindow))\n if (input.thinks) segments.push(input.effortLabel)\n if (input.facts.images === 'yes') segments.push(input.imageLabel)\n return segments\n}\n\nfunction trimNumber(value: number): string {\n const rounded = Math.round(value * 10) / 10\n return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1)\n}\n\nfunction readPath(value: unknown, path: readonly string[]): unknown {\n let cursor = value\n for (const key of path) {\n if (!isRecord(cursor)) return undefined\n cursor = cursor[key]\n }\n return cursor\n}\n\nfunction isRecord(value: unknown): value is Record_ {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n","/**\n * Catalog flattening, tab derivation, and query matching for the picker.\n *\n * The model catalog groups models by provider; the picker shows one flat list\n * with a tab per provider, so both surfaces read from these rows. Matching\n * delegates to the shell's own ranker, which is why a query behaves the same\n * here as in the built-in menu: case-insensitive, ordered subsequence, prefix\n * hits first.\n */\n\nimport type { ModelDirectoryState } from '@deepseek-ai/dsh-client-ui-model-selection/client'\n\n/** One provider group of the shared catalog. */\nexport type ProviderGroup = ModelDirectoryState['groups'][number]\n\n/** One model inside a provider group. */\nexport type CatalogModel = ProviderGroup['models'][number]\n\n/** One flat catalog row, carrying the provider identity beside the model. */\nexport interface ModelRow extends ModelRef {\n readonly providerName: string\n /** The model id inside its provider, the other half of a selection. */\n readonly model: string\n /** The catalog entry itself, for name and reasoning metadata. */\n readonly entry: CatalogModel\n}\n\n/** A provider and one of its models: the pair every selection and favorite names. */\nexport interface ModelRef {\n readonly provider: string\n readonly model: string\n}\n\n/** One picker tab. */\nexport interface PickerTab {\n readonly id: string\n readonly label: string\n}\n\n/** The tab id standing for every provider. */\nexport const ALL_TAB = '*'\n\n/**\n * Flatten the catalog to one row per model.\n * @param groups - the shared directory's provider groups, in display order.\n * @returns the rows in catalog order.\n */\nexport function flattenGroups(groups: readonly ProviderGroup[]): readonly ModelRow[] {\n return groups.flatMap((group) => group.models.map((model) => ({\n provider: group.id,\n providerName: group.name,\n model: model.id,\n entry: model,\n })))\n}\n\n/**\n * Build the picker's tabs: the all-tab, then one per provider that has a model.\n * @param groups - the shared directory's provider groups.\n * @param allLabel - localized label for the all-tab.\n * @returns the tabs in catalog order.\n */\nexport function pickerTabs(groups: readonly ProviderGroup[], allLabel = 'All'): readonly PickerTab[] {\n return [\n { id: ALL_TAB, label: allLabel },\n ...groups.filter((group) => group.models.length > 0).map((group) => ({ id: group.id, label: group.name })),\n ]\n}\n\n/**\n * @param rows - the flat catalog.\n * @param tab - the tab id; the all-tab keeps every row.\n * @returns the rows the tab shows, in their current order.\n */\nexport function rowsForTab(rows: readonly ModelRow[], tab: string): readonly ModelRow[] {\n return tab === ALL_TAB ? rows : rows.filter((row) => row.provider === tab)\n}\n\n/** One row handed to the shell's ranker, carrying both searchable keys. */\nexport interface SearchCandidate {\n readonly name: string\n readonly label: string\n readonly row: ModelRow\n}\n\n/**\n * Shape rows as the shell's ranker candidates: the model name is the name, the\n * provider display name is the second key, so a query reaches either.\n * @param rows - the rows to search.\n * @returns candidates in row order.\n */\nexport function searchCandidates(rows: readonly ModelRow[]): readonly SearchCandidate[] {\n return rows.map((row) => ({ name: row.entry.name, label: row.providerName, row }))\n}\n\n/**\n * Read the reasoning metadata the catalog advertises for one model.\n * @param groups - the shared directory's provider groups.\n * @returns the metadata, or undefined when that model advertises none.\n */\nexport function reasoningOf(\n groups: readonly ProviderGroup[],\n): (provider: string, model: string) => CatalogModel['reasoning'] {\n return (provider, model) => groups\n .find((group) => group.id === provider)\n ?.models.find((candidate) => candidate.id === model)\n ?.reasoning\n}\n\n/**\n * Filter rows by a query over the model name and the provider name.\n * @param rows - the rows to search.\n * @param query - the typed text; an empty or blank query keeps every row.\n * @param rank - the shell's own ranker, so this search behaves like every other one.\n * @returns the matching rows.\n */\nexport function searchRows(\n rows: readonly ModelRow[],\n query: string,\n rank: (items: readonly SearchCandidate[], query: string) => readonly SearchCandidate[],\n): readonly ModelRow[] {\n const trimmed = query.trim()\n if (trimmed === '') return rows\n return rank(searchCandidates(rows), trimmed).map((match) => match.row)\n}\n","/**\n * The model picker: the centered dialog opened from the menu's last row.\n *\n * It is the one place every provider is visible at once — a search field, a tab\n * per provider with a model, and one flat list with each model's metadata line.\n * Favorites lead the list and can be toggled from a row's star without\n * selecting that row. A click commits at once; a rejected commit keeps the\n * dialog open so the user can retry or pick another model.\n */\n\nimport { useEffect, useId, useMemo, useRef, useState } from 'react'\nimport {\n IconSearchOutlineRegular,\n Input,\n Modal,\n ShortcutKeys,\n rankByName,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { ModelDirectoryState } from '@deepseek-ai/dsh-client-ui-model-selection/client'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport { ModelRow } from './ModelRow.tsx'\nimport { isFavorite, orderWithFavorites } from './favorites.ts'\nimport type { ModelFacts } from './model-facts.ts'\nimport { metaSegments } from './model-facts.ts'\nimport {\n ALL_TAB,\n flattenGroups,\n pickerTabs,\n reasoningOf,\n rowsForTab,\n searchRows,\n type CatalogModel,\n type ModelRow as CatalogRow,\n} from './rows.ts'\nimport { Tag, isMobileDevice } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { ModelFactsLookup } from './facts-source.ts'\nimport type { Selection } from './selection.ts'\nimport { planModelCommit } from './selection.ts'\nimport css from './ModelOptions.module.css'\n\n/** Props the seat hands the picker. */\nexport interface ModelPickerProps {\n /** Whether the dialog is showing. */\n readonly open: boolean\n /** Resolve a catalog badge's localized text for the active language. */\n readonly resolveText: (text: NonNullable<CatalogModel['badge']>['text']) => string\n /** The shared directory snapshot. */\n readonly state: ModelDirectoryState\n /** The selection in force, null before a first selection resolves. */\n readonly live: Selection | null\n /** The metadata lookup for the current settings answer. */\n readonly facts: ModelFactsLookup\n /** The favorite table right now. */\n readonly favorites: readonly { readonly provider: string; readonly model: string }[]\n /** Whether a selection is in flight. */\n readonly busy: boolean\n /**\n * The reasoning level this browser last used for one model, if any.\n * @param provider - the provider route key.\n * @param model - the model id inside that provider.\n * @returns the remembered level, or undefined when none is remembered.\n */\n readonly rememberedOf?: (provider: string, model: string) => string | undefined\n /** Submit one selection. */\n readonly onPick: (selection: Selection) => void\n /** Toggle one model's favorite. */\n readonly onToggleFavorite: (provider: string, model: string) => void\n /** Reload the catalog after a failure. */\n readonly onReload: () => void\n /** Close the dialog, optionally returning focus. */\n readonly onClose: () => void\n}\n\n/**\n * Render the picker.\n * @param props - the catalog, the live selection, and the picker's verbs.\n * @returns the dialog; nothing while closed.\n */\nexport function ModelPicker({\n open, resolveText, state, live, facts, favorites, busy, rememberedOf, onPick, onToggleFavorite, onReload, onClose, t,\n}: ModelPickerProps & PropsLocale<'modelSubmenu'>) {\n const id = useId()\n const searchRef = useRef<HTMLInputElement | null>(null)\n const listRef = useRef<HTMLDivElement | null>(null)\n const [query, setQuery] = useState('')\n const [tab, setTab] = useState(ALL_TAB)\n\n const tabs = useMemo(() => pickerTabs(state.groups, t('picker.all')), [state.groups, t])\n const reason = useMemo(() => reasoningOf(state.groups), [state.groups])\n const rows = useMemo(() => orderWithFavorites(\n rowsForTab(flattenGroups(state.groups), tab),\n (row) => isFavorite(favorites, row.provider, row.model),\n ), [state.groups, tab, favorites])\n // Search runs inside the favorite partition, never across it: a query must not\n // pull an unstarred model above a starred one, because favorites lead the list\n // whether or not a query is typed.\n const visible = useMemo(() => orderWithFavorites(\n searchRows(rows, query, rankByName),\n (row) => isFavorite(favorites, row.provider, row.model),\n ), [rows, query, favorites])\n\n useEffect(() => {\n if (!open) return\n setQuery('')\n setTab(ALL_TAB)\n if (listRef.current !== null) listRef.current.scrollTop = 0\n // 粗指针设备不自动聚焦文本输入:移动端弹键盘会顶起布局,宿主座位也这样守。\n const frame = requestAnimationFrame(() => {\n if (!isMobileDevice()) searchRef.current?.focus()\n })\n return () => { cancelAnimationFrame(frame) }\n }, [open])\n\n if (!open) return null\n\n const pick = (row: CatalogRow) => {\n const selection = planModelCommit(live, row.provider, row.model, reason, rememberedOf?.(row.provider, row.model))\n if (selection === null) {\n onClose()\n return\n }\n onPick(selection)\n }\n const failures = state.failures.filter((failure) => tab === ALL_TAB || failure.id === tab)\n const failed = state.error !== null && state.status === 'error'\n const loading = state.status === 'loading'\n return (\n <Modal\n open={open}\n onClose={onClose}\n title={t('picker.title')}\n closeLabel={t('picker.close')}\n className={css.pickerDialog ?? ''}\n contentClassName={css.pickerContent ?? ''}\n >\n <div className={css.pickerSearch}>\n <Input\n ref={searchRef}\n className={css.pickerSearchInput ?? ''}\n type=\"text\"\n role=\"searchbox\"\n aria-label={t('picker.search')}\n aria-controls={`${id}-list`}\n placeholder={t('picker.search')}\n icon={<IconSearchOutlineRegular size={14} />}\n value={query}\n readOnly={busy}\n onChange={(event) => { setQuery(event.target.value) }}\n data-modal-autofocus=\"\"\n />\n </div>\n <div className={css.pickerTabs} role=\"tablist\" aria-label={t('picker.title')}>\n {tabs.map((entry) => (\n <button\n key={entry.id}\n type=\"button\"\n role=\"tab\"\n id={`${id}-tab-${entry.id}`}\n aria-selected={entry.id === tab}\n aria-controls={`${id}-list`}\n tabIndex={0}\n className={css.pickerTab}\n onClick={() => { setTab(entry.id) }}\n >\n {entry.label}\n </button>\n ))}\n </div>\n <div ref={listRef} id={`${id}-list`} className={css.pickerList}>\n {loading && <div className={css.notice}>{t('status.loading')}</div>}\n {failed && (\n <div className={css.notice}>\n <span>{t('status.failed', { message: state.error ?? '' })}</span>\n <button type=\"button\" className={css.retry} onClick={onReload}>{t('action.reload')}</button>\n </div>\n )}\n {failures.map((failure) => (\n <div className={css.notice} key={failure.id}>\n <span>{t('status.providerFailed', { name: failure.name, message: failure.message })}</span>\n <button type=\"button\" className={css.retry} onClick={onReload}>{t('action.reload')}</button>\n </div>\n ))}\n {visible.length === 0 && !loading && !failed && (\n <div className={css.notice}>{state.groups.length === 0 ? t('status.empty') : t('picker.searchEmpty')}</div>\n )}\n {visible.map((row) => (\n <div key={`${row.provider}/${row.model}`}>\n <ModelRow\n name={row.entry.name}\n badge={row.entry.badge === undefined\n ? undefined\n : <Tag tone={row.entry.badge.tone} className={css.rowBadge}>{resolveText(row.entry.badge.text)}</Tag>}\n selected={live?.provider === row.provider && live.model === row.model}\n busy={busy}\n favorited={isFavorite(favorites, row.provider, row.model)}\n meta={metaLine({\n row,\n facts: facts(row.provider, row.model),\n providerName: tab === ALL_TAB ? row.providerName : undefined,\n thinks: row.entry.reasoning !== undefined,\n effortLabel: t('meta.effort'),\n imageLabel: t('meta.images'),\n })}\n starLabel={isFavorite(favorites, row.provider, row.model)\n ? t('picker.unfavorite', { model: row.entry.name })\n : t('picker.favorite', { model: row.entry.name })}\n onPick={() => { pick(row) }}\n onToggleFavorite={() => { onToggleFavorite(row.provider, row.model) }}\n />\n </div>\n ))}\n </div>\n <div className={css.pickerFooter}>\n <span className={css.footerHint}>\n <ShortcutKeys keys={['Esc']} />\n <span>{t('picker.hintClose')}</span>\n </span>\n </div>\n </Modal>\n )\n}\n\nfunction metaLine(input: {\n readonly row: CatalogRow\n readonly facts: ModelFacts\n readonly providerName: string | undefined\n readonly thinks: boolean\n readonly effortLabel: string\n readonly imageLabel: string\n}): string {\n return metaSegments({\n ...input.providerName === undefined ? {} : { providerName: input.providerName },\n facts: input.facts,\n thinks: input.thinks,\n effortLabel: input.effortLabel,\n imageLabel: input.imageLabel,\n }).join(' · ')\n}\n\n","/**\n * The per-model reasoning level this browser last chose.\n *\n * The Host stores one level per session, so switching models away and back\n * forgets what was picked for the first one. This module keeps that memory\n * beside the favorite table, keyed the same way: provider route key plus model\n * id. Remembering the provider default removes the row, so \"follow the\n * provider\" stays distinguishable from \"remembered a level\".\n */\n\n/** One remembered reasoning level. */\nexport interface RememberedEffort {\n readonly provider: string\n readonly model: string\n readonly effort: string\n}\n\n/** The single browser-wide storage key holding the whole memory. */\nexport const EFFORTS_KEY = 'dsh-model-submenu.efforts'\n\n/** The part of `Storage` this module uses; absent or throwing reads mean \"none\". */\nexport type EffortStorage = Pick<Storage, 'getItem' | 'setItem'>\n\n/**\n * Read the memory, keeping only rows naming both halves and a level.\n * @param storage - browser storage, or undefined when the browser withholds it.\n * @returns the remembered rows; empty when unreadable.\n */\nexport function readEfforts(storage: EffortStorage | undefined): RememberedEffort[] {\n const raw = getItem(storage)\n if (raw === null) return []\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return []\n }\n if (!Array.isArray(parsed)) return []\n return parsed.filter((row): row is RememberedEffort => typeof row === 'object' && row !== null\n && typeof (row as RememberedEffort).provider === 'string'\n && typeof (row as RememberedEffort).model === 'string'\n && typeof (row as RememberedEffort).effort === 'string'\n && (row as RememberedEffort).effort !== '')\n}\n\n/**\n * @param memory - the rows to search.\n * @param provider - the provider route key.\n * @param model - the model id inside that provider.\n * @returns the level last chosen for that exact model, or undefined.\n */\nexport function effortFor(\n memory: readonly RememberedEffort[],\n provider: string,\n model: string,\n): string | undefined {\n return memory.find((row) => row.provider === provider && row.model === model)?.effort\n}\n\n/**\n * Remember one model's level, or forget it when the choice is the provider default.\n * @param storage - browser storage, or undefined when writes are unavailable.\n * @param provider - the provider route key.\n * @param model - the model id inside that provider.\n * @param effort - the chosen level, undefined meaning the provider default.\n * @returns the memory after the change, whether or not the write landed.\n */\nexport function rememberEffort(\n storage: EffortStorage | undefined,\n provider: string,\n model: string,\n effort: string | undefined,\n): RememberedEffort[] {\n const rest = readEfforts(storage).filter((row) => row.provider !== provider || row.model !== model)\n const next = effort === undefined ? rest : [...rest, { provider, model, effort }]\n try {\n storage?.setItem(EFFORTS_KEY, JSON.stringify(next))\n } catch {\n // A full quota or a locked-down browser loses the memory, not the menu.\n }\n return next\n}\n\nfunction getItem(storage: EffortStorage | undefined): string | null {\n try {\n return storage?.getItem(EFFORTS_KEY) ?? null\n } catch {\n return null\n }\n}\n","/**\n * The composer overlay's picker view: the dialog, its catalog, and its verbs.\n *\n * It lives in `conversation.input.overlay` rather than beside the row, because\n * the Host unmounts the model menu as soon as a row is clicked. Everything the\n * dialog reads arrives through this entry's injected face.\n */\nimport { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { IconWarningOutlineRegular, Toast } from '@deepseek-ai/dsh-client-ui-primitives';\nimport type { ModelSelectInjected } from '@deepseek-ai/dsh-client-ui-model-selection/client';\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';\nimport { ModelPicker } from './ModelPicker.tsx';\nimport type { FactsSource } from './facts-source.ts';\nimport {\n readFavorites,\n toggleFavorite,\n type FavoriteModel,\n type FavoritesStorage,\n} from './favorites.ts';\nimport {\n effortFor,\n readEfforts,\n rememberEffort,\n type RememberedEffort,\n} from './effort-memory.ts';\nimport { selectionOf, type Selection } from './selection.ts';\nimport type { PickerStore } from './picker-state.ts';\nimport css from './ModelOptions.module.css';\n\n/** Props of the overlay view: the session, the store, and the seat's injected face. */\nexport type ModelPickerHostProps = PropsLocale<'modelSubmenu'>\n & ModelSelectInjected\n & {\n /** Session whose catalog this view renders. */\n readonly sessionId: string;\n /** The request written by the menu row. */\n readonly picker: PickerStore;\n /** The settings-derived metadata the picker's line reads. */\n readonly factsSource: FactsSource;\n }\n\n/**\n * Render the picker for this session.\n * @param props - session, store, catalog face, and localized copy.\n * @returns the dialog while the store names this session; otherwise nothing.\n */\nexport function ModelPickerHost({\n sessionId, picker, available, directory, load, select, resolveText, factsSource, t,\n}: ModelPickerHostProps) {\n const request = useSyncExternalStore(picker.subscribe, picker.getSnapshot);\n const state = useSyncExternalStore((listener) => directory.subscribe(listener), () => directory.getSnapshot());\n const [favorites, setFavorites] = useState<readonly FavoriteModel[]>(() => readFavorites(storageOf()));\n const [memory, setMemory] = useState<readonly RememberedEffort[]>(() => readEfforts(storageOf()));\n const [toast, setToast] = useState<{ seq: number; text: string } | null>(null);\n const toastSeq = useRef(0);\n const rootRef = useRef<HTMLDivElement | null>(null);\n\n const busy = state.status === 'selecting';\n const live = useMemo(() => selectionOf(state), [state]);\n const facts = useSyncExternalStore((listener) => factsSource.subscribe(listener), () => factsSource.getSnapshot());\n const open = request.sessionId === sessionId;\n // The injected face is cached per entry and session, but the read goes through\n // a ref anyway: a catalog load writes the store, so an unstable `load` in the\n // dependency list would reload on every render it caused.\n const loadRef = useRef(load);\n loadRef.current = load;\n\n // Opening reads the catalog again: another client may have changed it, and a\n // failed load has to be retryable from the same gesture.\n useEffect(() => {\n if (open) loadRef.current();\n }, [open]);\n\n // The Host's own menu changes the reasoning level without passing through this\n // plugin, so the memory is written from the directory snapshot too — a picker\n // click alone would only ever record levels chosen in the dialog. The key guard\n // keeps one write per distinct selection instead of one per snapshot.\n const current = state.current;\n const rememberedRef = useRef<string | null>(null);\n useEffect(() => {\n if (current === null) return;\n const key = `${current.provider}/${current.model}/${current.reasoningEffort ?? ''}`;\n if (rememberedRef.current === key) return;\n rememberedRef.current = key;\n setMemory(rememberEffort(storageOf(), current.provider, current.model, current.reasoningEffort));\n }, [current]);\n\n if (!available) return null;\n\n const raiseToast = (text: string): void => {\n toastSeq.current += 1;\n setToast({ seq: toastSeq.current, text });\n };\n const submit = (selection: Selection): void => {\n void select(selection).then((result) => {\n if (result === undefined || result.ok) {\n setMemory(rememberEffort(storageOf(), selection.provider, selection.model, selection.reasoningEffort));\n picker.close();\n return;\n }\n raiseToast(result.error.code === 'session/writer-held'\n ? t('error.sessionInUse')\n : t('error.action', { message: `${result.error.code}: ${result.error.message}` }));\n });\n };\n\n return <div ref={rootRef} className={css.pickerHost}>\n <ModelPicker\n open={open}\n resolveText={resolveText}\n state={state}\n live={live}\n facts={facts}\n favorites={favorites}\n busy={busy}\n t={t}\n rememberedOf={(provider, model) => effortFor(memory, provider, model)}\n onPick={submit}\n onToggleFavorite={(provider, model) => {\n setFavorites(toggleFavorite(storageOf(), provider, model));\n }}\n onReload={load}\n onClose={() => {\n picker.close();\n }}\n />\n {toast !== null && <Toast\n key={toast.seq}\n text={toast.text}\n icon={<IconWarningOutlineRegular />}\n anchor={rootRef.current?.closest('[data-composer-card]') ?? null}\n onDone={() => {\n setToast(null);\n }}\n />}\n </div>;\n}\n\n/** Isolate the browser's storage so a locked-down browser reads as \"no favorites\". */\nfunction storageOf(): FavoritesStorage | undefined {\n try {\n return globalThis.localStorage;\n } catch {\n return undefined;\n }\n}\n","/**\n * Which session's picker is open.\n *\n * The row that asks for the picker lives inside the composer model menu, and the\n * Host unmounts that menu the moment a row is clicked. The dialog lives in the\n * composer's overlay slot, which stays mounted. One plugin-wide store carries the\n * request across that unmount — the same split the Host's own command popups use\n * between their controller and their overlay view.\n */\n\n/** What the store publishes. */\nexport interface PickerSnapshot {\n /** Session whose picker is open, or null when none is. */\n readonly sessionId: string | null\n}\n\n/** Open and close verbs plus the observable the overlay view subscribes to. */\nexport interface PickerStore {\n /** @returns the current request. */\n readonly getSnapshot: () => PickerSnapshot\n /** @param listener - called after every change. @returns the unsubscribe. */\n readonly subscribe: (listener: () => void) => () => void\n /** @param sessionId - open the picker for one session, replacing any other. */\n readonly open: (sessionId: string) => void\n /** Close whatever is open. */\n readonly close: () => void\n}\n\n/**\n * @returns a store that starts closed.\n */\nexport function createPickerStore(): PickerStore {\n let snapshot: PickerSnapshot = { sessionId: null };\n const listeners = new Set<() => void>();\n const publish = (next: PickerSnapshot): void => {\n if (next.sessionId === snapshot.sessionId) return;\n snapshot = next;\n for (const listener of listeners) listener();\n };\n return {\n getSnapshot: () => snapshot,\n subscribe: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n open: (sessionId) => {\n publish({ sessionId });\n },\n close: () => {\n publish({ sessionId: null });\n },\n };\n}\n","/**\n * The browser's live answer to \"what does the settings document know about this\n * model?\".\n *\n * Two host answers meet here: the shared settings mirror (which the settings\n * page already keeps warm) and the LLM provider directory that says which\n * settings namespace holds each provider's profile. Both can change while the\n * page is open — a saved profile, a reconnected host — so the join is rebuilt\n * whenever the mirror or the directory reports a change, and readers subscribe\n * to the lookup itself.\n */\n\nimport type { ModelFacts } from './model-facts.ts'\nimport {\n buildModelFacts,\n type ConfigurableProviderRow,\n type NamespaceRow,\n} from './model-facts.ts'\n\n/** One lookup answering both metadata fields for a provider and model. */\nexport type ModelFactsLookup = (provider: string, model: string) => ModelFacts\n\n/** The mirror face this source reads; a subset of the settings provider's. */\nexport interface DescribeFace {\n getSnapshot(): { readonly view: { readonly namespaces: readonly NamespaceRow[] } | undefined }\n subscribe(listener: () => void): () => void\n ensure(): Promise<void>\n}\n\n/** One remote answer, as the gateway returns it. */\nexport interface DirectoryAnswer {\n readonly ok: boolean\n readonly value?: readonly ConfigurableProviderRow[]\n}\n\n/** A stable, subscribable lookup over the current settings answer. */\nexport interface FactsSource {\n /** @returns the lookup for the current answer; the identity changes on every rebuild. */\n getSnapshot(): ModelFactsLookup\n /** @param listener - called after each rebuild. @returns the disposer. */\n subscribe(listener: () => void): () => void\n}\n\nconst EMPTY: ModelFactsLookup = () => ({ contextWindow: undefined, images: 'unknown' })\n\n/**\n * Join the settings mirror with the provider directory.\n * @param deps.describe - the settings mirror's describe face.\n * @param deps.listProviders - reads the LLM provider directory once.\n * @returns the source, plus `start` which wires both reads and returns its disposer.\n */\nexport function createFactsSource(deps: {\n readonly describe: DescribeFace\n readonly listProviders: () => Promise<DirectoryAnswer>\n}): { readonly source: FactsSource; readonly start: () => () => void } {\n const listeners = new Set<() => void>()\n let providers: readonly ConfigurableProviderRow[] = []\n let lookup: ModelFactsLookup = EMPTY\n\n const rebuild = () => {\n const namespaces = deps.describe.getSnapshot().view?.namespaces ?? []\n lookup = providers.length === 0 || namespaces.length === 0\n ? EMPTY\n : buildModelFacts(providers, namespaces)\n for (const listener of [...listeners]) listener()\n }\n\n return {\n source: {\n getSnapshot: () => lookup,\n subscribe: (listener) => {\n listeners.add(listener)\n return () => { listeners.delete(listener) }\n },\n },\n start: () => {\n const off = deps.describe.subscribe(rebuild)\n void deps.describe.ensure().catch(() => { /* a failed read leaves the empty lookup in place */ })\n void deps.listProviders().then(\n (answer) => {\n providers = answer.ok && answer.value !== undefined ? answer.value : []\n rebuild()\n },\n () => { /* the directory being unavailable leaves every model's facts unknown */ },\n )\n rebuild()\n return () => {\n off()\n listeners.clear()\n }\n },\n }\n}\n","/** Locale bundles for the all-provider model picker. */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'modelSubmenu'\n\n/** Locale keys the picker renders. */\nexport type ModelSubmenuLocaleKey =\n | 'status.loading'\n | 'status.empty'\n | 'status.failed'\n | 'status.providerFailed'\n | 'action.reload'\n | 'error.action'\n | 'error.sessionInUse'\n | 'picker.title'\n | 'picker.close'\n | 'picker.search'\n | 'picker.searchEmpty'\n | 'picker.all'\n | 'picker.favorite'\n | 'picker.unfavorite'\n | 'picker.hintClose'\n | 'meta.effort'\n | 'meta.images'\n | 'effort.label'\n | 'effort.providerDefault'\n | 'effort.useDefault'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The all-provider model picker's copy. */\n modelSubmenu: ModelSubmenuLocaleKey\n }\n}\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh: Record<ModelSubmenuLocaleKey, string> = {\n 'status.loading': '正在加载模型…',\n 'status.empty': '没有可用的模型。',\n 'status.failed': '模型列表加载失败:{message}',\n 'status.providerFailed': '{name} 加载失败:{message}',\n 'action.reload': '重新加载',\n 'error.action': '模型操作失败:{message}',\n 'error.sessionInUse': '当前会话已被占用,可能是其他正在运行的 DSH 导致的(如其他 dsh web、桌面端),请退出其他正在运行的 DSH 后重试。',\n 'picker.title': '切换模型',\n 'picker.close': '关闭',\n 'picker.search': '搜索模型或提供商…',\n 'picker.searchEmpty': '没有匹配的模型。',\n 'picker.all': '全部',\n 'picker.favorite': '收藏 {model}',\n 'picker.unfavorite': '取消收藏 {model}',\n 'picker.hintClose': '关闭',\n 'meta.effort': '思考',\n 'meta.images': '图片输入',\n 'effort.label': '推理等级',\n 'effort.providerDefault': 'Default',\n 'effort.useDefault': '切换为默认档位',\n}\n\n/** English dictionary, checked complete against the zh key set. */\nexport const en: Record<ModelSubmenuLocaleKey, string> = {\n 'status.loading': 'Loading models…',\n 'status.empty': 'No models available.',\n 'status.failed': 'Model list failed to load: {message}',\n 'status.providerFailed': '{name} failed to load: {message}',\n 'action.reload': 'Reload',\n 'error.action': 'Model operation failed: {message}',\n '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.',\n 'picker.title': 'Switch model',\n 'picker.close': 'Close',\n 'picker.search': 'Search models or providers…',\n 'picker.searchEmpty': 'No matching models.',\n 'picker.all': 'All',\n 'picker.favorite': 'Favorite {model}',\n 'picker.unfavorite': 'Remove {model} from favorites',\n 'picker.hintClose': 'close',\n 'meta.effort': 'Effort',\n 'meta.images': 'Images',\n 'effort.label': 'Reasoning effort',\n 'effort.providerDefault': 'Default',\n 'effort.useDefault': 'Switch to the provider default',\n}\n","import type { Context as ClientContext } from '@deepseek-ai/cordis'\n// Type-only: `slots` and its LocaleNamespaceMap merge come from the renderer and\n// the slot package; the model menu's options slot and the composer overlay from\n// ui-conversation; the `modelDirectories` service and the seat's injected face\n// from ui-model-selection's browser half; `configForms` from the settings\n// provider, whose shared mirror answers the metadata line.\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-model-selection/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { EffortTint } from './EffortTint.tsx'\nimport { ModelMenuBridge } from './ModelMenuBridge.tsx'\nimport { ModelPickerHost } from './ModelPickerHost.tsx'\nimport { createPickerStore } from './picker-state.ts'\nimport { createFactsSource, type DescribeFace } from './facts-source.ts'\nimport { NS, en, zh } from './locales.ts'\n\nexport { EffortTint } from './EffortTint.tsx'\nexport { ModelMenuBridge } from './ModelMenuBridge.tsx'\nexport { ModelPickerHost } from './ModelPickerHost.tsx'\nexport { createPickerStore } from './picker-state.ts'\n\n/**\n * Cordis services required by the browser half. `slots` is the contribution\n * registry, `sessions` tells whether a session may select a model,\n * `modelDirectories` owns the per-session catalog snapshot both entries render\n * from, and `configForms` carries the settings provider's shared describe\n * mirror, which answers each model's context window and accepted modalities.\n * `remote`, `remote.llm`, and `remote.session` are declared because a service\n * method runs on the calling context: `directoryFor` reads\n * `this.ctx.remote.session`, and the provider directory that names each\n * settings address is read through `this.ctx.remote.llm`.\n */\nexport const inject = [\n 'configForms',\n 'locale',\n 'modelDirectories',\n 'remote',\n 'remote.llm',\n 'remote.session',\n 'sessions',\n 'slots',\n]\n\n/**\n * Client plugin body: register this plugin's dictionaries, then contribute the\n * bridge that turns the Host's model cell into the picker's door and its effort\n * cell into the reasoning-level slider, the overlay that renders the picker, and\n * the tint that keeps the collapsed trigger's effort caption on the same color.\n *\n * The two are separate entries because the Host unmounts the model menu the\n * moment a row is clicked; the overlay slot stays mounted, so the dialog\n * survives the menu's teardown.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-model-submenu: dictionaries')\n ctx.inject(['slots', 'modelDirectories', 'configForms', 'remote'], (scope) => {\n const models = scope.modelDirectories\n const sessions = scope.sessions\n const picker = createPickerStore()\n const facts = createFactsSource({\n describe: scope.configForms.describe() as DescribeFace,\n listProviders: () => scope.remote.llm.listConfigurableProviders(),\n })\n ctx.effect(() => facts.start(), 'dsh-model-submenu: model metadata')\n /**\n * Build one entry's business face for a session.\n * @param sessionId - the slot's session scope.\n * @returns the catalog face plus the session and store both entries need.\n */\n const faceOf = (sessionId: string) => {\n // The slot's inject parameter is typed as a bare string; the service\n // faces carry the branded SessionId of that same runtime value.\n const key = sessionId as Parameters<typeof models.directoryFor>[0]\n const directory = models.directoryFor(key)\n const available = sessions.subagentAddress(key) === undefined\n return {\n sessionId,\n picker,\n available,\n directory: directory.store,\n factsSource: facts.source,\n load: () => {\n if (available) directory.load().catch(() => {})\n },\n resolveText: (text: Parameters<typeof ctx.locale.resolveText>[0]) => ctx.locale.resolveText(text),\n select: (selection: Parameters<typeof directory.select>[0]) => available\n ? directory.select(selection)\n : Promise.resolve(undefined),\n }\n }\n scope.slots.inject('conversation.input.model.options', () => scope.slots.register({\n name: 'conversation.input.model.options',\n id: 'model-menu-bridge',\n registrant: '@lynn123411/dsh-model-submenu',\n locale: NS,\n inject: faceOf,\n }, ModelMenuBridge))\n scope.slots.inject('conversation.input.overlay', () => scope.slots.register({\n name: 'conversation.input.overlay',\n id: 'model-picker',\n registrant: '@lynn123411/dsh-model-submenu',\n locale: NS,\n inject: faceOf,\n }, ModelPickerHost))\n // The trigger's effort caption lives outside the menu, so its tint needs an\n // entry that stays mounted for the whole session.\n scope.slots.inject('conversation.input.overlay', () => scope.slots.register({\n name: 'conversation.input.overlay',\n id: 'model-effort-tint',\n registrant: '@lynn123411/dsh-model-submenu',\n inject: faceOf,\n }, EffortTint))\n })\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynn123411/dsh-model-submenu",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "description": "增强输入框的模型菜单:第一行直接打开跨供应商的模型弹窗,第二行的推理等级是可拖动的滑块,档位带配色。",
5
5
  "license": "MIT",
6
6
  "packageManager": "pnpm@11.7.0",
@@ -392,6 +392,3 @@
392
392
  flex-wrap: wrap;
393
393
  }
394
394
 
395
- .footerDot {
396
- color: var(--dsw-alias-label-dimmed);
397
- }
@@ -31,7 +31,6 @@ import {
31
31
  searchRows,
32
32
  type CatalogModel,
33
33
  type ModelRow as CatalogRow,
34
- type PickerTab,
35
34
  } from './rows.ts'
36
35
  import { Tag, isMobileDevice } from '@deepseek-ai/dsh-client-ui-primitives'
37
36
  import type { ModelFactsLookup } from './facts-source.ts'
@@ -85,8 +84,6 @@ export function ModelPicker({
85
84
  const listRef = useRef<HTMLDivElement | null>(null)
86
85
  const [query, setQuery] = useState('')
87
86
  const [tab, setTab] = useState(ALL_TAB)
88
- const [highlight, setHighlight] = useState(0)
89
- const tabRefs = useRef(new Map<string, HTMLButtonElement>())
90
87
 
91
88
  const tabs = useMemo(() => pickerTabs(state.groups, t('picker.all')), [state.groups, t])
92
89
  const reason = useMemo(() => reasoningOf(state.groups), [state.groups])
@@ -106,7 +103,6 @@ export function ModelPicker({
106
103
  if (!open) return
107
104
  setQuery('')
108
105
  setTab(ALL_TAB)
109
- setHighlight(0)
110
106
  if (listRef.current !== null) listRef.current.scrollTop = 0
111
107
  // 粗指针设备不自动聚焦文本输入:移动端弹键盘会顶起布局,宿主座位也这样守。
112
108
  const frame = requestAnimationFrame(() => {
@@ -115,34 +111,8 @@ export function ModelPicker({
115
111
  return () => { cancelAnimationFrame(frame) }
116
112
  }, [open])
117
113
 
118
- useEffect(() => {
119
- setHighlight((current) => visible.length === 0 ? 0 : Math.min(current, visible.length - 1))
120
- }, [visible.length])
121
-
122
- useEffect(() => {
123
- const row = listRef.current?.querySelector<HTMLElement>(`[data-row-index="${highlight}"]`)
124
- row?.scrollIntoView({ block: 'nearest' })
125
- }, [highlight])
126
-
127
114
  if (!open) return null
128
115
 
129
- const activeRow = visible[highlight]
130
- const activeId = activeRow === undefined ? undefined : `${id}-row-${highlight}`
131
- const onSearchKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
132
- if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
133
- event.preventDefault()
134
- if (visible.length === 0) return
135
- setHighlight((current) => {
136
- const next = current + (event.key === 'ArrowDown' ? 1 : -1)
137
- return (next + visible.length) % visible.length
138
- })
139
- return
140
- }
141
- if (event.key === 'Enter' && activeRow !== undefined && !busy) {
142
- event.preventDefault()
143
- pick(activeRow)
144
- }
145
- }
146
116
  const pick = (row: CatalogRow) => {
147
117
  const selection = planModelCommit(live, row.provider, row.model, reason, rememberedOf?.(row.provider, row.model))
148
118
  if (selection === null) {
@@ -151,27 +121,6 @@ export function ModelPicker({
151
121
  }
152
122
  onPick(selection)
153
123
  }
154
- /** Move to another tab and hand it the keyboard, in one step. */
155
- const goTab = (next: PickerTab | undefined) => {
156
- if (next === undefined || next.id === tab) return
157
- setTab(next.id)
158
- setHighlight(0)
159
- tabRefs.current.get(next.id)?.focus()
160
- }
161
- const onTabKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
162
- const index = tabs.findIndex((candidate) => candidate.id === tab)
163
- if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {
164
- event.preventDefault()
165
- const step = event.key === 'ArrowRight' ? 1 : -1
166
- goTab(tabs[((index === -1 ? 0 : index) + step + tabs.length) % tabs.length])
167
- return
168
- }
169
- if (event.key === 'Home' || event.key === 'End') {
170
- event.preventDefault()
171
- goTab(tabs[event.key === 'Home' ? 0 : tabs.length - 1])
172
- }
173
- }
174
-
175
124
  const failures = state.failures.filter((failure) => tab === ALL_TAB || failure.id === tab)
176
125
  const failed = state.error !== null && state.status === 'error'
177
126
  const loading = state.status === 'loading'
@@ -192,13 +141,11 @@ export function ModelPicker({
192
141
  role="searchbox"
193
142
  aria-label={t('picker.search')}
194
143
  aria-controls={`${id}-list`}
195
- aria-activedescendant={activeId}
196
144
  placeholder={t('picker.search')}
197
145
  icon={<IconSearchOutlineRegular size={14} />}
198
146
  value={query}
199
147
  readOnly={busy}
200
148
  onChange={(event) => { setQuery(event.target.value) }}
201
- onKeyDown={onSearchKeyDown}
202
149
  data-modal-autofocus=""
203
150
  />
204
151
  </div>
@@ -206,10 +153,6 @@ export function ModelPicker({
206
153
  {tabs.map((entry) => (
207
154
  <button
208
155
  key={entry.id}
209
- ref={(node) => {
210
- if (node === null) tabRefs.current.delete(entry.id)
211
- else tabRefs.current.set(entry.id, node)
212
- }}
213
156
  type="button"
214
157
  role="tab"
215
158
  id={`${id}-tab-${entry.id}`}
@@ -217,29 +160,13 @@ export function ModelPicker({
217
160
  aria-controls={`${id}-list`}
218
161
  tabIndex={0}
219
162
  className={css.pickerTab}
220
- onClick={() => {
221
- setTab(entry.id)
222
- setHighlight(0)
223
- }}
224
- onKeyDown={onTabKeyDown}
225
- onFocus={() => {
226
- if (entry.id !== tab) {
227
- setTab(entry.id)
228
- setHighlight(0)
229
- }
230
- }}
163
+ onClick={() => { setTab(entry.id) }}
231
164
  >
232
165
  {entry.label}
233
166
  </button>
234
167
  ))}
235
168
  </div>
236
- <div
237
- ref={listRef}
238
- id={`${id}-list`}
239
- className={css.pickerList}
240
- role="menu"
241
- aria-label={t('picker.title')}
242
- >
169
+ <div ref={listRef} id={`${id}-list`} className={css.pickerList}>
243
170
  {loading && <div className={css.notice}>{t('status.loading')}</div>}
244
171
  {failed && (
245
172
  <div className={css.notice}>
@@ -256,8 +183,8 @@ export function ModelPicker({
256
183
  {visible.length === 0 && !loading && !failed && (
257
184
  <div className={css.notice}>{state.groups.length === 0 ? t('status.empty') : t('picker.searchEmpty')}</div>
258
185
  )}
259
- {visible.map((row, index) => (
260
- <div key={`${row.provider}/${row.model}`} data-row-index={index} id={`${id}-row-${index}`}>
186
+ {visible.map((row) => (
187
+ <div key={`${row.provider}/${row.model}`}>
261
188
  <ModelRow
262
189
  name={row.entry.name}
263
190
  badge={row.entry.badge === undefined
@@ -277,10 +204,7 @@ export function ModelPicker({
277
204
  starLabel={isFavorite(favorites, row.provider, row.model)
278
205
  ? t('picker.unfavorite', { model: row.entry.name })
279
206
  : t('picker.favorite', { model: row.entry.name })}
280
- starTabStop={index === highlight}
281
- rowTabIndex={-1}
282
207
  onPick={() => { pick(row) }}
283
- onFocusRow={() => { setHighlight(index) }}
284
208
  onToggleFavorite={() => { onToggleFavorite(row.provider, row.model) }}
285
209
  />
286
210
  </div>
@@ -288,12 +212,6 @@ export function ModelPicker({
288
212
  </div>
289
213
  <div className={css.pickerFooter}>
290
214
  <span className={css.footerHint}>
291
- <ShortcutKeys keys={['↑', '↓']} />
292
- <span>{t('picker.hintNavigate')}</span>
293
- <span className={css.footerDot}>·</span>
294
- <ShortcutKeys keys={['Enter']} />
295
- <span>{t('picker.hintSelect')}</span>
296
- <span className={css.footerDot}>·</span>
297
215
  <ShortcutKeys keys={['Esc']} />
298
216
  <span>{t('picker.hintClose')}</span>
299
217
  </span>
@@ -27,14 +27,8 @@ export interface ModelRowProps {
27
27
  readonly meta?: string | undefined;
28
28
  /** Localized accessible name for the star control. */
29
29
  readonly starLabel?: string | undefined;
30
- /** Whether the star is a Tab stop right now (the picker's roving highlight). */
31
- readonly starTabStop?: boolean;
32
- /** Tab order of the row itself; the picker keeps rows out of it so ↑/↓ own the walk. */
33
- readonly rowTabIndex?: number;
34
30
  /** Pick this row. */
35
31
  readonly onPick: () => void;
36
- /** The row took the keyboard, so its star becomes reachable. */
37
- readonly onFocusRow?: (() => void) | undefined;
38
32
  /** Toggle this row's favorite. */
39
33
  readonly onToggleFavorite: () => void;
40
34
  /** Extra row content, such as the picker's disabled reason. */
@@ -48,7 +42,7 @@ export interface ModelRowProps {
48
42
  */
49
43
  export function ModelRow({
50
44
  name, badge, selected, busy, favorited, meta, starLabel,
51
- starTabStop = false, rowTabIndex, onPick, onFocusRow, onToggleFavorite, children,
45
+ onPick, onToggleFavorite, children,
52
46
  }: ModelRowProps) {
53
47
  return <div
54
48
  className={css.row}
@@ -57,14 +51,11 @@ export function ModelRow({
57
51
  >
58
52
  <button
59
53
  type="button"
60
- role="menuitemradio"
61
- aria-checked={selected}
62
- tabIndex={rowTabIndex}
54
+ aria-current={selected ? 'true' : undefined}
63
55
  className={css.rowPick}
64
56
  title={name}
65
57
  disabled={busy}
66
58
  onClick={onPick}
67
- onFocus={onFocusRow}
68
59
  >
69
60
  <span className={css.rowCopy}>
70
61
  <span className={css.rowName}>{name}</span>
@@ -77,7 +68,6 @@ export function ModelRow({
77
68
  className={css.starButton}
78
69
  aria-pressed={favorited}
79
70
  aria-label={starLabel}
80
- tabIndex={starTabStop ? 0 : -1}
81
71
  disabled={busy}
82
72
  onClick={onToggleFavorite}
83
73
  >
@@ -19,8 +19,6 @@ export type ModelSubmenuLocaleKey =
19
19
  | 'picker.all'
20
20
  | 'picker.favorite'
21
21
  | 'picker.unfavorite'
22
- | 'picker.hintNavigate'
23
- | 'picker.hintSelect'
24
22
  | 'picker.hintClose'
25
23
  | 'meta.effort'
26
24
  | 'meta.images'
@@ -51,8 +49,6 @@ export const zh: Record<ModelSubmenuLocaleKey, string> = {
51
49
  'picker.all': '全部',
52
50
  'picker.favorite': '收藏 {model}',
53
51
  'picker.unfavorite': '取消收藏 {model}',
54
- 'picker.hintNavigate': '导航',
55
- 'picker.hintSelect': '选择',
56
52
  'picker.hintClose': '关闭',
57
53
  'meta.effort': '思考',
58
54
  'meta.images': '图片输入',
@@ -77,8 +73,6 @@ export const en: Record<ModelSubmenuLocaleKey, string> = {
77
73
  'picker.all': 'All',
78
74
  'picker.favorite': 'Favorite {model}',
79
75
  'picker.unfavorite': 'Remove {model} from favorites',
80
- 'picker.hintNavigate': 'navigate',
81
- 'picker.hintSelect': 'select',
82
76
  'picker.hintClose': 'close',
83
77
  'meta.effort': 'Effort',
84
78
  'meta.images': 'Images',