minecodex 1.2.1 → 2.0.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 +9 -3
- package/features/images/README.md +16 -98
- package/features/images/codex-feature.json +6 -14
- package/features/images/src/http-server.mjs +15 -5
- package/features/images/src/image-library.mjs +37 -9
- package/features/images/src/library-prompt-page.mjs +239 -0
- package/features/images/src/prompt-index.mjs +27 -24
- package/features/images/src/prompt-source.mjs +100 -0
- package/features/images/web/app.js +180 -67
- package/features/images/web/i18n.mjs +5 -0
- package/features/images/web/index.html +29 -24
- package/features/images/web/styles.css +169 -139
- package/features/model-slider/README.md +13 -1
- package/features/model-slider/THIRD_PARTY_NOTICES.md +1 -1
- package/features/notes/DESIGN.md +66 -0
- package/features/notes/README.md +18 -99
- package/features/notes/assets/icons/book-open-nav.svg +15 -0
- package/features/notes/assets/icons/book-open.svg +16 -0
- package/features/notes/assets/native-emojis.json +1 -0
- package/features/notes/assets/native-prompt-icons.json +28 -0
- package/features/notes/codex-feature.json +12 -70
- package/features/notes/src/main.mjs +2 -2
- package/features/notes/src/product.mjs +287 -613
- package/features/notes/src/prompt-schema.mjs +117 -0
- package/features/notes/src/quick-prompts-page.mjs +1559 -0
- package/features/notes/src/quick-prompts-server.mjs +167 -0
- package/features/session-status/DESIGN.md +10 -2
- package/features/session-status/README.md +13 -3
- package/features/session-status/src/session-status.mjs +329 -24
- package/features/sidebar-focus/DESIGN.md +16 -0
- package/features/sidebar-focus/codex-feature.json +20 -0
- package/features/sidebar-focus/src/sidebar-focus.mjs +134 -0
- package/features/sidebar-focus/vendor/lucide-LICENSE +43 -0
- package/features/unread-inbox/DESIGN.md +28 -0
- package/features/unread-inbox/codex-feature.json +20 -0
- package/features/unread-inbox/src/unread-inbox.mjs +360 -0
- package/features/usage-meter/DESIGN.md +49 -0
- package/features/usage-meter/README.md +18 -0
- package/features/usage-meter/codex-feature.json +13 -0
- package/features/usage-meter/src/usage-meter.mjs +200 -0
- package/package.json +21 -6
- package/packages/cli/src/config.mjs +4 -1
- package/packages/cli/src/control-server.mjs +2 -2
- package/packages/runtime-host/src/cloud-images.mjs +123 -0
- package/packages/runtime-host/src/codex-cdp.mjs +42 -8
- package/packages/runtime-host/src/codex-design-contract.mjs +10 -0
- package/packages/runtime-host/src/codex-injection.mjs +364 -116
- package/packages/runtime-host/src/codex-runtime.mjs +105 -28
- package/packages/runtime-host/src/feature-registry.mjs +3 -0
- package/packages/runtime-host/src/session-status-reader.mjs +65 -24
- package/features/notes/assets/icons/circle.svg +0 -15
- package/features/notes/assets/icons/ellipsis.svg +0 -17
- package/features/notes/assets/icons/external-link.svg +0 -17
- package/features/notes/assets/icons/list-todo.svg +0 -19
- package/features/notes/assets/icons/message-square-quote.svg +0 -17
- package/features/notes/assets/icons/paperclip.svg +0 -15
- package/features/notes/assets/icons/star-off.svg +0 -17
- package/features/notes/assets/icons/star.svg +0 -15
- package/features/notes/assets/icons/sticky-note.svg +0 -16
- package/features/notes/assets/icons/trash-2.svg +0 -19
- package/features/notes/src/http-server.mjs +0 -580
- package/features/notes/web/app.js +0 -1202
- package/features/notes/web/codex-artifact-document.svg +0 -1
- package/features/notes/web/codex-attachment.svg +0 -1
- package/features/notes/web/codex-build.svg +0 -1
- package/features/notes/web/codex-code.svg +0 -1
- package/features/notes/web/codex-cplusplus.svg +0 -1
- package/features/notes/web/codex-css.svg +0 -1
- package/features/notes/web/codex-document.svg +0 -1
- package/features/notes/web/codex-file.svg +0 -1
- package/features/notes/web/codex-folder.svg +0 -1
- package/features/notes/web/codex-hashes.svg +0 -1
- package/features/notes/web/codex-html.svg +0 -1
- package/features/notes/web/codex-image.svg +0 -1
- package/features/notes/web/codex-java.svg +0 -1
- package/features/notes/web/codex-javascript.svg +0 -1
- package/features/notes/web/codex-json.svg +0 -1
- package/features/notes/web/codex-notebook.svg +0 -1
- package/features/notes/web/codex-pdf.svg +0 -1
- package/features/notes/web/codex-pencil.svg +0 -3
- package/features/notes/web/codex-php.svg +0 -1
- package/features/notes/web/codex-plus.svg +0 -3
- package/features/notes/web/codex-presentation.svg +0 -1
- package/features/notes/web/codex-python.svg +0 -1
- package/features/notes/web/codex-react.svg +0 -1
- package/features/notes/web/codex-rust.svg +0 -1
- package/features/notes/web/codex-shell.svg +0 -1
- package/features/notes/web/codex-skill.svg +0 -1
- package/features/notes/web/codex-spreadsheet.svg +0 -1
- package/features/notes/web/codex-task.svg +0 -3
- package/features/notes/web/codex-terminal.svg +0 -1
- package/features/notes/web/codex-toml.svg +0 -1
- package/features/notes/web/codex-typescript.svg +0 -1
- package/features/notes/web/codex-view-all.svg +0 -3
- package/features/notes/web/file-icons.mjs +0 -68
- package/features/notes/web/i18n.mjs +0 -237
- package/features/notes/web/index.html +0 -15
- package/features/notes/web/styles.css +0 -1155
- package/features/notes/web/todo-drag.mjs +0 -18
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// CodexRuntime 与 CDP 客户端在 codex-runtime.mjs / codex-cdp.mjs。
|
|
4
4
|
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
|
-
import { CODEX_THEME_TOKENS } from "./codex-design-contract.mjs";
|
|
6
|
+
import { CODEX_THEME_TOKENS, CODEX_THEME_TOKEN_ALIASES } from "./codex-design-contract.mjs";
|
|
7
7
|
|
|
8
8
|
export const RUNTIME_VERSION = 101;
|
|
9
9
|
export const HOST_BINDING_NAME = "__codexPersonalHostAction";
|
|
@@ -158,6 +158,7 @@ export function createInjectionSource(features, {
|
|
|
158
158
|
temporaryChat: ["temporary chat", "临时聊天", "临时对话"],
|
|
159
159
|
};
|
|
160
160
|
const themeTokenNames = config.themeTokens;
|
|
161
|
+
const themeTokenAliases = config.themeTokenAliases;
|
|
161
162
|
|
|
162
163
|
function normalizedLocale(value) {
|
|
163
164
|
return String(value ?? "").toLowerCase().startsWith("zh") ? "zh-CN" : "en";
|
|
@@ -251,10 +252,6 @@ export function createInjectionSource(features, {
|
|
|
251
252
|
let modelSelectorObservedTrigger = null;
|
|
252
253
|
let modelSelectorControllerMenu = null;
|
|
253
254
|
let modelSelectorNativeFastIcons = null;
|
|
254
|
-
// Fast 图标来源标记:bundle 静态提取 vs DOM 克隆,DOM 克隆优先以保持像素一致。
|
|
255
|
-
let modelSelectorNativeFastIconSources = null;
|
|
256
|
-
// DOM 克隆可见性门控:仅在 Fast 控件从无到有时扫描一次,常驻会话不逐帧扫描。
|
|
257
|
-
let modelSelectorFastControlVisible = false;
|
|
258
255
|
// 持久化脏标记:仅在图标实际变化时写一次 localStorage。
|
|
259
256
|
let modelSelectorNativeFastIconsDirty = false;
|
|
260
257
|
const modelSelectorFastIconStorageKey = "codex-model-slider:fast-icons:v1";
|
|
@@ -344,10 +341,12 @@ export function createInjectionSource(features, {
|
|
|
344
341
|
}
|
|
345
342
|
[data-codex-model-slider-generic] { box-sizing: border-box; width: 100%; }
|
|
346
343
|
[data-codex-model-slider-fast] { border: 0; background: transparent; cursor: pointer; }
|
|
344
|
+
[data-codex-model-slider-fast]:not(:hover) [data-codex-model-slider-fast-content] { background: transparent; }
|
|
347
345
|
[data-codex-model-slider-fast]:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: 1px; }
|
|
348
346
|
[data-codex-model-slider-rail][data-codex-model-slider-fallback] { position: relative; height: 38px; margin: 1px 8px 0; outline: none; cursor: pointer; }
|
|
349
347
|
[data-codex-model-slider-track] { position: absolute; top: 12px; right: 0; left: 0; height: 16px; border-radius: 999px; background: var(--color-background-control); }
|
|
350
348
|
[data-codex-model-slider-range] { position: absolute; top: 0; bottom: 0; left: 0; width: var(--codex-model-slider-progress); border-radius: inherit; background: var(--color-text-accent); }
|
|
349
|
+
[data-codex-model-slider-rail][data-max="true"] [data-codex-model-slider-range] { background: var(--color-chart-purple); }
|
|
351
350
|
[data-codex-model-slider-fallback] [data-codex-model-slider-tick] {
|
|
352
351
|
position: absolute; top: 16px; width: 6px; height: 6px; transform: translate(-50%, -50%); border: 0; border-radius: 50%;
|
|
353
352
|
padding: 0; background: var(--color-text-foreground-tertiary); pointer-events: none;
|
|
@@ -371,10 +370,6 @@ export function createInjectionSource(features, {
|
|
|
371
370
|
const persistedIcons = loadPersistedNativeFastIcons(modelSelectorFastIconStorageKey);
|
|
372
371
|
if (persistedIcons) {
|
|
373
372
|
modelSelectorNativeFastIcons = persistedIcons;
|
|
374
|
-
modelSelectorNativeFastIconSources = {};
|
|
375
|
-
for (const state of Object.keys(persistedIcons)) {
|
|
376
|
-
modelSelectorNativeFastIconSources[state] = "persisted";
|
|
377
|
-
}
|
|
378
373
|
}
|
|
379
374
|
void loadNativeFastIconFromBundle();
|
|
380
375
|
}
|
|
@@ -395,12 +390,14 @@ export function createInjectionSource(features, {
|
|
|
395
390
|
"config",
|
|
396
391
|
`"use strict";\n${source}`,
|
|
397
392
|
);
|
|
393
|
+
const pageConfig = feature.pageScript.config ??= {};
|
|
394
|
+
pageConfig.requestHostAction = (action, payload) => requestPageHostAction(feature.id, action, payload);
|
|
398
395
|
const cleanup = installer(
|
|
399
396
|
lifetime.signal,
|
|
400
397
|
window,
|
|
401
398
|
document,
|
|
402
399
|
MutationObserver,
|
|
403
|
-
|
|
400
|
+
pageConfig,
|
|
404
401
|
);
|
|
405
402
|
if (typeof cleanup === "function") pageScriptCleanups.push(cleanup);
|
|
406
403
|
} catch (error) {
|
|
@@ -789,7 +786,6 @@ export function createInjectionSource(features, {
|
|
|
789
786
|
const source = await fetch(sourceLink.href).then((response) => response.text());
|
|
790
787
|
if (!modelSelectorNativeFastIcons) {
|
|
791
788
|
modelSelectorNativeFastIcons = {};
|
|
792
|
-
modelSelectorNativeFastIconSources = {};
|
|
793
789
|
}
|
|
794
790
|
let gained = false;
|
|
795
791
|
const define = (state, prefix, definition) => {
|
|
@@ -806,7 +802,6 @@ export function createInjectionSource(features, {
|
|
|
806
802
|
svg.setAttribute("width", "20");
|
|
807
803
|
svg.setAttribute("height", "20");
|
|
808
804
|
modelSelectorNativeFastIcons[state] = svg;
|
|
809
|
-
modelSelectorNativeFastIconSources[state] = "bundle";
|
|
810
805
|
gained = true;
|
|
811
806
|
};
|
|
812
807
|
define("active", "M11\\.9125 21\\.4125", { viewBox: "0 0 24 24" });
|
|
@@ -822,50 +817,24 @@ export function createInjectionSource(features, {
|
|
|
822
817
|
}
|
|
823
818
|
}
|
|
824
819
|
|
|
825
|
-
function hasVisibleFastControl() {
|
|
826
|
-
return Boolean(
|
|
827
|
-
document.querySelector("[data-codex-model-slider-fast]")
|
|
828
|
-
|| document.querySelector('[class*="_FastModeToggle_"]')
|
|
829
|
-
|| document.querySelector('[class*="_ModelPickerTriggerFastIndicator_"] svg'),
|
|
830
|
-
);
|
|
831
|
-
}
|
|
832
|
-
|
|
833
820
|
function captureNativeFastIcon() {
|
|
834
|
-
//
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
const
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
template.removeAttribute("id");
|
|
852
|
-
// 统一呈现尺寸:保留原生 viewBox,固定 20px。DOM 克隆来源是
|
|
853
|
-
// 原生 active(24px)/inactive(20px) 两套 SVG,若不归一化,
|
|
854
|
-
// active 24px 放进 26px 的 Fast content 容器会显得大一圈。
|
|
855
|
-
template.setAttribute("width", "20");
|
|
856
|
-
template.setAttribute("height", "20");
|
|
857
|
-
if (!modelSelectorNativeFastIcons) {
|
|
858
|
-
modelSelectorNativeFastIcons = {};
|
|
859
|
-
modelSelectorNativeFastIconSources = {};
|
|
860
|
-
}
|
|
861
|
-
modelSelectorNativeFastIcons[state] = template;
|
|
862
|
-
modelSelectorNativeFastIconSources[state] = "dom";
|
|
863
|
-
modelSelectorNativeFastIconsDirty = true;
|
|
864
|
-
};
|
|
865
|
-
capture("active", "M11.9125 21.4125");
|
|
866
|
-
// 现版 app 的 inactive bolt 为 20x20,路径以 M9.80999 开头;
|
|
867
|
-
// 旧的 M7.38 前缀已匹配不到任何图标,导致 inactive 缺失。
|
|
868
|
-
capture("inactive", "M9.80999 17.8302");
|
|
821
|
+
// 原生控件每次只渲染当前状态;状态切换后按控件本身更新缓存。
|
|
822
|
+
const nativeContent = Array.from(document.querySelectorAll('[class*="_FastModeToggleContent_"]'))
|
|
823
|
+
.find((content) => !content.closest("[data-codex-model-slider-generic]"));
|
|
824
|
+
const toggle = nativeContent?.closest('[class*="_FastModeToggle_"]');
|
|
825
|
+
const state = toggle?.getAttribute("data-fast-mode-enabled") === "true" ? "active" : "inactive";
|
|
826
|
+
const svg = nativeContent?.querySelector("svg");
|
|
827
|
+
const current = modelSelectorNativeFastIcons?.[state];
|
|
828
|
+
if (svg && (current?.innerHTML !== svg.innerHTML
|
|
829
|
+
|| current.getAttribute("viewBox") !== svg.getAttribute("viewBox")
|
|
830
|
+
|| current.getAttribute("width") !== svg.getAttribute("width"))) {
|
|
831
|
+
const template = svg.cloneNode(true);
|
|
832
|
+
template.removeAttribute("id");
|
|
833
|
+
if (!modelSelectorNativeFastIcons) {
|
|
834
|
+
modelSelectorNativeFastIcons = {};
|
|
835
|
+
}
|
|
836
|
+
modelSelectorNativeFastIcons[state] = template;
|
|
837
|
+
modelSelectorNativeFastIconsDirty = true;
|
|
869
838
|
}
|
|
870
839
|
const ready = Boolean(
|
|
871
840
|
modelSelectorNativeFastIcons?.active && modelSelectorNativeFastIcons?.inactive,
|
|
@@ -874,31 +843,42 @@ export function createInjectionSource(features, {
|
|
|
874
843
|
persistNativeFastIcons();
|
|
875
844
|
applyNativeFastIconsToShells();
|
|
876
845
|
}
|
|
877
|
-
return modelSelectorNativeFastIcons;
|
|
878
846
|
}
|
|
879
847
|
|
|
880
848
|
function applyNativeFastIconsToShells() {
|
|
881
849
|
document.querySelectorAll("[data-codex-model-slider-fast]").forEach((button) => {
|
|
882
850
|
const content = button.querySelector("[data-codex-model-slider-fast-content]");
|
|
883
|
-
|
|
884
|
-
if (!content || content.querySelector("svg")) return;
|
|
851
|
+
if (!content) return;
|
|
885
852
|
const icon = createNativeFastIcon(button.getAttribute("aria-pressed") === "true");
|
|
886
|
-
if (icon)
|
|
853
|
+
if (!icon) return;
|
|
854
|
+
const current = content.querySelector("svg");
|
|
855
|
+
if (!current || current.innerHTML !== icon.innerHTML
|
|
856
|
+
|| current.getAttribute("viewBox") !== icon.getAttribute("viewBox")
|
|
857
|
+
|| current.getAttribute("width") !== icon.getAttribute("width")) {
|
|
858
|
+
content.replaceChildren(icon);
|
|
859
|
+
}
|
|
887
860
|
});
|
|
888
861
|
}
|
|
889
862
|
|
|
890
863
|
function createNativeFastIcon(active) {
|
|
891
864
|
// 直接读取已就绪的图标存储(启动时由 bundle/持久化填充,运行期由
|
|
892
865
|
// captureNativeFastIcon 升级为 DOM 克隆),避免再次触发捕获造成递归。
|
|
893
|
-
//
|
|
894
|
-
const
|
|
895
|
-
.
|
|
896
|
-
|
|
897
|
-
|
|
866
|
+
// 优先克隆当前面板的图标;切换瞬间使用缓存时,也沿用原生图标尺寸。
|
|
867
|
+
const nativeContents = Array.from(document.querySelectorAll('[class*="_FastModeToggleContent_"]'))
|
|
868
|
+
.filter((content) => !content.closest("[data-codex-model-slider-generic]"));
|
|
869
|
+
const nativeContent = nativeContents.find((content) => (
|
|
870
|
+
(content.getAttribute("data-fast-mode-enabled")
|
|
871
|
+
?? content.closest('[class*="_FastModeToggle_"]')?.getAttribute("data-fast-mode-enabled")) === String(active)
|
|
872
|
+
));
|
|
873
|
+
const nativeSvg = nativeContent?.querySelector("svg") ?? nativeContents[0]?.querySelector("svg");
|
|
898
874
|
const template = nativeContent?.querySelector("svg")
|
|
899
875
|
?? modelSelectorNativeFastIcons?.[active ? "active" : "inactive"];
|
|
900
876
|
if (!template) return null;
|
|
901
877
|
const icon = template.cloneNode(true);
|
|
878
|
+
if (nativeSvg) {
|
|
879
|
+
icon.setAttribute("width", nativeSvg.getAttribute("width") ?? "16");
|
|
880
|
+
icon.setAttribute("height", nativeSvg.getAttribute("height") ?? "16");
|
|
881
|
+
}
|
|
902
882
|
icon.setAttribute("aria-hidden", "true");
|
|
903
883
|
return icon;
|
|
904
884
|
}
|
|
@@ -994,9 +974,25 @@ export function createInjectionSource(features, {
|
|
|
994
974
|
function nativeComposerModelController() {
|
|
995
975
|
const trigger = nativeComposerTrigger();
|
|
996
976
|
let fiber = currentReactFiber(trigger);
|
|
977
|
+
let powerController = null;
|
|
997
978
|
for (let depth = 0; fiber && depth < 40; depth += 1, fiber = fiber.return) {
|
|
998
979
|
const props = fiber.memoizedProps;
|
|
980
|
+
if (
|
|
981
|
+
Array.isArray(props?.powerSelections)
|
|
982
|
+
&& typeof props.onCommitPower === "function"
|
|
983
|
+
) powerController ??= props;
|
|
999
984
|
if (Array.isArray(props?.models) && typeof props.onSelectReasoningEffort === "function") return props;
|
|
985
|
+
if (Array.isArray(props?.models) && typeof props.onCommitPower === "function") {
|
|
986
|
+
const power = powerController ?? props;
|
|
987
|
+
return {
|
|
988
|
+
...props,
|
|
989
|
+
powerSelections: power.powerSelections ?? props.powerSelections,
|
|
990
|
+
selectedPowerSelection: power.selectedPowerSelection ?? props.selectedPowerSelection,
|
|
991
|
+
fallbackPowerSelection: power.fallbackPowerSelection ?? props.fallbackPowerSelection,
|
|
992
|
+
onSelectPower: power.onSelectPower ?? props.onSelectPower,
|
|
993
|
+
onCommitPower: power.onCommitPower,
|
|
994
|
+
};
|
|
995
|
+
}
|
|
1000
996
|
// Chat/Temporary Chat 使用原生模型选择器,不进入任务模型适配。
|
|
1001
997
|
if (props?.selectedModel && typeof props.onModelChange === "function") return null;
|
|
1002
998
|
}
|
|
@@ -1663,7 +1659,11 @@ export function createInjectionSource(features, {
|
|
|
1663
1659
|
if (!wrapper?.hasAttribute("data-radix-popper-content-wrapper")) return;
|
|
1664
1660
|
const existing = modelSelectorPositionObservers.get(wrapper);
|
|
1665
1661
|
if (existing) {
|
|
1666
|
-
existing.menu
|
|
1662
|
+
if (existing.menu !== menu) {
|
|
1663
|
+
existing.resizeObserver.unobserve(existing.menu);
|
|
1664
|
+
existing.resizeObserver.observe(menu);
|
|
1665
|
+
existing.menu = menu;
|
|
1666
|
+
}
|
|
1667
1667
|
return;
|
|
1668
1668
|
}
|
|
1669
1669
|
const record = {
|
|
@@ -1713,6 +1713,32 @@ export function createInjectionSource(features, {
|
|
|
1713
1713
|
}
|
|
1714
1714
|
}
|
|
1715
1715
|
|
|
1716
|
+
function restoreGenericModelPicker(container) {
|
|
1717
|
+
const original = modelSelectorGenericContainers.get(container);
|
|
1718
|
+
if (!original) return;
|
|
1719
|
+
original.effectMirror?.disconnect();
|
|
1720
|
+
if (container.isConnected) {
|
|
1721
|
+
const menu = container.parentElement;
|
|
1722
|
+
menu?.querySelector(":scope > [data-codex-model-slider-generic]")?.remove();
|
|
1723
|
+
menu?.removeAttribute("data-codex-model-slider-generic-menu");
|
|
1724
|
+
menu?.style.removeProperty("--codex-model-slider-menu-center-shift");
|
|
1725
|
+
container.style.position = original.position;
|
|
1726
|
+
container.style.inset = original.inset;
|
|
1727
|
+
container.style.opacity = original.opacity;
|
|
1728
|
+
container.style.pointerEvents = original.pointerEvents;
|
|
1729
|
+
if (original.ariaHidden == null) container.removeAttribute("aria-hidden");
|
|
1730
|
+
else container.setAttribute("aria-hidden", original.ariaHidden);
|
|
1731
|
+
container.inert = original.inert;
|
|
1732
|
+
}
|
|
1733
|
+
modelSelectorGenericContainers.delete(container);
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
function pruneGenericModelPickerContainers() {
|
|
1737
|
+
for (const container of modelSelectorGenericContainers.keys()) {
|
|
1738
|
+
if (!container.isConnected) restoreGenericModelPicker(container);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1716
1742
|
function closeControllerModelMenu() {
|
|
1717
1743
|
const record = modelSelectorControllerMenu;
|
|
1718
1744
|
if (!record) return false;
|
|
@@ -1853,22 +1879,6 @@ export function createInjectionSource(features, {
|
|
|
1853
1879
|
// 原生 effort 子菜单与滑条视觉 — effort 事务、Fast/通用滑条视觉与状态同步
|
|
1854
1880
|
// ========================================================================
|
|
1855
1881
|
|
|
1856
|
-
function openNativeModelSubmenu(parentMenu) {
|
|
1857
|
-
let attempts = 0;
|
|
1858
|
-
const open = () => {
|
|
1859
|
-
attempts += 1;
|
|
1860
|
-
const currentMenu = nativeModelPickerMenu() ?? parentMenu;
|
|
1861
|
-
const row = nativeModelPickerRow(currentMenu);
|
|
1862
|
-
if (row) {
|
|
1863
|
-
setReactControlledMenuOpen(row, true);
|
|
1864
|
-
setTimeout(queueEnsure, 0);
|
|
1865
|
-
return;
|
|
1866
|
-
}
|
|
1867
|
-
if (attempts < 12) requestAnimationFrame(open);
|
|
1868
|
-
};
|
|
1869
|
-
setTimeout(open, 80);
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
1882
|
function nativeEffortRow(parentMenu) {
|
|
1873
1883
|
return Array.from(parentMenu?.querySelectorAll('[role="menuitem"][aria-haspopup="menu"]') ?? [])
|
|
1874
1884
|
.filter((row) => !row.hasAttribute("data-model-picker-view-toggle"))[1] ?? null;
|
|
@@ -1895,6 +1905,30 @@ export function createInjectionSource(features, {
|
|
|
1895
1905
|
queueEnsure();
|
|
1896
1906
|
return;
|
|
1897
1907
|
}
|
|
1908
|
+
if (typeof controller?.onCommitPower === "function") {
|
|
1909
|
+
const normalizedEffort = String(effort).toLowerCase();
|
|
1910
|
+
const activeModel = String(controller.model ?? nativeCatalogModelFor(identity)?.slug ?? identity.raw);
|
|
1911
|
+
const next = controller.powerSelections?.find((selection) => (
|
|
1912
|
+
String(selection?.model ?? "") === activeModel
|
|
1913
|
+
&& String(selection?.reasoningEffort ?? "").toLowerCase() === normalizedEffort
|
|
1914
|
+
));
|
|
1915
|
+
const previous = controller.selectedPowerSelection
|
|
1916
|
+
?? controller.powerSelections?.find((selection) => (
|
|
1917
|
+
String(selection?.model ?? "") === activeModel
|
|
1918
|
+
&& String(selection?.reasoningEffort ?? "").toLowerCase()
|
|
1919
|
+
=== String(controller.reasoningEffort ?? "").toLowerCase()
|
|
1920
|
+
))
|
|
1921
|
+
?? controller.fallbackPowerSelection;
|
|
1922
|
+
if (!next || !previous) return;
|
|
1923
|
+
controller.onSelectPower?.(next);
|
|
1924
|
+
controller.onCommitPower(next, previous);
|
|
1925
|
+
if (modelSelectorPendingOutsideDismiss) {
|
|
1926
|
+
closeReactControlledMenu(nativeComposerTrigger());
|
|
1927
|
+
modelSelectorPendingOutsideDismiss = false;
|
|
1928
|
+
}
|
|
1929
|
+
queueEnsure();
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1898
1932
|
const row = nativeEffortRow(parentMenu);
|
|
1899
1933
|
if (!row) return;
|
|
1900
1934
|
beginNativeEffortTransaction();
|
|
@@ -1960,6 +1994,19 @@ export function createInjectionSource(features, {
|
|
|
1960
1994
|
rail.setAttribute("aria-valuenow", String(index));
|
|
1961
1995
|
}
|
|
1962
1996
|
|
|
1997
|
+
function syncGenericMaximumVisual(rail, effortText, fastButton, maximum) {
|
|
1998
|
+
const value = String(maximum);
|
|
1999
|
+
rail?.setAttribute("data-highlighted", value);
|
|
2000
|
+
rail?.setAttribute("data-max", value);
|
|
2001
|
+
effortText?.setAttribute("data-maximum", value);
|
|
2002
|
+
fastButton?.setAttribute("data-max-power-selection", value);
|
|
2003
|
+
const maxEffects = rail?.querySelector('[class*="_MaxEffects_"]');
|
|
2004
|
+
if (maxEffects) {
|
|
2005
|
+
maxEffects.style.opacity = maximum ? "1" : "0";
|
|
2006
|
+
if (maximum) maxEffects.style.transform = "translateX(0)";
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
|
|
1963
2010
|
function neutralizeStaleSliderVisual(rail) {
|
|
1964
2011
|
for (const selector of [
|
|
1965
2012
|
"[data-codex-model-slider-native-range]",
|
|
@@ -1983,9 +2030,21 @@ export function createInjectionSource(features, {
|
|
|
1983
2030
|
return value - Math.floor(value);
|
|
1984
2031
|
}
|
|
1985
2032
|
|
|
1986
|
-
function syncNativeFastParticles(rail, enabled) {
|
|
2033
|
+
function syncNativeFastParticles(rail, enabled, entering = false) {
|
|
1987
2034
|
const track = Array.from(rail.children).find((element) => String(element.className).includes("_Track_"));
|
|
1988
2035
|
if (!track) return;
|
|
2036
|
+
const staticParticles = rail.querySelector('[class*="_ParticleTransition_"]');
|
|
2037
|
+
if (staticParticles && !matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
2038
|
+
staticParticles.__codexStaticParticleFade?.cancel();
|
|
2039
|
+
staticParticles.style.opacity = enabled ? "0" : "1";
|
|
2040
|
+
if (entering) {
|
|
2041
|
+
// 动态粒子渐显时,让原有星点在同一时间淡出。
|
|
2042
|
+
staticParticles.__codexStaticParticleFade = staticParticles.animate(
|
|
2043
|
+
[{ opacity: 1 }, { opacity: 0 }],
|
|
2044
|
+
{ duration: 240, easing: "ease-out" },
|
|
2045
|
+
);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
1989
2048
|
track.querySelectorAll('[class*="_FastParticleClip_"]').forEach((element) => element.remove());
|
|
1990
2049
|
if (!enabled || matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
1991
2050
|
const clipClass = nativeCssModuleClass("FastParticleClip");
|
|
@@ -2025,6 +2084,10 @@ export function createInjectionSource(features, {
|
|
|
2025
2084
|
clip.append(particles);
|
|
2026
2085
|
const tickRail = Array.from(track.children).find((element) => String(element.className).includes("_TickRail_"));
|
|
2027
2086
|
track.insertBefore(clip, tickRail ?? null);
|
|
2087
|
+
if (entering) {
|
|
2088
|
+
// 首帧沿用关闭 Fast 时的星点,再渐显流动粒子。
|
|
2089
|
+
clip.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 240, easing: "ease-out" });
|
|
2090
|
+
}
|
|
2028
2091
|
}
|
|
2029
2092
|
|
|
2030
2093
|
function syncGenericFastVisual(shell, enabled, maxPowerSelection, animate = false) {
|
|
@@ -2033,7 +2096,12 @@ export function createInjectionSource(features, {
|
|
|
2033
2096
|
if (!button || !rail) return;
|
|
2034
2097
|
button.setAttribute("aria-pressed", String(enabled));
|
|
2035
2098
|
button.setAttribute("data-fast-mode-enabled", String(enabled));
|
|
2036
|
-
|
|
2099
|
+
syncGenericMaximumVisual(
|
|
2100
|
+
rail,
|
|
2101
|
+
shell.querySelector("[data-codex-model-slider-effort-row] [data-maximum]"),
|
|
2102
|
+
button,
|
|
2103
|
+
maxPowerSelection,
|
|
2104
|
+
);
|
|
2037
2105
|
button.setAttribute("aria-label", enabled
|
|
2038
2106
|
? (locale === "zh-CN" ? "关闭 Fast 模式" : "Disable Fast mode")
|
|
2039
2107
|
: (locale === "zh-CN" ? "开启 Fast 模式" : "Enable Fast mode"));
|
|
@@ -2042,8 +2110,18 @@ export function createInjectionSource(features, {
|
|
|
2042
2110
|
if (icon && content) content.replaceChildren(icon);
|
|
2043
2111
|
rail.setAttribute("data-fast-mode", String(enabled));
|
|
2044
2112
|
rail.setAttribute("data-fast-mode-dot-transition", animate ? (enabled ? "entering" : "exiting") : (enabled ? "active" : "inactive"));
|
|
2045
|
-
syncNativeFastParticles(rail, enabled);
|
|
2113
|
+
syncNativeFastParticles(rail, enabled, animate && enabled);
|
|
2046
2114
|
setGenericSliderVisual(rail, Number(rail.getAttribute("aria-valuenow") ?? 0), Number(rail.getAttribute("aria-valuemax") ?? 0) + 1);
|
|
2115
|
+
if (animate && enabled) {
|
|
2116
|
+
shell.setAttribute("data-codex-model-slider-fast-transitioning", "");
|
|
2117
|
+
setTimeout(() => {
|
|
2118
|
+
if (!shell.isConnected) return;
|
|
2119
|
+
shell.removeAttribute("data-codex-model-slider-fast-transitioning");
|
|
2120
|
+
queueEnsure();
|
|
2121
|
+
}, 260);
|
|
2122
|
+
} else {
|
|
2123
|
+
shell.removeAttribute("data-codex-model-slider-fast-transitioning");
|
|
2124
|
+
}
|
|
2047
2125
|
if (animate) {
|
|
2048
2126
|
setTimeout(() => {
|
|
2049
2127
|
if (rail.isConnected && rail.getAttribute("data-fast-mode") === String(enabled)) {
|
|
@@ -2102,6 +2180,88 @@ export function createInjectionSource(features, {
|
|
|
2102
2180
|
return clone;
|
|
2103
2181
|
}
|
|
2104
2182
|
|
|
2183
|
+
function syncClonedPowerEffects(nativeContainer, shell) {
|
|
2184
|
+
const source = nativeContainer.querySelector("[data-model-picker-power-slider]");
|
|
2185
|
+
const clone = shell.querySelector("[data-codex-model-slider-native-slider]");
|
|
2186
|
+
if (!source || !clone) return null;
|
|
2187
|
+
const sourceCanvas = source.querySelector("canvas");
|
|
2188
|
+
const cloneCanvas = clone.querySelector("canvas");
|
|
2189
|
+
let canvasTimer = null;
|
|
2190
|
+
let observer = null;
|
|
2191
|
+
const disconnect = () => {
|
|
2192
|
+
observer?.disconnect();
|
|
2193
|
+
if (canvasTimer != null) clearInterval(canvasTimer);
|
|
2194
|
+
};
|
|
2195
|
+
if (sourceCanvas && cloneCanvas) {
|
|
2196
|
+
// 原生 canvas 可能在首次克隆时尚未绘制;持续同步其高光纹理。
|
|
2197
|
+
const scratch = document.createElement("canvas");
|
|
2198
|
+
const scratchContext = scratch.getContext("2d");
|
|
2199
|
+
const cloneContext = cloneCanvas.getContext("2d");
|
|
2200
|
+
const copyCanvas = () => {
|
|
2201
|
+
if (!source.isConnected || !shell.isConnected) {
|
|
2202
|
+
disconnect();
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
if (scratch.width !== sourceCanvas.width || scratch.height !== sourceCanvas.height) {
|
|
2206
|
+
scratch.width = sourceCanvas.width;
|
|
2207
|
+
scratch.height = sourceCanvas.height;
|
|
2208
|
+
cloneCanvas.width = sourceCanvas.width;
|
|
2209
|
+
cloneCanvas.height = sourceCanvas.height;
|
|
2210
|
+
}
|
|
2211
|
+
scratchContext.clearRect(0, 0, scratch.width, scratch.height);
|
|
2212
|
+
scratchContext.drawImage(sourceCanvas, 0, 0);
|
|
2213
|
+
const y = Math.floor(scratch.height / 2);
|
|
2214
|
+
const hasDrawing = [0.25, 0.5, 0.75].some((ratio) => (
|
|
2215
|
+
scratchContext.getImageData(Math.floor(scratch.width * ratio), y, 1, 1).data[3] > 0
|
|
2216
|
+
));
|
|
2217
|
+
if (!hasDrawing) return;
|
|
2218
|
+
cloneContext.clearRect(0, 0, cloneCanvas.width, cloneCanvas.height);
|
|
2219
|
+
cloneContext.drawImage(scratch, 0, 0);
|
|
2220
|
+
};
|
|
2221
|
+
copyCanvas();
|
|
2222
|
+
canvasTimer = setInterval(copyCanvas, 80);
|
|
2223
|
+
}
|
|
2224
|
+
const sourceEffects = source.querySelector('[class*="_MaxEffects_"]');
|
|
2225
|
+
const cloneEffects = clone.querySelector('[class*="_MaxEffects_"]');
|
|
2226
|
+
if (!sourceEffects || !cloneEffects) return { disconnect };
|
|
2227
|
+
const sync = () => {
|
|
2228
|
+
if (!source.isConnected || !shell.isConnected) {
|
|
2229
|
+
disconnect();
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
const sourceTransition = sourceEffects.querySelector('[class*="_ParticleTransition_"]');
|
|
2233
|
+
let cloneTransition = cloneEffects.querySelector('[class*="_ParticleTransition_"]');
|
|
2234
|
+
if (!sourceTransition) {
|
|
2235
|
+
if (clone.querySelector("[data-fast-mode]")?.getAttribute("data-fast-mode") !== "true") {
|
|
2236
|
+
cloneTransition?.remove();
|
|
2237
|
+
}
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
const sourceParticles = sourceTransition.querySelectorAll('[class*="_TrackParticle_"]');
|
|
2241
|
+
let cloneParticles = cloneTransition?.querySelectorAll('[class*="_TrackParticle_"]');
|
|
2242
|
+
if (!cloneTransition || sourceParticles.length !== cloneParticles.length) {
|
|
2243
|
+
cloneTransition?.remove();
|
|
2244
|
+
cloneTransition = sourceTransition.cloneNode(true);
|
|
2245
|
+
cloneEffects.append(cloneTransition);
|
|
2246
|
+
cloneParticles = cloneTransition.querySelectorAll('[class*="_TrackParticle_"]');
|
|
2247
|
+
}
|
|
2248
|
+
cloneTransition.setAttribute("style", sourceTransition.getAttribute("style") ?? "");
|
|
2249
|
+
sourceParticles.forEach((particle, index) => {
|
|
2250
|
+
const style = particle.getAttribute("style") ?? "";
|
|
2251
|
+
if (cloneParticles[index].getAttribute("style") !== style) {
|
|
2252
|
+
cloneParticles[index].setAttribute("style", style);
|
|
2253
|
+
}
|
|
2254
|
+
});
|
|
2255
|
+
if (!matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
2256
|
+
cloneTransition.style.opacity = clone.querySelector("[data-fast-mode]")?.getAttribute("data-fast-mode") === "true" ? "0" : "1";
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
2259
|
+
observer = new MutationObserver(sync);
|
|
2260
|
+
observer.observe(sourceEffects, { attributes: true, attributeFilter: ["style"], childList: true, subtree: true });
|
|
2261
|
+
sync();
|
|
2262
|
+
return { disconnect };
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2105
2265
|
function createGenericModelSlider(parentMenu, selector, identity, model, selectedEffort) {
|
|
2106
2266
|
const efforts = model.supportedReasoningLevels.map((level) => level.effort);
|
|
2107
2267
|
const selectedIndex = efforts.indexOf(selectedEffort);
|
|
@@ -2158,9 +2318,21 @@ export function createInjectionSource(features, {
|
|
|
2158
2318
|
buttonLabel.className = classes.ViewToggleModelLabel;
|
|
2159
2319
|
buttonLabel.setAttribute("data-codex-model-slider-model-label", "");
|
|
2160
2320
|
buttonLabel.textContent = modelDisplayLabel(identity, selector);
|
|
2321
|
+
const modelRow = document.createElement("span");
|
|
2322
|
+
modelRow.setAttribute("data-codex-model-slider-model-row", "");
|
|
2323
|
+
modelRow.style.display = "inline-flex";
|
|
2324
|
+
modelRow.style.alignItems = "center";
|
|
2325
|
+
modelRow.style.justifyContent = "center";
|
|
2326
|
+
modelRow.style.gap = "calc(var(--spacing) / 2)";
|
|
2327
|
+
modelRow.style.minWidth = "0";
|
|
2328
|
+
modelRow.style.maxWidth = "100%";
|
|
2329
|
+
modelRow.append(buttonLabel);
|
|
2161
2330
|
const effortText = selectedEffortVisible ? document.createElement("span") : null;
|
|
2162
2331
|
if (effortText) {
|
|
2163
2332
|
effortText.className = classes.ViewToggleEffortLabel;
|
|
2333
|
+
// 原生标签会跨满整行;收回中列,使上下两行各自居中。
|
|
2334
|
+
effortText.style.gridColumn = "2";
|
|
2335
|
+
effortText.style.gridRow = "1";
|
|
2164
2336
|
effortText.textContent = effortLabel(selectedEffort);
|
|
2165
2337
|
effortText.setAttribute("data-accent", "true");
|
|
2166
2338
|
effortText.setAttribute("data-effort-only", "false");
|
|
@@ -2174,13 +2346,13 @@ export function createInjectionSource(features, {
|
|
|
2174
2346
|
)?.cloneNode(true);
|
|
2175
2347
|
if (nativeChevron) {
|
|
2176
2348
|
nativeChevron.classList.add(classes.ViewToggleIcon);
|
|
2349
|
+
nativeChevron.style.gridArea = "auto";
|
|
2350
|
+
nativeChevron.style.flexShrink = "0";
|
|
2177
2351
|
nativeChevron.setAttribute("aria-hidden", "true");
|
|
2178
|
-
|
|
2179
|
-
effortRow.append(nativeChevron);
|
|
2180
|
-
} else {
|
|
2181
|
-
if (effortText) effortRow.append(effortText);
|
|
2352
|
+
modelRow.append(nativeChevron);
|
|
2182
2353
|
}
|
|
2183
|
-
|
|
2354
|
+
if (effortText) effortRow.append(effortText);
|
|
2355
|
+
buttonContent.append(effortRow, modelRow);
|
|
2184
2356
|
modelButton.append(buttonContent);
|
|
2185
2357
|
const openModels = () => openControllerModelMenu(parentMenu, modelButton, selector);
|
|
2186
2358
|
modelButton.addEventListener("click", openModels);
|
|
@@ -2297,7 +2469,12 @@ export function createInjectionSource(features, {
|
|
|
2297
2469
|
}
|
|
2298
2470
|
setGenericSliderVisual(rail, selectedIndex, efforts.length);
|
|
2299
2471
|
syncNativeFastParticles(rail, fastEnabled);
|
|
2300
|
-
|
|
2472
|
+
syncGenericMaximumVisual(
|
|
2473
|
+
rail,
|
|
2474
|
+
effortText,
|
|
2475
|
+
shell.querySelector("[data-codex-model-slider-fast]"),
|
|
2476
|
+
selectedEffort === "ultra",
|
|
2477
|
+
);
|
|
2301
2478
|
|
|
2302
2479
|
let pendingIndex = selectedIndex;
|
|
2303
2480
|
let dragging = false;
|
|
@@ -2316,10 +2493,9 @@ export function createInjectionSource(features, {
|
|
|
2316
2493
|
rail.setAttribute("aria-valuetext", effortLabel(effort));
|
|
2317
2494
|
if (effortText) {
|
|
2318
2495
|
effortText.textContent = effortLabel(effort);
|
|
2319
|
-
effortText.setAttribute("data-maximum", String(effort === "ultra"));
|
|
2320
2496
|
}
|
|
2321
2497
|
const fastButton = shell.querySelector("[data-codex-model-slider-fast]");
|
|
2322
|
-
|
|
2498
|
+
syncGenericMaximumVisual(rail, effortText, fastButton, effort === "ultra");
|
|
2323
2499
|
};
|
|
2324
2500
|
const preview = (clientX) => previewIndex(indexAt(clientX));
|
|
2325
2501
|
rail.addEventListener("pointerdown", (event) => {
|
|
@@ -2421,7 +2597,8 @@ export function createInjectionSource(features, {
|
|
|
2421
2597
|
const selectedServiceTier = nativeComposerModelController()?.selectedServiceTier;
|
|
2422
2598
|
const serviceTierKey = selectedServiceTier?.id ?? selectedServiceTier ?? "standard";
|
|
2423
2599
|
const selectionMode = nativeComposerModelController()?.selectionMode ?? "";
|
|
2424
|
-
const
|
|
2600
|
+
const structureKey = `${identity.raw}:${selectedEffortValue}:${selectionMode}:${efforts.map((level) => level.effort).join(",")}`;
|
|
2601
|
+
const key = `${structureKey}:${serviceTierKey}`;
|
|
2425
2602
|
const existing = parentMenu.querySelector(":scope > [data-codex-model-slider-generic]");
|
|
2426
2603
|
const nativeTemplateReady = Boolean(
|
|
2427
2604
|
parentMenu.firstElementChild
|
|
@@ -2439,11 +2616,24 @@ export function createInjectionSource(features, {
|
|
|
2439
2616
|
const canUpgradeFallback = Boolean(
|
|
2440
2617
|
existing?.querySelector("[data-codex-model-slider-fallback]") && nativeTemplateReady,
|
|
2441
2618
|
);
|
|
2619
|
+
if (existing?.getAttribute("data-codex-model-slider-structure-key") === structureKey
|
|
2620
|
+
&& existing.hasAttribute("data-codex-model-slider-fast-transitioning") && !canUpgradeFallback) {
|
|
2621
|
+
centerGenericModelPicker(parentMenu);
|
|
2622
|
+
return true;
|
|
2623
|
+
}
|
|
2624
|
+
const currentFast = existing?.querySelector("[data-codex-model-slider-rail]")?.getAttribute("data-fast-mode");
|
|
2625
|
+
const nativeFast = parentMenu.firstElementChild
|
|
2626
|
+
?.querySelector("[data-model-picker-power-slider] [data-fast-mode]")?.getAttribute("data-fast-mode");
|
|
2627
|
+
if (existing?.getAttribute("data-codex-model-slider-structure-key") === structureKey
|
|
2628
|
+
&& currentFast != null && currentFast === nativeFast && !canUpgradeFallback) {
|
|
2629
|
+
existing.setAttribute("data-codex-model-slider-generic", key);
|
|
2630
|
+
centerGenericModelPicker(parentMenu);
|
|
2631
|
+
return true;
|
|
2632
|
+
}
|
|
2442
2633
|
if (existing?.getAttribute("data-codex-model-slider-generic") === key && !canUpgradeFallback) {
|
|
2443
2634
|
centerGenericModelPicker(parentMenu);
|
|
2444
2635
|
return true;
|
|
2445
2636
|
}
|
|
2446
|
-
existing?.remove();
|
|
2447
2637
|
const nativeContainer = parentMenu.firstElementChild;
|
|
2448
2638
|
if (!nativeContainer) return false;
|
|
2449
2639
|
const shell = createGenericModelSlider(
|
|
@@ -2453,29 +2643,37 @@ export function createInjectionSource(features, {
|
|
|
2453
2643
|
displayModel,
|
|
2454
2644
|
selectedEffortValue,
|
|
2455
2645
|
);
|
|
2456
|
-
if (!shell)
|
|
2646
|
+
if (!shell) {
|
|
2647
|
+
restoreGenericModelPicker(nativeContainer);
|
|
2648
|
+
return false;
|
|
2649
|
+
}
|
|
2650
|
+
modelSelectorGenericContainers.get(nativeContainer)?.effectMirror?.disconnect();
|
|
2651
|
+
existing?.remove();
|
|
2457
2652
|
if (!modelSelectorGenericContainers.has(nativeContainer)) {
|
|
2458
|
-
modelSelectorGenericContainers.set(nativeContainer,
|
|
2653
|
+
modelSelectorGenericContainers.set(nativeContainer, {
|
|
2654
|
+
position: nativeContainer.style.position,
|
|
2655
|
+
inset: nativeContainer.style.inset,
|
|
2656
|
+
opacity: nativeContainer.style.opacity,
|
|
2657
|
+
pointerEvents: nativeContainer.style.pointerEvents,
|
|
2658
|
+
ariaHidden: nativeContainer.getAttribute("aria-hidden"),
|
|
2659
|
+
inert: nativeContainer.inert,
|
|
2660
|
+
});
|
|
2459
2661
|
}
|
|
2460
2662
|
parentMenu.setAttribute("data-codex-model-slider-generic-menu", "");
|
|
2461
|
-
nativeContainer.style.
|
|
2663
|
+
nativeContainer.style.position = "absolute";
|
|
2664
|
+
nativeContainer.style.inset = "0";
|
|
2665
|
+
nativeContainer.style.opacity = "0";
|
|
2666
|
+
nativeContainer.style.pointerEvents = "none";
|
|
2667
|
+
nativeContainer.setAttribute("aria-hidden", "true");
|
|
2668
|
+
nativeContainer.inert = true;
|
|
2462
2669
|
shell.setAttribute("data-codex-model-slider-generic", key);
|
|
2670
|
+
shell.setAttribute("data-codex-model-slider-structure-key", structureKey);
|
|
2463
2671
|
parentMenu.append(shell);
|
|
2672
|
+
modelSelectorGenericContainers.get(nativeContainer).effectMirror = syncClonedPowerEffects(nativeContainer, shell);
|
|
2464
2673
|
centerGenericModelPicker(parentMenu);
|
|
2465
2674
|
return true;
|
|
2466
2675
|
}
|
|
2467
2676
|
|
|
2468
|
-
function restoreGenericModelPicker(parentMenu) {
|
|
2469
|
-
parentMenu.querySelector(":scope > [data-codex-model-slider-generic]")?.remove();
|
|
2470
|
-
parentMenu.removeAttribute("data-codex-model-slider-generic-menu");
|
|
2471
|
-
parentMenu.style.removeProperty("--codex-model-slider-menu-center-shift");
|
|
2472
|
-
const nativeContainer = parentMenu.firstElementChild;
|
|
2473
|
-
if (nativeContainer && modelSelectorGenericContainers.has(nativeContainer)) {
|
|
2474
|
-
nativeContainer.style.display = modelSelectorGenericContainers.get(nativeContainer);
|
|
2475
|
-
modelSelectorGenericContainers.delete(nativeContainer);
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
|
|
2479
2677
|
function enhanceNativeModelPicker(parentMenu, selector) {
|
|
2480
2678
|
if (enhanceGenericModelPicker(parentMenu, selector)) return;
|
|
2481
2679
|
const toggle = parentMenu.querySelector("[data-model-picker-view-toggle]");
|
|
@@ -2583,9 +2781,10 @@ export function createInjectionSource(features, {
|
|
|
2583
2781
|
}
|
|
2584
2782
|
// 原生开合的临时 min-width 钉定与弹层水平居中都基于对 Measurement
|
|
2585
2783
|
// 探针的测量;把探针文本同步为 MCX 显示标签,原生机制即可测得
|
|
2586
|
-
//
|
|
2784
|
+
// 真实显示宽度。保留 React 持有的子节点,避免切模型时卸载失败。
|
|
2587
2785
|
const measurement = trigger.querySelector('[class*="_ModelPickerTriggerMeasurement_"]');
|
|
2588
|
-
|
|
2786
|
+
const measurementText = measurement && firstVisibleTextNode(measurement);
|
|
2787
|
+
if (measurementText && measurementText.nodeValue !== label) measurementText.nodeValue = label;
|
|
2589
2788
|
}
|
|
2590
2789
|
|
|
2591
2790
|
function ensureModelSelector() {
|
|
@@ -2597,6 +2796,7 @@ export function createInjectionSource(features, {
|
|
|
2597
2796
|
}
|
|
2598
2797
|
pruneModelSubmenuPositionObservers();
|
|
2599
2798
|
pruneNativeModelToggleHandlers();
|
|
2799
|
+
pruneGenericModelPickerContainers();
|
|
2600
2800
|
document.documentElement.setAttribute("data-codex-model-slider-catalog-ready", "");
|
|
2601
2801
|
if (!modelSelectorStyle.isConnected) document.head?.append(modelSelectorStyle);
|
|
2602
2802
|
enhanceComposerModelTrigger(selector);
|
|
@@ -2667,8 +2867,8 @@ export function createInjectionSource(features, {
|
|
|
2667
2867
|
if (handlers.originalAriaExpanded == null) toggle.removeAttribute("aria-expanded");
|
|
2668
2868
|
else toggle.setAttribute("aria-expanded", handlers.originalAriaExpanded);
|
|
2669
2869
|
}
|
|
2670
|
-
for (const
|
|
2671
|
-
|
|
2870
|
+
for (const container of modelSelectorGenericContainers.keys()) {
|
|
2871
|
+
restoreGenericModelPicker(container);
|
|
2672
2872
|
}
|
|
2673
2873
|
document.querySelectorAll("[data-codex-model-slider-divider]")
|
|
2674
2874
|
.forEach((element) => element.remove());
|
|
@@ -2685,7 +2885,6 @@ export function createInjectionSource(features, {
|
|
|
2685
2885
|
document.documentElement.removeAttribute("data-codex-model-slider-catalog-ready");
|
|
2686
2886
|
modelSelectorStyle.remove();
|
|
2687
2887
|
modelSelectorOriginalToggleLabels.clear();
|
|
2688
|
-
modelSelectorGenericContainers.clear();
|
|
2689
2888
|
modelSelectorNativeToggleHandlers.clear();
|
|
2690
2889
|
}
|
|
2691
2890
|
|
|
@@ -2728,6 +2927,10 @@ export function createInjectionSource(features, {
|
|
|
2728
2927
|
// CDP 重连会轮换 token;保留已有页面和草稿,只重试未就绪的页面。
|
|
2729
2928
|
config.bindingName = bindingName;
|
|
2730
2929
|
config.bindingToken = bindingToken;
|
|
2930
|
+
for (const feature of pageScriptFeatures) {
|
|
2931
|
+
feature.pageScript.config.bindingName = bindingName;
|
|
2932
|
+
feature.pageScript.config.bindingToken = bindingToken;
|
|
2933
|
+
}
|
|
2731
2934
|
for (const [requestId, pending] of pendingHostActions) {
|
|
2732
2935
|
if (pending.kind === "surface-load") pendingHostActions.delete(requestId);
|
|
2733
2936
|
}
|
|
@@ -2821,7 +3024,17 @@ export function createInjectionSource(features, {
|
|
|
2821
3024
|
.map((name) => [name, style.getPropertyValue(name).trim()])
|
|
2822
3025
|
.filter(([, value]) => value),
|
|
2823
3026
|
);
|
|
2824
|
-
|
|
3027
|
+
// 按语义和命名空间解析,不依赖构建哈希或猜测近似名称。
|
|
3028
|
+
for (const name of themeTokenNames) {
|
|
3029
|
+
if (tokens[name]) continue;
|
|
3030
|
+
const semanticNames = [name, ...(themeTokenAliases[name] ?? [])];
|
|
3031
|
+
const candidates = semanticNames.flatMap((candidate) => candidate.startsWith("--color-")
|
|
3032
|
+
? [candidate, candidate.replace("--color-", "--app-color-")]
|
|
3033
|
+
: [candidate]);
|
|
3034
|
+
const value = candidates.map((candidate) => style.getPropertyValue(candidate).trim()).find(Boolean);
|
|
3035
|
+
if (value) tokens[name] = value;
|
|
3036
|
+
}
|
|
3037
|
+
const mode = root.dataset.theme === "dark" || root.classList.contains("electron-dark") || style.colorScheme === "dark"
|
|
2825
3038
|
? "dark"
|
|
2826
3039
|
: "light";
|
|
2827
3040
|
return { root, style, tokens, mode };
|
|
@@ -3091,6 +3304,8 @@ export function createInjectionSource(features, {
|
|
|
3091
3304
|
frame.title = title;
|
|
3092
3305
|
frame.allow = "clipboard-write";
|
|
3093
3306
|
frame.style.cssText = "width:100%;height:100%;border:0;display:block;background:transparent";
|
|
3307
|
+
// 图片页覆盖原生标题栏,避免控件点击被窗口拖拽区域吞掉。
|
|
3308
|
+
if (featureId === "images") frame.style.setProperty("-webkit-app-region", "no-drag");
|
|
3094
3309
|
return frame;
|
|
3095
3310
|
}
|
|
3096
3311
|
|
|
@@ -4290,7 +4505,7 @@ export function createInjectionSource(features, {
|
|
|
4290
4505
|
respondToSurface(record, requestId, { ok: true, result: { paths } });
|
|
4291
4506
|
return;
|
|
4292
4507
|
}
|
|
4293
|
-
if (["attach-file", "create-image-edit-thread"].includes(action)) {
|
|
4508
|
+
if (["attach-file", "create-image-edit-thread", "list-cloud-images", "import-cloud-image"].includes(action)) {
|
|
4294
4509
|
if (typeof globalThis[config.bindingName] !== "function") {
|
|
4295
4510
|
throw new Error("The native file bridge is unavailable");
|
|
4296
4511
|
}
|
|
@@ -4313,10 +4528,36 @@ export function createInjectionSource(features, {
|
|
|
4313
4528
|
}
|
|
4314
4529
|
}
|
|
4315
4530
|
|
|
4531
|
+
function requestPageHostAction(featureId, action, payload) {
|
|
4532
|
+
if (!featureById.get(featureId)?.hostActions.has(action)) return Promise.reject(new Error("Host action is not registered"));
|
|
4533
|
+
if (typeof globalThis[config.bindingName] !== "function") return Promise.reject(new Error("Host bridge is unavailable"));
|
|
4534
|
+
return new Promise((resolve, reject) => {
|
|
4535
|
+
const requestId = crypto.randomUUID();
|
|
4536
|
+
const timer = setTimeout(() => {
|
|
4537
|
+
pendingHostActions.delete(requestId);
|
|
4538
|
+
reject(new Error("Host request timed out"));
|
|
4539
|
+
}, 10_000);
|
|
4540
|
+
pendingHostActions.set(requestId, { kind: "page-script", resolve, reject, timer });
|
|
4541
|
+
try {
|
|
4542
|
+
globalThis[config.bindingName](JSON.stringify({ token: config.bindingToken, featureId, requestId, action, payload }));
|
|
4543
|
+
} catch (error) {
|
|
4544
|
+
clearTimeout(timer);
|
|
4545
|
+
pendingHostActions.delete(requestId);
|
|
4546
|
+
reject(error);
|
|
4547
|
+
}
|
|
4548
|
+
});
|
|
4549
|
+
}
|
|
4550
|
+
|
|
4316
4551
|
function resolveHostAction(requestId, response) {
|
|
4317
4552
|
const pending = pendingHostActions.get(requestId);
|
|
4318
4553
|
if (!pending) return false;
|
|
4319
4554
|
pendingHostActions.delete(requestId);
|
|
4555
|
+
if (pending.kind === "page-script") {
|
|
4556
|
+
clearTimeout(pending.timer);
|
|
4557
|
+
if (response.ok) pending.resolve(response.result);
|
|
4558
|
+
else pending.reject(new Error(response.error?.message ?? "Host request failed"));
|
|
4559
|
+
return true;
|
|
4560
|
+
}
|
|
4320
4561
|
const record = surfaceRecords.get(pending.recordKey);
|
|
4321
4562
|
if (!record) return false;
|
|
4322
4563
|
if (pending.kind === "surface-load") {
|
|
@@ -4472,7 +4713,7 @@ export function createInjectionSource(features, {
|
|
|
4472
4713
|
const themeObserver = new MutationObserver(queueTheme);
|
|
4473
4714
|
themeObserver.observe(document.documentElement, {
|
|
4474
4715
|
attributes: true,
|
|
4475
|
-
attributeFilter: ["class", "style"],
|
|
4716
|
+
attributeFilter: ["class", "style", "data-theme"],
|
|
4476
4717
|
});
|
|
4477
4718
|
domObservers.push(themeObserver);
|
|
4478
4719
|
|
|
@@ -4499,6 +4740,12 @@ export function createInjectionSource(features, {
|
|
|
4499
4740
|
resolveHostAction,
|
|
4500
4741
|
dispose: () => {
|
|
4501
4742
|
lifetime.abort();
|
|
4743
|
+
for (const [id, pending] of pendingHostActions) {
|
|
4744
|
+
if (pending.kind !== "page-script") continue;
|
|
4745
|
+
clearTimeout(pending.timer);
|
|
4746
|
+
pending.reject(new Error("Runtime disposed"));
|
|
4747
|
+
pendingHostActions.delete(id);
|
|
4748
|
+
}
|
|
4502
4749
|
for (const cleanup of pageScriptCleanups.splice(0)) {
|
|
4503
4750
|
try { cleanup(); } catch (error) {
|
|
4504
4751
|
console.warn("[codex-personal] page-script cleanup failed", error);
|
|
@@ -4559,6 +4806,7 @@ export function createInjectionSource(features, {
|
|
|
4559
4806
|
summaryLayout: RESPONSIVE_SUMMARY_LAYOUT,
|
|
4560
4807
|
rightPanelRouteKinds: RIGHT_PANEL_ROUTE_KINDS,
|
|
4561
4808
|
themeTokens: CODEX_THEME_TOKENS,
|
|
4809
|
+
themeTokenAliases: CODEX_THEME_TOKEN_ALIASES,
|
|
4562
4810
|
features: runtimeFeatures,
|
|
4563
4811
|
})})`;
|
|
4564
4812
|
}
|