@bittersmilezzz/dsh-model-selector 0.1.20 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -7
- package/lib/client.js +395 -138
- package/lib/client.js.map +1 -1
- package/lib/types/client/ModelSelect.d.ts +3 -1
- package/lib/types/client/ModelSelect.d.ts.map +1 -1
- package/lib/types/client/copy.d.ts +43 -0
- package/lib/types/client/copy.d.ts.map +1 -0
- package/lib/types/client/effort.d.ts +29 -4
- package/lib/types/client/effort.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +8 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/keys.d.ts +62 -0
- package/lib/types/client/keys.d.ts.map +1 -0
- package/lib/types/client/menuFit.d.ts +5 -0
- package/lib/types/client/menuFit.d.ts.map +1 -1
- package/lib/types/client/roving.d.ts +8 -0
- package/lib/types/client/roving.d.ts.map +1 -1
- package/lib/types/client/search.d.ts +73 -0
- package/lib/types/client/search.d.ts.map +1 -0
- package/lib/types/client/styles.d.ts +5 -1
- package/lib/types/client/styles.d.ts.map +1 -1
- package/package.json +8 -3
package/lib/client.js
CHANGED
|
@@ -119,6 +119,37 @@ window.__ModuleLoader__.load({
|
|
|
119
119
|
}
|
|
120
120
|
return best?.id;
|
|
121
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* 已知档位的秩;适配器自造的非规范 id(如 'turbo')返回 undefined —— 与
|
|
124
|
+
* 「已知但秩为 0」(off)区分开。maxEffortOf 对并列的未知档取首个,把它当
|
|
125
|
+
* 「最强档」提交等于随机挑一个档、还可能被宿主拒,故调用方必须先过这一关。
|
|
126
|
+
*/
|
|
127
|
+
function dmsEffortRank(id) {
|
|
128
|
+
return Object.prototype.hasOwnProperty.call(EFFORT_RANK, id) ? EFFORT_RANK[id] : void 0;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 选择模型时自动落到最强档的决策(从 choose() 搬出并修正):
|
|
132
|
+
* - 非推理模型 / 无档位 → 不提交 effort;
|
|
133
|
+
* - 最强档是 'off'(模型只提供 off)→ 不提交;
|
|
134
|
+
* - 最强档是**非规范 id**(rank 未知)→ 不提交:maxEffortOf 对并列未知档取
|
|
135
|
+
* 首个,把它当「最强」提交会挑错档甚至被宿主拒(用户看到「切换失败」);
|
|
136
|
+
* - 否则提交已知最强档;只有它不等于模型声明的默认档时才播报落点。
|
|
137
|
+
*/
|
|
138
|
+
function dmsChoosePlan(reasoning) {
|
|
139
|
+
if (reasoning === void 0) return {
|
|
140
|
+
effort: void 0,
|
|
141
|
+
autoRaised: false
|
|
142
|
+
};
|
|
143
|
+
const best = maxEffortOf(reasoning);
|
|
144
|
+
if (best === void 0 || best === "off" || dmsEffortRank(best) === void 0) return {
|
|
145
|
+
effort: void 0,
|
|
146
|
+
autoRaised: false
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
effort: best,
|
|
150
|
+
autoRaised: best !== reasoning.defaultEffort
|
|
151
|
+
};
|
|
152
|
+
}
|
|
122
153
|
function dmsEffortIndex(levels, id) {
|
|
123
154
|
return levels.findIndex((level) => level.id === id);
|
|
124
155
|
}
|
|
@@ -168,12 +199,15 @@ window.__ModuleLoader__.load({
|
|
|
168
199
|
* 只有当自回滚以来没有任何新的提交改写 committedRef(仍等于回滚前的档位)、
|
|
169
200
|
* 提交纪元未推进(epoch === epochAtCommit,即期间没有发起过新提交——提交失败
|
|
170
201
|
* 回滚后 committedRef 会回到与上一轮回滚相同的值,值比较无法区分「无新提交」
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
202
|
+
* 与「新提交失败回滚」,纪元才能区分)、当前无拖动/提交在途,**且生效模型仍是
|
|
203
|
+
* 发起提交时的那个**(sameModel)时,迟到结果才仍对应当前唯一的意图链——此时
|
|
204
|
+
* UI 处于「回滚但后端已生效」的错位态,应补一次同步而不是保持回滚。
|
|
205
|
+
* 否则以新操作链为准:模型被外部改写(另一入口切换 / 宿主推送新投影 / 重连后
|
|
206
|
+
* 同步出别的选择)时,next/index 是在旧模型的档位表上算出来的,写回 UI 会显示
|
|
207
|
+
* 一个后端并未生效的档位。
|
|
174
208
|
*/
|
|
175
|
-
function dmsShouldAdoptLateSuccess(committed, previous, dragging, committing, epoch, epochAtCommit) {
|
|
176
|
-
return committed === previous && epoch === epochAtCommit && !dragging && !committing;
|
|
209
|
+
function dmsShouldAdoptLateSuccess(committed, previous, dragging, committing, epoch, epochAtCommit, sameModel) {
|
|
210
|
+
return sameModel && committed === previous && epoch === epochAtCommit && !dragging && !committing;
|
|
177
211
|
}
|
|
178
212
|
/**
|
|
179
213
|
* 拖动终止事件是否对应当前活动拖动:pointerActive 为假(拖动已结束/从未开始)
|
|
@@ -525,6 +559,11 @@ window.__ModuleLoader__.load({
|
|
|
525
559
|
* 拖出屏外)时,返回钳到视口内的 left 值,调用方改用 left 锚定。
|
|
526
560
|
* 返回 undefined 表示保持默认右锚定。
|
|
527
561
|
* menuWidth 传菜单实际渲染宽度(offsetWidth),与 CSS `min(280px, 100vw-32px)` 解耦。
|
|
562
|
+
*
|
|
563
|
+
* **坐标系**:入参与返回值都是**视口坐标**。菜单是 .dms-root(position:
|
|
564
|
+
* relative)内的 absolute 元素,left 的参考系是 root 的 padding box —— 调用方
|
|
565
|
+
* 必须减去 root 的左缘再写进 style.left,否则窄窗口下菜单整体右移、横向溢出
|
|
566
|
+
* 时整幅画到屏外。
|
|
528
567
|
*/
|
|
529
568
|
function dmsMenuLeft(rootRight, menuWidth, viewportWidth, margin) {
|
|
530
569
|
if (rootRight - menuWidth >= margin && rootRight <= viewportWidth - margin) return void 0;
|
|
@@ -533,6 +572,26 @@ window.__ModuleLoader__.load({
|
|
|
533
572
|
//#endregion
|
|
534
573
|
//#region src/client/roving.ts
|
|
535
574
|
/**
|
|
575
|
+
* dsh-model-selector — 菜单 roving tabindex 的纯决策逻辑(node 可测)。
|
|
576
|
+
*
|
|
577
|
+
* 模型列表可达数百行,若每行都是 Tab 停靠点,Tab 键会陷入行海;roving
|
|
578
|
+
* tabindex(WAI-ARIA menu 模式的通行做法)只让「活动行」留在 Tab 序里,
|
|
579
|
+
* 其余行 tabindex=-1、仅方向键可达。本模块只负责一件事:给定当前渲染的
|
|
580
|
+
* 行集合与选中行,算出 Tab 从搜索框进入列表时应停靠的默认行键。
|
|
581
|
+
*/
|
|
582
|
+
/**
|
|
583
|
+
* 模型行键的**唯一构造点**:菜单 DOM 的 `data-row-key`、roving 决策与行焦点
|
|
584
|
+
* 回调必须同源。此前这三处各自拼模板串,一次局部改名就能让 dmsDefaultRowKey
|
|
585
|
+
* 返回的键匹配不到任何行(Tab 落点静默失效),且测试拿自制副本验证不到。
|
|
586
|
+
*/
|
|
587
|
+
function dmsRowKey(providerId, modelId) {
|
|
588
|
+
return `${providerId}\u0000${modelId}`;
|
|
589
|
+
}
|
|
590
|
+
/** 组头行键(分组视图里组头也参与方向键导航)。 */
|
|
591
|
+
function dmsHeaderKey(providerId) {
|
|
592
|
+
return `header:${providerId}`;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
536
595
|
* Tab 默认落点:搜索态取「选中行若在命中集内,否则首个命中」;分组态取
|
|
537
596
|
* 「选中行若可见,否则首个可见模型行」;全部折叠(无可见模型行)时回退到
|
|
538
597
|
* 首个组头。返回 null 表示当前没有任何可聚焦行(空态由菜单外的 role=status
|
|
@@ -547,6 +606,144 @@ window.__ModuleLoader__.load({
|
|
|
547
606
|
return set.headerKeys[0] ?? null;
|
|
548
607
|
}
|
|
549
608
|
//#endregion
|
|
609
|
+
//#region src/client/search.ts
|
|
610
|
+
/**
|
|
611
|
+
* 搜索归一化:NFKD 分解 → 去组合标记 → 小写。
|
|
612
|
+
*
|
|
613
|
+
* 比裸 `toLowerCase()` 宽容:`'İstanbul'` 能被 `'istanbul'` 命中(裸 toLowerCase
|
|
614
|
+
* 把 U+0130 折成 `'i̇'` 两个码位,与 ASCII `'i'` 反而不匹配 → 漏检;`'café'` 同理)。
|
|
615
|
+
* 注意它**可能改变码位长度**,所以不能用它的下标去切原串 —— 高亮区间另走
|
|
616
|
+
* {@link dmsNameHit},只在长度不变时给区间。
|
|
617
|
+
*/
|
|
618
|
+
function dmsFold(text) {
|
|
619
|
+
return text.normalize("NFKD").replace(/\p{M}+/gu, "").toLowerCase();
|
|
620
|
+
}
|
|
621
|
+
/** 搜索词归一化(去首尾空白后折叠)。空串 = 非搜索态。 */
|
|
622
|
+
function dmsNormalizeQuery(raw) {
|
|
623
|
+
return dmsFold(raw.trim());
|
|
624
|
+
}
|
|
625
|
+
/** 模型条目的搜索文本(haystack)。与 choices 构建处同源,避免两处漂移。 */
|
|
626
|
+
function dmsHaystack(group, model) {
|
|
627
|
+
return dmsFold(`${model.name}\n${model.description ?? ""}\n${group.name}\n${model.id}\n${group.id}`);
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* 名称内的命中区间:折叠后长度不变时用下标定位;长度变化(含会改变码位数的
|
|
631
|
+
* 大小写/变音符映射)时返回 null —— 宁可不高亮,也不能用错位的区间去切原串
|
|
632
|
+
* (那会把高亮画到别的字符上,甚至越界)。
|
|
633
|
+
*/
|
|
634
|
+
function dmsNameHit(name, normalizedQuery) {
|
|
635
|
+
if (normalizedQuery === "") return null;
|
|
636
|
+
const folded = dmsFold(name);
|
|
637
|
+
if (folded.length !== name.length) return null;
|
|
638
|
+
const at = folded.indexOf(normalizedQuery);
|
|
639
|
+
return at < 0 ? null : {
|
|
640
|
+
start: at,
|
|
641
|
+
end: at + normalizedQuery.length
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* 搜索:遍历 choices 计数全部命中,只构造窗口内(前 limit 条)的渲染条目 ——
|
|
646
|
+
* 宽泛关键词(单字母)命中数百条时,每次击键省掉数百次对象分配与名称匹配;
|
|
647
|
+
* total 单独累计,播报与「仅显示前 N 条」提示仍然准确。
|
|
648
|
+
*/
|
|
649
|
+
function dmsSearchHits(choices, normalizedQuery, limit) {
|
|
650
|
+
if (normalizedQuery === "") return null;
|
|
651
|
+
const items = [];
|
|
652
|
+
let total = 0;
|
|
653
|
+
for (const choice of choices) {
|
|
654
|
+
if (!choice.haystack.includes(normalizedQuery)) continue;
|
|
655
|
+
total += 1;
|
|
656
|
+
if (items.length >= limit) continue;
|
|
657
|
+
items.push({
|
|
658
|
+
group: choice.group,
|
|
659
|
+
model: choice.model,
|
|
660
|
+
nameHit: dmsNameHit(choice.model.name, normalizedQuery)
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
items,
|
|
665
|
+
total
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* 空态判定:**无模型优先于无命中** —— 目录为空而用户又输了关键词时,真相是
|
|
670
|
+
* 「没有可用的模型」(该引导用户去配置/加载目录),不是「没有匹配关键词的模型」
|
|
671
|
+
* (那会引导用户去改关键词,而无论改什么都不会有结果)。
|
|
672
|
+
*/
|
|
673
|
+
function dmsMenuEmptyState(input) {
|
|
674
|
+
if (input.status === "ready" && input.choiceCount === 0) return "noModels";
|
|
675
|
+
if (input.searching && input.hitCount === 0) return "noHits";
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
//#endregion
|
|
679
|
+
//#region src/client/keys.ts
|
|
680
|
+
/**
|
|
681
|
+
* 按键 → 动作。无行可去时对方向键/Home/End 返回 none 而不是吞键:列表为空
|
|
682
|
+
* (加载中/无命中/全折叠)时搜索框里的上下键仍应有移光标语义,preventDefault
|
|
683
|
+
* 由调用方在真正执行动作时才发。
|
|
684
|
+
*/
|
|
685
|
+
function dmsMenuKeyAction(input) {
|
|
686
|
+
if (input.composing) return { type: "none" };
|
|
687
|
+
const { key, open, fromSearch, inMenu, onNonInput, hasQuery, rowCount, hitCount } = input;
|
|
688
|
+
if (key === "Escape" && open) {
|
|
689
|
+
if (hasQuery && inMenu) return {
|
|
690
|
+
type: "clearQuery",
|
|
691
|
+
focusSearch: !fromSearch
|
|
692
|
+
};
|
|
693
|
+
return { type: "close" };
|
|
694
|
+
}
|
|
695
|
+
if (!open) return { type: "none" };
|
|
696
|
+
if ((key === "ArrowDown" || key === "ArrowUp") && (onNonInput || fromSearch)) {
|
|
697
|
+
if (rowCount === 0) return { type: "none" };
|
|
698
|
+
return {
|
|
699
|
+
type: "moveFocus",
|
|
700
|
+
offset: key === "ArrowDown" ? 1 : -1
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
if ((key === "Home" || key === "End") && onNonInput) {
|
|
704
|
+
if (rowCount === 0) return { type: "none" };
|
|
705
|
+
return {
|
|
706
|
+
type: "focusEdge",
|
|
707
|
+
last: key === "End"
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
if (key === "Enter" && fromSearch && hitCount > 0) return { type: "chooseFirstHit" };
|
|
711
|
+
return { type: "none" };
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* roving 环绕下标:焦点不在任何行上(activeIndex < 0)时向下从首行进、向上从
|
|
715
|
+
* 末行进;已在某行上时按 offset 循环。length <= 0 返回 -1(无可去之处)。
|
|
716
|
+
*/
|
|
717
|
+
function dmsNextRowIndex(activeIndex, offset, length) {
|
|
718
|
+
if (length <= 0) return -1;
|
|
719
|
+
return ((activeIndex < 0 ? offset > 0 ? -1 : 0 : activeIndex) + offset + length) % length;
|
|
720
|
+
}
|
|
721
|
+
//#endregion
|
|
722
|
+
//#region src/client/copy.ts
|
|
723
|
+
/**
|
|
724
|
+
* trigger 文案。目录成员资格只是参考(routable 契约):current 匹配不到任何
|
|
725
|
+
* group 不代表没有选择 —— 此时显示 provider/model 原始 id,而不是「选择模型」。
|
|
726
|
+
*/
|
|
727
|
+
function dmsTriggerCopy(input, t) {
|
|
728
|
+
const { current, modelName, providerName, effortLabel, waiting } = input;
|
|
729
|
+
const modelLabel = modelName ?? (waiting ? t("trigger.loading") : current === null ? t("trigger.fallback") : `${current.provider}/${current.model}`);
|
|
730
|
+
const providerLabel = modelName === void 0 ? void 0 : providerName;
|
|
731
|
+
const label = effortLabel === void 0 ? modelLabel : `${modelLabel} · ${effortLabel}`;
|
|
732
|
+
const title = providerLabel === void 0 ? label : `${providerLabel} · ${label}`;
|
|
733
|
+
const named = providerLabel === void 0 ? modelLabel : `${providerLabel} ${modelLabel}`;
|
|
734
|
+
const ariaModel = modelName === void 0 ? current === null ? null : `${current.provider}/${current.model}` : named;
|
|
735
|
+
return {
|
|
736
|
+
modelLabel,
|
|
737
|
+
providerLabel,
|
|
738
|
+
label,
|
|
739
|
+
title,
|
|
740
|
+
aria: waiting ? t("trigger.loading") : ariaModel === null ? t("trigger.selectAria") : effortLabel === void 0 ? t("trigger.aria", { model: ariaModel }) : t("trigger.ariaEffort", {
|
|
741
|
+
model: ariaModel,
|
|
742
|
+
effort: effortLabel
|
|
743
|
+
})
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
//#endregion
|
|
550
747
|
//#region src/client/ModelSelect.tsx
|
|
551
748
|
/**
|
|
552
749
|
* ModelSelect: the enhanced composer model seat (`conversation.input.model`).
|
|
@@ -582,12 +779,14 @@ window.__ModuleLoader__.load({
|
|
|
582
779
|
* 必须释放滑杆的 committing 锁并回滚,否则滑杆被锁死到菜单关闭。
|
|
583
780
|
* 调法:后端/网络慢时调大避免误回滚(滑杆锁死时间随之变长);调小更早释放交互。 */
|
|
584
781
|
const EFFORT_COMMIT_TIMEOUT_MS = 12e3;
|
|
585
|
-
const EffortSlider = react.memo(function EffortSlider({ state, select, t }) {
|
|
782
|
+
const EffortSlider = react.memo(function EffortSlider({ state, select, t, onSelectFailure }) {
|
|
586
783
|
const levels = dmsSliderLevels(state);
|
|
587
784
|
const [effort, setEffort] = react.useState(() => levels[dmsEffectiveEffortIndex(levels, state)]?.id ?? "");
|
|
588
785
|
const [preview, setPreview] = react.useState(() => dmsEffectiveEffortIndex(levels, state));
|
|
589
786
|
const [committing, setCommitting] = react.useState(false);
|
|
590
787
|
const [localError, setLocalError] = react.useState(null);
|
|
788
|
+
const stateRef = react.useRef(state);
|
|
789
|
+
stateRef.current = state;
|
|
591
790
|
const canvasRef = react.useRef(null);
|
|
592
791
|
const inputRef = react.useRef(null);
|
|
593
792
|
const committedRef = react.useRef("");
|
|
@@ -616,7 +815,7 @@ window.__ModuleLoader__.load({
|
|
|
616
815
|
setLocalError(null);
|
|
617
816
|
}, [
|
|
618
817
|
available,
|
|
619
|
-
levels
|
|
818
|
+
levels,
|
|
620
819
|
state.current?.provider,
|
|
621
820
|
state.current?.model,
|
|
622
821
|
state.current?.reasoningEffort
|
|
@@ -658,7 +857,13 @@ window.__ModuleLoader__.load({
|
|
|
658
857
|
if (dmsEffortBusy(committingRef.current, state.status)) return;
|
|
659
858
|
const index = dmsClampIndex(raw, levels.length);
|
|
660
859
|
const next = levels[index]?.id;
|
|
661
|
-
if (next === void 0
|
|
860
|
+
if (next === void 0) return;
|
|
861
|
+
if (next === committedRef.current) {
|
|
862
|
+
previewRef.current = index;
|
|
863
|
+
setPreview(index);
|
|
864
|
+
setEffort(next);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
662
867
|
committingRef.current = true;
|
|
663
868
|
const previous = committedRef.current;
|
|
664
869
|
commitEpochRef.current += 1;
|
|
@@ -671,6 +876,7 @@ window.__ModuleLoader__.load({
|
|
|
671
876
|
try {
|
|
672
877
|
const current = state.current;
|
|
673
878
|
if (current === null) throw new Error(t("empty.efforts"));
|
|
879
|
+
const modelKeyAtCommit = dmsRowKey(current.provider, current.model);
|
|
674
880
|
if (!await new Promise((resolve, reject) => {
|
|
675
881
|
let settled = false;
|
|
676
882
|
const timer = window.setTimeout(() => {
|
|
@@ -686,7 +892,9 @@ window.__ModuleLoader__.load({
|
|
|
686
892
|
reasoningEffort: next
|
|
687
893
|
}).then((accepted) => {
|
|
688
894
|
if (settled) {
|
|
689
|
-
|
|
895
|
+
const latest = stateRef.current.current;
|
|
896
|
+
const sameModel = latest !== null && dmsRowKey(latest.provider, latest.model) === modelKeyAtCommit;
|
|
897
|
+
if (accepted && mountedRef.current && dmsShouldAdoptLateSuccess(committedRef.current, previous, draggingRef.current, committingRef.current, commitEpochRef.current, epochAtCommit, sameModel)) {
|
|
690
898
|
committedRef.current = next;
|
|
691
899
|
previewRef.current = index;
|
|
692
900
|
setEffort(next);
|
|
@@ -714,6 +922,7 @@ window.__ModuleLoader__.load({
|
|
|
714
922
|
setPreview(index);
|
|
715
923
|
}
|
|
716
924
|
} catch (cause) {
|
|
925
|
+
onSelectFailure();
|
|
717
926
|
const restore = Math.max(0, dmsEffortIndex(levels, previous));
|
|
718
927
|
committedRef.current = previous;
|
|
719
928
|
previewRef.current = restore;
|
|
@@ -730,7 +939,8 @@ window.__ModuleLoader__.load({
|
|
|
730
939
|
levels,
|
|
731
940
|
select,
|
|
732
941
|
state,
|
|
733
|
-
t
|
|
942
|
+
t,
|
|
943
|
+
onSelectFailure
|
|
734
944
|
]);
|
|
735
945
|
const drag = useEffortDrag({
|
|
736
946
|
levelCount: () => levels.length,
|
|
@@ -818,14 +1028,14 @@ window.__ModuleLoader__.load({
|
|
|
818
1028
|
className: "dms-effort-desc",
|
|
819
1029
|
children: effortDesc
|
|
820
1030
|
}),
|
|
821
|
-
|
|
1031
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
822
1032
|
className: "dms-sr",
|
|
823
1033
|
role: "status",
|
|
824
|
-
children: error
|
|
1034
|
+
children: error ?? ""
|
|
825
1035
|
}),
|
|
826
|
-
|
|
1036
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
827
1037
|
className: "dms-effort-error",
|
|
828
|
-
children: error
|
|
1038
|
+
children: error ?? ""
|
|
829
1039
|
})
|
|
830
1040
|
]
|
|
831
1041
|
});
|
|
@@ -839,7 +1049,7 @@ window.__ModuleLoader__.load({
|
|
|
839
1049
|
* 但 100 条命中里绝大多数命中区间不变——按值比较 start/end,值相同即跳过
|
|
840
1050
|
* 重渲染,搜索才不卡顿。
|
|
841
1051
|
*/
|
|
842
|
-
const ModelOption = react.memo(function ModelOption({ group, model, showProvider, selected, busy, rowKey, active, onRowFocus, nameHit, t, onChoose }) {
|
|
1052
|
+
const ModelOption = react.memo(function ModelOption({ group, model, showProvider, selected, busy, rowKey, active, searchMode, onRowFocus, nameHit, t, onChoose }) {
|
|
843
1053
|
const hit = nameHit === void 0 || nameHit === null || nameHit.start === nameHit.end ? null : [
|
|
844
1054
|
model.name.slice(0, nameHit.start),
|
|
845
1055
|
model.name.slice(nameHit.start, nameHit.end),
|
|
@@ -847,8 +1057,9 @@ window.__ModuleLoader__.load({
|
|
|
847
1057
|
];
|
|
848
1058
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
849
1059
|
type: "button",
|
|
850
|
-
role: "menuitemradio",
|
|
851
|
-
"aria-checked": selected,
|
|
1060
|
+
role: searchMode ? "option" : "menuitemradio",
|
|
1061
|
+
"aria-checked": searchMode ? void 0 : selected,
|
|
1062
|
+
"aria-selected": searchMode ? selected : void 0,
|
|
852
1063
|
"data-row-key": rowKey,
|
|
853
1064
|
tabIndex: active ? 0 : -1,
|
|
854
1065
|
className: `dms-model-option${selected ? " dms-model-optionSelected" : ""}`,
|
|
@@ -903,6 +1114,7 @@ window.__ModuleLoader__.load({
|
|
|
903
1114
|
if (prev.busy !== next.busy) return false;
|
|
904
1115
|
if (prev.rowKey !== next.rowKey) return false;
|
|
905
1116
|
if (prev.active !== next.active) return false;
|
|
1117
|
+
if (prev.searchMode !== next.searchMode) return false;
|
|
906
1118
|
if (prev.onRowFocus !== next.onRowFocus) return false;
|
|
907
1119
|
if (prev.t !== next.t) return false;
|
|
908
1120
|
if (prev.onChoose !== next.onChoose) return false;
|
|
@@ -932,6 +1144,12 @@ window.__ModuleLoader__.load({
|
|
|
932
1144
|
failed
|
|
933
1145
|
});
|
|
934
1146
|
}, []);
|
|
1147
|
+
const markSelectFailure = react.useCallback(() => {
|
|
1148
|
+
lastActionRef.current = "select";
|
|
1149
|
+
}, []);
|
|
1150
|
+
const dismissToast = react.useCallback(() => {
|
|
1151
|
+
setToast(null);
|
|
1152
|
+
}, []);
|
|
935
1153
|
const lastActionRef = react.useRef("load");
|
|
936
1154
|
const rootRef = react.useRef(null);
|
|
937
1155
|
const triggerRef = react.useRef(null);
|
|
@@ -941,7 +1159,7 @@ window.__ModuleLoader__.load({
|
|
|
941
1159
|
const choices = react.useMemo(() => state.groups.flatMap((group) => group.models.map((model) => ({
|
|
942
1160
|
group,
|
|
943
1161
|
model,
|
|
944
|
-
haystack:
|
|
1162
|
+
haystack: dmsHaystack(group, model),
|
|
945
1163
|
selection: {
|
|
946
1164
|
provider: group.id,
|
|
947
1165
|
model: model.id,
|
|
@@ -957,41 +1175,30 @@ window.__ModuleLoader__.load({
|
|
|
957
1175
|
const effectiveEffort = state.current?.reasoningEffort ?? reasoning?.defaultEffort;
|
|
958
1176
|
const effortLabel = reasoning === void 0 ? void 0 : effectiveEffort === void 0 ? t("effort.providerDefault") : reasoning.efforts.find((level) => level.id === effectiveEffort)?.name ?? effectiveEffort;
|
|
959
1177
|
const busy = state.status === "selecting";
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
items.push({
|
|
976
|
-
group: choice.group,
|
|
977
|
-
model: choice.model,
|
|
978
|
-
nameHit
|
|
979
|
-
});
|
|
980
|
-
}
|
|
981
|
-
return {
|
|
982
|
-
items,
|
|
983
|
-
total
|
|
984
|
-
};
|
|
985
|
-
}, [choices, normalized]);
|
|
1178
|
+
const chooseInputRef = react.useRef({
|
|
1179
|
+
busy,
|
|
1180
|
+
current: state.current,
|
|
1181
|
+
choices,
|
|
1182
|
+
open
|
|
1183
|
+
});
|
|
1184
|
+
chooseInputRef.current = {
|
|
1185
|
+
busy,
|
|
1186
|
+
current: state.current,
|
|
1187
|
+
choices,
|
|
1188
|
+
open
|
|
1189
|
+
};
|
|
1190
|
+
const normalized = dmsNormalizeQuery(query);
|
|
1191
|
+
const hits = react.useMemo(() => dmsSearchHits(choices, normalized, MAX_VISIBLE_HITS), [choices, normalized]);
|
|
1192
|
+
const visibleModelKeys = react.useMemo(() => state.groups.flatMap((g) => collapsed.has(g.id) ? [] : g.models.map((m) => dmsRowKey(g.id, m.id))), [state.groups, collapsed]);
|
|
986
1193
|
const defaultRowKey = react.useMemo(() => dmsDefaultRowKey({
|
|
987
|
-
hitKeys: hits === null ? null : hits.items.map((h) =>
|
|
988
|
-
modelKeys:
|
|
989
|
-
headerKeys: state.groups.map((g) =>
|
|
990
|
-
selectedKey: state.current === null ? null :
|
|
1194
|
+
hitKeys: hits === null ? null : hits.items.map((h) => dmsRowKey(h.group.id, h.model.id)),
|
|
1195
|
+
modelKeys: visibleModelKeys,
|
|
1196
|
+
headerKeys: state.groups.map((g) => dmsHeaderKey(g.id)),
|
|
1197
|
+
selectedKey: state.current === null ? null : dmsRowKey(state.current.provider, state.current.model)
|
|
991
1198
|
}), [
|
|
992
1199
|
hits,
|
|
1200
|
+
visibleModelKeys,
|
|
993
1201
|
state.groups,
|
|
994
|
-
collapsed,
|
|
995
1202
|
state.current
|
|
996
1203
|
]);
|
|
997
1204
|
react.useEffect(() => {
|
|
@@ -1028,7 +1235,9 @@ window.__ModuleLoader__.load({
|
|
|
1028
1235
|
setMenuAbove(dmsMenuAbove(rect.top, rect.bottom, window.innerHeight));
|
|
1029
1236
|
setBelowMaxHeight(dmsBelowMaxHeight(rect.bottom, window.innerHeight, 420));
|
|
1030
1237
|
const menuWidth = menuRef.current?.offsetWidth ?? 0;
|
|
1031
|
-
|
|
1238
|
+
const left = dmsMenuLeft(rect.right, menuWidth, window.innerWidth, 12);
|
|
1239
|
+
const rootLeft = rootRef.current?.getBoundingClientRect().left ?? 0;
|
|
1240
|
+
setMenuLeft(left === void 0 ? void 0 : left - rootLeft);
|
|
1032
1241
|
};
|
|
1033
1242
|
measure();
|
|
1034
1243
|
let raf = 0;
|
|
@@ -1111,42 +1320,47 @@ window.__ModuleLoader__.load({
|
|
|
1111
1320
|
state.groups.length,
|
|
1112
1321
|
reload
|
|
1113
1322
|
]);
|
|
1323
|
+
const focusTrigger = () => {
|
|
1324
|
+
const trigger = triggerRef.current;
|
|
1325
|
+
if (trigger !== null && !trigger.disabled) {
|
|
1326
|
+
trigger.focus();
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
rootRef.current?.focus();
|
|
1330
|
+
};
|
|
1114
1331
|
const close = react.useCallback((restoreFocus = false) => {
|
|
1115
1332
|
setOpen(false);
|
|
1116
1333
|
setNotice(null);
|
|
1117
1334
|
setQuery("");
|
|
1118
|
-
if (restoreFocus) queueMicrotask(
|
|
1119
|
-
triggerRef.current?.focus();
|
|
1120
|
-
});
|
|
1335
|
+
if (restoreFocus) queueMicrotask(focusTrigger);
|
|
1121
1336
|
}, []);
|
|
1122
1337
|
const dismissOnOutsidePointer = react.useCallback(() => {
|
|
1123
1338
|
close(true);
|
|
1124
1339
|
}, [close]);
|
|
1125
1340
|
(0, _deepseek_ai_dsh_client_ui_primitives.useDismissOnOutsidePointer)(rootRef, open, dismissOnOutsidePointer);
|
|
1126
1341
|
react.useEffect(() => {
|
|
1127
|
-
if (locked && open) close();
|
|
1342
|
+
if (locked && open) close(true);
|
|
1128
1343
|
}, [
|
|
1129
1344
|
locked,
|
|
1130
1345
|
open,
|
|
1131
1346
|
close
|
|
1132
1347
|
]);
|
|
1133
1348
|
const choose = react.useCallback((selection) => {
|
|
1134
|
-
|
|
1135
|
-
if (
|
|
1349
|
+
const { busy: isBusy, current, choices: catalog } = chooseInputRef.current;
|
|
1350
|
+
if (isBusy) return;
|
|
1351
|
+
if (current?.provider === selection.provider && current.model === selection.model) {
|
|
1136
1352
|
setNotice(t("notice.already"));
|
|
1137
1353
|
return;
|
|
1138
1354
|
}
|
|
1139
|
-
const target =
|
|
1140
|
-
const
|
|
1141
|
-
const effort = max === "off" ? void 0 : max;
|
|
1355
|
+
const target = catalog.find((c) => c.selection.provider === selection.provider && c.selection.model === selection.model);
|
|
1356
|
+
const plan = dmsChoosePlan(target?.model.reasoning);
|
|
1142
1357
|
const full = {
|
|
1143
1358
|
provider: selection.provider,
|
|
1144
1359
|
model: selection.model,
|
|
1145
|
-
...effort === void 0 ? {} : { reasoningEffort: effort }
|
|
1360
|
+
...plan.effort === void 0 ? {} : { reasoningEffort: plan.effort }
|
|
1146
1361
|
};
|
|
1147
1362
|
lastActionRef.current = "select";
|
|
1148
|
-
const
|
|
1149
|
-
const autoName = target?.model.reasoning?.efforts.find((level) => level.id === effort)?.name ?? effort ?? "";
|
|
1363
|
+
const autoName = plan.effort === void 0 ? "" : target?.model.reasoning?.efforts.find((level) => level.id === plan.effort)?.name ?? plan.effort;
|
|
1150
1364
|
(async () => {
|
|
1151
1365
|
try {
|
|
1152
1366
|
if (!await new Promise((resolve, reject) => {
|
|
@@ -1172,18 +1386,13 @@ window.__ModuleLoader__.load({
|
|
|
1172
1386
|
showToast(message !== null ? t("error.action", { message }) : t("notice.selectFailed"));
|
|
1173
1387
|
return;
|
|
1174
1388
|
}
|
|
1175
|
-
if (
|
|
1176
|
-
|
|
1177
|
-
if (autoRaised) showToast(t("toast.effortAuto", { effort: autoName }), false);
|
|
1178
|
-
}
|
|
1389
|
+
if (chooseInputRef.current.open) close(true);
|
|
1390
|
+
if (plan.autoRaised) showToast(t("toast.effortAuto", { effort: autoName }), false);
|
|
1179
1391
|
} catch {
|
|
1180
1392
|
showToast(t("notice.selectFailed"));
|
|
1181
1393
|
}
|
|
1182
1394
|
})();
|
|
1183
1395
|
}, [
|
|
1184
|
-
busy,
|
|
1185
|
-
state.current,
|
|
1186
|
-
choices,
|
|
1187
1396
|
select,
|
|
1188
1397
|
t,
|
|
1189
1398
|
directory,
|
|
@@ -1204,8 +1413,7 @@ window.__ModuleLoader__.load({
|
|
|
1204
1413
|
if (menu === null) return;
|
|
1205
1414
|
const items = [...menu.querySelectorAll("[data-row-key]")];
|
|
1206
1415
|
if (items.length === 0) return;
|
|
1207
|
-
|
|
1208
|
-
items[((active < 0 ? offset > 0 ? -1 : 0 : active) + offset + items.length) % items.length]?.focus();
|
|
1416
|
+
items[dmsNextRowIndex(items.findIndex((item) => item === document.activeElement), offset, items.length)]?.focus();
|
|
1209
1417
|
};
|
|
1210
1418
|
const focusEdge = (last) => {
|
|
1211
1419
|
const items = menuRef.current?.querySelectorAll("[data-row-key]");
|
|
@@ -1213,65 +1421,70 @@ window.__ModuleLoader__.load({
|
|
|
1213
1421
|
(last ? items[items.length - 1] : items[0])?.focus();
|
|
1214
1422
|
};
|
|
1215
1423
|
const onRootKeyDown = (event) => {
|
|
1216
|
-
if (event.nativeEvent.isComposing) return;
|
|
1217
1424
|
const target = event.target;
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1425
|
+
const action = dmsMenuKeyAction({
|
|
1426
|
+
key: event.key,
|
|
1427
|
+
composing: event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229,
|
|
1428
|
+
open,
|
|
1429
|
+
fromSearch: target instanceof HTMLInputElement && target === searchRef.current,
|
|
1430
|
+
inMenu: menuRef.current?.contains(document.activeElement) === true,
|
|
1431
|
+
onNonInput: !(target instanceof HTMLInputElement),
|
|
1432
|
+
hasQuery: query !== "",
|
|
1433
|
+
rowCount: menuRef.current?.querySelectorAll("[data-row-key]").length ?? 0,
|
|
1434
|
+
hitCount: hits?.items.length ?? 0
|
|
1435
|
+
});
|
|
1436
|
+
switch (action.type) {
|
|
1437
|
+
case "none": return;
|
|
1438
|
+
case "clearQuery":
|
|
1439
|
+
event.preventDefault();
|
|
1221
1440
|
setNotice(null);
|
|
1222
1441
|
setQuery("");
|
|
1223
|
-
if (
|
|
1442
|
+
if (action.focusSearch) searchRef.current?.focus();
|
|
1224
1443
|
return;
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
if (event.key === "Enter" && fromSearch) {
|
|
1243
|
-
if (hits !== null && hits.items.length > 0) {
|
|
1444
|
+
case "close":
|
|
1445
|
+
event.preventDefault();
|
|
1446
|
+
close(true);
|
|
1447
|
+
return;
|
|
1448
|
+
case "moveFocus":
|
|
1449
|
+
event.preventDefault();
|
|
1450
|
+
moveFocus(action.offset);
|
|
1451
|
+
return;
|
|
1452
|
+
case "focusEdge":
|
|
1453
|
+
event.preventDefault();
|
|
1454
|
+
focusEdge(action.last);
|
|
1455
|
+
return;
|
|
1456
|
+
case "chooseFirstHit": {
|
|
1457
|
+
const first = hits?.items[0];
|
|
1458
|
+
if (first === void 0) return;
|
|
1244
1459
|
event.preventDefault();
|
|
1245
|
-
const first = hits.items[0];
|
|
1246
1460
|
choose({
|
|
1247
1461
|
provider: first.group.id,
|
|
1248
1462
|
model: first.model.id
|
|
1249
1463
|
});
|
|
1464
|
+
return;
|
|
1250
1465
|
}
|
|
1251
1466
|
}
|
|
1252
1467
|
};
|
|
1253
1468
|
const onBlur = (event) => {
|
|
1254
|
-
if (busy) return;
|
|
1255
1469
|
const related = event.relatedTarget;
|
|
1470
|
+
if (related instanceof Node && rootRef.current?.contains(related) === true) return;
|
|
1256
1471
|
if (related instanceof Node) {
|
|
1257
|
-
if (rootRef.current?.contains(related)) return;
|
|
1258
1472
|
close();
|
|
1259
1473
|
return;
|
|
1260
1474
|
}
|
|
1261
|
-
close();
|
|
1475
|
+
if (!document.hasFocus()) close();
|
|
1262
1476
|
};
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
effort: effortLabel
|
|
1271
|
-
});
|
|
1477
|
+
const trigger = dmsTriggerCopy({
|
|
1478
|
+
current: state.current,
|
|
1479
|
+
modelName: currentChoice?.model.name,
|
|
1480
|
+
providerName: currentChoice?.group.name,
|
|
1481
|
+
effortLabel,
|
|
1482
|
+
waiting: state.current === null && state.status === "loading"
|
|
1483
|
+
}, t);
|
|
1272
1484
|
const renderErrorStrip = () => {
|
|
1273
1485
|
if (state.error !== null && lastActionRef.current === "load") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1274
1486
|
className: "dms-error",
|
|
1487
|
+
role: "alert",
|
|
1275
1488
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("error.action", { message: state.error }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1276
1489
|
type: "button",
|
|
1277
1490
|
className: "dms-retry",
|
|
@@ -1293,11 +1506,16 @@ window.__ModuleLoader__.load({
|
|
|
1293
1506
|
children: t("action.reload")
|
|
1294
1507
|
})]
|
|
1295
1508
|
}, failure.id));
|
|
1296
|
-
const
|
|
1297
|
-
|
|
1509
|
+
const emptyState = dmsMenuEmptyState({
|
|
1510
|
+
status: state.status,
|
|
1511
|
+
choiceCount: choices.length,
|
|
1512
|
+
searching: hits !== null,
|
|
1513
|
+
hitCount: hits?.total ?? 0
|
|
1514
|
+
});
|
|
1298
1515
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1299
1516
|
ref: rootRef,
|
|
1300
1517
|
className: "dms-root",
|
|
1518
|
+
tabIndex: -1,
|
|
1301
1519
|
onKeyDown: onRootKeyDown,
|
|
1302
1520
|
onBlur,
|
|
1303
1521
|
children: [
|
|
@@ -1305,21 +1523,21 @@ window.__ModuleLoader__.load({
|
|
|
1305
1523
|
ref: triggerRef,
|
|
1306
1524
|
type: "button",
|
|
1307
1525
|
className: "dms-trigger",
|
|
1308
|
-
"aria-label":
|
|
1526
|
+
"aria-label": trigger.aria,
|
|
1309
1527
|
"aria-haspopup": "menu",
|
|
1310
1528
|
"aria-expanded": open,
|
|
1311
1529
|
"aria-controls": open ? `${id}-menu` : void 0,
|
|
1312
|
-
title:
|
|
1530
|
+
title: trigger.title,
|
|
1313
1531
|
disabled: locked,
|
|
1314
1532
|
onClick: () => open ? close() : show(),
|
|
1315
1533
|
children: [
|
|
1316
1534
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1317
1535
|
className: "dms-triggerLabel",
|
|
1318
|
-
children: modelLabel
|
|
1536
|
+
children: trigger.modelLabel
|
|
1319
1537
|
}),
|
|
1320
|
-
providerLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1538
|
+
trigger.providerLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1321
1539
|
className: "dms-triggerProvider",
|
|
1322
|
-
children: providerLabel
|
|
1540
|
+
children: trigger.providerLabel
|
|
1323
1541
|
}),
|
|
1324
1542
|
effortLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1325
1543
|
className: "dms-triggerEffort",
|
|
@@ -1343,15 +1561,16 @@ window.__ModuleLoader__.load({
|
|
|
1343
1561
|
}
|
|
1344
1562
|
},
|
|
1345
1563
|
className: "dms-menu dms-menuModel" + (menuAbove ? "" : " dms-menuBelow"),
|
|
1346
|
-
"aria-busy": state.status === "loading" || busy,
|
|
1347
1564
|
children: [
|
|
1348
1565
|
state.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1349
1566
|
className: "dms-status",
|
|
1567
|
+
role: "status",
|
|
1350
1568
|
children: t("status.loading")
|
|
1351
1569
|
}),
|
|
1352
1570
|
renderErrorStrip(),
|
|
1353
1571
|
state.failures.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1354
1572
|
className: "dms-failures",
|
|
1573
|
+
role: "alert",
|
|
1355
1574
|
children: renderFailures(state.failures)
|
|
1356
1575
|
}),
|
|
1357
1576
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1363,7 +1582,10 @@ window.__ModuleLoader__.load({
|
|
|
1363
1582
|
value: query,
|
|
1364
1583
|
placeholder: t("search.placeholder"),
|
|
1365
1584
|
"aria-label": t("search.placeholder"),
|
|
1366
|
-
"
|
|
1585
|
+
role: "combobox",
|
|
1586
|
+
"aria-expanded": hits !== null,
|
|
1587
|
+
"aria-autocomplete": "list",
|
|
1588
|
+
"aria-controls": emptyState === null ? `${id}-groups` : void 0,
|
|
1367
1589
|
autoComplete: "off",
|
|
1368
1590
|
enterKeyHint: "search",
|
|
1369
1591
|
spellCheck: false,
|
|
@@ -1387,28 +1609,34 @@ window.__ModuleLoader__.load({
|
|
|
1387
1609
|
})
|
|
1388
1610
|
})]
|
|
1389
1611
|
}),
|
|
1390
|
-
|
|
1612
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1391
1613
|
className: "dms-sr",
|
|
1392
1614
|
role: "status",
|
|
1393
|
-
children: t("search.status", { count: String(hits.total) })
|
|
1615
|
+
children: hits !== null && hits.total > 0 ? t("search.status", { count: String(hits.total) }) : ""
|
|
1616
|
+
}),
|
|
1617
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1618
|
+
className: "dms-sr",
|
|
1619
|
+
role: "note",
|
|
1620
|
+
children: t("badge.reasoningHint")
|
|
1394
1621
|
}),
|
|
1395
|
-
noHits && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1622
|
+
emptyState === "noHits" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1396
1623
|
className: "dms-empty dms-groupsFill",
|
|
1397
1624
|
role: "status",
|
|
1398
1625
|
children: t("search.noMatch", { query: query.trim() })
|
|
1399
1626
|
}),
|
|
1400
|
-
noModels && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1627
|
+
emptyState === "noModels" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1401
1628
|
className: "dms-empty dms-groupsFill",
|
|
1402
1629
|
role: "status",
|
|
1403
1630
|
children: t("empty.models")
|
|
1404
1631
|
}),
|
|
1405
|
-
|
|
1632
|
+
emptyState === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1406
1633
|
className: "dms-groups",
|
|
1407
1634
|
id: `${id}-groups`,
|
|
1408
|
-
role: "menu",
|
|
1635
|
+
role: hits !== null ? "listbox" : "menu",
|
|
1636
|
+
"aria-busy": state.status === "loading" || busy,
|
|
1409
1637
|
"aria-label": t("menu.aria"),
|
|
1410
1638
|
children: hits !== null ? hits.items.map((hit) => {
|
|
1411
|
-
const rowKey =
|
|
1639
|
+
const rowKey = dmsRowKey(hit.group.id, hit.model.id);
|
|
1412
1640
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelOption, {
|
|
1413
1641
|
group: hit.group,
|
|
1414
1642
|
model: hit.model,
|
|
@@ -1417,6 +1645,7 @@ window.__ModuleLoader__.load({
|
|
|
1417
1645
|
busy,
|
|
1418
1646
|
rowKey,
|
|
1419
1647
|
active: rowKey === activeRowKey,
|
|
1648
|
+
searchMode: true,
|
|
1420
1649
|
onRowFocus,
|
|
1421
1650
|
nameHit: hit.nameHit,
|
|
1422
1651
|
t,
|
|
@@ -1424,6 +1653,7 @@ window.__ModuleLoader__.load({
|
|
|
1424
1653
|
}, rowKey);
|
|
1425
1654
|
}) : state.groups.map((group) => {
|
|
1426
1655
|
const headingId = `${id}-${group.id}`;
|
|
1656
|
+
const headerKey = dmsHeaderKey(group.id);
|
|
1427
1657
|
const isCollapsed = collapsed.has(group.id);
|
|
1428
1658
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1429
1659
|
"data-group-id": group.id,
|
|
@@ -1433,14 +1663,15 @@ window.__ModuleLoader__.load({
|
|
|
1433
1663
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1434
1664
|
type: "button",
|
|
1435
1665
|
role: "menuitem",
|
|
1436
|
-
"data-row-key":
|
|
1437
|
-
tabIndex: -1,
|
|
1666
|
+
"data-row-key": headerKey,
|
|
1667
|
+
tabIndex: activeRowKey === headerKey ? 0 : -1,
|
|
1438
1668
|
className: "dms-groupHeader",
|
|
1439
1669
|
"aria-expanded": !isCollapsed,
|
|
1440
1670
|
"aria-label": t("group.toggleAria", {
|
|
1441
1671
|
name: group.name,
|
|
1442
1672
|
count: String(group.models.length)
|
|
1443
1673
|
}),
|
|
1674
|
+
onFocus: () => onRowFocus(headerKey),
|
|
1444
1675
|
onClick: () => toggleCollapse(group.id),
|
|
1445
1676
|
children: [
|
|
1446
1677
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1459,7 +1690,7 @@ window.__ModuleLoader__.load({
|
|
|
1459
1690
|
})
|
|
1460
1691
|
]
|
|
1461
1692
|
}), !isCollapsed && group.models.map((model) => {
|
|
1462
|
-
const rowKey =
|
|
1693
|
+
const rowKey = dmsRowKey(group.id, model.id);
|
|
1463
1694
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelOption, {
|
|
1464
1695
|
group,
|
|
1465
1696
|
model,
|
|
@@ -1468,6 +1699,7 @@ window.__ModuleLoader__.load({
|
|
|
1468
1699
|
busy,
|
|
1469
1700
|
rowKey,
|
|
1470
1701
|
active: rowKey === activeRowKey,
|
|
1702
|
+
searchMode: false,
|
|
1471
1703
|
onRowFocus,
|
|
1472
1704
|
t,
|
|
1473
1705
|
onChoose: choose
|
|
@@ -1492,13 +1724,14 @@ window.__ModuleLoader__.load({
|
|
|
1492
1724
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EffortSlider, {
|
|
1493
1725
|
state,
|
|
1494
1726
|
select,
|
|
1495
|
-
t
|
|
1727
|
+
t,
|
|
1728
|
+
onSelectFailure: markSelectFailure
|
|
1496
1729
|
})]
|
|
1497
1730
|
}),
|
|
1498
|
-
|
|
1731
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1499
1732
|
className: "dms-notice",
|
|
1500
1733
|
role: "status",
|
|
1501
|
-
children: notice
|
|
1734
|
+
children: notice ?? ""
|
|
1502
1735
|
})
|
|
1503
1736
|
]
|
|
1504
1737
|
}) : null,
|
|
@@ -1506,9 +1739,7 @@ window.__ModuleLoader__.load({
|
|
|
1506
1739
|
text: toast.text,
|
|
1507
1740
|
icon: toast.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {}) : void 0,
|
|
1508
1741
|
anchor: rootRef.current?.closest("[data-composer-card]") ?? null,
|
|
1509
|
-
onDone:
|
|
1510
|
-
setToast(null);
|
|
1511
|
-
}
|
|
1742
|
+
onDone: dismissToast
|
|
1512
1743
|
}, toast.seq)
|
|
1513
1744
|
]
|
|
1514
1745
|
});
|
|
@@ -1522,12 +1753,18 @@ window.__ModuleLoader__.load({
|
|
|
1522
1753
|
* apply and removed again on unload. Class names are prefixed `dms-` so they
|
|
1523
1754
|
* cannot collide with CSS-module hashes from other plugins. Colors come only
|
|
1524
1755
|
* from `--dsw-*` theme tokens, matching the shipped Menu material.
|
|
1756
|
+
*
|
|
1757
|
+
* 显式 `: string` 注解:不加注解时 tsc 会把整份 CSS 推断成字面量类型并写进
|
|
1758
|
+
* lib/types/client/styles.d.ts(19KB 的 d.ts 只为声明一个常量),发布包里
|
|
1759
|
+
* 白白多出十几 KB 且每次改样式都全量重写。
|
|
1525
1760
|
*/
|
|
1526
1761
|
const CSS = `
|
|
1527
1762
|
.dms-root {
|
|
1528
1763
|
position: relative;
|
|
1529
1764
|
min-width: 0;
|
|
1530
1765
|
}
|
|
1766
|
+
/* root 只作为焦点落点(tabIndex=-1):不画焦点环,真正的可聚焦控件在它内部。 */
|
|
1767
|
+
.dms-root:focus { outline: none; }
|
|
1531
1768
|
|
|
1532
1769
|
.dms-trigger {
|
|
1533
1770
|
display: flex;
|
|
@@ -1789,6 +2026,8 @@ window.__ModuleLoader__.load({
|
|
|
1789
2026
|
font-size: 12px;
|
|
1790
2027
|
line-height: 18px;
|
|
1791
2028
|
}
|
|
2029
|
+
/* 常驻 live region(内容随状态更新):空时收起,不占位。 */
|
|
2030
|
+
.dms-notice:empty { display: none; }
|
|
1792
2031
|
.dms-effort {
|
|
1793
2032
|
display: flex;
|
|
1794
2033
|
align-items: center;
|
|
@@ -1959,6 +2198,8 @@ window.__ModuleLoader__.load({
|
|
|
1959
2198
|
}
|
|
1960
2199
|
.dms-effort.is-busy { opacity: .72; }
|
|
1961
2200
|
.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; }
|
|
2201
|
+
/* 常驻容器(内容随状态更新):空时收起,不占位。 */
|
|
2202
|
+
.dms-effort-error:empty { display: none; }
|
|
1962
2203
|
/* 视觉隐藏工具类(SR 播报/错误原文:不占布局但可被读屏读到)。 */
|
|
1963
2204
|
.dms-sr {
|
|
1964
2205
|
position: absolute;
|
|
@@ -1999,14 +2240,21 @@ window.__ModuleLoader__.load({
|
|
|
1999
2240
|
}
|
|
2000
2241
|
.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; }
|
|
2001
2242
|
.dms-model-check { color: var(--dsw-static-deepseek-500, #4d70ff); font-size: 15px; text-align: center; }
|
|
2002
|
-
/*
|
|
2003
|
-
|
|
2243
|
+
/* 方向键导航把焦点落在选项上。选中态是「背景 + 加粗 + 勾号」,悬停是「背景」,
|
|
2244
|
+
两者与焦点指示必须两两可辨 —— 只用同一个 hover 背景 token 会让「焦点落在
|
|
2245
|
+
选中行上」完全不可见(roving 的默认落点恰恰优先选中行)。 */
|
|
2246
|
+
.dms-model-option:focus-visible {
|
|
2247
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
2248
|
+
box-shadow: inset 0 0 0 2px var(--dsw-alias-border-l3);
|
|
2249
|
+
}
|
|
2004
2250
|
/* 当前选中行:仅靠勾号不足以在密集列表里定位。 */
|
|
2005
2251
|
.dms-model-optionSelected { background: var(--dsw-alias-interactive-bg-hover); }
|
|
2006
2252
|
.dms-model-optionSelected .dms-model-option-name { font-weight: 600; }
|
|
2007
2253
|
/* select 进行中:官方以 disabled 变灰表达,这里用 aria-busy 驱动同样的视觉、
|
|
2008
|
-
不夺键盘焦点(aria-disabled 保连续性,见 ModelOption)。
|
|
2009
|
-
|
|
2254
|
+
不夺键盘焦点(aria-disabled 保连续性,见 ModelOption)。aria-busy 挂在
|
|
2255
|
+
role=menu/listbox 容器上(.dms-groups)—— 挂在外层 .dms-menu 会罩住
|
|
2256
|
+
live region、压制其播报。 */
|
|
2257
|
+
.dms-groups[aria-busy="true"] .dms-model-option { color: var(--dsw-alias-label-dimmed); }
|
|
2010
2258
|
/* 搜索结果里的供应商标是区分跨供应商同名模型的唯一线索,之前无任何样式。 */
|
|
2011
2259
|
.dms-model-option-provider {
|
|
2012
2260
|
display: block;
|
|
@@ -2118,6 +2366,14 @@ body:not([data-ds-dark-theme]) .dms-effort.is-dragging .dms-effort-knob {
|
|
|
2118
2366
|
//#region src/client/index.ts
|
|
2119
2367
|
/** Dictionary namespace owned by this plugin. */
|
|
2120
2368
|
const NS = "modelSelector";
|
|
2369
|
+
/**
|
|
2370
|
+
* 座位优先级。ui-conversation 把 `conversation.input.model` 声明为 kind 'single'
|
|
2371
|
+
* (单槽按 priority 升序取最低者渲染),官方占用者
|
|
2372
|
+
* (@deepseek-ai/dsh-client-ui-model-selection)注册时不传 priority(即默认 0),
|
|
2373
|
+
* 故 -1 恒胜出。**改这个值等于静默换回官方 UI** —— test/plugin-contract.test.mjs
|
|
2374
|
+
* 直接断言本常量(而不是扫源码文本,那样一行注释就能骗过它),注册处也不许再写裸字面量。
|
|
2375
|
+
*/
|
|
2376
|
+
const SEAT_PRIORITY = -1;
|
|
2121
2377
|
/** Required services: the registry, session lookup, locale, the slot seat, and the model directory's Remote faces. */
|
|
2122
2378
|
const inject = [
|
|
2123
2379
|
"slots",
|
|
@@ -2175,10 +2431,11 @@ body:not([data-ds-dark-theme]) .dms-effort.is-dragging .dms-effort-knob {
|
|
|
2175
2431
|
});
|
|
2176
2432
|
}
|
|
2177
2433
|
//#endregion
|
|
2434
|
+
exports.SEAT_PRIORITY = SEAT_PRIORITY;
|
|
2178
2435
|
exports.apply = apply;
|
|
2179
2436
|
exports.inject = inject;
|
|
2180
2437
|
return module.exports;
|
|
2181
|
-
}
|
|
2438
|
+
/* __DMS_SRC_HASH__ = "eead5888f44285e2028692c6971ab7d6d8cfeb16272176f9c2c392186c440c04" */ }
|
|
2182
2439
|
});
|
|
2183
2440
|
|
|
2184
2441
|
//# sourceMappingURL=client.js.map
|