@bittersmilezzz/dsh-model-selector 0.3.4 → 0.3.5
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
|
@@ -818,7 +818,7 @@ window.__ModuleLoader__.load({
|
|
|
818
818
|
});
|
|
819
819
|
const redrawRef = react.useRef(null);
|
|
820
820
|
const available = state.current !== null && levels.length >= 2;
|
|
821
|
-
const busy = dmsEffortBusy(committing, state.status);
|
|
821
|
+
const busy = dmsEffortBusy(committing, state.status, state.pending);
|
|
822
822
|
const error = localError;
|
|
823
823
|
react.useEffect(() => {
|
|
824
824
|
if (!available || committingRef.current || draggingRef.current) return;
|
|
@@ -2484,7 +2484,7 @@ body:not([data-ds-dark-theme]) .dms-effort.is-dragging .dms-effort-knob {
|
|
|
2484
2484
|
exports.apply = apply;
|
|
2485
2485
|
exports.inject = inject;
|
|
2486
2486
|
return module.exports;
|
|
2487
|
-
/* __DMS_SRC_HASH__ = "
|
|
2487
|
+
/* __DMS_SRC_HASH__ = "15cd0c7d1ec0bb3a49c18a144fa080674142df134d4baf6d3a4a2b6e369fa427" */ }
|
|
2488
2488
|
});
|
|
2489
2489
|
|
|
2490
2490
|
//# sourceMappingURL=client.js.map
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["IconChevronDownOutlineRegular","IconCheckOutlineRegular","IconCloseFillRegular","useAnchoredMaxHeight","Toast","IconWarningOutlineRegular","zhDict","enDict"],"sources":["../src/client/locales.ts","../src/client/effort.ts","../src/client/effortCanvas.ts","../src/client/effortDrag.ts","../src/client/menuFit.ts","../src/client/roving.ts","../src/client/search.ts","../src/client/keys.ts","../src/client/copy.ts","../src/client/ModelSelect.tsx","../src/client/styles.ts","../src/client/index.ts"],"sourcesContent":["/**\n * `modelSelector` namespace dictionaries for the enhanced model seat.\n *\n * Simplified Chinese is the key-set source of truth; the English dictionary is\n * checked complete against it. Product copy is Chinese-first per repo style.\n */\nexport const zh = {\n\t\"trigger.fallback\": \"选择模型\",\n\t\"trigger.loading\": \"正在加载模型…\",\n\t\"trigger.selectAria\": \"选择模型\",\n\t\"trigger.aria\": \"选择模型,当前 {model}\",\n\t\"trigger.ariaEffort\": \"选择模型,当前 {model},推理等级 {effort}\",\n\t\"menu.aria\": \"模型列表\",\n\t\"menu.effort\": \"推理等级\",\n\t\"search.placeholder\": \"搜索模型\",\n\t\"search.clearAria\": \"清除搜索\",\n\t\"search.noMatch\": \"没有匹配“{query}”的模型。\",\n\t\"search.status\": \"{count} 个结果\",\n\t\"search.more\": \"仅显示前 {shown} 条(共 {total} 条命中),请细化关键词。\",\n\t\"group.toggleAria\": \"{name}({count} 个模型)\",\n\t\"effort.providerDefault\": \"Default\",\n\t\"status.loading\": \"正在刷新模型列表…\",\n\t\"error.action\": \"模型操作失败:{message}\",\n\t\"action.reload\": \"重新加载\",\n\t\"warning.groupLoad\": \"{name} 加载失败:{message}\",\n\t\"empty.models\": \"没有可用的模型。\",\n\t\"empty.efforts\": \"当前模型未提供推理等级。\",\n\t\"effort.failed\": \"档位切换未生效(后端未接受),已恢复原档位\",\n\t\"effort.timeout\": \"档位切换超时,已恢复原档位\",\n\t\"notice.already\": \"已是当前模型,无需切换\",\n\t\"notice.selectFailed\": \"切换失败,请重试\",\n\t\"toast.effortAuto\": \"已自动选到最强思考档:{effort}\",\n\t\"badge.reasoning\": \"推理\",\n\t\"badge.reasoningHint\": \"支持思考等级,切换后自动选到最大思考强度\"\n};\n\n\nexport const en = {\n\t\"trigger.fallback\": \"Select model\",\n\t\"trigger.loading\": \"Loading models…\",\n\t\"trigger.selectAria\": \"Select model\",\n\t\"trigger.aria\": \"Select model, current {model}\",\n\t\"trigger.ariaEffort\": \"Select model, current {model}, reasoning effort {effort}\",\n\t\"menu.aria\": \"Model list\",\n\t\"menu.effort\": \"Effort\",\n\t\"search.placeholder\": \"Search models\",\n\t\"search.clearAria\": \"Clear search\",\n\t\"search.noMatch\": \"No models match “{query}”.\",\n\t\"search.status\": \"{count} results\",\n\t\"search.more\": \"Showing first {shown} of {total} matches; refine your query.\",\n\t\"group.toggleAria\": \"{name} ({count} models)\",\n\t\"effort.providerDefault\": \"Default\",\n\t\"status.loading\": \"Refreshing model list…\",\n\t\"error.action\": \"Model operation failed: {message}\",\n\t\"action.reload\": \"Reload\",\n\t\"warning.groupLoad\": \"{name} failed to load: {message}\",\n\t\"empty.models\": \"No models available.\",\n\t\"empty.efforts\": \"This model provides no reasoning effort levels.\",\n\t\"effort.failed\": \"Failed to switch effort (backend rejected); restored\",\n\t\"effort.timeout\": \"Timed out switching effort; restored\",\n\t\"notice.already\": \"Already the current model\",\n\t\"notice.selectFailed\": \"Switch failed, please try again\",\n\t\"toast.effortAuto\": \"Auto-selected the strongest reasoning level: {effort}\",\n\t\"badge.reasoning\": \"Reasoning\",\n\t\"badge.reasoningHint\": \"Supports reasoning levels; switches land on the strongest\"\n};\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The enhanced model seat copy. */\n modelSelector: keyof typeof zh\n }\n}\n","/**\n * dsh-model-selector — 推理强度档位的纯函数。\n *\n * 从 ModelSelect.tsx 原样搬出(无 JSX、无 DOM、无模块级副作用),以便用\n * node --test 直接跑源码:滑块钳位、档位映射与「取模型最强档」都是易错且\n * 用户可见的逻辑,留在 .tsx 里则无法脱离 React 测试。\n */\nimport type { ModelDirectoryState } from '@deepseek-ai/dsh-client-ui-model-selection/client'\nimport type { ModelProviderGroup, ModelReasoning } from '@deepseek-ai/dsh-api-session-controller/types'\n\n/** One model effort level as advertised by the adapter. */\nexport type EffortLevel = ModelReasoning['efforts'][number]\n/** Per-session model directory snapshot (official state shape). */\nexport type DirectoryState = ModelDirectoryState\n/** The selected model entry, or undefined when nothing resolves. */\nexport type ModelEntry = ModelProviderGroup['models'][number]\n\n/** Canonical strength order of pi-ai thinking levels (strongest = highest). */\nexport const EFFORT_RANK = {\n off: 0,\n minimal: 1,\n low: 2,\n medium: 3,\n high: 4,\n xhigh: 5,\n max: 6,\n}\n\n/** The strongest thinking level a model offers, or undefined for none. */\nexport function maxEffortOf(reasoning: ModelReasoning): string | undefined {\n let best\n for (const effort of reasoning.efforts) {\n const rank = EFFORT_RANK[effort.id as keyof typeof EFFORT_RANK] ?? 0\n if (best === void 0 || rank > best.rank) best = {\n id: effort.id,\n rank,\n }\n }\n return best?.id\n}\n\n/**\n * 已知档位的秩;适配器自造的非规范 id(如 'turbo')返回 undefined —— 与\n * 「已知但秩为 0」(off)区分开。maxEffortOf 对并列的未知档取首个,把它当\n * 「最强档」提交等于随机挑一个档、还可能被宿主拒,故调用方必须先过这一关。\n */\nexport function dmsEffortRank(id: string): number | undefined {\n return Object.prototype.hasOwnProperty.call(EFFORT_RANK, id)\n ? EFFORT_RANK[id as keyof typeof EFFORT_RANK]\n : void 0\n}\n\n/** 选中模型时的自动档位决策。 */\nexport interface ChoosePlan {\n /** 随选择提交的档位 id;undefined = 不提交 reasoningEffort(交给宿主用模型默认档)。 */\n effort: string | undefined\n /** 是否播报「已自动选到最强思考档」:只有确实替用户做了决定(提交档 ≠ 模型默认档)才播。 */\n autoRaised: boolean\n}\n\n/**\n * 选择模型时自动落到最强档的决策(从 choose() 搬出并修正):\n * - 非推理模型 / 无档位 → 不提交 effort;\n * - 最强档是 'off'(模型只提供 off)→ 不提交;\n * - 最强档是**非规范 id**(rank 未知)→ 不提交:maxEffortOf 对并列未知档取\n * 首个,把它当「最强」提交会挑错档甚至被宿主拒(用户看到「切换失败」);\n * - 否则提交已知最强档;只有它不等于模型声明的默认档时才播报落点。\n */\nexport function dmsChoosePlan(reasoning: ModelReasoning | undefined): ChoosePlan {\n if (reasoning === void 0) return { effort: void 0, autoRaised: false }\n const best = maxEffortOf(reasoning)\n if (best === void 0 || best === 'off' || dmsEffortRank(best) === void 0) return { effort: void 0, autoRaised: false }\n return { effort: best, autoRaised: best !== reasoning.defaultEffort }\n}\n\nexport function dmsEffortIndex(levels: readonly EffortLevel[], id: string | undefined): number {\n return levels.findIndex((level) => level.id === id)\n}\n\n/** 索引钳位:档位数为 0 时返回 0,四舍五入后落在 [0, count-1]。 */\nexport function dmsClampIndex(value: number, count: number): number {\n if (count <= 0) return 0\n return Math.max(0, Math.min(count - 1, Math.round(value)))\n}\n\n/**\n * 指针水平位置 → 档位原始值(未四舍五入,拖动中途可落在两档之间;落点由\n * 提交方 clamp)。输入条退化(宽度 ≤ 0 或档位数 < 2)时保持当前值不动。\n */\nexport function dmsPointerRaw(clientX: number, left: number, width: number, levelCount: number, current: number): number {\n if (width <= 0 || levelCount < 2) return current\n return Math.max(0, Math.min(levelCount - 1, (clientX - left) / width * (levelCount - 1)))\n}\n\nexport function dmsCurrentModel(state: DirectoryState): ModelEntry | undefined {\n if (state.current === null) return void 0\n const current = state.current\n const group = state.groups.find((g) => g.id === current.provider)\n const model = group?.models.find((m) => m.id === current.model)\n return model ?? void 0\n}\n\n/**\n * trigger 的 effort 文案:已选模型在 catalog 中查不到时(rc.2 官方目录「保留态」:\n * current 仍留着,模型/提供商已离开目录)回落到目录的 `retainedEffort`。\n * 官方 ModelSelect 同规则——不回落会让 trigger 在模型离开目录后莫名丢掉档位名。\n * 老运行时(rc.1 及更早)无 retainedEffort 字段,回落值为 undefined,行为不变。\n */\nexport function dmsRetainedEffortLabel(state: DirectoryState): string | undefined {\n return state.retainedEffort ?? void 0\n}\n\n/** 当前生效档位:用户已选优先,其次模型默认,最后取中间档。 */\nexport function dmsEffectiveEffortIndex(levels: readonly EffortLevel[], state: DirectoryState): number {\n const reasoning = dmsCurrentModel(state)?.reasoning\n const current = dmsEffortIndex(levels, state.current?.reasoningEffort)\n if (current >= 0) return current\n const fallback = dmsEffortIndex(levels, reasoning?.defaultEffort)\n if (fallback >= 0) return fallback\n return Math.floor((levels.length - 1) / 2)\n}\n\n/** 滑块档位:少于两档(无法构成滑杆)时返回空数组。 */\nexport function dmsSliderLevels(state: DirectoryState): readonly EffortLevel[] {\n const efforts = dmsCurrentModel(state)?.reasoning?.efforts\n return efforts !== void 0 && efforts.length >= 2 ? efforts : []\n}\n\n/**\n * 滑杆是否处于忙态:自身提交中,或共享目录上有任一 select 在途\n * (目录 select 是 last-writer-wins,模型切换在途时再发 effort RPC 会与\n * 它交错——effort 打到旧模型上,与模型切换互相覆盖;choose() 按同一状态\n * 拒绝了模型点击,滑杆必须同规则拒绝交互)。\n *\n * 目录在途的判据是 `pending !== null`(DSH 0.1.7-rc.2 起官方目录语义):\n * 它比 `status === 'selecting'` 早一步置位——`select()` 一被调用就记下\n * pending,RPC 未返回前 status 仍是 idle/ready。只按 status 判断存在一个\n * 窗口期:pending 已写入而 status 未翻,此时拖动会被接受并与在途的 select\n * 交错。旧运行时没有 pending 字段(rc.1 及更早),`pending` 为 undefined\n * 时退回 status 判据,保证向后兼容。\n */\nexport function dmsEffortBusy(committing: boolean, status: DirectoryState['status'], pending?: DirectoryState['pending']): boolean {\n return committing || (pending !== undefined ? pending !== null : status === 'selecting')\n}\n\n/**\n * 超时回滚后 select 迟到成功是否应采纳并同步 UI:\n * 只有当自回滚以来没有任何新的提交改写 committedRef(仍等于回滚前的档位)、\n * 提交纪元未推进(epoch === epochAtCommit,即期间没有发起过新提交——提交失败\n * 回滚后 committedRef 会回到与上一轮回滚相同的值,值比较无法区分「无新提交」\n * 与「新提交失败回滚」,纪元才能区分)、当前无拖动/提交在途,**且生效模型仍是\n * 发起提交时的那个**(sameModel)时,迟到结果才仍对应当前唯一的意图链——此时\n * UI 处于「回滚但后端已生效」的错位态,应补一次同步而不是保持回滚。\n * 否则以新操作链为准:模型被外部改写(另一入口切换 / 宿主推送新投影 / 重连后\n * 同步出别的选择)时,next/index 是在旧模型的档位表上算出来的,写回 UI 会显示\n * 一个后端并未生效的档位。\n */\nexport function dmsShouldAdoptLateSuccess(committed: string, previous: string, dragging: boolean, committing: boolean, epoch: number, epochAtCommit: number, sameModel: boolean): boolean {\n return sameModel && committed === previous && epoch === epochAtCommit && !dragging && !committing\n}\n\n/**\n * 拖动终止事件是否对应当前活动拖动:pointerActive 为假(拖动已结束/从未开始)\n * 或事件 pointerId 与活动指针不一致(迟到/重复事件——典型是 pointercancel 终态\n * 后平台补发的 pointerup)时返回 false,调用方应幂等跳过终止动作:不清状态、\n * 不触发提交/回滚回调。pointerId 缺省(blur 兜底提交,无事件对象)时只要求\n * 拖动仍在进行中。\n */\nexport function dmsIsActiveDrag(pointerActive: boolean, activePointerId: number | null, pointerId: number | undefined): boolean {\n if (!pointerActive) return false\n if (pointerId !== void 0 && activePointerId !== pointerId) return false\n return true\n}\n","/**\n * dsh-model-selector — EffortSlider 的 canvas 辐射特效模块。\n *\n * 从 ModelSelect.tsx 原样搬出(原 EffortSlider 组件内的 330 行状态机里,\n * 绘制与动画循环约占一半且与 React 状态解耦):\n * - dmsDrawRadiation:纯绘制函数,state(progress/dragging)与主题都由参数\n * 传入,不读组件闭包——同一帧可被 pointer 事件、rAF 循环、主题变化各路径复用。\n * - useEffortCanvas:canvas 生命周期(尺寸/DPR、ResizeObserver、主题\n * MutationObserver、visibilitychange、prefers-reduced-motion)与相位缓动\n * 动画循环。外部通过 radiationRef 更新 target/dragging 驱动重绘,hook\n * 返回的 redraw 注册到组件侧 ref(preview 变化时组件直接调它触发一帧)。\n */\nimport * as react from 'react'\n\n/** 绘制状态的最小契约:进度(0..1)+ 是否拖拽(拖拽时加速/改粒子参数)。 */\nexport interface RadiationState {\n progress: number\n dragging: boolean\n /** 目标进度;undefined 表示尚未初始化(首帧同步起跳,不做缓动)。 */\n target?: number\n}\n\n/** 拖动时辐射推挤的缓动系数(越小越跟手),非拖拽时回弹系数。 */\nconst DRAG_EASE = 0.55\nconst IDLE_EASE = 0.12\nconst SETTLE_EPSILON = 0.002\n\n/**\n * 纯绘制:把滑块进度画成拖尾辐射(列能量 + 径向光晕 + 粒子流 + 热核光斑)。\n * 全部可变量(尺寸、相位、进度、拖拽态、深浅色)都来自参数——无模块级状态。\n */\nexport function dmsDrawRadiation(context: CanvasRenderingContext2D, width: number, height: number, time: number, state: RadiationState, isDark: boolean): void {\n const origin = state.progress * width;\n const cell = 4;\n const speed = state.dragging ? 2.8 : 1;\n context.clearRect(0, 0, width, height);\n if (origin <= 0) return;\n context.save();\n context.beginPath();\n context.rect(0, 0, origin, height);\n context.clip();\n for (let x = 0; x < origin; x += cell) {\n const delta = x + cell * 0.5 - origin;\n const distance = Math.abs(delta);\n const phaseA = distance / 10 - time * 74e-4 * speed;\n const phaseB = distance / 23 - time * 41e-4 * speed + 1.7;\n const phaseC = distance / 40 - time * 22e-4 * speed + 3.4;\n const sinA = Math.max(0, Math.sin(phaseA));\n const sinB = Math.max(0, Math.sin(phaseB));\n const sinC = Math.max(0, Math.sin(phaseC));\n const waveA = Math.pow(sinA, 2.6);\n const waveB = Math.pow(sinB, 3.2);\n const waveC = Math.pow(sinC, 4);\n const crest = Math.pow(sinA, 15) + Math.pow(sinB, 18) * 0.78;\n const wave = Math.min(1, waveA * 0.76 + waveB * 0.58 + waveC * 0.32);\n const trail = 0.38 + 0.62 * Math.exp(-distance / 90);\n const pillar = Math.pow(Math.max(0, Math.sin(x / 20 + time * 16e-4)), 3) * 0.27;\n const columnEnergy = trail * (wave * 1.04 + pillar + crest * 0.32);\n if (columnEnergy > 0.012) {\n const nearness = Math.max(0, 1 - distance / 140);\n const red = isDark ? Math.round(42 + 124 * nearness + 75 * wave) : Math.round(28 + 58 * nearness + 15 * wave);\n const green = isDark ? Math.round(56 + 58 * nearness + 44 * crest) : Math.round(88 + 72 * nearness + 30 * crest);\n const blue = isDark ? Math.round(175 + 72 * nearness + 8 * wave) : Math.round(182 + 62 * nearness);\n const alpha = isDark ? Math.min(0.88, columnEnergy * 0.72) : Math.min(0.62, columnEnergy * 0.54);\n context.fillStyle = `rgba(${red}, ${green}, ${blue}, ${alpha})`;\n context.fillRect(x, 0, cell - 1, height);\n }\n for (let y = 0; y < height; y += cell) {\n const deltaY = y + cell * 0.5 - height * 0.5;\n const radial = Math.hypot(delta / 38, deltaY / 11);\n const halo = Math.exp(-radial * 0.96) * 1.08;\n const verticalShape = 0.58 + 0.42 * Math.cos(deltaY / height * Math.PI);\n const grain = 0.72 + 0.28 * Math.sin(x * 0.73 + y * 1.31 + time * 6e-3);\n const alpha = Math.min(0.96, (columnEnergy * 0.88 + halo + crest * 0.19) * verticalShape * grain);\n if (alpha < 0.035) continue;\n const hot = Math.max(0, 1 - radial / 2.4);\n const red = isDark ? Math.round(54 + 148 * hot + 42 * wave + 35 * crest) : Math.round(25 + 72 * hot + 12 * wave);\n const green = isDark ? Math.round(68 + 78 * hot + 46 * crest) : Math.round(98 + 72 * hot + 24 * crest);\n const blue = isDark ? Math.round(186 + 64 * hot) : Math.round(194 + 56 * hot);\n context.fillStyle = `rgba(${red}, ${green}, ${blue}, ${isDark ? alpha : alpha * 0.72})`;\n context.fillRect(x, y, cell - 1, cell - 1);\n }\n }\n for (let i = 0; i < 14; i += 1) {\n const travel = (time * (state.dragging ? 0.16 : 0.065) * (0.78 + i % 5 * 0.09) + i * 23) % Math.max(30, origin + 64);\n const particleX = origin - travel;\n if (particleX < -24 || particleX > width + 16) continue;\n const particleY = 3 + (i * 13 + Math.sin(time * 3e-3 + i) * 5) % Math.max(7, height - 6);\n const length = 4 + i % 4 * 4 + (state.dragging ? 6 : 0);\n const alpha = 0.28 + i % 5 * 0.1;\n const streak = context.createLinearGradient(particleX, 0, particleX + length, 0);\n streak.addColorStop(0, isDark ? \"rgba(72,118,255,0)\" : \"rgba(24,94,184,0)\");\n streak.addColorStop(0.68, isDark ? `rgba(112,135,255,${alpha})` : `rgba(36,108,202,${alpha * 0.72})`);\n streak.addColorStop(1, isDark ? `rgba(236,222,255,${Math.min(1, alpha + 0.26)})` : `rgba(103,175,248,${Math.min(0.82, alpha + 0.18)})`);\n context.fillStyle = streak;\n context.fillRect(particleX, particleY, length, i % 3 === 0 ? 2 : 1);\n }\n const glow = context.createRadialGradient(origin, height / 2, 0, origin, height / 2, 24);\n glow.addColorStop(0, isDark ? \"rgba(255,255,255,.82)\" : \"rgba(255,255,255,.86)\");\n glow.addColorStop(0.14, isDark ? \"rgba(183,190,255,.54)\" : \"rgba(162,210,255,.48)\");\n glow.addColorStop(0.44, isDark ? \"rgba(103,74,255,.28)\" : \"rgba(37,112,207,.22)\");\n glow.addColorStop(1, isDark ? \"rgba(86,31,210,0)\" : \"rgba(25,91,181,0)\");\n context.fillStyle = glow;\n context.fillRect(origin - 26, 0, 52, height);\n context.restore();\n}\n\n/**\n * 辐射画布生命周期 hook:尺寸/DPR 自适应、主题变化监听、动画循环与相位时钟。\n *\n * 外部契约:\n * - radiationRef.current.target / .dragging 由组件在 preview/忙态变化时更新,\n * hook 的动画循环按 target 做指数缓动(拖动中每帧逼近 target,回弹时渐近);\n * - 返回的 redraw 应注册到组件侧 ref,供事件驱动的重绘(拖动一帧、主题翻转)\n * 即时调用;挂载时画首帧并把 progress 直接对齐 target(打开菜单不闪中间态)。\n *\n * 动画成本:拖拽时只画事件驱动帧(pointer 移动触发的单帧),循环空闲时\n * 一帧即停——画布不常驻 rAF,CPU 占用只在缓动回弹窗口内存在。\n */\nexport function useEffortCanvas(canvasRef: react.RefObject<HTMLCanvasElement | null>, radiationRef: react.MutableRefObject<RadiationState>): () => void {\n const redrawRef = react.useRef<(() => void) | null>(null);\n react.useEffect(() => {\n const canvas = canvasRef.current;\n if (canvas === null) return;\n const context = canvas.getContext(\"2d\");\n if (context === null) return;\n const reducedMotion = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n let width = 1;\n let height = 1;\n let frame = 0;\n // Phase clock advances only while the loop runs, so a frozen (idle)\n // effect resumes at the exact wave phase it paused at — no jump.\n let phase = 0;\n let lastFrameAt = 0;\n const resize = () => {\n const bounds = canvas.getBoundingClientRect();\n const ratio = Math.min(window.devicePixelRatio || 1, 2);\n width = Math.max(1, bounds.width);\n height = Math.max(1, bounds.height);\n canvas.width = Math.max(1, Math.round(width * ratio));\n canvas.height = Math.max(1, Math.round(height * ratio));\n context.setTransform(ratio, 0, 0, ratio, 0, 0);\n };\n // Draw one frame at the current phase; returns true while the effect\n // should keep animating (still easing toward its target).\n // Idle = one static frame, zero further CPU — the loop only runs while\n // the glow is settling, instead of forever.\n // While dragging, stop after a single frame: every pointer move bumps\n // `preview` and redraw() draws one event-driven frame, so the glow\n // follows the knob without a back-to-back repaint stream. Continuously\n // repainting this mix-blend canvas is what flickered the whole menu.\n const draw = (): boolean => {\n const r = radiationRef.current;\n if (r.target !== void 0) {\n const k = r.dragging ? DRAG_EASE : IDLE_EASE;\n r.progress += (r.target - r.progress) * k;\n if (Math.abs(r.target - r.progress) < SETTLE_EPSILON) r.progress = r.target;\n }\n // 主题在这里(每次 draw 时)读取一次:dmsDrawRadiation 保持参数输入、\n // 无 DOM 依赖可测;主题翻转由 themeObserver → draw() 即时取到新值。\n const isDark = document.body.hasAttribute(\"data-ds-dark-theme\");\n dmsDrawRadiation(context, width, height, phase, r, isDark);\n if (r.dragging) return false;\n return r.target !== void 0 && Math.abs(r.target - r.progress) > SETTLE_EPSILON;\n };\n const loop = (time: number): void => {\n if (lastFrameAt !== 0) phase += time - lastFrameAt;\n lastFrameAt = time;\n if (draw() && !document.hidden) {\n frame = window.requestAnimationFrame(loop);\n } else {\n frame = 0;\n lastFrameAt = 0;\n }\n };\n const redraw = () => {\n if (reducedMotion.matches) {\n draw();\n return;\n }\n // Restart the loop on demand (pointer/theme/preview changes);\n // a running loop already draws every frame, so no extra draw here.\n if (frame === 0 && !document.hidden) {\n lastFrameAt = 0;\n frame = window.requestAnimationFrame(loop);\n }\n };\n const resizeObserver = new ResizeObserver(() => {\n resize();\n draw();\n });\n const themeObserver = new MutationObserver(() => draw());\n const onVisibility = () => {\n if (!document.hidden) redraw();\n };\n // 跨屏(窗口拖到另一台显示器)时 devicePixelRatio 变化:canvas 的 CSS\n // 尺寸不变,ResizeObserver 不会触发,位图分辨率停留在旧 DPR → 模糊。\n // 按当前 DPR 构造 resolution 媒体查询,DPR 一变旧查询即失配 → change。\n const resolutionQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`);\n const onResolutionChange = () => {\n resize();\n draw();\n };\n if (resolutionQuery.addEventListener !== void 0) {\n resolutionQuery.addEventListener(\"change\", onResolutionChange);\n } else {\n resolutionQuery.addListener(onResolutionChange); // 旧版 WebKit\n }\n resizeObserver.observe(canvas);\n themeObserver.observe(document.body, { attributes: true, attributeFilter: [\"data-ds-dark-theme\"] });\n document.addEventListener(\"visibilitychange\", onVisibility);\n redrawRef.current = redraw;\n resize();\n // 首帧直接把进度对齐 target(若已初始化):菜单打开瞬间不展示中间进度。\n const r = radiationRef.current;\n if (r.target !== void 0) r.progress = r.target;\n draw();\n if (!reducedMotion.matches && !document.hidden) frame = window.requestAnimationFrame(loop);\n return () => {\n window.cancelAnimationFrame(frame);\n resizeObserver.disconnect();\n themeObserver.disconnect();\n if (resolutionQuery.removeEventListener !== void 0) {\n resolutionQuery.removeEventListener(\"change\", onResolutionChange);\n } else {\n resolutionQuery.removeListener(onResolutionChange);\n }\n document.removeEventListener(\"visibilitychange\", onVisibility);\n redrawRef.current = null;\n };\n }, [canvasRef, radiationRef]);\n return react.useCallback(() => redrawRef.current?.(), []);\n}\n","/**\n * dsh-model-selector — EffortSlider 的指针拖动状态机。\n *\n * 从 ModelSelect.tsx 原样搬出(原 EffortSlider 的 8 个 ref 指针生命周期):\n * 拖动只认发起时记录的活动 pointerId,window 捕获阶段的 move/up/cancel 兜底\n * 指针拖出输入条/菜单外的场景;取消(pointercancel / 触控失控)走回滚回调,\n * 不提交。几何换算(clientX → 档位原始值)与忙态判定都经由回调外接,\n * hook 本身不读组件状态,拖动中途组件重渲染(preview setState)不会中断拖动。\n */\nimport * as react from 'react'\nimport { dmsIsActiveDrag, dmsPointerRaw } from './effort.ts'\n\nexport interface EffortDragHandlers {\n onPointerDown: (event: react.PointerEvent<HTMLInputElement>) => void\n onPointerMove: (event: react.PointerEvent<HTMLInputElement>) => void\n onPointerUp: (event: react.PointerEvent<HTMLInputElement>) => void\n onPointerCancel: (event: react.PointerEvent<HTMLInputElement>) => void\n onBlur: () => void\n}\n\nexport interface UseEffortDragOptions {\n /** 当前档位数(levels.length),拖动态期间变化时以最新值为准。 */\n levelCount: () => number\n /** 忙态(提交中/目录 select 在途)时拒绝新开拖动。 */\n canStart: () => boolean\n /** 拖动中预览(组件侧乐观 setState);返回值供组件更新其当前 raw ref。 */\n onPreview: (raw: number) => void\n /** 拖动结束(pointerup / blur 兜底)时提交;拖出后归位也会提交同一档。 */\n onCommit: (raw: number) => void\n /** 取消(pointercancel / 触控失控)时回滚到已提交档,不提交。 */\n onRollback: () => void\n inputRef: react.RefObject<HTMLInputElement | null>\n /** 拖动活状态(true = 指针按下未结束)。组件与 hook 共享同一 ref:\n * 组件侧用它守卫「拖动态不被打断」(commit 迟到采纳判定、目录同步 effect)。 */\n draggingRef: react.MutableRefObject<boolean>\n}\n\n/**\n * 拖动中由 pointerId 校验的几何换算:raw = (clientX - left) / width 映射到档位区间。\n * 输入条退化(宽度 ≤ 0 / 档位数 < 2)时返回 fallback(当前值,不跳档)。\n */\nfunction rawFromPointer(input: HTMLInputElement, clientX: number, levelCount: number, fallback: number): number {\n const bounds = input.getBoundingClientRect();\n return dmsPointerRaw(clientX, bounds.left, bounds.width, levelCount, fallback);\n}\n\nexport function useEffortDrag({ levelCount, canStart, onPreview, onCommit, onRollback, inputRef, draggingRef }: UseEffortDragOptions) {\n const [dragging, setDragging] = react.useState(false);\n const pointerActiveRef = react.useRef(false);\n const activePointerIdRef = react.useRef<number | null>(null);\n // 拖动中最近一次预览的 raw:blur 兜底提交(无 clientX)时用它。\n const lastRawRef = react.useRef(0);\n // 回调经 ref 转发,hook 内部的事件处理器始终持有最新闭包(组件重渲染不重建监听)。\n const callbacksRef = react.useRef({ levelCount, canStart, onPreview, onCommit, onRollback });\n callbacksRef.current = { levelCount, canStart, onPreview, onCommit, onRollback };\n\n const showPointerPreview = react.useCallback((input: HTMLInputElement, clientX: number): void => {\n const raw = rawFromPointer(input, clientX, callbacksRef.current.levelCount(), lastRawRef.current);\n lastRawRef.current = raw;\n callbacksRef.current.onPreview(raw);\n }, []);\n\n const beginDragging = react.useCallback((input: HTMLInputElement, pointerId: number, clientX: number): void => {\n pointerActiveRef.current = true;\n activePointerIdRef.current = pointerId;\n draggingRef.current = true;\n setDragging(true);\n showPointerPreview(input, clientX);\n try {\n if (!input.hasPointerCapture(pointerId)) input.setPointerCapture(pointerId);\n } catch (cause) {\n // 预期内失败:setPointerCapture 在元素已卸载 / 指针已失效等场景会抛\n // NotFoundError/InvalidStateError。影响仅限「指针拖出输入条后由元素继续\n // 接收事件」这条优化路径——window 捕获阶段的 move/up 兜底监听仍然生效,\n // 拖动不会因此中断,无需恢复;留 debug 痕迹便于排查真实异常。\n console.debug(\"[dms-effort] setPointerCapture failed\", cause);\n }\n }, [showPointerPreview]);\n\n const moveDragging = react.useCallback((input: HTMLInputElement, pointerId: number, clientX: number): void => {\n if (!pointerActiveRef.current || activePointerIdRef.current !== pointerId) return;\n showPointerPreview(input, clientX);\n }, [showPointerPreview]);\n\n const stopDragging = react.useCallback((input: HTMLInputElement, pointerId?: number, clientX?: number): void => {\n if (!dmsIsActiveDrag(pointerActiveRef.current, activePointerIdRef.current, pointerId)) return;\n const raw = clientX === void 0 ? lastRawRef.current : rawFromPointer(input, clientX, callbacksRef.current.levelCount(), lastRawRef.current);\n lastRawRef.current = raw;\n pointerActiveRef.current = false;\n activePointerIdRef.current = null;\n draggingRef.current = false;\n setDragging(false);\n if (pointerId !== void 0 && input.hasPointerCapture(pointerId)) {\n input.releasePointerCapture(pointerId);\n }\n callbacksRef.current.onCommit(raw);\n }, []);\n\n // 取消路径的公共清理:pointercancel 是终态事件(规范上不再有该指针的后续事件),\n // 但部分平台/浏览器在 cancel 后仍会补发 pointerup——不清干净会把「取消」误当成\n // 正常提交。此函数经 dmsIsActiveDrag 校验活动指针、清空全部拖动状态、回滚不提交,\n // 幂等(第二次调用因指针已清空而直接返回)。blur 兜底共用 stopDragging 的同一\n // 校验,双触发路径不会产生重复 commit。\n const cancelDragging = (pointerId: number): void => {\n if (!dmsIsActiveDrag(pointerActiveRef.current, activePointerIdRef.current, pointerId)) return;\n pointerActiveRef.current = false;\n activePointerIdRef.current = null;\n draggingRef.current = false;\n setDragging(false);\n callbacksRef.current.onRollback();\n };\n\n // 全局兜底监听:拖动中指针移出输入条/菜单仍留在窗口内时,move/up 由 window 捕获。\n // 只在拖动进行中挂载(pointerdown 命中 → dragging=true),up/cancel/blur 结束\n // 拖动(dragging=false)即卸载——菜单开着但不拖动时窗口上不挂任何监听。pointercancel\n // 是终态,平台随后补发的 pointerup 要么因监听已卸载而收不到、要么走 stopDragging\n // 的 dmsIsActiveDrag 守卫幂等跳过,与常驻挂载时语义一致。挂载缝隙(pointerdown\n // 到 effect 落地之间)内指针仍在输入条上,由输入条自身的 JSX 事件兜住,无事件丢失。\n const globalMoveRef = react.useRef<(event: PointerEvent) => void>(() => {});\n const globalEndRef = react.useRef<(event: PointerEvent) => void>(() => {});\n const globalCancelRef = react.useRef<(event: PointerEvent) => void>(() => {});\n globalMoveRef.current = (event) => {\n const input = inputRef.current;\n if (input !== null) moveDragging(input, event.pointerId, event.clientX);\n };\n globalEndRef.current = (event) => {\n const input = inputRef.current;\n if (input !== null) stopDragging(input, event.pointerId, event.clientX);\n };\n globalCancelRef.current = (event: PointerEvent): void => cancelDragging(event.pointerId);\n react.useEffect(() => {\n if (!dragging) return;\n const move = (event: PointerEvent) => globalMoveRef.current(event);\n const end = (event: PointerEvent) => globalEndRef.current(event);\n const cancel = (event: PointerEvent) => globalCancelRef.current(event);\n window.addEventListener(\"pointermove\", move, true);\n window.addEventListener(\"pointerup\", end, true);\n window.addEventListener(\"pointercancel\", cancel, true);\n return () => {\n window.removeEventListener(\"pointermove\", move, true);\n window.removeEventListener(\"pointerup\", end, true);\n window.removeEventListener(\"pointercancel\", cancel, true);\n };\n }, [dragging]);\n\n const handlers: EffortDragHandlers = {\n onPointerDown: (event: react.PointerEvent<HTMLInputElement>) => {\n // 忙态(模型切换/提交在途)不开新拖拽:目录 select 是\n // last-writer-wins,交错的 effort RPC 会打到旧模型上。\n if (!callbacksRef.current.canStart()) return;\n event.preventDefault();\n event.currentTarget.focus();\n beginDragging(event.currentTarget, event.pointerId, event.clientX);\n },\n onPointerMove: (event: react.PointerEvent<HTMLInputElement>) => moveDragging(event.currentTarget, event.pointerId, event.clientX),\n onPointerUp: (event: react.PointerEvent<HTMLInputElement>) => stopDragging(event.currentTarget, event.pointerId, event.clientX),\n onPointerCancel: (event: react.PointerEvent<HTMLInputElement>) => {\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId);\n }\n cancelDragging(event.pointerId);\n },\n onBlur: () => {\n const input = inputRef.current;\n if (input !== null) stopDragging(input);\n },\n };\n\n return {\n dragging,\n draggingRef,\n handlers,\n };\n}\n","/**\n * dsh-model-selector — 菜单弹出方向与高度钳位的纯函数。\n *\n * 从 ModelSelect.tsx 原样搬出(无 JSX、无 DOM、无模块级副作用),以便用\n * node --test 直接跑源码:方向决策(向上 vs 向下弹)和向下弹的高度钳位都是\n * 用户可见且曾在矮窗口/滚动场景出过 bug 的逻辑,留在 .tsx 里则无法脱离\n * 浏览器测试。\n */\n\n/** 菜单设计最大高度(px);实际由 useAnchoredMaxHeight 按视口可用空间钳位。\n * 调法:调大允许更高菜单(矮视口下仍会被钳到视口内),调小菜单更矮。 */\nexport const MENU_MAX_HEIGHT = 420;\n/** 与视口边缘保留的距离(px);对齐 primitives 里未导出的 MARGIN。\n *\n * 调用 `useAnchoredMaxHeight` 时**显式传这个值**作为 margin 参数(见\n * ModelSelect.tsx 的调用点),而不是靠官方默认值恰好也是 12 的巧合:上游一旦把\n * 默认 MARGIN 改掉,隐式依赖会让本菜单的贴边距离跟着漂,而菜单自身毫无察觉。\n * `test/official-seat-shadow.test.mjs` 反向钉住「两边仍相等」——官方改了会红,\n * 那时再决定是跟随还是显式分叉。\n * 改动同时影响向上/向下弹与水平钳位的边距,需与官方 MARGIN 意图保持一致。 */\nexport const MENU_VIEWPORT_MARGIN = 12;\n\n/**\n * 向上弹当且仅当 trigger 上方可用空间不小于下方可用空间:\n * 取上下两侧中空间更大的一侧,避免矮窗口把面板挤到视口下沿外面。\n */\nexport function dmsMenuAbove(triggerTop: number, triggerBottom: number, viewportHeight: number): boolean {\n return triggerTop >= viewportHeight - triggerBottom;\n}\n\n/** 向下弹时按 trigger 下方空间钳位菜单高度(top-anchored,不能复用向上弹的 fit)。 */\nexport function dmsBelowMaxHeight(triggerBottom: number, viewportHeight: number, cap: number): number {\n return Math.max(0, Math.min(cap, viewportHeight - triggerBottom - MENU_VIEWPORT_MARGIN));\n}\n\n/**\n * 菜单水平钳位:默认右锚定(right:0,与 seat 右缘对齐);seat 右缘左侧放不下\n * 整幅菜单(seat 靠输入区左下 + 窄窗口)、或 seat 右缘本身已越出视口右缘\n * (页面横向溢出/缩放时 rect.right 可大于 innerWidth,右锚定会把整幅菜单\n * 拖出屏外)时,返回钳到视口内的 left 值,调用方改用 left 锚定。\n * 返回 undefined 表示保持默认右锚定。\n * menuWidth 传菜单实际渲染宽度(offsetWidth),与 CSS `min(280px, 100vw-32px)` 解耦。\n *\n * **坐标系**:入参与返回值都是**视口坐标**。菜单是 .dms-root(position:\n * relative)内的 absolute 元素,left 的参考系是 root 的 padding box —— 调用方\n * 必须减去 root 的左缘再写进 style.left,否则窄窗口下菜单整体右移、横向溢出\n * 时整幅画到屏外。\n */\nexport function dmsMenuLeft(rootRight: number, menuWidth: number, viewportWidth: number, margin: number): number | undefined {\n if (rootRight - menuWidth >= margin && rootRight <= viewportWidth - margin) return void 0;\n return Math.min(\n Math.max(margin, rootRight - menuWidth),\n Math.max(margin, viewportWidth - margin - menuWidth),\n );\n}\n","/**\n * dsh-model-selector — 菜单 roving tabindex 的纯决策逻辑(node 可测)。\n *\n * 模型列表可达数百行,若每行都是 Tab 停靠点,Tab 键会陷入行海;roving\n * tabindex(WAI-ARIA menu 模式的通行做法)只让「活动行」留在 Tab 序里,\n * 其余行 tabindex=-1、仅方向键可达。本模块只负责一件事:给定当前渲染的\n * 行集合与选中行,算出 Tab 从搜索框进入列表时应停靠的默认行键。\n */\n/**\n * 模型行键的**唯一构造点**:菜单 DOM 的 `data-row-key`、roving 决策与行焦点\n * 回调必须同源。此前这三处各自拼模板串,一次局部改名就能让 dmsDefaultRowKey\n * 返回的键匹配不到任何行(Tab 落点静默失效),且测试拿自制副本验证不到。\n */\nexport function dmsRowKey(providerId: string, modelId: string): string {\n return `${providerId}\\u0000${modelId}`\n}\n\n/** 组头行键(分组视图里组头也参与方向键导航)。 */\nexport function dmsHeaderKey(providerId: string): string {\n return `header:${providerId}`\n}\n\nexport interface RovingRowSet {\n /** 搜索命中行键(渲染顺序);null = 非搜索态(分组视图)。 */\n hitKeys: readonly string[] | null\n /** 分组视图中可见(未折叠)的模型行键(渲染顺序)。 */\n modelKeys: readonly string[]\n /** 分组视图中全部组头键(组头始终渲染,即使组被折叠)。 */\n headerKeys: readonly string[]\n /** 当前选中行键;null = 尚无选中。 */\n selectedKey: string | null\n}\n\n/**\n * Tab 默认落点:搜索态取「选中行若在命中集内,否则首个命中」;分组态取\n * 「选中行若可见,否则首个可见模型行」;全部折叠(无可见模型行)时回退到\n * 首个组头。返回 null 表示当前没有任何可聚焦行(空态由菜单外的 role=status\n * 播报,列表容器不渲染)。\n */\nexport function dmsDefaultRowKey(set: RovingRowSet): string | null {\n if (set.hitKeys !== null) {\n if (set.hitKeys.length === 0) return null\n return set.selectedKey !== null && set.hitKeys.includes(set.selectedKey) ? set.selectedKey : set.hitKeys[0]!\n }\n if (set.modelKeys.length > 0) {\n return set.selectedKey !== null && set.modelKeys.includes(set.selectedKey) ? set.selectedKey : set.modelKeys[0]!\n }\n return set.headerKeys[0] ?? null\n}\n","/**\n * dsh-model-selector — 搜索匹配、高亮区间与列表空态的纯函数(node 可测)。\n *\n * 从 ModelSelect.tsx 原样搬出:归一化、haystack 构造、命中窗口截断、名称高亮\n * 区间与「无模型 / 无命中」的优先级都是用户可见且曾经出过错的分支,留在 .tsx\n * 里只能靠浏览器手测。\n */\nimport type { ModelProviderGroup, ModelSelection } from '@deepseek-ai/dsh-api-session-controller/types'\n\n/** 搜索索引条目:模型 + 折叠后的 haystack + 选中载荷。 */\nexport interface SearchChoice {\n group: ModelProviderGroup\n model: ModelProviderGroup['models'][number]\n /** dmsFold 之后的搜索文本(名称/描述/供应商名/id)。 */\n haystack: string\n selection: ModelSelection\n}\n\n/** 搜索命中的渲染条目。 */\nexport interface SearchHit {\n group: ModelProviderGroup\n model: ModelProviderGroup['models'][number]\n /** 名称内的命中区间(原串坐标);null = 命中落在描述/供应商/id 上,或无法安全定位。 */\n nameHit: { start: number; end: number } | null\n}\n\n/** 搜索结果:items 为窗口内条目(渲染用),total 为命中总数(播报/截断提示用)。 */\nexport interface SearchResult {\n items: SearchHit[]\n total: number\n}\n\n/**\n * 搜索归一化:NFKD 分解 → 去组合标记 → 小写。\n *\n * 比裸 `toLowerCase()` 宽容:`'İstanbul'` 能被 `'istanbul'` 命中(裸 toLowerCase\n * 把 U+0130 折成 `'i̇'` 两个码位,与 ASCII `'i'` 反而不匹配 → 漏检;`'café'` 同理)。\n * 注意它**可能改变码位长度**,所以不能用它的下标去切原串 —— 高亮区间另走\n * {@link dmsNameHit},只在长度不变时给区间。\n */\nexport function dmsFold(text: string): string {\n return text.normalize('NFKD').replace(/\\p{M}+/gu, '').toLowerCase()\n}\n\n/** 搜索词归一化(去首尾空白后折叠)。空串 = 非搜索态。 */\nexport function dmsNormalizeQuery(raw: string): string {\n return dmsFold(raw.trim())\n}\n\n/** 模型条目的搜索文本(haystack)。与 choices 构建处同源,避免两处漂移。 */\nexport function dmsHaystack(group: ModelProviderGroup, model: ModelProviderGroup['models'][number]): string {\n return dmsFold(`${model.name}\\n${model.description ?? ''}\\n${group.name}\\n${model.id}\\n${group.id}`)\n}\n\n/**\n * 名称内的命中区间:折叠后长度不变时用下标定位;长度变化(含会改变码位数的\n * 大小写/变音符映射)时返回 null —— 宁可不高亮,也不能用错位的区间去切原串\n * (那会把高亮画到别的字符上,甚至越界)。\n */\nexport function dmsNameHit(name: string, normalizedQuery: string): { start: number; end: number } | null {\n if (normalizedQuery === '') return null\n const folded = dmsFold(name)\n if (folded.length !== name.length) return null\n const at = folded.indexOf(normalizedQuery)\n return at < 0 ? null : { start: at, end: at + normalizedQuery.length }\n}\n\n/**\n * 搜索:遍历 choices 计数全部命中,只构造窗口内(前 limit 条)的渲染条目 ——\n * 宽泛关键词(单字母)命中数百条时,每次击键省掉数百次对象分配与名称匹配;\n * total 单独累计,播报与「仅显示前 N 条」提示仍然准确。\n */\nexport function dmsSearchHits(\n choices: readonly SearchChoice[],\n normalizedQuery: string,\n limit: number,\n): SearchResult | null {\n if (normalizedQuery === '') return null\n const items: SearchHit[] = []\n let total = 0\n for (const choice of choices) {\n if (!choice.haystack.includes(normalizedQuery)) continue\n total += 1\n if (items.length >= limit) continue\n items.push({ group: choice.group, model: choice.model, nameHit: dmsNameHit(choice.model.name, normalizedQuery) })\n }\n return { items, total }\n}\n\n/** 列表区空态:'noModels' = 目录里没有模型,'noHits' = 搜索无命中,null = 正常渲染列表。 */\nexport type MenuEmptyState = 'noModels' | 'noHits' | null\n\n/**\n * 空态判定:**无模型优先于无命中** —— 目录为空而用户又输了关键词时,真相是\n * 「没有可用的模型」(该引导用户去配置/加载目录),不是「没有匹配关键词的模型」\n * (那会引导用户去改关键词,而无论改什么都不会有结果)。\n */\nexport function dmsMenuEmptyState(input: {\n status: string\n choiceCount: number\n searching: boolean\n hitCount: number\n}): MenuEmptyState {\n if (input.status === 'ready' && input.choiceCount === 0) return 'noModels'\n if (input.searching && input.hitCount === 0) return 'noHits'\n return null\n}\n","/**\n * dsh-model-selector — 菜单键盘状态机的纯决策逻辑(node 可测)。\n *\n * 从 ModelSelect.tsx 的 onRootKeyDown 原样搬出(无 JSX、无 DOM、无模块级\n * 副作用):Escape 分层、搜索框内方向键进列表、Home/End 跳首末、Enter 选\n * 首个命中,都是分支密集且易被「顺手改坏」的用户可见逻辑,留在 .tsx 里\n * 只能靠浏览器手测。事件处理器只做 dispatch,分支判定全在这里。\n */\n\n/** 键盘事件归一化后的输入面。 */\nexport interface MenuKeyInput {\n /** event.key(按原样比较,不做大小写归一)。 */\n key: string\n /**\n * IME 组合输入中。调用方除 isComposing 外还要折算 Safari 的 keyCode 229:\n * WebKit 在 compositionend 之后补发的那次 Enter 带 isComposing === false,\n * 只有 keyCode 229 能识别 —— 否则中文候选上屏会直接选中首个命中、切换模型。\n */\n composing: boolean\n /** 菜单是否打开。 */\n open: boolean\n /** 焦点是否在搜索框内。 */\n fromSearch: boolean\n /** 焦点是否在菜单容器内(搜索框/行/滑杆;trigger 不算)。 */\n inMenu: boolean\n /** 焦点是否不在任何 input 上(range 滑杆也是 input,方向键归它调档)。 */\n onNonInput: boolean\n /** 搜索词是否非空。 */\n hasQuery: boolean\n /** 当前可聚焦的行数(搜索态 = 命中数,分组态 = 组头 + 可见模型行)。 */\n rowCount: number\n /** 搜索命中总数(Enter 选中首个命中的判据)。 */\n hitCount: number\n}\n\n/** 决策结果:调用方据此执行副作用(preventDefault 由调用方按需决定)。 */\nexport type MenuKeyAction =\n | { type: 'none' }\n | { type: 'clearQuery'; focusSearch: boolean }\n | { type: 'close' }\n | { type: 'moveFocus'; offset: number }\n | { type: 'focusEdge'; last: boolean }\n | { type: 'chooseFirstHit' }\n\n/**\n * 按键 → 动作。无行可去时对方向键/Home/End 返回 none 而不是吞键:列表为空\n * (加载中/无命中/全折叠)时搜索框里的上下键仍应有移光标语义,preventDefault\n * 由调用方在真正执行动作时才发。\n */\nexport function dmsMenuKeyAction(input: MenuKeyInput): MenuKeyAction {\n if (input.composing) return { type: 'none' }\n const { key, open, fromSearch, inMenu, onNonInput, hasQuery, rowCount, hitCount } = input\n if (key === 'Escape' && open) {\n // Escape 分层:查询非空且焦点在菜单内时第一按只清词(列表内清词会让命中行\n // 卸载,焦点还回搜索框;搜索框内清词焦点留在原处),再按一次才关菜单。\n // 焦点在 trigger 上(不在菜单内)时直接关 —— 「打开后马上关」的常规路径。\n if (hasQuery && inMenu) return { type: 'clearQuery', focusSearch: !fromSearch }\n return { type: 'close' }\n }\n if (!open) return { type: 'none' }\n if ((key === 'ArrowDown' || key === 'ArrowUp') && (onNonInput || fromSearch)) {\n // 搜索框自动聚焦后,方向键原本只在输入框内移光标(键盘导航死路);现在\n // 搜索框内的上下键也进入结果列表。滑杆(input)上的方向键不劫持 —— 那是调档键。\n if (rowCount === 0) return { type: 'none' }\n return { type: 'moveFocus', offset: key === 'ArrowDown' ? 1 : -1 }\n }\n if ((key === 'Home' || key === 'End') && onNonInput) {\n if (rowCount === 0) return { type: 'none' }\n return { type: 'focusEdge', last: key === 'End' }\n }\n // 只有搜索框内的 Enter 才选中第一个命中:焦点落在 effort 滑杆等其它控件时,\n // Enter 不应把用户的选择抢走。\n if (key === 'Enter' && fromSearch && hitCount > 0) return { type: 'chooseFirstHit' }\n return { type: 'none' }\n}\n\n/**\n * roving 环绕下标:焦点不在任何行上(activeIndex < 0)时向下从首行进、向上从\n * 末行进;已在某行上时按 offset 循环。length <= 0 返回 -1(无可去之处)。\n */\nexport function dmsNextRowIndex(activeIndex: number, offset: number, length: number): number {\n if (length <= 0) return -1\n const start = activeIndex < 0 ? (offset > 0 ? -1 : 0) : activeIndex\n return (start + offset + length) % length\n}\n","/**\n * dsh-model-selector — trigger 文案回退链的纯函数(node 可测)。\n *\n * 从 ModelSelect.tsx 原样搬出:四条近乎互相复制的表达式(可见文本 / title /\n * aria)含三层嵌套三目,是本组件里 drift 风险最高的一段,抽出来用假 t 直接测。\n */\nimport type { ModelSelection } from '@deepseek-ai/dsh-api-session-controller/types'\nimport type { Translate } from '@deepseek-ai/dsh-client-ui-slots'\n\n/**\n * 本模块用到的字典键。用具体联合而不是 string:调用处传进来的\n * `TranslateNS<'modelSelector'>` 参数逆变后要求这些键必须真的在字典里 ——\n * 字典改名会在编译期报错,而不是运行时渲染出原始 key。\n */\nexport type TriggerKey =\n | 'trigger.fallback'\n | 'trigger.loading'\n | 'trigger.selectAria'\n | 'trigger.aria'\n | 'trigger.ariaEffort'\n\n/** 文案输入:目录命中的名字优先,命中不到时回退到原始 id。 */\nexport interface TriggerCopyInput {\n current: ModelSelection | null\n /** 当前选中项在目录里的名称;不在目录中时为 undefined。 */\n modelName: string | undefined\n /** 当前选中项所属供应商名;不在目录中时为 undefined。 */\n providerName: string | undefined\n /** 当前生效的推理档位名;模型无推理或档位未解析时为 undefined。 */\n effortLabel: string | undefined\n /** current 为 null 且目录仍在加载。 */\n waiting: boolean\n}\n\n/** trigger 的四路文案。 */\nexport interface TriggerCopy {\n modelLabel: string\n providerLabel: string | undefined\n /** 可见文本(`·` 拼接 effort)。 */\n label: string\n /** title:供应商 · 可见文本。 */\n title: string\n /** aria-label:读屏可听到模型 + 档位。 */\n aria: string\n}\n\n/**\n * trigger 文案。目录成员资格只是参考(routable 契约):current 匹配不到任何\n * group 不代表没有选择 —— 此时显示 provider/model 原始 id,而不是「选择模型」。\n */\nexport function dmsTriggerCopy(input: TriggerCopyInput, t: Translate<TriggerKey>): TriggerCopy {\n const { current, modelName, providerName, effortLabel, waiting } = input\n const modelLabel = modelName\n ?? (waiting\n ? t('trigger.loading')\n : current === null ? t('trigger.fallback') : `${current.provider}/${current.model}`)\n // 供应商名只在目录命中时才有意义(原始 id 回退态不显示供应商行)。\n const providerLabel = modelName === undefined ? void 0 : providerName\n const label = effortLabel === undefined ? modelLabel : `${modelLabel} · ${effortLabel}`\n const title = providerLabel === undefined ? label : `${providerLabel} · ${label}`\n const named = providerLabel === undefined ? modelLabel : `${providerLabel} ${modelLabel}`\n // aria 的模型部分:目录命中时用「供应商 模型名」,回退态用原始 id(与可见文本同源)。\n const ariaModel = modelName === undefined ? (current === null ? null : `${current.provider}/${current.model}`) : named\n const aria = waiting\n ? t('trigger.loading')\n : ariaModel === null\n ? t('trigger.selectAria')\n : effortLabel === undefined\n ? t('trigger.aria', { model: ariaModel })\n : t('trigger.ariaEffort', { model: ariaModel, effort: effortLabel })\n return { modelLabel, providerLabel, label, title, aria }\n}\n","/**\n * ModelSelect: the enhanced composer model seat (`conversation.input.model`).\n *\n * A single-pane menu (simpler than the shipped two-level root menu): opening\n * the seat lands directly on the model list — search box + collapsible\n * provider groups — with the current model's effort slider pinned inline at\n * the bottom of the menu, so switching models and tuning reasoning effort\n * both happen in one surface without an intermediate page.\n *\n * Data and submission ride the SAME per-session directory the /model popup\n * shares (via `modelDirectories`), so a switch made here is what the popup\n * shows next and vice versa. Icons, Toast and the menu fit/dismiss hooks are\n * reused from @deepseek-ai/dsh-client-ui-primitives at runtime; colors\n * come from `--dsw-*` tokens in the injected stylesheet.\n */\nimport * as react from 'react'\nimport { zh as zhDict, en as enDict } from './locales.ts'\n// Runtime reuse of the official primitives (already an external in\n// tsdown.config.ts and a platform module in the web loader): Toast is the\n// sanctioned surface for a rejected selection, and the two hooks fit/dismiss\n// the open menu. The list itself stays hand-rolled — primitives' Menu renders\n// every entry (footer included) as a <button role=\"menuitem\">, which cannot\n// host the effort range input or the search field.\nimport { Toast, IconWarningOutlineRegular, IconChevronDownOutlineRegular, IconCheckOutlineRegular, IconCloseFillRegular, useAnchoredMaxHeight, useDismissOnOutsidePointer } from '@deepseek-ai/dsh-client-ui-primitives'\n\nexport { zhDict, enDict }\n// Type-only: official model-selection directory types (the enhanced seat's\n// data contract — same shared per-session directory the /model popup reads).\nimport type { ModelDirectoryState, ModelSelectInjected } from '@deepseek-ai/dsh-client-ui-model-selection/client'\nimport type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SnapshotStore } from '@deepseek-ai/dsh-client-store'\n// Effort helpers live in effort.ts (pure, no JSX/DOM) so node --test can cover them.\nimport { dmsChoosePlan, dmsClampIndex, dmsEffortIndex, dmsEffectiveEffortIndex, dmsEffortBusy, dmsRetainedEffortLabel, dmsShouldAdoptLateSuccess, dmsSliderLevels } from './effort.ts'\n// 辐射画布(绘制纯函数 + 动画循环 hook)与指针拖动状态机:从 EffortSlider\n// 组件内拆分独立成模块,组件本体只保留档位/提交/渲染三件事。\nimport { useEffortCanvas } from './effortCanvas.ts'\nimport { useEffortDrag } from './effortDrag.ts'\n// Menu direction/clamp helpers likewise (pure — the direction flip and the\n// below-clamp are user-visible and were previously only browser-testable).\nimport { MENU_MAX_HEIGHT, MENU_VIEWPORT_MARGIN, dmsMenuAbove, dmsBelowMaxHeight, dmsMenuLeft } from './menuFit.ts'\n// roving tabindex 的默认落点决策与行键构造(纯函数,node 可测——行键格式必须\n// 与渲染出的 data-row-key 同源,否则默认落点静默失效)。\nimport { dmsDefaultRowKey, dmsHeaderKey, dmsRowKey } from './roving.ts'\n// 搜索(归一化/命中窗口/高亮区间/空态)与键盘状态机:分支密集的用户可见逻辑,\n// 全部抽成纯函数模块(node 可测),事件处理器只做 dispatch。\nimport { dmsHaystack, dmsMenuEmptyState, dmsNormalizeQuery, dmsSearchHits } from './search.ts'\nimport type { SearchChoice, SearchResult } from './search.ts'\nimport { dmsMenuKeyAction, dmsNextRowIndex } from './keys.ts'\nimport { dmsTriggerCopy } from './copy.ts'\n// Type-only: the model catalog carrier types (moved here in dsh alpha.2).\nimport type { ModelSelection, ModelProviderGroup } from '@deepseek-ai/dsh-api-session-controller/types'\n\n/** Per-session model directory snapshot (official state shape). */\ntype DirectoryState = ModelDirectoryState\n\n/** 官方座位注入面的 select 结果(alpha.2 契约:RemoteResult,或 undefined = 该会话不可选)。 */\ntype SelectResult = Awaited<ReturnType<ModelSelectInjected['select']>>\n\ninterface EffortSliderProps {\n state: DirectoryState\n select: ModelSelectInjected['select']\n t: TranslateNS<'modelSelector'>\n /** 提交失败(超时/拒绝)时回调:让菜单把错误条归到「加载失败」之外(见 renderErrorStrip)。 */\n onSelectFailure: () => void\n}\n\n\n// 图标直接用 primitives 的组件(此前本仓自带一份逐字节相同的 SVG 副本);\n// 图标组件只接受 size/className,aria-hidden 由调用处的包装元素承载。\nconst IconChevronDown = <IconChevronDownOutlineRegular size={14} />;\nconst IconCheck = <IconCheckOutlineRegular size={14} />;\nconst IconClear = <IconCloseFillRegular size={14} />;\n/**\n* How long a successfully loaded directory snapshot is trusted before the\n* menu re-fetches it over RPC. The snapshot lives in the per-session store,\n* so reopening the menu within this window costs zero RPC and zero re-render.\n* 调法:调大 → 打开更省(RPC/重渲染都免),但目录侧改动要等窗口过期才可见;\n* 调小 → 目录更新更实时。目录内容由官方 modelDirectories 推送,本值只影响\n* 打开瞬间是否信任快照。\n*/\nconst DIRECTORY_STALE_MS = 3e4;\n/** 搜索命中渲染上限:宽泛关键词(如单字母)命中数百条时避免 DOM 爆炸。\n * 调法:调大 → 宽泛搜索看到更多命中(渲染与搜索成本线性上升);调小 → 更省。 */\nconst MAX_VISIBLE_HITS = 100;\n/** effort select RPC 的超时护栏:官方 select 无超时契约,RPC 永久挂起时\n * 必须释放滑杆的 committing 锁并回滚,否则滑杆被锁死到菜单关闭。\n * 调法:后端/网络慢时调大避免误回滚(滑杆锁死时间随之变长);调小更早释放交互。 */\nconst EFFORT_COMMIT_TIMEOUT_MS = 12e3;\n// ── 推理强度滑块(移植自 dsh-reasoning-effort:辐射特效 + 档位随模型自动适配)──\n// 绘制与动画循环见 ./effortCanvas.ts(dmsDrawRadiation / useEffortCanvas),\n// 指针拖动状态机见 ./effortDrag.ts(useEffortDrag);本组件只做档位状态、\n// 提交/回滚与渲染。\nexport const EffortSlider = react.memo(function EffortSlider({ state, select, t, onSelectFailure }: EffortSliderProps) {\n\tconst levels = dmsSliderLevels(state);\n\t// 惰性初始化到当前生效档:useState(\"\")/0 会让菜单打开的第一帧档位名为空、\n\t// 进度闪 0%,随后才被同步 effect 纠正(首帧即正确,无闪烁)。\n\tconst [effort, setEffort] = react.useState(() => levels[dmsEffectiveEffortIndex(levels, state)]?.id ?? \"\");\n\tconst [preview, setPreview] = react.useState(() => dmsEffectiveEffortIndex(levels, state));\n\tconst [committing, setCommitting] = react.useState(false);\n\tconst [localError, setLocalError] = react.useState<string | null>(null);\n\t// 最新 state 经 ref 读取:迟到 settle 的判定要看「此刻」的生效模型,而不是\n\t// 提交发起时闭包里捕获的那份(见 dmsShouldAdoptLateSuccess 的 sameModel)。\n\tconst stateRef = react.useRef(state);\n\tstateRef.current = state;\n\tconst canvasRef = react.useRef<HTMLCanvasElement | null>(null);\n\tconst inputRef = react.useRef<HTMLInputElement | null>(null);\n\tconst committedRef = react.useRef(\"\");\n\tconst committingRef = react.useRef(false);\n\tconst previewRef = react.useRef(0);\n\tconst draggingRef = react.useRef(false);\n\t// 提交超时定时器句柄 + 挂载标志:菜单关闭(EffortSlider 卸载)时由 cleanup\n\t// clearTimeout 并置 unmounted——迟到成功采纳/超时回滚的 setState 只在挂载期\n\t// 跑,卸载后不再触碰 React 状态,定时器也不必在卸载后继续空转。\n\tconst commitTimerRef = react.useRef<number | null>(null);\n\tconst mountedRef = react.useRef(false);\n\t// 提交纪元:单调递增,迟到采纳判据靠它区分「无新提交」与「新提交失败回滚」。\n\tconst commitEpochRef = react.useRef(0);\n\t// 辐射状态由组件持有:preview 变化/拖动时更新 target/dragging 驱动重绘;\n\t// progress 惰性对齐当前档位(打开菜单首帧即正确,无中间态缓动)。useRef\n\t// 初始化表达式每次渲染都会求值但仅首帧生效,这里取首次渲染的档位作起点。\n\tconst radiationRef = react.useRef<{ progress: number; dragging: boolean; target?: number }>({\n\t\tprogress: levels.length >= 2 ? dmsEffectiveEffortIndex(levels, state) / (levels.length - 1) : 0.5,\n\t\tdragging: false,\n\t});\n\tconst redrawRef = react.useRef<(() => void) | null>(null);\n\tconst available = state.current !== null && levels.length >= 2;\n\tconst busy = dmsEffortBusy(committing, state.status);\n\tconst error = localError;\n\treact.useEffect(() => {\n\t\tif (!available || committingRef.current || draggingRef.current) return;\n\t\tconst index = dmsEffectiveEffortIndex(levels, state);\n\t\tconst next = levels[index]?.id ?? \"\";\n\t\tcommittedRef.current = next;\n\t\tpreviewRef.current = index;\n\t\tsetEffort(next);\n\t\tsetPreview(index);\n\t\tsetLocalError(null);\n\t\t// levels 进依赖:同一模型、档位个数不变但档位 id 变了(适配器/目录刷新把\n\t\t// high 换成 xhigh)时也要重同步,否则读数落到原始 id、preview 与新档位错位。\n\t}, [available, levels, state.current?.provider, state.current?.model, state.current?.reasoningEffort]);\n\treact.useEffect(() => {\n\t\tpreviewRef.current = preview;\n\t\tradiationRef.current.target = levels.length >= 2 ? preview / (levels.length - 1) : 0.5;\n\t\tredrawRef.current?.();\n\t}, [preview, levels.length]);\n\tconst redraw = useEffortCanvas(canvasRef, radiationRef);\n\treact.useEffect(() => {\n\t\tredrawRef.current = redraw;\n\t\treturn () => { redrawRef.current = null; };\n\t}, [redraw]);\n\t// 卸载清理:迟到的 select settle 不再 setState(迟到成功采纳/超时回滚的\n\t// setState 都经 mountedRef 守卫);未决的超时定时器直接清除——组件已卸载,\n\t// 回滚/采纳都无人可见,定时器没有存在的意义。\n\treact.useEffect(() => {\n\t\tmountedRef.current = true;\n\t\treturn () => {\n\t\t\tmountedRef.current = false;\n\t\t\tif (commitTimerRef.current !== null) {\n\t\t\t\twindow.clearTimeout(commitTimerRef.current);\n\t\t\t\tcommitTimerRef.current = null;\n\t\t\t}\n\t\t};\n\t}, []);\n\tconst showPointerPreview = react.useCallback((raw: number): void => {\n\t\tpreviewRef.current = raw;\n\t\tsetPreview(raw);\n\t\tsetEffort(levels[dmsClampIndex(raw, levels.length)]?.id ?? \"\");\n\t}, [levels]);\n\tconst rollback = react.useCallback(() => {\n\t\tconst previous = committedRef.current;\n\t\tpreviewRef.current = Math.max(0, dmsEffortIndex(levels, previous));\n\t\tsetEffort(previous);\n\t\tsetPreview(Math.max(0, dmsEffortIndex(levels, previous)));\n\t}, [levels]);\n\tconst commit = react.useCallback(async (raw: number): Promise<void> => {\n\t\tif (dmsEffortBusy(committingRef.current, state.status, state.pending)) return;\n\t\t// 先按钳位后的档位判断是否无操作:落回已提交的同一档(含拖动归位、键盘\n\t\t// 重复按当前档)时,不发多余 RPC,也不闪本地 busy 灰——原实现把这次早退\n\t\t// 放在 setCommitting(true)/乐观 setState 之后,同一档提交仍会闪一次。\n\t\t// levels 非空时 clamp 保证 index ∈ [0, count-1],next 不可能为 undefined。\n\t\tconst index = dmsClampIndex(raw, levels.length);\n\t\tconst next = levels[index]?.id;\n\t\tif (next === void 0) return;\n\t\t// 落回已提交的同一档(拖动归位、键盘重复按当前档):不发多余 RPC,也不闪\n\t\t// 本地 busy 灰——但必须把 preview 归一化回整档。raw 是未取整的分数(见\n\t\t// dmsPointerRaw),不归一化会让旋钮/进度条/画布停在两档之间,而同步 effect\n\t\t// 因依赖未变不会重跑纠正它(此前只有指针路径会留下这个残影)。\n\t\tif (next === committedRef.current) {\n\t\t\tpreviewRef.current = index;\n\t\t\tsetPreview(index);\n\t\t\tsetEffort(next);\n\t\t\treturn;\n\t\t}\n\t\tcommittingRef.current = true;\n\t\tconst previous = committedRef.current;\n\t\t// 提交纪元:每次发起新提交单调递增。迟到采纳判据用它区分「无新提交」与\n\t\t// 「新提交失败回滚」——两者 committedRef 都会回到 previous,值比较无法\n\t\t// 区分,纪元未变才说明迟到结果仍属当前唯一意图链。\n\t\tcommitEpochRef.current += 1;\n\t\tconst epochAtCommit = commitEpochRef.current;\n\t\t// 注意:这里不重置 dragging——commit 的唯二入口是拖动结束(stopDragging\n\t\t// 已先 setDragging(false))与键盘(本就非拖拽),无需防御性复位。\n\t\tsetCommitting(true);\n\t\tsetLocalError(null);\n\t\tpreviewRef.current = index;\n\t\tsetPreview(index);\n\t\tsetEffort(next);\n\t\ttry {\n\t\t\tconst current = state.current;\n\t\t\tif (current === null) throw new Error(t(\"empty.efforts\"));\n\t\t\t// 提交时的模型身份:迟到采纳必须确认生效模型仍是这一个 —— next/index 是在\n\t\t\t// 这份档位表上算出来的,模型被外部改写后写回 UI 会显示一个后端并未生效的档位。\n\t\t\tconst modelKeyAtCommit = dmsRowKey(current.provider, current.model);\n\t\t\t// 超时护栏:官方 select 无超时契约,把它包进带 12s 定时的 Promise——\n\t\t\t// 超时即视为失败(回滚 + 播报),并释放 committing 锁。select 迟到的\n\t\t\t// resolve 不直接丢弃:若它确认了新档位且自回滚以来没有新提交改写\n\t\t\t// committedRef(回滚态仍对应本次提交链),把 UI/committedRef 同步回\n\t\t\t// 实际已生效的档位;迟到的 reject 保持回滚态。timer 在 select 先完成\n\t\t\t// 时清除,不泄漏空转定时器。\n\t\t\tconst result = await new Promise<SelectResult>((resolve, reject) => {\n\t\t\t\tlet settled = false;\n\t\t\t\tconst timer = window.setTimeout(() => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tcommitTimerRef.current = null;\n\t\t\t\t\treject(new Error(t(\"effort.timeout\")));\n\t\t\t\t}, EFFORT_COMMIT_TIMEOUT_MS);\n\t\t\t\t// 定时器句柄进 ref:菜单关闭(本组件卸载)时由 cleanup 直接\n\t\t\t\t// clearTimeout,不再依赖 Promise 闭包把定时器活到 select 迟到 settle。\n\t\t\t\tcommitTimerRef.current = timer;\n\t\t\t\tselect({ provider: current.provider, model: current.model, reasoningEffort: next }).then(\n\t\t\t\t\t(result) => {\n\t\t\t\t\t\tif (settled) {\n\t\t\t\t\t\t\t// 迟到成功且实际生效:committedRef 还是回滚前的 previous\n\t\t\t\t\t\t\t// 且提交纪元未推进(期间没有新的提交/拖动改写它),说明\n\t\t\t\t\t\t\t// 回滚态对应用户当前唯一意图链,后端已确认新档位——补一次\n\t\t\t\t\t\t\t// UI 同步,消除「UI 回滚、后端已改档」的错位;否则以新\n\t\t\t\t\t\t\t// 操作链为准不覆盖(新提交失败回滚后 committedRef 值与\n\t\t\t\t\t\t\t// 上一轮相同,纪元未推进才说明没有新提交)。\n\t\t\t\t\t\t\t// 判定收敛到 dmsShouldAdoptLateSuccess(effort.ts,node 可测)。\n\t\t\t\t\t\t\tconst latest = stateRef.current.current;\n\t\t\t\t\t\t\tconst sameModel = latest !== null && dmsRowKey(latest.provider, latest.model) === modelKeyAtCommit;\n\t\t\t\t\t\t\tif (result?.ok === true && mountedRef.current && dmsShouldAdoptLateSuccess(committedRef.current, previous, draggingRef.current, committingRef.current, commitEpochRef.current, epochAtCommit, sameModel)) {\n\t\t\t\t\t\t\t\tcommittedRef.current = next;\n\t\t\t\t\t\t\t\tpreviewRef.current = index;\n\t\t\t\t\t\t\t\tsetEffort(next);\n\t\t\t\t\t\t\t\tsetPreview(index);\n\t\t\t\t\t\t\t\tsetLocalError(null);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\twindow.clearTimeout(timer);\n\t\t\t\t\t\tcommitTimerRef.current = null;\n\t\t\t\t\t\tresolve(result);\n\t\t\t\t\t},\n\t\t\t\t\t(cause: unknown) => {\n\t\t\t\t\t\tif (settled) return;\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\twindow.clearTimeout(timer);\n\t\t\t\t\t\tcommitTimerRef.current = null;\n\t\t\t\t\t\treject(cause);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t});\n\t\t\tif (result === undefined || !result.ok) {\n\t\t\t\tif (result !== undefined) console.warn('[dsh-model-selector] effort select failed:', result.error);\n\t\t\t\tthrow new Error(t(\"effort.failed\"));\n\t\t\t}\n\t\t\tif (mountedRef.current) {\n\t\t\t\tcommittedRef.current = next;\n\t\t\t\tpreviewRef.current = index;\n\t\t\t\tsetEffort(next);\n\t\t\t\tsetPreview(index);\n\t\t\t}\n\t\t} catch (cause) {\n\t\t\t// 迟到超时/失败不得回滚新意图链:提交 B 快速成功后,提交 A 的 12s 定时器\n\t\t\t// 才到点 reject——此时 committedRef 已是 B 的结果,若按 A 的 previous\n\t\t\t// 回滚就会把用户已成功的 B 覆盖掉(dmsShouldAdoptLateSuccess 只保护\n\t\t\t// 「迟到成功」路径,回滚路径必须自己查纪元闸)。\n\t\t\tif (commitEpochRef.current === epochAtCommit) {\n\t\t\t\t// 通知菜单把错误归到「档位切换」而不是「目录加载」:否则 effort 提交失败\n\t\t\t\t// 会点亮加载错误条 + 无用的「重新加载」按钮(见 renderErrorStrip)。\n\t\t\t\tonSelectFailure();\n\t\t\t\tconst restore = Math.max(0, dmsEffortIndex(levels, previous));\n\t\t\t\tcommittedRef.current = previous;\n\t\t\t\tpreviewRef.current = restore;\n\t\t\t\tif (mountedRef.current) {\n\t\t\t\t\tsetEffort(previous);\n\t\t\t\t\tsetPreview(restore);\n\t\t\t\t\tsetLocalError(cause instanceof Error ? cause.message : String(cause));\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\t// 同上:迟到 settled 的 finally 不得释放新提交的 committing 锁(否则\n\t\t\t// 新提交仍在途,UI 已显示可操作,连点可发起第三条提交)。\n\t\t\tif (commitEpochRef.current === epochAtCommit) {\n\t\t\t\tcommittingRef.current = false;\n\t\t\t\tif (mountedRef.current) setCommitting(false);\n\t\t\t}\n\t\t}\n\t}, [levels, select, state, t, onSelectFailure]);\n\t// 指针拖动状态机(pointer 生命周期 + window 兜底监听)在 effortDrag.ts;\n\t// commit/rollback/showPointerPreview 作回调外接——hook 经 callbacksRef 持有\n\t// 最新闭包,事件触发时取到当前渲染的函数。\n\tconst drag = useEffortDrag({\n\t\tlevelCount: () => levels.length,\n\t\tcanStart: () => !busy,\n\t\tonPreview: showPointerPreview,\n\t\tonCommit: (raw) => void commit(raw),\n\t\tonRollback: rollback,\n\t\tinputRef,\n\t\tdraggingRef,\n\t});\n\tconst { dragging } = drag;\n\t// 拖动态同步到辐射状态与画布(preview effect 已处理 target,这里只切 dragging 速度)。\n\treact.useEffect(() => {\n\t\tradiationRef.current.dragging = dragging;\n\t\tredrawRef.current?.();\n\t}, [dragging]);\n\tconst onKeyDown = (event: react.KeyboardEvent<HTMLInputElement>): void => {\n\t\tconst current = dmsClampIndex(Number(event.currentTarget.value), levels.length);\n\t\tlet target;\n\t\tif (event.key === \"ArrowLeft\" || event.key === \"ArrowDown\" || event.key === \"PageDown\") {\n\t\t\ttarget = Math.max(0, current - 1);\n\t\t} else if (event.key === \"ArrowRight\" || event.key === \"ArrowUp\" || event.key === \"PageUp\") {\n\t\t\ttarget = Math.min(levels.length - 1, current + 1);\n\t\t} else if (event.key === \"Home\") {\n\t\t\ttarget = 0;\n\t\t} else if (event.key === \"End\") {\n\t\t\ttarget = levels.length - 1;\n\t\t}\n\t\tif (target === void 0) return;\n\t\t// 忙态(自身提交中/共享目录 select 在途)吞掉按键:必须先 preventDefault,\n\t\t// 否则 range input 的原生步进会改 DOM value,与受控值漂移。\n\t\tevent.preventDefault();\n\t\tif (busy) return;\n\t\tvoid commit(target);\n\t};\n\tif (!available) return null;\n\tconst count = levels.length;\n\tconst effortName = levels[dmsEffortIndex(levels, effort)]?.name ?? effort;\n\tconst effortDesc = levels[dmsEffortIndex(levels, effort)]?.description;\n\tconst progress = preview / (count - 1) * 100;\n\tconst style = { \"--dms-progress\": `${progress}%` } as react.CSSProperties;\n\treturn (\n\t\t<div\n\t\t\tclassName={`dms-effort${dragging ? \" is-dragging\" : \"\"}${busy ? \" is-busy\" : \"\"}${error === null ? \"\" : \" is-error\"}`}\n\t\t>\n\t\t\t<div\n\t\t\t\tclassName=\"dms-effort-slider\"\n\t\t\t\tstyle={style}\n\t\t\t>\n\t\t\t\t<div className=\"dms-effort-track\" aria-hidden=\"true\" />\n\t\t\t\t<div className=\"dms-effort-fx\" aria-hidden=\"true\">\n\t\t\t\t\t<canvas ref={canvasRef} className=\"dms-effort-canvas\" />\n\t\t\t\t\t<span className=\"dms-effort-flare\" />\n\t\t\t\t</div>\n\t\t\t\t<input\n\t\t\t\t\tref={inputRef}\n\t\t\t\t\tclassName=\"dms-effort-input\"\n\t\t\t\t\ttype=\"range\"\n\t\t\t\t\tmin=\"0\"\n\t\t\t\t\tmax={count - 1}\n\t\t\t\t\tstep=\"0.01\"\n\t\t\t\t\tvalue={preview}\n\t\t\t\t\taria-disabled={busy}\n\t\t\t\t\taria-label={t(\"menu.effort\")}\n\t\t\t\t\taria-valuetext={effortName}\n\t\t\t\t\tonChange={(event: react.ChangeEvent<HTMLInputElement>) => {\n\t\t\t\t\t\tconst raw = Number(event.currentTarget.value);\n\t\t\t\t\t\tshowPointerPreview(raw);\n\t\t\t\t\t}}\n\t\t\t\t\tonPointerDown={drag.handlers.onPointerDown}\n\t\t\t\t\tonPointerMove={drag.handlers.onPointerMove}\n\t\t\t\t\tonPointerUp={drag.handlers.onPointerUp}\n\t\t\t\t\tonPointerCancel={drag.handlers.onPointerCancel}\n\t\t\t\t\tonBlur={drag.handlers.onBlur}\n\t\t\t\t\tonKeyDown={onKeyDown}\n\t\t\t\t/>\n\t\t\t\t<span className=\"dms-effort-knob\" aria-hidden=\"true\" />\n\t\t\t</div>\n\t\t\t<span className=\"dms-effort-value\">{effortName}</span>\n\t\t\t{effortDesc === undefined ? null : <span className=\"dms-effort-desc\">{effortDesc}</span>}\n\t\t\t{/* 错误:视觉隐藏的 role=status 供读屏播报,可见错误块同文案。两处容器都\n\t\t\t 常驻(空时 :empty 隐藏)—— 插入式 live region 的首次播报不可靠。 */}\n\t\t\t<span className=\"dms-sr\" role=\"status\">{error ?? ''}</span>\n\t\t\t<div className=\"dms-effort-error\">{error ?? ''}</div>\n\t\t</div>\n\t);\n});\n\ninterface ModelOptionProps {\n\tgroup: ModelProviderGroup\n\tmodel: ModelProviderGroup['models'][number]\n\tshowProvider: boolean\n\tselected: boolean\n\tbusy: boolean\n\t/** 键盘导航用的稳定行标识:菜单内 `[data-row-key]` 查询即按 DOM 顺序聚焦。 */\n\trowKey: string\n\t/** roving tabindex:true = 当前活动行(唯一可 Tab 到的模型行,其余 -1)。 */\n\tactive: boolean\n\t/** 搜索态(结果列表是 listbox):行用 option + aria-selected;分组态用 menuitemradio。 */\n\tsearchMode: boolean\n\t/** 行聚焦回调(箭头导航/点击/程序化 focus 都经它同步活动行键)。 */\n\tonRowFocus: (key: string) => void\n\t/** 搜索命中且命中落在名称内时的片段区间(高亮);undefined/null = 不标。 */\n\tnameHit?: { start: number; end: number } | null\n\t/** 搜索态的集合规模(总命中数)与该行 1-based 序号:截断到 MAX_VISIBLE_HITS\n\t * 时 AT 需要知道「这是 N 条中的第几条」。分组态不传(menu 无 setsize 语义)。 */\n\tsetsize?: number | undefined\n\tposinset?: number | undefined\n\tt: TranslateNS<'modelSelector'>\n\tonChoose: (selection: ModelSelection) => void\n}\n/**\n * 菜单里的一行模型。独立成 memo 组件:目录可能数百行,搜索输入每击键都会\n * 重建菜单内容,行 props(group/model 引用、selected/busy/rowKey、稳定的\n * onChoose)稳定时 React 直接跳过 reconcile,只重渲染真正变化的那行。\n *\n * 自定义比较器(默认浅比较的补充):nameHit 是每次击键新建的对象(引用必变),\n * 但 100 条命中里绝大多数命中区间不变——按值比较 start/end,值相同即跳过\n * 重渲染,搜索才不卡顿。\n */\nconst ModelOption = react.memo(function ModelOption({ group, model, showProvider, selected, busy, rowKey, active, searchMode, onRowFocus, nameHit, setsize, posinset, t, onChoose }: ModelOptionProps) {\n\tconst hit = nameHit === undefined || nameHit === null || nameHit.start === nameHit.end\n\t\t? null\n\t\t: [\n\t\t\tmodel.name.slice(0, nameHit.start),\n\t\t\tmodel.name.slice(nameHit.start, nameHit.end),\n\t\t\tmodel.name.slice(nameHit.end),\n\t\t];\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\t// 搜索态的结果容器是 listbox(搜索框是 combobox,见 .dms-searchInput),\n\t\t\t// 行必须用 option + aria-selected;分组态是 ARIA menu,行用 menuitemradio\n\t\t\t// + aria-checked。两套语义不能混用(menuitemradio 在 listbox 里无效)。\n\t\t\trole={searchMode ? \"option\" : \"menuitemradio\"}\n\t\t\taria-checked={searchMode ? undefined : selected}\n\t\t\taria-selected={searchMode ? selected : undefined}\n\t\t\t// 搜索态给行一个 id:combobox 的 aria-activedescendant 与「这是 N 条中的\n\t\t\t// 第几条」播报都要求 option 可被引用。rowKey 内含 '\\u0000' 分隔符\n\t\t\t// (dmsRowKey),作 HTML id 合法但不可读,编码成可读形式。\n\t\t\tid={searchMode ? `dms-opt-${encodeURIComponent(rowKey)}` : undefined}\n\t\t\t// 结果被截断到 MAX_VISIBLE_HITS 时,AT 需要知道集合规模与本行位置,\n\t\t\t// 否则用户无法感知「只显示了前 100 条」。分组态是 ARIA menu,无 setsize 语义。\n\t\t\taria-setsize={searchMode && setsize !== undefined && setsize > posinset! ? setsize : undefined}\n\t\t\taria-posinset={searchMode && posinset !== undefined ? posinset : undefined}\n\t\t\tdata-row-key={rowKey}\n\t\t\ttabIndex={active ? 0 : -1}\n\t\t\tclassName={`dms-model-option${selected ? \" dms-model-optionSelected\" : \"\"}`}\n\t\t\ttitle={model.description === undefined ? model.name : `${model.name} — ${model.description}`}\n\t\t\taria-disabled={busy}\n\t\t\tonFocus={() => onRowFocus(rowKey)}\n\t\t\tonClick={() => onChoose({ provider: group.id, model: model.id })}\n\t\t>\n\t\t\t<span className=\"dms-model-option-copy\">\n\t\t\t\t<span className=\"dms-nameRow\">\n\t\t\t\t\t<span className=\"dms-model-option-name\">\n\t\t\t\t\t\t{hit === null ? model.name : <>\n\t\t\t\t\t\t\t{hit[0]}<span className=\"dms-hit\">{hit[1]}</span>{hit[2]}\n\t\t\t\t\t\t</>}\n\t\t\t\t\t</span>\n\t\t\t\t\t{model.reasoning !== undefined && <span className=\"dms-badge\" title={t(\"badge.reasoningHint\")}>{t(\"badge.reasoning\")}</span>}\n\t\t\t\t</span>\n\t\t\t\t{model.description !== undefined && <span className=\"dms-model-option-desc\">{model.description}</span>}\n\t\t\t\t{showProvider && <span className=\"dms-model-option-provider\">{group.name}</span>}\n\t\t\t</span>\n\t\t\t<span className=\"dms-model-check\" aria-hidden=\"true\">\n\t\t\t\t{selected ? IconCheck : null}\n\t\t\t</span>\n\t\t</button>\n\t);\n}, (prev, next) => {\n\tif (prev.group !== next.group) return false;\n\tif (prev.model !== next.model) return false;\n\tif (prev.showProvider !== next.showProvider) return false;\n\tif (prev.selected !== next.selected) return false;\n\tif (prev.busy !== next.busy) return false;\n\tif (prev.rowKey !== next.rowKey) return false;\n\tif (prev.active !== next.active) return false;\n\tif (prev.searchMode !== next.searchMode) return false;\n\tif (prev.onRowFocus !== next.onRowFocus) return false;\n\tif (prev.t !== next.t) return false;\n\tif (prev.onChoose !== next.onChoose) return false;\n\t// 截断规模/序号:搜索词变化引起 total 或排序变化时必须重渲染。\n\tif (prev.setsize !== next.setsize) return false;\n\tif (prev.posinset !== next.posinset) return false;\n\t// 引用相同(含都是 null/undefined)即视为未变化;否则按值比较命中区间。\n\tif (prev.nameHit === next.nameHit) return true;\n\tif (prev.nameHit === null || prev.nameHit === void 0) return false;\n\tif (next.nameHit === null || next.nameHit === void 0) return false;\n\treturn prev.nameHit.start === next.nameHit.start && prev.nameHit.end === next.nameHit.end;\n});\n\ninterface ModelSelectProps {\n locked: boolean\n available: boolean\n directory: SnapshotStore<ModelDirectoryState>\n load: () => void\n select: ModelSelectInjected['select']\n t: TranslateNS<'modelSelector'>\n}\nexport function ModelSelect({ locked, available, directory, load, select, t }: ModelSelectProps) {\n\tconst state = react.useSyncExternalStore(directory.subscribe, directory.getSnapshot);\n\tconst [open, setOpen] = react.useState(false);\n\t// 弹出方向自适应:菜单向上弹出(bottom 定位),但 trigger 距视口顶部\n\t// 空间不足时(新会话/页面中部)改为向下弹出,避免搜索框与选项溢出视口。\n\tconst [menuAbove, setMenuAbove] = react.useState(true);\n\tconst [query, setQuery] = react.useState(\"\");\n\tconst [collapsed, setCollapsed] = react.useState<Set<string>>(() => new Set());\n\tconst [notice, setNotice] = react.useState<string | null>(null);\n\t// roving tabindex 的活动行键:全列表只有这一行 tabindex=0(其余 -1,仅方向键\n\t// 可达),Tab 从搜索框进列表落在它上面。行焦点移动经 onRowFocus 更新;行集合\n\t// 变化(打开/搜索/折叠/选中迁移)时由下方 effect 重置到默认落点。\n\tconst [activeRowKey, setActiveRowKey] = react.useState<string | null>(null);\n\tconst onRowFocus = react.useCallback((key: string): void => {\n\t\tsetActiveRowKey(key);\n\t}, []);\n\t// 菜单内 notice 会随 close() 一起清掉,失败原因必须落在菜单外的瞬时横幅上\n\t// (官方 seat 正是用 Toast 播报 select 拒绝)。seq 递增让同一段文案可重播。\n\tconst [toast, setToast] = react.useState<{ seq: number; text: string; failed: boolean } | null>(null);\n\tconst toastSeqRef = react.useRef(0);\n\tconst showToast = react.useCallback((text: string, failed = true): void => {\n\t\ttoastSeqRef.current += 1;\n\t\tsetToast({ seq: toastSeqRef.current, text, failed });\n\t}, []);\n\t// 稳定引用:这两个回调作为 props 传给 EffortSlider / Toast。内联箭头会让\n\t// 子组件的 effect 依赖每次渲染都变(Toast 的卸载定时器被重启、滑杆 memo 失效)。\n\t// markSelectFailure 把错误来源标成「切换」:否则 effort 提交失败会点亮\n\t// 菜单里的「目录加载失败」条 + 无用的「重新加载」按钮(见 renderErrorStrip)。\n\tconst markSelectFailure = react.useCallback((): void => {\n\t\tlastActionRef.current = \"select\";\n\t}, []);\n\tconst dismissToast = react.useCallback((): void => {\n\t\tsetToast(null);\n\t}, []);\n\tconst lastActionRef = react.useRef<\"load\" | \"select\">(\"load\");\n\tconst rootRef = react.useRef<HTMLDivElement | null>(null);\n\tconst triggerRef = react.useRef<HTMLButtonElement | null>(null);\n\tconst searchRef = react.useRef<HTMLInputElement | null>(null);\n\tconst lastLoadRef = react.useRef(0);\n\tconst id = react.useId();\n\t// 模型目录稳定时,haystack 索引不随 select/状态抖动重建:仅在 groups 引用\n\t// 真正变化(新 load 结果)时重建 400+ 条搜索索引。useMemo 保证 group 引用\n\t// 未变时 choices 引用稳定,currentChoice/hits 的 useMemo 依赖它也不会抖动。\n\tconst choices = react.useMemo<readonly SearchChoice[]>(\n\t\t() => state.groups.flatMap((group) => group.models.map((model) => ({\n\t\t\tgroup,\n\t\t\tmodel,\n\t\t\thaystack: dmsHaystack(group, model),\n\t\t\tselection: {\n\t\t\t\tprovider: group.id,\n\t\t\t\tmodel: model.id,\n\t\t\t\t...model.reasoning?.defaultEffort === void 0 ? {} : { reasoningEffort: model.reasoning.defaultEffort }\n\t\t\t}\n\t\t}))),\n\t\t[state.groups],\n\t);\n\tconst currentChoice = react.useMemo(() => {\n\t\tconst current = state.current;\n\t\tif (current === null) return void 0;\n\t\treturn choices.find((c) => c.selection.provider === current.provider && c.selection.model === current.model);\n\t}, [choices, state.current]);\n\tconst reasoning = currentChoice?.model.reasoning;\n\tconst effectiveEffort = state.current?.reasoningEffort ?? reasoning?.defaultEffort;\n\t// 已选模型离开 catalog 时(rc.2 官方目录「保留态」语义:current 仍留着,\n\t// catalog 里已查不到),effort 文案回落到目录的 retainedEffort——官方\n\t// ModelSelect 同规则,否则 trigger 会莫名丢掉档位名。\n\tconst effortLabel = reasoning === void 0\n\t\t? dmsRetainedEffortLabel(state)\n\t\t: effectiveEffort === void 0 ? t(\"effort.providerDefault\") : reasoning.efforts.find((level) => level.id === effectiveEffort)?.name ?? effectiveEffort;\n\tconst busy = dmsEffortBusy(false, state.status, state.pending);\n\t// choose 的最新输入面经 ref 读取:choose 因此不随 busy/current/choices/open 抖动\n\t// —— 它作为行 props 传进 ModelOption,引用一变数百行的自定义 memo 就全线失效。\n\tconst chooseInputRef = react.useRef({ busy, current: state.current, choices, open });\n\tchooseInputRef.current = { busy, current: state.current, choices, open };\n\tconst normalized = dmsNormalizeQuery(query);\n\t// 归一化可能改变码位长度(见 dmsFold),因此匹配与高亮全部走 search.ts 的\n\t// 纯函数:haystack 已折叠、高亮只在长度不变时给区间,不再用小写串下标切原串。\n\tconst hits = react.useMemo<SearchResult | null>(\n\t\t() => dmsSearchHits(choices, normalized, MAX_VISIBLE_HITS),\n\t\t[choices, normalized],\n\t);\n\t// roving tabindex 的默认落点(Tab 从搜索框进列表的停靠行):选中行优先、\n\t// 其次首个可见模型行、全折叠时回退首个组头。纯决策在 roving.ts(node 可测),\n\t// 行键构造同样来自那里 —— 渲染出的 data-row-key 与这里的键必须同源。\n\tconst visibleModelKeys = react.useMemo(\n\t\t() => state.groups.flatMap((g) => (collapsed.has(g.id) ? [] : g.models.map((m) => dmsRowKey(g.id, m.id)))),\n\t\t[state.groups, collapsed],\n\t);\n\tconst defaultRowKey = react.useMemo(() => dmsDefaultRowKey({\n\t\thitKeys: hits === null ? null : hits.items.map((h) => dmsRowKey(h.group.id, h.model.id)),\n\t\tmodelKeys: visibleModelKeys,\n\t\theaderKeys: state.groups.map((g) => dmsHeaderKey(g.id)),\n\t\tselectedKey: state.current === null ? null : dmsRowKey(state.current.provider, state.current.model),\n\t}), [hits, visibleModelKeys, state.groups, state.current]);\n\t// 行集合/选中变化时把 tabindex=0 的默认行同步到当前渲染集;菜单关闭清空。\n\t// 焦点已在行上(onRowFocus 改写过 activeRowKey)时,只要默认落点没变本\n\t// effect 不重跑,活动行保持用户最后聚焦的那行。\n\treact.useEffect(() => {\n\t\tif (!open) {\n\t\t\tsetActiveRowKey(null);\n\t\t\treturn;\n\t\t}\n\t\tsetActiveRowKey(defaultRowKey);\n\t}, [open, defaultRowKey]);\n\tconst reload = react.useCallback(() => {\n\t\tlastActionRef.current = \"load\";\n\t\tlastLoadRef.current = Date.now();\n\t\tload();\n\t}, [load]);\n\treact.useEffect(() => {\n\t\tif (available) {\n\t\t\t// 目录新鲜度守卫:inject 重跑会重建 load 引用触发本 effect,30s 内已有\n\t\t\t// 有效目录时不重复全量 load(show() 打开路径已有同款守卫)。\n\t\t\tif (Date.now() - lastLoadRef.current < DIRECTORY_STALE_MS && state.status === \"ready\" && state.groups.length > 0) return;\n\t\t\tlastActionRef.current = \"load\";\n\t\t\tlastLoadRef.current = Date.now();\n\t\t\tload();\n\t\t}\n\t}, [available, load]);\n\t// 菜单高度按视口实测钳位(原先 CSS 写死 min(420px, 100vh - 96px)),\n\t// composer 变高/窗口变小时自动收,不再溢出。\n\tconst menuRef = react.useRef<HTMLDivElement | null>(null);\n\t// useAnchoredMaxHeight 只适配「底边固定、向上生长」的底部锚定浮层:fit 读的是\n\t// 浮层自身底缘与视口顶缘的距离。向下弹(menuAbove=false)时面板改为顶边固定,\n\t// hook 度量的底缘是向下弹出后的位置,返回值在渲染处(maxHeight 三目)被忽略——\n\t// 继续挂监听只白付测量:每次 scroll/resize 都未节流 getBoundingClientRect +\n\t// setState 重渲染。向下弹期间把被测 ref 换成恒 null 的空 ref,hook 的 layout\n\t// effect 因 el === null 整体跳过(不注册监听、不测量);方向翻回向上时 ref 换回\n\t// menuRef,effect 因 ref 身份变化重跑并立即 fit——翻向后的钳位是新鲜测量\n\t// (原实现 signal 不变不重跑,翻回时用的还是向下弹期间留下的旧值)。\n\tconst inactiveMenuRef = react.useRef<HTMLDivElement | null>(null);\n\t// 第 4 参 margin 显式传本插件的 MENU_VIEWPORT_MARGIN,不依赖官方默认值恰好也是\n\t// 12:上游改默认 MARGIN 时隐式依赖会让本菜单的贴边距离静默跟随漂移。\n\t// official-seat-shadow.test.mjs 反向钉住两边相等(官方改了会红,届时再决策)。\n\tconst menuMaxHeight = useAnchoredMaxHeight(menuAbove ? menuRef : inactiveMenuRef, MENU_MAX_HEIGHT, open, MENU_VIEWPORT_MARGIN);\n\tconst [belowMaxHeight, setBelowMaxHeight] = react.useState(MENU_MAX_HEIGHT);\n\t// 为何不采纳官方 useAnchoredPosition(与文件头「不用官方 Menu」并列的第二个不采纳决定):\n\t// 该 hook 的水平起点固定是 anchor 左缘(left = rect.left,仅越界时钳进视口),没有\n\t// 「右缘对齐 anchor 右缘」这种模式;而本菜单默认就是右锚定(.dms-menu 的 right:0,与\n\t// seat 右缘对齐),只有右侧放不下时才钳到视口内改用 left。方向也不由 hook 决定:\n\t// side 是调用方写死的 'top' | 'bottom',而这里要按 trigger 上下空间自动选边\n\t// (dmsMenuAbove);且 hook 返回的是 fixed 定位坐标,本菜单是 .dms-root 内的\n\t// absolute + bottom/right 锚定,采纳它等于同时改默认对齐、方向策略与定位模型——\n\t// 属用户可见的运行时行为变更,故保留自研测量。\n\t// 水平钳位:seat 右缘放不下整幅菜单(窄窗口)时改为 left 锚定,undefined = 默认右锚定。\n\tconst [menuLeft, setMenuLeft] = react.useState<number | undefined>(undefined);\n\t// 布局生效前测量(对齐 useAnchoredMaxHeight 的 useLayoutEffect 模式):\n\t// 用 useEffect 会在首帧绘制后才翻转方向/水平锚定,向下弹或左夹取场景\n\t// 菜单先按默认(向上弹 + 右锚定)画一帧再跳位,肉眼可见闪错位。\n\treact.useLayoutEffect(() => {\n\t\tif (!open) return;\n\t\tconst measure = () => {\n\t\t\tconst trigger = triggerRef.current;\n\t\t\tif (trigger === null) return;\n\t\t\tconst rect = trigger.getBoundingClientRect();\n\t\t\t// 取上下两侧中空间更大的一侧:只有上方放不下时才向下弹。\n\t\t\t// 原先要求上方空出满高(MENU_MAX_HEIGHT + 20),窗口一矮就反而把面板\n\t\t\t// 挤到视口下沿外面。\n\t\t\tsetMenuAbove(dmsMenuAbove(rect.top, rect.bottom, window.innerHeight));\n\t\t\t// Downward the panel is top-anchored, so the hook's own fit would feed\n\t\t\t// back on itself; clamp against the space below the trigger instead.\n\t\t\tsetBelowMaxHeight(dmsBelowMaxHeight(rect.bottom, window.innerHeight, MENU_MAX_HEIGHT));\n\t\t\t// 水平:菜单实际渲染宽度为准(offsetWidth),右锚定放不下时钳到视口内。\n\t\t\t// dmsMenuLeft 的入参/返回值都是**视口坐标**,而 .dms-menu 是 .dms-root\n\t\t\t// (position: relative)内的 absolute 元素 —— left 的参考系是 root 的\n\t\t\t// padding box。不减去 root 左缘的话,钳位一旦生效菜单整体右移\n\t\t\t// rootRect.left;横向溢出(rect.right > innerWidth)那一支更会把整幅\n\t\t\t// 菜单画到屏外,恰是这次钳位要避免的现象。\n\t\t\tconst menuWidth = menuRef.current?.offsetWidth ?? 0;\n\t\t\tconst left = dmsMenuLeft(rect.right, menuWidth, window.innerWidth, MENU_VIEWPORT_MARGIN);\n\t\t\tconst rootLeft = rootRef.current?.getBoundingClientRect().left ?? 0;\n\t\t\tsetMenuLeft(left === undefined ? undefined : left - rootLeft);\n\t\t};\n\t\tmeasure();\n\t\tlet raf = 0;\n\t\t// scroll 捕获阶段每帧触发,resize 也高频;rAF 合并同帧多次触发,\n\t\t// passive 声明让浏览器不必等我们作 preventDefault。\n\t\tconst onViewportChange = () => {\n\t\t\tif (raf !== 0) return;\n\t\t\traf = requestAnimationFrame(() => {\n\t\t\t\traf = 0;\n\t\t\t\tmeasure();\n\t\t\t});\n\t\t};\n\t\twindow.addEventListener(\"resize\", onViewportChange, { passive: true });\n\t\twindow.addEventListener(\"scroll\", onViewportChange, { capture: true, passive: true });\n\t\treturn () => {\n\t\t\tif (raf !== 0) cancelAnimationFrame(raf);\n\t\t\twindow.removeEventListener(\"resize\", onViewportChange);\n\t\t\twindow.removeEventListener(\"scroll\", onViewportChange, { capture: true });\n\t\t};\n\t}, [open]);\n\treact.useEffect(() => {\n\t\tif (!open) return;\n\t\tsearchRef.current?.focus();\n\t}, [open]);\n\t// 打开即把当前选中行滚进可视区:选中模型在长列表深处时不用手动翻找。\n\t// 手动改 scrollTop 而不用 scrollIntoView,避免连带滚动页面/其它祖先容器。\n\t// 吸顶分组头(sticky top:0)会盖住滚到容器顶缘的行:偏移量按所在组的\n\t// 头部实际高度让位,否则「滚到了」却看不见。\n\t// 依赖补全:目录异步加载完成(state.groups 引用变化)后重新定位——原实现\n\t// 只依赖 [open],加载完成晚于开合的那次渲染时选中行永远不会滚进可视区。\n\t// useLayoutEffect(而非 queueMicrotask):DOM 已在提交阶段就绪,同步滚动\n\t// 不产生首帧跳动;且此时行/头部尺寸可测。\n\tconst scrollSelectedIntoView = (): void => {\n\t\tconst list = menuRef.current?.querySelector(\".dms-groups\");\n\t\tif (list === null || list === void 0) return;\n\t\t// 折叠/展开/目录刷新改变内容高度后,scrollTop 可能超出新的可滚上限\n\t\t// (浏览器不自动钳回,滚动条下方露出空白),先钳回合法区间再定位。\n\t\tconst maxScroll = list.scrollHeight - list.clientHeight;\n\t\tif (list.scrollTop > maxScroll) list.scrollTop = Math.max(0, maxScroll);\n\t\t// 选中行不在 DOM(选中组被折叠 / 搜索把选中模型滤掉)时,退而滚动到\n\t\t// 选中项所在分组(含折叠态,让用户看到「当前模型在那个组」)。\n\t\tconst selected = state.current;\n\t\tconst row = list.querySelector('[role=\"menuitemradio\"][aria-checked=\"true\"]');\n\t\tif (row === null) {\n\t\t\tif (selected === null) return;\n\t\t\tconst section = list.querySelector<HTMLElement>(`section[data-group-id=\"${globalThis.CSS.escape(selected.provider)}\"]`);\n\t\t\tif (section === null) return;\n\t\t\tconst sectionRect = section.getBoundingClientRect();\n\t\t\tconst listRect = list.getBoundingClientRect();\n\t\t\tif (sectionRect.top < listRect.top) list.scrollTop += sectionRect.top - listRect.top;\n\t\t\telse if (sectionRect.bottom > listRect.bottom) list.scrollTop += sectionRect.bottom - listRect.bottom;\n\t\t\treturn;\n\t\t}\n\t\tconst rowRect = row.getBoundingClientRect();\n\t\tconst listRect = list.getBoundingClientRect();\n\t\tconst header = row.closest(\"section\")?.querySelector(\".dms-groupHeader\");\n\t\tconst headerHeight = header === null || header === undefined ? 0 : header.getBoundingClientRect().height;\n\t\tif (rowRect.top < listRect.top + headerHeight) list.scrollTop += rowRect.top - (listRect.top + headerHeight);\n\t\telse if (rowRect.bottom > listRect.bottom) list.scrollTop += rowRect.bottom - listRect.bottom;\n\t};\n\t// 打开瞬间自动展开选中组(若被折叠)。用 ref 记录「本次打开周期已自动展开过\n\t// 哪个 provider」:layout effect 依赖含 collapsed,若不拦截,用户手动折叠\n\t// 选中组时 effect 会立刻把它重新展开——折叠功能对选中组形同失效。\n\t// 只在 provider 首次解析到(含异步 load 完成后)且处于折叠态时展开一次,\n\t// 之后同 provider 的手动折叠不再被抢。\n\tconst expandedProviderRef = react.useRef<string | null>(null);\n\treact.useLayoutEffect(() => {\n\t\tif (!open) {\n\t\t\texpandedProviderRef.current = null;\n\t\t\treturn;\n\t\t}\n\t\t// 搜索命中渲染期间不滚动/不自动展开:命中列表替换了分组 DOM,滚动测量\n\t\t// 没有意义;且每次击键 hits 引用都变化——提前返回避免每次击键重跑\n\t\t// querySelector×2 + getBoundingClientRect×3 的定位测量(当前模型命中时\n\t\t// 还会写 scrollTop)。查询清空(hits 回 null)后本 effect 因 deps 变化\n\t\t// 重跑,滚动/展开行为与首次打开、异步加载完成场景保持一致。\n\t\tif (hits !== null) return;\n\t\tconst provider = state.current?.provider ?? null;\n\t\tif (provider !== null && provider !== expandedProviderRef.current && collapsed.has(provider)) {\n\t\t\texpandedProviderRef.current = provider;\n\t\t\tsetCollapsed((prev) => {\n\t\t\t\tif (!prev.has(provider)) return prev;\n\t\t\t\tconst next = new Set(prev);\n\t\t\t\tnext.delete(provider);\n\t\t\t\treturn next;\n\t\t\t});\n\t\t}\n\t\tscrollSelectedIntoView();\n\t}, [open, state.groups, state.current, collapsed, hits]);\n\t// show/close/choose/toggleCollapse 四个 useCallback 必须全部位于下方\n\t// `if (!available) return null` 早退之前:hooks 数量不得随渲染分支变化\n\t// (React 会直接抛 \"Rendered more hooks than during the previous render\")。\n\tconst show = react.useCallback(() => {\n\t\tsetOpen(true);\n\t\tif (state.status === \"error\" || state.groups.length === 0 || Date.now() - lastLoadRef.current > DIRECTORY_STALE_MS) reload();\n\t}, [state.status, state.groups.length, reload]);\n\t// 还焦:优先 trigger;trigger 被 locked 禁用时 focus() 是空操作(焦点会掉回\n\t// body,下一次 Tab 从文档开头重来),退到 root 容器(tabIndex=-1)承接。\n\tconst focusTrigger = (): void => {\n\t\tconst trigger = triggerRef.current;\n\t\tif (trigger !== null && !trigger.disabled) {\n\t\t\ttrigger.focus();\n\t\t\treturn;\n\t\t}\n\t\trootRef.current?.focus();\n\t};\n\tconst close = react.useCallback((restoreFocus = false) => {\n\t\tsetOpen(false);\n\t\tsetNotice(null);\n\t\tsetQuery(\"\");\n\t\tif (restoreFocus) queueMicrotask(focusTrigger);\n\t}, []);\n\t// 外部 pointerdown 关闭与 Escape 一致走 close(true):搜索框随菜单卸载后\n\t// 焦点落回 trigger(此前焦点直接掉 body)。hook 只以 false 调用 setter,\n\t// 忽略参数即可。\n\tconst dismissOnOutsidePointer = react.useCallback(() => {\n\t\tclose(true);\n\t}, [close]);\n\tuseDismissOnOutsidePointer(rootRef, open, dismissOnOutsidePointer);\n\t// locked 翻转(会话移除/失活/页面 inert 等)时若菜单还开着直接关闭:\n\t// trigger 已禁用,挂着的菜单没有可交互入口;close(true) 顺带清掉搜索词与\n\t// notice,并把焦点从即将卸载的菜单里接出来(focusTrigger 因 trigger disabled\n\t// 落到 root 容器,不让焦点掉回 body)。\n\treact.useEffect(() => {\n\t\tif (locked && open) close(true);\n\t}, [locked, open, close]);\n\tconst choose = react.useCallback((selection: ModelSelection): void => {\n\t\tconst { busy: isBusy, current, choices: catalog } = chooseInputRef.current;\n\t\tif (isBusy) return;\n\t\tif (current?.provider === selection.provider && current.model === selection.model) {\n\t\t\tsetNotice(t(\"notice.already\"));\n\t\t\treturn;\n\t\t}\n\t\tconst target = catalog.find((c) => c.selection.provider === selection.provider && c.selection.model === selection.model);\n\t\t// 自动拉档决策收敛到 dmsChoosePlan(effort.ts,node 可测):非规范档位 id\n\t\t// 不再被当成「最强档」提交(那会挑错档、甚至被宿主拒绝)。\n\t\tconst plan = dmsChoosePlan(target?.model.reasoning);\n\t\tconst full: ModelSelection = {\n\t\t\tprovider: selection.provider,\n\t\t\tmodel: selection.model,\n\t\t\t...plan.effort === void 0 ? {} : { reasoningEffort: plan.effort }\n\t\t};\n\t\tlastActionRef.current = \"select\";\n\t\tconst autoName = plan.effort === void 0\n\t\t\t? \"\"\n\t\t\t: target?.model.reasoning?.efforts.find((level) => level.id === plan.effort)?.name ?? plan.effort;\n\t\t// 超时护栏与 EffortSlider.commit() 同款:目录 select 无超时契约,RPC 永久\n\t\t// 挂起时若不加护栏,status='selecting' 由 store 持有、本插件无法复位,交互\n\t\t// 永久锁死。超时/拒绝都按切换失败播报 Toast(复用既有文案,不新增 key);\n\t\t// 不强行复位 store——目录 settle 后状态自然回到 ready,与 effort commit 一致。\n\t\t// 迟到的 settle 一律忽略:成功与否都会经共享目录写回,UI 无需再动作。\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tconst result = await new Promise<SelectResult>((resolve, reject) => {\n\t\t\t\t\tlet settled = false;\n\t\t\t\t\tconst timer = window.setTimeout(() => {\n\t\t\t\t\t\tif (settled) return;\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\treject(new Error(t(\"notice.selectFailed\")));\n\t\t\t\t\t}, EFFORT_COMMIT_TIMEOUT_MS);\n\t\t\t\t\tselect(full).then(\n\t\t\t\t\t\t(settledResult) => {\n\t\t\t\t\t\t\tif (settled) return;\n\t\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\t\twindow.clearTimeout(timer);\n\t\t\t\t\t\t\tresolve(settledResult);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(cause: unknown) => {\n\t\t\t\t\t\t\tif (settled) return;\n\t\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\t\twindow.clearTimeout(timer);\n\t\t\t\t\t\t\treject(cause);\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t\tif (result === undefined || !result.ok) {\n\t\t\t\t\t// alpha.2 契约:失败详情随 RemoteResult.error 回来;契约缺席\n\t\t\t\t\t// (undefined = 该会话不可选)时回退目录快照的错误(旧行为)。\n\t\t\t\t\tconst message = result !== undefined\n\t\t\t\t\t\t? `${result.error.code}: ${result.error.message}`\n\t\t\t\t\t\t: directory.getSnapshot().error;\n\t\t\t\t\tshowToast(message !== null ? t(\"error.action\", { message }) : t(\"notice.selectFailed\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// 只有菜单仍开着时才关(并清搜索词、还焦):用户在 RPC 在途期间已经\n\t\t\t\t// 关掉菜单/移走焦点时,这里的 close(true) 会把焦点从用户当前所在\n\t\t\t\t// 控件抢回 trigger,还会清掉用户刚输入的搜索词。Toast 与 open 无关,\n\t\t\t\t// 照常播报。\n\t\t\t\tif (chooseInputRef.current.open) close(true);\n\t\t\t\tif (plan.autoRaised) showToast(t(\"toast.effortAuto\", { effort: autoName }), false);\n\t\t\t} catch {\n\t\t\t\t// 超时 / select 拒绝:都按切换失败播报(防御性 catch 同时杜绝\n\t\t\t\t// unhandled rejection——未来注入面变更也不会让它漏出去)。\n\t\t\t\tshowToast(t(\"notice.selectFailed\"));\n\t\t\t}\n\t\t})();\n\t\t// 依赖只剩稳定引用(注入面的 select/t、目录 store、close/showToast 都是\n\t\t// useCallback([]) 或 props 缓存):busy/current/choices/open 一律经\n\t\t// chooseInputRef 读最新值 —— 否则一次模型切换就让 choose 换引用,数百行的\n\t\t// ModelOption 自定义 memo 全线失效。\n\t}, [select, t, directory, close, showToast]);\n\tconst toggleCollapse = react.useCallback((groupId: string): void => {\n\t\tsetCollapsed((prev) => {\n\t\t\tconst next = new Set(prev);\n\t\t\tif (next.has(groupId)) next.delete(groupId);\n\t\t\telse next.add(groupId);\n\t\t\treturn next;\n\t\t});\n\t}, []);\n\tif (!available) return null;\n\t// 键盘导航:按 DOM 顺序查询菜单内全部行(data-row-key),不再依赖渲染期\n\t// 收集的 ref 数组(那既阻止行级 memo,又把副作用塞进 render)。环绕下标\n\t// 收敛到 dmsNextRowIndex(keys.ts,node 可测)。\n\tconst moveFocus = (offset: number): void => {\n\t\tconst menu = menuRef.current;\n\t\tif (menu === null) return;\n\t\tconst items = [...menu.querySelectorAll<HTMLButtonElement>('[data-row-key]')];\n\t\tif (items.length === 0) return;\n\t\tconst active = items.findIndex((item) => item === document.activeElement);\n\t\titems[dmsNextRowIndex(active, offset, items.length)]?.focus();\n\t};\n\t// Home/End 跳首/末行(WAI-ARIA menu 标准键;搜索框内不劫持——那是移光标键)。\n\tconst focusEdge = (last: boolean): void => {\n\t\tconst items = menuRef.current?.querySelectorAll<HTMLButtonElement>('[data-row-key]');\n\t\tif (items === undefined || items.length === 0) return;\n\t\t(last ? items[items.length - 1] : items[0])?.focus();\n\t};\n\t// 按键 → 动作的判定全部收敛在 dmsMenuKeyAction(keys.ts,node 可测);\n\t// 这里只做 dispatch 与副作用(preventDefault 只在真的执行动作时才发,\n\t// 无行可去时不吞键)。\n\tconst onRootKeyDown = (event: react.KeyboardEvent<HTMLDivElement>): void => {\n\t\tconst target = event.target;\n\t\tconst action = dmsMenuKeyAction({\n\t\t\tkey: event.key,\n\t\t\t// IME 组合输入期间不劫持按键:Enter 是候选上屏确认、方向键在候选窗翻页、\n\t\t\t// Escape 是取消组合。除 isComposing 外还要认 keyCode 229 —— WebKit 在\n\t\t\t// compositionend 之后补发的那次 Enter 带 isComposing === false,不挡就会把\n\t\t\t// 「候选上屏」当成「选中首个命中」直接切模型。\n\t\t\tcomposing: event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229,\n\t\t\topen,\n\t\t\tfromSearch: target instanceof HTMLInputElement && target === searchRef.current,\n\t\t\t// 「菜单内」= 菜单容器(搜索框/行/滑杆);trigger 不算 —— 焦点在 trigger 上\n\t\t\t// 按 Escape 应当直接关菜单,而不是被清词逻辑拽进搜索框。\n\t\t\tinMenu: menuRef.current?.contains(document.activeElement) === true,\n\t\t\tonNonInput: !(target instanceof HTMLInputElement),\n\t\t\thasQuery: query !== \"\",\n\t\t\trowCount: menuRef.current?.querySelectorAll('[data-row-key]').length ?? 0,\n\t\t\thitCount: hits?.items.length ?? 0,\n\t\t});\n\t\tswitch (action.type) {\n\t\t\tcase 'none':\n\t\t\t\treturn;\n\t\t\tcase 'clearQuery':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tsetNotice(null);\n\t\t\t\tsetQuery(\"\");\n\t\t\t\tif (action.focusSearch) searchRef.current?.focus();\n\t\t\t\treturn;\n\t\t\tcase 'close':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tclose(true);\n\t\t\t\treturn;\n\t\t\tcase 'moveFocus':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tmoveFocus(action.offset);\n\t\t\t\treturn;\n\t\t\tcase 'focusEdge':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tfocusEdge(action.last);\n\t\t\t\treturn;\n\t\t\tcase 'chooseFirstHit': {\n\t\t\t\tconst first = hits?.items[0];\n\t\t\t\tif (first === undefined) return;\n\t\t\t\tevent.preventDefault();\n\t\t\t\tchoose({ provider: first.group.id, model: first.model.id });\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t};\n\tconst onBlur = (event: react.FocusEvent<HTMLDivElement>): void => {\n\t\tconst related = event.relatedTarget;\n\t\t// 焦点仍在 root 内(菜单、搜索框、滑杆、root 自身):不关。\n\t\tif (related instanceof Node && rootRef.current?.contains(related) === true) return;\n\t\t// 焦点落到 root 外的可聚焦元素(Tab 出去):关。不再豁免 busy —— 否则在途\n\t\t// 选择期间会留下「焦点已在菜单外、Escape 也关不掉」的悬挂菜单(keydown\n\t\t// 不再冒泡到 root),而菜单外的点击本就有 outside-pointer 路径兜底。\n\t\tif (related instanceof Node) {\n\t\t\tclose();\n\t\t\treturn;\n\t\t}\n\t\t// relatedTarget 为 null:只有窗口失焦(alt-tab / 切到别的应用)才关。点击菜单内的\n\t\t// 非可聚焦区域(内边距、标签、分组间隙)也会让浏览器把焦点收回 body,但那是菜单内\n\t\t// 操作,不该关菜单。\n\t\t// ⚠ 判据用 `visibilityState` 而不是 `document.hasFocus()`:后者在 iframe /\n\t\t// 多文档场景(桌面端 WebView 承载、或页面被嵌进别的文档)下,用户点击\n\t\t// iframe 外的区域时返回 false,会把「焦点只是被外层文档拿走」误判成窗口\n\t\t// 失焦 → 菜单静默关掉。`hidden` 只在文档真正不可见(切标签页 / 最小化 /\n\t\t// 锁屏)时为真,与「用户不在看这个页面」的语义一致。\n\t\tif (document.visibilityState === 'hidden') close();\n\t};\n\t// 标签兜底链收敛到 dmsTriggerCopy(copy.ts,node 可测):目录成员资格只是\n\t// 参考(routable 契约),current 匹配不到 group 时显示 provider/model 原始\n\t// id,而不是「选择模型」。\n\tconst trigger = dmsTriggerCopy({\n\t\tcurrent: state.current,\n\t\tmodelName: currentChoice?.model.name,\n\t\tproviderName: currentChoice?.group.name,\n\t\teffortLabel,\n\t\twaiting: state.current === null && state.status === \"loading\",\n\t}, t);\n\tconst renderErrorStrip = () => {\n\t\t// Load failures only: a rejected select is announced by the Toast, which\n\t\t// also survives closing the menu (see choose()). role=alert:菜单打开时\n\t\t// 焦点在搜索框,失败条插在它上方,读屏用户不浏览过去就不知道有失败。\n\t\tif (state.error !== null && lastActionRef.current === \"load\") {\n\t\t\treturn (\n\t\t\t\t<div className=\"dms-error\" role=\"alert\">\n\t\t\t\t\t<span>{t(\"error.action\", { message: state.error })}</span>\n\t\t\t\t\t<button type=\"button\" className=\"dms-retry\" onClick={reload}>{t(\"action.reload\")}</button>\n\t\t\t\t</div>\n\t\t\t);\n\t\t}\n\t\treturn null;\n\t};\n\tconst renderFailures = (failures: DirectoryState['failures']) => failures.map((failure) => (\n\t\t<div key={failure.id} className=\"dms-warning\">\n\t\t\t<span>{t(\"warning.groupLoad\", { name: failure.name, message: failure.message })}</span>\n\t\t\t<button type=\"button\" className=\"dms-retry\" onClick={reload}>{t(\"action.reload\")}</button>\n\t\t</div>\n\t));\n\t// 列表区的空态:判定收敛到 dmsMenuEmptyState(search.ts,node 可测)—— 无模型\n\t// 优先于无命中(目录为空时不该引导用户去改关键词)。空态播报节点必须放在\n\t// role=menu 容器外(menu 内容模型只允许菜单节点),视觉位置由 .dms-groupsFill\n\t// 的弹性占位补回,空态时列表容器本身不渲染。\n\tconst emptyState = dmsMenuEmptyState({\n\t\tstatus: state.status,\n\t\tchoiceCount: choices.length,\n\t\tsearching: hits !== null,\n\t\thitCount: hits?.total ?? 0,\n\t});\n\treturn (\n\t\t// tabIndex=-1:root 只作为「焦点落点」承接(locked 关闭菜单时 trigger 已\n\t\t// 禁用,不能让焦点掉回 body;点击菜单内非可聚焦区域时浏览器把焦点交给它,\n\t\t// onBlur 便不会误判成「焦点离开菜单」)。不进 Tab 序。\n\t\t<div ref={rootRef} className=\"dms-root\" tabIndex={-1} onKeyDown={onRootKeyDown} onBlur={onBlur}>\n\t\t\t<button\n\t\t\t\tref={triggerRef}\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"dms-trigger\"\n\t\t\t\taria-label={trigger.aria}\n\t\t\t\taria-haspopup=\"menu\"\n\t\t\t\taria-expanded={open}\n\t\t\t\taria-controls={open ? `${id}-menu` : undefined}\n\t\t\t\ttitle={trigger.title}\n\t\t\t\tdisabled={locked}\n\t\t\t\tonClick={() => (open ? close() : show())}\n\t\t\t>\n\t\t\t\t<span className=\"dms-triggerLabel\">{trigger.modelLabel}</span>\n\t\t\t\t{trigger.providerLabel !== undefined && <span className=\"dms-triggerProvider\">{trigger.providerLabel}</span>}\n\t\t\t\t{effortLabel !== undefined && <span className=\"dms-triggerEffort\">{effortLabel}</span>}\n\t\t\t\t<span className={`dms-chevron${open ? \" dms-chevronOpen\" : \"\"}`} aria-hidden=\"true\">\n\t\t\t\t\t{IconChevronDown}\n\t\t\t\t</span>\n\t\t\t</button>\n\t\t\t{open ? (\n\t\t\t\t<div\n\t\t\t\t\tid={`${id}-menu`}\n\t\t\t\t\tref={menuRef}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tmaxHeight: menuAbove ? menuMaxHeight : belowMaxHeight,\n\t\t\t\t\t\t...(menuLeft === undefined ? null : { left: menuLeft, right: \"auto\" }),\n\t\t\t\t\t}}\n\t\t\t\t\tclassName={'dms-menu dms-menuModel' + (menuAbove ? '' : ' dms-menuBelow')}\n\t\t\t\t>\n\t\t\t\t\t{state.status === 'loading' && <div className=\"dms-status\" role=\"status\">{t('status.loading')}</div>}\n\t\t\t\t\t{renderErrorStrip()}\n\t\t\t\t\t{/* role=alert 挂容器:一次 load 可能多家供应商同时失败,逐条 alert\n\t\t\t\t\t 会连播数遍。 */}\n\t\t\t\t\t{state.failures.length > 0 && <div className=\"dms-failures\" role=\"alert\">{renderFailures(state.failures)}</div>}\n\t\t\t\t\t<div className=\"dms-search\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref={searchRef}\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tclassName=\"dms-searchInput\"\n\t\t\t\t\t\t\tvalue={query}\n\t\t\t\t\t\t\tplaceholder={t('search.placeholder')}\n\t\t\t\t\t\t\taria-label={t('search.placeholder')}\n\t\t\t\t\t\t\t// 输入即过滤一个结果列表 → combobox 语义(APG「Editable\n\t\t\t\t\t\t\t// Combobox With List Autocomplete」)。aria-expanded 只在搜索态\n\t\t\t\t\t\t\t// 为 true:那时结果容器才是 listbox;非搜索态的分组视图是 menu,\n\t\t\t\t\t\t\t// 不冒充「展开的建议列表」。\n\t\t\t\t\t\t\t// 0 命中时同步收起:listbox 容器不渲染(见下),声称「已展开」\n\t\t\t\t\t\t\t// 会让读尸用户听到一个指向空集的 combo(空态文案由 role=status\n\t\t\t\t\t\t\t// 那条 live region 承担播报,不依赖 expanded)。\n\t\t\t\t\t\t\trole=\"combobox\"\n\t\t\t\t\t\t\taria-expanded={hits !== null && hits.total > 0}\n\t\t\t\t\t\t\taria-autocomplete=\"list\"\n\t\t\t\t\t\t\t// 空态时结果容器不渲染,指向它就是悬空 IDREF(官方 trigger\n\t\t\t\t\t\t\t// 同款处理:关闭/不存在时省略 aria-controls)。\n\t\t\t\t\t\t\taria-controls={emptyState === null ? `${id}-groups` : undefined}\n\t\t\t\t\t\t\tautoComplete=\"off\"\n\t\t\t\t\t\t\tenterKeyHint=\"search\"\n\t\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\t\tonChange={(event: react.ChangeEvent<HTMLInputElement>) => {\n\t\t\t\t\t\t\t\tsetNotice(null);\n\t\t\t\t\t\t\t\tsetQuery(event.target.value);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{query !== '' && (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclassName=\"dms-searchClear\"\n\t\t\t\t\t\t\t\taria-label={t('search.clearAria')}\n\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\tsetNotice(null);\n\t\t\t\t\t\t\t\t\tsetQuery('');\n\t\t\t\t\t\t\t\t\tsearchRef.current?.focus();\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<span aria-hidden=\"true\" className=\"dms-icon-slot\">{IconClear}</span>\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t\t{/* 搜索结果播报:容器常驻(内容随状态更新)—— 插入式 live region 在各\n\t\t\t\t\t AT 上行为不一致,可能整条漏播。0 命中时不播计数(空态文案已承担),\n\t\t\t\t\t 避免同帧两条 role=status 重复播报。 */}\n\t\t\t\t\t<span className=\"dms-sr\" role=\"status\">\n\t\t\t\t\t\t{hits !== null && hits.total > 0 ? t('search.status', { count: String(hits.total) }) : ''}\n\t\t\t\t\t</span>\n\t\t\t\t\t{/* 推理标记的说明:只挂在 title 上则键盘/读屏用户拿不到,这里补一条\n\t\t\t\t\t 视觉隐藏的静态说明(role=note,不冒充菜单项)。 */}\n\t\t\t\t\t<span className=\"dms-sr\" role=\"note\">{t('badge.reasoningHint')}</span>\n\t\t\t\t\t{/* 空态(无命中/无模型)与「仅显示前 N 条」note 全部在结果容器外:\n\t\t\t\t\t menu/listbox 的直接内容模型只允许菜单/选项节点,live region 会被\n\t\t\t\t\t 菜单导航跳过、静态说明不该冒充选项。视觉位置由 .dms-groupsFill\n\t\t\t\t\t (空态)与容器后的 .dms-more 保持。 */}\n\t\t\t\t\t{emptyState === 'noHits' && (\n\t\t\t\t\t\t<div className=\"dms-empty dms-groupsFill\" role=\"status\">{t('search.noMatch', { query: query.trim() })}</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{emptyState === 'noModels' && (\n\t\t\t\t\t\t<div className=\"dms-empty dms-groupsFill\" role=\"status\">{t('empty.models')}</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{emptyState === null && (\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"dms-groups\"\n\t\t\t\t\t\tid={`${id}-groups`}\n\t\t\t\t\t\t// 搜索态是 listbox(combobox 的弹层,行用 option);分组态是 menu\n\t\t\t\t\t\t// (行用 menuitemradio)。aria-busy 从 .dms-menu 移到这里:它罩住\n\t\t\t\t\t\t// live region 会压制播报,而 menu 容器自身才是「内容仍在变」的子树。\n\t\t\t\t\t\trole={hits !== null ? 'listbox' : 'menu'}\n\t\t\t\t\t\taria-busy={state.status === 'loading' || busy}\n\t\t\t\t\t\taria-label={t(\"menu.aria\")}\n\t\t\t\t\t>\n\t\t\t\t\t\t{hits !== null\n\t\t\t\t\t\t\t? hits.items.map((hit, index) => {\n\t\t\t\t\t\t\t\tconst rowKey = dmsRowKey(hit.group.id, hit.model.id);\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<ModelOption\n\t\t\t\t\t\t\t\t\t\tkey={rowKey}\n\t\t\t\t\t\t\t\t\t\tgroup={hit.group}\n\t\t\t\t\t\t\t\t\t\tmodel={hit.model}\n\t\t\t\t\t\t\t\t\t\tshowProvider\n\t\t\t\t\t\t\t\t\t\tselected={state.current?.provider === hit.group.id && state.current.model === hit.model.id}\n\t\t\t\t\t\t\t\t\t\tbusy={busy}\n\t\t\t\t\t\t\t\t\t\trowKey={rowKey}\n\t\t\t\t\t\t\t\t\t\tactive={rowKey === activeRowKey}\n\t\t\t\t\t\t\t\t\t\tsearchMode\n\t\t\t\t\t\t\t\t\t\tonRowFocus={onRowFocus}\n\t\t\t\t\t\t\t\t\t\tnameHit={hit.nameHit}\n\t\t\t\t\t\t\t\t\t\tsetsize={hits.total}\n\t\t\t\t\t\t\t\t\t\tposinset={index + 1}\n\t\t\t\t\t\t\t\t\t\tt={t}\n\t\t\t\t\t\t\t\t\t\tonChoose={choose}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t: state.groups.map((group) => {\n\t\t\t\t\t\t\t\tconst headingId = `${id}-${group.id}`;\n\t\t\t\t\t\t\t\tconst headerKey = dmsHeaderKey(group.id);\n\t\t\t\t\t\t\t\tconst isCollapsed = collapsed.has(group.id);\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<section key={group.id} data-group-id={group.id} role=\"group\" aria-labelledby={headingId} className=\"dms-group\">\n\t\t\t\t\t\t\t\t\t\t{/* 组头参与 roving 导航(data-row-key + onFocus 同步活动行键):\n\t\t\t\t\t\t\t\t\t\t 折叠/展开对键盘可达,且「全部折叠」时 dmsDefaultRowKey\n\t\t\t\t\t\t\t\t\t\t 回退到的正是组头 —— 它必须能承接 tabIndex=0,否则\n\t\t\t\t\t\t\t\t\t\t Tab 会直接跳过整个列表(活动行键落空)。非活动组头\n\t\t\t\t\t\t\t\t\t\t 仍是 -1,数百行不会撑爆 Tab 停靠点。role=menuitem +\n\t\t\t\t\t\t\t\t\t\t aria-expanded 是 ARIA menu 模式里「可展开菜单项」的语义。 */}\n<button\n\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\trole=\"menuitem\"\n\t\t\t\t\t\t\t\t\t\t\tdata-row-key={headerKey}\n\t\t\t\t\t\t\t\t\t\t\ttabIndex={activeRowKey === headerKey ? 0 : -1}\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"dms-groupHeader\"\n\t\t\t\t\t\t\t\t\t\t\taria-expanded={!isCollapsed}\n\t\t\t\t\t\t\t\t\t\t\taria-label={t('group.toggleAria', { name: group.name, count: String(group.models.length) })}\n\t\t\t\t\t\t\t\t\t\t\tonFocus={() => onRowFocus(headerKey)}\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => toggleCollapse(group.id)}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<span className={`dms-groupChevron${isCollapsed ? ' dms-groupChevronClosed' : ''}`} aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t{IconChevronDown}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t<span id={headingId} className=\"dms-groupName\">{group.name}</span>\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"dms-groupCount\">{group.models.length}</span>\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t{!isCollapsed && group.models.map((model) => {\n\t\t\t\t\t\t\t\t\t\t\tconst rowKey = dmsRowKey(group.id, model.id);\n\t\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t<ModelOption\n\t\t\t\t\t\t\t\t\t\t\t\t\tkey={rowKey}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgroup={group}\n\t\t\t\t\t\t\t\t\t\t\t\t\tmodel={model}\n\t\t\t\t\t\t\t\t\t\t\t\t\tshowProvider={false}\n\t\t\t\t\t\t\t\t\t\t\t\t\tselected={state.current?.provider === group.id && state.current.model === model.id}\n\t\t\t\t\t\t\t\t\t\t\t\t\tbusy={busy}\n\t\t\t\t\t\t\t\t\t\t\t\t\trowKey={rowKey}\n\t\t\t\t\t\t\t\t\t\t\t\t\tactive={rowKey === activeRowKey}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsearchMode={false}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonRowFocus={onRowFocus}\n\t\t\t\t\t\t\t\t\t\t\t\t\tt={t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonChoose={choose}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t</section>\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{hits !== null && hits.total > MAX_VISIBLE_HITS && (\n\t\t\t\t\t\t// role=note:静态辅助说明,从菜单项语义里退出来。\n\t\t\t\t\t\t<div className=\"dms-more\" role=\"note\">{t('search.more', { shown: String(MAX_VISIBLE_HITS), total: String(hits.total) })}</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{state.current !== null && dmsSliderLevels(state).length >= 2 && (\n\t\t\t\t\t\t<div className=\"dms-effortFooter\">\n\t\t\t\t\t\t\t<span className=\"dms-effortFooterLabel\">{t('menu.effort')}</span>\n\t\t\t\t\t\t\t<EffortSlider state={state} select={select} t={t} onSelectFailure={markSelectFailure} />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{/* notice 容器常驻(空时 :empty 隐藏):插入式 live region 的首次播报\n\t\t\t\t\t 在各 AT 上不可靠,「已是当前模型」这类反馈只有这一条通道。 */}\n\t\t\t\t\t<div className=\"dms-notice\" role=\"status\">{notice ?? ''}</div>\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t\t{toast !== null && (\n\t\t\t\t<Toast\n\t\t\t\t\tkey={toast.seq}\n\t\t\t\t\ttext={toast.text}\n\t\t\t\t\ticon={toast.failed ? <IconWarningOutlineRegular size={14} /> : undefined}\n\t\t\t\t\tanchor={rootRef.current?.closest<HTMLElement>(\"[data-composer-card]\") ?? null}\n\t\t\t\t\tonDone={dismissToast}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</div>\n\t);\n}","/**\n* Stylesheet for the enhanced model seat.\n*\n* Injected as one `<style data-plugin=\"@bittersmilezzz/dsh-model-selector\">` tag by the client\n* apply and removed again on unload. Class names are prefixed `dms-` so they\n* cannot collide with CSS-module hashes from other plugins. Colors come only\n* from `--dsw-*` theme tokens, matching the shipped Menu material.\n*\n* 显式 `: string` 注解:不加注解时 tsc 会把整份 CSS 推断成字面量类型并写进\n* lib/types/client/styles.d.ts(19KB 的 d.ts 只为声明一个常量),发布包里\n* 白白多出十几 KB 且每次改样式都全量重写。\n*/\nexport const CSS: string = `\n.dms-root {\n position: relative;\n min-width: 0;\n}\n/* root 只作为焦点落点(tabIndex=-1):不画焦点环,真正的可聚焦控件在它内部。 */\n.dms-root:focus { outline: none; }\n\n.dms-trigger {\n display: flex;\n align-items: center;\n gap: 4px;\n min-width: 0;\n max-width: min(420px, calc(100vw - 48px));\n height: 28px;\n padding: 0 4px 0 8px;\n border: none;\n border-radius: 24px;\n outline: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 13px;\n line-height: 20px;\n font-weight: 500;\n cursor: pointer;\n}\n.dms-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }\n.dms-trigger:focus-visible { box-shadow: 0 0 0 2px var(--dsw-alias-border-l3); }\n.dms-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }\n\n.dms-triggerLabel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n line-height: 20px;\n}\n.dms-triggerEffort {\n flex: 0 0 auto;\n line-height: 20px;\n color: var(--dsw-alias-label-caption);\n}\n.dms-triggerProvider {\n flex: 0 0 auto;\n max-width: 88px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--dsw-alias-label-caption);\n font-size: 11px;\n line-height: 20px;\n font-weight: 400;\n}\n\n.dms-chevron,\n.dms-groupChevron {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n line-height: 0;\n}\n.dms-chevron {\n color: var(--dsw-alias-label-caption);\n transition: transform 120ms ease;\n}\n.dms-chevronOpen { transform: rotate(180deg); }\n.dms-groupChevron {\n color: var(--dsw-alias-label-tertiary);\n transition: transform 120ms ease;\n}\n.dms-groupChevronClosed { transform: rotate(-90deg); }\n.dms-model-check svg { display: block; }\n\n.dms-menu {\n position: absolute;\n right: 0;\n bottom: calc(100% + 8px);\n z-index: 20;\n display: flex;\n flex-direction: column;\n width: min(280px, calc(100vw - 32px));\n overflow: hidden;\n padding: 4px 0 4px 4px;\n box-sizing: border-box;\n border: 1px solid var(--dsw-alias-border-inverted);\n border-radius: 12px;\n background: var(--dsw-specific-menu);\n box-shadow: var(--dsw-shadow-lv3);\n color: var(--dsw-alias-label-primary);\n animation: dms-menu-in 150ms cubic-bezier(.22,1,.36,1);\n --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);\n --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);\n}\n.dms-menuModel { border-right: none; }\n.dms-menuBelow {\n bottom: auto;\n top: calc(100% + 8px);\n animation-name: dms-menu-in-below;\n}\n\n/* 菜单头部的「非列表」栏(loading / 空态 / 错误条 / 失败条 / 截断提示):\n 一律允许收缩(flex-shrink 1)且各自限高,让 .dms-groups 始终保住可滚动\n 的最小可视高度。2026-09-21 修复的 P0:向上弹时 useAnchoredMaxHeight 会把\n maxHeight 钳到「trigger 底缘 - 12px」(底缘固定、向上生长的语义),trigger\n 靠近视口顶部时该值很小;原先这些栏是 flex: 0 0 auto 完全不收缩,头部\n 40-100px 全部占完后 .dms-groups 的可视高度趋近 0,而 .dms-menu 是\n overflow:hidden —— 用户看到错误条却看不到也滚不到任何模型行。\n 现在给它们统一的上限(相对菜单高度)+ overflow,最坏情况是栏内自己滚动。 */\n.dms-status,\n.dms-empty {\n flex: 0 1 auto;\n min-height: 0;\n max-height: 40%;\n overflow-y: auto;\n padding: 10px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 13px;\n line-height: 20px;\n}\n\n.dms-more {\n flex: 0 1 auto;\n min-height: 0;\n max-height: 20%;\n overflow-y: auto;\n padding: 8px 12px;\n border-top: 1px solid var(--dsw-alias-border-l1, transparent);\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n/* Provider-load failure strip: capped and scrollable so a long list of failed\n groups can never squeeze the model list out of the menu (E4). */\n.dms-failures {\n flex: 0 1 auto;\n max-height: min(96px, 30%);\n min-height: 0;\n overflow-y: auto;\n}\n\n.dms-error,\n.dms-warning {\n flex: 0 1 auto;\n min-height: 0;\n max-height: 40%;\n overflow-y: auto;\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 8px;\n margin-bottom: 4px;\n padding: 7px 8px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover-danger);\n color: var(--dsw-alias-state-error-primary);\n font-size: 12px;\n line-height: 18px;\n}\n.dms-warning {\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-state-warn-label);\n}\n.dms-retry {\n flex: 0 0 auto;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: inherit;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n}\n.dms-retry:focus-visible { box-shadow: 0 0 0 2px var(--dsw-alias-border-l3); }\n\n/* Search box pinned above the scrollable list. */\n.dms-search {\n position: relative;\n margin-bottom: 4px;\n margin-right: 4px;\n flex: 0 0 auto;\n}\n.dms-searchInput {\n box-sizing: border-box;\n width: 100%;\n height: 30px;\n padding: 0 30px 0 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n outline: none;\n background: var(--dsw-alias-bg-input);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n line-height: 18px;\n}\n.dms-searchInput::placeholder { color: var(--dsw-alias-label-tertiary); }\n.dms-searchInput:focus-visible { box-shadow: 0 0 0 2px var(--dsw-alias-border-l3); }\n.dms-searchClear {\n position: absolute;\n top: 0;\n right: 0;\n display: grid;\n place-items: center;\n width: 30px;\n height: 30px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dms-searchClear:hover { color: var(--dsw-alias-label-primary); }\n/* 键盘焦点此前在这三个可聚焦控件上完全隐身(上轮只补了模型行)。 */\n.dms-searchClear:focus-visible {\n border-radius: 8px;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n/* 图标组件只收 size/className,装饰性 aria-hidden 只能包一层 span;contents 让它不占布局。 */\n.dms-icon-slot { display: contents; }\n\n/* 列表区:菜单里唯一的弹性主体。max-height 被 useAnchoredMaxHeight 钳得很小\n (trigger 靠近视口顶 + 向上弹)时,32px 保底让「至少见到一行模型」始终成立\n —— 头部各栏此时按自己的 max-height 百分比上限收缩(见 .dms-status 等)。 */\n.dms-groups { flex: 1 1 auto; min-height: 32px; overflow-y: auto; contain: content; }\n/* 空态占位:空态播报节点在 role=menu 容器外,用与 .dms-groups 同款弹性布局\n 补回列表区本来的位置(不挤压其余栏,消息也不会贴到菜单顶部)。 */\n.dms-groupsFill { flex: 1 1 auto; min-height: 0; overflow-y: auto; contain: content; }\n.dms-group + .dms-group { margin-top: 4px; }\n\n/* Collapsible provider header: a full-width toggle button. */\n.dms-groupHeader {\n position: sticky;\n top: 0;\n z-index: 1;\n display: flex;\n align-items: center;\n gap: 4px;\n width: 100%;\n padding: 5px 8px 3px;\n border: none;\n outline: none;\n background: var(--dsw-specific-menu);\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n text-align: left;\n cursor: pointer;\n}\n.dms-groupHeader:hover { color: var(--dsw-alias-label-primary); }\n.dms-groupHeader:focus-visible { box-shadow: inset 0 0 0 2px var(--dsw-alias-border-l3); }\n.dms-groupName {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dms-groupCount { flex: 0 0 auto; color: var(--dsw-alias-label-dimmed); font-weight: 400; }\n\n.dms-nameRow {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.dms-badge {\n flex: 0 0 auto;\n padding: 0 6px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n color: var(--dsw-alias-label-caption);\n font-size: 10px;\n line-height: 16px;\n font-weight: 500;\n}\n.dms-notice {\n flex: 0 0 auto;\n margin: 4px 0 0;\n padding: 6px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n}\n/* 常驻 live region(内容随状态更新):空时收起,不占位。 */\n.dms-notice:empty { display: none; }\n.dms-effort {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n width: 100%;\n min-width: 0;\n min-height: 32px;\n color: var(--dsw-alias-label-secondary);\n user-select: none;\n box-sizing: border-box;\n}\n/* 档位读数:拖动时实时跟随 preview,替代此前只有读屏能看到的 aria-valuetext。 */\n.dms-effort-value {\n flex: 0 0 auto;\n margin-left: 8px;\n min-width: 3.2em;\n color: var(--dsw-alias-label-primary);\n font-size: 11px;\n line-height: 16px;\n text-align: right;\n}\n/* 官方契约的 efforts[].description,整行展示在滑杆下方。 */\n.dms-effort-desc {\n flex: 1 0 100%;\n margin-top: 2px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n line-height: 14px;\n}\n.dms-effort-slider {\n --dms-progress: 50%;\n position: relative;\n width: 100%;\n height: 30px;\n flex: 1 1 0;\n min-width: 0;\n border-radius: 999px;\n isolation: isolate;\n transition: filter 180ms ease;\n}\n.dms-effort-track {\n position: absolute;\n inset: 0;\n overflow: hidden;\n border-radius: inherit;\n background: linear-gradient(100deg, #03040a 0%, #071126 22%, #101d4c 45%, #302262 70%, #5d35a0 100%);\n box-shadow:\n inset 0 1px 0 rgba(189, 199, 255, .15),\n inset 0 -1px 0 rgba(0, 0, 0, .55),\n 0 3px 10px rgba(12, 17, 55, .34);\n}\n.dms-effort-track::after {\n content: \"\";\n position: absolute;\n inset: 0;\n background:\n radial-gradient(circle at 18% 45%, rgba(82, 130, 255, .12), transparent 24%),\n linear-gradient(90deg, rgba(0, 0, 0, .28), transparent 42%, rgba(168, 113, 255, .12));\n pointer-events: none;\n}\n.dms-effort-fx {\n position: absolute;\n z-index: 1;\n inset: 0;\n overflow: hidden;\n border-radius: inherit;\n pointer-events: none;\n}\n.dms-effort-canvas {\n position: absolute;\n z-index: 2;\n inset: 0;\n width: 100%;\n height: 100%;\n opacity: 1;\n image-rendering: pixelated;\n mix-blend-mode: screen;\n transition: filter 140ms ease;\n}\n.dms-effort-flare {\n position: absolute;\n z-index: 3;\n transition: left 70ms linear, filter 140ms ease;\n top: 50%;\n left: var(--dms-progress);\n width: 78px;\n height: 46px;\n border-radius: 50%;\n background: radial-gradient(ellipse at 100% 50%, rgba(255,255,255,.96) 0 4%, rgba(188,189,255,.8) 11%, rgba(106,87,255,.5) 28%, rgba(105,31,255,.2) 49%, transparent 74%);\n filter: blur(2px) saturate(1.25);\n mix-blend-mode: screen;\n transform: translate(-100%, -50%);\n pointer-events: none;\n}\n.dms-effort-flare::before,\n.dms-effort-flare::after {\n content: \"\";\n position: absolute;\n inset: 50% auto auto 100%;\n border-radius: 999px;\n transform: translate(-50%, -50%);\n}\n.dms-effort-flare::before {\n width: 52px;\n height: 1px;\n background: linear-gradient(90deg, transparent, rgba(100,160,255,.42), #f1ecff, rgba(193,82,255,.65), transparent);\n box-shadow: 0 0 7px #9b7cff, 0 0 13px rgba(72,132,255,.64);\n}\n.dms-effort-flare::after {\n width: 1px;\n height: 20px;\n background: linear-gradient(180deg, transparent, rgba(196,190,255,.84), transparent);\n box-shadow: 0 0 7px #9c7cff;\n}\n.dms-effort-knob {\n position: absolute;\n z-index: 4;\n top: 50%;\n left: clamp(14px, var(--dms-progress), calc(100% - 14px));\n width: 28px;\n height: 28px;\n border: 1px solid rgba(255,255,255,.94);\n border-radius: 50%;\n background: #fff;\n box-shadow:\n 0 0 0 2px rgba(92,105,255,.12),\n 0 0 14px rgba(121,82,255,.48),\n 0 2px 7px rgba(0,0,0,.3);\n transform: translate(-50%, -50%);\n transition: left 240ms cubic-bezier(.22,1,.36,1), transform 160ms ease, box-shadow 180ms ease;\n pointer-events: none;\n}\n.dms-effort-input {\n position: absolute;\n z-index: 5;\n inset: -5px 0;\n width: 100%;\n height: calc(100% + 10px);\n margin: 0;\n opacity: 0;\n cursor: grab;\n touch-action: none;\n}\n.dms-effort-input:active { cursor: grabbing; }\n.dms-effort-input:focus-visible + .dms-effort-knob {\n outline: 2px solid var(--dsw-static-blue-400);\n outline-offset: 2px;\n}\n.dms-effort.is-dragging .dms-effort-canvas {\n filter: saturate(1.45) brightness(1.28) contrast(1.06);\n}\n.dms-effort.is-dragging .dms-effort-flare {\n filter: blur(1.5px) saturate(1.6) brightness(1.42);\n transition: none;\n}\n.dms-effort.is-dragging .dms-effort-knob {\n transform: translate(-50%, -50%) scale(1.07);\n transition: none;\n box-shadow:\n 0 0 0 3px rgba(113,115,255,.25),\n 0 0 20px rgba(74,145,255,.86),\n 0 0 31px rgba(171,53,255,.66),\n 0 3px 8px rgba(0,0,0,.32);\n}\n.dms-effort.is-error .dms-effort-slider {\n outline: 1px solid var(--dsw-alias-state-error-secondary);\n outline-offset: 2px;\n}\n.dms-effort.is-busy { opacity: .72; }\n.dms-effort-error { flex: 1 0 100%; margin-top: 8px; padding: 6px 10px; border-radius: 8px; color: var(--dsw-alias-state-error-primary, #c83e4d); background: var(--dsw-alias-state-error-tertiary, rgba(220,55,70,.08)); font-size: 11px; line-height: 1.5; }\n/* 常驻容器(内容随状态更新):空时收起,不占位。 */\n.dms-effort-error:empty { display: none; }\n/* 视觉隐藏工具类(SR 播报/错误原文:不占布局但可被读屏读到)。 */\n.dms-sr {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n.dms-model-option {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 20px;\n align-items: center;\n gap: 8px;\n width: 100%;\n min-height: 38px;\n padding: 7px 9px;\n border: 0;\n border-radius: 9px;\n color: inherit;\n background: transparent;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.dms-model-option:hover { background: var(--dsw-alias-fill-tertiary, rgba(120,125,140,.09)); }\n.dms-model-option-copy { min-width: 0; }\n.dms-model-option-name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }\n/* 搜索命中片段:名称内标下划线(命中描述/供应商时不标,见 nameHit)。 */\n.dms-hit {\n text-decoration: underline;\n text-decoration-color: var(--dsw-static-blue-400, #4d70ff);\n text-decoration-thickness: 2px;\n text-underline-offset: 3px;\n}\n.dms-model-option-desc { display: block; margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9296a0); font-size: 10px; }\n.dms-model-check { color: var(--dsw-static-deepseek-500, #4d70ff); font-size: 15px; text-align: center; }\n/* 方向键导航把焦点落在选项上。选中态是「背景 + 加粗 + 勾号」,悬停是「背景」,\n 两者与焦点指示必须两两可辨 —— 只用同一个 hover 背景 token 会让「焦点落在\n 选中行上」完全不可见(roving 的默认落点恰恰优先选中行)。 */\n.dms-model-option:focus-visible {\n background: var(--dsw-alias-interactive-bg-hover);\n box-shadow: inset 0 0 0 2px var(--dsw-alias-border-l3);\n}\n/* 当前选中行:仅靠勾号不足以在密集列表里定位。 */\n.dms-model-optionSelected { background: var(--dsw-alias-interactive-bg-hover); }\n.dms-model-optionSelected .dms-model-option-name { font-weight: 600; }\n/* select 进行中:官方以 disabled 变灰表达,这里用 aria-busy 驱动同样的视觉、\n 不夺键盘焦点(aria-disabled 保连续性,见 ModelOption)。aria-busy 挂在\n role=menu/listbox 容器上(.dms-groups)—— 挂在外层 .dms-menu 会罩住\n live region、压制其播报。 */\n.dms-groups[aria-busy=\"true\"] .dms-model-option { color: var(--dsw-alias-label-dimmed); }\n/* 搜索结果里的供应商标是区分跨供应商同名模型的唯一线索,之前无任何样式。 */\n.dms-model-option-provider {\n display: block;\n margin-top: 2px;\n overflow: hidden;\n color: var(--dsw-alias-label-dimmed, #9296a0);\n font-size: 10px;\n line-height: 14px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n@keyframes dms-menu-in {\n from { opacity: 0; transform: translateY(5px) scale(.98); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n}\n@keyframes dms-menu-in-below {\n from { opacity: 0; transform: translateY(-5px) scale(.98); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n}\nbody:not([data-ds-dark-theme]) .dms-effort-slider {\n filter: none;\n}\nbody:not([data-ds-dark-theme]) .dms-effort-track {\n background: var(--dsw-static-blue-75, #e5f0ff);\n box-shadow:\n inset 0 1px 0 rgba(255,255,255,.9),\n inset 0 0 0 1px rgba(80,133,194,.14),\n 0 3px 10px rgba(48,101,165,.13);\n}\nbody:not([data-ds-dark-theme]) .dms-effort-track::before {\n content: \"\";\n position: absolute;\n z-index: 0;\n inset: 0 auto 0 0;\n width: var(--dms-progress);\n border-radius: inherit;\n background: linear-gradient(90deg, #fff 0%, #e2f0ff 20%, #a8d0fb 57%, #438fdf 100%);\n transition: width 240ms cubic-bezier(.22,1,.36,1);\n}\nbody:not([data-ds-dark-theme]) .dms-effort.is-dragging .dms-effort-track::before {\n transition: none;\n}\nbody:not([data-ds-dark-theme]) .dms-effort-track::after {\n z-index: 1;\n background: linear-gradient(90deg, rgba(255,255,255,.48), transparent 34%, rgba(23,101,201,.07));\n}\nbody:not([data-ds-dark-theme]) .dms-effort-canvas {\n opacity: .78;\n mix-blend-mode: multiply;\n}\nbody:not([data-ds-dark-theme]) .dms-effort-flare {\n background: radial-gradient(ellipse at 100% 50%, rgba(255,255,255,.98) 0 5%, rgba(204,231,255,.88) 13%, rgba(91,162,241,.48) 31%, rgba(37,111,207,.16) 53%, transparent 75%);\n filter: blur(2px) saturate(1.12);\n}\nbody:not([data-ds-dark-theme]) .dms-effort-flare::before {\n background: linear-gradient(90deg, transparent, rgba(116,177,244,.34), #fff, rgba(66,139,225,.58), transparent);\n box-shadow: 0 0 7px rgba(58,133,222,.5), 0 0 13px rgba(104,176,255,.38);\n}\nbody:not([data-ds-dark-theme]) .dms-effort-flare::after {\n background: linear-gradient(180deg, transparent, rgba(255,255,255,.94), transparent);\n box-shadow: 0 0 7px rgba(64,137,224,.44);\n}\nbody:not([data-ds-dark-theme]) .dms-effort-knob {\n border-color: rgba(126,160,197,.32);\n box-shadow:\n 0 0 0 2px rgba(58,124,207,.09),\n 0 0 13px rgba(48,118,207,.3),\n 0 3px 8px rgba(39,77,119,.18);\n}\nbody:not([data-ds-dark-theme]) .dms-effort.is-dragging .dms-effort-knob {\n box-shadow:\n 0 0 0 3px rgba(36,105,192,.15),\n 0 0 20px rgba(25,100,201,.45),\n 0 3px 8px rgba(39,77,119,.18);\n}\n@media (prefers-reduced-motion: reduce) {\n .dms-chevron,\n .dms-groupChevron,\n .dms-effort-slider,\n .dms-effort-canvas,\n .dms-effort-knob,\n .dms-effort-flare,\n body:not([data-ds-dark-theme]) .dms-effort-track::before { transition: none; }\n .dms-menu { animation: none; }\n}\n\n/* Inline effort footer pinned below the scrollable model list (single-pane\n menu: switching models and adjusting effort both live in one surface). */\n.dms-effortFooter {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 4px 4px 0 0;\n padding: 6px 10px;\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dms-effortFooterLabel {\n flex: 0 0 auto;\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n line-height: 18px;\n white-space: nowrap;\n}\n.dms-effortFooter .dms-effort { flex: 1 1 auto; min-width: 0; }\n\n`;\n","/**\n * dsh-model-selector — browser half.\n *\n * Replaces the shipped `conversation.input.model` seat with an enhanced one\n * (provider-group collapse + name search) while keeping the exact shared\n * per-session directory (`ctx.modelDirectories`) the /model popup reads, so\n * both entries stay in sync. Shadowing is the sanctioned seam: the slot is\n * `single`, so a registration at a LOWER priority than the shipped occupant\n * (default 0) becomes the rendered winner.\n */\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type { SessionId } from '@deepseek-ai/dsh-session/types'\n// Type-only: pulls the ui-renderer Context merge (ctx.slots), moved here in dsh alpha.2.\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\n// Type-only: pulls the session controller Context merge (ctx.sessions) in alpha.2.\nimport type {} from '@deepseek-ai/dsh-api-session-controller/client'\n// Type-only: pulls the ui-conversation SlotMap merge (the input model seat).\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the model-selection plugin's Context merge (ctx.modelDirectories)\n// plus the injected face type the seat registration hands to the component.\nimport type { ModelSelectInjected } from '@deepseek-ai/dsh-client-ui-model-selection/client'\nimport { ModelSelect, zhDict, enDict } from './ModelSelect.tsx'\nimport { CSS } from './styles.ts'\n\n/** Dictionary namespace owned by this plugin. */\nconst NS = 'modelSelector'\n\n/**\n * 座位优先级。ui-conversation 把 `conversation.input.model` 声明为 kind 'single'\n * (单槽按 priority 升序取最低者渲染),官方占用者\n * (@deepseek-ai/dsh-client-ui-model-selection)注册时不传 priority(即默认 0),\n * 故 -1 恒胜出。**改这个值等于静默换回官方 UI** —— test/plugin-contract.test.mjs\n * 直接断言本常量(而不是扫源码文本,那样一行注释就能骗过它),注册处也不许再写裸字面量。\n */\nexport const SEAT_PRIORITY = -1\n\n/** Required services: the registry, session lookup, locale, the slot seat, and the model directory's Remote faces. */\nexport const inject = [\n 'slots',\n 'sessions',\n 'locale',\n 'modelDirectories',\n 'remote',\n 'remote.session',\n]\n\n/**\n * Client plugin body: register the dictionaries and stylesheet, then take the\n * model seat over the shared directory once `modelDirectories` appears.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, {\n zh: zhDict,\n en: enDict,\n }), 'dsh-model-selector: dictionaries')\n ctx.effect(() => {\n const tag = document.createElement('style')\n tag.dataset.plugin = '@bittersmilezzz/dsh-model-selector'\n tag.textContent = CSS\n document.head.appendChild(tag)\n return () => {\n tag.remove()\n }\n }, 'dsh-model-selector: styles')\n ctx.inject(['modelDirectories'], (scope) => {\n const models = scope.modelDirectories\n const sessions = scope.sessions\n // 有意的座位层重叠,勿当 bug「修掉」:官方 @deepseek-ai/dsh-client-ui-model-selection\n // 自带同名座位(packages/client/ui-model-selection/src/client/index.ts:177,\n // 未传 priority → 默认 0),本插件靠 priority: -1 遮蔽它。ui-conversation 把该座位\n // 声明为 single/session(kind: 'single'; scope: 'session'),single 槽「priority 升序\n // 取最低者渲染」,故 -1 恒胜出。两者共用同一份 ctx.modelDirectories 会话目录,\n // 任一入口的切换就是另一入口的显示——重叠是设计取舍,不是冲突。\n // 重新评估触发条件:官方若改动该座位的默认优先级、或把它从 single 改成 chain\n // (chain 每项都参与渲染,遮蔽不生效),此遮蔽即失效,不能默认仍然成立。\n scope.slots.inject('conversation.input.model', () => scope.slots.register({\n name: 'conversation.input.model',\n locale: NS,\n priority: SEAT_PRIORITY,\n inject: (sessionId: string): ModelSelectInjected => {\n const sid = sessionId as SessionId\n const directory = models.directoryFor(sid)\n const available = sessions.subagentAddress(sid) === undefined\n return {\n available,\n directory: directory.store,\n load: () => {\n if (available) directory.load().catch((error: unknown) => { console.warn('[dsh-model-selector] directory.load failed:', error) })\n },\n // alpha.2 契约:直接透传 directory.select 的 RemoteResult(失败详情随 error\n // 回给调用方播报),不可选时 Promise.resolve(undefined)(与官方注册同款)。\n select: (selection) => available\n ? directory.select(selection)\n : Promise.resolve(undefined),\n }\n },\n }, ModelSelect))\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["IconChevronDownOutlineRegular","IconCheckOutlineRegular","IconCloseFillRegular","useAnchoredMaxHeight","Toast","IconWarningOutlineRegular","zhDict","enDict"],"sources":["../src/client/locales.ts","../src/client/effort.ts","../src/client/effortCanvas.ts","../src/client/effortDrag.ts","../src/client/menuFit.ts","../src/client/roving.ts","../src/client/search.ts","../src/client/keys.ts","../src/client/copy.ts","../src/client/ModelSelect.tsx","../src/client/styles.ts","../src/client/index.ts"],"sourcesContent":["/**\n * `modelSelector` namespace dictionaries for the enhanced model seat.\n *\n * Simplified Chinese is the key-set source of truth; the English dictionary is\n * checked complete against it. Product copy is Chinese-first per repo style.\n */\nexport const zh = {\n\t\"trigger.fallback\": \"选择模型\",\n\t\"trigger.loading\": \"正在加载模型…\",\n\t\"trigger.selectAria\": \"选择模型\",\n\t\"trigger.aria\": \"选择模型,当前 {model}\",\n\t\"trigger.ariaEffort\": \"选择模型,当前 {model},推理等级 {effort}\",\n\t\"menu.aria\": \"模型列表\",\n\t\"menu.effort\": \"推理等级\",\n\t\"search.placeholder\": \"搜索模型\",\n\t\"search.clearAria\": \"清除搜索\",\n\t\"search.noMatch\": \"没有匹配“{query}”的模型。\",\n\t\"search.status\": \"{count} 个结果\",\n\t\"search.more\": \"仅显示前 {shown} 条(共 {total} 条命中),请细化关键词。\",\n\t\"group.toggleAria\": \"{name}({count} 个模型)\",\n\t\"effort.providerDefault\": \"Default\",\n\t\"status.loading\": \"正在刷新模型列表…\",\n\t\"error.action\": \"模型操作失败:{message}\",\n\t\"action.reload\": \"重新加载\",\n\t\"warning.groupLoad\": \"{name} 加载失败:{message}\",\n\t\"empty.models\": \"没有可用的模型。\",\n\t\"empty.efforts\": \"当前模型未提供推理等级。\",\n\t\"effort.failed\": \"档位切换未生效(后端未接受),已恢复原档位\",\n\t\"effort.timeout\": \"档位切换超时,已恢复原档位\",\n\t\"notice.already\": \"已是当前模型,无需切换\",\n\t\"notice.selectFailed\": \"切换失败,请重试\",\n\t\"toast.effortAuto\": \"已自动选到最强思考档:{effort}\",\n\t\"badge.reasoning\": \"推理\",\n\t\"badge.reasoningHint\": \"支持思考等级,切换后自动选到最大思考强度\"\n};\n\n\nexport const en = {\n\t\"trigger.fallback\": \"Select model\",\n\t\"trigger.loading\": \"Loading models…\",\n\t\"trigger.selectAria\": \"Select model\",\n\t\"trigger.aria\": \"Select model, current {model}\",\n\t\"trigger.ariaEffort\": \"Select model, current {model}, reasoning effort {effort}\",\n\t\"menu.aria\": \"Model list\",\n\t\"menu.effort\": \"Effort\",\n\t\"search.placeholder\": \"Search models\",\n\t\"search.clearAria\": \"Clear search\",\n\t\"search.noMatch\": \"No models match “{query}”.\",\n\t\"search.status\": \"{count} results\",\n\t\"search.more\": \"Showing first {shown} of {total} matches; refine your query.\",\n\t\"group.toggleAria\": \"{name} ({count} models)\",\n\t\"effort.providerDefault\": \"Default\",\n\t\"status.loading\": \"Refreshing model list…\",\n\t\"error.action\": \"Model operation failed: {message}\",\n\t\"action.reload\": \"Reload\",\n\t\"warning.groupLoad\": \"{name} failed to load: {message}\",\n\t\"empty.models\": \"No models available.\",\n\t\"empty.efforts\": \"This model provides no reasoning effort levels.\",\n\t\"effort.failed\": \"Failed to switch effort (backend rejected); restored\",\n\t\"effort.timeout\": \"Timed out switching effort; restored\",\n\t\"notice.already\": \"Already the current model\",\n\t\"notice.selectFailed\": \"Switch failed, please try again\",\n\t\"toast.effortAuto\": \"Auto-selected the strongest reasoning level: {effort}\",\n\t\"badge.reasoning\": \"Reasoning\",\n\t\"badge.reasoningHint\": \"Supports reasoning levels; switches land on the strongest\"\n};\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The enhanced model seat copy. */\n modelSelector: keyof typeof zh\n }\n}\n","/**\n * dsh-model-selector — 推理强度档位的纯函数。\n *\n * 从 ModelSelect.tsx 原样搬出(无 JSX、无 DOM、无模块级副作用),以便用\n * node --test 直接跑源码:滑块钳位、档位映射与「取模型最强档」都是易错且\n * 用户可见的逻辑,留在 .tsx 里则无法脱离 React 测试。\n */\nimport type { ModelDirectoryState } from '@deepseek-ai/dsh-client-ui-model-selection/client'\nimport type { ModelProviderGroup, ModelReasoning } from '@deepseek-ai/dsh-api-session-controller/types'\n\n/** One model effort level as advertised by the adapter. */\nexport type EffortLevel = ModelReasoning['efforts'][number]\n/** Per-session model directory snapshot (official state shape). */\nexport type DirectoryState = ModelDirectoryState\n/** The selected model entry, or undefined when nothing resolves. */\nexport type ModelEntry = ModelProviderGroup['models'][number]\n\n/** Canonical strength order of pi-ai thinking levels (strongest = highest). */\nexport const EFFORT_RANK = {\n off: 0,\n minimal: 1,\n low: 2,\n medium: 3,\n high: 4,\n xhigh: 5,\n max: 6,\n}\n\n/** The strongest thinking level a model offers, or undefined for none. */\nexport function maxEffortOf(reasoning: ModelReasoning): string | undefined {\n let best\n for (const effort of reasoning.efforts) {\n const rank = EFFORT_RANK[effort.id as keyof typeof EFFORT_RANK] ?? 0\n if (best === void 0 || rank > best.rank) best = {\n id: effort.id,\n rank,\n }\n }\n return best?.id\n}\n\n/**\n * 已知档位的秩;适配器自造的非规范 id(如 'turbo')返回 undefined —— 与\n * 「已知但秩为 0」(off)区分开。maxEffortOf 对并列的未知档取首个,把它当\n * 「最强档」提交等于随机挑一个档、还可能被宿主拒,故调用方必须先过这一关。\n */\nexport function dmsEffortRank(id: string): number | undefined {\n return Object.prototype.hasOwnProperty.call(EFFORT_RANK, id)\n ? EFFORT_RANK[id as keyof typeof EFFORT_RANK]\n : void 0\n}\n\n/** 选中模型时的自动档位决策。 */\nexport interface ChoosePlan {\n /** 随选择提交的档位 id;undefined = 不提交 reasoningEffort(交给宿主用模型默认档)。 */\n effort: string | undefined\n /** 是否播报「已自动选到最强思考档」:只有确实替用户做了决定(提交档 ≠ 模型默认档)才播。 */\n autoRaised: boolean\n}\n\n/**\n * 选择模型时自动落到最强档的决策(从 choose() 搬出并修正):\n * - 非推理模型 / 无档位 → 不提交 effort;\n * - 最强档是 'off'(模型只提供 off)→ 不提交;\n * - 最强档是**非规范 id**(rank 未知)→ 不提交:maxEffortOf 对并列未知档取\n * 首个,把它当「最强」提交会挑错档甚至被宿主拒(用户看到「切换失败」);\n * - 否则提交已知最强档;只有它不等于模型声明的默认档时才播报落点。\n */\nexport function dmsChoosePlan(reasoning: ModelReasoning | undefined): ChoosePlan {\n if (reasoning === void 0) return { effort: void 0, autoRaised: false }\n const best = maxEffortOf(reasoning)\n if (best === void 0 || best === 'off' || dmsEffortRank(best) === void 0) return { effort: void 0, autoRaised: false }\n return { effort: best, autoRaised: best !== reasoning.defaultEffort }\n}\n\nexport function dmsEffortIndex(levels: readonly EffortLevel[], id: string | undefined): number {\n return levels.findIndex((level) => level.id === id)\n}\n\n/** 索引钳位:档位数为 0 时返回 0,四舍五入后落在 [0, count-1]。 */\nexport function dmsClampIndex(value: number, count: number): number {\n if (count <= 0) return 0\n return Math.max(0, Math.min(count - 1, Math.round(value)))\n}\n\n/**\n * 指针水平位置 → 档位原始值(未四舍五入,拖动中途可落在两档之间;落点由\n * 提交方 clamp)。输入条退化(宽度 ≤ 0 或档位数 < 2)时保持当前值不动。\n */\nexport function dmsPointerRaw(clientX: number, left: number, width: number, levelCount: number, current: number): number {\n if (width <= 0 || levelCount < 2) return current\n return Math.max(0, Math.min(levelCount - 1, (clientX - left) / width * (levelCount - 1)))\n}\n\nexport function dmsCurrentModel(state: DirectoryState): ModelEntry | undefined {\n if (state.current === null) return void 0\n const current = state.current\n const group = state.groups.find((g) => g.id === current.provider)\n const model = group?.models.find((m) => m.id === current.model)\n return model ?? void 0\n}\n\n/**\n * trigger 的 effort 文案:已选模型在 catalog 中查不到时(rc.2 官方目录「保留态」:\n * current 仍留着,模型/提供商已离开目录)回落到目录的 `retainedEffort`。\n * 官方 ModelSelect 同规则——不回落会让 trigger 在模型离开目录后莫名丢掉档位名。\n * 老运行时(rc.1 及更早)无 retainedEffort 字段,回落值为 undefined,行为不变。\n */\nexport function dmsRetainedEffortLabel(state: DirectoryState): string | undefined {\n return state.retainedEffort ?? void 0\n}\n\n/** 当前生效档位:用户已选优先,其次模型默认,最后取中间档。 */\nexport function dmsEffectiveEffortIndex(levels: readonly EffortLevel[], state: DirectoryState): number {\n const reasoning = dmsCurrentModel(state)?.reasoning\n const current = dmsEffortIndex(levels, state.current?.reasoningEffort)\n if (current >= 0) return current\n const fallback = dmsEffortIndex(levels, reasoning?.defaultEffort)\n if (fallback >= 0) return fallback\n return Math.floor((levels.length - 1) / 2)\n}\n\n/** 滑块档位:少于两档(无法构成滑杆)时返回空数组。 */\nexport function dmsSliderLevels(state: DirectoryState): readonly EffortLevel[] {\n const efforts = dmsCurrentModel(state)?.reasoning?.efforts\n return efforts !== void 0 && efforts.length >= 2 ? efforts : []\n}\n\n/**\n * 滑杆是否处于忙态:自身提交中,或共享目录上有任一 select 在途\n * (目录 select 是 last-writer-wins,模型切换在途时再发 effort RPC 会与\n * 它交错——effort 打到旧模型上,与模型切换互相覆盖;choose() 按同一状态\n * 拒绝了模型点击,滑杆必须同规则拒绝交互)。\n *\n * 目录在途的判据是 `pending !== null`(DSH 0.1.7-rc.2 起官方目录语义):\n * 它比 `status === 'selecting'` 早一步置位——`select()` 一被调用就记下\n * pending,RPC 未返回前 status 仍是 idle/ready。只按 status 判断存在一个\n * 窗口期:pending 已写入而 status 未翻,此时拖动会被接受并与在途的 select\n * 交错。旧运行时没有 pending 字段(rc.1 及更早),`pending` 为 undefined\n * 时退回 status 判据,保证向后兼容。\n */\nexport function dmsEffortBusy(committing: boolean, status: DirectoryState['status'], pending?: DirectoryState['pending']): boolean {\n return committing || (pending !== undefined ? pending !== null : status === 'selecting')\n}\n\n/**\n * 超时回滚后 select 迟到成功是否应采纳并同步 UI:\n * 只有当自回滚以来没有任何新的提交改写 committedRef(仍等于回滚前的档位)、\n * 提交纪元未推进(epoch === epochAtCommit,即期间没有发起过新提交——提交失败\n * 回滚后 committedRef 会回到与上一轮回滚相同的值,值比较无法区分「无新提交」\n * 与「新提交失败回滚」,纪元才能区分)、当前无拖动/提交在途,**且生效模型仍是\n * 发起提交时的那个**(sameModel)时,迟到结果才仍对应当前唯一的意图链——此时\n * UI 处于「回滚但后端已生效」的错位态,应补一次同步而不是保持回滚。\n * 否则以新操作链为准:模型被外部改写(另一入口切换 / 宿主推送新投影 / 重连后\n * 同步出别的选择)时,next/index 是在旧模型的档位表上算出来的,写回 UI 会显示\n * 一个后端并未生效的档位。\n */\nexport function dmsShouldAdoptLateSuccess(committed: string, previous: string, dragging: boolean, committing: boolean, epoch: number, epochAtCommit: number, sameModel: boolean): boolean {\n return sameModel && committed === previous && epoch === epochAtCommit && !dragging && !committing\n}\n\n/**\n * 拖动终止事件是否对应当前活动拖动:pointerActive 为假(拖动已结束/从未开始)\n * 或事件 pointerId 与活动指针不一致(迟到/重复事件——典型是 pointercancel 终态\n * 后平台补发的 pointerup)时返回 false,调用方应幂等跳过终止动作:不清状态、\n * 不触发提交/回滚回调。pointerId 缺省(blur 兜底提交,无事件对象)时只要求\n * 拖动仍在进行中。\n */\nexport function dmsIsActiveDrag(pointerActive: boolean, activePointerId: number | null, pointerId: number | undefined): boolean {\n if (!pointerActive) return false\n if (pointerId !== void 0 && activePointerId !== pointerId) return false\n return true\n}\n","/**\n * dsh-model-selector — EffortSlider 的 canvas 辐射特效模块。\n *\n * 从 ModelSelect.tsx 原样搬出(原 EffortSlider 组件内的 330 行状态机里,\n * 绘制与动画循环约占一半且与 React 状态解耦):\n * - dmsDrawRadiation:纯绘制函数,state(progress/dragging)与主题都由参数\n * 传入,不读组件闭包——同一帧可被 pointer 事件、rAF 循环、主题变化各路径复用。\n * - useEffortCanvas:canvas 生命周期(尺寸/DPR、ResizeObserver、主题\n * MutationObserver、visibilitychange、prefers-reduced-motion)与相位缓动\n * 动画循环。外部通过 radiationRef 更新 target/dragging 驱动重绘,hook\n * 返回的 redraw 注册到组件侧 ref(preview 变化时组件直接调它触发一帧)。\n */\nimport * as react from 'react'\n\n/** 绘制状态的最小契约:进度(0..1)+ 是否拖拽(拖拽时加速/改粒子参数)。 */\nexport interface RadiationState {\n progress: number\n dragging: boolean\n /** 目标进度;undefined 表示尚未初始化(首帧同步起跳,不做缓动)。 */\n target?: number\n}\n\n/** 拖动时辐射推挤的缓动系数(越小越跟手),非拖拽时回弹系数。 */\nconst DRAG_EASE = 0.55\nconst IDLE_EASE = 0.12\nconst SETTLE_EPSILON = 0.002\n\n/**\n * 纯绘制:把滑块进度画成拖尾辐射(列能量 + 径向光晕 + 粒子流 + 热核光斑)。\n * 全部可变量(尺寸、相位、进度、拖拽态、深浅色)都来自参数——无模块级状态。\n */\nexport function dmsDrawRadiation(context: CanvasRenderingContext2D, width: number, height: number, time: number, state: RadiationState, isDark: boolean): void {\n const origin = state.progress * width;\n const cell = 4;\n const speed = state.dragging ? 2.8 : 1;\n context.clearRect(0, 0, width, height);\n if (origin <= 0) return;\n context.save();\n context.beginPath();\n context.rect(0, 0, origin, height);\n context.clip();\n for (let x = 0; x < origin; x += cell) {\n const delta = x + cell * 0.5 - origin;\n const distance = Math.abs(delta);\n const phaseA = distance / 10 - time * 74e-4 * speed;\n const phaseB = distance / 23 - time * 41e-4 * speed + 1.7;\n const phaseC = distance / 40 - time * 22e-4 * speed + 3.4;\n const sinA = Math.max(0, Math.sin(phaseA));\n const sinB = Math.max(0, Math.sin(phaseB));\n const sinC = Math.max(0, Math.sin(phaseC));\n const waveA = Math.pow(sinA, 2.6);\n const waveB = Math.pow(sinB, 3.2);\n const waveC = Math.pow(sinC, 4);\n const crest = Math.pow(sinA, 15) + Math.pow(sinB, 18) * 0.78;\n const wave = Math.min(1, waveA * 0.76 + waveB * 0.58 + waveC * 0.32);\n const trail = 0.38 + 0.62 * Math.exp(-distance / 90);\n const pillar = Math.pow(Math.max(0, Math.sin(x / 20 + time * 16e-4)), 3) * 0.27;\n const columnEnergy = trail * (wave * 1.04 + pillar + crest * 0.32);\n if (columnEnergy > 0.012) {\n const nearness = Math.max(0, 1 - distance / 140);\n const red = isDark ? Math.round(42 + 124 * nearness + 75 * wave) : Math.round(28 + 58 * nearness + 15 * wave);\n const green = isDark ? Math.round(56 + 58 * nearness + 44 * crest) : Math.round(88 + 72 * nearness + 30 * crest);\n const blue = isDark ? Math.round(175 + 72 * nearness + 8 * wave) : Math.round(182 + 62 * nearness);\n const alpha = isDark ? Math.min(0.88, columnEnergy * 0.72) : Math.min(0.62, columnEnergy * 0.54);\n context.fillStyle = `rgba(${red}, ${green}, ${blue}, ${alpha})`;\n context.fillRect(x, 0, cell - 1, height);\n }\n for (let y = 0; y < height; y += cell) {\n const deltaY = y + cell * 0.5 - height * 0.5;\n const radial = Math.hypot(delta / 38, deltaY / 11);\n const halo = Math.exp(-radial * 0.96) * 1.08;\n const verticalShape = 0.58 + 0.42 * Math.cos(deltaY / height * Math.PI);\n const grain = 0.72 + 0.28 * Math.sin(x * 0.73 + y * 1.31 + time * 6e-3);\n const alpha = Math.min(0.96, (columnEnergy * 0.88 + halo + crest * 0.19) * verticalShape * grain);\n if (alpha < 0.035) continue;\n const hot = Math.max(0, 1 - radial / 2.4);\n const red = isDark ? Math.round(54 + 148 * hot + 42 * wave + 35 * crest) : Math.round(25 + 72 * hot + 12 * wave);\n const green = isDark ? Math.round(68 + 78 * hot + 46 * crest) : Math.round(98 + 72 * hot + 24 * crest);\n const blue = isDark ? Math.round(186 + 64 * hot) : Math.round(194 + 56 * hot);\n context.fillStyle = `rgba(${red}, ${green}, ${blue}, ${isDark ? alpha : alpha * 0.72})`;\n context.fillRect(x, y, cell - 1, cell - 1);\n }\n }\n for (let i = 0; i < 14; i += 1) {\n const travel = (time * (state.dragging ? 0.16 : 0.065) * (0.78 + i % 5 * 0.09) + i * 23) % Math.max(30, origin + 64);\n const particleX = origin - travel;\n if (particleX < -24 || particleX > width + 16) continue;\n const particleY = 3 + (i * 13 + Math.sin(time * 3e-3 + i) * 5) % Math.max(7, height - 6);\n const length = 4 + i % 4 * 4 + (state.dragging ? 6 : 0);\n const alpha = 0.28 + i % 5 * 0.1;\n const streak = context.createLinearGradient(particleX, 0, particleX + length, 0);\n streak.addColorStop(0, isDark ? \"rgba(72,118,255,0)\" : \"rgba(24,94,184,0)\");\n streak.addColorStop(0.68, isDark ? `rgba(112,135,255,${alpha})` : `rgba(36,108,202,${alpha * 0.72})`);\n streak.addColorStop(1, isDark ? `rgba(236,222,255,${Math.min(1, alpha + 0.26)})` : `rgba(103,175,248,${Math.min(0.82, alpha + 0.18)})`);\n context.fillStyle = streak;\n context.fillRect(particleX, particleY, length, i % 3 === 0 ? 2 : 1);\n }\n const glow = context.createRadialGradient(origin, height / 2, 0, origin, height / 2, 24);\n glow.addColorStop(0, isDark ? \"rgba(255,255,255,.82)\" : \"rgba(255,255,255,.86)\");\n glow.addColorStop(0.14, isDark ? \"rgba(183,190,255,.54)\" : \"rgba(162,210,255,.48)\");\n glow.addColorStop(0.44, isDark ? \"rgba(103,74,255,.28)\" : \"rgba(37,112,207,.22)\");\n glow.addColorStop(1, isDark ? \"rgba(86,31,210,0)\" : \"rgba(25,91,181,0)\");\n context.fillStyle = glow;\n context.fillRect(origin - 26, 0, 52, height);\n context.restore();\n}\n\n/**\n * 辐射画布生命周期 hook:尺寸/DPR 自适应、主题变化监听、动画循环与相位时钟。\n *\n * 外部契约:\n * - radiationRef.current.target / .dragging 由组件在 preview/忙态变化时更新,\n * hook 的动画循环按 target 做指数缓动(拖动中每帧逼近 target,回弹时渐近);\n * - 返回的 redraw 应注册到组件侧 ref,供事件驱动的重绘(拖动一帧、主题翻转)\n * 即时调用;挂载时画首帧并把 progress 直接对齐 target(打开菜单不闪中间态)。\n *\n * 动画成本:拖拽时只画事件驱动帧(pointer 移动触发的单帧),循环空闲时\n * 一帧即停——画布不常驻 rAF,CPU 占用只在缓动回弹窗口内存在。\n */\nexport function useEffortCanvas(canvasRef: react.RefObject<HTMLCanvasElement | null>, radiationRef: react.MutableRefObject<RadiationState>): () => void {\n const redrawRef = react.useRef<(() => void) | null>(null);\n react.useEffect(() => {\n const canvas = canvasRef.current;\n if (canvas === null) return;\n const context = canvas.getContext(\"2d\");\n if (context === null) return;\n const reducedMotion = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n let width = 1;\n let height = 1;\n let frame = 0;\n // Phase clock advances only while the loop runs, so a frozen (idle)\n // effect resumes at the exact wave phase it paused at — no jump.\n let phase = 0;\n let lastFrameAt = 0;\n const resize = () => {\n const bounds = canvas.getBoundingClientRect();\n const ratio = Math.min(window.devicePixelRatio || 1, 2);\n width = Math.max(1, bounds.width);\n height = Math.max(1, bounds.height);\n canvas.width = Math.max(1, Math.round(width * ratio));\n canvas.height = Math.max(1, Math.round(height * ratio));\n context.setTransform(ratio, 0, 0, ratio, 0, 0);\n };\n // Draw one frame at the current phase; returns true while the effect\n // should keep animating (still easing toward its target).\n // Idle = one static frame, zero further CPU — the loop only runs while\n // the glow is settling, instead of forever.\n // While dragging, stop after a single frame: every pointer move bumps\n // `preview` and redraw() draws one event-driven frame, so the glow\n // follows the knob without a back-to-back repaint stream. Continuously\n // repainting this mix-blend canvas is what flickered the whole menu.\n const draw = (): boolean => {\n const r = radiationRef.current;\n if (r.target !== void 0) {\n const k = r.dragging ? DRAG_EASE : IDLE_EASE;\n r.progress += (r.target - r.progress) * k;\n if (Math.abs(r.target - r.progress) < SETTLE_EPSILON) r.progress = r.target;\n }\n // 主题在这里(每次 draw 时)读取一次:dmsDrawRadiation 保持参数输入、\n // 无 DOM 依赖可测;主题翻转由 themeObserver → draw() 即时取到新值。\n const isDark = document.body.hasAttribute(\"data-ds-dark-theme\");\n dmsDrawRadiation(context, width, height, phase, r, isDark);\n if (r.dragging) return false;\n return r.target !== void 0 && Math.abs(r.target - r.progress) > SETTLE_EPSILON;\n };\n const loop = (time: number): void => {\n if (lastFrameAt !== 0) phase += time - lastFrameAt;\n lastFrameAt = time;\n if (draw() && !document.hidden) {\n frame = window.requestAnimationFrame(loop);\n } else {\n frame = 0;\n lastFrameAt = 0;\n }\n };\n const redraw = () => {\n if (reducedMotion.matches) {\n draw();\n return;\n }\n // Restart the loop on demand (pointer/theme/preview changes);\n // a running loop already draws every frame, so no extra draw here.\n if (frame === 0 && !document.hidden) {\n lastFrameAt = 0;\n frame = window.requestAnimationFrame(loop);\n }\n };\n const resizeObserver = new ResizeObserver(() => {\n resize();\n draw();\n });\n const themeObserver = new MutationObserver(() => draw());\n const onVisibility = () => {\n if (!document.hidden) redraw();\n };\n // 跨屏(窗口拖到另一台显示器)时 devicePixelRatio 变化:canvas 的 CSS\n // 尺寸不变,ResizeObserver 不会触发,位图分辨率停留在旧 DPR → 模糊。\n // 按当前 DPR 构造 resolution 媒体查询,DPR 一变旧查询即失配 → change。\n const resolutionQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`);\n const onResolutionChange = () => {\n resize();\n draw();\n };\n if (resolutionQuery.addEventListener !== void 0) {\n resolutionQuery.addEventListener(\"change\", onResolutionChange);\n } else {\n resolutionQuery.addListener(onResolutionChange); // 旧版 WebKit\n }\n resizeObserver.observe(canvas);\n themeObserver.observe(document.body, { attributes: true, attributeFilter: [\"data-ds-dark-theme\"] });\n document.addEventListener(\"visibilitychange\", onVisibility);\n redrawRef.current = redraw;\n resize();\n // 首帧直接把进度对齐 target(若已初始化):菜单打开瞬间不展示中间进度。\n const r = radiationRef.current;\n if (r.target !== void 0) r.progress = r.target;\n draw();\n if (!reducedMotion.matches && !document.hidden) frame = window.requestAnimationFrame(loop);\n return () => {\n window.cancelAnimationFrame(frame);\n resizeObserver.disconnect();\n themeObserver.disconnect();\n if (resolutionQuery.removeEventListener !== void 0) {\n resolutionQuery.removeEventListener(\"change\", onResolutionChange);\n } else {\n resolutionQuery.removeListener(onResolutionChange);\n }\n document.removeEventListener(\"visibilitychange\", onVisibility);\n redrawRef.current = null;\n };\n }, [canvasRef, radiationRef]);\n return react.useCallback(() => redrawRef.current?.(), []);\n}\n","/**\n * dsh-model-selector — EffortSlider 的指针拖动状态机。\n *\n * 从 ModelSelect.tsx 原样搬出(原 EffortSlider 的 8 个 ref 指针生命周期):\n * 拖动只认发起时记录的活动 pointerId,window 捕获阶段的 move/up/cancel 兜底\n * 指针拖出输入条/菜单外的场景;取消(pointercancel / 触控失控)走回滚回调,\n * 不提交。几何换算(clientX → 档位原始值)与忙态判定都经由回调外接,\n * hook 本身不读组件状态,拖动中途组件重渲染(preview setState)不会中断拖动。\n */\nimport * as react from 'react'\nimport { dmsIsActiveDrag, dmsPointerRaw } from './effort.ts'\n\nexport interface EffortDragHandlers {\n onPointerDown: (event: react.PointerEvent<HTMLInputElement>) => void\n onPointerMove: (event: react.PointerEvent<HTMLInputElement>) => void\n onPointerUp: (event: react.PointerEvent<HTMLInputElement>) => void\n onPointerCancel: (event: react.PointerEvent<HTMLInputElement>) => void\n onBlur: () => void\n}\n\nexport interface UseEffortDragOptions {\n /** 当前档位数(levels.length),拖动态期间变化时以最新值为准。 */\n levelCount: () => number\n /** 忙态(提交中/目录 select 在途)时拒绝新开拖动。 */\n canStart: () => boolean\n /** 拖动中预览(组件侧乐观 setState);返回值供组件更新其当前 raw ref。 */\n onPreview: (raw: number) => void\n /** 拖动结束(pointerup / blur 兜底)时提交;拖出后归位也会提交同一档。 */\n onCommit: (raw: number) => void\n /** 取消(pointercancel / 触控失控)时回滚到已提交档,不提交。 */\n onRollback: () => void\n inputRef: react.RefObject<HTMLInputElement | null>\n /** 拖动活状态(true = 指针按下未结束)。组件与 hook 共享同一 ref:\n * 组件侧用它守卫「拖动态不被打断」(commit 迟到采纳判定、目录同步 effect)。 */\n draggingRef: react.MutableRefObject<boolean>\n}\n\n/**\n * 拖动中由 pointerId 校验的几何换算:raw = (clientX - left) / width 映射到档位区间。\n * 输入条退化(宽度 ≤ 0 / 档位数 < 2)时返回 fallback(当前值,不跳档)。\n */\nfunction rawFromPointer(input: HTMLInputElement, clientX: number, levelCount: number, fallback: number): number {\n const bounds = input.getBoundingClientRect();\n return dmsPointerRaw(clientX, bounds.left, bounds.width, levelCount, fallback);\n}\n\nexport function useEffortDrag({ levelCount, canStart, onPreview, onCommit, onRollback, inputRef, draggingRef }: UseEffortDragOptions) {\n const [dragging, setDragging] = react.useState(false);\n const pointerActiveRef = react.useRef(false);\n const activePointerIdRef = react.useRef<number | null>(null);\n // 拖动中最近一次预览的 raw:blur 兜底提交(无 clientX)时用它。\n const lastRawRef = react.useRef(0);\n // 回调经 ref 转发,hook 内部的事件处理器始终持有最新闭包(组件重渲染不重建监听)。\n const callbacksRef = react.useRef({ levelCount, canStart, onPreview, onCommit, onRollback });\n callbacksRef.current = { levelCount, canStart, onPreview, onCommit, onRollback };\n\n const showPointerPreview = react.useCallback((input: HTMLInputElement, clientX: number): void => {\n const raw = rawFromPointer(input, clientX, callbacksRef.current.levelCount(), lastRawRef.current);\n lastRawRef.current = raw;\n callbacksRef.current.onPreview(raw);\n }, []);\n\n const beginDragging = react.useCallback((input: HTMLInputElement, pointerId: number, clientX: number): void => {\n pointerActiveRef.current = true;\n activePointerIdRef.current = pointerId;\n draggingRef.current = true;\n setDragging(true);\n showPointerPreview(input, clientX);\n try {\n if (!input.hasPointerCapture(pointerId)) input.setPointerCapture(pointerId);\n } catch (cause) {\n // 预期内失败:setPointerCapture 在元素已卸载 / 指针已失效等场景会抛\n // NotFoundError/InvalidStateError。影响仅限「指针拖出输入条后由元素继续\n // 接收事件」这条优化路径——window 捕获阶段的 move/up 兜底监听仍然生效,\n // 拖动不会因此中断,无需恢复;留 debug 痕迹便于排查真实异常。\n console.debug(\"[dms-effort] setPointerCapture failed\", cause);\n }\n }, [showPointerPreview]);\n\n const moveDragging = react.useCallback((input: HTMLInputElement, pointerId: number, clientX: number): void => {\n if (!pointerActiveRef.current || activePointerIdRef.current !== pointerId) return;\n showPointerPreview(input, clientX);\n }, [showPointerPreview]);\n\n const stopDragging = react.useCallback((input: HTMLInputElement, pointerId?: number, clientX?: number): void => {\n if (!dmsIsActiveDrag(pointerActiveRef.current, activePointerIdRef.current, pointerId)) return;\n const raw = clientX === void 0 ? lastRawRef.current : rawFromPointer(input, clientX, callbacksRef.current.levelCount(), lastRawRef.current);\n lastRawRef.current = raw;\n pointerActiveRef.current = false;\n activePointerIdRef.current = null;\n draggingRef.current = false;\n setDragging(false);\n if (pointerId !== void 0 && input.hasPointerCapture(pointerId)) {\n input.releasePointerCapture(pointerId);\n }\n callbacksRef.current.onCommit(raw);\n }, []);\n\n // 取消路径的公共清理:pointercancel 是终态事件(规范上不再有该指针的后续事件),\n // 但部分平台/浏览器在 cancel 后仍会补发 pointerup——不清干净会把「取消」误当成\n // 正常提交。此函数经 dmsIsActiveDrag 校验活动指针、清空全部拖动状态、回滚不提交,\n // 幂等(第二次调用因指针已清空而直接返回)。blur 兜底共用 stopDragging 的同一\n // 校验,双触发路径不会产生重复 commit。\n const cancelDragging = (pointerId: number): void => {\n if (!dmsIsActiveDrag(pointerActiveRef.current, activePointerIdRef.current, pointerId)) return;\n pointerActiveRef.current = false;\n activePointerIdRef.current = null;\n draggingRef.current = false;\n setDragging(false);\n callbacksRef.current.onRollback();\n };\n\n // 全局兜底监听:拖动中指针移出输入条/菜单仍留在窗口内时,move/up 由 window 捕获。\n // 只在拖动进行中挂载(pointerdown 命中 → dragging=true),up/cancel/blur 结束\n // 拖动(dragging=false)即卸载——菜单开着但不拖动时窗口上不挂任何监听。pointercancel\n // 是终态,平台随后补发的 pointerup 要么因监听已卸载而收不到、要么走 stopDragging\n // 的 dmsIsActiveDrag 守卫幂等跳过,与常驻挂载时语义一致。挂载缝隙(pointerdown\n // 到 effect 落地之间)内指针仍在输入条上,由输入条自身的 JSX 事件兜住,无事件丢失。\n const globalMoveRef = react.useRef<(event: PointerEvent) => void>(() => {});\n const globalEndRef = react.useRef<(event: PointerEvent) => void>(() => {});\n const globalCancelRef = react.useRef<(event: PointerEvent) => void>(() => {});\n globalMoveRef.current = (event) => {\n const input = inputRef.current;\n if (input !== null) moveDragging(input, event.pointerId, event.clientX);\n };\n globalEndRef.current = (event) => {\n const input = inputRef.current;\n if (input !== null) stopDragging(input, event.pointerId, event.clientX);\n };\n globalCancelRef.current = (event: PointerEvent): void => cancelDragging(event.pointerId);\n react.useEffect(() => {\n if (!dragging) return;\n const move = (event: PointerEvent) => globalMoveRef.current(event);\n const end = (event: PointerEvent) => globalEndRef.current(event);\n const cancel = (event: PointerEvent) => globalCancelRef.current(event);\n window.addEventListener(\"pointermove\", move, true);\n window.addEventListener(\"pointerup\", end, true);\n window.addEventListener(\"pointercancel\", cancel, true);\n return () => {\n window.removeEventListener(\"pointermove\", move, true);\n window.removeEventListener(\"pointerup\", end, true);\n window.removeEventListener(\"pointercancel\", cancel, true);\n };\n }, [dragging]);\n\n const handlers: EffortDragHandlers = {\n onPointerDown: (event: react.PointerEvent<HTMLInputElement>) => {\n // 忙态(模型切换/提交在途)不开新拖拽:目录 select 是\n // last-writer-wins,交错的 effort RPC 会打到旧模型上。\n if (!callbacksRef.current.canStart()) return;\n event.preventDefault();\n event.currentTarget.focus();\n beginDragging(event.currentTarget, event.pointerId, event.clientX);\n },\n onPointerMove: (event: react.PointerEvent<HTMLInputElement>) => moveDragging(event.currentTarget, event.pointerId, event.clientX),\n onPointerUp: (event: react.PointerEvent<HTMLInputElement>) => stopDragging(event.currentTarget, event.pointerId, event.clientX),\n onPointerCancel: (event: react.PointerEvent<HTMLInputElement>) => {\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId);\n }\n cancelDragging(event.pointerId);\n },\n onBlur: () => {\n const input = inputRef.current;\n if (input !== null) stopDragging(input);\n },\n };\n\n return {\n dragging,\n draggingRef,\n handlers,\n };\n}\n","/**\n * dsh-model-selector — 菜单弹出方向与高度钳位的纯函数。\n *\n * 从 ModelSelect.tsx 原样搬出(无 JSX、无 DOM、无模块级副作用),以便用\n * node --test 直接跑源码:方向决策(向上 vs 向下弹)和向下弹的高度钳位都是\n * 用户可见且曾在矮窗口/滚动场景出过 bug 的逻辑,留在 .tsx 里则无法脱离\n * 浏览器测试。\n */\n\n/** 菜单设计最大高度(px);实际由 useAnchoredMaxHeight 按视口可用空间钳位。\n * 调法:调大允许更高菜单(矮视口下仍会被钳到视口内),调小菜单更矮。 */\nexport const MENU_MAX_HEIGHT = 420;\n/** 与视口边缘保留的距离(px);对齐 primitives 里未导出的 MARGIN。\n *\n * 调用 `useAnchoredMaxHeight` 时**显式传这个值**作为 margin 参数(见\n * ModelSelect.tsx 的调用点),而不是靠官方默认值恰好也是 12 的巧合:上游一旦把\n * 默认 MARGIN 改掉,隐式依赖会让本菜单的贴边距离跟着漂,而菜单自身毫无察觉。\n * `test/official-seat-shadow.test.mjs` 反向钉住「两边仍相等」——官方改了会红,\n * 那时再决定是跟随还是显式分叉。\n * 改动同时影响向上/向下弹与水平钳位的边距,需与官方 MARGIN 意图保持一致。 */\nexport const MENU_VIEWPORT_MARGIN = 12;\n\n/**\n * 向上弹当且仅当 trigger 上方可用空间不小于下方可用空间:\n * 取上下两侧中空间更大的一侧,避免矮窗口把面板挤到视口下沿外面。\n */\nexport function dmsMenuAbove(triggerTop: number, triggerBottom: number, viewportHeight: number): boolean {\n return triggerTop >= viewportHeight - triggerBottom;\n}\n\n/** 向下弹时按 trigger 下方空间钳位菜单高度(top-anchored,不能复用向上弹的 fit)。 */\nexport function dmsBelowMaxHeight(triggerBottom: number, viewportHeight: number, cap: number): number {\n return Math.max(0, Math.min(cap, viewportHeight - triggerBottom - MENU_VIEWPORT_MARGIN));\n}\n\n/**\n * 菜单水平钳位:默认右锚定(right:0,与 seat 右缘对齐);seat 右缘左侧放不下\n * 整幅菜单(seat 靠输入区左下 + 窄窗口)、或 seat 右缘本身已越出视口右缘\n * (页面横向溢出/缩放时 rect.right 可大于 innerWidth,右锚定会把整幅菜单\n * 拖出屏外)时,返回钳到视口内的 left 值,调用方改用 left 锚定。\n * 返回 undefined 表示保持默认右锚定。\n * menuWidth 传菜单实际渲染宽度(offsetWidth),与 CSS `min(280px, 100vw-32px)` 解耦。\n *\n * **坐标系**:入参与返回值都是**视口坐标**。菜单是 .dms-root(position:\n * relative)内的 absolute 元素,left 的参考系是 root 的 padding box —— 调用方\n * 必须减去 root 的左缘再写进 style.left,否则窄窗口下菜单整体右移、横向溢出\n * 时整幅画到屏外。\n */\nexport function dmsMenuLeft(rootRight: number, menuWidth: number, viewportWidth: number, margin: number): number | undefined {\n if (rootRight - menuWidth >= margin && rootRight <= viewportWidth - margin) return void 0;\n return Math.min(\n Math.max(margin, rootRight - menuWidth),\n Math.max(margin, viewportWidth - margin - menuWidth),\n );\n}\n","/**\n * dsh-model-selector — 菜单 roving tabindex 的纯决策逻辑(node 可测)。\n *\n * 模型列表可达数百行,若每行都是 Tab 停靠点,Tab 键会陷入行海;roving\n * tabindex(WAI-ARIA menu 模式的通行做法)只让「活动行」留在 Tab 序里,\n * 其余行 tabindex=-1、仅方向键可达。本模块只负责一件事:给定当前渲染的\n * 行集合与选中行,算出 Tab 从搜索框进入列表时应停靠的默认行键。\n */\n/**\n * 模型行键的**唯一构造点**:菜单 DOM 的 `data-row-key`、roving 决策与行焦点\n * 回调必须同源。此前这三处各自拼模板串,一次局部改名就能让 dmsDefaultRowKey\n * 返回的键匹配不到任何行(Tab 落点静默失效),且测试拿自制副本验证不到。\n */\nexport function dmsRowKey(providerId: string, modelId: string): string {\n return `${providerId}\\u0000${modelId}`\n}\n\n/** 组头行键(分组视图里组头也参与方向键导航)。 */\nexport function dmsHeaderKey(providerId: string): string {\n return `header:${providerId}`\n}\n\nexport interface RovingRowSet {\n /** 搜索命中行键(渲染顺序);null = 非搜索态(分组视图)。 */\n hitKeys: readonly string[] | null\n /** 分组视图中可见(未折叠)的模型行键(渲染顺序)。 */\n modelKeys: readonly string[]\n /** 分组视图中全部组头键(组头始终渲染,即使组被折叠)。 */\n headerKeys: readonly string[]\n /** 当前选中行键;null = 尚无选中。 */\n selectedKey: string | null\n}\n\n/**\n * Tab 默认落点:搜索态取「选中行若在命中集内,否则首个命中」;分组态取\n * 「选中行若可见,否则首个可见模型行」;全部折叠(无可见模型行)时回退到\n * 首个组头。返回 null 表示当前没有任何可聚焦行(空态由菜单外的 role=status\n * 播报,列表容器不渲染)。\n */\nexport function dmsDefaultRowKey(set: RovingRowSet): string | null {\n if (set.hitKeys !== null) {\n if (set.hitKeys.length === 0) return null\n return set.selectedKey !== null && set.hitKeys.includes(set.selectedKey) ? set.selectedKey : set.hitKeys[0]!\n }\n if (set.modelKeys.length > 0) {\n return set.selectedKey !== null && set.modelKeys.includes(set.selectedKey) ? set.selectedKey : set.modelKeys[0]!\n }\n return set.headerKeys[0] ?? null\n}\n","/**\n * dsh-model-selector — 搜索匹配、高亮区间与列表空态的纯函数(node 可测)。\n *\n * 从 ModelSelect.tsx 原样搬出:归一化、haystack 构造、命中窗口截断、名称高亮\n * 区间与「无模型 / 无命中」的优先级都是用户可见且曾经出过错的分支,留在 .tsx\n * 里只能靠浏览器手测。\n */\nimport type { ModelProviderGroup, ModelSelection } from '@deepseek-ai/dsh-api-session-controller/types'\n\n/** 搜索索引条目:模型 + 折叠后的 haystack + 选中载荷。 */\nexport interface SearchChoice {\n group: ModelProviderGroup\n model: ModelProviderGroup['models'][number]\n /** dmsFold 之后的搜索文本(名称/描述/供应商名/id)。 */\n haystack: string\n selection: ModelSelection\n}\n\n/** 搜索命中的渲染条目。 */\nexport interface SearchHit {\n group: ModelProviderGroup\n model: ModelProviderGroup['models'][number]\n /** 名称内的命中区间(原串坐标);null = 命中落在描述/供应商/id 上,或无法安全定位。 */\n nameHit: { start: number; end: number } | null\n}\n\n/** 搜索结果:items 为窗口内条目(渲染用),total 为命中总数(播报/截断提示用)。 */\nexport interface SearchResult {\n items: SearchHit[]\n total: number\n}\n\n/**\n * 搜索归一化:NFKD 分解 → 去组合标记 → 小写。\n *\n * 比裸 `toLowerCase()` 宽容:`'İstanbul'` 能被 `'istanbul'` 命中(裸 toLowerCase\n * 把 U+0130 折成 `'i̇'` 两个码位,与 ASCII `'i'` 反而不匹配 → 漏检;`'café'` 同理)。\n * 注意它**可能改变码位长度**,所以不能用它的下标去切原串 —— 高亮区间另走\n * {@link dmsNameHit},只在长度不变时给区间。\n */\nexport function dmsFold(text: string): string {\n return text.normalize('NFKD').replace(/\\p{M}+/gu, '').toLowerCase()\n}\n\n/** 搜索词归一化(去首尾空白后折叠)。空串 = 非搜索态。 */\nexport function dmsNormalizeQuery(raw: string): string {\n return dmsFold(raw.trim())\n}\n\n/** 模型条目的搜索文本(haystack)。与 choices 构建处同源,避免两处漂移。 */\nexport function dmsHaystack(group: ModelProviderGroup, model: ModelProviderGroup['models'][number]): string {\n return dmsFold(`${model.name}\\n${model.description ?? ''}\\n${group.name}\\n${model.id}\\n${group.id}`)\n}\n\n/**\n * 名称内的命中区间:折叠后长度不变时用下标定位;长度变化(含会改变码位数的\n * 大小写/变音符映射)时返回 null —— 宁可不高亮,也不能用错位的区间去切原串\n * (那会把高亮画到别的字符上,甚至越界)。\n */\nexport function dmsNameHit(name: string, normalizedQuery: string): { start: number; end: number } | null {\n if (normalizedQuery === '') return null\n const folded = dmsFold(name)\n if (folded.length !== name.length) return null\n const at = folded.indexOf(normalizedQuery)\n return at < 0 ? null : { start: at, end: at + normalizedQuery.length }\n}\n\n/**\n * 搜索:遍历 choices 计数全部命中,只构造窗口内(前 limit 条)的渲染条目 ——\n * 宽泛关键词(单字母)命中数百条时,每次击键省掉数百次对象分配与名称匹配;\n * total 单独累计,播报与「仅显示前 N 条」提示仍然准确。\n */\nexport function dmsSearchHits(\n choices: readonly SearchChoice[],\n normalizedQuery: string,\n limit: number,\n): SearchResult | null {\n if (normalizedQuery === '') return null\n const items: SearchHit[] = []\n let total = 0\n for (const choice of choices) {\n if (!choice.haystack.includes(normalizedQuery)) continue\n total += 1\n if (items.length >= limit) continue\n items.push({ group: choice.group, model: choice.model, nameHit: dmsNameHit(choice.model.name, normalizedQuery) })\n }\n return { items, total }\n}\n\n/** 列表区空态:'noModels' = 目录里没有模型,'noHits' = 搜索无命中,null = 正常渲染列表。 */\nexport type MenuEmptyState = 'noModels' | 'noHits' | null\n\n/**\n * 空态判定:**无模型优先于无命中** —— 目录为空而用户又输了关键词时,真相是\n * 「没有可用的模型」(该引导用户去配置/加载目录),不是「没有匹配关键词的模型」\n * (那会引导用户去改关键词,而无论改什么都不会有结果)。\n */\nexport function dmsMenuEmptyState(input: {\n status: string\n choiceCount: number\n searching: boolean\n hitCount: number\n}): MenuEmptyState {\n if (input.status === 'ready' && input.choiceCount === 0) return 'noModels'\n if (input.searching && input.hitCount === 0) return 'noHits'\n return null\n}\n","/**\n * dsh-model-selector — 菜单键盘状态机的纯决策逻辑(node 可测)。\n *\n * 从 ModelSelect.tsx 的 onRootKeyDown 原样搬出(无 JSX、无 DOM、无模块级\n * 副作用):Escape 分层、搜索框内方向键进列表、Home/End 跳首末、Enter 选\n * 首个命中,都是分支密集且易被「顺手改坏」的用户可见逻辑,留在 .tsx 里\n * 只能靠浏览器手测。事件处理器只做 dispatch,分支判定全在这里。\n */\n\n/** 键盘事件归一化后的输入面。 */\nexport interface MenuKeyInput {\n /** event.key(按原样比较,不做大小写归一)。 */\n key: string\n /**\n * IME 组合输入中。调用方除 isComposing 外还要折算 Safari 的 keyCode 229:\n * WebKit 在 compositionend 之后补发的那次 Enter 带 isComposing === false,\n * 只有 keyCode 229 能识别 —— 否则中文候选上屏会直接选中首个命中、切换模型。\n */\n composing: boolean\n /** 菜单是否打开。 */\n open: boolean\n /** 焦点是否在搜索框内。 */\n fromSearch: boolean\n /** 焦点是否在菜单容器内(搜索框/行/滑杆;trigger 不算)。 */\n inMenu: boolean\n /** 焦点是否不在任何 input 上(range 滑杆也是 input,方向键归它调档)。 */\n onNonInput: boolean\n /** 搜索词是否非空。 */\n hasQuery: boolean\n /** 当前可聚焦的行数(搜索态 = 命中数,分组态 = 组头 + 可见模型行)。 */\n rowCount: number\n /** 搜索命中总数(Enter 选中首个命中的判据)。 */\n hitCount: number\n}\n\n/** 决策结果:调用方据此执行副作用(preventDefault 由调用方按需决定)。 */\nexport type MenuKeyAction =\n | { type: 'none' }\n | { type: 'clearQuery'; focusSearch: boolean }\n | { type: 'close' }\n | { type: 'moveFocus'; offset: number }\n | { type: 'focusEdge'; last: boolean }\n | { type: 'chooseFirstHit' }\n\n/**\n * 按键 → 动作。无行可去时对方向键/Home/End 返回 none 而不是吞键:列表为空\n * (加载中/无命中/全折叠)时搜索框里的上下键仍应有移光标语义,preventDefault\n * 由调用方在真正执行动作时才发。\n */\nexport function dmsMenuKeyAction(input: MenuKeyInput): MenuKeyAction {\n if (input.composing) return { type: 'none' }\n const { key, open, fromSearch, inMenu, onNonInput, hasQuery, rowCount, hitCount } = input\n if (key === 'Escape' && open) {\n // Escape 分层:查询非空且焦点在菜单内时第一按只清词(列表内清词会让命中行\n // 卸载,焦点还回搜索框;搜索框内清词焦点留在原处),再按一次才关菜单。\n // 焦点在 trigger 上(不在菜单内)时直接关 —— 「打开后马上关」的常规路径。\n if (hasQuery && inMenu) return { type: 'clearQuery', focusSearch: !fromSearch }\n return { type: 'close' }\n }\n if (!open) return { type: 'none' }\n if ((key === 'ArrowDown' || key === 'ArrowUp') && (onNonInput || fromSearch)) {\n // 搜索框自动聚焦后,方向键原本只在输入框内移光标(键盘导航死路);现在\n // 搜索框内的上下键也进入结果列表。滑杆(input)上的方向键不劫持 —— 那是调档键。\n if (rowCount === 0) return { type: 'none' }\n return { type: 'moveFocus', offset: key === 'ArrowDown' ? 1 : -1 }\n }\n if ((key === 'Home' || key === 'End') && onNonInput) {\n if (rowCount === 0) return { type: 'none' }\n return { type: 'focusEdge', last: key === 'End' }\n }\n // 只有搜索框内的 Enter 才选中第一个命中:焦点落在 effort 滑杆等其它控件时,\n // Enter 不应把用户的选择抢走。\n if (key === 'Enter' && fromSearch && hitCount > 0) return { type: 'chooseFirstHit' }\n return { type: 'none' }\n}\n\n/**\n * roving 环绕下标:焦点不在任何行上(activeIndex < 0)时向下从首行进、向上从\n * 末行进;已在某行上时按 offset 循环。length <= 0 返回 -1(无可去之处)。\n */\nexport function dmsNextRowIndex(activeIndex: number, offset: number, length: number): number {\n if (length <= 0) return -1\n const start = activeIndex < 0 ? (offset > 0 ? -1 : 0) : activeIndex\n return (start + offset + length) % length\n}\n","/**\n * dsh-model-selector — trigger 文案回退链的纯函数(node 可测)。\n *\n * 从 ModelSelect.tsx 原样搬出:四条近乎互相复制的表达式(可见文本 / title /\n * aria)含三层嵌套三目,是本组件里 drift 风险最高的一段,抽出来用假 t 直接测。\n */\nimport type { ModelSelection } from '@deepseek-ai/dsh-api-session-controller/types'\nimport type { Translate } from '@deepseek-ai/dsh-client-ui-slots'\n\n/**\n * 本模块用到的字典键。用具体联合而不是 string:调用处传进来的\n * `TranslateNS<'modelSelector'>` 参数逆变后要求这些键必须真的在字典里 ——\n * 字典改名会在编译期报错,而不是运行时渲染出原始 key。\n */\nexport type TriggerKey =\n | 'trigger.fallback'\n | 'trigger.loading'\n | 'trigger.selectAria'\n | 'trigger.aria'\n | 'trigger.ariaEffort'\n\n/** 文案输入:目录命中的名字优先,命中不到时回退到原始 id。 */\nexport interface TriggerCopyInput {\n current: ModelSelection | null\n /** 当前选中项在目录里的名称;不在目录中时为 undefined。 */\n modelName: string | undefined\n /** 当前选中项所属供应商名;不在目录中时为 undefined。 */\n providerName: string | undefined\n /** 当前生效的推理档位名;模型无推理或档位未解析时为 undefined。 */\n effortLabel: string | undefined\n /** current 为 null 且目录仍在加载。 */\n waiting: boolean\n}\n\n/** trigger 的四路文案。 */\nexport interface TriggerCopy {\n modelLabel: string\n providerLabel: string | undefined\n /** 可见文本(`·` 拼接 effort)。 */\n label: string\n /** title:供应商 · 可见文本。 */\n title: string\n /** aria-label:读屏可听到模型 + 档位。 */\n aria: string\n}\n\n/**\n * trigger 文案。目录成员资格只是参考(routable 契约):current 匹配不到任何\n * group 不代表没有选择 —— 此时显示 provider/model 原始 id,而不是「选择模型」。\n */\nexport function dmsTriggerCopy(input: TriggerCopyInput, t: Translate<TriggerKey>): TriggerCopy {\n const { current, modelName, providerName, effortLabel, waiting } = input\n const modelLabel = modelName\n ?? (waiting\n ? t('trigger.loading')\n : current === null ? t('trigger.fallback') : `${current.provider}/${current.model}`)\n // 供应商名只在目录命中时才有意义(原始 id 回退态不显示供应商行)。\n const providerLabel = modelName === undefined ? void 0 : providerName\n const label = effortLabel === undefined ? modelLabel : `${modelLabel} · ${effortLabel}`\n const title = providerLabel === undefined ? label : `${providerLabel} · ${label}`\n const named = providerLabel === undefined ? modelLabel : `${providerLabel} ${modelLabel}`\n // aria 的模型部分:目录命中时用「供应商 模型名」,回退态用原始 id(与可见文本同源)。\n const ariaModel = modelName === undefined ? (current === null ? null : `${current.provider}/${current.model}`) : named\n const aria = waiting\n ? t('trigger.loading')\n : ariaModel === null\n ? t('trigger.selectAria')\n : effortLabel === undefined\n ? t('trigger.aria', { model: ariaModel })\n : t('trigger.ariaEffort', { model: ariaModel, effort: effortLabel })\n return { modelLabel, providerLabel, label, title, aria }\n}\n","/**\n * ModelSelect: the enhanced composer model seat (`conversation.input.model`).\n *\n * A single-pane menu (simpler than the shipped two-level root menu): opening\n * the seat lands directly on the model list — search box + collapsible\n * provider groups — with the current model's effort slider pinned inline at\n * the bottom of the menu, so switching models and tuning reasoning effort\n * both happen in one surface without an intermediate page.\n *\n * Data and submission ride the SAME per-session directory the /model popup\n * shares (via `modelDirectories`), so a switch made here is what the popup\n * shows next and vice versa. Icons, Toast and the menu fit/dismiss hooks are\n * reused from @deepseek-ai/dsh-client-ui-primitives at runtime; colors\n * come from `--dsw-*` tokens in the injected stylesheet.\n */\nimport * as react from 'react'\nimport { zh as zhDict, en as enDict } from './locales.ts'\n// Runtime reuse of the official primitives (already an external in\n// tsdown.config.ts and a platform module in the web loader): Toast is the\n// sanctioned surface for a rejected selection, and the two hooks fit/dismiss\n// the open menu. The list itself stays hand-rolled — primitives' Menu renders\n// every entry (footer included) as a <button role=\"menuitem\">, which cannot\n// host the effort range input or the search field.\nimport { Toast, IconWarningOutlineRegular, IconChevronDownOutlineRegular, IconCheckOutlineRegular, IconCloseFillRegular, useAnchoredMaxHeight, useDismissOnOutsidePointer } from '@deepseek-ai/dsh-client-ui-primitives'\n\nexport { zhDict, enDict }\n// Type-only: official model-selection directory types (the enhanced seat's\n// data contract — same shared per-session directory the /model popup reads).\nimport type { ModelDirectoryState, ModelSelectInjected } from '@deepseek-ai/dsh-client-ui-model-selection/client'\nimport type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SnapshotStore } from '@deepseek-ai/dsh-client-store'\n// Effort helpers live in effort.ts (pure, no JSX/DOM) so node --test can cover them.\nimport { dmsChoosePlan, dmsClampIndex, dmsEffortIndex, dmsEffectiveEffortIndex, dmsEffortBusy, dmsRetainedEffortLabel, dmsShouldAdoptLateSuccess, dmsSliderLevels } from './effort.ts'\n// 辐射画布(绘制纯函数 + 动画循环 hook)与指针拖动状态机:从 EffortSlider\n// 组件内拆分独立成模块,组件本体只保留档位/提交/渲染三件事。\nimport { useEffortCanvas } from './effortCanvas.ts'\nimport { useEffortDrag } from './effortDrag.ts'\n// Menu direction/clamp helpers likewise (pure — the direction flip and the\n// below-clamp are user-visible and were previously only browser-testable).\nimport { MENU_MAX_HEIGHT, MENU_VIEWPORT_MARGIN, dmsMenuAbove, dmsBelowMaxHeight, dmsMenuLeft } from './menuFit.ts'\n// roving tabindex 的默认落点决策与行键构造(纯函数,node 可测——行键格式必须\n// 与渲染出的 data-row-key 同源,否则默认落点静默失效)。\nimport { dmsDefaultRowKey, dmsHeaderKey, dmsRowKey } from './roving.ts'\n// 搜索(归一化/命中窗口/高亮区间/空态)与键盘状态机:分支密集的用户可见逻辑,\n// 全部抽成纯函数模块(node 可测),事件处理器只做 dispatch。\nimport { dmsHaystack, dmsMenuEmptyState, dmsNormalizeQuery, dmsSearchHits } from './search.ts'\nimport type { SearchChoice, SearchResult } from './search.ts'\nimport { dmsMenuKeyAction, dmsNextRowIndex } from './keys.ts'\nimport { dmsTriggerCopy } from './copy.ts'\n// Type-only: the model catalog carrier types (moved here in dsh alpha.2).\nimport type { ModelSelection, ModelProviderGroup } from '@deepseek-ai/dsh-api-session-controller/types'\n\n/** Per-session model directory snapshot (official state shape). */\ntype DirectoryState = ModelDirectoryState\n\n/** 官方座位注入面的 select 结果(alpha.2 契约:RemoteResult,或 undefined = 该会话不可选)。 */\ntype SelectResult = Awaited<ReturnType<ModelSelectInjected['select']>>\n\ninterface EffortSliderProps {\n state: DirectoryState\n select: ModelSelectInjected['select']\n t: TranslateNS<'modelSelector'>\n /** 提交失败(超时/拒绝)时回调:让菜单把错误条归到「加载失败」之外(见 renderErrorStrip)。 */\n onSelectFailure: () => void\n}\n\n\n// 图标直接用 primitives 的组件(此前本仓自带一份逐字节相同的 SVG 副本);\n// 图标组件只接受 size/className,aria-hidden 由调用处的包装元素承载。\nconst IconChevronDown = <IconChevronDownOutlineRegular size={14} />;\nconst IconCheck = <IconCheckOutlineRegular size={14} />;\nconst IconClear = <IconCloseFillRegular size={14} />;\n/**\n* How long a successfully loaded directory snapshot is trusted before the\n* menu re-fetches it over RPC. The snapshot lives in the per-session store,\n* so reopening the menu within this window costs zero RPC and zero re-render.\n* 调法:调大 → 打开更省(RPC/重渲染都免),但目录侧改动要等窗口过期才可见;\n* 调小 → 目录更新更实时。目录内容由官方 modelDirectories 推送,本值只影响\n* 打开瞬间是否信任快照。\n*/\nconst DIRECTORY_STALE_MS = 3e4;\n/** 搜索命中渲染上限:宽泛关键词(如单字母)命中数百条时避免 DOM 爆炸。\n * 调法:调大 → 宽泛搜索看到更多命中(渲染与搜索成本线性上升);调小 → 更省。 */\nconst MAX_VISIBLE_HITS = 100;\n/** effort select RPC 的超时护栏:官方 select 无超时契约,RPC 永久挂起时\n * 必须释放滑杆的 committing 锁并回滚,否则滑杆被锁死到菜单关闭。\n * 调法:后端/网络慢时调大避免误回滚(滑杆锁死时间随之变长);调小更早释放交互。 */\nconst EFFORT_COMMIT_TIMEOUT_MS = 12e3;\n// ── 推理强度滑块(移植自 dsh-reasoning-effort:辐射特效 + 档位随模型自动适配)──\n// 绘制与动画循环见 ./effortCanvas.ts(dmsDrawRadiation / useEffortCanvas),\n// 指针拖动状态机见 ./effortDrag.ts(useEffortDrag);本组件只做档位状态、\n// 提交/回滚与渲染。\nexport const EffortSlider = react.memo(function EffortSlider({ state, select, t, onSelectFailure }: EffortSliderProps) {\n\tconst levels = dmsSliderLevels(state);\n\t// 惰性初始化到当前生效档:useState(\"\")/0 会让菜单打开的第一帧档位名为空、\n\t// 进度闪 0%,随后才被同步 effect 纠正(首帧即正确,无闪烁)。\n\tconst [effort, setEffort] = react.useState(() => levels[dmsEffectiveEffortIndex(levels, state)]?.id ?? \"\");\n\tconst [preview, setPreview] = react.useState(() => dmsEffectiveEffortIndex(levels, state));\n\tconst [committing, setCommitting] = react.useState(false);\n\tconst [localError, setLocalError] = react.useState<string | null>(null);\n\t// 最新 state 经 ref 读取:迟到 settle 的判定要看「此刻」的生效模型,而不是\n\t// 提交发起时闭包里捕获的那份(见 dmsShouldAdoptLateSuccess 的 sameModel)。\n\tconst stateRef = react.useRef(state);\n\tstateRef.current = state;\n\tconst canvasRef = react.useRef<HTMLCanvasElement | null>(null);\n\tconst inputRef = react.useRef<HTMLInputElement | null>(null);\n\tconst committedRef = react.useRef(\"\");\n\tconst committingRef = react.useRef(false);\n\tconst previewRef = react.useRef(0);\n\tconst draggingRef = react.useRef(false);\n\t// 提交超时定时器句柄 + 挂载标志:菜单关闭(EffortSlider 卸载)时由 cleanup\n\t// clearTimeout 并置 unmounted——迟到成功采纳/超时回滚的 setState 只在挂载期\n\t// 跑,卸载后不再触碰 React 状态,定时器也不必在卸载后继续空转。\n\tconst commitTimerRef = react.useRef<number | null>(null);\n\tconst mountedRef = react.useRef(false);\n\t// 提交纪元:单调递增,迟到采纳判据靠它区分「无新提交」与「新提交失败回滚」。\n\tconst commitEpochRef = react.useRef(0);\n\t// 辐射状态由组件持有:preview 变化/拖动时更新 target/dragging 驱动重绘;\n\t// progress 惰性对齐当前档位(打开菜单首帧即正确,无中间态缓动)。useRef\n\t// 初始化表达式每次渲染都会求值但仅首帧生效,这里取首次渲染的档位作起点。\n\tconst radiationRef = react.useRef<{ progress: number; dragging: boolean; target?: number }>({\n\t\tprogress: levels.length >= 2 ? dmsEffectiveEffortIndex(levels, state) / (levels.length - 1) : 0.5,\n\t\tdragging: false,\n\t});\n\tconst redrawRef = react.useRef<(() => void) | null>(null);\n\tconst available = state.current !== null && levels.length >= 2;\n\t// pending 取自 state(组件 prop 直接持有):rc.2 官方目录在 select() 入口即写\n\t// pending,status 要等 RPC 返回才翻到 selecting——这段窗口期内必须已算忙,\n\t// 否则拖动/键盘路径会照常交互(commit() 的 :176 虽会拦下 RPC,但已产生\n\t// preview 视觉变化且 is-busy / aria-disabled 未置位,反馈不一致)。\n\tconst busy = dmsEffortBusy(committing, state.status, state.pending);\n\tconst error = localError;\n\treact.useEffect(() => {\n\t\tif (!available || committingRef.current || draggingRef.current) return;\n\t\tconst index = dmsEffectiveEffortIndex(levels, state);\n\t\tconst next = levels[index]?.id ?? \"\";\n\t\tcommittedRef.current = next;\n\t\tpreviewRef.current = index;\n\t\tsetEffort(next);\n\t\tsetPreview(index);\n\t\tsetLocalError(null);\n\t\t// levels 进依赖:同一模型、档位个数不变但档位 id 变了(适配器/目录刷新把\n\t\t// high 换成 xhigh)时也要重同步,否则读数落到原始 id、preview 与新档位错位。\n\t}, [available, levels, state.current?.provider, state.current?.model, state.current?.reasoningEffort]);\n\treact.useEffect(() => {\n\t\tpreviewRef.current = preview;\n\t\tradiationRef.current.target = levels.length >= 2 ? preview / (levels.length - 1) : 0.5;\n\t\tredrawRef.current?.();\n\t}, [preview, levels.length]);\n\tconst redraw = useEffortCanvas(canvasRef, radiationRef);\n\treact.useEffect(() => {\n\t\tredrawRef.current = redraw;\n\t\treturn () => { redrawRef.current = null; };\n\t}, [redraw]);\n\t// 卸载清理:迟到的 select settle 不再 setState(迟到成功采纳/超时回滚的\n\t// setState 都经 mountedRef 守卫);未决的超时定时器直接清除——组件已卸载,\n\t// 回滚/采纳都无人可见,定时器没有存在的意义。\n\treact.useEffect(() => {\n\t\tmountedRef.current = true;\n\t\treturn () => {\n\t\t\tmountedRef.current = false;\n\t\t\tif (commitTimerRef.current !== null) {\n\t\t\t\twindow.clearTimeout(commitTimerRef.current);\n\t\t\t\tcommitTimerRef.current = null;\n\t\t\t}\n\t\t};\n\t}, []);\n\tconst showPointerPreview = react.useCallback((raw: number): void => {\n\t\tpreviewRef.current = raw;\n\t\tsetPreview(raw);\n\t\tsetEffort(levels[dmsClampIndex(raw, levels.length)]?.id ?? \"\");\n\t}, [levels]);\n\tconst rollback = react.useCallback(() => {\n\t\tconst previous = committedRef.current;\n\t\tpreviewRef.current = Math.max(0, dmsEffortIndex(levels, previous));\n\t\tsetEffort(previous);\n\t\tsetPreview(Math.max(0, dmsEffortIndex(levels, previous)));\n\t}, [levels]);\n\tconst commit = react.useCallback(async (raw: number): Promise<void> => {\n\t\tif (dmsEffortBusy(committingRef.current, state.status, state.pending)) return;\n\t\t// 先按钳位后的档位判断是否无操作:落回已提交的同一档(含拖动归位、键盘\n\t\t// 重复按当前档)时,不发多余 RPC,也不闪本地 busy 灰——原实现把这次早退\n\t\t// 放在 setCommitting(true)/乐观 setState 之后,同一档提交仍会闪一次。\n\t\t// levels 非空时 clamp 保证 index ∈ [0, count-1],next 不可能为 undefined。\n\t\tconst index = dmsClampIndex(raw, levels.length);\n\t\tconst next = levels[index]?.id;\n\t\tif (next === void 0) return;\n\t\t// 落回已提交的同一档(拖动归位、键盘重复按当前档):不发多余 RPC,也不闪\n\t\t// 本地 busy 灰——但必须把 preview 归一化回整档。raw 是未取整的分数(见\n\t\t// dmsPointerRaw),不归一化会让旋钮/进度条/画布停在两档之间,而同步 effect\n\t\t// 因依赖未变不会重跑纠正它(此前只有指针路径会留下这个残影)。\n\t\tif (next === committedRef.current) {\n\t\t\tpreviewRef.current = index;\n\t\t\tsetPreview(index);\n\t\t\tsetEffort(next);\n\t\t\treturn;\n\t\t}\n\t\tcommittingRef.current = true;\n\t\tconst previous = committedRef.current;\n\t\t// 提交纪元:每次发起新提交单调递增。迟到采纳判据用它区分「无新提交」与\n\t\t// 「新提交失败回滚」——两者 committedRef 都会回到 previous,值比较无法\n\t\t// 区分,纪元未变才说明迟到结果仍属当前唯一意图链。\n\t\tcommitEpochRef.current += 1;\n\t\tconst epochAtCommit = commitEpochRef.current;\n\t\t// 注意:这里不重置 dragging——commit 的唯二入口是拖动结束(stopDragging\n\t\t// 已先 setDragging(false))与键盘(本就非拖拽),无需防御性复位。\n\t\tsetCommitting(true);\n\t\tsetLocalError(null);\n\t\tpreviewRef.current = index;\n\t\tsetPreview(index);\n\t\tsetEffort(next);\n\t\ttry {\n\t\t\tconst current = state.current;\n\t\t\tif (current === null) throw new Error(t(\"empty.efforts\"));\n\t\t\t// 提交时的模型身份:迟到采纳必须确认生效模型仍是这一个 —— next/index 是在\n\t\t\t// 这份档位表上算出来的,模型被外部改写后写回 UI 会显示一个后端并未生效的档位。\n\t\t\tconst modelKeyAtCommit = dmsRowKey(current.provider, current.model);\n\t\t\t// 超时护栏:官方 select 无超时契约,把它包进带 12s 定时的 Promise——\n\t\t\t// 超时即视为失败(回滚 + 播报),并释放 committing 锁。select 迟到的\n\t\t\t// resolve 不直接丢弃:若它确认了新档位且自回滚以来没有新提交改写\n\t\t\t// committedRef(回滚态仍对应本次提交链),把 UI/committedRef 同步回\n\t\t\t// 实际已生效的档位;迟到的 reject 保持回滚态。timer 在 select 先完成\n\t\t\t// 时清除,不泄漏空转定时器。\n\t\t\tconst result = await new Promise<SelectResult>((resolve, reject) => {\n\t\t\t\tlet settled = false;\n\t\t\t\tconst timer = window.setTimeout(() => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tcommitTimerRef.current = null;\n\t\t\t\t\treject(new Error(t(\"effort.timeout\")));\n\t\t\t\t}, EFFORT_COMMIT_TIMEOUT_MS);\n\t\t\t\t// 定时器句柄进 ref:菜单关闭(本组件卸载)时由 cleanup 直接\n\t\t\t\t// clearTimeout,不再依赖 Promise 闭包把定时器活到 select 迟到 settle。\n\t\t\t\tcommitTimerRef.current = timer;\n\t\t\t\tselect({ provider: current.provider, model: current.model, reasoningEffort: next }).then(\n\t\t\t\t\t(result) => {\n\t\t\t\t\t\tif (settled) {\n\t\t\t\t\t\t\t// 迟到成功且实际生效:committedRef 还是回滚前的 previous\n\t\t\t\t\t\t\t// 且提交纪元未推进(期间没有新的提交/拖动改写它),说明\n\t\t\t\t\t\t\t// 回滚态对应用户当前唯一意图链,后端已确认新档位——补一次\n\t\t\t\t\t\t\t// UI 同步,消除「UI 回滚、后端已改档」的错位;否则以新\n\t\t\t\t\t\t\t// 操作链为准不覆盖(新提交失败回滚后 committedRef 值与\n\t\t\t\t\t\t\t// 上一轮相同,纪元未推进才说明没有新提交)。\n\t\t\t\t\t\t\t// 判定收敛到 dmsShouldAdoptLateSuccess(effort.ts,node 可测)。\n\t\t\t\t\t\t\tconst latest = stateRef.current.current;\n\t\t\t\t\t\t\tconst sameModel = latest !== null && dmsRowKey(latest.provider, latest.model) === modelKeyAtCommit;\n\t\t\t\t\t\t\tif (result?.ok === true && mountedRef.current && dmsShouldAdoptLateSuccess(committedRef.current, previous, draggingRef.current, committingRef.current, commitEpochRef.current, epochAtCommit, sameModel)) {\n\t\t\t\t\t\t\t\tcommittedRef.current = next;\n\t\t\t\t\t\t\t\tpreviewRef.current = index;\n\t\t\t\t\t\t\t\tsetEffort(next);\n\t\t\t\t\t\t\t\tsetPreview(index);\n\t\t\t\t\t\t\t\tsetLocalError(null);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\twindow.clearTimeout(timer);\n\t\t\t\t\t\tcommitTimerRef.current = null;\n\t\t\t\t\t\tresolve(result);\n\t\t\t\t\t},\n\t\t\t\t\t(cause: unknown) => {\n\t\t\t\t\t\tif (settled) return;\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\twindow.clearTimeout(timer);\n\t\t\t\t\t\tcommitTimerRef.current = null;\n\t\t\t\t\t\treject(cause);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t});\n\t\t\tif (result === undefined || !result.ok) {\n\t\t\t\tif (result !== undefined) console.warn('[dsh-model-selector] effort select failed:', result.error);\n\t\t\t\tthrow new Error(t(\"effort.failed\"));\n\t\t\t}\n\t\t\tif (mountedRef.current) {\n\t\t\t\tcommittedRef.current = next;\n\t\t\t\tpreviewRef.current = index;\n\t\t\t\tsetEffort(next);\n\t\t\t\tsetPreview(index);\n\t\t\t}\n\t\t} catch (cause) {\n\t\t\t// 迟到超时/失败不得回滚新意图链:提交 B 快速成功后,提交 A 的 12s 定时器\n\t\t\t// 才到点 reject——此时 committedRef 已是 B 的结果,若按 A 的 previous\n\t\t\t// 回滚就会把用户已成功的 B 覆盖掉(dmsShouldAdoptLateSuccess 只保护\n\t\t\t// 「迟到成功」路径,回滚路径必须自己查纪元闸)。\n\t\t\tif (commitEpochRef.current === epochAtCommit) {\n\t\t\t\t// 通知菜单把错误归到「档位切换」而不是「目录加载」:否则 effort 提交失败\n\t\t\t\t// 会点亮加载错误条 + 无用的「重新加载」按钮(见 renderErrorStrip)。\n\t\t\t\tonSelectFailure();\n\t\t\t\tconst restore = Math.max(0, dmsEffortIndex(levels, previous));\n\t\t\t\tcommittedRef.current = previous;\n\t\t\t\tpreviewRef.current = restore;\n\t\t\t\tif (mountedRef.current) {\n\t\t\t\t\tsetEffort(previous);\n\t\t\t\t\tsetPreview(restore);\n\t\t\t\t\tsetLocalError(cause instanceof Error ? cause.message : String(cause));\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\t// 同上:迟到 settled 的 finally 不得释放新提交的 committing 锁(否则\n\t\t\t// 新提交仍在途,UI 已显示可操作,连点可发起第三条提交)。\n\t\t\tif (commitEpochRef.current === epochAtCommit) {\n\t\t\t\tcommittingRef.current = false;\n\t\t\t\tif (mountedRef.current) setCommitting(false);\n\t\t\t}\n\t\t}\n\t}, [levels, select, state, t, onSelectFailure]);\n\t// 指针拖动状态机(pointer 生命周期 + window 兜底监听)在 effortDrag.ts;\n\t// commit/rollback/showPointerPreview 作回调外接——hook 经 callbacksRef 持有\n\t// 最新闭包,事件触发时取到当前渲染的函数。\n\tconst drag = useEffortDrag({\n\t\tlevelCount: () => levels.length,\n\t\tcanStart: () => !busy,\n\t\tonPreview: showPointerPreview,\n\t\tonCommit: (raw) => void commit(raw),\n\t\tonRollback: rollback,\n\t\tinputRef,\n\t\tdraggingRef,\n\t});\n\tconst { dragging } = drag;\n\t// 拖动态同步到辐射状态与画布(preview effect 已处理 target,这里只切 dragging 速度)。\n\treact.useEffect(() => {\n\t\tradiationRef.current.dragging = dragging;\n\t\tredrawRef.current?.();\n\t}, [dragging]);\n\tconst onKeyDown = (event: react.KeyboardEvent<HTMLInputElement>): void => {\n\t\tconst current = dmsClampIndex(Number(event.currentTarget.value), levels.length);\n\t\tlet target;\n\t\tif (event.key === \"ArrowLeft\" || event.key === \"ArrowDown\" || event.key === \"PageDown\") {\n\t\t\ttarget = Math.max(0, current - 1);\n\t\t} else if (event.key === \"ArrowRight\" || event.key === \"ArrowUp\" || event.key === \"PageUp\") {\n\t\t\ttarget = Math.min(levels.length - 1, current + 1);\n\t\t} else if (event.key === \"Home\") {\n\t\t\ttarget = 0;\n\t\t} else if (event.key === \"End\") {\n\t\t\ttarget = levels.length - 1;\n\t\t}\n\t\tif (target === void 0) return;\n\t\t// 忙态(自身提交中/共享目录 select 在途)吞掉按键:必须先 preventDefault,\n\t\t// 否则 range input 的原生步进会改 DOM value,与受控值漂移。\n\t\tevent.preventDefault();\n\t\tif (busy) return;\n\t\tvoid commit(target);\n\t};\n\tif (!available) return null;\n\tconst count = levels.length;\n\tconst effortName = levels[dmsEffortIndex(levels, effort)]?.name ?? effort;\n\tconst effortDesc = levels[dmsEffortIndex(levels, effort)]?.description;\n\tconst progress = preview / (count - 1) * 100;\n\tconst style = { \"--dms-progress\": `${progress}%` } as react.CSSProperties;\n\treturn (\n\t\t<div\n\t\t\tclassName={`dms-effort${dragging ? \" is-dragging\" : \"\"}${busy ? \" is-busy\" : \"\"}${error === null ? \"\" : \" is-error\"}`}\n\t\t>\n\t\t\t<div\n\t\t\t\tclassName=\"dms-effort-slider\"\n\t\t\t\tstyle={style}\n\t\t\t>\n\t\t\t\t<div className=\"dms-effort-track\" aria-hidden=\"true\" />\n\t\t\t\t<div className=\"dms-effort-fx\" aria-hidden=\"true\">\n\t\t\t\t\t<canvas ref={canvasRef} className=\"dms-effort-canvas\" />\n\t\t\t\t\t<span className=\"dms-effort-flare\" />\n\t\t\t\t</div>\n\t\t\t\t<input\n\t\t\t\t\tref={inputRef}\n\t\t\t\t\tclassName=\"dms-effort-input\"\n\t\t\t\t\ttype=\"range\"\n\t\t\t\t\tmin=\"0\"\n\t\t\t\t\tmax={count - 1}\n\t\t\t\t\tstep=\"0.01\"\n\t\t\t\t\tvalue={preview}\n\t\t\t\t\taria-disabled={busy}\n\t\t\t\t\taria-label={t(\"menu.effort\")}\n\t\t\t\t\taria-valuetext={effortName}\n\t\t\t\t\tonChange={(event: react.ChangeEvent<HTMLInputElement>) => {\n\t\t\t\t\t\tconst raw = Number(event.currentTarget.value);\n\t\t\t\t\t\tshowPointerPreview(raw);\n\t\t\t\t\t}}\n\t\t\t\t\tonPointerDown={drag.handlers.onPointerDown}\n\t\t\t\t\tonPointerMove={drag.handlers.onPointerMove}\n\t\t\t\t\tonPointerUp={drag.handlers.onPointerUp}\n\t\t\t\t\tonPointerCancel={drag.handlers.onPointerCancel}\n\t\t\t\t\tonBlur={drag.handlers.onBlur}\n\t\t\t\t\tonKeyDown={onKeyDown}\n\t\t\t\t/>\n\t\t\t\t<span className=\"dms-effort-knob\" aria-hidden=\"true\" />\n\t\t\t</div>\n\t\t\t<span className=\"dms-effort-value\">{effortName}</span>\n\t\t\t{effortDesc === undefined ? null : <span className=\"dms-effort-desc\">{effortDesc}</span>}\n\t\t\t{/* 错误:视觉隐藏的 role=status 供读屏播报,可见错误块同文案。两处容器都\n\t\t\t 常驻(空时 :empty 隐藏)—— 插入式 live region 的首次播报不可靠。 */}\n\t\t\t<span className=\"dms-sr\" role=\"status\">{error ?? ''}</span>\n\t\t\t<div className=\"dms-effort-error\">{error ?? ''}</div>\n\t\t</div>\n\t);\n});\n\ninterface ModelOptionProps {\n\tgroup: ModelProviderGroup\n\tmodel: ModelProviderGroup['models'][number]\n\tshowProvider: boolean\n\tselected: boolean\n\tbusy: boolean\n\t/** 键盘导航用的稳定行标识:菜单内 `[data-row-key]` 查询即按 DOM 顺序聚焦。 */\n\trowKey: string\n\t/** roving tabindex:true = 当前活动行(唯一可 Tab 到的模型行,其余 -1)。 */\n\tactive: boolean\n\t/** 搜索态(结果列表是 listbox):行用 option + aria-selected;分组态用 menuitemradio。 */\n\tsearchMode: boolean\n\t/** 行聚焦回调(箭头导航/点击/程序化 focus 都经它同步活动行键)。 */\n\tonRowFocus: (key: string) => void\n\t/** 搜索命中且命中落在名称内时的片段区间(高亮);undefined/null = 不标。 */\n\tnameHit?: { start: number; end: number } | null\n\t/** 搜索态的集合规模(总命中数)与该行 1-based 序号:截断到 MAX_VISIBLE_HITS\n\t * 时 AT 需要知道「这是 N 条中的第几条」。分组态不传(menu 无 setsize 语义)。 */\n\tsetsize?: number | undefined\n\tposinset?: number | undefined\n\tt: TranslateNS<'modelSelector'>\n\tonChoose: (selection: ModelSelection) => void\n}\n/**\n * 菜单里的一行模型。独立成 memo 组件:目录可能数百行,搜索输入每击键都会\n * 重建菜单内容,行 props(group/model 引用、selected/busy/rowKey、稳定的\n * onChoose)稳定时 React 直接跳过 reconcile,只重渲染真正变化的那行。\n *\n * 自定义比较器(默认浅比较的补充):nameHit 是每次击键新建的对象(引用必变),\n * 但 100 条命中里绝大多数命中区间不变——按值比较 start/end,值相同即跳过\n * 重渲染,搜索才不卡顿。\n */\nconst ModelOption = react.memo(function ModelOption({ group, model, showProvider, selected, busy, rowKey, active, searchMode, onRowFocus, nameHit, setsize, posinset, t, onChoose }: ModelOptionProps) {\n\tconst hit = nameHit === undefined || nameHit === null || nameHit.start === nameHit.end\n\t\t? null\n\t\t: [\n\t\t\tmodel.name.slice(0, nameHit.start),\n\t\t\tmodel.name.slice(nameHit.start, nameHit.end),\n\t\t\tmodel.name.slice(nameHit.end),\n\t\t];\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\t// 搜索态的结果容器是 listbox(搜索框是 combobox,见 .dms-searchInput),\n\t\t\t// 行必须用 option + aria-selected;分组态是 ARIA menu,行用 menuitemradio\n\t\t\t// + aria-checked。两套语义不能混用(menuitemradio 在 listbox 里无效)。\n\t\t\trole={searchMode ? \"option\" : \"menuitemradio\"}\n\t\t\taria-checked={searchMode ? undefined : selected}\n\t\t\taria-selected={searchMode ? selected : undefined}\n\t\t\t// 搜索态给行一个 id:combobox 的 aria-activedescendant 与「这是 N 条中的\n\t\t\t// 第几条」播报都要求 option 可被引用。rowKey 内含 '\\u0000' 分隔符\n\t\t\t// (dmsRowKey),作 HTML id 合法但不可读,编码成可读形式。\n\t\t\tid={searchMode ? `dms-opt-${encodeURIComponent(rowKey)}` : undefined}\n\t\t\t// 结果被截断到 MAX_VISIBLE_HITS 时,AT 需要知道集合规模与本行位置,\n\t\t\t// 否则用户无法感知「只显示了前 100 条」。分组态是 ARIA menu,无 setsize 语义。\n\t\t\taria-setsize={searchMode && setsize !== undefined && setsize > posinset! ? setsize : undefined}\n\t\t\taria-posinset={searchMode && posinset !== undefined ? posinset : undefined}\n\t\t\tdata-row-key={rowKey}\n\t\t\ttabIndex={active ? 0 : -1}\n\t\t\tclassName={`dms-model-option${selected ? \" dms-model-optionSelected\" : \"\"}`}\n\t\t\ttitle={model.description === undefined ? model.name : `${model.name} — ${model.description}`}\n\t\t\taria-disabled={busy}\n\t\t\tonFocus={() => onRowFocus(rowKey)}\n\t\t\tonClick={() => onChoose({ provider: group.id, model: model.id })}\n\t\t>\n\t\t\t<span className=\"dms-model-option-copy\">\n\t\t\t\t<span className=\"dms-nameRow\">\n\t\t\t\t\t<span className=\"dms-model-option-name\">\n\t\t\t\t\t\t{hit === null ? model.name : <>\n\t\t\t\t\t\t\t{hit[0]}<span className=\"dms-hit\">{hit[1]}</span>{hit[2]}\n\t\t\t\t\t\t</>}\n\t\t\t\t\t</span>\n\t\t\t\t\t{model.reasoning !== undefined && <span className=\"dms-badge\" title={t(\"badge.reasoningHint\")}>{t(\"badge.reasoning\")}</span>}\n\t\t\t\t</span>\n\t\t\t\t{model.description !== undefined && <span className=\"dms-model-option-desc\">{model.description}</span>}\n\t\t\t\t{showProvider && <span className=\"dms-model-option-provider\">{group.name}</span>}\n\t\t\t</span>\n\t\t\t<span className=\"dms-model-check\" aria-hidden=\"true\">\n\t\t\t\t{selected ? IconCheck : null}\n\t\t\t</span>\n\t\t</button>\n\t);\n}, (prev, next) => {\n\tif (prev.group !== next.group) return false;\n\tif (prev.model !== next.model) return false;\n\tif (prev.showProvider !== next.showProvider) return false;\n\tif (prev.selected !== next.selected) return false;\n\tif (prev.busy !== next.busy) return false;\n\tif (prev.rowKey !== next.rowKey) return false;\n\tif (prev.active !== next.active) return false;\n\tif (prev.searchMode !== next.searchMode) return false;\n\tif (prev.onRowFocus !== next.onRowFocus) return false;\n\tif (prev.t !== next.t) return false;\n\tif (prev.onChoose !== next.onChoose) return false;\n\t// 截断规模/序号:搜索词变化引起 total 或排序变化时必须重渲染。\n\tif (prev.setsize !== next.setsize) return false;\n\tif (prev.posinset !== next.posinset) return false;\n\t// 引用相同(含都是 null/undefined)即视为未变化;否则按值比较命中区间。\n\tif (prev.nameHit === next.nameHit) return true;\n\tif (prev.nameHit === null || prev.nameHit === void 0) return false;\n\tif (next.nameHit === null || next.nameHit === void 0) return false;\n\treturn prev.nameHit.start === next.nameHit.start && prev.nameHit.end === next.nameHit.end;\n});\n\ninterface ModelSelectProps {\n locked: boolean\n available: boolean\n directory: SnapshotStore<ModelDirectoryState>\n load: () => void\n select: ModelSelectInjected['select']\n t: TranslateNS<'modelSelector'>\n}\nexport function ModelSelect({ locked, available, directory, load, select, t }: ModelSelectProps) {\n\tconst state = react.useSyncExternalStore(directory.subscribe, directory.getSnapshot);\n\tconst [open, setOpen] = react.useState(false);\n\t// 弹出方向自适应:菜单向上弹出(bottom 定位),但 trigger 距视口顶部\n\t// 空间不足时(新会话/页面中部)改为向下弹出,避免搜索框与选项溢出视口。\n\tconst [menuAbove, setMenuAbove] = react.useState(true);\n\tconst [query, setQuery] = react.useState(\"\");\n\tconst [collapsed, setCollapsed] = react.useState<Set<string>>(() => new Set());\n\tconst [notice, setNotice] = react.useState<string | null>(null);\n\t// roving tabindex 的活动行键:全列表只有这一行 tabindex=0(其余 -1,仅方向键\n\t// 可达),Tab 从搜索框进列表落在它上面。行焦点移动经 onRowFocus 更新;行集合\n\t// 变化(打开/搜索/折叠/选中迁移)时由下方 effect 重置到默认落点。\n\tconst [activeRowKey, setActiveRowKey] = react.useState<string | null>(null);\n\tconst onRowFocus = react.useCallback((key: string): void => {\n\t\tsetActiveRowKey(key);\n\t}, []);\n\t// 菜单内 notice 会随 close() 一起清掉,失败原因必须落在菜单外的瞬时横幅上\n\t// (官方 seat 正是用 Toast 播报 select 拒绝)。seq 递增让同一段文案可重播。\n\tconst [toast, setToast] = react.useState<{ seq: number; text: string; failed: boolean } | null>(null);\n\tconst toastSeqRef = react.useRef(0);\n\tconst showToast = react.useCallback((text: string, failed = true): void => {\n\t\ttoastSeqRef.current += 1;\n\t\tsetToast({ seq: toastSeqRef.current, text, failed });\n\t}, []);\n\t// 稳定引用:这两个回调作为 props 传给 EffortSlider / Toast。内联箭头会让\n\t// 子组件的 effect 依赖每次渲染都变(Toast 的卸载定时器被重启、滑杆 memo 失效)。\n\t// markSelectFailure 把错误来源标成「切换」:否则 effort 提交失败会点亮\n\t// 菜单里的「目录加载失败」条 + 无用的「重新加载」按钮(见 renderErrorStrip)。\n\tconst markSelectFailure = react.useCallback((): void => {\n\t\tlastActionRef.current = \"select\";\n\t}, []);\n\tconst dismissToast = react.useCallback((): void => {\n\t\tsetToast(null);\n\t}, []);\n\tconst lastActionRef = react.useRef<\"load\" | \"select\">(\"load\");\n\tconst rootRef = react.useRef<HTMLDivElement | null>(null);\n\tconst triggerRef = react.useRef<HTMLButtonElement | null>(null);\n\tconst searchRef = react.useRef<HTMLInputElement | null>(null);\n\tconst lastLoadRef = react.useRef(0);\n\tconst id = react.useId();\n\t// 模型目录稳定时,haystack 索引不随 select/状态抖动重建:仅在 groups 引用\n\t// 真正变化(新 load 结果)时重建 400+ 条搜索索引。useMemo 保证 group 引用\n\t// 未变时 choices 引用稳定,currentChoice/hits 的 useMemo 依赖它也不会抖动。\n\tconst choices = react.useMemo<readonly SearchChoice[]>(\n\t\t() => state.groups.flatMap((group) => group.models.map((model) => ({\n\t\t\tgroup,\n\t\t\tmodel,\n\t\t\thaystack: dmsHaystack(group, model),\n\t\t\tselection: {\n\t\t\t\tprovider: group.id,\n\t\t\t\tmodel: model.id,\n\t\t\t\t...model.reasoning?.defaultEffort === void 0 ? {} : { reasoningEffort: model.reasoning.defaultEffort }\n\t\t\t}\n\t\t}))),\n\t\t[state.groups],\n\t);\n\tconst currentChoice = react.useMemo(() => {\n\t\tconst current = state.current;\n\t\tif (current === null) return void 0;\n\t\treturn choices.find((c) => c.selection.provider === current.provider && c.selection.model === current.model);\n\t}, [choices, state.current]);\n\tconst reasoning = currentChoice?.model.reasoning;\n\tconst effectiveEffort = state.current?.reasoningEffort ?? reasoning?.defaultEffort;\n\t// 已选模型离开 catalog 时(rc.2 官方目录「保留态」语义:current 仍留着,\n\t// catalog 里已查不到),effort 文案回落到目录的 retainedEffort——官方\n\t// ModelSelect 同规则,否则 trigger 会莫名丢掉档位名。\n\tconst effortLabel = reasoning === void 0\n\t\t? dmsRetainedEffortLabel(state)\n\t\t: effectiveEffort === void 0 ? t(\"effort.providerDefault\") : reasoning.efforts.find((level) => level.id === effectiveEffort)?.name ?? effectiveEffort;\n\tconst busy = dmsEffortBusy(false, state.status, state.pending);\n\t// choose 的最新输入面经 ref 读取:choose 因此不随 busy/current/choices/open 抖动\n\t// —— 它作为行 props 传进 ModelOption,引用一变数百行的自定义 memo 就全线失效。\n\tconst chooseInputRef = react.useRef({ busy, current: state.current, choices, open });\n\tchooseInputRef.current = { busy, current: state.current, choices, open };\n\tconst normalized = dmsNormalizeQuery(query);\n\t// 归一化可能改变码位长度(见 dmsFold),因此匹配与高亮全部走 search.ts 的\n\t// 纯函数:haystack 已折叠、高亮只在长度不变时给区间,不再用小写串下标切原串。\n\tconst hits = react.useMemo<SearchResult | null>(\n\t\t() => dmsSearchHits(choices, normalized, MAX_VISIBLE_HITS),\n\t\t[choices, normalized],\n\t);\n\t// roving tabindex 的默认落点(Tab 从搜索框进列表的停靠行):选中行优先、\n\t// 其次首个可见模型行、全折叠时回退首个组头。纯决策在 roving.ts(node 可测),\n\t// 行键构造同样来自那里 —— 渲染出的 data-row-key 与这里的键必须同源。\n\tconst visibleModelKeys = react.useMemo(\n\t\t() => state.groups.flatMap((g) => (collapsed.has(g.id) ? [] : g.models.map((m) => dmsRowKey(g.id, m.id)))),\n\t\t[state.groups, collapsed],\n\t);\n\tconst defaultRowKey = react.useMemo(() => dmsDefaultRowKey({\n\t\thitKeys: hits === null ? null : hits.items.map((h) => dmsRowKey(h.group.id, h.model.id)),\n\t\tmodelKeys: visibleModelKeys,\n\t\theaderKeys: state.groups.map((g) => dmsHeaderKey(g.id)),\n\t\tselectedKey: state.current === null ? null : dmsRowKey(state.current.provider, state.current.model),\n\t}), [hits, visibleModelKeys, state.groups, state.current]);\n\t// 行集合/选中变化时把 tabindex=0 的默认行同步到当前渲染集;菜单关闭清空。\n\t// 焦点已在行上(onRowFocus 改写过 activeRowKey)时,只要默认落点没变本\n\t// effect 不重跑,活动行保持用户最后聚焦的那行。\n\treact.useEffect(() => {\n\t\tif (!open) {\n\t\t\tsetActiveRowKey(null);\n\t\t\treturn;\n\t\t}\n\t\tsetActiveRowKey(defaultRowKey);\n\t}, [open, defaultRowKey]);\n\tconst reload = react.useCallback(() => {\n\t\tlastActionRef.current = \"load\";\n\t\tlastLoadRef.current = Date.now();\n\t\tload();\n\t}, [load]);\n\treact.useEffect(() => {\n\t\tif (available) {\n\t\t\t// 目录新鲜度守卫:inject 重跑会重建 load 引用触发本 effect,30s 内已有\n\t\t\t// 有效目录时不重复全量 load(show() 打开路径已有同款守卫)。\n\t\t\tif (Date.now() - lastLoadRef.current < DIRECTORY_STALE_MS && state.status === \"ready\" && state.groups.length > 0) return;\n\t\t\tlastActionRef.current = \"load\";\n\t\t\tlastLoadRef.current = Date.now();\n\t\t\tload();\n\t\t}\n\t}, [available, load]);\n\t// 菜单高度按视口实测钳位(原先 CSS 写死 min(420px, 100vh - 96px)),\n\t// composer 变高/窗口变小时自动收,不再溢出。\n\tconst menuRef = react.useRef<HTMLDivElement | null>(null);\n\t// useAnchoredMaxHeight 只适配「底边固定、向上生长」的底部锚定浮层:fit 读的是\n\t// 浮层自身底缘与视口顶缘的距离。向下弹(menuAbove=false)时面板改为顶边固定,\n\t// hook 度量的底缘是向下弹出后的位置,返回值在渲染处(maxHeight 三目)被忽略——\n\t// 继续挂监听只白付测量:每次 scroll/resize 都未节流 getBoundingClientRect +\n\t// setState 重渲染。向下弹期间把被测 ref 换成恒 null 的空 ref,hook 的 layout\n\t// effect 因 el === null 整体跳过(不注册监听、不测量);方向翻回向上时 ref 换回\n\t// menuRef,effect 因 ref 身份变化重跑并立即 fit——翻向后的钳位是新鲜测量\n\t// (原实现 signal 不变不重跑,翻回时用的还是向下弹期间留下的旧值)。\n\tconst inactiveMenuRef = react.useRef<HTMLDivElement | null>(null);\n\t// 第 4 参 margin 显式传本插件的 MENU_VIEWPORT_MARGIN,不依赖官方默认值恰好也是\n\t// 12:上游改默认 MARGIN 时隐式依赖会让本菜单的贴边距离静默跟随漂移。\n\t// official-seat-shadow.test.mjs 反向钉住两边相等(官方改了会红,届时再决策)。\n\tconst menuMaxHeight = useAnchoredMaxHeight(menuAbove ? menuRef : inactiveMenuRef, MENU_MAX_HEIGHT, open, MENU_VIEWPORT_MARGIN);\n\tconst [belowMaxHeight, setBelowMaxHeight] = react.useState(MENU_MAX_HEIGHT);\n\t// 为何不采纳官方 useAnchoredPosition(与文件头「不用官方 Menu」并列的第二个不采纳决定):\n\t// 该 hook 的水平起点固定是 anchor 左缘(left = rect.left,仅越界时钳进视口),没有\n\t// 「右缘对齐 anchor 右缘」这种模式;而本菜单默认就是右锚定(.dms-menu 的 right:0,与\n\t// seat 右缘对齐),只有右侧放不下时才钳到视口内改用 left。方向也不由 hook 决定:\n\t// side 是调用方写死的 'top' | 'bottom',而这里要按 trigger 上下空间自动选边\n\t// (dmsMenuAbove);且 hook 返回的是 fixed 定位坐标,本菜单是 .dms-root 内的\n\t// absolute + bottom/right 锚定,采纳它等于同时改默认对齐、方向策略与定位模型——\n\t// 属用户可见的运行时行为变更,故保留自研测量。\n\t// 水平钳位:seat 右缘放不下整幅菜单(窄窗口)时改为 left 锚定,undefined = 默认右锚定。\n\tconst [menuLeft, setMenuLeft] = react.useState<number | undefined>(undefined);\n\t// 布局生效前测量(对齐 useAnchoredMaxHeight 的 useLayoutEffect 模式):\n\t// 用 useEffect 会在首帧绘制后才翻转方向/水平锚定,向下弹或左夹取场景\n\t// 菜单先按默认(向上弹 + 右锚定)画一帧再跳位,肉眼可见闪错位。\n\treact.useLayoutEffect(() => {\n\t\tif (!open) return;\n\t\tconst measure = () => {\n\t\t\tconst trigger = triggerRef.current;\n\t\t\tif (trigger === null) return;\n\t\t\tconst rect = trigger.getBoundingClientRect();\n\t\t\t// 取上下两侧中空间更大的一侧:只有上方放不下时才向下弹。\n\t\t\t// 原先要求上方空出满高(MENU_MAX_HEIGHT + 20),窗口一矮就反而把面板\n\t\t\t// 挤到视口下沿外面。\n\t\t\tsetMenuAbove(dmsMenuAbove(rect.top, rect.bottom, window.innerHeight));\n\t\t\t// Downward the panel is top-anchored, so the hook's own fit would feed\n\t\t\t// back on itself; clamp against the space below the trigger instead.\n\t\t\tsetBelowMaxHeight(dmsBelowMaxHeight(rect.bottom, window.innerHeight, MENU_MAX_HEIGHT));\n\t\t\t// 水平:菜单实际渲染宽度为准(offsetWidth),右锚定放不下时钳到视口内。\n\t\t\t// dmsMenuLeft 的入参/返回值都是**视口坐标**,而 .dms-menu 是 .dms-root\n\t\t\t// (position: relative)内的 absolute 元素 —— left 的参考系是 root 的\n\t\t\t// padding box。不减去 root 左缘的话,钳位一旦生效菜单整体右移\n\t\t\t// rootRect.left;横向溢出(rect.right > innerWidth)那一支更会把整幅\n\t\t\t// 菜单画到屏外,恰是这次钳位要避免的现象。\n\t\t\tconst menuWidth = menuRef.current?.offsetWidth ?? 0;\n\t\t\tconst left = dmsMenuLeft(rect.right, menuWidth, window.innerWidth, MENU_VIEWPORT_MARGIN);\n\t\t\tconst rootLeft = rootRef.current?.getBoundingClientRect().left ?? 0;\n\t\t\tsetMenuLeft(left === undefined ? undefined : left - rootLeft);\n\t\t};\n\t\tmeasure();\n\t\tlet raf = 0;\n\t\t// scroll 捕获阶段每帧触发,resize 也高频;rAF 合并同帧多次触发,\n\t\t// passive 声明让浏览器不必等我们作 preventDefault。\n\t\tconst onViewportChange = () => {\n\t\t\tif (raf !== 0) return;\n\t\t\traf = requestAnimationFrame(() => {\n\t\t\t\traf = 0;\n\t\t\t\tmeasure();\n\t\t\t});\n\t\t};\n\t\twindow.addEventListener(\"resize\", onViewportChange, { passive: true });\n\t\twindow.addEventListener(\"scroll\", onViewportChange, { capture: true, passive: true });\n\t\treturn () => {\n\t\t\tif (raf !== 0) cancelAnimationFrame(raf);\n\t\t\twindow.removeEventListener(\"resize\", onViewportChange);\n\t\t\twindow.removeEventListener(\"scroll\", onViewportChange, { capture: true });\n\t\t};\n\t}, [open]);\n\treact.useEffect(() => {\n\t\tif (!open) return;\n\t\tsearchRef.current?.focus();\n\t}, [open]);\n\t// 打开即把当前选中行滚进可视区:选中模型在长列表深处时不用手动翻找。\n\t// 手动改 scrollTop 而不用 scrollIntoView,避免连带滚动页面/其它祖先容器。\n\t// 吸顶分组头(sticky top:0)会盖住滚到容器顶缘的行:偏移量按所在组的\n\t// 头部实际高度让位,否则「滚到了」却看不见。\n\t// 依赖补全:目录异步加载完成(state.groups 引用变化)后重新定位——原实现\n\t// 只依赖 [open],加载完成晚于开合的那次渲染时选中行永远不会滚进可视区。\n\t// useLayoutEffect(而非 queueMicrotask):DOM 已在提交阶段就绪,同步滚动\n\t// 不产生首帧跳动;且此时行/头部尺寸可测。\n\tconst scrollSelectedIntoView = (): void => {\n\t\tconst list = menuRef.current?.querySelector(\".dms-groups\");\n\t\tif (list === null || list === void 0) return;\n\t\t// 折叠/展开/目录刷新改变内容高度后,scrollTop 可能超出新的可滚上限\n\t\t// (浏览器不自动钳回,滚动条下方露出空白),先钳回合法区间再定位。\n\t\tconst maxScroll = list.scrollHeight - list.clientHeight;\n\t\tif (list.scrollTop > maxScroll) list.scrollTop = Math.max(0, maxScroll);\n\t\t// 选中行不在 DOM(选中组被折叠 / 搜索把选中模型滤掉)时,退而滚动到\n\t\t// 选中项所在分组(含折叠态,让用户看到「当前模型在那个组」)。\n\t\tconst selected = state.current;\n\t\tconst row = list.querySelector('[role=\"menuitemradio\"][aria-checked=\"true\"]');\n\t\tif (row === null) {\n\t\t\tif (selected === null) return;\n\t\t\tconst section = list.querySelector<HTMLElement>(`section[data-group-id=\"${globalThis.CSS.escape(selected.provider)}\"]`);\n\t\t\tif (section === null) return;\n\t\t\tconst sectionRect = section.getBoundingClientRect();\n\t\t\tconst listRect = list.getBoundingClientRect();\n\t\t\tif (sectionRect.top < listRect.top) list.scrollTop += sectionRect.top - listRect.top;\n\t\t\telse if (sectionRect.bottom > listRect.bottom) list.scrollTop += sectionRect.bottom - listRect.bottom;\n\t\t\treturn;\n\t\t}\n\t\tconst rowRect = row.getBoundingClientRect();\n\t\tconst listRect = list.getBoundingClientRect();\n\t\tconst header = row.closest(\"section\")?.querySelector(\".dms-groupHeader\");\n\t\tconst headerHeight = header === null || header === undefined ? 0 : header.getBoundingClientRect().height;\n\t\tif (rowRect.top < listRect.top + headerHeight) list.scrollTop += rowRect.top - (listRect.top + headerHeight);\n\t\telse if (rowRect.bottom > listRect.bottom) list.scrollTop += rowRect.bottom - listRect.bottom;\n\t};\n\t// 打开瞬间自动展开选中组(若被折叠)。用 ref 记录「本次打开周期已自动展开过\n\t// 哪个 provider」:layout effect 依赖含 collapsed,若不拦截,用户手动折叠\n\t// 选中组时 effect 会立刻把它重新展开——折叠功能对选中组形同失效。\n\t// 只在 provider 首次解析到(含异步 load 完成后)且处于折叠态时展开一次,\n\t// 之后同 provider 的手动折叠不再被抢。\n\tconst expandedProviderRef = react.useRef<string | null>(null);\n\treact.useLayoutEffect(() => {\n\t\tif (!open) {\n\t\t\texpandedProviderRef.current = null;\n\t\t\treturn;\n\t\t}\n\t\t// 搜索命中渲染期间不滚动/不自动展开:命中列表替换了分组 DOM,滚动测量\n\t\t// 没有意义;且每次击键 hits 引用都变化——提前返回避免每次击键重跑\n\t\t// querySelector×2 + getBoundingClientRect×3 的定位测量(当前模型命中时\n\t\t// 还会写 scrollTop)。查询清空(hits 回 null)后本 effect 因 deps 变化\n\t\t// 重跑,滚动/展开行为与首次打开、异步加载完成场景保持一致。\n\t\tif (hits !== null) return;\n\t\tconst provider = state.current?.provider ?? null;\n\t\tif (provider !== null && provider !== expandedProviderRef.current && collapsed.has(provider)) {\n\t\t\texpandedProviderRef.current = provider;\n\t\t\tsetCollapsed((prev) => {\n\t\t\t\tif (!prev.has(provider)) return prev;\n\t\t\t\tconst next = new Set(prev);\n\t\t\t\tnext.delete(provider);\n\t\t\t\treturn next;\n\t\t\t});\n\t\t}\n\t\tscrollSelectedIntoView();\n\t}, [open, state.groups, state.current, collapsed, hits]);\n\t// show/close/choose/toggleCollapse 四个 useCallback 必须全部位于下方\n\t// `if (!available) return null` 早退之前:hooks 数量不得随渲染分支变化\n\t// (React 会直接抛 \"Rendered more hooks than during the previous render\")。\n\tconst show = react.useCallback(() => {\n\t\tsetOpen(true);\n\t\tif (state.status === \"error\" || state.groups.length === 0 || Date.now() - lastLoadRef.current > DIRECTORY_STALE_MS) reload();\n\t}, [state.status, state.groups.length, reload]);\n\t// 还焦:优先 trigger;trigger 被 locked 禁用时 focus() 是空操作(焦点会掉回\n\t// body,下一次 Tab 从文档开头重来),退到 root 容器(tabIndex=-1)承接。\n\tconst focusTrigger = (): void => {\n\t\tconst trigger = triggerRef.current;\n\t\tif (trigger !== null && !trigger.disabled) {\n\t\t\ttrigger.focus();\n\t\t\treturn;\n\t\t}\n\t\trootRef.current?.focus();\n\t};\n\tconst close = react.useCallback((restoreFocus = false) => {\n\t\tsetOpen(false);\n\t\tsetNotice(null);\n\t\tsetQuery(\"\");\n\t\tif (restoreFocus) queueMicrotask(focusTrigger);\n\t}, []);\n\t// 外部 pointerdown 关闭与 Escape 一致走 close(true):搜索框随菜单卸载后\n\t// 焦点落回 trigger(此前焦点直接掉 body)。hook 只以 false 调用 setter,\n\t// 忽略参数即可。\n\tconst dismissOnOutsidePointer = react.useCallback(() => {\n\t\tclose(true);\n\t}, [close]);\n\tuseDismissOnOutsidePointer(rootRef, open, dismissOnOutsidePointer);\n\t// locked 翻转(会话移除/失活/页面 inert 等)时若菜单还开着直接关闭:\n\t// trigger 已禁用,挂着的菜单没有可交互入口;close(true) 顺带清掉搜索词与\n\t// notice,并把焦点从即将卸载的菜单里接出来(focusTrigger 因 trigger disabled\n\t// 落到 root 容器,不让焦点掉回 body)。\n\treact.useEffect(() => {\n\t\tif (locked && open) close(true);\n\t}, [locked, open, close]);\n\tconst choose = react.useCallback((selection: ModelSelection): void => {\n\t\tconst { busy: isBusy, current, choices: catalog } = chooseInputRef.current;\n\t\tif (isBusy) return;\n\t\tif (current?.provider === selection.provider && current.model === selection.model) {\n\t\t\tsetNotice(t(\"notice.already\"));\n\t\t\treturn;\n\t\t}\n\t\tconst target = catalog.find((c) => c.selection.provider === selection.provider && c.selection.model === selection.model);\n\t\t// 自动拉档决策收敛到 dmsChoosePlan(effort.ts,node 可测):非规范档位 id\n\t\t// 不再被当成「最强档」提交(那会挑错档、甚至被宿主拒绝)。\n\t\tconst plan = dmsChoosePlan(target?.model.reasoning);\n\t\tconst full: ModelSelection = {\n\t\t\tprovider: selection.provider,\n\t\t\tmodel: selection.model,\n\t\t\t...plan.effort === void 0 ? {} : { reasoningEffort: plan.effort }\n\t\t};\n\t\tlastActionRef.current = \"select\";\n\t\tconst autoName = plan.effort === void 0\n\t\t\t? \"\"\n\t\t\t: target?.model.reasoning?.efforts.find((level) => level.id === plan.effort)?.name ?? plan.effort;\n\t\t// 超时护栏与 EffortSlider.commit() 同款:目录 select 无超时契约,RPC 永久\n\t\t// 挂起时若不加护栏,status='selecting' 由 store 持有、本插件无法复位,交互\n\t\t// 永久锁死。超时/拒绝都按切换失败播报 Toast(复用既有文案,不新增 key);\n\t\t// 不强行复位 store——目录 settle 后状态自然回到 ready,与 effort commit 一致。\n\t\t// 迟到的 settle 一律忽略:成功与否都会经共享目录写回,UI 无需再动作。\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tconst result = await new Promise<SelectResult>((resolve, reject) => {\n\t\t\t\t\tlet settled = false;\n\t\t\t\t\tconst timer = window.setTimeout(() => {\n\t\t\t\t\t\tif (settled) return;\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\treject(new Error(t(\"notice.selectFailed\")));\n\t\t\t\t\t}, EFFORT_COMMIT_TIMEOUT_MS);\n\t\t\t\t\tselect(full).then(\n\t\t\t\t\t\t(settledResult) => {\n\t\t\t\t\t\t\tif (settled) return;\n\t\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\t\twindow.clearTimeout(timer);\n\t\t\t\t\t\t\tresolve(settledResult);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(cause: unknown) => {\n\t\t\t\t\t\t\tif (settled) return;\n\t\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\t\twindow.clearTimeout(timer);\n\t\t\t\t\t\t\treject(cause);\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t\tif (result === undefined || !result.ok) {\n\t\t\t\t\t// alpha.2 契约:失败详情随 RemoteResult.error 回来;契约缺席\n\t\t\t\t\t// (undefined = 该会话不可选)时回退目录快照的错误(旧行为)。\n\t\t\t\t\tconst message = result !== undefined\n\t\t\t\t\t\t? `${result.error.code}: ${result.error.message}`\n\t\t\t\t\t\t: directory.getSnapshot().error;\n\t\t\t\t\tshowToast(message !== null ? t(\"error.action\", { message }) : t(\"notice.selectFailed\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// 只有菜单仍开着时才关(并清搜索词、还焦):用户在 RPC 在途期间已经\n\t\t\t\t// 关掉菜单/移走焦点时,这里的 close(true) 会把焦点从用户当前所在\n\t\t\t\t// 控件抢回 trigger,还会清掉用户刚输入的搜索词。Toast 与 open 无关,\n\t\t\t\t// 照常播报。\n\t\t\t\tif (chooseInputRef.current.open) close(true);\n\t\t\t\tif (plan.autoRaised) showToast(t(\"toast.effortAuto\", { effort: autoName }), false);\n\t\t\t} catch {\n\t\t\t\t// 超时 / select 拒绝:都按切换失败播报(防御性 catch 同时杜绝\n\t\t\t\t// unhandled rejection——未来注入面变更也不会让它漏出去)。\n\t\t\t\tshowToast(t(\"notice.selectFailed\"));\n\t\t\t}\n\t\t})();\n\t\t// 依赖只剩稳定引用(注入面的 select/t、目录 store、close/showToast 都是\n\t\t// useCallback([]) 或 props 缓存):busy/current/choices/open 一律经\n\t\t// chooseInputRef 读最新值 —— 否则一次模型切换就让 choose 换引用,数百行的\n\t\t// ModelOption 自定义 memo 全线失效。\n\t}, [select, t, directory, close, showToast]);\n\tconst toggleCollapse = react.useCallback((groupId: string): void => {\n\t\tsetCollapsed((prev) => {\n\t\t\tconst next = new Set(prev);\n\t\t\tif (next.has(groupId)) next.delete(groupId);\n\t\t\telse next.add(groupId);\n\t\t\treturn next;\n\t\t});\n\t}, []);\n\tif (!available) return null;\n\t// 键盘导航:按 DOM 顺序查询菜单内全部行(data-row-key),不再依赖渲染期\n\t// 收集的 ref 数组(那既阻止行级 memo,又把副作用塞进 render)。环绕下标\n\t// 收敛到 dmsNextRowIndex(keys.ts,node 可测)。\n\tconst moveFocus = (offset: number): void => {\n\t\tconst menu = menuRef.current;\n\t\tif (menu === null) return;\n\t\tconst items = [...menu.querySelectorAll<HTMLButtonElement>('[data-row-key]')];\n\t\tif (items.length === 0) return;\n\t\tconst active = items.findIndex((item) => item === document.activeElement);\n\t\titems[dmsNextRowIndex(active, offset, items.length)]?.focus();\n\t};\n\t// Home/End 跳首/末行(WAI-ARIA menu 标准键;搜索框内不劫持——那是移光标键)。\n\tconst focusEdge = (last: boolean): void => {\n\t\tconst items = menuRef.current?.querySelectorAll<HTMLButtonElement>('[data-row-key]');\n\t\tif (items === undefined || items.length === 0) return;\n\t\t(last ? items[items.length - 1] : items[0])?.focus();\n\t};\n\t// 按键 → 动作的判定全部收敛在 dmsMenuKeyAction(keys.ts,node 可测);\n\t// 这里只做 dispatch 与副作用(preventDefault 只在真的执行动作时才发,\n\t// 无行可去时不吞键)。\n\tconst onRootKeyDown = (event: react.KeyboardEvent<HTMLDivElement>): void => {\n\t\tconst target = event.target;\n\t\tconst action = dmsMenuKeyAction({\n\t\t\tkey: event.key,\n\t\t\t// IME 组合输入期间不劫持按键:Enter 是候选上屏确认、方向键在候选窗翻页、\n\t\t\t// Escape 是取消组合。除 isComposing 外还要认 keyCode 229 —— WebKit 在\n\t\t\t// compositionend 之后补发的那次 Enter 带 isComposing === false,不挡就会把\n\t\t\t// 「候选上屏」当成「选中首个命中」直接切模型。\n\t\t\tcomposing: event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229,\n\t\t\topen,\n\t\t\tfromSearch: target instanceof HTMLInputElement && target === searchRef.current,\n\t\t\t// 「菜单内」= 菜单容器(搜索框/行/滑杆);trigger 不算 —— 焦点在 trigger 上\n\t\t\t// 按 Escape 应当直接关菜单,而不是被清词逻辑拽进搜索框。\n\t\t\tinMenu: menuRef.current?.contains(document.activeElement) === true,\n\t\t\tonNonInput: !(target instanceof HTMLInputElement),\n\t\t\thasQuery: query !== \"\",\n\t\t\trowCount: menuRef.current?.querySelectorAll('[data-row-key]').length ?? 0,\n\t\t\thitCount: hits?.items.length ?? 0,\n\t\t});\n\t\tswitch (action.type) {\n\t\t\tcase 'none':\n\t\t\t\treturn;\n\t\t\tcase 'clearQuery':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tsetNotice(null);\n\t\t\t\tsetQuery(\"\");\n\t\t\t\tif (action.focusSearch) searchRef.current?.focus();\n\t\t\t\treturn;\n\t\t\tcase 'close':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tclose(true);\n\t\t\t\treturn;\n\t\t\tcase 'moveFocus':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tmoveFocus(action.offset);\n\t\t\t\treturn;\n\t\t\tcase 'focusEdge':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tfocusEdge(action.last);\n\t\t\t\treturn;\n\t\t\tcase 'chooseFirstHit': {\n\t\t\t\tconst first = hits?.items[0];\n\t\t\t\tif (first === undefined) return;\n\t\t\t\tevent.preventDefault();\n\t\t\t\tchoose({ provider: first.group.id, model: first.model.id });\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t};\n\tconst onBlur = (event: react.FocusEvent<HTMLDivElement>): void => {\n\t\tconst related = event.relatedTarget;\n\t\t// 焦点仍在 root 内(菜单、搜索框、滑杆、root 自身):不关。\n\t\tif (related instanceof Node && rootRef.current?.contains(related) === true) return;\n\t\t// 焦点落到 root 外的可聚焦元素(Tab 出去):关。不再豁免 busy —— 否则在途\n\t\t// 选择期间会留下「焦点已在菜单外、Escape 也关不掉」的悬挂菜单(keydown\n\t\t// 不再冒泡到 root),而菜单外的点击本就有 outside-pointer 路径兜底。\n\t\tif (related instanceof Node) {\n\t\t\tclose();\n\t\t\treturn;\n\t\t}\n\t\t// relatedTarget 为 null:只有窗口失焦(alt-tab / 切到别的应用)才关。点击菜单内的\n\t\t// 非可聚焦区域(内边距、标签、分组间隙)也会让浏览器把焦点收回 body,但那是菜单内\n\t\t// 操作,不该关菜单。\n\t\t// ⚠ 判据用 `visibilityState` 而不是 `document.hasFocus()`:后者在 iframe /\n\t\t// 多文档场景(桌面端 WebView 承载、或页面被嵌进别的文档)下,用户点击\n\t\t// iframe 外的区域时返回 false,会把「焦点只是被外层文档拿走」误判成窗口\n\t\t// 失焦 → 菜单静默关掉。`hidden` 只在文档真正不可见(切标签页 / 最小化 /\n\t\t// 锁屏)时为真,与「用户不在看这个页面」的语义一致。\n\t\tif (document.visibilityState === 'hidden') close();\n\t};\n\t// 标签兜底链收敛到 dmsTriggerCopy(copy.ts,node 可测):目录成员资格只是\n\t// 参考(routable 契约),current 匹配不到 group 时显示 provider/model 原始\n\t// id,而不是「选择模型」。\n\tconst trigger = dmsTriggerCopy({\n\t\tcurrent: state.current,\n\t\tmodelName: currentChoice?.model.name,\n\t\tproviderName: currentChoice?.group.name,\n\t\teffortLabel,\n\t\twaiting: state.current === null && state.status === \"loading\",\n\t}, t);\n\tconst renderErrorStrip = () => {\n\t\t// Load failures only: a rejected select is announced by the Toast, which\n\t\t// also survives closing the menu (see choose()). role=alert:菜单打开时\n\t\t// 焦点在搜索框,失败条插在它上方,读屏用户不浏览过去就不知道有失败。\n\t\tif (state.error !== null && lastActionRef.current === \"load\") {\n\t\t\treturn (\n\t\t\t\t<div className=\"dms-error\" role=\"alert\">\n\t\t\t\t\t<span>{t(\"error.action\", { message: state.error })}</span>\n\t\t\t\t\t<button type=\"button\" className=\"dms-retry\" onClick={reload}>{t(\"action.reload\")}</button>\n\t\t\t\t</div>\n\t\t\t);\n\t\t}\n\t\treturn null;\n\t};\n\tconst renderFailures = (failures: DirectoryState['failures']) => failures.map((failure) => (\n\t\t<div key={failure.id} className=\"dms-warning\">\n\t\t\t<span>{t(\"warning.groupLoad\", { name: failure.name, message: failure.message })}</span>\n\t\t\t<button type=\"button\" className=\"dms-retry\" onClick={reload}>{t(\"action.reload\")}</button>\n\t\t</div>\n\t));\n\t// 列表区的空态:判定收敛到 dmsMenuEmptyState(search.ts,node 可测)—— 无模型\n\t// 优先于无命中(目录为空时不该引导用户去改关键词)。空态播报节点必须放在\n\t// role=menu 容器外(menu 内容模型只允许菜单节点),视觉位置由 .dms-groupsFill\n\t// 的弹性占位补回,空态时列表容器本身不渲染。\n\tconst emptyState = dmsMenuEmptyState({\n\t\tstatus: state.status,\n\t\tchoiceCount: choices.length,\n\t\tsearching: hits !== null,\n\t\thitCount: hits?.total ?? 0,\n\t});\n\treturn (\n\t\t// tabIndex=-1:root 只作为「焦点落点」承接(locked 关闭菜单时 trigger 已\n\t\t// 禁用,不能让焦点掉回 body;点击菜单内非可聚焦区域时浏览器把焦点交给它,\n\t\t// onBlur 便不会误判成「焦点离开菜单」)。不进 Tab 序。\n\t\t<div ref={rootRef} className=\"dms-root\" tabIndex={-1} onKeyDown={onRootKeyDown} onBlur={onBlur}>\n\t\t\t<button\n\t\t\t\tref={triggerRef}\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"dms-trigger\"\n\t\t\t\taria-label={trigger.aria}\n\t\t\t\taria-haspopup=\"menu\"\n\t\t\t\taria-expanded={open}\n\t\t\t\taria-controls={open ? `${id}-menu` : undefined}\n\t\t\t\ttitle={trigger.title}\n\t\t\t\tdisabled={locked}\n\t\t\t\tonClick={() => (open ? close() : show())}\n\t\t\t>\n\t\t\t\t<span className=\"dms-triggerLabel\">{trigger.modelLabel}</span>\n\t\t\t\t{trigger.providerLabel !== undefined && <span className=\"dms-triggerProvider\">{trigger.providerLabel}</span>}\n\t\t\t\t{effortLabel !== undefined && <span className=\"dms-triggerEffort\">{effortLabel}</span>}\n\t\t\t\t<span className={`dms-chevron${open ? \" dms-chevronOpen\" : \"\"}`} aria-hidden=\"true\">\n\t\t\t\t\t{IconChevronDown}\n\t\t\t\t</span>\n\t\t\t</button>\n\t\t\t{open ? (\n\t\t\t\t<div\n\t\t\t\t\tid={`${id}-menu`}\n\t\t\t\t\tref={menuRef}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tmaxHeight: menuAbove ? menuMaxHeight : belowMaxHeight,\n\t\t\t\t\t\t...(menuLeft === undefined ? null : { left: menuLeft, right: \"auto\" }),\n\t\t\t\t\t}}\n\t\t\t\t\tclassName={'dms-menu dms-menuModel' + (menuAbove ? '' : ' dms-menuBelow')}\n\t\t\t\t>\n\t\t\t\t\t{state.status === 'loading' && <div className=\"dms-status\" role=\"status\">{t('status.loading')}</div>}\n\t\t\t\t\t{renderErrorStrip()}\n\t\t\t\t\t{/* role=alert 挂容器:一次 load 可能多家供应商同时失败,逐条 alert\n\t\t\t\t\t 会连播数遍。 */}\n\t\t\t\t\t{state.failures.length > 0 && <div className=\"dms-failures\" role=\"alert\">{renderFailures(state.failures)}</div>}\n\t\t\t\t\t<div className=\"dms-search\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref={searchRef}\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tclassName=\"dms-searchInput\"\n\t\t\t\t\t\t\tvalue={query}\n\t\t\t\t\t\t\tplaceholder={t('search.placeholder')}\n\t\t\t\t\t\t\taria-label={t('search.placeholder')}\n\t\t\t\t\t\t\t// 输入即过滤一个结果列表 → combobox 语义(APG「Editable\n\t\t\t\t\t\t\t// Combobox With List Autocomplete」)。aria-expanded 只在搜索态\n\t\t\t\t\t\t\t// 为 true:那时结果容器才是 listbox;非搜索态的分组视图是 menu,\n\t\t\t\t\t\t\t// 不冒充「展开的建议列表」。\n\t\t\t\t\t\t\t// 0 命中时同步收起:listbox 容器不渲染(见下),声称「已展开」\n\t\t\t\t\t\t\t// 会让读尸用户听到一个指向空集的 combo(空态文案由 role=status\n\t\t\t\t\t\t\t// 那条 live region 承担播报,不依赖 expanded)。\n\t\t\t\t\t\t\trole=\"combobox\"\n\t\t\t\t\t\t\taria-expanded={hits !== null && hits.total > 0}\n\t\t\t\t\t\t\taria-autocomplete=\"list\"\n\t\t\t\t\t\t\t// 空态时结果容器不渲染,指向它就是悬空 IDREF(官方 trigger\n\t\t\t\t\t\t\t// 同款处理:关闭/不存在时省略 aria-controls)。\n\t\t\t\t\t\t\taria-controls={emptyState === null ? `${id}-groups` : undefined}\n\t\t\t\t\t\t\tautoComplete=\"off\"\n\t\t\t\t\t\t\tenterKeyHint=\"search\"\n\t\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t\t\tonChange={(event: react.ChangeEvent<HTMLInputElement>) => {\n\t\t\t\t\t\t\t\tsetNotice(null);\n\t\t\t\t\t\t\t\tsetQuery(event.target.value);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{query !== '' && (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclassName=\"dms-searchClear\"\n\t\t\t\t\t\t\t\taria-label={t('search.clearAria')}\n\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\tsetNotice(null);\n\t\t\t\t\t\t\t\t\tsetQuery('');\n\t\t\t\t\t\t\t\t\tsearchRef.current?.focus();\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<span aria-hidden=\"true\" className=\"dms-icon-slot\">{IconClear}</span>\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t\t{/* 搜索结果播报:容器常驻(内容随状态更新)—— 插入式 live region 在各\n\t\t\t\t\t AT 上行为不一致,可能整条漏播。0 命中时不播计数(空态文案已承担),\n\t\t\t\t\t 避免同帧两条 role=status 重复播报。 */}\n\t\t\t\t\t<span className=\"dms-sr\" role=\"status\">\n\t\t\t\t\t\t{hits !== null && hits.total > 0 ? t('search.status', { count: String(hits.total) }) : ''}\n\t\t\t\t\t</span>\n\t\t\t\t\t{/* 推理标记的说明:只挂在 title 上则键盘/读屏用户拿不到,这里补一条\n\t\t\t\t\t 视觉隐藏的静态说明(role=note,不冒充菜单项)。 */}\n\t\t\t\t\t<span className=\"dms-sr\" role=\"note\">{t('badge.reasoningHint')}</span>\n\t\t\t\t\t{/* 空态(无命中/无模型)与「仅显示前 N 条」note 全部在结果容器外:\n\t\t\t\t\t menu/listbox 的直接内容模型只允许菜单/选项节点,live region 会被\n\t\t\t\t\t 菜单导航跳过、静态说明不该冒充选项。视觉位置由 .dms-groupsFill\n\t\t\t\t\t (空态)与容器后的 .dms-more 保持。 */}\n\t\t\t\t\t{emptyState === 'noHits' && (\n\t\t\t\t\t\t<div className=\"dms-empty dms-groupsFill\" role=\"status\">{t('search.noMatch', { query: query.trim() })}</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{emptyState === 'noModels' && (\n\t\t\t\t\t\t<div className=\"dms-empty dms-groupsFill\" role=\"status\">{t('empty.models')}</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{emptyState === null && (\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"dms-groups\"\n\t\t\t\t\t\tid={`${id}-groups`}\n\t\t\t\t\t\t// 搜索态是 listbox(combobox 的弹层,行用 option);分组态是 menu\n\t\t\t\t\t\t// (行用 menuitemradio)。aria-busy 从 .dms-menu 移到这里:它罩住\n\t\t\t\t\t\t// live region 会压制播报,而 menu 容器自身才是「内容仍在变」的子树。\n\t\t\t\t\t\trole={hits !== null ? 'listbox' : 'menu'}\n\t\t\t\t\t\taria-busy={state.status === 'loading' || busy}\n\t\t\t\t\t\taria-label={t(\"menu.aria\")}\n\t\t\t\t\t>\n\t\t\t\t\t\t{hits !== null\n\t\t\t\t\t\t\t? hits.items.map((hit, index) => {\n\t\t\t\t\t\t\t\tconst rowKey = dmsRowKey(hit.group.id, hit.model.id);\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<ModelOption\n\t\t\t\t\t\t\t\t\t\tkey={rowKey}\n\t\t\t\t\t\t\t\t\t\tgroup={hit.group}\n\t\t\t\t\t\t\t\t\t\tmodel={hit.model}\n\t\t\t\t\t\t\t\t\t\tshowProvider\n\t\t\t\t\t\t\t\t\t\tselected={state.current?.provider === hit.group.id && state.current.model === hit.model.id}\n\t\t\t\t\t\t\t\t\t\tbusy={busy}\n\t\t\t\t\t\t\t\t\t\trowKey={rowKey}\n\t\t\t\t\t\t\t\t\t\tactive={rowKey === activeRowKey}\n\t\t\t\t\t\t\t\t\t\tsearchMode\n\t\t\t\t\t\t\t\t\t\tonRowFocus={onRowFocus}\n\t\t\t\t\t\t\t\t\t\tnameHit={hit.nameHit}\n\t\t\t\t\t\t\t\t\t\tsetsize={hits.total}\n\t\t\t\t\t\t\t\t\t\tposinset={index + 1}\n\t\t\t\t\t\t\t\t\t\tt={t}\n\t\t\t\t\t\t\t\t\t\tonChoose={choose}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t: state.groups.map((group) => {\n\t\t\t\t\t\t\t\tconst headingId = `${id}-${group.id}`;\n\t\t\t\t\t\t\t\tconst headerKey = dmsHeaderKey(group.id);\n\t\t\t\t\t\t\t\tconst isCollapsed = collapsed.has(group.id);\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<section key={group.id} data-group-id={group.id} role=\"group\" aria-labelledby={headingId} className=\"dms-group\">\n\t\t\t\t\t\t\t\t\t\t{/* 组头参与 roving 导航(data-row-key + onFocus 同步活动行键):\n\t\t\t\t\t\t\t\t\t\t 折叠/展开对键盘可达,且「全部折叠」时 dmsDefaultRowKey\n\t\t\t\t\t\t\t\t\t\t 回退到的正是组头 —— 它必须能承接 tabIndex=0,否则\n\t\t\t\t\t\t\t\t\t\t Tab 会直接跳过整个列表(活动行键落空)。非活动组头\n\t\t\t\t\t\t\t\t\t\t 仍是 -1,数百行不会撑爆 Tab 停靠点。role=menuitem +\n\t\t\t\t\t\t\t\t\t\t aria-expanded 是 ARIA menu 模式里「可展开菜单项」的语义。 */}\n<button\n\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\trole=\"menuitem\"\n\t\t\t\t\t\t\t\t\t\t\tdata-row-key={headerKey}\n\t\t\t\t\t\t\t\t\t\t\ttabIndex={activeRowKey === headerKey ? 0 : -1}\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"dms-groupHeader\"\n\t\t\t\t\t\t\t\t\t\t\taria-expanded={!isCollapsed}\n\t\t\t\t\t\t\t\t\t\t\taria-label={t('group.toggleAria', { name: group.name, count: String(group.models.length) })}\n\t\t\t\t\t\t\t\t\t\t\tonFocus={() => onRowFocus(headerKey)}\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => toggleCollapse(group.id)}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<span className={`dms-groupChevron${isCollapsed ? ' dms-groupChevronClosed' : ''}`} aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t{IconChevronDown}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t<span id={headingId} className=\"dms-groupName\">{group.name}</span>\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"dms-groupCount\">{group.models.length}</span>\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t{!isCollapsed && group.models.map((model) => {\n\t\t\t\t\t\t\t\t\t\t\tconst rowKey = dmsRowKey(group.id, model.id);\n\t\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t<ModelOption\n\t\t\t\t\t\t\t\t\t\t\t\t\tkey={rowKey}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgroup={group}\n\t\t\t\t\t\t\t\t\t\t\t\t\tmodel={model}\n\t\t\t\t\t\t\t\t\t\t\t\t\tshowProvider={false}\n\t\t\t\t\t\t\t\t\t\t\t\t\tselected={state.current?.provider === group.id && state.current.model === model.id}\n\t\t\t\t\t\t\t\t\t\t\t\t\tbusy={busy}\n\t\t\t\t\t\t\t\t\t\t\t\t\trowKey={rowKey}\n\t\t\t\t\t\t\t\t\t\t\t\t\tactive={rowKey === activeRowKey}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsearchMode={false}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonRowFocus={onRowFocus}\n\t\t\t\t\t\t\t\t\t\t\t\t\tt={t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonChoose={choose}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t</section>\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{hits !== null && hits.total > MAX_VISIBLE_HITS && (\n\t\t\t\t\t\t// role=note:静态辅助说明,从菜单项语义里退出来。\n\t\t\t\t\t\t<div className=\"dms-more\" role=\"note\">{t('search.more', { shown: String(MAX_VISIBLE_HITS), total: String(hits.total) })}</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{state.current !== null && dmsSliderLevels(state).length >= 2 && (\n\t\t\t\t\t\t<div className=\"dms-effortFooter\">\n\t\t\t\t\t\t\t<span className=\"dms-effortFooterLabel\">{t('menu.effort')}</span>\n\t\t\t\t\t\t\t<EffortSlider state={state} select={select} t={t} onSelectFailure={markSelectFailure} />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{/* notice 容器常驻(空时 :empty 隐藏):插入式 live region 的首次播报\n\t\t\t\t\t 在各 AT 上不可靠,「已是当前模型」这类反馈只有这一条通道。 */}\n\t\t\t\t\t<div className=\"dms-notice\" role=\"status\">{notice ?? ''}</div>\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t\t{toast !== null && (\n\t\t\t\t<Toast\n\t\t\t\t\tkey={toast.seq}\n\t\t\t\t\ttext={toast.text}\n\t\t\t\t\ticon={toast.failed ? <IconWarningOutlineRegular size={14} /> : undefined}\n\t\t\t\t\tanchor={rootRef.current?.closest<HTMLElement>(\"[data-composer-card]\") ?? null}\n\t\t\t\t\tonDone={dismissToast}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</div>\n\t);\n}","/**\n* Stylesheet for the enhanced model seat.\n*\n* Injected as one `<style data-plugin=\"@bittersmilezzz/dsh-model-selector\">` tag by the client\n* apply and removed again on unload. Class names are prefixed `dms-` so they\n* cannot collide with CSS-module hashes from other plugins. Colors come only\n* from `--dsw-*` theme tokens, matching the shipped Menu material.\n*\n* 显式 `: string` 注解:不加注解时 tsc 会把整份 CSS 推断成字面量类型并写进\n* lib/types/client/styles.d.ts(19KB 的 d.ts 只为声明一个常量),发布包里\n* 白白多出十几 KB 且每次改样式都全量重写。\n*/\nexport const CSS: string = `\n.dms-root {\n position: relative;\n min-width: 0;\n}\n/* root 只作为焦点落点(tabIndex=-1):不画焦点环,真正的可聚焦控件在它内部。 */\n.dms-root:focus { outline: none; }\n\n.dms-trigger {\n display: flex;\n align-items: center;\n gap: 4px;\n min-width: 0;\n max-width: min(420px, calc(100vw - 48px));\n height: 28px;\n padding: 0 4px 0 8px;\n border: none;\n border-radius: 24px;\n outline: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 13px;\n line-height: 20px;\n font-weight: 500;\n cursor: pointer;\n}\n.dms-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }\n.dms-trigger:focus-visible { box-shadow: 0 0 0 2px var(--dsw-alias-border-l3); }\n.dms-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }\n\n.dms-triggerLabel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n line-height: 20px;\n}\n.dms-triggerEffort {\n flex: 0 0 auto;\n line-height: 20px;\n color: var(--dsw-alias-label-caption);\n}\n.dms-triggerProvider {\n flex: 0 0 auto;\n max-width: 88px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--dsw-alias-label-caption);\n font-size: 11px;\n line-height: 20px;\n font-weight: 400;\n}\n\n.dms-chevron,\n.dms-groupChevron {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n line-height: 0;\n}\n.dms-chevron {\n color: var(--dsw-alias-label-caption);\n transition: transform 120ms ease;\n}\n.dms-chevronOpen { transform: rotate(180deg); }\n.dms-groupChevron {\n color: var(--dsw-alias-label-tertiary);\n transition: transform 120ms ease;\n}\n.dms-groupChevronClosed { transform: rotate(-90deg); }\n.dms-model-check svg { display: block; }\n\n.dms-menu {\n position: absolute;\n right: 0;\n bottom: calc(100% + 8px);\n z-index: 20;\n display: flex;\n flex-direction: column;\n width: min(280px, calc(100vw - 32px));\n overflow: hidden;\n padding: 4px 0 4px 4px;\n box-sizing: border-box;\n border: 1px solid var(--dsw-alias-border-inverted);\n border-radius: 12px;\n background: var(--dsw-specific-menu);\n box-shadow: var(--dsw-shadow-lv3);\n color: var(--dsw-alias-label-primary);\n animation: dms-menu-in 150ms cubic-bezier(.22,1,.36,1);\n --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);\n --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);\n}\n.dms-menuModel { border-right: none; }\n.dms-menuBelow {\n bottom: auto;\n top: calc(100% + 8px);\n animation-name: dms-menu-in-below;\n}\n\n/* 菜单头部的「非列表」栏(loading / 空态 / 错误条 / 失败条 / 截断提示):\n 一律允许收缩(flex-shrink 1)且各自限高,让 .dms-groups 始终保住可滚动\n 的最小可视高度。2026-09-21 修复的 P0:向上弹时 useAnchoredMaxHeight 会把\n maxHeight 钳到「trigger 底缘 - 12px」(底缘固定、向上生长的语义),trigger\n 靠近视口顶部时该值很小;原先这些栏是 flex: 0 0 auto 完全不收缩,头部\n 40-100px 全部占完后 .dms-groups 的可视高度趋近 0,而 .dms-menu 是\n overflow:hidden —— 用户看到错误条却看不到也滚不到任何模型行。\n 现在给它们统一的上限(相对菜单高度)+ overflow,最坏情况是栏内自己滚动。 */\n.dms-status,\n.dms-empty {\n flex: 0 1 auto;\n min-height: 0;\n max-height: 40%;\n overflow-y: auto;\n padding: 10px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 13px;\n line-height: 20px;\n}\n\n.dms-more {\n flex: 0 1 auto;\n min-height: 0;\n max-height: 20%;\n overflow-y: auto;\n padding: 8px 12px;\n border-top: 1px solid var(--dsw-alias-border-l1, transparent);\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n/* Provider-load failure strip: capped and scrollable so a long list of failed\n groups can never squeeze the model list out of the menu (E4). */\n.dms-failures {\n flex: 0 1 auto;\n max-height: min(96px, 30%);\n min-height: 0;\n overflow-y: auto;\n}\n\n.dms-error,\n.dms-warning {\n flex: 0 1 auto;\n min-height: 0;\n max-height: 40%;\n overflow-y: auto;\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 8px;\n margin-bottom: 4px;\n padding: 7px 8px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover-danger);\n color: var(--dsw-alias-state-error-primary);\n font-size: 12px;\n line-height: 18px;\n}\n.dms-warning {\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-state-warn-label);\n}\n.dms-retry {\n flex: 0 0 auto;\n padding: 0;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: inherit;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n}\n.dms-retry:focus-visible { box-shadow: 0 0 0 2px var(--dsw-alias-border-l3); }\n\n/* Search box pinned above the scrollable list. */\n.dms-search {\n position: relative;\n margin-bottom: 4px;\n margin-right: 4px;\n flex: 0 0 auto;\n}\n.dms-searchInput {\n box-sizing: border-box;\n width: 100%;\n height: 30px;\n padding: 0 30px 0 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n outline: none;\n background: var(--dsw-alias-bg-input);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n line-height: 18px;\n}\n.dms-searchInput::placeholder { color: var(--dsw-alias-label-tertiary); }\n.dms-searchInput:focus-visible { box-shadow: 0 0 0 2px var(--dsw-alias-border-l3); }\n.dms-searchClear {\n position: absolute;\n top: 0;\n right: 0;\n display: grid;\n place-items: center;\n width: 30px;\n height: 30px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dms-searchClear:hover { color: var(--dsw-alias-label-primary); }\n/* 键盘焦点此前在这三个可聚焦控件上完全隐身(上轮只补了模型行)。 */\n.dms-searchClear:focus-visible {\n border-radius: 8px;\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\n}\n/* 图标组件只收 size/className,装饰性 aria-hidden 只能包一层 span;contents 让它不占布局。 */\n.dms-icon-slot { display: contents; }\n\n/* 列表区:菜单里唯一的弹性主体。max-height 被 useAnchoredMaxHeight 钳得很小\n (trigger 靠近视口顶 + 向上弹)时,32px 保底让「至少见到一行模型」始终成立\n —— 头部各栏此时按自己的 max-height 百分比上限收缩(见 .dms-status 等)。 */\n.dms-groups { flex: 1 1 auto; min-height: 32px; overflow-y: auto; contain: content; }\n/* 空态占位:空态播报节点在 role=menu 容器外,用与 .dms-groups 同款弹性布局\n 补回列表区本来的位置(不挤压其余栏,消息也不会贴到菜单顶部)。 */\n.dms-groupsFill { flex: 1 1 auto; min-height: 0; overflow-y: auto; contain: content; }\n.dms-group + .dms-group { margin-top: 4px; }\n\n/* Collapsible provider header: a full-width toggle button. */\n.dms-groupHeader {\n position: sticky;\n top: 0;\n z-index: 1;\n display: flex;\n align-items: center;\n gap: 4px;\n width: 100%;\n padding: 5px 8px 3px;\n border: none;\n outline: none;\n background: var(--dsw-specific-menu);\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n text-align: left;\n cursor: pointer;\n}\n.dms-groupHeader:hover { color: var(--dsw-alias-label-primary); }\n.dms-groupHeader:focus-visible { box-shadow: inset 0 0 0 2px var(--dsw-alias-border-l3); }\n.dms-groupName {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dms-groupCount { flex: 0 0 auto; color: var(--dsw-alias-label-dimmed); font-weight: 400; }\n\n.dms-nameRow {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.dms-badge {\n flex: 0 0 auto;\n padding: 0 6px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n color: var(--dsw-alias-label-caption);\n font-size: 10px;\n line-height: 16px;\n font-weight: 500;\n}\n.dms-notice {\n flex: 0 0 auto;\n margin: 4px 0 0;\n padding: 6px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n}\n/* 常驻 live region(内容随状态更新):空时收起,不占位。 */\n.dms-notice:empty { display: none; }\n.dms-effort {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n width: 100%;\n min-width: 0;\n min-height: 32px;\n color: var(--dsw-alias-label-secondary);\n user-select: none;\n box-sizing: border-box;\n}\n/* 档位读数:拖动时实时跟随 preview,替代此前只有读屏能看到的 aria-valuetext。 */\n.dms-effort-value {\n flex: 0 0 auto;\n margin-left: 8px;\n min-width: 3.2em;\n color: var(--dsw-alias-label-primary);\n font-size: 11px;\n line-height: 16px;\n text-align: right;\n}\n/* 官方契约的 efforts[].description,整行展示在滑杆下方。 */\n.dms-effort-desc {\n flex: 1 0 100%;\n margin-top: 2px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n line-height: 14px;\n}\n.dms-effort-slider {\n --dms-progress: 50%;\n position: relative;\n width: 100%;\n height: 30px;\n flex: 1 1 0;\n min-width: 0;\n border-radius: 999px;\n isolation: isolate;\n transition: filter 180ms ease;\n}\n.dms-effort-track {\n position: absolute;\n inset: 0;\n overflow: hidden;\n border-radius: inherit;\n background: linear-gradient(100deg, #03040a 0%, #071126 22%, #101d4c 45%, #302262 70%, #5d35a0 100%);\n box-shadow:\n inset 0 1px 0 rgba(189, 199, 255, .15),\n inset 0 -1px 0 rgba(0, 0, 0, .55),\n 0 3px 10px rgba(12, 17, 55, .34);\n}\n.dms-effort-track::after {\n content: \"\";\n position: absolute;\n inset: 0;\n background:\n radial-gradient(circle at 18% 45%, rgba(82, 130, 255, .12), transparent 24%),\n linear-gradient(90deg, rgba(0, 0, 0, .28), transparent 42%, rgba(168, 113, 255, .12));\n pointer-events: none;\n}\n.dms-effort-fx {\n position: absolute;\n z-index: 1;\n inset: 0;\n overflow: hidden;\n border-radius: inherit;\n pointer-events: none;\n}\n.dms-effort-canvas {\n position: absolute;\n z-index: 2;\n inset: 0;\n width: 100%;\n height: 100%;\n opacity: 1;\n image-rendering: pixelated;\n mix-blend-mode: screen;\n transition: filter 140ms ease;\n}\n.dms-effort-flare {\n position: absolute;\n z-index: 3;\n transition: left 70ms linear, filter 140ms ease;\n top: 50%;\n left: var(--dms-progress);\n width: 78px;\n height: 46px;\n border-radius: 50%;\n background: radial-gradient(ellipse at 100% 50%, rgba(255,255,255,.96) 0 4%, rgba(188,189,255,.8) 11%, rgba(106,87,255,.5) 28%, rgba(105,31,255,.2) 49%, transparent 74%);\n filter: blur(2px) saturate(1.25);\n mix-blend-mode: screen;\n transform: translate(-100%, -50%);\n pointer-events: none;\n}\n.dms-effort-flare::before,\n.dms-effort-flare::after {\n content: \"\";\n position: absolute;\n inset: 50% auto auto 100%;\n border-radius: 999px;\n transform: translate(-50%, -50%);\n}\n.dms-effort-flare::before {\n width: 52px;\n height: 1px;\n background: linear-gradient(90deg, transparent, rgba(100,160,255,.42), #f1ecff, rgba(193,82,255,.65), transparent);\n box-shadow: 0 0 7px #9b7cff, 0 0 13px rgba(72,132,255,.64);\n}\n.dms-effort-flare::after {\n width: 1px;\n height: 20px;\n background: linear-gradient(180deg, transparent, rgba(196,190,255,.84), transparent);\n box-shadow: 0 0 7px #9c7cff;\n}\n.dms-effort-knob {\n position: absolute;\n z-index: 4;\n top: 50%;\n left: clamp(14px, var(--dms-progress), calc(100% - 14px));\n width: 28px;\n height: 28px;\n border: 1px solid rgba(255,255,255,.94);\n border-radius: 50%;\n background: #fff;\n box-shadow:\n 0 0 0 2px rgba(92,105,255,.12),\n 0 0 14px rgba(121,82,255,.48),\n 0 2px 7px rgba(0,0,0,.3);\n transform: translate(-50%, -50%);\n transition: left 240ms cubic-bezier(.22,1,.36,1), transform 160ms ease, box-shadow 180ms ease;\n pointer-events: none;\n}\n.dms-effort-input {\n position: absolute;\n z-index: 5;\n inset: -5px 0;\n width: 100%;\n height: calc(100% + 10px);\n margin: 0;\n opacity: 0;\n cursor: grab;\n touch-action: none;\n}\n.dms-effort-input:active { cursor: grabbing; }\n.dms-effort-input:focus-visible + .dms-effort-knob {\n outline: 2px solid var(--dsw-static-blue-400);\n outline-offset: 2px;\n}\n.dms-effort.is-dragging .dms-effort-canvas {\n filter: saturate(1.45) brightness(1.28) contrast(1.06);\n}\n.dms-effort.is-dragging .dms-effort-flare {\n filter: blur(1.5px) saturate(1.6) brightness(1.42);\n transition: none;\n}\n.dms-effort.is-dragging .dms-effort-knob {\n transform: translate(-50%, -50%) scale(1.07);\n transition: none;\n box-shadow:\n 0 0 0 3px rgba(113,115,255,.25),\n 0 0 20px rgba(74,145,255,.86),\n 0 0 31px rgba(171,53,255,.66),\n 0 3px 8px rgba(0,0,0,.32);\n}\n.dms-effort.is-error .dms-effort-slider {\n outline: 1px solid var(--dsw-alias-state-error-secondary);\n outline-offset: 2px;\n}\n.dms-effort.is-busy { opacity: .72; }\n.dms-effort-error { flex: 1 0 100%; margin-top: 8px; padding: 6px 10px; border-radius: 8px; color: var(--dsw-alias-state-error-primary, #c83e4d); background: var(--dsw-alias-state-error-tertiary, rgba(220,55,70,.08)); font-size: 11px; line-height: 1.5; }\n/* 常驻容器(内容随状态更新):空时收起,不占位。 */\n.dms-effort-error:empty { display: none; }\n/* 视觉隐藏工具类(SR 播报/错误原文:不占布局但可被读屏读到)。 */\n.dms-sr {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n.dms-model-option {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 20px;\n align-items: center;\n gap: 8px;\n width: 100%;\n min-height: 38px;\n padding: 7px 9px;\n border: 0;\n border-radius: 9px;\n color: inherit;\n background: transparent;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.dms-model-option:hover { background: var(--dsw-alias-fill-tertiary, rgba(120,125,140,.09)); }\n.dms-model-option-copy { min-width: 0; }\n.dms-model-option-name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }\n/* 搜索命中片段:名称内标下划线(命中描述/供应商时不标,见 nameHit)。 */\n.dms-hit {\n text-decoration: underline;\n text-decoration-color: var(--dsw-static-blue-400, #4d70ff);\n text-decoration-thickness: 2px;\n text-underline-offset: 3px;\n}\n.dms-model-option-desc { display: block; margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #9296a0); font-size: 10px; }\n.dms-model-check { color: var(--dsw-static-deepseek-500, #4d70ff); font-size: 15px; text-align: center; }\n/* 方向键导航把焦点落在选项上。选中态是「背景 + 加粗 + 勾号」,悬停是「背景」,\n 两者与焦点指示必须两两可辨 —— 只用同一个 hover 背景 token 会让「焦点落在\n 选中行上」完全不可见(roving 的默认落点恰恰优先选中行)。 */\n.dms-model-option:focus-visible {\n background: var(--dsw-alias-interactive-bg-hover);\n box-shadow: inset 0 0 0 2px var(--dsw-alias-border-l3);\n}\n/* 当前选中行:仅靠勾号不足以在密集列表里定位。 */\n.dms-model-optionSelected { background: var(--dsw-alias-interactive-bg-hover); }\n.dms-model-optionSelected .dms-model-option-name { font-weight: 600; }\n/* select 进行中:官方以 disabled 变灰表达,这里用 aria-busy 驱动同样的视觉、\n 不夺键盘焦点(aria-disabled 保连续性,见 ModelOption)。aria-busy 挂在\n role=menu/listbox 容器上(.dms-groups)—— 挂在外层 .dms-menu 会罩住\n live region、压制其播报。 */\n.dms-groups[aria-busy=\"true\"] .dms-model-option { color: var(--dsw-alias-label-dimmed); }\n/* 搜索结果里的供应商标是区分跨供应商同名模型的唯一线索,之前无任何样式。 */\n.dms-model-option-provider {\n display: block;\n margin-top: 2px;\n overflow: hidden;\n color: var(--dsw-alias-label-dimmed, #9296a0);\n font-size: 10px;\n line-height: 14px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n@keyframes dms-menu-in {\n from { opacity: 0; transform: translateY(5px) scale(.98); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n}\n@keyframes dms-menu-in-below {\n from { opacity: 0; transform: translateY(-5px) scale(.98); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n}\nbody:not([data-ds-dark-theme]) .dms-effort-slider {\n filter: none;\n}\nbody:not([data-ds-dark-theme]) .dms-effort-track {\n background: var(--dsw-static-blue-75, #e5f0ff);\n box-shadow:\n inset 0 1px 0 rgba(255,255,255,.9),\n inset 0 0 0 1px rgba(80,133,194,.14),\n 0 3px 10px rgba(48,101,165,.13);\n}\nbody:not([data-ds-dark-theme]) .dms-effort-track::before {\n content: \"\";\n position: absolute;\n z-index: 0;\n inset: 0 auto 0 0;\n width: var(--dms-progress);\n border-radius: inherit;\n background: linear-gradient(90deg, #fff 0%, #e2f0ff 20%, #a8d0fb 57%, #438fdf 100%);\n transition: width 240ms cubic-bezier(.22,1,.36,1);\n}\nbody:not([data-ds-dark-theme]) .dms-effort.is-dragging .dms-effort-track::before {\n transition: none;\n}\nbody:not([data-ds-dark-theme]) .dms-effort-track::after {\n z-index: 1;\n background: linear-gradient(90deg, rgba(255,255,255,.48), transparent 34%, rgba(23,101,201,.07));\n}\nbody:not([data-ds-dark-theme]) .dms-effort-canvas {\n opacity: .78;\n mix-blend-mode: multiply;\n}\nbody:not([data-ds-dark-theme]) .dms-effort-flare {\n background: radial-gradient(ellipse at 100% 50%, rgba(255,255,255,.98) 0 5%, rgba(204,231,255,.88) 13%, rgba(91,162,241,.48) 31%, rgba(37,111,207,.16) 53%, transparent 75%);\n filter: blur(2px) saturate(1.12);\n}\nbody:not([data-ds-dark-theme]) .dms-effort-flare::before {\n background: linear-gradient(90deg, transparent, rgba(116,177,244,.34), #fff, rgba(66,139,225,.58), transparent);\n box-shadow: 0 0 7px rgba(58,133,222,.5), 0 0 13px rgba(104,176,255,.38);\n}\nbody:not([data-ds-dark-theme]) .dms-effort-flare::after {\n background: linear-gradient(180deg, transparent, rgba(255,255,255,.94), transparent);\n box-shadow: 0 0 7px rgba(64,137,224,.44);\n}\nbody:not([data-ds-dark-theme]) .dms-effort-knob {\n border-color: rgba(126,160,197,.32);\n box-shadow:\n 0 0 0 2px rgba(58,124,207,.09),\n 0 0 13px rgba(48,118,207,.3),\n 0 3px 8px rgba(39,77,119,.18);\n}\nbody:not([data-ds-dark-theme]) .dms-effort.is-dragging .dms-effort-knob {\n box-shadow:\n 0 0 0 3px rgba(36,105,192,.15),\n 0 0 20px rgba(25,100,201,.45),\n 0 3px 8px rgba(39,77,119,.18);\n}\n@media (prefers-reduced-motion: reduce) {\n .dms-chevron,\n .dms-groupChevron,\n .dms-effort-slider,\n .dms-effort-canvas,\n .dms-effort-knob,\n .dms-effort-flare,\n body:not([data-ds-dark-theme]) .dms-effort-track::before { transition: none; }\n .dms-menu { animation: none; }\n}\n\n/* Inline effort footer pinned below the scrollable model list (single-pane\n menu: switching models and adjusting effort both live in one surface). */\n.dms-effortFooter {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 4px 4px 0 0;\n padding: 6px 10px;\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dms-effortFooterLabel {\n flex: 0 0 auto;\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n line-height: 18px;\n white-space: nowrap;\n}\n.dms-effortFooter .dms-effort { flex: 1 1 auto; min-width: 0; }\n\n`;\n","/**\n * dsh-model-selector — browser half.\n *\n * Replaces the shipped `conversation.input.model` seat with an enhanced one\n * (provider-group collapse + name search) while keeping the exact shared\n * per-session directory (`ctx.modelDirectories`) the /model popup reads, so\n * both entries stay in sync. Shadowing is the sanctioned seam: the slot is\n * `single`, so a registration at a LOWER priority than the shipped occupant\n * (default 0) becomes the rendered winner.\n */\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type { SessionId } from '@deepseek-ai/dsh-session/types'\n// Type-only: pulls the ui-renderer Context merge (ctx.slots), moved here in dsh alpha.2.\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\n// Type-only: pulls the session controller Context merge (ctx.sessions) in alpha.2.\nimport type {} from '@deepseek-ai/dsh-api-session-controller/client'\n// Type-only: pulls the ui-conversation SlotMap merge (the input model seat).\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the model-selection plugin's Context merge (ctx.modelDirectories)\n// plus the injected face type the seat registration hands to the component.\nimport type { ModelSelectInjected } from '@deepseek-ai/dsh-client-ui-model-selection/client'\nimport { ModelSelect, zhDict, enDict } from './ModelSelect.tsx'\nimport { CSS } from './styles.ts'\n\n/** Dictionary namespace owned by this plugin. */\nconst NS = 'modelSelector'\n\n/**\n * 座位优先级。ui-conversation 把 `conversation.input.model` 声明为 kind 'single'\n * (单槽按 priority 升序取最低者渲染),官方占用者\n * (@deepseek-ai/dsh-client-ui-model-selection)注册时不传 priority(即默认 0),\n * 故 -1 恒胜出。**改这个值等于静默换回官方 UI** —— test/plugin-contract.test.mjs\n * 直接断言本常量(而不是扫源码文本,那样一行注释就能骗过它),注册处也不许再写裸字面量。\n */\nexport const SEAT_PRIORITY = -1\n\n/** Required services: the registry, session lookup, locale, the slot seat, and the model directory's Remote faces. */\nexport const inject = [\n 'slots',\n 'sessions',\n 'locale',\n 'modelDirectories',\n 'remote',\n 'remote.session',\n]\n\n/**\n * Client plugin body: register the dictionaries and stylesheet, then take the\n * model seat over the shared directory once `modelDirectories` appears.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, {\n zh: zhDict,\n en: enDict,\n }), 'dsh-model-selector: dictionaries')\n ctx.effect(() => {\n const tag = document.createElement('style')\n tag.dataset.plugin = '@bittersmilezzz/dsh-model-selector'\n tag.textContent = CSS\n document.head.appendChild(tag)\n return () => {\n tag.remove()\n }\n }, 'dsh-model-selector: styles')\n ctx.inject(['modelDirectories'], (scope) => {\n const models = scope.modelDirectories\n const sessions = scope.sessions\n // 有意的座位层重叠,勿当 bug「修掉」:官方 @deepseek-ai/dsh-client-ui-model-selection\n // 自带同名座位(packages/client/ui-model-selection/src/client/index.ts:177,\n // 未传 priority → 默认 0),本插件靠 priority: -1 遮蔽它。ui-conversation 把该座位\n // 声明为 single/session(kind: 'single'; scope: 'session'),single 槽「priority 升序\n // 取最低者渲染」,故 -1 恒胜出。两者共用同一份 ctx.modelDirectories 会话目录,\n // 任一入口的切换就是另一入口的显示——重叠是设计取舍,不是冲突。\n // 重新评估触发条件:官方若改动该座位的默认优先级、或把它从 single 改成 chain\n // (chain 每项都参与渲染,遮蔽不生效),此遮蔽即失效,不能默认仍然成立。\n scope.slots.inject('conversation.input.model', () => scope.slots.register({\n name: 'conversation.input.model',\n locale: NS,\n priority: SEAT_PRIORITY,\n inject: (sessionId: string): ModelSelectInjected => {\n const sid = sessionId as SessionId\n const directory = models.directoryFor(sid)\n const available = sessions.subagentAddress(sid) === undefined\n return {\n available,\n directory: directory.store,\n load: () => {\n if (available) directory.load().catch((error: unknown) => { console.warn('[dsh-model-selector] directory.load failed:', error) })\n },\n // alpha.2 契约:直接透传 directory.select 的 RemoteResult(失败详情随 error\n // 回给调用方播报),不可选时 Promise.resolve(undefined)(与官方注册同款)。\n select: (selection) => available\n ? directory.select(selection)\n : Promise.resolve(undefined),\n }\n },\n }, ModelSelect))\n })\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModelSelect.d.ts","sourceRoot":"","sources":["../../../src/client/ModelSelect.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,cAAc,CAAA;AASzD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAA;AAGzB,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,mDAAmD,CAAA;AACjH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAsBlE,mEAAmE;AACnE,KAAK,cAAc,GAAG,mBAAmB,CAAA;AAKzC,UAAU,iBAAiB;IACzB,KAAK,EAAE,cAAc,CAAA;IACrB,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IACrC,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,CAAA;IAC/B,4DAA4D;IAC5D,eAAe,EAAE,MAAM,IAAI,CAAA;CAC5B;AA4BD,eAAO,MAAM,YAAY,oEAA2E,iBAAiB,
|
|
1
|
+
{"version":3,"file":"ModelSelect.d.ts","sourceRoot":"","sources":["../../../src/client/ModelSelect.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,cAAc,CAAA;AASzD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAA;AAGzB,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,mDAAmD,CAAA;AACjH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAsBlE,mEAAmE;AACnE,KAAK,cAAc,GAAG,mBAAmB,CAAA;AAKzC,UAAU,iBAAiB;IACzB,KAAK,EAAE,cAAc,CAAA;IACrB,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IACrC,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,CAAA;IAC/B,4DAA4D;IAC5D,eAAe,EAAE,MAAM,IAAI,CAAA;CAC5B;AA4BD,eAAO,MAAM,YAAY,oEAA2E,iBAAiB,8BA8SnH,CAAC;AA0GH,UAAU,gBAAgB;IACxB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAA;IAC7C,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IACrC,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,CAAA;CAChC;AACD,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,gBAAgB,4BAutB9F"}
|