@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.
@@ -96,6 +96,7 @@ function parseModelMenuChoices(value, bound) {
96
96
  provider,
97
97
  model,
98
98
  label: `${providerName} / ${name}`,
99
+ providerLabel: providerName,
99
100
  efforts
100
101
  });
101
102
  }
@@ -142,11 +143,11 @@ function modelMenuShortName(choice) {
142
143
  /** Group catalog choices by provider and filter them by a case-insensitive query. */
143
144
  function groupModelMenuChoices(choices, query) {
144
145
  const needle = query.trim().toLowerCase();
145
- const matched = needle ? choices.filter((choice) => choice.label.toLowerCase().includes(needle) || choice.model.toLowerCase().includes(needle) || choice.provider.toLowerCase().includes(needle)) : choices;
146
+ 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;
146
147
  const groups = [];
147
148
  for (const choice of matched) {
148
149
  const sep = choice.label.indexOf(" / ");
149
- const name = sep > 0 ? choice.label.slice(0, sep) : choice.provider;
150
+ const name = choice.providerLabel ?? (sep > 0 ? choice.label.slice(0, sep) : choice.provider);
150
151
  const last = groups.at(-1);
151
152
  if (last && last.provider === choice.provider) last.items.push(choice);
152
153
  else groups.push({
@@ -519,7 +520,9 @@ Object.values({
519
520
  "--dsh-scrollbar-thumb-hover",
520
521
  "--dsh-content-font-size-secondary",
521
522
  "--dsh-content-font-delta",
522
- "--dsh-frame-top-clearance"
523
+ "--dsh-frame-top-clearance",
524
+ "--dsh-composer-model-icon-display",
525
+ "--dsh-composer-model-text-display"
523
526
  ]
524
527
  }).flat();
525
528
  /**
@@ -1223,7 +1226,7 @@ function ModelMenu(props) {
1223
1226
  empty: "No matching models",
1224
1227
  clear: "Clear search"
1225
1228
  };
1226
- 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;
1229
+ 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;
1227
1230
  const rootRef = (0, react.useRef)(null);
1228
1231
  const openRef = (0, react.useRef)(open);
1229
1232
  openRef.current = open;
@@ -1238,9 +1241,10 @@ function ModelMenu(props) {
1238
1241
  const [pane, setPane] = (0, react.useState)("root");
1239
1242
  const [query, setQuery] = (0, react.useState)("");
1240
1243
  const [highlightedIndex, setHighlightedIndex] = (0, react.useState)(null);
1244
+ const [keyNav, setKeyNav] = (0, react.useState)(0);
1241
1245
  const [menuPos, setMenuPos] = (0, react.useState)(null);
1242
1246
  const id = (0, react.useId)();
1243
- const showSearch = choices.length > 4;
1247
+ const showSearch = alwaysShowSearch || choices.length > 4;
1244
1248
  const effortRows = (0, react.useMemo)(() => [{
1245
1249
  id: "",
1246
1250
  name: defaultEffortLabel
@@ -1257,7 +1261,12 @@ function ModelMenu(props) {
1257
1261
  items: (0, _deepseek_ai_dsh_client_ui_primitives.rankByName)(group.items.map((choice) => ({
1258
1262
  choice,
1259
1263
  name: modelMenuShortName(choice),
1260
- label: choice.model
1264
+ label: [
1265
+ choice.provider,
1266
+ choice.providerLabel,
1267
+ choice.model,
1268
+ choice.label
1269
+ ].filter(Boolean).join(" ")
1261
1270
  })), showSearch ? query.trim() : "")
1262
1271
  })).filter((group) => group.items.length > 0), [
1263
1272
  choices,
@@ -1289,10 +1298,12 @@ function ModelMenu(props) {
1289
1298
  paneFocus.current = "drill";
1290
1299
  setPane(next);
1291
1300
  };
1292
- /** Leave a drilled pane for the root one, handing the keyboard back to its cell. */
1301
+ /** Leave a drilled pane for the entry pane, handing the keyboard back to it.
1302
+ * A root entry refocuses the cell that opened the pane; a search-first
1303
+ * (model) entry refocuses that pane's own intent. */
1293
1304
  const back = (from) => {
1294
- paneFocus.current = from;
1295
- setPane("root");
1305
+ paneFocus.current = initialPane === "root" ? from : "drill";
1306
+ setPane(initialPane);
1296
1307
  };
1297
1308
  const moveFocus = (offset) => {
1298
1309
  const items = itemRefs.current.filter((item) => item !== null && !item.disabled);
@@ -1332,10 +1343,18 @@ function ModelMenu(props) {
1332
1343
  }, [requestedOpen, open]);
1333
1344
  (0, react.useEffect)(() => {
1334
1345
  if (!open) return;
1335
- setPane("root");
1346
+ setPane(initialPane);
1347
+ if (initialPane === "model") paneFocus.current = "drill";
1336
1348
  setQuery("");
1337
1349
  setHighlightedIndex(null);
1338
- }, [open]);
1350
+ }, [open, initialPane]);
1351
+ (0, react.useEffect)(() => {
1352
+ if (disabled && open && closeOnDisable) changeOpen(false);
1353
+ }, [
1354
+ disabled,
1355
+ open,
1356
+ closeOnDisable
1357
+ ]);
1339
1358
  (0, react.useEffect)(() => {
1340
1359
  if (!open) return;
1341
1360
  const closeOutside = (event) => {
@@ -1343,9 +1362,25 @@ function ModelMenu(props) {
1343
1362
  if (rootRef.current?.contains(target) || menuRef.current?.contains(target)) return;
1344
1363
  changeOpen(false);
1345
1364
  };
1365
+ const escapeOutside = (event) => {
1366
+ if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
1367
+ if (!openRef.current) return;
1368
+ event.preventDefault();
1369
+ if (pane !== initialPane) back(pane);
1370
+ else close(true);
1371
+ };
1346
1372
  document.addEventListener("mousedown", closeOutside);
1347
- return () => document.removeEventListener("mousedown", closeOutside);
1348
- }, [open, onOpenChange]);
1373
+ document.addEventListener("keydown", escapeOutside);
1374
+ return () => {
1375
+ document.removeEventListener("mousedown", closeOutside);
1376
+ document.removeEventListener("keydown", escapeOutside);
1377
+ };
1378
+ }, [
1379
+ open,
1380
+ onOpenChange,
1381
+ pane,
1382
+ initialPane
1383
+ ]);
1349
1384
  (0, react.useEffect)(() => {
1350
1385
  const intent = paneFocus.current;
1351
1386
  if (!open || menuPos === null || intent === null) return;
@@ -1375,13 +1410,21 @@ function ModelMenu(props) {
1375
1410
  pane,
1376
1411
  filteredGroups
1377
1412
  ]);
1413
+ const modelPaneOpen = open && pane === "model";
1414
+ const scrollSignal = !modelPaneOpen ? "closed" : keyNav === 0 ? "open" : `key:${keyNav}`;
1415
+ const scrollSignalRef = (0, react.useRef)(null);
1378
1416
  (0, react.useLayoutEffect)(() => {
1379
- if (open && pane === "model" && activeModelIndex >= 0) itemRefs.current[leading.length + activeModelIndex]?.scrollIntoView({ block: "nearest" });
1417
+ if (!modelPaneOpen || activeModelIndex < 0) {
1418
+ if (!modelPaneOpen) scrollSignalRef.current = null;
1419
+ return;
1420
+ }
1421
+ if (scrollSignalRef.current === scrollSignal) return;
1422
+ scrollSignalRef.current = scrollSignal;
1423
+ itemRefs.current[leading.length + activeModelIndex]?.scrollIntoView({ block: "nearest" });
1380
1424
  }, [
1381
- open,
1382
- pane,
1425
+ modelPaneOpen,
1426
+ scrollSignal,
1383
1427
  activeModelIndex,
1384
- visibleModels,
1385
1428
  leading.length
1386
1429
  ]);
1387
1430
  (0, react.useLayoutEffect)(() => {
@@ -1396,7 +1439,7 @@ function ModelMenu(props) {
1396
1439
  let x = rect.right - lw, y = rect.top - 8 - lh;
1397
1440
  if (lw > 0) x = Math.min(Math.max(x, margin), window.innerWidth - lw - margin);
1398
1441
  if (lh > 0) y = Math.min(Math.max(y, margin), window.innerHeight - lh - margin);
1399
- setMenuPos({
1442
+ setMenuPos((prev) => prev && prev.left === x && prev.top === y ? prev : {
1400
1443
  left: x,
1401
1444
  top: y
1402
1445
  });
@@ -1416,6 +1459,13 @@ function ModelMenu(props) {
1416
1459
  ]);
1417
1460
  function onKeyDown(event) {
1418
1461
  if (event.defaultPrevented || event.nativeEvent.isComposing) return;
1462
+ if (event.key === "Escape" && open) {
1463
+ event.preventDefault();
1464
+ if (pane !== initialPane) back(pane);
1465
+ else close(true);
1466
+ return;
1467
+ }
1468
+ if (disabled) return;
1419
1469
  if (!open && [
1420
1470
  "Enter",
1421
1471
  " ",
@@ -1429,17 +1479,12 @@ function ModelMenu(props) {
1429
1479
  requestOpen();
1430
1480
  return;
1431
1481
  }
1432
- if (event.key === "Escape" && open) {
1433
- event.preventDefault();
1434
- if (pane !== "root") back(pane);
1435
- else close(true);
1436
- return;
1437
- }
1438
1482
  if (!open) return;
1439
1483
  if (pane === "model" && showSearch && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
1440
1484
  event.preventDefault();
1441
1485
  if (visibleModels.length > 0) {
1442
1486
  setHighlightedIndex((activeModelIndex + (event.key === "ArrowDown" ? 1 : -1) + visibleModels.length) % visibleModels.length);
1487
+ setKeyNav((n) => n + 1);
1443
1488
  searchRef.current?.focus();
1444
1489
  }
1445
1490
  return;
@@ -1448,19 +1493,19 @@ function ModelMenu(props) {
1448
1493
  if (event.key === "Tab" && visibleModels.length === 0) return;
1449
1494
  event.preventDefault();
1450
1495
  const highlighted = visibleModels[activeModelIndex];
1451
- if (highlighted) {
1496
+ if (highlighted && !busy) {
1452
1497
  onPick({
1453
1498
  provider: highlighted.provider,
1454
1499
  model: highlighted.model
1455
1500
  });
1456
- close(true);
1501
+ if (closeOnPick) close(true);
1457
1502
  }
1458
1503
  return;
1459
1504
  }
1460
1505
  if (event.key === "Tab") {
1461
1506
  if (event.shiftKey) {
1462
1507
  event.preventDefault();
1463
- if (pane !== "root") back(pane);
1508
+ if (pane !== initialPane) back(pane);
1464
1509
  else close(true);
1465
1510
  return;
1466
1511
  }
@@ -1484,7 +1529,10 @@ function ModelMenu(props) {
1484
1529
  }
1485
1530
  }
1486
1531
  function onBlur(event) {
1487
- if (event.relatedTarget instanceof Node && (rootRef.current?.contains(event.relatedTarget) || menuRef.current?.contains(event.relatedTarget))) return;
1532
+ const related = event.relatedTarget;
1533
+ if (related instanceof Node && (rootRef.current?.contains(related) || menuRef.current?.contains(related) || !related.isConnected)) return;
1534
+ if (!closeOnPick && !(related instanceof Node)) return;
1535
+ if (busy && related == null) return;
1488
1536
  if (open) changeOpen(false);
1489
1537
  }
1490
1538
  itemRefs.current = [];
@@ -1505,11 +1553,16 @@ function ModelMenu(props) {
1505
1553
  "aria-label": modelLabel,
1506
1554
  lang: language,
1507
1555
  children: [
1556
+ menuHeader ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1557
+ className: "dsh-model-menu-header",
1558
+ children: menuHeader
1559
+ }) : null,
1508
1560
  pane === "root" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1509
1561
  ref: itemRef(),
1510
1562
  type: "button",
1511
1563
  role: "menuitem",
1512
1564
  className: "dsh-model-menu-cell",
1565
+ disabled,
1513
1566
  onClick: () => drill("model"),
1514
1567
  children: [
1515
1568
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1522,11 +1575,12 @@ function ModelMenu(props) {
1522
1575
  }),
1523
1576
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { className: "dsh-model-menu-cellChevron" })
1524
1577
  ]
1525
- }), efforts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1578
+ }), efforts.length > 0 && !reasoningSlider ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1526
1579
  ref: itemRef(),
1527
1580
  type: "button",
1528
1581
  role: "menuitem",
1529
1582
  className: "dsh-model-menu-cell",
1583
+ disabled,
1530
1584
  onClick: () => drill("effort"),
1531
1585
  children: [
1532
1586
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1545,6 +1599,7 @@ function ModelMenu(props) {
1545
1599
  className: "dsh-model-menu-searchRow",
1546
1600
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
1547
1601
  ref: searchRef,
1602
+ disabled,
1548
1603
  className: `dsh-model-menu-search${query !== "" ? " dsh-model-menu-searchWithQuery" : ""}`,
1549
1604
  type: "text",
1550
1605
  role: "searchbox",
@@ -1561,6 +1616,7 @@ function ModelMenu(props) {
1561
1616
  type: "button",
1562
1617
  className: "dsh-model-menu-searchClear",
1563
1618
  "aria-label": clearSearchLabel,
1619
+ disabled,
1564
1620
  onClick: () => {
1565
1621
  setQuery("");
1566
1622
  setHighlightedIndex(null);
@@ -1583,10 +1639,12 @@ function ModelMenu(props) {
1583
1639
  type: "button",
1584
1640
  role: "menuitemradio",
1585
1641
  "aria-checked": item.selected === true,
1642
+ disabled: disabled || busy,
1586
1643
  className: `dsh-model-menu-option${item.selected === true ? " dsh-model-menu-selected" : ""}`,
1587
1644
  onClick: () => {
1645
+ if (disabled || busy) return;
1588
1646
  onPickLeading?.(item.id);
1589
- close(true);
1647
+ if (closeOnPick) close(true);
1590
1648
  },
1591
1649
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1592
1650
  className: "dsh-model-menu-optionCopy",
@@ -1612,6 +1670,7 @@ function ModelMenu(props) {
1612
1670
  "aria-checked": checked,
1613
1671
  id: `${id}-model-${index}`,
1614
1672
  tabIndex: showSearch ? -1 : 0,
1673
+ disabled: disabled || busy,
1615
1674
  onFocus: () => setHighlightedIndex(index),
1616
1675
  "data-highlighted": index === activeModelIndex ? "" : void 0,
1617
1676
  className: `dsh-model-menu-option dsh-model-menu-modelOption${checked ? " dsh-model-menu-selected" : ""}${index === activeModelIndex ? " dsh-model-menu-optionActive" : ""}`,
@@ -1621,11 +1680,12 @@ function ModelMenu(props) {
1621
1680
  },
1622
1681
  title: choice.label,
1623
1682
  onClick: () => {
1683
+ if (disabled || busy) return;
1624
1684
  onPick({
1625
1685
  provider: choice.provider,
1626
1686
  model: choice.model
1627
1687
  });
1628
- close(true);
1688
+ if (closeOnPick) close(true);
1629
1689
  },
1630
1690
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1631
1691
  className: "dsh-model-menu-optionCopy",
@@ -1641,6 +1701,14 @@ function ModelMenu(props) {
1641
1701
  })
1642
1702
  }, group.provider))]
1643
1703
  }),
1704
+ reasoningSlider && efforts.length > 0 && filteredGroups.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EffortSlider, {
1705
+ label: effortLabel,
1706
+ rows: effortRows,
1707
+ selected: selectedEffort ?? "",
1708
+ disabled,
1709
+ busy,
1710
+ onPick: (value) => onPickEffort?.(value)
1711
+ }) : null,
1644
1712
  filteredGroups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1645
1713
  className: "dsh-model-menu-empty",
1646
1714
  role: "status",
@@ -1654,10 +1722,12 @@ function ModelMenu(props) {
1654
1722
  type: "button",
1655
1723
  role: "menuitemradio",
1656
1724
  "aria-checked": checked,
1725
+ disabled: disabled || busy,
1657
1726
  className: `dsh-model-menu-option${checked ? " dsh-model-menu-selected" : ""}`,
1658
1727
  onClick: () => {
1728
+ if (disabled || busy) return;
1659
1729
  onPickEffort?.(row.id || void 0);
1660
- close(true);
1730
+ if (closeOnPick) close(true);
1661
1731
  },
1662
1732
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1663
1733
  className: "dsh-model-menu-optionCopy",
@@ -1676,7 +1746,8 @@ function ModelMenu(props) {
1676
1746
  const trigger = (0, react.isValidElement)(anchor) ? (0, react.cloneElement)(anchor, {
1677
1747
  "aria-haspopup": "menu",
1678
1748
  "aria-expanded": open,
1679
- "aria-controls": open ? `${id}-menu` : void 0
1749
+ "aria-controls": open ? `${id}-menu` : void 0,
1750
+ "aria-disabled": disabled || void 0
1680
1751
  }) : anchor;
1681
1752
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1682
1753
  ref: rootRef,
@@ -1684,7 +1755,7 @@ function ModelMenu(props) {
1684
1755
  onKeyDown,
1685
1756
  onBlur,
1686
1757
  onClick: (event) => {
1687
- if (event.defaultPrevented) return;
1758
+ if (disabled || event.defaultPrevented) return;
1688
1759
  const button = event.target instanceof Element ? event.target.closest("button") : null;
1689
1760
  if (!(button instanceof HTMLButtonElement) || button.disabled || !rootRef.current?.contains(button)) return;
1690
1761
  requestOpen();
@@ -1696,6 +1767,102 @@ function ModelMenu(props) {
1696
1767
  });
1697
1768
  }
1698
1769
  /**
1770
+ * Discrete reasoning-effort slider shown under the model list in enhanced mode.
1771
+ *
1772
+ * A native range input (`step=1`) over exactly the rows the caller supplies —
1773
+ * the default row plus each effort the selected model advertises — so it can
1774
+ * never invent a level. It previews locally while dragging or arrowing and
1775
+ * commits only when the gesture ends (pointer up, key up, or blur), so a drag
1776
+ * does not write on every step. The menu stays open, so adjusting the effort
1777
+ * never loses the model selection or the search query.
1778
+ *
1779
+ * The input itself stays the entire interaction and accessibility surface —
1780
+ * keyboard, ARIA, disabled and the native step snapping all come from it —
1781
+ * while the track, gradient fill and thumb below are decorative layers painted
1782
+ * from the `--_p` percentage the preview position sets on the rail. Value
1783
+ * changes only update that custom property, so the CSS transitions animate
1784
+ * between levels without remounting nodes or restarting an animation.
1785
+ */
1786
+ function EffortSlider(props) {
1787
+ const { label, rows, selected, disabled = false, busy = false, onPick } = props;
1788
+ const active = Math.max(0, rows.findIndex((row) => row.id === selected));
1789
+ const [position, setPosition] = (0, react.useState)(active);
1790
+ const positionRef = (0, react.useRef)(position);
1791
+ (0, react.useEffect)(() => {
1792
+ positionRef.current = active;
1793
+ setPosition(active);
1794
+ }, [active]);
1795
+ const preview = (next) => {
1796
+ positionRef.current = next;
1797
+ setPosition(next);
1798
+ };
1799
+ const commit = () => {
1800
+ if (disabled || busy) return;
1801
+ const row = rows[positionRef.current];
1802
+ if (row !== void 0) onPick(row.id === "" ? void 0 : row.id);
1803
+ };
1804
+ const max = Math.max(0, rows.length - 1);
1805
+ const percent = max > 0 ? Math.round(position / max * 100) : 0;
1806
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1807
+ className: "dsh-model-menu-sliderGroup",
1808
+ role: "group",
1809
+ "aria-label": label,
1810
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1811
+ className: "dsh-model-menu-sliderHead",
1812
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1813
+ className: "dsh-model-menu-sliderTitle",
1814
+ children: label
1815
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1816
+ className: "dsh-model-menu-sliderValue",
1817
+ children: rows[position]?.name
1818
+ })]
1819
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1820
+ className: "dsh-model-menu-sliderRail",
1821
+ style: { "--_p": percent },
1822
+ children: [
1823
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1824
+ type: "range",
1825
+ className: "dsh-model-menu-sliderInput",
1826
+ min: 0,
1827
+ max,
1828
+ step: 1,
1829
+ value: position,
1830
+ disabled,
1831
+ "aria-label": label,
1832
+ "aria-disabled": disabled || busy || void 0,
1833
+ "aria-valuetext": rows[position]?.name,
1834
+ onChange: (event) => {
1835
+ const value = Number(event.target.value);
1836
+ if (Number.isFinite(value)) preview(value);
1837
+ },
1838
+ onPointerUp: commit,
1839
+ onKeyUp: commit,
1840
+ onBlur: commit
1841
+ }),
1842
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1843
+ className: "dsh-model-menu-sliderTrack",
1844
+ "aria-hidden": "true",
1845
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1846
+ className: "dsh-model-menu-sliderFill",
1847
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsh-model-menu-sliderFillBar" })
1848
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1849
+ className: "dsh-model-menu-sliderNodes",
1850
+ children: rows.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1851
+ "data-covered": index <= position ? "" : void 0,
1852
+ className: "dsh-model-menu-sliderNode",
1853
+ style: { "--_n": max > 0 ? Math.round(index / max * 100) : 0 }
1854
+ }, row.id || "__default"))
1855
+ })]
1856
+ }),
1857
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1858
+ className: "dsh-model-menu-sliderThumb",
1859
+ "aria-hidden": "true"
1860
+ })
1861
+ ]
1862
+ })]
1863
+ });
1864
+ }
1865
+ /**
1699
1866
  * ModelMenu styles, transcribed from the official ModelSelect.module.css (the
1700
1867
  * published dsh-client-ui-model-selection bundle): card capped at
1701
1868
  * min(360px, 100vh - 96px), 34px option cells at 13/18 with 5px 7px padding,
@@ -1754,6 +1921,7 @@ const modelMenuCss = `
1754
1921
  overflow: hidden;
1755
1922
  }
1756
1923
  .dsh-model-menu-status, .dsh-model-menu-empty { color: var(--dsw-alias-label-tertiary); padding: 8px; font-size: 12px; line-height: 18px; }
1924
+ .dsh-model-menu-header { flex: none; padding: 2px 2px 4px; }
1757
1925
  .dsh-model-menu-searchRow { flex-shrink: 0; margin: 2px 0 3px; position: relative; }
1758
1926
  .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; }
1759
1927
  .dsh-model-menu-searchRow .dsh-model-menu-search:focus-within { border-color: transparent; }
@@ -1832,8 +2000,236 @@ const modelMenuCss = `
1832
2000
  .dsh-model-menu-cellLabel { white-space: nowrap; flex: none; }
1833
2001
  .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; }
1834
2002
  .dsh-model-menu-cellChevron { width: 12px; height: 12px; color: var(--dsw-alias-menu-icon); flex: none; }
2003
+ /* Discrete reasoning-effort slider under the model list (enhanced mode).
2004
+ * The painted control is layered over a fully functional native range input:
2005
+ * the input stays on top, transparent, and owns pointer/keyboard/ARIA, while
2006
+ * the decorative track, fill and thumb render beneath it at pointer-events:
2007
+ * none. Position lives in one custom property (--_p, 0–100) on the rail, so
2008
+ * the only animated work a level change triggers is a clip-path/left
2009
+ * transition — no keyframes, no element churn. */
2010
+ .dsh-model-menu-sliderGroup { border-top: 0.5px solid var(--dsw-alias-border-l2); flex: none; margin-top: 4px; padding: 6px 8px 8px; }
2011
+ .dsh-model-menu-sliderHead { align-items: baseline; justify-content: space-between; gap: 8px; margin: 0 2px 4px; display: flex; }
2012
+ .dsh-model-menu-sliderTitle { color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
2013
+ .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); }
2014
+ .dsh-model-menu-sliderRail { position: relative; direction: ltr; height: 38px; margin-top: 2px; }
2015
+ .dsh-model-menu-sliderRail:has(:disabled) { opacity: 0.45; }
2016
+ .dsh-model-menu-sliderInput {
2017
+ appearance: none;
2018
+ -webkit-appearance: none;
2019
+ box-sizing: border-box;
2020
+ border: 0;
2021
+ cursor: pointer;
2022
+ direction: ltr;
2023
+ opacity: 0;
2024
+ margin: 0;
2025
+ padding: 0;
2026
+ width: 100%;
2027
+ height: 100%;
2028
+ position: absolute;
2029
+ inset: 0;
2030
+ z-index: 1;
2031
+ touch-action: pan-y;
2032
+ }
2033
+ .dsh-model-menu-sliderInput:disabled { cursor: default; }
2034
+ /* The transparent input must keep the painted thumb's geometry: an
2035
+ * appearance:none webkit thumb defaults to 16px, which would shift the
2036
+ * end-point positions 7px off the decorative 30px disc and misplace the
2037
+ * native drag hit target. Track gets the full rail height so the whole band
2038
+ * maps to values identically. */
2039
+ .dsh-model-menu-sliderInput::-webkit-slider-runnable-track { width: 100%; height: 100%; }
2040
+ .dsh-model-menu-sliderInput::-webkit-slider-thumb { appearance: none; -webkit-appearance: none; width: 30px; height: 30px; margin: 0; }
2041
+ .dsh-model-menu-sliderInput::-moz-range-track { width: 100%; height: 100%; }
2042
+ .dsh-model-menu-sliderInput::-moz-range-thumb { width: 30px; height: 30px; margin: 0; border: 0; }
2043
+ .dsh-model-menu-sliderTrack {
2044
+ pointer-events: none;
2045
+ border-radius: 999px;
2046
+ width: 100%;
2047
+ height: 26px;
2048
+ background: var(--dsw-alias-interactive-bg-hover);
2049
+ box-shadow: inset 0 0 0 0.5px var(--dsw-alias-border-l2);
2050
+ position: absolute;
2051
+ inset-inline: 0;
2052
+ top: 50%;
2053
+ transform: translateY(-50%);
2054
+ /* No overflow:hidden — it would clip the fill's drop-shadow glow; the bar's
2055
+ * own clip-path already supplies the rounded reveal shape. */
2056
+ }
2057
+ /* The accent ramp paints the full track width once and is revealed by an
2058
+ * animated clip-path, so raising the effort unwraps more of the spectrum
2059
+ * instead of squashing the same gradient into a narrower bar. The glow lives
2060
+ * on the un-clipped parent: a filter on the clipped element itself would be
2061
+ * clipped away with it. */
2062
+ .dsh-model-menu-sliderFill {
2063
+ display: block;
2064
+ height: 100%;
2065
+ filter: drop-shadow(0 0 5px var(--dsw-alias-state-business-primary));
2066
+ transition: filter var(--ds-transition-duration) var(--ds-ease-in-out);
2067
+ }
2068
+ .dsh-model-menu-sliderInput:hover:not(:disabled) ~ .dsh-model-menu-sliderTrack .dsh-model-menu-sliderFill,
2069
+ .dsh-model-menu-sliderInput:active:not(:disabled) ~ .dsh-model-menu-sliderTrack .dsh-model-menu-sliderFill {
2070
+ filter: drop-shadow(0 0 9px var(--dsw-alias-state-business-primary));
2071
+ }
2072
+ /* The fill stops at the thumb's center, not at the track edge — at a 30px
2073
+ * thumb the end stops sit 15px in from each side, matching the node dots. */
2074
+ .dsh-model-menu-sliderFillBar {
2075
+ display: block;
2076
+ height: 100%;
2077
+ background: linear-gradient(90deg,
2078
+ var(--dsw-alias-state-business-primary),
2079
+ var(--dsw-alias-state-success-primary),
2080
+ var(--dsw-alias-state-warn-primary),
2081
+ var(--dsw-alias-state-error-primary));
2082
+ clip-path: inset(0 calc(100% - 15px - (100% - 30px) * var(--_p, 0) / 100) 0 0 round 999px);
2083
+ transition: clip-path var(--ds-transition-duration) var(--ds-ease-in-out);
2084
+ }
2085
+ /* Stop dots: one per declared row (default + every advertised effort), evenly
2086
+ * spread across the band between the two thumb-center extremes (inset 15px,
2087
+ * i.e. half the 30px thumb). --_n is 0-100 along that band. A covered dot
2088
+ * reads as a pale pin through the gradient; an uncovered one stays a neutral
2089
+ * notch on the empty track. */
2090
+ .dsh-model-menu-sliderNodes {
2091
+ pointer-events: none;
2092
+ display: block;
2093
+ position: absolute;
2094
+ inset-inline: 15px;
2095
+ inset-block: 0;
2096
+ }
2097
+ .dsh-model-menu-sliderNode {
2098
+ pointer-events: none;
2099
+ box-sizing: border-box;
2100
+ width: 4px;
2101
+ height: 4px;
2102
+ border-radius: 50%;
2103
+ background: color-mix(in srgb, var(--dsw-alias-label-secondary) 35%, transparent);
2104
+ position: absolute;
2105
+ left: calc(var(--_n, 0) * 1%);
2106
+ top: 50%;
2107
+ transform: translate(-50%, -50%);
2108
+ transition: background-color var(--ds-transition-duration) var(--ds-ease-in-out),
2109
+ transform var(--ds-transition-duration) var(--ds-ease-in-out);
2110
+ }
2111
+ .dsh-model-menu-sliderNode[data-covered] { background: color-mix(in srgb, var(--dsw-alias-label-primary) 60%, transparent); }
2112
+ .dsh-model-menu-sliderThumb {
2113
+ pointer-events: none;
2114
+ box-sizing: border-box;
2115
+ border-radius: 50%;
2116
+ width: 30px;
2117
+ height: 30px;
2118
+ background: var(--dsw-alias-button-primary-fill);
2119
+ border: 0.5px solid var(--dsw-alias-border-l4);
2120
+ box-shadow: 0 0 0 3px var(--dsw-alias-interactive-bg-hover), var(--dsw-elevation-soft);
2121
+ position: absolute;
2122
+ left: calc((100% - 30px) * var(--_p, 0) / 100);
2123
+ top: 50%;
2124
+ transform: translateY(-50%) scale(0.92);
2125
+ transition: left var(--ds-transition-duration) var(--ds-ease-in-out),
2126
+ transform var(--ds-transition-duration) var(--ds-ease-in-out),
2127
+ box-shadow var(--ds-transition-duration) var(--ds-ease-in-out);
2128
+ }
2129
+ .dsh-model-menu-sliderInput:hover:not(:disabled) ~ .dsh-model-menu-sliderThumb { transform: translateY(-50%) scale(1); }
2130
+ .dsh-model-menu-sliderInput:active:not(:disabled) ~ .dsh-model-menu-sliderThumb {
2131
+ transform: translateY(-50%) scale(1.1);
2132
+ box-shadow: 0 0 0 4px var(--dsw-alias-interactive-bg-hover), 0 0 12px var(--dsw-alias-state-business-primary);
2133
+ }
2134
+ .dsh-model-menu-sliderInput:focus-visible ~ .dsh-model-menu-sliderThumb {
2135
+ box-shadow: 0 0 0 3px var(--dsw-alias-interactive-bg-hover),
2136
+ 0 0 0 calc(3px + var(--dsw-focus-ring-width)) var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
2137
+ }
2138
+ @media (prefers-reduced-motion: reduce) {
2139
+ .dsh-model-menu-sliderFill, .dsh-model-menu-sliderFillBar, .dsh-model-menu-sliderThumb,
2140
+ .dsh-model-menu-sliderNode, .dsh-model-menu-sliderValue { transition: none; }
2141
+ }
1835
2142
  `;
1836
2143
  //#endregion
2144
+ //#region ../dsh-plugin-kit/src/model-picker.tsx
2145
+ /**
2146
+ * Optional shared model picker (`klarkxy.model-picker` Factory).
2147
+ *
2148
+ * `@klarkxy/dsh-model-picker`, when installed, registers one root-scoped
2149
+ * Component Factory under `MODEL_PICKER_FACTORY`: an enhanced model menu with
2150
+ * a searchable provider-grouped list and a reasoning-effort slider, built on
2151
+ * the same ModelMenu recipe. Consumers keep their own ModelMenu as the
2152
+ * `fallback`; when no definition is registered the renderer resolves that
2153
+ * fallback verbatim, so the shared picker is strictly opt-in and every saved
2154
+ * value keeps its existing write path.
2155
+ *
2156
+ * `renderFactorySlot` is a framework-composed prop, never a context: the
2157
+ * component a plugin registers into any slot receives it automatically, but
2158
+ * the plugin must pass it down to whichever child renders the menu (or to the
2159
+ * render callback handed to registerSessionSettingsItem).
2160
+ *
2161
+ * The Factory manages its own open state; a consumer's `open`/`onOpenChange`
2162
+ * pair belongs to the fallback menu only and must not be fed to both.
2163
+ */
2164
+ /**
2165
+ * The trigger every consumer used to hand its own ModelMenu, rebuilt as the
2166
+ * factory's `anchor` local component: identical classes and chevron, the menu
2167
+ * state coming from the occurrence's props instead of local open state.
2168
+ *
2169
+ * `ariaLabel` and `title` are the whole accessible name and tooltip (the
2170
+ * caller formats "field: current route" itself because the factory's resolved
2171
+ * caption may intentionally be a short name). The component identity stays
2172
+ * put across label changes — a new function would unmount the trigger, and
2173
+ * its cleanup would close a menu that `closeOnPick={false}` is keeping open.
2174
+ * The latest label, title, and `onClose` are read from refs.
2175
+ */
2176
+ function useModelMenuAnchor(ariaLabel, title, onClose) {
2177
+ const closeRef = (0, react.useRef)(onClose);
2178
+ const labelRef = (0, react.useRef)(ariaLabel);
2179
+ const titleRef = (0, react.useRef)(title);
2180
+ closeRef.current = onClose;
2181
+ labelRef.current = ariaLabel;
2182
+ titleRef.current = title;
2183
+ return (0, react.useMemo)(() => {
2184
+ const ModelPickerTrigger = (props) => {
2185
+ (0, react.useEffect)(() => () => closeRef.current?.(), []);
2186
+ const record = props;
2187
+ const injected = {};
2188
+ for (const [key, value] of Object.entries(record)) if (key.startsWith("aria-")) injected[key] = value;
2189
+ const extraClass = typeof record.className === "string" ? ` ${record.className}` : "";
2190
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2191
+ type: "button",
2192
+ className: `dsh-model-menu-trigger${extraClass}`,
2193
+ disabled: props.disabled,
2194
+ "aria-haspopup": "menu",
2195
+ "aria-expanded": props.open,
2196
+ ...injected,
2197
+ "aria-label": labelRef.current,
2198
+ title: titleRef.current,
2199
+ children: [
2200
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2201
+ className: "dsh-model-menu-triggerLabel",
2202
+ children: props.label
2203
+ }),
2204
+ props.effortLabel ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2205
+ className: "dsh-model-menu-triggerEffort",
2206
+ children: props.effortLabel
2207
+ }) : null,
2208
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${props.open ? " dsh-model-menu-chevronOpen" : ""}` })
2209
+ ]
2210
+ });
2211
+ };
2212
+ return ModelPickerTrigger;
2213
+ }, []);
2214
+ }
2215
+ /**
2216
+ * Render the shared model picker while it is registered; `options.fallback`
2217
+ * whenever it is not (plugin absent, disabled, or unloaded).
2218
+ *
2219
+ * `options.anchor` is optional: the factory ships a default trigger, so only
2220
+ * consumers that need their own chrome (custom aria-label, tooltip, class)
2221
+ * pass one built with {@link useModelMenuAnchor}. The returned node is never
2222
+ * assumed to be the factory's own output — a defensive `?? fallback` covers a
2223
+ * renderer that yields null for an unregistered definition.
2224
+ */
2225
+ function renderModelPicker(renderFactorySlot, props, options) {
2226
+ if (typeof renderFactorySlot !== "function") return options.fallback;
2227
+ return renderFactorySlot("klarkxy.model-picker", props, {
2228
+ ...options.anchor ? { slots: { ["anchor"]: options.anchor } } : {},
2229
+ fallback: options.fallback
2230
+ }) ?? options.fallback;
2231
+ }
2232
+ //#endregion
1837
2233
  //#region ../dsh-plugin-kit/src/auxiliary-model-client.tsx
1838
2234
  const AUXILIARY_MODEL_PACKAGE = "@klarkxy/dsh-plugin-kit";
1839
2235
  const AUXILIARY_MODEL_SEAT_ID = "auxiliary-model";
@@ -1911,7 +2307,7 @@ async function callAuxiliaryRpc(client, endpoint, payload = {}) {
1911
2307
  * The shared card. All state comes from the shared host service over the
1912
2308
  * plugin-kit RPC channel; the card only renders the stored row.
1913
2309
  */
1914
- function AuxiliaryModelCard({ client, subscribe }) {
2310
+ function AuxiliaryModelCard({ client, subscribe, renderFactorySlot }) {
1915
2311
  const language = useAuxiliaryLocale(client);
1916
2312
  const text = auxiliaryCopy[language];
1917
2313
  const [settings, setSettings] = (0, react.useState)();
@@ -2032,6 +2428,17 @@ function AuxiliaryModelCard({ client, subscribe }) {
2032
2428
  refreshCatalog
2033
2429
  ]);
2034
2430
  const locked = busy;
2431
+ const current = settings?.model ?? {
2432
+ provider: "",
2433
+ model: ""
2434
+ };
2435
+ const follow = !current.provider || !current.model;
2436
+ const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
2437
+ const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
2438
+ const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
2439
+ const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
2440
+ const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
2441
+ const anchor = useModelMenuAnchor(`${text.model}: ${settings ? currentLabel : ""}`, void 0, () => setMenuOpen(false));
2035
2442
  if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2036
2443
  className: "ams-root dsh-ui-card",
2037
2444
  "data-testid": "auxiliary-model-settings",
@@ -2070,16 +2477,6 @@ function AuxiliaryModelCard({ client, subscribe }) {
2070
2477
  })
2071
2478
  ]
2072
2479
  });
2073
- const current = settings.model ?? {
2074
- provider: "",
2075
- model: ""
2076
- };
2077
- const follow = !current.provider || !current.model;
2078
- const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
2079
- const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
2080
- const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
2081
- const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
2082
- const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
2083
2480
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2084
2481
  className: "ams-root dsh-ui-card",
2085
2482
  "data-testid": "auxiliary-model-settings",
@@ -2103,28 +2500,12 @@ function AuxiliaryModelCard({ client, subscribe }) {
2103
2500
  state: catalogState,
2104
2501
  locale: language
2105
2502
  }),
2106
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
2503
+ renderModelPicker(renderFactorySlot, {
2107
2504
  locale: language,
2108
- open: menuOpen && !locked,
2505
+ disabled: locked,
2506
+ busy: locked,
2109
2507
  onOpenChange: (next) => setMenuOpen(next && !locked),
2110
2508
  listClassName: "ams-root ams-menu-portal",
2111
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2112
- type: "button",
2113
- className: "dsh-model-menu-trigger",
2114
- disabled: locked,
2115
- "aria-label": `${text.model}: ${currentLabel}`,
2116
- children: [
2117
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2118
- className: "dsh-model-menu-triggerLabel",
2119
- children: currentLabel
2120
- }),
2121
- !follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2122
- className: "dsh-model-menu-triggerEffort",
2123
- children: effortName ?? text.effortDefault
2124
- }) : null,
2125
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
2126
- ]
2127
- }),
2128
2509
  choices,
2129
2510
  selected: follow ? void 0 : {
2130
2511
  provider: current.provider,
@@ -2147,9 +2528,61 @@ function AuxiliaryModelCard({ client, subscribe }) {
2147
2528
  model: current.model,
2148
2529
  ...effort ? { reasoningEffort: effort } : {}
2149
2530
  }),
2531
+ triggerLabel: currentLabel,
2532
+ triggerEffortLabel: !follow && efforts.length ? effortName ?? text.effortDefault : void 0,
2150
2533
  modelLabel: text.model,
2151
2534
  modelValue: currentLabel,
2152
2535
  defaultEffortLabel: text.effortDefault
2536
+ }, {
2537
+ anchor,
2538
+ fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
2539
+ locale: language,
2540
+ open: menuOpen && !locked,
2541
+ onOpenChange: (next) => setMenuOpen(next && !locked),
2542
+ listClassName: "ams-root ams-menu-portal",
2543
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2544
+ type: "button",
2545
+ className: "dsh-model-menu-trigger",
2546
+ disabled: locked,
2547
+ "aria-label": `${text.model}: ${currentLabel}`,
2548
+ children: [
2549
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2550
+ className: "dsh-model-menu-triggerLabel",
2551
+ children: currentLabel
2552
+ }),
2553
+ !follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2554
+ className: "dsh-model-menu-triggerEffort",
2555
+ children: effortName ?? text.effortDefault
2556
+ }) : null,
2557
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
2558
+ ]
2559
+ }),
2560
+ choices,
2561
+ selected: follow ? void 0 : {
2562
+ provider: current.provider,
2563
+ model: current.model
2564
+ },
2565
+ onPick: (picked) => save({
2566
+ provider: picked.provider,
2567
+ model: picked.model
2568
+ }),
2569
+ leading: [{
2570
+ id: "default",
2571
+ label: text.modelDefault,
2572
+ selected: follow
2573
+ }],
2574
+ onPickLeading: () => save(null),
2575
+ efforts,
2576
+ selectedEffort: current.reasoningEffort || "",
2577
+ onPickEffort: (effort) => save(follow ? null : {
2578
+ provider: current.provider,
2579
+ model: current.model,
2580
+ ...effort ? { reasoningEffort: effort } : {}
2581
+ }),
2582
+ modelLabel: text.model,
2583
+ modelValue: currentLabel,
2584
+ defaultEffortLabel: text.effortDefault
2585
+ })
2153
2586
  }),
2154
2587
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2155
2588
  className: "dsh-ui-hint",
@@ -2251,9 +2684,10 @@ function acquireAuxiliaryModelCard(root, ctx) {
2251
2684
  let unprovide;
2252
2685
  const watchLease = acquireWatchCoordinator(root).mount();
2253
2686
  const subscriber = createModelSettingsSubscriber(ctx.root, AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY, (ns) => ns === AUXILIARY_MODEL_NS);
2254
- const render = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuxiliaryModelCard, {
2687
+ const render = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuxiliaryModelCard, {
2255
2688
  client: root,
2256
- subscribe: subscriber.subscribe
2689
+ subscribe: subscriber.subscribe,
2690
+ renderFactorySlot: props.renderFactorySlot
2257
2691
  });
2258
2692
  const service = {
2259
2693
  retain() {
@@ -2715,6 +3149,7 @@ function TitleSettings(props) {
2715
3149
  const currentLabel = follow ? text.modelDefault : selectedModel?.label ?? `${savedModel.provider} / ${savedModel.model}`;
2716
3150
  const selectedEffort = efforts.find((item) => item.id === (savedModel.reasoningEffort || ""));
2717
3151
  const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, seat.locale) : void 0;
3152
+ const anchor = useModelMenuAnchor(`${text.model}: ${currentLabel}`, void 0, () => setMenuOpen(false));
2718
3153
  const title = status?.session?.title;
2719
3154
  const pinned = status?.session?.pinned === true;
2720
3155
  const weOwn = status?.support?.weOwn === true;
@@ -2926,28 +3361,12 @@ function TitleSettings(props) {
2926
3361
  state: catalogState,
2927
3362
  locale: seat.locale
2928
3363
  }),
2929
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
3364
+ renderModelPicker(props.renderFactorySlot, {
2930
3365
  locale: seat.locale,
2931
- open: menuOpen && Boolean(status) && !busy && !loadingSettings,
3366
+ disabled: busy || loadingSettings || !status,
3367
+ busy: busy || loadingSettings,
2932
3368
  onOpenChange: (next) => setMenuOpen(next && Boolean(status) && !busy && !loadingSettings),
2933
3369
  listClassName: "ct-root",
2934
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2935
- type: "button",
2936
- className: "dsh-model-menu-trigger",
2937
- disabled: busy || loadingSettings || !status,
2938
- "aria-label": `${text.model}: ${currentLabel}`,
2939
- children: [
2940
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2941
- className: "dsh-model-menu-triggerLabel",
2942
- children: currentLabel
2943
- }),
2944
- !follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2945
- className: "dsh-model-menu-triggerEffort",
2946
- children: effortName ?? text.modelDefault
2947
- }) : null,
2948
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen ? " dsh-model-menu-chevronOpen" : ""}` })
2949
- ]
2950
- }),
2951
3370
  choices,
2952
3371
  selected: follow ? void 0 : {
2953
3372
  provider: savedModel.provider,
@@ -2976,9 +3395,67 @@ function TitleSettings(props) {
2976
3395
  model: savedModel.model,
2977
3396
  ...effort ? { reasoningEffort: effort } : {}
2978
3397
  } }),
3398
+ triggerLabel: currentLabel,
3399
+ triggerEffortLabel: !follow && efforts.length ? effortName ?? text.modelDefault : void 0,
2979
3400
  modelLabel: text.model,
2980
3401
  modelValue: currentLabel,
2981
3402
  defaultEffortLabel: text.modelDefault
3403
+ }, {
3404
+ anchor,
3405
+ fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
3406
+ locale: seat.locale,
3407
+ open: menuOpen && Boolean(status) && !busy && !loadingSettings,
3408
+ onOpenChange: (next) => setMenuOpen(next && Boolean(status) && !busy && !loadingSettings),
3409
+ listClassName: "ct-root",
3410
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3411
+ type: "button",
3412
+ className: "dsh-model-menu-trigger",
3413
+ disabled: busy || loadingSettings || !status,
3414
+ "aria-label": `${text.model}: ${currentLabel}`,
3415
+ children: [
3416
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3417
+ className: "dsh-model-menu-triggerLabel",
3418
+ children: currentLabel
3419
+ }),
3420
+ !follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3421
+ className: "dsh-model-menu-triggerEffort",
3422
+ children: effortName ?? text.modelDefault
3423
+ }) : null,
3424
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen ? " dsh-model-menu-chevronOpen" : ""}` })
3425
+ ]
3426
+ }),
3427
+ choices,
3428
+ selected: follow ? void 0 : {
3429
+ provider: savedModel.provider,
3430
+ model: savedModel.model
3431
+ },
3432
+ onPick: (picked) => saveSettings({ model: {
3433
+ provider: picked.provider,
3434
+ model: picked.model
3435
+ } }),
3436
+ leading: [{
3437
+ id: "default",
3438
+ label: text.modelDefault,
3439
+ selected: follow
3440
+ }],
3441
+ onPickLeading: () => saveSettings({ model: {
3442
+ provider: "",
3443
+ model: ""
3444
+ } }),
3445
+ efforts,
3446
+ selectedEffort: savedModel.reasoningEffort || "",
3447
+ onPickEffort: (effort) => saveSettings({ model: follow ? {
3448
+ provider: "",
3449
+ model: ""
3450
+ } : {
3451
+ provider: savedModel.provider,
3452
+ model: savedModel.model,
3453
+ ...effort ? { reasoningEffort: effort } : {}
3454
+ } }),
3455
+ modelLabel: text.model,
3456
+ modelValue: currentLabel,
3457
+ defaultEffortLabel: text.modelDefault
3458
+ })
2982
3459
  }),
2983
3460
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2984
3461
  className: "dsh-ui-meta",
@@ -3046,7 +3523,8 @@ function TitleSettingsItem(props) {
3046
3523
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TitleSettings, {
3047
3524
  client: props.client,
3048
3525
  marker: props.marker,
3049
- subscribeSettings: props.subscribeSettings
3526
+ subscribeSettings: props.subscribeSettings,
3527
+ renderFactorySlot: props.renderFactorySlot
3050
3528
  })
3051
3529
  }) : null]
3052
3530
  });
@@ -3084,10 +3562,11 @@ function apply(ctx) {
3084
3562
  ctx.effect(() => registerSessionSettingsItem(ctx, {
3085
3563
  id: "current-title",
3086
3564
  order: 70
3087
- }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TitleSettingsItem, {
3565
+ }, (slotProps) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TitleSettingsItem, {
3088
3566
  client,
3089
3567
  marker,
3090
- subscribeSettings
3568
+ subscribeSettings,
3569
+ renderFactorySlot: slotProps?.renderFactorySlot
3091
3570
  })), "current-title.settings");
3092
3571
  ctx.effect(() => registerAuxiliaryModelSettings(ctx), "current-title.auxiliary-model");
3093
3572
  ctx.effect(() => {