@klarkxy/dsh-current-title 0.3.2 → 0.3.4

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
@@ -102,6 +102,7 @@ function parseModelMenuChoices(value, bound) {
102
102
  provider,
103
103
  model,
104
104
  label: `${providerName} / ${name}`,
105
+ providerLabel: providerName,
105
106
  efforts
106
107
  });
107
108
  }
@@ -148,11 +149,11 @@ function modelMenuShortName(choice) {
148
149
  /** Group catalog choices by provider and filter them by a case-insensitive query. */
149
150
  function groupModelMenuChoices(choices, query) {
150
151
  const needle = query.trim().toLowerCase();
151
- const matched = needle ? choices.filter((choice) => choice.label.toLowerCase().includes(needle) || choice.model.toLowerCase().includes(needle) || choice.provider.toLowerCase().includes(needle)) : choices;
152
+ const matched = needle ? choices.filter((choice) => choice.label.toLowerCase().includes(needle) || choice.model.toLowerCase().includes(needle) || choice.provider.toLowerCase().includes(needle) || (choice.providerLabel?.toLowerCase().includes(needle) ?? false)) : choices;
152
153
  const groups = [];
153
154
  for (const choice of matched) {
154
155
  const sep = choice.label.indexOf(" / ");
155
- const name = sep > 0 ? choice.label.slice(0, sep) : choice.provider;
156
+ const name = choice.providerLabel ?? (sep > 0 ? choice.label.slice(0, sep) : choice.provider);
156
157
  const last = groups.at(-1);
157
158
  if (last && last.provider === choice.provider) last.items.push(choice);
158
159
  else groups.push({
@@ -525,7 +526,9 @@ Object.values({
525
526
  "--dsh-scrollbar-thumb-hover",
526
527
  "--dsh-content-font-size-secondary",
527
528
  "--dsh-content-font-delta",
528
- "--dsh-frame-top-clearance"
529
+ "--dsh-frame-top-clearance",
530
+ "--dsh-composer-model-icon-display",
531
+ "--dsh-composer-model-text-display"
529
532
  ]
530
533
  }).flat();
531
534
  /**
@@ -1229,7 +1232,7 @@ function ModelMenu(props) {
1229
1232
  empty: "No matching models",
1230
1233
  clear: "Clear search"
1231
1234
  };
1232
- const { open, onOpenChange, anchor, choices, selected, onPick, leading = [], onPickLeading, efforts = [], selectedEffort, onPickEffort, modelLabel = copy.model, modelValue = "—", effortLabel = copy.effort, effortValue, defaultEffortLabel = copy.default, searchPlaceholder = copy.search, emptyLabel = copy.empty, clearSearchLabel = copy.clear, className, listClassName } = props;
1235
+ const { open, onOpenChange, anchor, choices, selected, onPick, leading = [], onPickLeading, efforts = [], selectedEffort, onPickEffort, modelLabel = copy.model, modelValue = "—", effortLabel = copy.effort, effortValue, defaultEffortLabel = copy.default, searchPlaceholder = copy.search, emptyLabel = copy.empty, clearSearchLabel = copy.clear, initialPane = "root", alwaysShowSearch = false, reasoningSlider = false, menuHeader, disabled = false, busy = false, closeOnPick = true, closeOnDisable = true, className, listClassName } = props;
1233
1236
  const rootRef = (0, react.useRef)(null);
1234
1237
  const openRef = (0, react.useRef)(open);
1235
1238
  openRef.current = open;
@@ -1244,9 +1247,10 @@ function ModelMenu(props) {
1244
1247
  const [pane, setPane] = (0, react.useState)("root");
1245
1248
  const [query, setQuery] = (0, react.useState)("");
1246
1249
  const [highlightedIndex, setHighlightedIndex] = (0, react.useState)(null);
1250
+ const [keyNav, setKeyNav] = (0, react.useState)(0);
1247
1251
  const [menuPos, setMenuPos] = (0, react.useState)(null);
1248
1252
  const id = (0, react.useId)();
1249
- const showSearch = choices.length > 4;
1253
+ const showSearch = alwaysShowSearch || choices.length > 4;
1250
1254
  const effortRows = (0, react.useMemo)(() => [{
1251
1255
  id: "",
1252
1256
  name: defaultEffortLabel
@@ -1263,7 +1267,12 @@ function ModelMenu(props) {
1263
1267
  items: (0, _deepseek_ai_dsh_client_ui_primitives.rankByName)(group.items.map((choice) => ({
1264
1268
  choice,
1265
1269
  name: modelMenuShortName(choice),
1266
- label: choice.model
1270
+ label: [
1271
+ choice.provider,
1272
+ choice.providerLabel,
1273
+ choice.model,
1274
+ choice.label
1275
+ ].filter(Boolean).join(" ")
1267
1276
  })), showSearch ? query.trim() : "")
1268
1277
  })).filter((group) => group.items.length > 0), [
1269
1278
  choices,
@@ -1295,10 +1304,12 @@ function ModelMenu(props) {
1295
1304
  paneFocus.current = "drill";
1296
1305
  setPane(next);
1297
1306
  };
1298
- /** Leave a drilled pane for the root one, handing the keyboard back to its cell. */
1307
+ /** Leave a drilled pane for the entry pane, handing the keyboard back to it.
1308
+ * A root entry refocuses the cell that opened the pane; a search-first
1309
+ * (model) entry refocuses that pane's own intent. */
1299
1310
  const back = (from) => {
1300
- paneFocus.current = from;
1301
- setPane("root");
1311
+ paneFocus.current = initialPane === "root" ? from : "drill";
1312
+ setPane(initialPane);
1302
1313
  };
1303
1314
  const moveFocus = (offset) => {
1304
1315
  const items = itemRefs.current.filter((item) => item !== null && !item.disabled);
@@ -1338,10 +1349,18 @@ function ModelMenu(props) {
1338
1349
  }, [requestedOpen, open]);
1339
1350
  (0, react.useEffect)(() => {
1340
1351
  if (!open) return;
1341
- setPane("root");
1352
+ setPane(initialPane);
1353
+ if (initialPane === "model") paneFocus.current = "drill";
1342
1354
  setQuery("");
1343
1355
  setHighlightedIndex(null);
1344
- }, [open]);
1356
+ }, [open, initialPane]);
1357
+ (0, react.useEffect)(() => {
1358
+ if (disabled && open && closeOnDisable) changeOpen(false);
1359
+ }, [
1360
+ disabled,
1361
+ open,
1362
+ closeOnDisable
1363
+ ]);
1345
1364
  (0, react.useEffect)(() => {
1346
1365
  if (!open) return;
1347
1366
  const closeOutside = (event) => {
@@ -1349,9 +1368,25 @@ function ModelMenu(props) {
1349
1368
  if (rootRef.current?.contains(target) || menuRef.current?.contains(target)) return;
1350
1369
  changeOpen(false);
1351
1370
  };
1371
+ const escapeOutside = (event) => {
1372
+ if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
1373
+ if (!openRef.current) return;
1374
+ event.preventDefault();
1375
+ if (pane !== initialPane) back(pane);
1376
+ else close(true);
1377
+ };
1352
1378
  document.addEventListener("mousedown", closeOutside);
1353
- return () => document.removeEventListener("mousedown", closeOutside);
1354
- }, [open, onOpenChange]);
1379
+ document.addEventListener("keydown", escapeOutside);
1380
+ return () => {
1381
+ document.removeEventListener("mousedown", closeOutside);
1382
+ document.removeEventListener("keydown", escapeOutside);
1383
+ };
1384
+ }, [
1385
+ open,
1386
+ onOpenChange,
1387
+ pane,
1388
+ initialPane
1389
+ ]);
1355
1390
  (0, react.useEffect)(() => {
1356
1391
  const intent = paneFocus.current;
1357
1392
  if (!open || menuPos === null || intent === null) return;
@@ -1381,13 +1416,21 @@ function ModelMenu(props) {
1381
1416
  pane,
1382
1417
  filteredGroups
1383
1418
  ]);
1419
+ const modelPaneOpen = open && pane === "model";
1420
+ const scrollSignal = !modelPaneOpen ? "closed" : keyNav === 0 ? "open" : `key:${keyNav}`;
1421
+ const scrollSignalRef = (0, react.useRef)(null);
1384
1422
  (0, react.useLayoutEffect)(() => {
1385
- if (open && pane === "model" && activeModelIndex >= 0) itemRefs.current[leading.length + activeModelIndex]?.scrollIntoView({ block: "nearest" });
1423
+ if (!modelPaneOpen || activeModelIndex < 0) {
1424
+ if (!modelPaneOpen) scrollSignalRef.current = null;
1425
+ return;
1426
+ }
1427
+ if (scrollSignalRef.current === scrollSignal) return;
1428
+ scrollSignalRef.current = scrollSignal;
1429
+ itemRefs.current[leading.length + activeModelIndex]?.scrollIntoView({ block: "nearest" });
1386
1430
  }, [
1387
- open,
1388
- pane,
1431
+ modelPaneOpen,
1432
+ scrollSignal,
1389
1433
  activeModelIndex,
1390
- visibleModels,
1391
1434
  leading.length
1392
1435
  ]);
1393
1436
  (0, react.useLayoutEffect)(() => {
@@ -1402,7 +1445,7 @@ function ModelMenu(props) {
1402
1445
  let x = rect.right - lw, y = rect.top - 8 - lh;
1403
1446
  if (lw > 0) x = Math.min(Math.max(x, margin), window.innerWidth - lw - margin);
1404
1447
  if (lh > 0) y = Math.min(Math.max(y, margin), window.innerHeight - lh - margin);
1405
- setMenuPos({
1448
+ setMenuPos((prev) => prev && prev.left === x && prev.top === y ? prev : {
1406
1449
  left: x,
1407
1450
  top: y
1408
1451
  });
@@ -1422,6 +1465,13 @@ function ModelMenu(props) {
1422
1465
  ]);
1423
1466
  function onKeyDown(event) {
1424
1467
  if (event.defaultPrevented || event.nativeEvent.isComposing) return;
1468
+ if (event.key === "Escape" && open) {
1469
+ event.preventDefault();
1470
+ if (pane !== initialPane) back(pane);
1471
+ else close(true);
1472
+ return;
1473
+ }
1474
+ if (disabled) return;
1425
1475
  if (!open && [
1426
1476
  "Enter",
1427
1477
  " ",
@@ -1435,17 +1485,12 @@ function ModelMenu(props) {
1435
1485
  requestOpen();
1436
1486
  return;
1437
1487
  }
1438
- if (event.key === "Escape" && open) {
1439
- event.preventDefault();
1440
- if (pane !== "root") back(pane);
1441
- else close(true);
1442
- return;
1443
- }
1444
1488
  if (!open) return;
1445
1489
  if (pane === "model" && showSearch && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
1446
1490
  event.preventDefault();
1447
1491
  if (visibleModels.length > 0) {
1448
1492
  setHighlightedIndex((activeModelIndex + (event.key === "ArrowDown" ? 1 : -1) + visibleModels.length) % visibleModels.length);
1493
+ setKeyNav((n) => n + 1);
1449
1494
  searchRef.current?.focus();
1450
1495
  }
1451
1496
  return;
@@ -1454,19 +1499,19 @@ function ModelMenu(props) {
1454
1499
  if (event.key === "Tab" && visibleModels.length === 0) return;
1455
1500
  event.preventDefault();
1456
1501
  const highlighted = visibleModels[activeModelIndex];
1457
- if (highlighted) {
1502
+ if (highlighted && !busy) {
1458
1503
  onPick({
1459
1504
  provider: highlighted.provider,
1460
1505
  model: highlighted.model
1461
1506
  });
1462
- close(true);
1507
+ if (closeOnPick) close(true);
1463
1508
  }
1464
1509
  return;
1465
1510
  }
1466
1511
  if (event.key === "Tab") {
1467
1512
  if (event.shiftKey) {
1468
1513
  event.preventDefault();
1469
- if (pane !== "root") back(pane);
1514
+ if (pane !== initialPane) back(pane);
1470
1515
  else close(true);
1471
1516
  return;
1472
1517
  }
@@ -1490,7 +1535,10 @@ function ModelMenu(props) {
1490
1535
  }
1491
1536
  }
1492
1537
  function onBlur(event) {
1493
- if (event.relatedTarget instanceof Node && (rootRef.current?.contains(event.relatedTarget) || menuRef.current?.contains(event.relatedTarget))) return;
1538
+ const related = event.relatedTarget;
1539
+ if (related instanceof Node && (rootRef.current?.contains(related) || menuRef.current?.contains(related) || !related.isConnected)) return;
1540
+ if (!closeOnPick && !(related instanceof Node)) return;
1541
+ if (busy && related == null) return;
1494
1542
  if (open) changeOpen(false);
1495
1543
  }
1496
1544
  itemRefs.current = [];
@@ -1511,11 +1559,16 @@ function ModelMenu(props) {
1511
1559
  "aria-label": modelLabel,
1512
1560
  lang: language,
1513
1561
  children: [
1562
+ menuHeader ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1563
+ className: "dsh-model-menu-header",
1564
+ children: menuHeader
1565
+ }) : null,
1514
1566
  pane === "root" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1515
1567
  ref: itemRef(),
1516
1568
  type: "button",
1517
1569
  role: "menuitem",
1518
1570
  className: "dsh-model-menu-cell",
1571
+ disabled,
1519
1572
  onClick: () => drill("model"),
1520
1573
  children: [
1521
1574
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1528,11 +1581,12 @@ function ModelMenu(props) {
1528
1581
  }),
1529
1582
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: "dsh-model-menu-cellChevron" })
1530
1583
  ]
1531
- }), efforts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1584
+ }), efforts.length > 0 && !reasoningSlider ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1532
1585
  ref: itemRef(),
1533
1586
  type: "button",
1534
1587
  role: "menuitem",
1535
1588
  className: "dsh-model-menu-cell",
1589
+ disabled,
1536
1590
  onClick: () => drill("effort"),
1537
1591
  children: [
1538
1592
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1551,6 +1605,7 @@ function ModelMenu(props) {
1551
1605
  className: "dsh-model-menu-searchRow",
1552
1606
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
1553
1607
  ref: searchRef,
1608
+ disabled,
1554
1609
  className: `dsh-model-menu-search${query !== "" ? " dsh-model-menu-searchWithQuery" : ""}`,
1555
1610
  type: "text",
1556
1611
  role: "searchbox",
@@ -1567,6 +1622,7 @@ function ModelMenu(props) {
1567
1622
  type: "button",
1568
1623
  className: "dsh-model-menu-searchClear",
1569
1624
  "aria-label": clearSearchLabel,
1625
+ disabled,
1570
1626
  onClick: () => {
1571
1627
  setQuery("");
1572
1628
  setHighlightedIndex(null);
@@ -1589,10 +1645,12 @@ function ModelMenu(props) {
1589
1645
  type: "button",
1590
1646
  role: "menuitemradio",
1591
1647
  "aria-checked": item.selected === true,
1648
+ disabled: disabled || busy,
1592
1649
  className: `dsh-model-menu-option${item.selected === true ? " dsh-model-menu-selected" : ""}`,
1593
1650
  onClick: () => {
1651
+ if (disabled || busy) return;
1594
1652
  onPickLeading?.(item.id);
1595
- close(true);
1653
+ if (closeOnPick) close(true);
1596
1654
  },
1597
1655
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1598
1656
  className: "dsh-model-menu-optionCopy",
@@ -1618,6 +1676,7 @@ function ModelMenu(props) {
1618
1676
  "aria-checked": checked,
1619
1677
  id: `${id}-model-${index}`,
1620
1678
  tabIndex: showSearch ? -1 : 0,
1679
+ disabled: disabled || busy,
1621
1680
  onFocus: () => setHighlightedIndex(index),
1622
1681
  "data-highlighted": index === activeModelIndex ? "" : void 0,
1623
1682
  className: `dsh-model-menu-option dsh-model-menu-modelOption${checked ? " dsh-model-menu-selected" : ""}${index === activeModelIndex ? " dsh-model-menu-optionActive" : ""}`,
@@ -1627,11 +1686,12 @@ function ModelMenu(props) {
1627
1686
  },
1628
1687
  title: choice.label,
1629
1688
  onClick: () => {
1689
+ if (disabled || busy) return;
1630
1690
  onPick({
1631
1691
  provider: choice.provider,
1632
1692
  model: choice.model
1633
1693
  });
1634
- close(true);
1694
+ if (closeOnPick) close(true);
1635
1695
  },
1636
1696
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1637
1697
  className: "dsh-model-menu-optionCopy",
@@ -1647,6 +1707,14 @@ function ModelMenu(props) {
1647
1707
  })
1648
1708
  }, group.provider))]
1649
1709
  }),
1710
+ reasoningSlider && efforts.length > 0 && filteredGroups.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EffortSlider, {
1711
+ label: effortLabel,
1712
+ rows: effortRows,
1713
+ selected: selectedEffort ?? "",
1714
+ disabled,
1715
+ busy,
1716
+ onPick: (value) => onPickEffort?.(value)
1717
+ }) : null,
1650
1718
  filteredGroups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1651
1719
  className: "dsh-model-menu-empty",
1652
1720
  role: "status",
@@ -1660,10 +1728,12 @@ function ModelMenu(props) {
1660
1728
  type: "button",
1661
1729
  role: "menuitemradio",
1662
1730
  "aria-checked": checked,
1731
+ disabled: disabled || busy,
1663
1732
  className: `dsh-model-menu-option${checked ? " dsh-model-menu-selected" : ""}`,
1664
1733
  onClick: () => {
1734
+ if (disabled || busy) return;
1665
1735
  onPickEffort?.(row.id || void 0);
1666
- close(true);
1736
+ if (closeOnPick) close(true);
1667
1737
  },
1668
1738
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1669
1739
  className: "dsh-model-menu-optionCopy",
@@ -1682,7 +1752,8 @@ function ModelMenu(props) {
1682
1752
  const trigger = (0, react.isValidElement)(anchor) ? (0, react.cloneElement)(anchor, {
1683
1753
  "aria-haspopup": "menu",
1684
1754
  "aria-expanded": open,
1685
- "aria-controls": open ? `${id}-menu` : void 0
1755
+ "aria-controls": open ? `${id}-menu` : void 0,
1756
+ "aria-disabled": disabled || void 0
1686
1757
  }) : anchor;
1687
1758
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1688
1759
  ref: rootRef,
@@ -1690,7 +1761,7 @@ function ModelMenu(props) {
1690
1761
  onKeyDown,
1691
1762
  onBlur,
1692
1763
  onClick: (event) => {
1693
- if (event.defaultPrevented) return;
1764
+ if (disabled || event.defaultPrevented) return;
1694
1765
  const button = event.target instanceof Element ? event.target.closest("button") : null;
1695
1766
  if (!(button instanceof HTMLButtonElement) || button.disabled || !rootRef.current?.contains(button)) return;
1696
1767
  requestOpen();
@@ -1702,6 +1773,102 @@ function ModelMenu(props) {
1702
1773
  });
1703
1774
  }
1704
1775
  /**
1776
+ * Discrete reasoning-effort slider shown under the model list in enhanced mode.
1777
+ *
1778
+ * A native range input (`step=1`) over exactly the rows the caller supplies —
1779
+ * the default row plus each effort the selected model advertises — so it can
1780
+ * never invent a level. It previews locally while dragging or arrowing and
1781
+ * commits only when the gesture ends (pointer up, key up, or blur), so a drag
1782
+ * does not write on every step. The menu stays open, so adjusting the effort
1783
+ * never loses the model selection or the search query.
1784
+ *
1785
+ * The input itself stays the entire interaction and accessibility surface —
1786
+ * keyboard, ARIA, disabled and the native step snapping all come from it —
1787
+ * while the track, gradient fill and thumb below are decorative layers painted
1788
+ * from the `--_p` percentage the preview position sets on the rail. Value
1789
+ * changes only update that custom property, so the CSS transitions animate
1790
+ * between levels without remounting nodes or restarting an animation.
1791
+ */
1792
+ function EffortSlider(props) {
1793
+ const { label, rows, selected, disabled = false, busy = false, onPick } = props;
1794
+ const active = Math.max(0, rows.findIndex((row) => row.id === selected));
1795
+ const [position, setPosition] = (0, react.useState)(active);
1796
+ const positionRef = (0, react.useRef)(position);
1797
+ (0, react.useEffect)(() => {
1798
+ positionRef.current = active;
1799
+ setPosition(active);
1800
+ }, [active]);
1801
+ const preview = (next) => {
1802
+ positionRef.current = next;
1803
+ setPosition(next);
1804
+ };
1805
+ const commit = () => {
1806
+ if (disabled || busy) return;
1807
+ const row = rows[positionRef.current];
1808
+ if (row !== void 0) onPick(row.id === "" ? void 0 : row.id);
1809
+ };
1810
+ const max = Math.max(0, rows.length - 1);
1811
+ const percent = max > 0 ? Math.round(position / max * 100) : 0;
1812
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1813
+ className: "dsh-model-menu-sliderGroup",
1814
+ role: "group",
1815
+ "aria-label": label,
1816
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1817
+ className: "dsh-model-menu-sliderHead",
1818
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1819
+ className: "dsh-model-menu-sliderTitle",
1820
+ children: label
1821
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1822
+ className: "dsh-model-menu-sliderValue",
1823
+ children: rows[position]?.name
1824
+ })]
1825
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1826
+ className: "dsh-model-menu-sliderRail",
1827
+ style: { "--_p": percent },
1828
+ children: [
1829
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1830
+ type: "range",
1831
+ className: "dsh-model-menu-sliderInput",
1832
+ min: 0,
1833
+ max,
1834
+ step: 1,
1835
+ value: position,
1836
+ disabled,
1837
+ "aria-label": label,
1838
+ "aria-disabled": disabled || busy || void 0,
1839
+ "aria-valuetext": rows[position]?.name,
1840
+ onChange: (event) => {
1841
+ const value = Number(event.target.value);
1842
+ if (Number.isFinite(value)) preview(value);
1843
+ },
1844
+ onPointerUp: commit,
1845
+ onKeyUp: commit,
1846
+ onBlur: commit
1847
+ }),
1848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1849
+ className: "dsh-model-menu-sliderTrack",
1850
+ "aria-hidden": "true",
1851
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1852
+ className: "dsh-model-menu-sliderFill",
1853
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsh-model-menu-sliderFillBar" })
1854
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1855
+ className: "dsh-model-menu-sliderNodes",
1856
+ children: rows.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1857
+ "data-covered": index <= position ? "" : void 0,
1858
+ className: "dsh-model-menu-sliderNode",
1859
+ style: { "--_n": max > 0 ? Math.round(index / max * 100) : 0 }
1860
+ }, row.id || "__default"))
1861
+ })]
1862
+ }),
1863
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1864
+ className: "dsh-model-menu-sliderThumb",
1865
+ "aria-hidden": "true"
1866
+ })
1867
+ ]
1868
+ })]
1869
+ });
1870
+ }
1871
+ /**
1705
1872
  * ModelMenu styles, transcribed from the official ModelSelect.module.css (the
1706
1873
  * published dsh-client-ui-model-selection bundle): card capped at
1707
1874
  * min(360px, 100vh - 96px), 34px option cells at 13/18 with 5px 7px padding,
@@ -1760,6 +1927,7 @@ const modelMenuCss = `
1760
1927
  overflow: hidden;
1761
1928
  }
1762
1929
  .dsh-model-menu-status, .dsh-model-menu-empty { color: var(--dsw-alias-label-tertiary); padding: 8px; font-size: 12px; line-height: 18px; }
1930
+ .dsh-model-menu-header { flex: none; padding: 2px 2px 4px; }
1763
1931
  .dsh-model-menu-searchRow { flex-shrink: 0; margin: 2px 0 3px; position: relative; }
1764
1932
  .dsh-model-menu-searchRow .dsh-model-menu-search { border-radius: var(--dsw-radius-md); background: none; border: 0 solid transparent; height: auto; padding: 5px 7px; display: flex; }
1765
1933
  .dsh-model-menu-searchRow .dsh-model-menu-search:focus-within { border-color: transparent; }
@@ -1838,8 +2006,236 @@ const modelMenuCss = `
1838
2006
  .dsh-model-menu-cellLabel { white-space: nowrap; flex: none; }
1839
2007
  .dsh-model-menu-cellValue { text-overflow: ellipsis; white-space: nowrap; text-align: right; min-width: 0; color: var(--dsw-alias-label-tertiary); flex: auto; overflow: hidden; }
1840
2008
  .dsh-model-menu-cellChevron { width: 12px; height: 12px; color: var(--dsw-alias-menu-icon); flex: none; }
2009
+ /* Discrete reasoning-effort slider under the model list (enhanced mode).
2010
+ * The painted control is layered over a fully functional native range input:
2011
+ * the input stays on top, transparent, and owns pointer/keyboard/ARIA, while
2012
+ * the decorative track, fill and thumb render beneath it at pointer-events:
2013
+ * none. Position lives in one custom property (--_p, 0–100) on the rail, so
2014
+ * the only animated work a level change triggers is a clip-path/left
2015
+ * transition — no keyframes, no element churn. */
2016
+ .dsh-model-menu-sliderGroup { border-top: 0.5px solid var(--dsw-alias-border-l2); flex: none; margin-top: 4px; padding: 6px 8px 8px; }
2017
+ .dsh-model-menu-sliderHead { align-items: baseline; justify-content: space-between; gap: 8px; margin: 0 2px 4px; display: flex; }
2018
+ .dsh-model-menu-sliderTitle { color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
2019
+ .dsh-model-menu-sliderValue { color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 600; line-height: 20px; transition: color var(--ds-transition-duration) var(--ds-ease-in-out); }
2020
+ .dsh-model-menu-sliderRail { position: relative; direction: ltr; height: 38px; margin-top: 2px; }
2021
+ .dsh-model-menu-sliderRail:has(:disabled) { opacity: 0.45; }
2022
+ .dsh-model-menu-sliderInput {
2023
+ appearance: none;
2024
+ -webkit-appearance: none;
2025
+ box-sizing: border-box;
2026
+ border: 0;
2027
+ cursor: pointer;
2028
+ direction: ltr;
2029
+ opacity: 0;
2030
+ margin: 0;
2031
+ padding: 0;
2032
+ width: 100%;
2033
+ height: 100%;
2034
+ position: absolute;
2035
+ inset: 0;
2036
+ z-index: 1;
2037
+ touch-action: pan-y;
2038
+ }
2039
+ .dsh-model-menu-sliderInput:disabled { cursor: default; }
2040
+ /* The transparent input must keep the painted thumb's geometry: an
2041
+ * appearance:none webkit thumb defaults to 16px, which would shift the
2042
+ * end-point positions 7px off the decorative 30px disc and misplace the
2043
+ * native drag hit target. Track gets the full rail height so the whole band
2044
+ * maps to values identically. */
2045
+ .dsh-model-menu-sliderInput::-webkit-slider-runnable-track { width: 100%; height: 100%; }
2046
+ .dsh-model-menu-sliderInput::-webkit-slider-thumb { appearance: none; -webkit-appearance: none; width: 30px; height: 30px; margin: 0; }
2047
+ .dsh-model-menu-sliderInput::-moz-range-track { width: 100%; height: 100%; }
2048
+ .dsh-model-menu-sliderInput::-moz-range-thumb { width: 30px; height: 30px; margin: 0; border: 0; }
2049
+ .dsh-model-menu-sliderTrack {
2050
+ pointer-events: none;
2051
+ border-radius: 999px;
2052
+ width: 100%;
2053
+ height: 26px;
2054
+ background: var(--dsw-alias-interactive-bg-hover);
2055
+ box-shadow: inset 0 0 0 0.5px var(--dsw-alias-border-l2);
2056
+ position: absolute;
2057
+ inset-inline: 0;
2058
+ top: 50%;
2059
+ transform: translateY(-50%);
2060
+ /* No overflow:hidden — it would clip the fill's drop-shadow glow; the bar's
2061
+ * own clip-path already supplies the rounded reveal shape. */
2062
+ }
2063
+ /* The accent ramp paints the full track width once and is revealed by an
2064
+ * animated clip-path, so raising the effort unwraps more of the spectrum
2065
+ * instead of squashing the same gradient into a narrower bar. The glow lives
2066
+ * on the un-clipped parent: a filter on the clipped element itself would be
2067
+ * clipped away with it. */
2068
+ .dsh-model-menu-sliderFill {
2069
+ display: block;
2070
+ height: 100%;
2071
+ filter: drop-shadow(0 0 5px var(--dsw-alias-state-business-primary));
2072
+ transition: filter var(--ds-transition-duration) var(--ds-ease-in-out);
2073
+ }
2074
+ .dsh-model-menu-sliderInput:hover:not(:disabled) ~ .dsh-model-menu-sliderTrack .dsh-model-menu-sliderFill,
2075
+ .dsh-model-menu-sliderInput:active:not(:disabled) ~ .dsh-model-menu-sliderTrack .dsh-model-menu-sliderFill {
2076
+ filter: drop-shadow(0 0 9px var(--dsw-alias-state-business-primary));
2077
+ }
2078
+ /* The fill stops at the thumb's center, not at the track edge — at a 30px
2079
+ * thumb the end stops sit 15px in from each side, matching the node dots. */
2080
+ .dsh-model-menu-sliderFillBar {
2081
+ display: block;
2082
+ height: 100%;
2083
+ background: linear-gradient(90deg,
2084
+ var(--dsw-alias-state-business-primary),
2085
+ var(--dsw-alias-state-success-primary),
2086
+ var(--dsw-alias-state-warn-primary),
2087
+ var(--dsw-alias-state-error-primary));
2088
+ clip-path: inset(0 calc(100% - 15px - (100% - 30px) * var(--_p, 0) / 100) 0 0 round 999px);
2089
+ transition: clip-path var(--ds-transition-duration) var(--ds-ease-in-out);
2090
+ }
2091
+ /* Stop dots: one per declared row (default + every advertised effort), evenly
2092
+ * spread across the band between the two thumb-center extremes (inset 15px,
2093
+ * i.e. half the 30px thumb). --_n is 0-100 along that band. A covered dot
2094
+ * reads as a pale pin through the gradient; an uncovered one stays a neutral
2095
+ * notch on the empty track. */
2096
+ .dsh-model-menu-sliderNodes {
2097
+ pointer-events: none;
2098
+ display: block;
2099
+ position: absolute;
2100
+ inset-inline: 15px;
2101
+ inset-block: 0;
2102
+ }
2103
+ .dsh-model-menu-sliderNode {
2104
+ pointer-events: none;
2105
+ box-sizing: border-box;
2106
+ width: 4px;
2107
+ height: 4px;
2108
+ border-radius: 50%;
2109
+ background: color-mix(in srgb, var(--dsw-alias-label-secondary) 35%, transparent);
2110
+ position: absolute;
2111
+ left: calc(var(--_n, 0) * 1%);
2112
+ top: 50%;
2113
+ transform: translate(-50%, -50%);
2114
+ transition: background-color var(--ds-transition-duration) var(--ds-ease-in-out),
2115
+ transform var(--ds-transition-duration) var(--ds-ease-in-out);
2116
+ }
2117
+ .dsh-model-menu-sliderNode[data-covered] { background: color-mix(in srgb, var(--dsw-alias-label-primary) 60%, transparent); }
2118
+ .dsh-model-menu-sliderThumb {
2119
+ pointer-events: none;
2120
+ box-sizing: border-box;
2121
+ border-radius: 50%;
2122
+ width: 30px;
2123
+ height: 30px;
2124
+ background: var(--dsw-alias-button-primary-fill);
2125
+ border: 0.5px solid var(--dsw-alias-border-l4);
2126
+ box-shadow: 0 0 0 3px var(--dsw-alias-interactive-bg-hover), var(--dsw-elevation-soft);
2127
+ position: absolute;
2128
+ left: calc((100% - 30px) * var(--_p, 0) / 100);
2129
+ top: 50%;
2130
+ transform: translateY(-50%) scale(0.92);
2131
+ transition: left var(--ds-transition-duration) var(--ds-ease-in-out),
2132
+ transform var(--ds-transition-duration) var(--ds-ease-in-out),
2133
+ box-shadow var(--ds-transition-duration) var(--ds-ease-in-out);
2134
+ }
2135
+ .dsh-model-menu-sliderInput:hover:not(:disabled) ~ .dsh-model-menu-sliderThumb { transform: translateY(-50%) scale(1); }
2136
+ .dsh-model-menu-sliderInput:active:not(:disabled) ~ .dsh-model-menu-sliderThumb {
2137
+ transform: translateY(-50%) scale(1.1);
2138
+ box-shadow: 0 0 0 4px var(--dsw-alias-interactive-bg-hover), 0 0 12px var(--dsw-alias-state-business-primary);
2139
+ }
2140
+ .dsh-model-menu-sliderInput:focus-visible ~ .dsh-model-menu-sliderThumb {
2141
+ box-shadow: 0 0 0 3px var(--dsw-alias-interactive-bg-hover),
2142
+ 0 0 0 calc(3px + var(--dsw-focus-ring-width)) var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
2143
+ }
2144
+ @media (prefers-reduced-motion: reduce) {
2145
+ .dsh-model-menu-sliderFill, .dsh-model-menu-sliderFillBar, .dsh-model-menu-sliderThumb,
2146
+ .dsh-model-menu-sliderNode, .dsh-model-menu-sliderValue { transition: none; }
2147
+ }
1841
2148
  `;
1842
2149
  //#endregion
2150
+ //#region ../dsh-plugin-kit/src/model-picker.tsx
2151
+ /**
2152
+ * Optional shared model picker (`klarkxy.model-picker` Factory).
2153
+ *
2154
+ * `@klarkxy/dsh-model-picker`, when installed, registers one root-scoped
2155
+ * Component Factory under `MODEL_PICKER_FACTORY`: an enhanced model menu with
2156
+ * a searchable provider-grouped list and a reasoning-effort slider, built on
2157
+ * the same ModelMenu recipe. Consumers keep their own ModelMenu as the
2158
+ * `fallback`; when no definition is registered the renderer resolves that
2159
+ * fallback verbatim, so the shared picker is strictly opt-in and every saved
2160
+ * value keeps its existing write path.
2161
+ *
2162
+ * `renderFactorySlot` is a framework-composed prop, never a context: the
2163
+ * component a plugin registers into any slot receives it automatically, but
2164
+ * the plugin must pass it down to whichever child renders the menu (or to the
2165
+ * render callback handed to registerSessionSettingsItem).
2166
+ *
2167
+ * The Factory manages its own open state; a consumer's `open`/`onOpenChange`
2168
+ * pair belongs to the fallback menu only and must not be fed to both.
2169
+ */
2170
+ /**
2171
+ * The trigger every consumer used to hand its own ModelMenu, rebuilt as the
2172
+ * factory's `anchor` local component: identical classes and chevron, the menu
2173
+ * state coming from the occurrence's props instead of local open state.
2174
+ *
2175
+ * `ariaLabel` and `title` are the whole accessible name and tooltip (the
2176
+ * caller formats "field: current route" itself because the factory's resolved
2177
+ * caption may intentionally be a short name). The component identity stays
2178
+ * put across label changes — a new function would unmount the trigger, and
2179
+ * its cleanup would close a menu that `closeOnPick={false}` is keeping open.
2180
+ * The latest label, title, and `onClose` are read from refs.
2181
+ */
2182
+ function useModelMenuAnchor(ariaLabel, title, onClose) {
2183
+ const closeRef = (0, react.useRef)(onClose);
2184
+ const labelRef = (0, react.useRef)(ariaLabel);
2185
+ const titleRef = (0, react.useRef)(title);
2186
+ closeRef.current = onClose;
2187
+ labelRef.current = ariaLabel;
2188
+ titleRef.current = title;
2189
+ return (0, react.useMemo)(() => {
2190
+ const ModelPickerTrigger = (props) => {
2191
+ (0, react.useEffect)(() => () => closeRef.current?.(), []);
2192
+ const record = props;
2193
+ const injected = {};
2194
+ for (const [key, value] of Object.entries(record)) if (key.startsWith("aria-")) injected[key] = value;
2195
+ const extraClass = typeof record.className === "string" ? ` ${record.className}` : "";
2196
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2197
+ type: "button",
2198
+ className: `dsh-model-menu-trigger${extraClass}`,
2199
+ disabled: props.disabled,
2200
+ "aria-haspopup": "menu",
2201
+ "aria-expanded": props.open,
2202
+ ...injected,
2203
+ "aria-label": labelRef.current,
2204
+ title: titleRef.current,
2205
+ children: [
2206
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2207
+ className: "dsh-model-menu-triggerLabel",
2208
+ children: props.label
2209
+ }),
2210
+ props.effortLabel ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2211
+ className: "dsh-model-menu-triggerEffort",
2212
+ children: props.effortLabel
2213
+ }) : null,
2214
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${props.open ? " dsh-model-menu-chevronOpen" : ""}` })
2215
+ ]
2216
+ });
2217
+ };
2218
+ return ModelPickerTrigger;
2219
+ }, []);
2220
+ }
2221
+ /**
2222
+ * Render the shared model picker while it is registered; `options.fallback`
2223
+ * whenever it is not (plugin absent, disabled, or unloaded).
2224
+ *
2225
+ * `options.anchor` is optional: the factory ships a default trigger, so only
2226
+ * consumers that need their own chrome (custom aria-label, tooltip, class)
2227
+ * pass one built with {@link useModelMenuAnchor}. The returned node is never
2228
+ * assumed to be the factory's own output — a defensive `?? fallback` covers a
2229
+ * renderer that yields null for an unregistered definition.
2230
+ */
2231
+ function renderModelPicker(renderFactorySlot, props, options) {
2232
+ if (typeof renderFactorySlot !== "function") return options.fallback;
2233
+ return renderFactorySlot("klarkxy.model-picker", props, {
2234
+ ...options.anchor ? { slots: { ["anchor"]: options.anchor } } : {},
2235
+ fallback: options.fallback
2236
+ }) ?? options.fallback;
2237
+ }
2238
+ //#endregion
1843
2239
  //#region ../dsh-plugin-kit/src/auxiliary-model-client.tsx
1844
2240
  const AUXILIARY_MODEL_PACKAGE = "@klarkxy/dsh-plugin-kit";
1845
2241
  const AUXILIARY_MODEL_SEAT_ID = "auxiliary-model";
@@ -1917,7 +2313,7 @@ async function callAuxiliaryRpc(client, endpoint, payload = {}) {
1917
2313
  * The shared card. All state comes from the shared host service over the
1918
2314
  * plugin-kit RPC channel; the card only renders the stored row.
1919
2315
  */
1920
- function AuxiliaryModelCard({ client, subscribe }) {
2316
+ function AuxiliaryModelCard({ client, subscribe, renderFactorySlot }) {
1921
2317
  const language = useAuxiliaryLocale(client);
1922
2318
  const text = auxiliaryCopy[language];
1923
2319
  const [settings, setSettings] = (0, react.useState)();
@@ -2038,6 +2434,17 @@ function AuxiliaryModelCard({ client, subscribe }) {
2038
2434
  refreshCatalog
2039
2435
  ]);
2040
2436
  const locked = busy;
2437
+ const current = settings?.model ?? {
2438
+ provider: "",
2439
+ model: ""
2440
+ };
2441
+ const follow = !current.provider || !current.model;
2442
+ const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
2443
+ const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
2444
+ const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
2445
+ const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
2446
+ const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
2447
+ const anchor = useModelMenuAnchor(`${text.model}: ${settings ? currentLabel : ""}`, void 0, () => setMenuOpen(false));
2041
2448
  if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2042
2449
  className: "ams-root dsh-ui-card",
2043
2450
  "data-testid": "auxiliary-model-settings",
@@ -2076,16 +2483,6 @@ function AuxiliaryModelCard({ client, subscribe }) {
2076
2483
  })
2077
2484
  ]
2078
2485
  });
2079
- const current = settings.model ?? {
2080
- provider: "",
2081
- model: ""
2082
- };
2083
- const follow = !current.provider || !current.model;
2084
- const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
2085
- const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
2086
- const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
2087
- const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
2088
- const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
2089
2486
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2090
2487
  className: "ams-root dsh-ui-card",
2091
2488
  "data-testid": "auxiliary-model-settings",
@@ -2109,28 +2506,12 @@ function AuxiliaryModelCard({ client, subscribe }) {
2109
2506
  state: catalogState,
2110
2507
  locale: language
2111
2508
  }),
2112
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
2509
+ renderModelPicker(renderFactorySlot, {
2113
2510
  locale: language,
2114
- open: menuOpen && !locked,
2511
+ disabled: locked,
2512
+ busy: locked,
2115
2513
  onOpenChange: (next) => setMenuOpen(next && !locked),
2116
2514
  listClassName: "ams-root ams-menu-portal",
2117
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2118
- type: "button",
2119
- className: "dsh-model-menu-trigger",
2120
- disabled: locked,
2121
- "aria-label": `${text.model}: ${currentLabel}`,
2122
- children: [
2123
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2124
- className: "dsh-model-menu-triggerLabel",
2125
- children: currentLabel
2126
- }),
2127
- !follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2128
- className: "dsh-model-menu-triggerEffort",
2129
- children: effortName ?? text.effortDefault
2130
- }) : null,
2131
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
2132
- ]
2133
- }),
2134
2515
  choices,
2135
2516
  selected: follow ? void 0 : {
2136
2517
  provider: current.provider,
@@ -2153,9 +2534,61 @@ function AuxiliaryModelCard({ client, subscribe }) {
2153
2534
  model: current.model,
2154
2535
  ...effort ? { reasoningEffort: effort } : {}
2155
2536
  }),
2537
+ triggerLabel: currentLabel,
2538
+ triggerEffortLabel: !follow && efforts.length ? effortName ?? text.effortDefault : void 0,
2156
2539
  modelLabel: text.model,
2157
2540
  modelValue: currentLabel,
2158
2541
  defaultEffortLabel: text.effortDefault
2542
+ }, {
2543
+ anchor,
2544
+ fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
2545
+ locale: language,
2546
+ open: menuOpen && !locked,
2547
+ onOpenChange: (next) => setMenuOpen(next && !locked),
2548
+ listClassName: "ams-root ams-menu-portal",
2549
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2550
+ type: "button",
2551
+ className: "dsh-model-menu-trigger",
2552
+ disabled: locked,
2553
+ "aria-label": `${text.model}: ${currentLabel}`,
2554
+ children: [
2555
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2556
+ className: "dsh-model-menu-triggerLabel",
2557
+ children: currentLabel
2558
+ }),
2559
+ !follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2560
+ className: "dsh-model-menu-triggerEffort",
2561
+ children: effortName ?? text.effortDefault
2562
+ }) : null,
2563
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
2564
+ ]
2565
+ }),
2566
+ choices,
2567
+ selected: follow ? void 0 : {
2568
+ provider: current.provider,
2569
+ model: current.model
2570
+ },
2571
+ onPick: (picked) => save({
2572
+ provider: picked.provider,
2573
+ model: picked.model
2574
+ }),
2575
+ leading: [{
2576
+ id: "default",
2577
+ label: text.modelDefault,
2578
+ selected: follow
2579
+ }],
2580
+ onPickLeading: () => save(null),
2581
+ efforts,
2582
+ selectedEffort: current.reasoningEffort || "",
2583
+ onPickEffort: (effort) => save(follow ? null : {
2584
+ provider: current.provider,
2585
+ model: current.model,
2586
+ ...effort ? { reasoningEffort: effort } : {}
2587
+ }),
2588
+ modelLabel: text.model,
2589
+ modelValue: currentLabel,
2590
+ defaultEffortLabel: text.effortDefault
2591
+ })
2159
2592
  }),
2160
2593
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2161
2594
  className: "dsh-ui-hint",
@@ -2257,9 +2690,10 @@ function acquireAuxiliaryModelCard(root, ctx) {
2257
2690
  let unprovide;
2258
2691
  const watchLease = acquireWatchCoordinator(root).mount();
2259
2692
  const subscriber = createModelSettingsSubscriber(ctx.root, AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY, (ns) => ns === AUXILIARY_MODEL_NS);
2260
- const render = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuxiliaryModelCard, {
2693
+ const render = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuxiliaryModelCard, {
2261
2694
  client: root,
2262
- subscribe: subscriber.subscribe
2695
+ subscribe: subscriber.subscribe,
2696
+ renderFactorySlot: props.renderFactorySlot
2263
2697
  });
2264
2698
  const service = {
2265
2699
  retain() {
@@ -2721,6 +3155,7 @@ function TitleSettings(props) {
2721
3155
  const currentLabel = follow ? text.modelDefault : selectedModel?.label ?? `${savedModel.provider} / ${savedModel.model}`;
2722
3156
  const selectedEffort = efforts.find((item) => item.id === (savedModel.reasoningEffort || ""));
2723
3157
  const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, seat.locale) : void 0;
3158
+ const anchor = useModelMenuAnchor(`${text.model}: ${currentLabel}`, void 0, () => setMenuOpen(false));
2724
3159
  const title = status?.session?.title;
2725
3160
  const pinned = status?.session?.pinned === true;
2726
3161
  const weOwn = status?.support?.weOwn === true;
@@ -2932,28 +3367,12 @@ function TitleSettings(props) {
2932
3367
  state: catalogState,
2933
3368
  locale: seat.locale
2934
3369
  }),
2935
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
3370
+ renderModelPicker(props.renderFactorySlot, {
2936
3371
  locale: seat.locale,
2937
- open: menuOpen && Boolean(status) && !busy && !loadingSettings,
3372
+ disabled: busy || loadingSettings || !status,
3373
+ busy: busy || loadingSettings,
2938
3374
  onOpenChange: (next) => setMenuOpen(next && Boolean(status) && !busy && !loadingSettings),
2939
3375
  listClassName: "ct-root",
2940
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2941
- type: "button",
2942
- className: "dsh-model-menu-trigger",
2943
- disabled: busy || loadingSettings || !status,
2944
- "aria-label": `${text.model}: ${currentLabel}`,
2945
- children: [
2946
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2947
- className: "dsh-model-menu-triggerLabel",
2948
- children: currentLabel
2949
- }),
2950
- !follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2951
- className: "dsh-model-menu-triggerEffort",
2952
- children: effortName ?? text.modelDefault
2953
- }) : null,
2954
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen ? " dsh-model-menu-chevronOpen" : ""}` })
2955
- ]
2956
- }),
2957
3376
  choices,
2958
3377
  selected: follow ? void 0 : {
2959
3378
  provider: savedModel.provider,
@@ -2982,9 +3401,67 @@ function TitleSettings(props) {
2982
3401
  model: savedModel.model,
2983
3402
  ...effort ? { reasoningEffort: effort } : {}
2984
3403
  } }),
3404
+ triggerLabel: currentLabel,
3405
+ triggerEffortLabel: !follow && efforts.length ? effortName ?? text.modelDefault : void 0,
2985
3406
  modelLabel: text.model,
2986
3407
  modelValue: currentLabel,
2987
3408
  defaultEffortLabel: text.modelDefault
3409
+ }, {
3410
+ anchor,
3411
+ fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
3412
+ locale: seat.locale,
3413
+ open: menuOpen && Boolean(status) && !busy && !loadingSettings,
3414
+ onOpenChange: (next) => setMenuOpen(next && Boolean(status) && !busy && !loadingSettings),
3415
+ listClassName: "ct-root",
3416
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3417
+ type: "button",
3418
+ className: "dsh-model-menu-trigger",
3419
+ disabled: busy || loadingSettings || !status,
3420
+ "aria-label": `${text.model}: ${currentLabel}`,
3421
+ children: [
3422
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3423
+ className: "dsh-model-menu-triggerLabel",
3424
+ children: currentLabel
3425
+ }),
3426
+ !follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3427
+ className: "dsh-model-menu-triggerEffort",
3428
+ children: effortName ?? text.modelDefault
3429
+ }) : null,
3430
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen ? " dsh-model-menu-chevronOpen" : ""}` })
3431
+ ]
3432
+ }),
3433
+ choices,
3434
+ selected: follow ? void 0 : {
3435
+ provider: savedModel.provider,
3436
+ model: savedModel.model
3437
+ },
3438
+ onPick: (picked) => saveSettings({ model: {
3439
+ provider: picked.provider,
3440
+ model: picked.model
3441
+ } }),
3442
+ leading: [{
3443
+ id: "default",
3444
+ label: text.modelDefault,
3445
+ selected: follow
3446
+ }],
3447
+ onPickLeading: () => saveSettings({ model: {
3448
+ provider: "",
3449
+ model: ""
3450
+ } }),
3451
+ efforts,
3452
+ selectedEffort: savedModel.reasoningEffort || "",
3453
+ onPickEffort: (effort) => saveSettings({ model: follow ? {
3454
+ provider: "",
3455
+ model: ""
3456
+ } : {
3457
+ provider: savedModel.provider,
3458
+ model: savedModel.model,
3459
+ ...effort ? { reasoningEffort: effort } : {}
3460
+ } }),
3461
+ modelLabel: text.model,
3462
+ modelValue: currentLabel,
3463
+ defaultEffortLabel: text.modelDefault
3464
+ })
2988
3465
  }),
2989
3466
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2990
3467
  className: "dsh-ui-meta",
@@ -3052,7 +3529,8 @@ function TitleSettingsItem(props) {
3052
3529
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TitleSettings, {
3053
3530
  client: props.client,
3054
3531
  marker: props.marker,
3055
- subscribeSettings: props.subscribeSettings
3532
+ subscribeSettings: props.subscribeSettings,
3533
+ renderFactorySlot: props.renderFactorySlot
3056
3534
  })
3057
3535
  }) : null]
3058
3536
  });
@@ -3090,10 +3568,11 @@ function apply(ctx) {
3090
3568
  ctx.effect(() => registerSessionSettingsItem(ctx, {
3091
3569
  id: "current-title",
3092
3570
  order: 70
3093
- }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TitleSettingsItem, {
3571
+ }, (slotProps) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TitleSettingsItem, {
3094
3572
  client,
3095
3573
  marker,
3096
- subscribeSettings
3574
+ subscribeSettings,
3575
+ renderFactorySlot: slotProps?.renderFactorySlot
3097
3576
  })), "current-title.settings");
3098
3577
  ctx.effect(() => registerAuxiliaryModelSettings(ctx), "current-title.auxiliary-model");
3099
3578
  ctx.effect(() => {