minecodex 2.0.48 → 2.0.50
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/composer-expand/src/injection-source.mjs +2 -1
- package/features/composer-expand/src/native.mjs +6 -18
- package/features/file-navigation/src/adapters/codex/inline-modules.mjs +54 -0
- package/features/file-navigation/src/adapters/codex/modules.mjs +16 -4
- package/features/file-navigation/src/injection-source.mjs +3 -1
- package/features/images/src/adapters/codex/library-api.mjs +8 -7
- package/features/images/src/adapters/codex/native-image-modules.mjs +27 -15
- package/features/images/src/backend/cloud-library.mjs +3 -1
- package/features/images/src/library-prompt-source.mjs +3 -2
- package/features/markdown-reading/src/adapters/codex/links.mjs +2 -3
- package/features/markdown-reading/src/adapters/codex/rich-text.mjs +5 -4
- package/features/markdown-reading/src/injection-source.mjs +2 -1
- package/features/mermaid-diagrams/src/adapters/codex/native.mjs +3 -2
- package/features/mermaid-diagrams/src/injection-source.mjs +2 -1
- package/features/model-slider/src/adapters/codex/controller.mjs +4 -4
- package/features/model-slider/src/frontend/install.mjs +96 -28
- package/features/model-slider/src/injection-source.mjs +2 -0
- package/features/quick-chat/src/adapters/codex/shortcut.mjs +12 -19
- package/features/quick-chat/src/adapters/codex/sidebar.mjs +5 -5
- package/features/quick-chat/src/injection-source.mjs +2 -1
- package/features/session-status/src/adapters/codex/context-usage.mjs +4 -15
- package/features/session-status/src/injection-source.mjs +2 -1
- package/package.json +2 -1
- package/packages/codex-adapter/src/markdown/block-actions.mjs +3 -3
- package/packages/codex-adapter/src/markdown/editor-api.mjs +4 -4
- package/packages/codex-adapter/src/markdown/toc-native.mjs +17 -13
- package/packages/codex-adapter/src/renderer/environment.mjs +4 -1
- package/packages/codex-adapter/src/renderer/modules.mjs +66 -0
- package/packages/runtime-host/src/codex-injection.mjs +7 -17
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
import {readFile} from 'node:fs/promises';
|
|
2
3
|
import {loadComposerExpandControls,createComposerExpandAdapter} from './native.mjs';
|
|
3
4
|
import {composerCanExpand,installComposerExpand} from './install.mjs';
|
|
4
5
|
export async function createPageScriptSource(){
|
|
5
|
-
return [loadComposerExpandControls,createComposerExpandAdapter,composerCanExpand,installComposerExpand]
|
|
6
|
+
return [findCodexModule, loadComposerExpandControls,createComposerExpandAdapter,composerCanExpand,installComposerExpand]
|
|
6
7
|
.map(fn=>`const ${fn.name}=${fn.toString()};`).join('\n')+'\n'+await readFile(new URL('./entry.mjs',import.meta.url),'utf8');
|
|
7
8
|
}
|
|
@@ -1,26 +1,14 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
export async function loadComposerExpandControls(signal) {
|
|
2
3
|
signal.throwIfAborted();
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const mainUrl = urls.find(url => /\/app-main-[^/]+\.js/.test(url));
|
|
7
|
-
return initialUrl && mainUrl ? {initialUrl,mainUrl} : null;
|
|
8
|
-
}
|
|
9
|
-
// 冷启动时入口标签可能晚于 DOMContentLoaded 挂载。
|
|
10
|
-
const {initialUrl,mainUrl} = modules() ?? await new Promise((resolve,reject) => {
|
|
11
|
-
const observer = new MutationObserver(check);
|
|
12
|
-
function cleanup() { observer.disconnect();signal.removeEventListener('abort',abort); }
|
|
13
|
-
function abort() { cleanup();reject(signal.reason); }
|
|
14
|
-
function check() { const found=modules();if(found){cleanup();resolve(found);} }
|
|
15
|
-
observer.observe(document.documentElement,{subtree:true,childList:true,attributes:true,attributeFilter:['src','href','rel']});
|
|
16
|
-
signal.addEventListener('abort',abort,{once:true});
|
|
17
|
-
check();
|
|
18
|
-
});
|
|
4
|
+
const initialUrl=await findCodexModule('app-initial',{signal,waitForEntry:true});
|
|
5
|
+
const mainUrl=await findCodexModule('app-main',{signal,waitForEntry:true});
|
|
6
|
+
if (!initialUrl || !mainUrl) throw new Error('composer-expand-modules-unavailable');
|
|
19
7
|
const read = async url => { const response=await fetch(url,{signal}); if(!response.ok) throw new Error('composer-expand-source-unavailable'); return response.text(); };
|
|
20
8
|
const [initialSource,mainSource] = await Promise.all([read(initialUrl),read(mainUrl)]);
|
|
21
9
|
const file = initialSource.match(/["'`]\.\/(app-primary-[\w-]+\.js)["'`]/)?.[1];
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
const primaryUrl = file ? new URL(file,initialUrl).href : await findCodexModule('app-primary',{read,signal});
|
|
11
|
+
if (!primaryUrl) throw new Error('composer-expand-primary-unavailable');
|
|
24
12
|
const source = await read(primaryUrl);
|
|
25
13
|
async function binding(source,url,local) {
|
|
26
14
|
if (!local) throw new Error('composer-expand-binding-unavailable');
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// 新版将文件树和目录读取合入入口;仍按调用角色绑定,不执行无关初始化器。
|
|
2
|
+
export async function loadInlineFileNavigationModules(initialUrl, mainUrl, read, load) {
|
|
3
|
+
const [source,main]=await Promise.all([read(initialUrl),read(mainUrl)]);
|
|
4
|
+
const aliases=text=>new Map([...text.slice(text.lastIndexOf('export{')+7).split('}')[0].split(',')]
|
|
5
|
+
.map(entry=>{const [local,alias=local]=entry.trim().split(/\s+as\s+/);return [local,alias];}));
|
|
6
|
+
const imports=(text,url)=>new Map([...text.matchAll(/import\{([^}]+)\}from["']([^"']+)["']/g)]
|
|
7
|
+
.flatMap(match=>match[1].split(',').map(entry=>{const parts=entry.trim().split(/\s+as\s+/);return [parts.at(-1),{name:parts[0],url:new URL(match[2],url).href}];})));
|
|
8
|
+
const exported=aliases(source),dependencies=imports(source,initialUrl),module=await load(initialUrl);
|
|
9
|
+
const binding=async local=>{
|
|
10
|
+
if (exported.has(local)) return module[exported.get(local)];
|
|
11
|
+
const value=dependencies.get(local);
|
|
12
|
+
if(!value)throw new Error('file-navigation-inline-dependency-unavailable');
|
|
13
|
+
return (await load(value.url))[value.name];
|
|
14
|
+
};
|
|
15
|
+
const functions=[...source.matchAll(/function ([\w$]+)\(/g)];
|
|
16
|
+
const bodies=functions.map((match,index)=>({name:match[1],body:source.slice(match.index,functions[index+1]?.index??source.length)}));
|
|
17
|
+
const trees=bodies.filter(({name,body})=>exported.has(name)&&/contextMenuEnabled:/.test(body)&&/initialExpandedPaths:/.test(body)&&/onSelectionChange:/.test(body));
|
|
18
|
+
if(trees.length!==1)throw new Error('file-navigation-inline-tree-unavailable');
|
|
19
|
+
const tree=trees[0],cache=tree.body.match(/\(0,([\w$]+)\.c\)/)?.[1];
|
|
20
|
+
if(cache) {
|
|
21
|
+
const assignment=source.indexOf(`${cache}=`),before=source.slice(source.lastIndexOf('function ',assignment),assignment);
|
|
22
|
+
const initializer=before.match(/^function ([\w$]+)\(\)\{return\(\1=/)?.[1];
|
|
23
|
+
if(!initializer || typeof await binding(initializer)!=='function')throw new Error('file-navigation-inline-initializer-unavailable');
|
|
24
|
+
(await binding(initializer))();
|
|
25
|
+
}
|
|
26
|
+
const Tree=await binding(tree.name);
|
|
27
|
+
const reactOwner=tree.body.match(/\(0,([\w$]+)\.useState\)/)?.[1];
|
|
28
|
+
const reactFactory=reactOwner && source.match(new RegExp(`${reactOwner.replace(/\$/g,'\\$')}=([\\w$]+)\\(\\)`))?.[1];
|
|
29
|
+
const rootOwner=main.match(/\(0,([\w$]+)\.createRoot\)/)?.[1];
|
|
30
|
+
const rootFactory=rootOwner && main.match(new RegExp(`${rootOwner.replace(/\$/g,'\\$')}=([\\w$]+)\\(\\)`))?.[1];
|
|
31
|
+
const rootImport=imports(main,mainUrl).get(rootFactory);
|
|
32
|
+
if(!rootImport || !reactFactory)throw new Error('file-navigation-inline-react-unavailable');
|
|
33
|
+
const React=(await binding(reactFactory))(),ReactDOM=(await load(rootImport.url))[rootImport.name]();
|
|
34
|
+
const requests=bodies.filter(({body})=>body.includes('.workspaceFiles')&&body.includes('.listDirectory(')&&body.includes('.throwIfAborted('));
|
|
35
|
+
if(requests.length!==1)throw new Error('file-navigation-inline-directory-unavailable');
|
|
36
|
+
const requestBody=requests[0].body;
|
|
37
|
+
const host=requestBody.match(/let [\w$]+=([\w$]+)\.workspaceFiles/)?.[1];
|
|
38
|
+
const cancellable=requestBody.match(/return ([\w$]+)\([\w$]+\.listDirectory\(/)?.[1];
|
|
39
|
+
const bridge=await binding(host),withSignal=await binding(cancellable);
|
|
40
|
+
if(typeof bridge?.workspaceFiles?.listDirectory!=='function'||typeof withSignal!=='function')throw new Error('file-navigation-inline-directory-unavailable');
|
|
41
|
+
const request=(_method,{params,signal})=>{signal?.throwIfAborted();return withSignal(bridge.workspaceFiles.listDirectory(params),signal);};
|
|
42
|
+
const sharedUrl=[...dependencies.values()].find(value=>/\/app-shared-/.test(value.url))?.url;
|
|
43
|
+
if(!sharedUrl)throw new Error('file-navigation-inline-icons-unavailable');
|
|
44
|
+
const sharedSource=await read(sharedUrl),shared=await load(sharedUrl),sharedExports=aliases(sharedSource);
|
|
45
|
+
const folderAt=sharedSource.indexOf('name:`folder-light-16`'),prefix=sharedSource.slice(sharedSource.lastIndexOf('function ',folderAt),folderAt);
|
|
46
|
+
const folderInit=prefix.match(/^function ([\w$]+)\(\)\{return\(\1=/)?.[1];
|
|
47
|
+
if(folderAt<0||typeof shared[sharedExports.get(folderInit)]!=='function')throw new Error('file-navigation-folder-icon-unavailable');
|
|
48
|
+
shared[sharedExports.get(folderInit)]();
|
|
49
|
+
const folder=Object.values(shared).find(value=>value?.name==='folder-light-16');
|
|
50
|
+
if(typeof Tree!=='function')throw new Error('file-navigation-inline-tree-unavailable');
|
|
51
|
+
if(!React?.createElement||!ReactDOM?.createRoot)throw new Error('file-navigation-inline-react-unavailable');
|
|
52
|
+
if(!folder?.canvas?.viewBox||typeof folder.body!=='string')throw new Error('file-navigation-folder-icon-unavailable');
|
|
53
|
+
return {Tree,React,ReactDOM,request,folder};
|
|
54
|
+
}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
+
import { loadInlineFileNavigationModules } from './inline-modules.mjs';
|
|
2
|
+
import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
3
|
// 依照原生 breadcrumb 的依赖角色查找公开模块,不绑定产物 hash 或短变量名。
|
|
2
4
|
export async function loadFileNavigationModules({read = async url => { const response = await fetch(url); if (!response.ok) throw new Error('file-navigation-source-unavailable'); return response.text(); }, load = url => import(url)} = {}) {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if (!
|
|
5
|
+
const [breadcrumbUrl,mainUrl]=await Promise.all([
|
|
6
|
+
findCodexModule('review-file-source-breadcrumb',{read}),findCodexModule('app-main',{read}),
|
|
7
|
+
]);
|
|
8
|
+
if (!mainUrl) throw new Error('file-navigation-modules-unavailable');
|
|
9
|
+
let resolved;
|
|
10
|
+
if (!breadcrumbUrl) {
|
|
11
|
+
const initialUrl=await findCodexModule('app-initial',{read});
|
|
12
|
+
if (!initialUrl) throw new Error('file-navigation-modules-unavailable');
|
|
13
|
+
resolved=await loadInlineFileNavigationModules(initialUrl,mainUrl,read,load);
|
|
14
|
+
}
|
|
15
|
+
if (!resolved) {
|
|
7
16
|
const imports = (source, url) => {
|
|
8
17
|
const result = new Map();
|
|
9
18
|
for (const match of source.matchAll(/import\{([^}]+)\}from["']([^"']+)["']/g))
|
|
@@ -51,6 +60,9 @@ export async function loadFileNavigationModules({read = async url => { const res
|
|
|
51
60
|
if (!folder?.canvas?.viewBox || typeof folder.body !== 'string')
|
|
52
61
|
throw new Error('file-navigation-folder-icon-unavailable');
|
|
53
62
|
// 分列导航没有纵向展开状态;通过原生样式入口使用固定的原生文件夹图标。
|
|
63
|
+
resolved={Tree,React,ReactDOM,request,folder};
|
|
64
|
+
}
|
|
65
|
+
const {Tree,React,ReactDOM,request,folder}=resolved;
|
|
54
66
|
const folderImage = `url("data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${folder.canvas.viewBox}">${folder.body}</svg>`)}")`;
|
|
55
67
|
const columnStyles = `
|
|
56
68
|
/* 原生树的逻辑焦点也响应鼠标;描边仅保留给键盘可见焦点。 */
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { loadInlineFileNavigationModules } from './adapters/codex/inline-modules.mjs';
|
|
2
|
+
import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
3
|
import { readFile } from 'node:fs/promises';
|
|
2
4
|
import { fileNavigationPath, selectFileNavigationDirectory } from './domain/navigation.mjs';
|
|
3
5
|
import { loadFileNavigationModules } from './adapters/codex/modules.mjs';
|
|
4
6
|
import { createFileNavigationAdapter } from './adapters/codex/navigation.mjs';
|
|
5
7
|
import { installFileNavigation } from './frontend/navigation.mjs';
|
|
6
8
|
export async function createPageScriptSource() {
|
|
7
|
-
const bindings = [fileNavigationPath, selectFileNavigationDirectory, loadFileNavigationModules, createFileNavigationAdapter, installFileNavigation];
|
|
9
|
+
const bindings = [loadInlineFileNavigationModules,findCodexModule, fileNavigationPath, selectFileNavigationDirectory, loadFileNavigationModules, createFileNavigationAdapter, installFileNavigation];
|
|
8
10
|
return bindings.map(fn => `const ${fn.name} = ${fn.toString()};`).join('\n') + '\n' + await readFile(new URL('./entry.mjs', import.meta.url), 'utf8');
|
|
9
11
|
}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
// 唯一的 Library 构建产物协议;调用方只消费命名操作。
|
|
2
3
|
export function createLibraryApi({read:readSource=async url=>(await fetch(url)).text(),load:loadModule=url=>import(url)} = {}) {
|
|
3
4
|
let capability=null, downloadCapability=null;
|
|
4
5
|
async function connect() {
|
|
5
6
|
if (!capability) capability=(async()=>{
|
|
6
|
-
const initialUrl =
|
|
7
|
-
.map(element => element.href || element.src).find(url => /\/app-initial-[^/]+\.js/.test(url));
|
|
7
|
+
const initialUrl = await findCodexModule('app-initial',{read:readSource});
|
|
8
8
|
if (!initialUrl) throw new Error("Cloud Library is unavailable in this Codex version");
|
|
9
9
|
const initialSource = await readSource(initialUrl);
|
|
10
|
-
const asset = name => {
|
|
10
|
+
const asset = async name => {
|
|
11
11
|
const file = initialSource.match(new RegExp(`${name}-[\\w-]+\\.js`))?.[0];
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const url=file ? new URL(file, initialUrl).href : await findCodexModule(name,{read:readSource,via:['library-page']});
|
|
13
|
+
if (!url) throw new Error(`Cloud Library capability ${name} is unavailable`);
|
|
14
|
+
return url;
|
|
14
15
|
};
|
|
15
|
-
const metadataUrl = asset("library-file-metadata");
|
|
16
|
+
const metadataUrl = await asset("library-file-metadata");
|
|
16
17
|
const metadataSource = await readSource(metadataUrl);
|
|
17
18
|
const apiLocal = metadataSource.match(/([\w$]+)\.safePost\(`\/files\/library`/)?.[1];
|
|
18
19
|
const importBlock = [...metadataSource.matchAll(/import\{([^}]+)\}from["']([^"']+)["']/g)].find(match => match[1].split(",").some(binding=>binding.trim().split(/\s+as\s+/).at(-1)===apiLocal));
|
|
@@ -33,7 +34,7 @@ export function createLibraryApi({read:readSource=async url=>(await fetch(url)).
|
|
|
33
34
|
async function download(file,signal) {
|
|
34
35
|
if (!downloadCapability) downloadCapability=(async()=>{
|
|
35
36
|
const {asset}=await connect();
|
|
36
|
-
const downloadUrl = asset("library-file-download");
|
|
37
|
+
const downloadUrl = await asset("library-file-download");
|
|
37
38
|
const downloadSource = await readSource(downloadUrl);
|
|
38
39
|
const exports = downloadSource.match(/export\{([^}]+)\}/)?.[1].split(",").map(s => s.trim().split(/\s+as\s+/)) ?? [];
|
|
39
40
|
const responseLocal = downloadSource.match(/async function ([\w$]+)\([^)]*\)\{return [\w$]+\(await [\w$]+\([^;]+showInline:!0/)?.[1];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
// 以调用位置和参数结构识别角色,不绑定压缩后的局部变量名。
|
|
2
3
|
export function discoverImageActionBinding(source, role) {
|
|
3
4
|
const patterns = {
|
|
@@ -53,6 +54,18 @@ export function discoverFilePreviewComposerBindings(source) {
|
|
|
53
54
|
return roles;
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
export function discoverImageCanvasComponents(canvas,layout) {
|
|
58
|
+
const select=(exports,predicate,name)=>{
|
|
59
|
+
const matches=Object.values(exports).filter(value=>typeof value==='function'&&predicate(String(value)));
|
|
60
|
+
if(matches.length!==1)throw new Error(`Native Images canvas role unavailable: ${name}`);
|
|
61
|
+
return matches[0];
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
Canvas:select(canvas,source=>/attachmentSrc:/.test(source)&&/onImageEditSubmit:/.test(source)&&/downloadSrc:/.test(source),'image preview'),
|
|
65
|
+
CanvasLayout:select(layout,source=>/reserveImageComposerSpace:/.test(source)&&/contentOwnsHeader:/.test(source),'preview layout'),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
56
69
|
// 构建产物发现只返回当前能力;原生组件呈现不参与引用解析。
|
|
57
70
|
export function createNativeImageModules({read = async url => (await fetch(url)).text(), load = url => import(url)} = {}) {
|
|
58
71
|
let starting, connecting, canvasConnecting;
|
|
@@ -68,29 +81,27 @@ export function createNativeImageModules({read = async url => (await fetch(url))
|
|
|
68
81
|
|
|
69
82
|
async function bootstrap() {
|
|
70
83
|
if (!starting) starting = (async () => {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
const [initialUrl,mainUrl]=await Promise.all([
|
|
85
|
+
findCodexModule('app-initial',{read}),findCodexModule('app-main',{read}),
|
|
86
|
+
]);
|
|
74
87
|
if (!initialUrl || !mainUrl) throw new Error("Native Images modules are unavailable");
|
|
75
88
|
const [initialSource, mainSource] = await Promise.all([read(initialUrl), read(mainUrl)]);
|
|
76
89
|
const initial = await load(initialUrl);
|
|
77
|
-
const asset = (name, references = initialSource) => {
|
|
90
|
+
const asset = async (name, references = initialSource) => {
|
|
78
91
|
const loaded = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')].map(node => node.href || node.src)
|
|
79
92
|
.find(url => new RegExp(`/${name}-[\\w-]+\\.js$`).test(url));
|
|
80
93
|
if (loaded) return loaded;
|
|
81
94
|
const file = references.match(new RegExp(`(?:["'./])(${name}-[\\w-]+\\.js)`))?.[1];
|
|
82
|
-
|
|
83
|
-
return
|
|
95
|
+
const url=file ? new URL(file, initialUrl).href : await findCodexModule(name,{read,via:['library-page','library-trash-banner','library-item-menu','pinned-pages','recent-pages','space-trash-items','tab','cloud-file-preview.electron','library-marquee-selection','preview-tab','preview']});
|
|
96
|
+
return url;
|
|
84
97
|
};
|
|
85
98
|
const module = async (name,references) => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
&& !new RegExp(`(?:["'./])(${name}-[\\w-]+\\.js)`).test(references ?? initialSource)) {
|
|
91
|
-
return {url:initialUrl, source:initialSource, exports:initial};
|
|
99
|
+
const url = await asset(name,references);
|
|
100
|
+
// 原生曾将这两个角色合入入口;先按实际资源引用查找,再使用已验证的内联分支。
|
|
101
|
+
if (!url && ['library-masonry','library-item-layout'].includes(name)) {
|
|
102
|
+
return {url:initialUrl,source:initialSource,exports:initial};
|
|
92
103
|
}
|
|
93
|
-
|
|
104
|
+
if (!url) throw new Error(`Native Images capability is unavailable: ${name}`);
|
|
94
105
|
const [source, exports] = await Promise.all([read(url), load(url)]);
|
|
95
106
|
// 调用产物公开的惰性初始化器,不扫描或执行任意导出函数。
|
|
96
107
|
for (const value of Object.values(exports)) {
|
|
@@ -233,14 +244,15 @@ export function createNativeImageModules({read = async url => (await fetch(url))
|
|
|
233
244
|
// 文件画布使用原生 Home Composer 的控制器、附件 scope 和输入组件。
|
|
234
245
|
const composerLocals=Object.values(discoverFilePreviewComposerBindings(filePreview.source));
|
|
235
246
|
const [useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope,FileComposer,fileComposerAvailability,localAttachmentService,deleteLocalAttachment,useFileNavigate,localThreadRoute] = await Promise.all(composerLocals.map(local=>binding(filePreview.source,filePreview.url,local)));
|
|
236
|
-
|
|
247
|
+
const {Canvas,CanvasLayout}=discoverImageCanvasComponents(canvas.exports,canvasLayout.exports);
|
|
248
|
+
if (!Canvas || !CanvasLayout || !Composer || !ComposerSurface || !ScopeProvider
|
|
237
249
|
|| !composerScope || !composerRouteScope?.parent
|
|
238
250
|
|| !FileComposer || !fileComposerAvailability
|
|
239
251
|
|| [createPortal,useComposerController,useHomeComposer,createComposerValue,localAttachmentService,deleteLocalAttachment,useFileNavigate,localThreadRoute].some(value=>typeof value!=="function")) {
|
|
240
252
|
throw new Error("Native Images canvas contract changed");
|
|
241
253
|
}
|
|
242
254
|
return {
|
|
243
|
-
createPortal,Canvas
|
|
255
|
+
createPortal,Canvas,CanvasLayout,
|
|
244
256
|
useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope,
|
|
245
257
|
FileComposer,fileComposerAvailability,localAttachmentService,deleteLocalAttachment,useFileNavigate,localThreadRoute,
|
|
246
258
|
editImage:component(initial,source=>/async function/.test(source)&&/requireDispatchAcceptance:/.test(source)&&/sourceAttachment:/.test(source)&&/imageVariant/.test(source),"image edit submission"),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
import { cloudImagesInRenderer } from "../adapters/codex/cloud-import.mjs";
|
|
2
3
|
import { createLibraryApi } from "../adapters/codex/library-api.mjs";
|
|
3
4
|
import { promptResolverSource } from "../adapters/codex/prompt-resolver-source.mjs";
|
|
@@ -6,5 +7,6 @@ export async function cloudImagesAction(evaluate, operation, payload = {}) {
|
|
|
6
7
|
if (!["list", "import"].includes(operation)) throw new Error("Unknown cloud image operation");
|
|
7
8
|
if (payload.cursor != null && (typeof payload.cursor !== "string" || payload.cursor.length > 4096)) throw new Error("Invalid Library cursor");
|
|
8
9
|
if (operation === "import" && (typeof payload.libraryId !== "string" || !/^libfile_[\w-]+$/.test(payload.libraryId))) throw new Error("Invalid Library file ID");
|
|
9
|
-
return evaluate(`(
|
|
10
|
+
return evaluate(`(async()=>{const findCodexModule=${findCodexModule.toString()};
|
|
11
|
+
return (${cloudImagesInRenderer.toString()})(${JSON.stringify(operation)},${JSON.stringify(payload)},${promptResolverSource()},${createLibraryApi.toString()});})()`, { timeoutMs: 110_000 });
|
|
10
12
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
import { createCloudImageActions } from "./frontend/cloud-image-actions.mjs";
|
|
2
3
|
import { createImageSourcesAdapter } from "./adapters/codex/image-sources.mjs";
|
|
3
4
|
import { createNativeImageSource } from "./adapters/codex/native-image-source.mjs";
|
|
4
|
-
import {createNativeImageModules, discoverImageActionBinding, discoverFilePreviewComposerBindings} from "./adapters/codex/native-image-modules.mjs";
|
|
5
|
+
import {createNativeImageModules, discoverImageCanvasComponents, discoverImageActionBinding, discoverFilePreviewComposerBindings} from "./adapters/codex/native-image-modules.mjs";
|
|
5
6
|
import { installLocalImageSource } from "./frontend/local-image-source.mjs";
|
|
6
7
|
import { createLibraryApi } from "./adapters/codex/library-api.mjs";
|
|
7
8
|
import { createLibraryViewerAdapter } from "./adapters/codex/library-viewer.mjs";
|
|
@@ -11,7 +12,7 @@ import { promptResolverSource } from "./adapters/codex/prompt-resolver-source.mj
|
|
|
11
12
|
export async function createPageScriptSource() {
|
|
12
13
|
const source=await readFile(new URL("./library-prompt-page.mjs",import.meta.url),"utf8");
|
|
13
14
|
const promptGlyphSource=await readFile(new URL("./assets/prompts-menu.svg",import.meta.url),"utf8");
|
|
14
|
-
const functions={createCloudImageActions,createImageSourcesAdapter,discoverImageActionBinding,discoverFilePreviewComposerBindings,createNativeImageModules,createNativeImageSource,installLocalImageSource,createLibraryApi,createLibraryViewerAdapter};
|
|
15
|
+
const functions={findCodexModule,discoverImageCanvasComponents,createCloudImageActions,createImageSourcesAdapter,discoverImageActionBinding,discoverFilePreviewComposerBindings,createNativeImageModules,createNativeImageSource,installLocalImageSource,createLibraryApi,createLibraryViewerAdapter};
|
|
15
16
|
const declarations=Object.entries(functions).map(([name,fn])=>`const ${name}=${fn.toString()};`).join("\n");
|
|
16
17
|
return `${declarations}\nconst promptGlyphSource=${JSON.stringify(promptGlyphSource)};\nconst promptFromConversation=${promptResolverSource()};\n${source}`;
|
|
17
18
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
// 修补原生 Preview 的链接装饰;源码、语法树与编辑器实例继续归 Codex。
|
|
2
3
|
export async function loadNativeMarkdownLinks() {
|
|
3
|
-
const url = [
|
|
4
|
-
.map((n) => n.href)
|
|
5
|
-
.find((u) => /\/text-file-editor-extensions-[^/]+\.js$/.test(u));
|
|
4
|
+
const url = await findCodexModule('text-file-editor-extensions',{via:['text-file-editor-tab-content.electron']});
|
|
6
5
|
if (!url) throw new Error('native-markdown-link-module-unavailable');
|
|
7
6
|
const source = await (await fetch(url)).text();
|
|
8
7
|
const opener = source.match(/([\w$]+)\(\{event:[\w$]+,href:[\w$]+,initiator:`markdown_link_click`/)?.[1];
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
import { collectMarkdownFootnotes } from '../../domain/footnotes.mjs';
|
|
2
3
|
import { captureMarkdownProviders } from '../../../../../packages/codex-adapter/src/markdown/providers.mjs';
|
|
3
4
|
|
|
4
5
|
// 复用消息流的代码组件与 Markdown lexer;文件内容及编辑器仍由 Codex 持有。
|
|
5
6
|
export async function loadNativeMarkdownRichText(native) {
|
|
6
|
-
const links = [...document.querySelectorAll('link[rel="modulepreload"]')].map((n) => n.href);
|
|
7
7
|
const { sharedUrl } = native;
|
|
8
8
|
const sharedSource = await (await fetch(sharedUrl)).text(),
|
|
9
9
|
shared = await import(sharedUrl);
|
|
@@ -37,16 +37,17 @@ export async function loadNativeMarkdownRichText(native) {
|
|
|
37
37
|
if (!codeInit || !sharedBinding(codeInit) || !sharedBinding(codeName))
|
|
38
38
|
throw new Error('native-code-snippet-unavailable');
|
|
39
39
|
sharedBinding(codeInit)();
|
|
40
|
-
let mathUrl =
|
|
40
|
+
let mathUrl = await findCodexModule('katex');
|
|
41
41
|
if (!mathUrl) {
|
|
42
|
-
const editorUrl =
|
|
42
|
+
const editorUrl = await findCodexModule('text-file-editor-extensions',{via:['text-file-editor-tab-content.electron']});
|
|
43
43
|
if (editorUrl) {
|
|
44
44
|
const editorSource = await (await fetch(editorUrl)).text(),
|
|
45
45
|
specifier = editorSource.match(/import\(`(\.\/katex-[^`]+\.js)`\)/)?.[1];
|
|
46
46
|
if (specifier) mathUrl = new URL(specifier, editorUrl).href;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
const
|
|
49
|
+
const mathModule=mathUrl ? await import(mathUrl) : null;
|
|
50
|
+
const katex=mathModule?.default ?? mathModule?.katex;
|
|
50
51
|
if (!katex?.renderToString) throw new Error('native-katex-unavailable');
|
|
51
52
|
return { ...native, CodeSnippet: sharedBinding(codeName), katex };
|
|
52
53
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findCodexModule, codexModuleStyles } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
import { loadMarkdownListAPI } from '../../../packages/codex-adapter/src/markdown/editor-api.mjs';
|
|
2
3
|
import { markdownExportAlias, loadNativeMarkdownTableActions } from '../../../packages/codex-adapter/src/markdown/block-actions.mjs';
|
|
3
4
|
import { loadNativeMarkdownRenderer } from '../../../packages/codex-adapter/src/markdown/markdown-native.mjs';
|
|
@@ -18,7 +19,7 @@ import { installMarkdownTables, markdownTableActionGeometry } from './adapters/c
|
|
|
18
19
|
import { installMarkdownLists } from './adapters/codex/lists.mjs';
|
|
19
20
|
import { createMarkdownPreviewAdapter } from '../../../packages/codex-adapter/src/markdown/preview.mjs';
|
|
20
21
|
export async function createPageScriptSource() {
|
|
21
|
-
const bindings = [
|
|
22
|
+
const bindings = [findCodexModule, codexModuleStyles,
|
|
22
23
|
collectMarkdownFootnotes,
|
|
23
24
|
captureMarkdownProviders,
|
|
24
25
|
createMarkdownReadingRuntime,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
import { saveQvDiagram } from '../../mermaid/export.mjs';
|
|
2
3
|
|
|
3
4
|
// 图表下载协议只由实验性功能加载,阅读增强不依赖此能力。
|
|
4
5
|
export async function loadNativeMermaidActions(native, {read = async url => { const response = await fetch(url); if (!response.ok) throw new Error('native-diagram-source-unavailable'); return response.text(); }, load = url => import(url)} = {}) {
|
|
5
|
-
const url =
|
|
6
|
-
|
|
6
|
+
const url = await findCodexModule('app-initial',{read});
|
|
7
|
+
if (!url) throw new Error('native-diagram-module-unavailable');
|
|
7
8
|
const source = await read(url);
|
|
8
9
|
const section = marker => {
|
|
9
10
|
const at = source.indexOf(marker);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findCodexModule, codexModuleStyles } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
import { readFile } from 'node:fs/promises';
|
|
2
3
|
import { homedir } from 'node:os';
|
|
3
4
|
import { join } from 'node:path';
|
|
@@ -16,7 +17,7 @@ import { loadNativeMermaidActions } from './adapters/codex/native.mjs';
|
|
|
16
17
|
import { createMermaidRuntime } from './adapters/codex/runtime.mjs';
|
|
17
18
|
|
|
18
19
|
export async function createPageScriptSource() {
|
|
19
|
-
const bindings = [createMarkdownPreviewAdapter, loadMarkdownListAPI, captureMarkdownProviders,
|
|
20
|
+
const bindings = [findCodexModule, codexModuleStyles, createMarkdownPreviewAdapter, loadMarkdownListAPI, captureMarkdownProviders,
|
|
20
21
|
loadNativeMarkdownToc, loadNativeMarkdownRenderer, markdownExportAlias, loadNativeMarkdownTableActions,
|
|
21
22
|
createQvResources, createQvFrame, readQvTheme, exportQvDiagram, saveQvDiagram, createQvLoading,
|
|
22
23
|
createMarkdownMermaidControls, installMarkdownMermaid, loadNativeMermaidActions, createMermaidRuntime];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
export function createModelControllerAdapter(codex,comparableModelLabel) {
|
|
2
3
|
function controllerView(props) {const view=Object.fromEntries(["models","model","reasoningEffort","powerSelections","selectedPowerSelection","fallbackPowerSelection","serviceTierOptions","selectedServiceTier","onSelectModel","onSelectReasoningEffort","onSelectPower","onCommitPower","onSelectServiceTier"].map(key=>[key,props[key]]));codex.report('model-controller',true);return view;}
|
|
3
4
|
function nativeComposerTriggers() {
|
|
@@ -83,10 +84,9 @@ function invokeNativeMenuItemSelect(item) {
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
async function fastIcons(signal) {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const source=await (await fetch(sourceLink.href,{signal})).text();
|
|
87
|
+
const sourceUrl=await findCodexModule('app-primary',{signal}) ?? await findCodexModule('app-initial',{signal});
|
|
88
|
+
if (!sourceUrl) {codex.report("fast-icons",false,"bundle-unavailable");return null;}
|
|
89
|
+
const source=await (await fetch(sourceUrl,{signal})).text();
|
|
90
90
|
const active=extractFastIconPath(source,"M11\\.9125 21\\.4125"),inactive=extractFastIconPath(source,"M9\\.80999 17\\.8302");
|
|
91
91
|
codex.report("fast-icons",Boolean(active && inactive),active && inactive ? null : "icon-resource-contract-changed");
|
|
92
92
|
return {active:{path:active,viewBox:"0 0 24 24"},inactive:{path:inactive,viewBox:"0 0 20 20"}};
|
|
@@ -18,6 +18,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
18
18
|
const modelSelectorNativeToggleHandlers = new Map();
|
|
19
19
|
const modelSelectorPositionObservers = new Map();
|
|
20
20
|
const modelSelectorMenuSizeFrames = new Map();
|
|
21
|
+
const modelSelectorOpeningFrames = new Map();
|
|
21
22
|
let modelSelectorReturnTimer = null;
|
|
22
23
|
let modelSelectorPendingReturn = false;
|
|
23
24
|
let modelSelectorEffortTransactionTimer = null;
|
|
@@ -91,6 +92,16 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
91
92
|
[data-codex-model-slider-trigger][data-codex-model-slider-trigger-placeholder] [class*="_ModelPickerTriggerContent_"] {
|
|
92
93
|
width: 160px; min-width: 160px !important; max-width: 160px;
|
|
93
94
|
}
|
|
95
|
+
/* 打开时先完成占位态,避免原生 crossfade 的宽度动画带动弹层。 */
|
|
96
|
+
[data-codex-model-slider-trigger][data-state="open"][data-codex-model-slider-trigger-placeholder] [class*="_ModelPickerTriggerContent_"] > span:has(> span > [class*="_ModelPickerTriggerPlaceholder_"]) {
|
|
97
|
+
width: 160px !important;
|
|
98
|
+
}
|
|
99
|
+
[data-codex-model-slider-trigger][data-state="open"][data-codex-model-slider-trigger-placeholder] span:has(> [class*="_ModelPickerTriggerPlaceholder_"]) {
|
|
100
|
+
opacity: 1 !important;
|
|
101
|
+
}
|
|
102
|
+
[data-codex-model-slider-trigger][data-state="open"][data-codex-model-slider-trigger-placeholder] [class*="_ModelPickerTriggerLabel_"] {
|
|
103
|
+
visibility: hidden;
|
|
104
|
+
}
|
|
94
105
|
[data-codex-model-slider-trigger] [class*="_ModelPickerTriggerEffortLabel_"] {
|
|
95
106
|
display: inline-flex; align-items: center; align-self: center;
|
|
96
107
|
}
|
|
@@ -126,6 +137,11 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
126
137
|
[data-codex-model-slider-generic-menu] {
|
|
127
138
|
overflow-x: hidden;
|
|
128
139
|
transform: translateX(var(--codex-model-slider-menu-center-shift, 0px)) !important;
|
|
140
|
+
/* 居中修正不能继承原生 transform 过渡,否则定位本身会播放位移。 */
|
|
141
|
+
transition-property: opacity, filter !important;
|
|
142
|
+
}
|
|
143
|
+
html[data-codex-model-slider-catalog-ready] [role="menu"][data-state="open"]:has([data-reasoning-slider]):not([data-codex-model-slider-opening-ready]) {
|
|
144
|
+
visibility: hidden !important;
|
|
129
145
|
}
|
|
130
146
|
[data-codex-model-slider-generic] { box-sizing: border-box; width: 100%; }
|
|
131
147
|
[data-codex-model-slider-fast] { border: 0; background: transparent; cursor: pointer; }
|
|
@@ -991,11 +1007,13 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
991
1007
|
const original = modelSelectorGenericContainers.get(container);
|
|
992
1008
|
if (!original) return;
|
|
993
1009
|
original.effectMirror?.disconnect();
|
|
1010
|
+
original.positionObserver?.disconnect();
|
|
994
1011
|
if (container.isConnected) {
|
|
995
1012
|
const menu = container.parentElement;
|
|
996
1013
|
menu?.querySelector(":scope > [data-codex-model-slider-generic]")?.remove();
|
|
997
1014
|
menu?.removeAttribute("data-codex-model-slider-generic-menu");
|
|
998
1015
|
menu?.style.removeProperty("--codex-model-slider-menu-center-shift");
|
|
1016
|
+
menu?.removeAttribute("data-codex-model-slider-opening-ready");
|
|
999
1017
|
container.style.position = original.position;
|
|
1000
1018
|
container.style.inset = original.inset;
|
|
1001
1019
|
container.style.opacity = original.opacity;
|
|
@@ -1095,11 +1113,10 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
1095
1113
|
if (!record) return false;
|
|
1096
1114
|
if (record.closing && !immediate) return true;
|
|
1097
1115
|
record.reveal?.cancel();
|
|
1098
|
-
const placeholder =
|
|
1099
|
-
|
|
1100
|
-
)
|
|
1101
|
-
|
|
1102
|
-
placeholder.firstChild.nodeValue = record.placeholderText;
|
|
1116
|
+
const placeholder = record.trigger?.querySelector('[class*="_ModelPickerTriggerPlaceholder_"]');
|
|
1117
|
+
const placeholderNode = placeholder && firstVisibleTextNode(placeholder);
|
|
1118
|
+
if (placeholderNode && record.placeholderText != null) {
|
|
1119
|
+
placeholderNode.nodeValue = record.placeholderText;
|
|
1103
1120
|
}
|
|
1104
1121
|
record.button.setAttribute("aria-expanded", "false");
|
|
1105
1122
|
record.closing = true;
|
|
@@ -1279,15 +1296,15 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
1279
1296
|
});
|
|
1280
1297
|
renderEnhancedModelMenu(menu, selector);
|
|
1281
1298
|
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1284
|
-
);
|
|
1285
|
-
const placeholderText =
|
|
1299
|
+
const trigger = nativeComposerTrigger();
|
|
1300
|
+
const placeholder = trigger?.querySelector('[class*="_ModelPickerTriggerPlaceholder_"]');
|
|
1301
|
+
const placeholderNode = placeholder && firstVisibleTextNode(placeholder);
|
|
1302
|
+
const placeholderText = placeholderNode?.nodeValue ?? null;
|
|
1286
1303
|
// 保留产品行为:模型列表打开时 chip 显示 Select model。只能原地更新
|
|
1287
1304
|
// 同一个文本节点(nodeValue),不能 textContent 替换整个节点,否则
|
|
1288
1305
|
// React 卸载菜单时 removeChild 找不到旧文本节点会触发错误边界。
|
|
1289
|
-
if (
|
|
1290
|
-
|
|
1306
|
+
if (placeholderNode) {
|
|
1307
|
+
placeholderNode.nodeValue = locale === "zh-CN" ? "选择模型" : "Select model";
|
|
1291
1308
|
}
|
|
1292
1309
|
|
|
1293
1310
|
modelSelectorControllerMenu = {
|
|
@@ -1295,6 +1312,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
1295
1312
|
content,
|
|
1296
1313
|
parentMenu,
|
|
1297
1314
|
button: modelButton,
|
|
1315
|
+
trigger,
|
|
1298
1316
|
placeholderText,
|
|
1299
1317
|
position: parentMenu.style.position,
|
|
1300
1318
|
overflow: parentMenu.style.overflow,
|
|
@@ -2000,11 +2018,42 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
2000
2018
|
const previousShift = Number.parseFloat(
|
|
2001
2019
|
parentMenu.style.getPropertyValue("--codex-model-slider-menu-center-shift"),
|
|
2002
2020
|
) || 0;
|
|
2003
|
-
const
|
|
2004
|
-
const
|
|
2021
|
+
const zoom = parentMenu.currentCSSZoom || 1;
|
|
2022
|
+
const unshiftedCenter = menuRect.left + (menuRect.width / 2) - previousShift * zoom;
|
|
2023
|
+
const nextShift = (triggerRect.left + (triggerRect.width / 2) - unshiftedCenter) / zoom;
|
|
2005
2024
|
parentMenu.style.setProperty("--codex-model-slider-menu-center-shift", `${nextShift}px`);
|
|
2006
2025
|
}
|
|
2007
2026
|
|
|
2027
|
+
function revealCenteredModelPicker(parentMenu) {
|
|
2028
|
+
if (parentMenu.hasAttribute("data-codex-model-slider-opening-ready")) {
|
|
2029
|
+
centerGenericModelPicker(parentMenu);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
parentMenu.style.visibility = "hidden";
|
|
2033
|
+
if (modelSelectorOpeningFrames.has(parentMenu)) return;
|
|
2034
|
+
let previous = null;
|
|
2035
|
+
const position = () => {
|
|
2036
|
+
modelSelectorOpeningFrames.delete(parentMenu);
|
|
2037
|
+
const trigger = nativeComposerTrigger();
|
|
2038
|
+
if (!parentMenu.isConnected || !trigger || signal.aborted) return;
|
|
2039
|
+
centerGenericModelPicker(parentMenu);
|
|
2040
|
+
const menuRect = parentMenu.getBoundingClientRect();
|
|
2041
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
2042
|
+
const geometry = [menuRect.left, menuRect.width, triggerRect.left, triggerRect.width];
|
|
2043
|
+
// 等原生 Popper 跟上入口宽度;首次可见的位置必须已经稳定且居中。
|
|
2044
|
+
if (menuRect.width > 0 && triggerRect.width > 0
|
|
2045
|
+
&& Math.abs(menuRect.left + menuRect.width / 2 - triggerRect.left - triggerRect.width / 2) < 1
|
|
2046
|
+
&& previous && geometry.every((value, index) => Math.abs(value - previous[index]) < 0.5)) {
|
|
2047
|
+
parentMenu.setAttribute("data-codex-model-slider-opening-ready", "");
|
|
2048
|
+
parentMenu.style.removeProperty("visibility");
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
previous = geometry;
|
|
2052
|
+
modelSelectorOpeningFrames.set(parentMenu, requestAnimationFrame(position));
|
|
2053
|
+
};
|
|
2054
|
+
modelSelectorOpeningFrames.set(parentMenu, requestAnimationFrame(position));
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2008
2057
|
// ========================================================================
|
|
2009
2058
|
// 通用 picker 增强与恢复 — 菜单/触发器增强、布局居中与恢复降级
|
|
2010
2059
|
// ========================================================================
|
|
@@ -2053,13 +2102,12 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
2053
2102
|
return true;
|
|
2054
2103
|
}
|
|
2055
2104
|
parentMenu.removeAttribute("data-codex-model-slider-template-wait");
|
|
2056
|
-
parentMenu.style.removeProperty("visibility");
|
|
2057
2105
|
const canUpgradeFallback = Boolean(
|
|
2058
2106
|
existing?.querySelector("[data-codex-model-slider-fallback]") && nativeTemplateReady,
|
|
2059
2107
|
);
|
|
2060
2108
|
if (existing?.getAttribute("data-codex-model-slider-structure-key") === structureKey
|
|
2061
2109
|
&& existing.hasAttribute("data-codex-model-slider-fast-transitioning") && !canUpgradeFallback) {
|
|
2062
|
-
|
|
2110
|
+
revealCenteredModelPicker(parentMenu);
|
|
2063
2111
|
return true;
|
|
2064
2112
|
}
|
|
2065
2113
|
const currentFast = existing?.querySelector("[data-codex-model-slider-rail]")?.getAttribute("data-fast-mode");
|
|
@@ -2068,11 +2116,11 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
2068
2116
|
if (existing?.getAttribute("data-codex-model-slider-structure-key") === structureKey
|
|
2069
2117
|
&& currentFast != null && currentFast === nativeFast && !canUpgradeFallback) {
|
|
2070
2118
|
existing.setAttribute("data-codex-model-slider-generic", key);
|
|
2071
|
-
|
|
2119
|
+
revealCenteredModelPicker(parentMenu);
|
|
2072
2120
|
return true;
|
|
2073
2121
|
}
|
|
2074
2122
|
if (existing?.getAttribute("data-codex-model-slider-generic") === key && !canUpgradeFallback) {
|
|
2075
|
-
|
|
2123
|
+
revealCenteredModelPicker(parentMenu);
|
|
2076
2124
|
return true;
|
|
2077
2125
|
}
|
|
2078
2126
|
const nativeContainer = parentMenu.firstElementChild;
|
|
@@ -2090,7 +2138,13 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
2090
2138
|
}
|
|
2091
2139
|
modelSelectorGenericContainers.get(nativeContainer)?.effectMirror?.disconnect();
|
|
2092
2140
|
if (!modelSelectorGenericContainers.has(nativeContainer)) {
|
|
2141
|
+
// Popper 会在后续帧改写 wrapper;同一轮微任务内重新居中,避免露出一帧旧位置。
|
|
2142
|
+
const positionObserver = new MutationObserver(() => centerGenericModelPicker(parentMenu));
|
|
2143
|
+
if (parentMenu.parentElement?.hasAttribute("data-radix-popper-content-wrapper")) {
|
|
2144
|
+
positionObserver.observe(parentMenu.parentElement, { attributes: true, attributeFilter: ["style"] });
|
|
2145
|
+
}
|
|
2093
2146
|
modelSelectorGenericContainers.set(nativeContainer, {
|
|
2147
|
+
positionObserver,
|
|
2094
2148
|
position: nativeContainer.style.position,
|
|
2095
2149
|
inset: nativeContainer.style.inset,
|
|
2096
2150
|
opacity: nativeContainer.style.opacity,
|
|
@@ -2110,12 +2164,14 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
2110
2164
|
shell.setAttribute("data-codex-model-slider-structure-key", structureKey);
|
|
2111
2165
|
replaceModelSliderShell(parentMenu, existing, shell);
|
|
2112
2166
|
modelSelectorGenericContainers.get(nativeContainer).effectMirror = syncClonedPowerEffects(nativeContainer, shell);
|
|
2113
|
-
|
|
2167
|
+
revealCenteredModelPicker(parentMenu);
|
|
2114
2168
|
return true;
|
|
2115
2169
|
}
|
|
2116
2170
|
|
|
2117
2171
|
function enhanceNativeModelPicker(parentMenu, selector) {
|
|
2118
2172
|
if (enhanceGenericModelPicker(parentMenu, selector)) return;
|
|
2173
|
+
parentMenu.setAttribute("data-codex-model-slider-opening-ready", "");
|
|
2174
|
+
parentMenu.style.removeProperty("visibility");
|
|
2119
2175
|
const toggle = parentMenu.querySelector("[data-model-picker-view-toggle]");
|
|
2120
2176
|
if (toggle && typeof nativeComposerModelController()?.onSelectModel === "function") {
|
|
2121
2177
|
// 私有 DOM 无法满足 MCX shell 契约时,保留原生菜单作为降级路径。
|
|
@@ -2171,10 +2227,20 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
2171
2227
|
|
|
2172
2228
|
function enhanceComposerModelTrigger(trigger, selector) {
|
|
2173
2229
|
if (!modelSelectorTriggerObservers.has(trigger)) {
|
|
2174
|
-
const observer = new MutationObserver(
|
|
2175
|
-
|
|
2230
|
+
const observer = new MutationObserver(() => {
|
|
2231
|
+
enhanceComposerModelTrigger(trigger, selector);
|
|
2232
|
+
queueEnsure();
|
|
2233
|
+
});
|
|
2234
|
+
observer.observe(trigger, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ["data-state"] });
|
|
2176
2235
|
modelSelectorTriggerObservers.set(trigger, observer);
|
|
2177
2236
|
}
|
|
2237
|
+
const openPlaceholder = trigger.getAttribute("data-state") === "open"
|
|
2238
|
+
&& trigger.querySelector('[class*="_ModelPickerTriggerPlaceholder_"]');
|
|
2239
|
+
if (openPlaceholder && modelSelectorControllerMenu?.trigger === trigger && !modelSelectorControllerMenu.closing) {
|
|
2240
|
+
const textNode = firstVisibleTextNode(openPlaceholder);
|
|
2241
|
+
const label = locale === "zh-CN" ? "选择模型" : "Select model";
|
|
2242
|
+
if (textNode && textNode.nodeValue !== label) textNode.nodeValue = label;
|
|
2243
|
+
}
|
|
2178
2244
|
const nativeLabels = Array.from(trigger.querySelectorAll(
|
|
2179
2245
|
'[class*="_ModelPickerTriggerModelText_"]:not([data-codex-model-slider-trigger-label])',
|
|
2180
2246
|
));
|
|
@@ -2182,7 +2248,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
2182
2248
|
trigger.setAttribute("data-codex-model-slider-trigger", "");
|
|
2183
2249
|
trigger.toggleAttribute(
|
|
2184
2250
|
"data-codex-model-slider-trigger-placeholder",
|
|
2185
|
-
isComposerModelPlaceholder(trigger.innerText),
|
|
2251
|
+
isComposerModelPlaceholder(openPlaceholder?.textContent || trigger.innerText),
|
|
2186
2252
|
);
|
|
2187
2253
|
restoreTriggerLabelWithoutNative(trigger, selector);
|
|
2188
2254
|
return;
|
|
@@ -2199,7 +2265,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
2199
2265
|
trigger.setAttribute("data-codex-model-slider-trigger-raw", raw);
|
|
2200
2266
|
// 区分占位态(Select model / Select effort)与真实模型态:占位态沿用
|
|
2201
2267
|
// 原生宽度契约,真实模型态让芯片 HUG 完整名称,避免第三方长名被截断。
|
|
2202
|
-
const placeholder = isComposerModelPlaceholder(label);
|
|
2268
|
+
const placeholder = isComposerModelPlaceholder(openPlaceholder?.textContent || label);
|
|
2203
2269
|
trigger.toggleAttribute("data-codex-model-slider-trigger-placeholder", placeholder);
|
|
2204
2270
|
for (const nativeLabel of nativeLabels) {
|
|
2205
2271
|
let displayLabel = nativeLabel.nextElementSibling;
|
|
@@ -2216,12 +2282,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
2216
2282
|
}
|
|
2217
2283
|
if (displayLabel.textContent !== label) displayLabel.textContent = label;
|
|
2218
2284
|
}
|
|
2219
|
-
//
|
|
2220
|
-
// 探针的测量;把探针文本同步为 MCX 显示标签,原生机制即可测得
|
|
2221
|
-
// 真实显示宽度。保留 React 持有的子节点,避免切模型时卸载失败。
|
|
2222
|
-
const measurement = trigger.querySelector('[class*="_ModelPickerTriggerMeasurement_"]');
|
|
2223
|
-
const measurementText = measurement && firstVisibleTextNode(measurement);
|
|
2224
|
-
if (measurementText && measurementText.nodeValue !== label) measurementText.nodeValue = label;
|
|
2285
|
+
// Measurement 属于原生 openLabel,占位宽度不能用真实模型名覆盖。
|
|
2225
2286
|
}
|
|
2226
2287
|
|
|
2227
2288
|
function ensureModelSelector() {
|
|
@@ -2284,6 +2345,11 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
2284
2345
|
modelSelectorPositionObservers.clear();
|
|
2285
2346
|
for (const frame of modelSelectorMenuSizeFrames.values()) cancelAnimationFrame(frame);
|
|
2286
2347
|
modelSelectorMenuSizeFrames.clear();
|
|
2348
|
+
for (const [menu, frame] of modelSelectorOpeningFrames) {
|
|
2349
|
+
cancelAnimationFrame(frame);
|
|
2350
|
+
menu.style.removeProperty("visibility");
|
|
2351
|
+
}
|
|
2352
|
+
modelSelectorOpeningFrames.clear();
|
|
2287
2353
|
modelSelectorReturnTimer = null;
|
|
2288
2354
|
modelSelectorEffortTransactionTimer = null;
|
|
2289
2355
|
modelSelectorPendingOutsideDismiss = false;
|
|
@@ -2318,6 +2384,8 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
|
|
|
2318
2384
|
document.querySelectorAll("[data-codex-model-slider-generic]").forEach((element) => element.remove());
|
|
2319
2385
|
document.querySelectorAll("[data-codex-model-slider-generic-menu]")
|
|
2320
2386
|
.forEach((element) => element.removeAttribute("data-codex-model-slider-generic-menu"));
|
|
2387
|
+
document.querySelectorAll("[data-codex-model-slider-opening-ready]")
|
|
2388
|
+
.forEach((element) => element.removeAttribute("data-codex-model-slider-opening-ready"));
|
|
2321
2389
|
document.querySelectorAll("[data-codex-model-slider-menu]")
|
|
2322
2390
|
.forEach((element) => element.removeAttribute("data-codex-model-slider-menu"));
|
|
2323
2391
|
document.querySelectorAll("[data-codex-model-slider-trigger]").forEach((element) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
import { createModelControllerAdapter } from "./adapters/codex/controller.mjs";
|
|
2
3
|
import { createModelRules } from "./domain/model-rules.mjs";
|
|
3
4
|
import { installModelSlider, replaceModelSliderShell } from "./frontend/install.mjs";
|
|
@@ -5,6 +6,7 @@ import { installModelSlider, replaceModelSliderShell } from "./frontend/install.
|
|
|
5
6
|
// 两部分均显式序列化,Renderer 不捕获 Node imports。
|
|
6
7
|
export function createModelSliderInstallerSource() {
|
|
7
8
|
return `function installModelSliderFeature(signal, context) {
|
|
9
|
+
const findCodexModule = ${findCodexModule.toString()};
|
|
8
10
|
const replaceModelSliderShell = ${replaceModelSliderShell.toString()};
|
|
9
11
|
return (${installModelSlider.toString()})(signal, context, (${createModelRules.toString()})(), ${createModelControllerAdapter.toString()});
|
|
10
12
|
}`;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
import {quickChatLifecycleActions, quickChatScope} from "./lifecycle.mjs";
|
|
2
3
|
// 通过原生能力检查与已有会话分支定位打开动作,不依赖构建哈希或压缩名称。
|
|
3
4
|
export function quickChatOpenExport(source) {
|
|
4
|
-
const match = source.match(/function ([\w$]+)\(([\w$]+),[\w$]+=\{\}\)\{if\(!\2\.get\([\w$]+,\{name:[`"']chatgpt\.quick-chat[`"']\}\)\.isCapable\)return;let [\w$]+=\2\.get\([\w$]+\),[\w$]+=\2\.get\([\w$]+\);if\(/);
|
|
5
|
+
const match = source.match(/function ([\w$]+)\(([\w$]+),[\w$]+=\{\}\)\{if\(!\2\.get\([\w$]+,\{name:[`"']chatgpt\.quick-chat[`"']\}\)\.isCapable\)return;let [\w$]+=\2\.get\([\w$]+\),[\w$]+=\2\.get\([\w$]+\);(?:if\(|[\w$]+==null\?)/);
|
|
5
6
|
if (!match) throw new Error('quick-chat-open-contract-unavailable');
|
|
6
7
|
const escaped = match[1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
7
8
|
const name = source.slice(source.lastIndexOf('export{')).match(new RegExp(`(?:\\{|,)${escaped} as ([\\w$]+)(?:,|\\})`))?.[1];
|
|
@@ -17,7 +18,8 @@ export function quickChatLifecycleExports(source) {
|
|
|
17
18
|
const local = exports.match(new RegExp(`(?:\\{|,)([\\w$]+) as ${escaped(open)}(?:,|\\})`))?.[1];
|
|
18
19
|
const start = source.indexOf(`function ${local}(`);
|
|
19
20
|
const body = source.slice(start, source.indexOf('function ', start + 9));
|
|
20
|
-
const create = body.match(/if\([\w$]+==null\)\{([\w$]+)\([\w$]+,[\w$]+\);return\}/)?.[1]
|
|
21
|
+
const create = body.match(/if\([\w$]+==null\)\{([\w$]+)\([\w$]+,[\w$]+\);return\}/)?.[1]
|
|
22
|
+
?? body.match(/[\w$]+==null\?([\w$]+)\([\w$]+,[\w$]+\):/)?.[1];
|
|
21
23
|
const state = body.match(/[\w$]+\.get\(([\w$]+)\)\|\|/)?.[1];
|
|
22
24
|
const exportOf = name => name && exports.match(new RegExp(`(?:\\{|,)${escaped(name)} as ([\\w$]+)(?:,|\\})`))?.[1];
|
|
23
25
|
const createExport = exportOf(create), openState = exportOf(state);
|
|
@@ -39,7 +41,8 @@ export function quickChatNativeCommand(source) {
|
|
|
39
41
|
|
|
40
42
|
export function installQuickChatShortcut(signal, codex, discover = null) {
|
|
41
43
|
const windows = navigator.platform.startsWith('Win');
|
|
42
|
-
let bindings = null, disposed = false, pendingFocus = null
|
|
44
|
+
let bindings = null, disposed = false, pendingFocus = null;
|
|
45
|
+
const loading=new AbortController();
|
|
43
46
|
function activateExisting(minimize = false) {
|
|
44
47
|
// 原生恢复按钮同时恢复 presentation、取消最小化并聚焦输入框。
|
|
45
48
|
for (const button of document.querySelectorAll('button')) {
|
|
@@ -95,20 +98,10 @@ export function installQuickChatShortcut(signal, codex, discover = null) {
|
|
|
95
98
|
return null;
|
|
96
99
|
}
|
|
97
100
|
async function load() {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const cleanup = () => { observer.disconnect(); signal.removeEventListener('abort', cancel); stopWaiting=null; };
|
|
103
|
-
const cancel = () => { cleanup(); reject(new Error('quick-chat-load-aborted')); };
|
|
104
|
-
const check = () => { const found=findUrl(); if (found) { cleanup(); resolve(found); } };
|
|
105
|
-
const observer = new MutationObserver(check);
|
|
106
|
-
stopWaiting=cancel;
|
|
107
|
-
observer.observe(document.documentElement, {childList:true,subtree:true,attributes:true,attributeFilter:['href','src']});
|
|
108
|
-
signal.addEventListener('abort', cancel, {once:true});
|
|
109
|
-
if (disposed || signal.aborted) cancel(); else check();
|
|
110
|
-
});
|
|
111
|
-
const source = await (await fetch(url, {signal})).text();
|
|
101
|
+
codex.report('quick-chat-shortcut', false, 'quick-chat-module-pending');
|
|
102
|
+
const url=await findCodexModule('app-initial',{signal:loading.signal,waitForEntry:true});
|
|
103
|
+
if (!url) throw new Error('quick-chat-module-unavailable');
|
|
104
|
+
const source = await (await fetch(url, {signal:loading.signal})).text();
|
|
112
105
|
const module = await import(url);
|
|
113
106
|
let native;
|
|
114
107
|
try { native=quickChatNativeCommand(source); }
|
|
@@ -117,7 +110,7 @@ export function installQuickChatShortcut(signal, codex, discover = null) {
|
|
|
117
110
|
// 新版命令目录移入共享模块;打开动作仍由入口模块导出。
|
|
118
111
|
const file=source.match(/["'`]\.\/(app-shared-[\w-]+\.js)["'`]/)?.[1];
|
|
119
112
|
if (!file) throw error;
|
|
120
|
-
const response=await fetch(new URL(file,url).href,{signal});
|
|
113
|
+
const response=await fetch(new URL(file,url).href,{signal:loading.signal});
|
|
121
114
|
if (!response.ok) throw error;
|
|
122
115
|
native=quickChatNativeCommand(await response.text());
|
|
123
116
|
}
|
|
@@ -178,7 +171,7 @@ export function installQuickChatShortcut(signal, codex, discover = null) {
|
|
|
178
171
|
event.stopPropagation();
|
|
179
172
|
}
|
|
180
173
|
document.addEventListener('keydown', keydown, {signal});
|
|
181
|
-
function dispose() { disposed = true;
|
|
174
|
+
function dispose() { disposed = true; loading.abort(); document.removeEventListener('keydown', keydown); bindings = null; pendingFocus=null; }
|
|
182
175
|
signal.addEventListener('abort', dispose, {once:true});
|
|
183
176
|
return {ready, dispose, sync:focusNew, activate, close, canClose:() => Boolean(bindings?.close && !disposed && !signal.aborted), available:() => Boolean(bindings && !bindings.native && !disposed && !signal.aborted)};
|
|
184
177
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
export async function loadQuickChatSidebarIcon(signal) {
|
|
2
|
-
const
|
|
3
|
-
const initialUrl = urls.find(url => /\/app-initial-[^/]+\.js/.test(url));
|
|
3
|
+
const initialUrl = await findCodexModule('app-initial',{signal});
|
|
4
4
|
if (!initialUrl) throw new Error('quick-chat-icon-module-unavailable');
|
|
5
5
|
const initial = await (await fetch(initialUrl,{signal})).text();
|
|
6
|
-
const file = initial.match(/["']\.\/((?:quick-chat-floating-surface|floating-chat-surface)-[\w-]+\.js)["']/)?.[1];
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const file = initial.match(/["'`]\.\/((?:quick-chat-floating-surface|floating-chat-surface)-[\w-]+\.js)["'`]/)?.[1];
|
|
7
|
+
const url = file ? new URL(file,initialUrl).href : await findCodexModule('floating-chat-surface',{signal,via:['quick-chat-window','page-codex-quick-chat-window','content-chat-surface']}) ?? await findCodexModule('quick-chat-floating-surface',{signal});
|
|
8
|
+
if (!url) throw new Error('quick-chat-icon-module-unavailable');
|
|
9
9
|
const source = await (await fetch(url,{signal})).text();
|
|
10
10
|
// 原生 Restore 在未传 minimizedIcon 时使用的默认聊天图标。
|
|
11
11
|
const local = source.match(/children:[\w$]+\?\?\(0,[\w$]+\.jsx\)\(([\w$]+),\{\}\)/)?.[1];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
import {quickChatLifecycleActions, quickChatScope} from "./adapters/codex/lifecycle.mjs";
|
|
2
3
|
import {quickChatHistoryVisible} from "./adapters/codex/history.mjs";
|
|
3
4
|
import { readFile } from "node:fs/promises";
|
|
@@ -9,7 +10,7 @@ import { installQuickChatSidebar } from "./frontend/sidebar.mjs";
|
|
|
9
10
|
import { createHistoryTracker } from "./domain/history.mjs";
|
|
10
11
|
export async function createPageScriptSource() {
|
|
11
12
|
const source = await readFile(new URL("./frontend/entry.mjs", import.meta.url), "utf8");
|
|
12
|
-
const functions = [quickChatHistoryVisible, loadQuickChatSidebarIcon, createQuickChatSidebarAdapter, installQuickChatSidebar,
|
|
13
|
+
const functions = [findCodexModule, quickChatHistoryVisible, loadQuickChatSidebarIcon, createQuickChatSidebarAdapter, installQuickChatSidebar,
|
|
13
14
|
quickChatLifecycleActions, quickChatScope, quickChatOpenExport, quickChatLifecycleExports, quickChatNativeCommand, installQuickChatShortcut, createQuickChatAdapter,
|
|
14
15
|
createSubmissionTracker, createHistoryTracker];
|
|
15
16
|
return functions.map(fn => `const ${fn.name}=${fn.toString()};`).join('\n') + '\n' + source;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
export function contextUsageExport(source) {
|
|
2
3
|
// 新版从宿主感知的 tokenUsageInfo selector 派生 thread atom family。
|
|
3
4
|
const selector = source.match(/([\w$]+)=[\w$]+\(([`"'])tokenUsageInfo\2\),([\w$]+)=[\w$]+\(/);
|
|
@@ -38,24 +39,12 @@ export function createContextUsageAdapter(signal, codex, onChange, loadFamily =
|
|
|
38
39
|
}
|
|
39
40
|
async function discover() {
|
|
40
41
|
if (loadFamily) return loadFamily();
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
...matching(/\/app-shared-[^/]+\.js(?:$|\?)/),
|
|
45
|
-
...matching(/\/(?:app-main|index)-[^/]+\.js(?:$|\?)/)];
|
|
46
|
-
if (!queue.length) throw new Error("context-bundle-unavailable");
|
|
47
|
-
const visited=new Set();
|
|
48
|
-
while (queue.length) {
|
|
49
|
-
const url=queue.shift();
|
|
50
|
-
if (visited.has(url)) continue;
|
|
51
|
-
visited.add(url);
|
|
42
|
+
for (const name of ['app-initial','app-shared']) {
|
|
43
|
+
const url=await findCodexModule(name,{signal});
|
|
44
|
+
if (!url) continue;
|
|
52
45
|
const source=await (await fetch(url,{signal})).text();
|
|
53
46
|
const exported=contextUsageExport(source);
|
|
54
47
|
if (exported) return (await import(url))[exported];
|
|
55
|
-
// modulepreload 只是优化提示;沿真实入口引用发现不再预加载的原生状态模块。
|
|
56
|
-
const references=[...source.matchAll(/["'`](\.\/app-(?:initial|main|shared)-[^/"'`]+\.js)["'`]/g)]
|
|
57
|
-
.map(match=>new URL(match[1],url).href);
|
|
58
|
-
queue.push(...references);
|
|
59
48
|
}
|
|
60
49
|
throw new Error("context-export-unavailable");
|
|
61
50
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { findCodexModule } from '../../../packages/codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { contextUsageExport, createContextUsageAdapter } from "./adapters/codex/context-usage.mjs";
|
|
3
4
|
export async function createPageScriptSource() {
|
|
4
5
|
const source=await readFile(new URL("./session-status.mjs",import.meta.url),"utf8");
|
|
5
|
-
return `const contextUsageExport=${contextUsageExport.toString()};\nconst createContextUsageAdapter=${createContextUsageAdapter.toString()};\n${source}`;
|
|
6
|
+
return `const findCodexModule=${findCodexModule.toString()};\nconst contextUsageExport=${contextUsageExport.toString()};\nconst createContextUsageAdapter=${createContextUsageAdapter.toString()};\n${source}`;
|
|
6
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minecodex",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.50",
|
|
4
4
|
"description": "Lightweight, local-first plugins for the Codex desktop app.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
"test:cli": "node --test packages/cli/test/*.test.mjs",
|
|
101
101
|
"test:runtime": "node --test packages/runtime-host/test/*.test.mjs",
|
|
102
102
|
"check": "node scripts/check-syntax.mjs",
|
|
103
|
+
"check:native": "node scripts/check-native-capabilities.mjs",
|
|
103
104
|
"build:service": "scripts/build-service-app.zsh",
|
|
104
105
|
"pack:check": "npm pack --dry-run",
|
|
105
106
|
"pack:audit": "node scripts/audit-package.mjs",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findCodexModule } from '../renderer/modules.mjs';
|
|
1
2
|
import { loadNativeMarkdownRenderer } from './markdown-native.mjs';
|
|
2
3
|
|
|
3
4
|
export function markdownExportAlias(source, name) {
|
|
@@ -13,9 +14,8 @@ export function markdownExportAlias(source, name) {
|
|
|
13
14
|
|
|
14
15
|
// 从 Chat Markdown 的公开组件发现悬浮操作栏和控件,不绑定构建 hash。
|
|
15
16
|
export async function loadNativeMarkdownTableActions(native) {
|
|
16
|
-
const url =
|
|
17
|
-
|
|
18
|
-
.find((url) => /\/app-initial-[^/]+\.js/.test(url));
|
|
17
|
+
const url = await findCodexModule('app-initial');
|
|
18
|
+
if (!url) throw new Error('native-table-actions-module-unavailable');
|
|
19
19
|
const response = await fetch(url);
|
|
20
20
|
if (!response.ok) throw new Error('native-table-actions-source-unavailable');
|
|
21
21
|
const source = await response.text();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { findCodexModule } from '../renderer/modules.mjs';
|
|
1
2
|
// 使用当前编辑器所属的原生 CodeMirror 实例,避免引入第二套编辑器依赖。
|
|
2
3
|
export async function loadMarkdownListAPI(view) {
|
|
3
|
-
const
|
|
4
|
-
for (const
|
|
5
|
-
|
|
6
|
-
const exports = Object.values(await import(link.href));
|
|
4
|
+
const urls=await findCodexModule('dist',{all:true});
|
|
5
|
+
for (const url of urls) {
|
|
6
|
+
const exports = Object.values(await import(url));
|
|
7
7
|
if (!exports.includes(view.constructor)) continue;
|
|
8
8
|
const Decoration = exports.find((value) => value?.line && value?.replace && value?.mark);
|
|
9
9
|
const ViewPlugin = exports.find((value) => value?.fromClass && value?.define);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { findCodexModule, codexModuleStyles } from '../renderer/modules.mjs';
|
|
1
2
|
// 直接导入原生 Marker、目录浮层和 Tooltip;仅按源码调用关系发现公开角色。
|
|
2
3
|
export async function loadNativeMarkdownToc() {
|
|
3
4
|
const urls = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')].map(
|
|
4
5
|
(n) => n.href || n.src,
|
|
5
6
|
);
|
|
6
|
-
const initialUrl
|
|
7
|
-
|
|
7
|
+
const [initialUrl,mainUrl]=await Promise.all([
|
|
8
|
+
findCodexModule('app-initial'),findCodexModule('app-main'),
|
|
9
|
+
]);
|
|
8
10
|
if (!initialUrl || !mainUrl) throw new Error('native-page-toc-modules-unavailable');
|
|
9
11
|
const read = async (url) => {
|
|
10
12
|
const response = await fetch(url);
|
|
@@ -12,8 +14,10 @@ export async function loadNativeMarkdownToc() {
|
|
|
12
14
|
return response.text();
|
|
13
15
|
};
|
|
14
16
|
const [initial, main] = await Promise.all([read(initialUrl), read(mainUrl)]);
|
|
17
|
+
const discovered=await findCodexModule('content',{all:true});
|
|
15
18
|
const candidates = [
|
|
16
19
|
...new Set([
|
|
20
|
+
...discovered,
|
|
17
21
|
...urls.filter((url) => /\/content-[^/]+\.js/.test(url)),
|
|
18
22
|
...[...initial.matchAll(/["']\.\/((?:content)-[\w-]+\.js)["']/g)].map(
|
|
19
23
|
(m) => new URL(m[1], initialUrl).href,
|
|
@@ -85,18 +89,18 @@ export async function loadNativeMarkdownToc() {
|
|
|
85
89
|
}
|
|
86
90
|
const ReactDOM = (await binding(rootFactory, mainImports))();
|
|
87
91
|
const css = initial.match(/["']\.\/((?:floating-navigation-rail(?:-layout)?)-[\w-]+\.css)["']/)?.[1];
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
link.rel
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
link
|
|
97
|
-
link.onerror = () => reject(new Error('native-page-toc-styles-unavailable'));
|
|
92
|
+
const cssUrls=css ? [new URL(css,initialUrl).href] : codexModuleStyles(initial,initialUrl,sourceUrl);
|
|
93
|
+
if (!cssUrls.length) throw new Error('native-page-toc-styles-unavailable');
|
|
94
|
+
await Promise.all(cssUrls.map(async cssUrl=>{
|
|
95
|
+
if ([...document.querySelectorAll('link[rel="stylesheet"]')].some(node=>node.href===cssUrl)) return;
|
|
96
|
+
const link=document.createElement('link');link.rel='stylesheet';link.href=cssUrl;
|
|
97
|
+
await new Promise((resolve,reject)=>{
|
|
98
|
+
link.onload=resolve;
|
|
99
|
+
link.onerror=()=>reject(new Error('native-page-toc-styles-unavailable'));
|
|
100
|
+
document.head.append(link);
|
|
98
101
|
});
|
|
99
|
-
}
|
|
102
|
+
}));
|
|
103
|
+
|
|
100
104
|
const component = (predicate) => {
|
|
101
105
|
const matches = Object.values(layout).filter(
|
|
102
106
|
(value) => typeof value === 'function' && predicate(String(value)),
|
|
@@ -125,7 +125,10 @@ export function createCodexEnvironment() {
|
|
|
125
125
|
const unknown = {status:"unknown", planType:null, source:null, key:null};
|
|
126
126
|
if (signal?.aborted || !auth || auth.isLoading || !auth.userId || !auth.accountId
|
|
127
127
|
|| !["chatgpt", "chatgptAuthTokens"].includes(auth.authMethod)) return unknown;
|
|
128
|
-
const data = client?.
|
|
128
|
+
const data = client?.getQueryCache().findAll({queryKey:["accounts","check"]})
|
|
129
|
+
.filter(query=>query.state.data)
|
|
130
|
+
.sort((a,b)=>(b.state.dataUpdatedAt??0)-(a.state.dataUpdatedAt??0))[0]?.state.data;
|
|
131
|
+
if (data && !Array.isArray(data.accounts)) return unknown;
|
|
129
132
|
const current = data?.accounts?.find(value => value.id === auth.accountId);
|
|
130
133
|
// 已有账号清单却找不到当前身份时,不借用上一个账号的登录套餐。
|
|
131
134
|
if (data && !current) return unknown;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// 只沿原生应用入口链读取模块引用;预加载标签不是完整的模块清单。
|
|
2
|
+
export async function findCodexModule(name, {read = null, signal, all = false, waitForEntry = false, via = []} = {}) {
|
|
3
|
+
const links = () => [...document.querySelectorAll('link[rel="modulepreload"],script[src]')]
|
|
4
|
+
.map(node => node.href || node.src).filter(Boolean);
|
|
5
|
+
const entry = url => /\/(?:index|app-(?:main|initial))-[^/]+\.js(?:$|\?)/.test(url);
|
|
6
|
+
let urls=links();
|
|
7
|
+
signal?.throwIfAborted();
|
|
8
|
+
if (waitForEntry && !urls.some(entry)) {
|
|
9
|
+
await new Promise((resolve,reject) => {
|
|
10
|
+
const cleanup=()=>{observer.disconnect();signal?.removeEventListener('abort',abort);};
|
|
11
|
+
const abort=()=>{cleanup();reject(signal.reason);};
|
|
12
|
+
const check=()=>{if(links().some(entry)){cleanup();resolve();}};
|
|
13
|
+
const observer=new MutationObserver(check);
|
|
14
|
+
observer.observe(document.documentElement,{childList:true,subtree:true,attributes:true,attributeFilter:['src','href','rel']});
|
|
15
|
+
signal?.addEventListener('abort',abort,{once:true});
|
|
16
|
+
if(signal?.aborted) abort(); else check();
|
|
17
|
+
});
|
|
18
|
+
urls=links();
|
|
19
|
+
}
|
|
20
|
+
const matches=url=>new URL(url).pathname.split('/').at(-1)?.startsWith(`${name}-`)
|
|
21
|
+
&& /\.js(?:$|\?)/.test(url);
|
|
22
|
+
const found=new Set(urls.filter(matches));
|
|
23
|
+
if (found.size && !all) return [...found][0];
|
|
24
|
+
const core=url=>/\/(?:index|app-(?:main|initial|primary|runtime|shared))-[^/]+\.js(?:$|\?)/.test(url)
|
|
25
|
+
|| via.some(role=>new URL(url).pathname.split('/').at(-1)?.startsWith(`${role}-`));
|
|
26
|
+
const queue=urls.filter(core).sort((a,b)=>Number(a.includes('/app-shared-'))-Number(b.includes('/app-shared-')));
|
|
27
|
+
const visited=new Set();
|
|
28
|
+
const source=read ?? (async url=>{
|
|
29
|
+
const response=await fetch(url,{signal});
|
|
30
|
+
if(!response.ok) throw new Error('native-module-source-unavailable');
|
|
31
|
+
return response.text();
|
|
32
|
+
});
|
|
33
|
+
while(queue.length) {
|
|
34
|
+
signal?.throwIfAborted();
|
|
35
|
+
const url=queue.shift();
|
|
36
|
+
if(visited.has(url)) continue;
|
|
37
|
+
visited.add(url);
|
|
38
|
+
const text=await source(url);
|
|
39
|
+
signal?.throwIfAborted();
|
|
40
|
+
for(const match of text.matchAll(/["'`](\.\/[\w.-]+\.js)["'`]/g)) {
|
|
41
|
+
const dependency=new URL(match[1],url).href;
|
|
42
|
+
if(matches(dependency)) {found.add(dependency);if(!all)return dependency;}
|
|
43
|
+
if(core(dependency) && !visited.has(dependency))queue.push(dependency);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return all ? [...found] : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 使用原生 lazy import 声明的 CSS 依赖,兼容 Vite 将语义样式名改成纯 hash。
|
|
50
|
+
export function codexModuleStyles(source, ownerUrl, moduleUrl) {
|
|
51
|
+
const table=source.match(/\.f=\[([^\]]+)\]/)?.[1];
|
|
52
|
+
if(!table)return [];
|
|
53
|
+
const dependencies=JSON.parse(`[${table}]`);
|
|
54
|
+
for(const match of source.matchAll(/import\(["'`]([^"'`]+)["'`]\)/g)) {
|
|
55
|
+
if(new URL(match[1],ownerUrl).href!==moduleUrl)continue;
|
|
56
|
+
const call=source.slice(match.index,source.indexOf('import.meta.url',match.index));
|
|
57
|
+
const indices=call.match(/__vite__mapDeps\(\[([\d,]+)\]\)/)?.[1];
|
|
58
|
+
if(!indices)return [];
|
|
59
|
+
return [...new Set(indices.split(',').map(Number).map(index=>{
|
|
60
|
+
const file=dependencies[index];
|
|
61
|
+
if(typeof file!=='string')throw new Error('native-module-style-dependency-unavailable');
|
|
62
|
+
return file;
|
|
63
|
+
}).filter(file=>file.endsWith('.css')).map(file=>new URL(file,ownerUrl).href))];
|
|
64
|
+
}
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findCodexModule } from '../../codex-adapter/src/renderer/modules.mjs';
|
|
1
2
|
import { createRendererSelfCheck, tokenRequirements } from "./renderer-self-check.mjs";
|
|
2
3
|
import { createCodexEnvironment } from "../../codex-adapter/src/renderer/environment.mjs";
|
|
3
4
|
// 注入源生成:createInjectionSource 产出注入到 Codex Renderer 的自包含脚本字符串。
|
|
@@ -10,7 +11,7 @@ import { MCP_NAVIGATION_ICONS } from "../../plugin/src/navigation-icons.mjs";
|
|
|
10
11
|
import { randomBytes } from "node:crypto";
|
|
11
12
|
import { CODEX_THEME_TOKENS, CODEX_THEME_TOKEN_ALIASES, MINECODEX_THEME_BINDINGS } from "../../ui/src/native/tokens.mjs";
|
|
12
13
|
|
|
13
|
-
export const RUNTIME_VERSION =
|
|
14
|
+
export const RUNTIME_VERSION = 114;
|
|
14
15
|
export const HOST_BINDING_NAME = "__codexPersonalHostAction";
|
|
15
16
|
export const CSP_BOOTSTRAP_VERSION = 1;
|
|
16
17
|
export const CSP_BOOTSTRAP_KEY = "__mineCodexCspBootstrapVersion";
|
|
@@ -124,14 +125,14 @@ export function createInjectionSource(features, {
|
|
|
124
125
|
bindingToken = "test-binding-token",
|
|
125
126
|
runtimeSessionId = randomBytes(16).toString("hex"),
|
|
126
127
|
} = {}) {
|
|
127
|
-
function install(config, installUI, installModelSliderFeature, createCodexEnvironment, createRendererSelfCheck) {
|
|
128
|
+
function install(config, installUI, installModelSliderFeature, createCodexEnvironment, createRendererSelfCheck, findCodexModule) {
|
|
128
129
|
if (window.top !== window) return;
|
|
129
130
|
if (!document.documentElement) {
|
|
130
131
|
// The document bootstrap runs before the root element exists. Defer the
|
|
131
132
|
// whole install until the DOM is ready instead of crashing on a null
|
|
132
133
|
// documentElement (which previously left the runtime permanently absent
|
|
133
134
|
// after a renderer reload).
|
|
134
|
-
document.addEventListener("DOMContentLoaded", () => install(config, installUI, installModelSliderFeature, createCodexEnvironment, createRendererSelfCheck), { once: true });
|
|
135
|
+
document.addEventListener("DOMContentLoaded", () => install(config, installUI, installModelSliderFeature, createCodexEnvironment, createRendererSelfCheck, findCodexModule), { once: true });
|
|
135
136
|
return;
|
|
136
137
|
}
|
|
137
138
|
const currentRuntime = window.__codexPersonalRuntime;
|
|
@@ -1506,11 +1507,6 @@ export function createInjectionSource(features, {
|
|
|
1506
1507
|
// 原生 Detail Tab capability — React fiber/右侧面板探测、原生详情页签打开与线程跳转
|
|
1507
1508
|
// ========================================================================
|
|
1508
1509
|
|
|
1509
|
-
function nativeTabModuleUrl() {
|
|
1510
|
-
return Array.from(document.querySelectorAll('link[rel="modulepreload"]'))
|
|
1511
|
-
.map((link) => link.href)
|
|
1512
|
-
.find((url) => /\/assets\/app-initial-[^/]+\.js(?:\?|$)/.test(url)) ?? null;
|
|
1513
|
-
}
|
|
1514
1510
|
|
|
1515
1511
|
function reactFiberFor(element) {
|
|
1516
1512
|
if (!(element instanceof Element)) return null;
|
|
@@ -1600,13 +1596,7 @@ export function createInjectionSource(features, {
|
|
|
1600
1596
|
}
|
|
1601
1597
|
|
|
1602
1598
|
async function waitForNativeTabModuleUrl() {
|
|
1603
|
-
|
|
1604
|
-
do {
|
|
1605
|
-
const moduleUrl = nativeTabModuleUrl();
|
|
1606
|
-
if (moduleUrl) return moduleUrl;
|
|
1607
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1608
|
-
} while (performance.now() < deadline);
|
|
1609
|
-
return null;
|
|
1599
|
+
return findCodexModule('app-initial',{signal:lifetime.signal,waitForEntry:true});
|
|
1610
1600
|
}
|
|
1611
1601
|
|
|
1612
1602
|
async function loadNativeTabCapability() {
|
|
@@ -1615,7 +1605,7 @@ export function createInjectionSource(features, {
|
|
|
1615
1605
|
nativeTabCapabilityPromise = (async () => {
|
|
1616
1606
|
try {
|
|
1617
1607
|
const moduleUrl = await waitForNativeTabModuleUrl();
|
|
1618
|
-
if (!moduleUrl) throw new Error("Codex app-initial
|
|
1608
|
+
if (!moduleUrl) throw new Error("Codex app-initial module was not found");
|
|
1619
1609
|
const appModule = await import(moduleUrl);
|
|
1620
1610
|
const controller = Object.values(appModule).find((value) => (
|
|
1621
1611
|
value
|
|
@@ -2468,7 +2458,7 @@ export function createInjectionSource(features, {
|
|
|
2468
2458
|
themeBindings: MINECODEX_THEME_BINDINGS,
|
|
2469
2459
|
mcpNavigationIcons: MCP_NAVIGATION_ICONS,
|
|
2470
2460
|
features: runtimeFeatures,
|
|
2471
|
-
})}, ${createUIInstallerSource()}, ${createModelSliderInstallerSource()}, ${createCodexEnvironment.toString()}, ${createRendererSelfCheck.toString()})`;
|
|
2461
|
+
})}, ${createUIInstallerSource()}, ${createModelSliderInstallerSource()}, ${createCodexEnvironment.toString()}, ${createRendererSelfCheck.toString()}, ${findCodexModule.toString()})`;
|
|
2472
2462
|
}
|
|
2473
2463
|
|
|
2474
2464
|
export function createRuntimeReadyContract({
|