minecodex 2.0.22 → 2.0.24
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/features/model-slider/codex-feature.json +10 -2
- package/features/model-slider/src/frontend/install.mjs +118 -7
- package/features/session-status/src/domain/session-stats.mjs +3 -3
- package/features/session-status/src/session-status.mjs +2 -2
- package/package.json +1 -1
- package/packages/runtime-host/src/feature-registry.mjs +1 -1
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"zh-CN": "模型窗口"
|
|
8
8
|
},
|
|
9
9
|
"description": {
|
|
10
|
-
"en": "
|
|
11
|
-
"zh-CN": "
|
|
10
|
+
"en": "Search and switch models and thinking levels, and keep favorites close at hand.",
|
|
11
|
+
"zh-CN": "搜索与切换模型和思考强度,收藏常用模型。"
|
|
12
12
|
},
|
|
13
13
|
"entry": {
|
|
14
14
|
"kind": "composer-model-selector"
|
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
"openai": "OpenAI"
|
|
29
29
|
},
|
|
30
30
|
"strings": {
|
|
31
|
+
"searchPlaceholder": {
|
|
32
|
+
"en": "Search model",
|
|
33
|
+
"zh-CN": "搜索模型"
|
|
34
|
+
},
|
|
35
|
+
"noResults": {
|
|
36
|
+
"en": "No models found",
|
|
37
|
+
"zh-CN": "未找到模型"
|
|
38
|
+
},
|
|
31
39
|
"star": {
|
|
32
40
|
"en": "Star {model}",
|
|
33
41
|
"zh-CN": "收藏 {model}"
|
|
@@ -31,6 +31,18 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
31
31
|
z-index: 10;
|
|
32
32
|
padding: 6px;
|
|
33
33
|
}
|
|
34
|
+
[data-codex-model-slider-search-header] {
|
|
35
|
+
position: sticky; top: 0; z-index: 1; flex-shrink: 0; padding: 2px 8px 8px;
|
|
36
|
+
background: var(--mcx-color-surface-elevated-secondary);
|
|
37
|
+
}
|
|
38
|
+
[data-codex-model-slider-search] {
|
|
39
|
+
width: 100%; height: 28px; border: 0; outline: none; padding: 0;
|
|
40
|
+
background: transparent; color: var(--mcx-color-text-primary);
|
|
41
|
+
font: inherit; font-size: var(--mcx-text-sm, 13px);
|
|
42
|
+
}
|
|
43
|
+
[data-codex-model-slider-search]::placeholder { color: var(--mcx-color-text-tertiary); }
|
|
44
|
+
[data-codex-model-slider-empty] { padding: 12px 8px; color: var(--mcx-color-text-secondary); font-size: 13px; }
|
|
45
|
+
[data-codex-model-slider-item][data-codex-model-slider-keyboard] { background: var(--mcx-color-background-primary-ghost-hover); }
|
|
34
46
|
[data-codex-model-slider-menu][data-codex-model-slider-overflow] {
|
|
35
47
|
max-height: var(--codex-model-slider-menu-max-height) !important;
|
|
36
48
|
overflow-y: auto;
|
|
@@ -647,7 +659,12 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
647
659
|
const items = modelMenuItems(menu).filter((item) => item.dataset.codexModelSliderRaw);
|
|
648
660
|
if (!items.length) return;
|
|
649
661
|
const favorites = loadModelFavorites(selector);
|
|
650
|
-
|
|
662
|
+
const query = menu.querySelector('[data-codex-model-slider-search]')?.value.trim().toLocaleLowerCase() ?? "";
|
|
663
|
+
const terms = query.split(/\s+/).filter(Boolean);
|
|
664
|
+
items.forEach((item) => {
|
|
665
|
+
const text = `${item.dataset.codexModelSliderRaw} ${item.querySelector('[data-codex-model-slider-copy]')?.textContent ?? ""}`.toLocaleLowerCase();
|
|
666
|
+
item.style.display = terms.every((term) => text.includes(term)) ? "" : "none";
|
|
667
|
+
});
|
|
651
668
|
if (!menu.hasAttribute("data-codex-model-slider-controller-menu")) {
|
|
652
669
|
if (!modelSelectorMenuSizeFrames.has(menu)) {
|
|
653
670
|
const frame = requestAnimationFrame(() => {
|
|
@@ -658,8 +675,9 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
658
675
|
}
|
|
659
676
|
return;
|
|
660
677
|
}
|
|
661
|
-
const
|
|
662
|
-
const
|
|
678
|
+
const visible = items.filter((item) => item.style.display !== "none");
|
|
679
|
+
const starred = visible.filter((item) => favorites.has(item.dataset.codexModelSliderRaw));
|
|
680
|
+
const ordinary = visible.filter((item) => !favorites.has(item.dataset.codexModelSliderRaw));
|
|
663
681
|
const desired = items.slice().sort((a, b) => compareEnhancedModelItems(a, b, favorites));
|
|
664
682
|
const parent = items[0].parentElement;
|
|
665
683
|
if (!parent || !desired.every((item) => item.parentElement === parent)) return;
|
|
@@ -673,13 +691,15 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
673
691
|
divider.setAttribute("data-codex-model-slider-divider", "");
|
|
674
692
|
divider.setAttribute("role", "separator");
|
|
675
693
|
}
|
|
676
|
-
const firstOrdinary = desired.find((item) => !favorites.has(item.dataset.codexModelSliderRaw));
|
|
694
|
+
const firstOrdinary = desired.find((item) => item.style.display !== "none" && !favorites.has(item.dataset.codexModelSliderRaw));
|
|
677
695
|
if (firstOrdinary && divider.nextElementSibling !== firstOrdinary) {
|
|
678
696
|
parent.insertBefore(divider, firstOrdinary);
|
|
679
697
|
}
|
|
680
698
|
} else {
|
|
681
699
|
divider?.remove();
|
|
682
700
|
}
|
|
701
|
+
const empty = menu.querySelector('[data-codex-model-slider-empty]');
|
|
702
|
+
if (empty) empty.hidden = visible.length > 0;
|
|
683
703
|
|
|
684
704
|
if (!modelSelectorMenuSizeFrames.has(menu)) {
|
|
685
705
|
const frame = requestAnimationFrame(() => {
|
|
@@ -693,7 +713,20 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
693
713
|
function sizeModelMenuViewport(menu, maxVisibleItems) {
|
|
694
714
|
if (!menu.isConnected) return;
|
|
695
715
|
if (modelSelectorControllerMenu?.menu === menu && modelSelectorControllerMenu.animations?.length) return;
|
|
696
|
-
const items = modelMenuItems(menu).filter((item) => item.dataset.codexModelSliderRaw);
|
|
716
|
+
const items = modelMenuItems(menu).filter((item) => item.dataset.codexModelSliderRaw && item.style.display !== "none");
|
|
717
|
+
if (menu.hasAttribute("data-codex-model-slider-controller-menu")) {
|
|
718
|
+
const content = menu.querySelector('[data-codex-model-slider-results]');
|
|
719
|
+
const header = menu.querySelector('[data-codex-model-slider-search-header]');
|
|
720
|
+
const style = getComputedStyle(menu);
|
|
721
|
+
const chrome = [style.paddingTop, style.paddingBottom, style.borderTopWidth, style.borderBottomWidth]
|
|
722
|
+
.reduce((sum, value) => sum + (Number.parseFloat(value) || 0), 0);
|
|
723
|
+
const last = items[Math.max(1, maxVisibleItems) - 1];
|
|
724
|
+
const resultsHeight = last ? last.offsetTop + last.offsetHeight - content.offsetTop : content.offsetHeight;
|
|
725
|
+
const available = modelSelectorControllerMenu.parentMenu.getBoundingClientRect().bottom - 8;
|
|
726
|
+
menu.style.height = `${Math.ceil(Math.min(chrome + header.offsetHeight + resultsHeight, available))}px`;
|
|
727
|
+
menu.style.overflowY = "auto";
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
697
730
|
if (items.length <= maxVisibleItems) {
|
|
698
731
|
menu.removeAttribute("data-codex-model-slider-overflow");
|
|
699
732
|
menu.style.removeProperty("--codex-model-slider-menu-max-height");
|
|
@@ -1055,7 +1088,20 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
1055
1088
|
featureUI.surface(menu, "dropdown", { variant: "opaque" });
|
|
1056
1089
|
// 切换同一个 picker 视图,不重复播放整个 Dropdown 的入场缩放。
|
|
1057
1090
|
menu.style.animation = "none";
|
|
1091
|
+
menu.style.boxSizing = "border-box";
|
|
1092
|
+
const header = document.createElement("div");
|
|
1093
|
+
header.setAttribute("data-codex-model-slider-search-header", "");
|
|
1094
|
+
const search = document.createElement("input");
|
|
1095
|
+
search.type = "text";
|
|
1096
|
+
search.setAttribute("data-codex-model-slider-search", "");
|
|
1097
|
+
search.placeholder = selector.strings.searchPlaceholder;
|
|
1098
|
+
search.setAttribute("aria-label", search.placeholder);
|
|
1099
|
+
search.autocomplete = "off";
|
|
1100
|
+
search.spellcheck = false;
|
|
1101
|
+
header.append(search);
|
|
1102
|
+
menu.append(header);
|
|
1058
1103
|
const content = document.createElement("div");
|
|
1104
|
+
content.setAttribute("data-codex-model-slider-results", "");
|
|
1059
1105
|
content.style.flexShrink = "0";
|
|
1060
1106
|
menu.append(content);
|
|
1061
1107
|
models.forEach((model, index) => {
|
|
@@ -1063,6 +1109,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
1063
1109
|
item.className = "no-drag outline-hidden rounded-lg px-[var(--padding-row-x)] py-[var(--padding-row-y)] text-sm text-default group hover:bg-primary-ghost-hover focus:bg-primary-ghost-hover cursor-interaction flex flex-col";
|
|
1064
1110
|
item.setAttribute("role", "menuitem");
|
|
1065
1111
|
item.setAttribute("tabindex", "-1");
|
|
1112
|
+
item.id = `mcx-model-result-${index}`;
|
|
1066
1113
|
enhanceModelItem(item, menu, selector, index, {
|
|
1067
1114
|
rawValue: String(model.model),
|
|
1068
1115
|
selected: String(model.model) === String(controller.model),
|
|
@@ -1071,6 +1118,71 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
1071
1118
|
});
|
|
1072
1119
|
content.append(item);
|
|
1073
1120
|
});
|
|
1121
|
+
const empty = document.createElement("div");
|
|
1122
|
+
empty.setAttribute("data-codex-model-slider-empty", "");
|
|
1123
|
+
empty.setAttribute("role", "status");
|
|
1124
|
+
empty.textContent = selector.strings.noResults;
|
|
1125
|
+
empty.hidden = true;
|
|
1126
|
+
content.append(empty);
|
|
1127
|
+
let keyboardItem = null;
|
|
1128
|
+
const resetKeyboard = () => {
|
|
1129
|
+
keyboardItem?.removeAttribute("data-codex-model-slider-keyboard");
|
|
1130
|
+
keyboardItem = null;
|
|
1131
|
+
search.removeAttribute("aria-activedescendant");
|
|
1132
|
+
};
|
|
1133
|
+
search.addEventListener("input", () => {
|
|
1134
|
+
resetKeyboard();
|
|
1135
|
+
const record = modelSelectorControllerMenu;
|
|
1136
|
+
if (!record || record.menu !== menu || record.closing) return;
|
|
1137
|
+
const previousHeight = menu.getBoundingClientRect().height;
|
|
1138
|
+
clearTimeout(record.motionTimer);
|
|
1139
|
+
record.animations?.forEach((animation) => animation.cancel());
|
|
1140
|
+
record.animations = [];
|
|
1141
|
+
menu.scrollTop = 0;
|
|
1142
|
+
renderEnhancedModelMenu(menu, selector);
|
|
1143
|
+
sizeModelMenuViewport(menu, selector.maxVisibleItems);
|
|
1144
|
+
record.listHeight = menu.getBoundingClientRect().height;
|
|
1145
|
+
if (matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
1146
|
+
const motion = menu.animate([{ height: `${previousHeight}px` }, { height: `${record.listHeight}px` }],
|
|
1147
|
+
{ duration: 320, easing: "cubic-bezier(.19,1,.22,1)" });
|
|
1148
|
+
record.animations.push(motion);
|
|
1149
|
+
record.motionTimer = setTimeout(() => {
|
|
1150
|
+
if (modelSelectorControllerMenu !== record || record.closing) return;
|
|
1151
|
+
motion.cancel();
|
|
1152
|
+
record.animations = [];
|
|
1153
|
+
}, 320);
|
|
1154
|
+
});
|
|
1155
|
+
// 原生菜单的 typeahead 不应吞掉搜索输入;鼠标操作保留输入焦点。
|
|
1156
|
+
menu.addEventListener("pointerdown", (event) => {
|
|
1157
|
+
if (event.target.closest?.('[data-codex-model-slider-item]')) event.preventDefault();
|
|
1158
|
+
});
|
|
1159
|
+
search.addEventListener("keydown", (event) => {
|
|
1160
|
+
event.stopPropagation();
|
|
1161
|
+
if (event.isComposing) return;
|
|
1162
|
+
if (event.key === "Escape") {
|
|
1163
|
+
event.preventDefault();
|
|
1164
|
+
closeControllerModelMenu();
|
|
1165
|
+
modelButton.focus();
|
|
1166
|
+
} else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
1167
|
+
event.preventDefault();
|
|
1168
|
+
const visible = modelMenuItems(menu).filter((item) => item.style.display !== "none");
|
|
1169
|
+
if (!visible.length) return;
|
|
1170
|
+
const current = visible.indexOf(keyboardItem);
|
|
1171
|
+
const index = current < 0 ? (event.key === "ArrowDown" ? 0 : visible.length - 1)
|
|
1172
|
+
: (current + (event.key === "ArrowDown" ? 1 : -1) + visible.length) % visible.length;
|
|
1173
|
+
resetKeyboard();
|
|
1174
|
+
keyboardItem = visible[index];
|
|
1175
|
+
keyboardItem.setAttribute("data-codex-model-slider-keyboard", "");
|
|
1176
|
+
search.setAttribute("aria-activedescendant", keyboardItem.id);
|
|
1177
|
+
const top = keyboardItem.offsetTop - header.offsetHeight;
|
|
1178
|
+
const bottom = keyboardItem.offsetTop + keyboardItem.offsetHeight - menu.clientHeight;
|
|
1179
|
+
if (top < menu.scrollTop) menu.scrollTop = top;
|
|
1180
|
+
else if (bottom > menu.scrollTop) menu.scrollTop = bottom;
|
|
1181
|
+
} else if (event.key === "Enter") {
|
|
1182
|
+
event.preventDefault();
|
|
1183
|
+
(keyboardItem ?? modelMenuItems(menu).find((item) => item.style.display !== "none"))?.click();
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1074
1186
|
menu.addEventListener("click", (event) => {
|
|
1075
1187
|
const star = event.target.closest?.("[data-codex-model-slider-star]");
|
|
1076
1188
|
const item = event.target.closest?.("[data-codex-model-slider-item]");
|
|
@@ -1132,8 +1244,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
1132
1244
|
parentMenu.append(menu);
|
|
1133
1245
|
sizeModelMenuViewport(menu, selector.maxVisibleItems);
|
|
1134
1246
|
modelButton.setAttribute("aria-expanded", "true");
|
|
1135
|
-
|
|
1136
|
-
?? menu.querySelector('[role="menuitem"]'))?.focus();
|
|
1247
|
+
search.focus({ preventScroll: true });
|
|
1137
1248
|
const record = modelSelectorControllerMenu;
|
|
1138
1249
|
record.listHeight = menu.getBoundingClientRect().height;
|
|
1139
1250
|
animateControllerModelMenu(record, true, () => {
|
|
@@ -109,15 +109,15 @@ export function sessionStatusSnapshot(state, threadId) {
|
|
|
109
109
|
const turnSpeeds = new Map();
|
|
110
110
|
for (const sample of state.speedMeasurements) {
|
|
111
111
|
const elapsed = sample.end - sample.start - boundedToolWaitMs(sample.tools, sample.start, sample.end, sample.outputs);
|
|
112
|
-
if (!sample.turnId || elapsed
|
|
112
|
+
if (!sample.turnId || !Number.isFinite(elapsed) || elapsed <= 0) continue;
|
|
113
113
|
const turn = turnSpeeds.get(sample.turnId) ?? { tokens:0, ms:0, samples:0 };
|
|
114
114
|
turn.tokens += sample.output;
|
|
115
115
|
turn.ms += elapsed;
|
|
116
116
|
turn.samples += 1;
|
|
117
117
|
turnSpeeds.set(sample.turnId, turn);
|
|
118
118
|
}
|
|
119
|
-
//
|
|
120
|
-
const recentTurns = [...state.turns].filter(id => turnSpeeds.has(id)).slice(-
|
|
119
|
+
// 按回合顺序取最近五个有效回合;同轮多步合并,无数据的回合不占名额。
|
|
120
|
+
const recentTurns = [...state.turns].filter(id => turnSpeeds.has(id)).slice(-5);
|
|
121
121
|
for (const id of recentTurns) {
|
|
122
122
|
const turn = turnSpeeds.get(id);
|
|
123
123
|
decodeTokens += turn.tokens;
|
|
@@ -7,14 +7,14 @@ const copyByLocale = { "zh-CN": {
|
|
|
7
7
|
firstToken: "平均首字延迟", input: "输入", cached: "缓存输入", output: "输出",
|
|
8
8
|
sum: "合计", capacity: "窗口容量", contextShare: "上下文占用", compactions: "压缩次数",
|
|
9
9
|
instructions: "指令", messages: "消息", toolHistory: "工具交互", other: "未分类",
|
|
10
|
-
speedHelp: "关于 TPS", speedInfo: "GPT 等模型不公开完整思考过程及逐 Token 生成时间。TPS 取最近
|
|
10
|
+
speedHelp: "关于 TPS", speedInfo: "GPT 等模型不公开完整思考过程及逐 Token 生成时间。TPS 取最近 5 个有有效数据的回合,不足 5 轮时按已有数据计算。它是基于可观测事件估算的体感响应速度,仅供参考,不代表模型真实的 Token 生成速度。",
|
|
11
11
|
}, en: {
|
|
12
12
|
turns: "turns", steps: "steps", speed: "TPS", cache: "Cache", cacheDetail: "Cache hit",
|
|
13
13
|
statistics: "Session stats", usage: "Token usage", context: "Context", total: "Total",
|
|
14
14
|
firstToken: "Avg first token", input: "Input", cached: "Cached input", output: "Output",
|
|
15
15
|
sum: "Sum", capacity: "Window size", contextShare: "Context used", compactions: "Compactions",
|
|
16
16
|
instructions: "Instructions", messages: "Messages", toolHistory: "Tool activity", other: "Unclassified",
|
|
17
|
-
speedHelp: "About TPS", speedInfo: "Models such as GPT do not expose their full reasoning process or per-token generation timing. TPS uses the latest
|
|
17
|
+
speedHelp: "About TPS", speedInfo: "Models such as GPT do not expose their full reasoning process or per-token generation timing. TPS uses the latest 5 turns with valid timing data, or fewer if unavailable. It estimates perceived response speed from observable events, rather than the model’s actual token generation speed.",
|
|
18
18
|
} };
|
|
19
19
|
function selectedCopy() {
|
|
20
20
|
const language = document.documentElement.lang || navigator.languages?.[0] || navigator.language || "en";
|
package/package.json
CHANGED
|
@@ -152,7 +152,7 @@ async function normalizeModelSelector(modelSelector, projectDir) {
|
|
|
152
152
|
throw new Error("modelSelector.icons requires fallback, star, and unstar");
|
|
153
153
|
}
|
|
154
154
|
const strings = {};
|
|
155
|
-
for (const key of ["star", "unstar"]) {
|
|
155
|
+
for (const key of new Set(["star", "unstar", ...Object.keys(modelSelector.strings ?? {})])) {
|
|
156
156
|
const localized = normalizeLocalizedText(modelSelector.strings?.[key], `modelSelector.strings.${key}`);
|
|
157
157
|
strings[key] = { value: localized.value, locales: localized.locales };
|
|
158
158
|
}
|