minecodex 2.0.5 → 2.0.6
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/DESIGN.md +15 -0
- package/README.md +5 -1
- package/apps/dashboard/DESIGN.md +53 -31
- package/apps/dashboard/app.js +55 -39
- package/apps/dashboard/assets/OFL.txt +93 -0
- package/apps/dashboard/assets/info.svg +15 -0
- package/apps/dashboard/assets/moon.svg +13 -0
- package/apps/dashboard/assets/refresh-cw.svg +16 -0
- package/apps/dashboard/assets/silkscreen.ttf +0 -0
- package/apps/dashboard/assets/sun.svg +21 -0
- package/apps/dashboard/assets/terrain.js +89 -0
- package/apps/dashboard/assets/triangle-alert.svg +15 -0
- package/apps/dashboard/favicon.svg +12 -17
- package/apps/dashboard/index.html +24 -18
- package/apps/dashboard/styles.css +147 -181
- package/assets/brand/README.md +19 -0
- package/assets/brand/minecodex-icon-dark.svg +9 -0
- package/assets/brand/minecodex-icon-light.svg +9 -0
- package/assets/brand/minecodex-icon.svg +8 -0
- package/assets/brand/references/lucide-LICENSE.txt +43 -0
- package/assets/readme/hero.svg +93 -0
- package/features/hide-upsell-banner/README.md +7 -5
- package/features/hide-upsell-banner/codex-feature.json +2 -2
- package/features/hide-upsell-banner/src/hide-upsell-banner.mjs +30 -12
- package/features/images/DESIGN.md +13 -2
- package/features/images/README.md +4 -3
- package/features/images/codex-feature.json +2 -2
- package/features/images/src/adapters/codex/native-image-modules.mjs +211 -0
- package/features/images/src/adapters/codex/native-image-source.mjs +70 -177
- package/features/images/src/frontend/cloud-image-actions.mjs +8 -6
- package/features/images/src/frontend/local-image-source.mjs +2 -1
- package/features/images/src/library-prompt-page.mjs +6 -1
- package/features/images/src/library-prompt-source.mjs +3 -5
- package/features/model-slider/codex-feature.json +2 -2
- package/features/notes/DESIGN.md +1 -1
- package/features/notes/codex-feature.json +2 -2
- package/features/notes/src/frontend/entry.mjs +1 -1
- package/features/session-status/codex-feature.json +2 -2
- package/features/sidebar-focus/DESIGN.md +4 -4
- package/features/sidebar-focus/codex-feature.json +3 -2
- package/features/sidebar-focus/src/adapters/codex/renderer.mjs +1 -1
- package/features/sidebar-focus/src/sidebar-focus.mjs +9 -7
- package/features/sites/DESIGN.md +25 -3
- package/features/sites/README.md +6 -2
- package/features/sites/codex-feature.json +3 -2
- package/features/sites/src/adapters/codex/renderer.mjs +25 -4
- package/features/sites/src/backend/project-sites.mjs +18 -6
- package/features/sites/src/backend/sites-service.mjs +22 -10
- package/features/sites/src/domain/discovery-candidates.mjs +53 -0
- package/features/sites/src/frontend/entry.mjs +76 -38
- package/features/sites/src/frontend/styles.css +3 -3
- package/features/unread-inbox/DESIGN.md +24 -25
- package/features/unread-inbox/codex-feature.json +3 -2
- package/features/unread-inbox/src/adapters/codex/modules.mjs +52 -0
- package/features/unread-inbox/src/adapters/codex/renderer.mjs +90 -0
- package/features/unread-inbox/src/domain/notifications.mjs +21 -0
- package/features/unread-inbox/src/injection-source.mjs +5 -2
- package/features/unread-inbox/src/unread-inbox.mjs +212 -297
- package/features/usage-meter/codex-feature.json +2 -2
- package/package.json +11 -3
- package/packages/cli/native/MineCodex.app/Contents/Info.plist +4 -2
- package/packages/cli/native/MineCodex.app/Contents/MacOS/MineCodexServiceManager +0 -0
- package/packages/cli/native/MineCodex.app/Contents/Resources/MineCodex.icns +0 -0
- package/packages/cli/native/MineCodex.app/Contents/_CodeSignature/CodeResources +11 -0
- package/packages/cli/native/README.md +31 -0
- package/packages/cli/src/browser-control-panel.mjs +48 -0
- package/packages/cli/src/commands.mjs +3 -1
- package/packages/cli/src/control-server.mjs +6 -3
- package/packages/cli/src/platform.mjs +5 -0
- package/packages/runtime-host/README.md +10 -5
- package/packages/runtime-host/src/codex-injection.mjs +10 -3
- package/packages/runtime-host/src/codex-runtime.mjs +10 -0
- package/packages/ui/DESIGN.md +5 -1
- package/packages/ui/assets/arrow-up-right.svg +14 -0
- package/packages/ui/assets/lucide-LICENSE.txt +43 -0
- package/packages/ui/src/components/account-menu.mjs +74 -0
- package/packages/ui/src/components/page.mjs +2 -2
- package/packages/ui/src/foundation/layers.mjs +1 -0
- package/packages/ui/src/install.mjs +3 -0
- package/packages/ui/src/native/account-menu.mjs +21 -0
- package/packages/ui/src/native/page.mjs +17 -2
- package/packages/ui/src/native/tokens.mjs +1 -0
|
@@ -1,152 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {createNativeImageModules} from "./native-image-modules.mjs";
|
|
2
|
+
|
|
3
|
+
// 原生呈现与上下文只在此适配;构建产物发现由专用模块提供。
|
|
4
|
+
export function createNativeImageSource(codex, { requestHostAction, libraryApi, createCloudImageActions, signal, modules = createNativeImageModules() } = {}) {
|
|
5
|
+
const connect = modules.gallery;
|
|
4
6
|
const opened = new Map();
|
|
5
7
|
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
throw new Error(`Native Images import contract changed: ${local}`);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function connect() {
|
|
17
|
-
if (!connecting) connecting = (async () => {
|
|
18
|
-
const urls = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')].map(node => node.href || node.src);
|
|
19
|
-
const initialUrl = urls.find(url => /\/app-initial-[^/]+\.js/.test(url));
|
|
20
|
-
const mainUrl = urls.find(url => /\/app-main-[^/]+\.js/.test(url));
|
|
21
|
-
if (!initialUrl || !mainUrl) throw new Error("Native Images modules are unavailable");
|
|
22
|
-
const [initialSource, mainSource] = await Promise.all([read(initialUrl), read(mainUrl)]);
|
|
23
|
-
const asset = (name, references = initialSource) => {
|
|
24
|
-
const file = references.match(new RegExp(`(?:["'./])(${name}-[\\w-]+\\.js)`))?.[1];
|
|
25
|
-
if (!file) throw new Error(`Native Images capability is unavailable: ${name}`);
|
|
26
|
-
return new URL(file, initialUrl).href;
|
|
27
|
-
};
|
|
28
|
-
const module = async (name,references) => {
|
|
29
|
-
const url = asset(name,references);
|
|
30
|
-
const [source, exports] = await Promise.all([read(url), load(url)]);
|
|
31
|
-
// 调用产物公开的惰性初始化器,不扫描或执行任意导出函数。
|
|
32
|
-
for (const value of Object.values(exports)) {
|
|
33
|
-
if (typeof value === "function" && /^function ([\w$]+)\(\)\{return\(\1=/.test(String(value))) value();
|
|
34
|
-
}
|
|
35
|
-
return {url, source, exports};
|
|
36
|
-
};
|
|
37
|
-
const [masonry, layout, sourceImage, menu, selection, initial] = await Promise.all([
|
|
38
|
-
module("library-masonry"), module("library-item-layout"), module("generated-image-source"),
|
|
39
|
-
module("generated-image-context-menu"), module("library-selection-toolbar"), load(initialUrl),
|
|
40
|
-
]);
|
|
41
|
-
const [moveDialog,dialogs] = await Promise.all([module("library-move-dialog",selection.source),module("library-item-dialogs",selection.source)]);
|
|
42
|
-
const [canvas,canvasLayout,filePreview] = await Promise.all([
|
|
43
|
-
module("image-side-panel"),module("preview-layout"),module("library-cloud-file-preview"),
|
|
44
|
-
]);
|
|
45
|
-
const component = (exports, predicate, name) => {
|
|
46
|
-
const matches = Object.values(exports).filter(value => typeof value === "function" && predicate(String(value)));
|
|
47
|
-
if (matches.length !== 1) throw new Error(`Native Images contract changed: ${name}`);
|
|
48
|
-
return matches[0];
|
|
49
|
-
};
|
|
50
|
-
const binding = async (source, url, local) => {
|
|
51
|
-
if (!local) throw new Error("Native Images component contract changed");
|
|
52
|
-
const importedBinding = imported(source, local);
|
|
53
|
-
return (await load(new URL(importedBinding.file, url).href))[importedBinding.name];
|
|
54
|
-
};
|
|
55
|
-
const routerLocal = selection.source.match(/get\(([\w$]+)\)\.prepareNavigation\(/)?.[1];
|
|
56
|
-
const routerAtom = await binding(selection.source, selection.url, routerLocal);
|
|
57
|
-
const switchLocal = selection.source.match(/([\w$]+)\(e,i\);let _=t.length===1/)?.[1];
|
|
58
|
-
const switchMode = await binding(selection.source, selection.url, switchLocal);
|
|
59
|
-
const draftLocal = selection.source.match(/let u=([\w$]+)\(e,\[\]\);e.set/)?.[1];
|
|
60
|
-
const createChatDraft = await binding(selection.source, selection.url, draftLocal);
|
|
61
|
-
const rootOwner = mainSource.match(/\(0,([\w$]+)\.createRoot\)/)?.[1];
|
|
62
|
-
const rootInit = rootOwner && mainSource.match(new RegExp(`${rootOwner}=([\\w$]+)\\(\\)`))?.[1];
|
|
63
|
-
const reactOwner = masonry.source.match(/([\w$]+)\.useRef\b/)?.[1];
|
|
64
|
-
const reactInit = reactOwner && masonry.source.match(new RegExp(`${reactOwner}=([\\w$]+)\\(\\)`))?.[1];
|
|
65
|
-
const [reactFactory, rootFactory] = await Promise.all([
|
|
66
|
-
binding(masonry.source, masonry.url, reactInit), binding(mainSource, mainUrl, rootInit),
|
|
67
|
-
]);
|
|
68
|
-
const React = reactFactory(), ReactDOM = rootFactory();
|
|
69
|
-
const portalOwner=canvas.source.match(/\(0,([\w$]+)\.createPortal\)/)?.[1];
|
|
70
|
-
const portalInit=portalOwner&&canvas.source.match(new RegExp(`${portalOwner}=([\\w$]+)\\(\\)`))?.[1];
|
|
71
|
-
const createPortal=(await binding(canvas.source,canvas.url,portalInit))().createPortal;
|
|
72
|
-
const dropdownLocal = selection.source.match(/\(0,[\w$]+\.jsxs?\)\(([\w$]+),\{contentWidth:`menu`/)?.[1];
|
|
73
|
-
const itemsLocal = selection.source.match(/\(0,[\w$]+\.jsxs?\)\(([\w$]+)\.Item,\{/)?.[1];
|
|
74
|
-
const buttonLocal = selection.source.match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{ref:[^,]+,color:`mediaOverlay`/)?.[1];
|
|
75
|
-
const iconLocal = selection.source.match(/color:`mediaOverlay`[^;]+?children:\(0,[\w$]+\.jsx\)\(([\w$]+),\{\}\)/)?.[1];
|
|
76
|
-
const [Dropdown, Menu, Button, MoreIcon] = await Promise.all([dropdownLocal, itemsLocal, buttonLocal, iconLocal].map(local => binding(selection.source, selection.url, local)));
|
|
77
|
-
const checkboxFunction = component(selection.exports,source=>/onCheckedChange:/.test(source)&&/viewMode:/.test(source),"selection checkbox");
|
|
78
|
-
const checkboxLocal = String(checkboxFunction).match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{className:`cursor-interaction`,variant:/)?.[1];
|
|
79
|
-
const Checkbox = await binding(selection.source,selection.url,checkboxLocal);
|
|
80
|
-
const rangeSelection = component(selection.exports,source=>/^function [\w$]+\(\{orderedIds:/.test(source)&&/extendRange:/.test(source),"range selection");
|
|
81
|
-
const selectionLocal = selection.source.match(/\.set\(([\w$]+),new Set\)/)?.[1];
|
|
82
|
-
const selectionExport = selection.source.match(/export\{([^}]+)\}/)?.[1].split(",").map(value=>value.trim().split(/\s+as\s+/)).find(parts=>parts[0]===selectionLocal)?.at(-1);
|
|
83
|
-
const nativeSelectedIds = selection.exports[selectionExport];
|
|
84
|
-
if (!nativeSelectedIds) throw new Error("Native selection state contract changed");
|
|
85
|
-
const clearPrefix = selection.source.slice(selection.source.indexOf("id:`libraryNext.selection.clear`"));
|
|
86
|
-
const clearLocal = clearPrefix.match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{\}\)/)?.[1];
|
|
87
|
-
const ClearIcon = await binding(selection.source,selection.url,clearLocal);
|
|
88
|
-
const dialogLocal = dialogs.source.match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{open:[^,]+,showDialogClose:!1/)?.[1];
|
|
89
|
-
const contentLocal = dialogs.source.match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{as:`form`,onSubmit:/)?.[1];
|
|
90
|
-
const [Dialog,DialogContent] = await Promise.all([dialogLocal,contentLocal].map(local=>binding(dialogs.source,dialogs.url,local)));
|
|
91
|
-
const menuCoreFile = menu.source.match(/from["']([^"']*generated-image-context-menu-[^"']+)["']/)?.[1];
|
|
92
|
-
const menuCoreUrl = menuCoreFile ? new URL(menuCoreFile,menu.url).href : menu.url;
|
|
93
|
-
const menuCoreSource = menuCoreFile ? await read(menuCoreUrl) : menu.source;
|
|
94
|
-
const imageActions = component(menu.exports,source=>/onOpenedInNewTab:/.test(source),"image actions");
|
|
95
|
-
const imageActionSource = String(imageActions);
|
|
96
|
-
const imageFileLocal = imageActionSource.match(/let\{file:[\w$]+\}=([\w$]+)\(/)?.[1];
|
|
97
|
-
const attachLocal = imageActionSource.match(/await ([\w$]+)\(e,i,\[n\],/)?.[1];
|
|
98
|
-
const downloadLocal = imageActionSource.match(/await ([\w$]+)\(\{downloadSrc:/)?.[1];
|
|
99
|
-
const [imageFile,attachChatImages,downloadImage] = await Promise.all([imageFileLocal,attachLocal,downloadLocal].map(local=>binding(menuCoreSource,menuCoreUrl,local)));
|
|
100
|
-
const toolbarClass = selection.source.match(/className:`(pointer-events-auto flex h-toolbar[^`]+)`/)?.[1];
|
|
101
|
-
const toolbarPositionClass = selection.source.match(/className:`(pointer-events-none fixed inset-x-4 bottom-6[^`]+)`/)?.[1];
|
|
102
|
-
if (!toolbarClass || !toolbarPositionClass) throw new Error("Native selection toolbar layout changed");
|
|
103
|
-
const libraryIcon = async label => {
|
|
104
|
-
const marker = selection.source.indexOf(`defaultMessage:\`${label}\``);
|
|
105
|
-
const prefix = selection.source.slice(Math.max(0, marker - 500), marker);
|
|
106
|
-
const match = [...prefix.matchAll(/leftIcon:\(0,[\w$]+\.jsx\)\(([\w$]+),\{icon:\{(assets:\{)?16:([\w$]+),20:([\w$]+)/g)].at(-1);
|
|
107
|
-
if (!match) throw new Error(`Native Library icon contract changed: ${label}`);
|
|
108
|
-
const [Icon, small, medium] = await Promise.all([match[1], match[3], match[4]].map(local => binding(selection.source, selection.url, local)));
|
|
109
|
-
const sizes = {16:small, 20:medium};
|
|
110
|
-
return React.createElement(Icon, {icon:match[2] ? {assets:sizes} : sizes});
|
|
111
|
-
};
|
|
112
|
-
const upload = component(initial, source => /async function/.test(source) && /libraryPersistenceMode:/.test(source) && /uploadPurpose:/.test(source), "Library upload");
|
|
113
|
-
const setFavorite = component(initial, source => /target_ref:/.test(source) && /showToast:/.test(source), "Library favorite");
|
|
114
|
-
const auth = String(setFavorite).match(/accountId:e.get\(([\w$]+)\),userId:e.get\(([\w$]+)\)/);
|
|
115
|
-
const [accountAtom,userAtom] = await Promise.all([auth?.[1],auth?.[2]].map(local => binding(initialSource,initialUrl,local)));
|
|
116
|
-
if (!accountAtom || !userAtom) throw new Error("Native Library account contract changed");
|
|
117
|
-
const toastLocal = selection.source.match(/get\(([\w$]+)\)\.danger\(/)?.[1];
|
|
118
|
-
const toastAtom = await binding(selection.source, selection.url, toastLocal);
|
|
119
|
-
const favoritePrefix = selection.source.slice(Math.max(0,selection.source.indexOf("id:`library.favorites.add`")-2000),selection.source.indexOf("id:`library.favorites.add`")+500);
|
|
120
|
-
const favoriteAsset = favoritePrefix.match(/\{16:([\w$]+),20:([\w$]+)\}:\{16:([\w$]+),20:([\w$]+)\}/);
|
|
121
|
-
if (!favoriteAsset) throw new Error("Native Library favorite icon contract changed");
|
|
122
|
-
const favoriteIconLocal = favoritePrefix.match(/icon:\{assets:/)?.index;
|
|
123
|
-
const favoriteIconOwner = favoritePrefix.slice(0,favoriteIconLocal).match(/\(0,[\w$]+\.jsx\)\(([\w$]+),\{$/)?.[1];
|
|
124
|
-
const [FavoriteIcon, savedSmall, savedMedium, emptySmall, emptyMedium] = await Promise.all([favoriteIconOwner,...favoriteAsset.slice(1)].map(local => binding(selection.source,selection.url,local)));
|
|
125
|
-
const favoriteIcon = saved => React.createElement(FavoriteIcon,{icon:{assets:saved?{16:savedSmall,20:savedMedium}:{16:emptySmall,20:emptyMedium}}});
|
|
126
|
-
const [chatIcon, downloadIcon, folderIcon, deleteIcon] = await Promise.all([
|
|
127
|
-
libraryIcon("Chat about this"), libraryIcon("Download"), libraryIcon("Add to folder"), libraryIcon("{kind, select, directory {Delete folder} other {Delete}}"),
|
|
128
|
-
]);
|
|
129
|
-
// 文件画布使用原生 Home Composer 的控制器、附件 scope 和输入组件。
|
|
130
|
-
const composerLocals=["kr","Qn","$n","rr","ke","hn","tn","P"];
|
|
131
|
-
const [useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope] = await Promise.all(composerLocals.map(local=>binding(filePreview.source,filePreview.url,local)));
|
|
132
|
-
if (!React.createElement || !ReactDOM.createRoot || !Menu.Item) throw new Error("Native Images rendering contract changed");
|
|
133
|
-
return {
|
|
134
|
-
React, ReactDOM, createPortal, Dropdown, Menu, Button, MoreIcon, chatIcon, downloadIcon, folderIcon, favoriteIcon, routerAtom, switchMode, createChatDraft, upload, setFavorite, toastAtom, accountAtom, userAtom,
|
|
135
|
-
MoveDialog:moveDialog.exports.MoveLibraryItemsDialog,
|
|
136
|
-
Checkbox,rangeSelection,nativeSelectedIds,ClearIcon,Dialog,DialogContent,ConfirmationContent:dialogs.exports.LibraryItemConfirmationContent,
|
|
137
|
-
Canvas:canvas.exports.t,CanvasLayout:canvasLayout.exports.r,
|
|
138
|
-
useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope,
|
|
139
|
-
editImage:component(initial,source=>/async function/.test(source)&&/requireDispatchAcceptance:/.test(source)&&/sourceAttachment:/.test(source)&&/imageVariant/.test(source),"image edit submission"),
|
|
140
|
-
imageFile,attachChatImages,downloadImage,toolbarClass,toolbarPositionClass,deleteIcon,
|
|
141
|
-
Masonry: component(masonry.exports, source => /\{items:[^}]+animate:/.test(source), "masonry"),
|
|
142
|
-
Skeleton: component(masonry.exports, source => /square:/.test(source) && /Loading library/.test(source), "skeleton"),
|
|
143
|
-
Card: component(layout.exports, source => /actionIndicator:/.test(source) && /documentHeader:/.test(source), "card"),
|
|
144
|
-
useImage: component(sourceImage.exports, source => /shouldLoadFileDataUrl:/.test(source), "image resolver"),
|
|
145
|
-
imageMenu: component(menu.exports, source => /onOpenedInNewTab:/.test(source), "image actions"),
|
|
146
|
-
openImage: component(initial, source => /availableImageCount:/.test(source) && /openInEditor:/.test(source) && /entrypoint:/.test(source), "image preview"),
|
|
147
|
-
};
|
|
148
|
-
})().catch(error => { connecting = null; throw error; });
|
|
149
|
-
return connecting;
|
|
8
|
+
function uploadFile({base64,name}) {
|
|
9
|
+
const decoded=atob(base64),bytes=new Uint8Array(decoded.length);
|
|
10
|
+
// 直接写入字节,避免大图经字符串迭代器创建中间数组。
|
|
11
|
+
for(let index=0;index<decoded.length;index++)bytes[index]=decoded.charCodeAt(index);
|
|
12
|
+
return new File([bytes],name,{type:"image/png"});
|
|
150
13
|
}
|
|
151
14
|
|
|
152
15
|
function environment(anchor) {
|
|
@@ -189,7 +52,7 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
189
52
|
}
|
|
190
53
|
|
|
191
54
|
async function mountNavigation(container, anchor, onSelect) {
|
|
192
|
-
const native = await
|
|
55
|
+
const native = await modules.navigation();
|
|
193
56
|
const context = environment(anchor);
|
|
194
57
|
const root = native.ReactDOM.createRoot(container);
|
|
195
58
|
return {
|
|
@@ -200,7 +63,6 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
200
63
|
selectedKey,
|
|
201
64
|
tabs:[...contract.props.tabs, {key:"imagegen", name:"Image Gen"}],
|
|
202
65
|
onSelect:key => {
|
|
203
|
-
if (key === "imagegen") context.libraryScope?.set(native.nativeSelectedIds,new Set());
|
|
204
66
|
onSelect(key);
|
|
205
67
|
if (key !== "imagegen") navigationContract(anchor).props.onSelect(key);
|
|
206
68
|
},
|
|
@@ -216,20 +78,20 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
216
78
|
}
|
|
217
79
|
|
|
218
80
|
async function mount(container, anchor, {onOpen, onPrompt, onRetry, onHidden}) {
|
|
219
|
-
|
|
81
|
+
let native = await connect();
|
|
220
82
|
const context = environment(anchor);
|
|
221
83
|
const {React, ReactDOM} = native;
|
|
222
84
|
const h = React.createElement;
|
|
223
85
|
let failure, currentContent, folderDialog, removeDialog, canvasImage, disposed=false, batchBusy=false;
|
|
224
|
-
let currentImages=[],currentLoading=true,selectedIds=new Set(),selectionAnchor=null;
|
|
86
|
+
let currentImages=[],currentLoading=true,selectedIds=new Set(),selectionAnchor=null,openRevision=0;
|
|
225
87
|
const scope = context.libraryScope;
|
|
226
88
|
if (!scope) throw new Error("Native Library context is unavailable");
|
|
227
89
|
const {accountId,userId} = scope.value;
|
|
228
90
|
const message = (id,defaultMessage) => context.intl.formatMessage({id,defaultMessage});
|
|
229
91
|
const toast = () => scope.get(native.toastAtom);
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
};
|
|
92
|
+
const sameAccount = () => scope.get(native.accountAtom) === accountId && scope.get(native.userAtom) === userId;
|
|
93
|
+
const isCurrent = () => !disposed && sameAccount();
|
|
94
|
+
const assertAccount = () => {if (!isCurrent()) throw new Error("Library action is no longer current");};
|
|
233
95
|
const reference = (image,operation,extra={}) => requestHostAction("local-image-source",{operation,id:image.id,accountId,userId,...extra});
|
|
234
96
|
async function favoriteStatus(id) {
|
|
235
97
|
const result = await libraryApi.favoriteStatus(id);
|
|
@@ -239,13 +101,12 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
239
101
|
}
|
|
240
102
|
const cloud = createCloudImageActions({
|
|
241
103
|
reference, assertAccount,
|
|
242
|
-
|
|
104
|
+
favoriteStatus,
|
|
243
105
|
setFavorite:(libraryFileId,favoriteId,isFavorited) => native.setFavorite(scope,{libraryFileId,favoriteId,isFavorited,surface:"library_grid",showToast:true}),
|
|
244
106
|
upload:async file => {
|
|
245
|
-
const bytes = Uint8Array.from(atob(file.base64),value => value.charCodeAt(0));
|
|
246
107
|
const pendingToast = toast().info(message("minecodex.images.saving","Saving image to Library…"),{duration:0,hasCloseButton:false});
|
|
247
108
|
try {
|
|
248
|
-
const uploaded = await native.upload(scope,
|
|
109
|
+
const uploaded = await native.upload(scope,uploadFile(file),{
|
|
249
110
|
libraryPersistenceMode:"required",storeInLibrary:true,uploadPurpose:"library",beforeRequest:assertAccount,
|
|
250
111
|
});
|
|
251
112
|
return uploaded.libraryFileId;
|
|
@@ -255,13 +116,21 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
255
116
|
async function folder(image) {
|
|
256
117
|
const images=Array.isArray(image)?image:[image];
|
|
257
118
|
const registered=[];
|
|
258
|
-
|
|
259
|
-
const
|
|
119
|
+
const entries=[];
|
|
120
|
+
for (const item of images) {
|
|
121
|
+
const result=await cloud.registered(item);
|
|
122
|
+
const node=await libraryApi.fileNode(result.libraryFileId);
|
|
123
|
+
assertAccount();
|
|
124
|
+
registered.push(result);
|
|
125
|
+
entries.push({node});
|
|
126
|
+
}
|
|
127
|
+
const created=registered.some(item=>item.created);
|
|
260
128
|
let moved = false;
|
|
261
129
|
folderDialog = h(native.MoveDialog,{
|
|
262
130
|
accountId,userId,entries,initialPath:[],
|
|
263
131
|
onMoved:() => {moved=true;},
|
|
264
132
|
onClose:() => {
|
|
133
|
+
if (!isCurrent()) return;
|
|
265
134
|
folderDialog=null;render(currentContent);
|
|
266
135
|
if (!moved) toast().info(created
|
|
267
136
|
? message("minecodex.images.folderCanceledSaved","Image saved to Library, but not added to a folder.")
|
|
@@ -271,7 +140,7 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
271
140
|
render(currentContent);
|
|
272
141
|
}
|
|
273
142
|
function operationError(error) {
|
|
274
|
-
if (
|
|
143
|
+
if (!isCurrent()) return;
|
|
275
144
|
toast().danger(error.code === "UPLOAD_UNCONFIRMED"
|
|
276
145
|
? message("minecodex.images.uploadUnconfirmed","Image upload is pending or could not be confirmed. Check Library before uploading again.")
|
|
277
146
|
: message("minecodex.images.libraryActionFailed","Couldn’t complete the Library action. Please try again."));
|
|
@@ -279,6 +148,7 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
279
148
|
}
|
|
280
149
|
const root = ReactDOM.createRoot(container, {onUncaughtError:error => {failure=error;onRetry(error);}});
|
|
281
150
|
function render(content) {
|
|
151
|
+
if (!isCurrent()) return;
|
|
282
152
|
currentContent = content;
|
|
283
153
|
let tree = h(React.Fragment,null,content,folderDialog,removeDialog,selectionToolbar(),canvasImage?h(ImageCanvas,{key:canvasImage.id,image:canvasImage}):null);
|
|
284
154
|
for (const provider of context.providers) tree = h(provider.type, {value:provider.value}, tree);
|
|
@@ -293,7 +163,7 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
293
163
|
selectedIds=next.selectedIds;selectionAnchor=next.anchor;refresh();
|
|
294
164
|
}
|
|
295
165
|
const selectedImages = () => currentImages.filter(image=>selectedIds.has(image.id));
|
|
296
|
-
function clearSelection() {selectedIds=new Set();selectionAnchor=null;removeDialog=null;refresh();}
|
|
166
|
+
function clearSelection() {openRevision++;selectedIds=new Set();selectionAnchor=null;removeDialog=null;refresh();}
|
|
297
167
|
async function performBatch(action) {
|
|
298
168
|
if (batchBusy) return;
|
|
299
169
|
batchBusy=true;refresh();
|
|
@@ -307,13 +177,13 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
307
177
|
h(native.DialogContent,{as:"form",onSubmit:event=>{
|
|
308
178
|
event.preventDefault();if(pending)return;pending=true;update();refresh();
|
|
309
179
|
void requestHostAction("local-image-source",{operation:"hide",ids:images.map(image=>image.id)}).then(result=>{
|
|
310
|
-
if(
|
|
180
|
+
if(!isCurrent())return;
|
|
311
181
|
const hidden=new Set(result.hiddenIds);
|
|
312
182
|
currentImages=currentImages.filter(image=>!hidden.has(image.id));
|
|
313
183
|
selectedIds=new Set([...selectedIds].filter(id=>!hidden.has(id)));
|
|
314
184
|
pending=false;removeDialog=null;onHidden?.(result.hiddenIds);refresh();
|
|
315
185
|
toast().info(message("minecodex.images.hidden","Hidden from Image Gen"));
|
|
316
|
-
}).catch(error=>{pending=false;update();refresh();operationError(error);});
|
|
186
|
+
}).catch(error=>{if(!isCurrent())return;pending=false;update();refresh();operationError(error);});
|
|
317
187
|
}},h(native.ConfirmationContent,{
|
|
318
188
|
title:context.intl.formatMessage({id:"minecodex.images.hideTitle",defaultMessage:"Delete {count, plural, one {image} other {# images}} from Image Gen?"},{count:images.length}),
|
|
319
189
|
description:message("minecodex.images.hideDescription","These images will only be hidden from Image Gen. Local originals and cloud copies will be kept."),
|
|
@@ -347,11 +217,13 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
347
217
|
}
|
|
348
218
|
document.addEventListener("keydown",onSelectionKey);
|
|
349
219
|
async function startChat(images) {
|
|
220
|
+
assertAccount();
|
|
350
221
|
const conversationId=native.createChatDraft(context.scope,[]);
|
|
351
222
|
native.switchMode(context.scope,"chat");
|
|
352
223
|
const files=[];
|
|
353
|
-
for(const image of images){const file=await reference(image,"upload-file");files.push(native.imageFile(`data:image/png;base64,${file.base64}`,file.name).file);}
|
|
224
|
+
for(const image of images){const file=await reference(image,"upload-file");assertAccount();files.push(native.imageFile(`data:image/png;base64,${file.base64}`,file.name).file);}
|
|
354
225
|
if (!await native.attachChatImages(context.scope,conversationId,files,{isTemporaryChat:false})) throw new Error("Could not attach selected images");
|
|
226
|
+
assertAccount();
|
|
355
227
|
const navigate=context.scope.get(native.routerAtom).prepareNavigation("/",{state:{codexAppMode:"chat",initialChatGptConversationId:conversationId,focusComposerNonce:Date.now()}});
|
|
356
228
|
if(!navigate)throw new Error("Native Chat navigation is unavailable");
|
|
357
229
|
clearSelection();await navigate();
|
|
@@ -359,9 +231,19 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
359
231
|
function localImage(image) {
|
|
360
232
|
return {id:image.imageGenerationItemId, src:image.sourcePath, previewSrc:image.sourcePath, alt:image.name, tabTitle:image.name, width:image.width, height:image.height};
|
|
361
233
|
}
|
|
362
|
-
function open(image, options) {
|
|
234
|
+
async function open(image, options) {
|
|
235
|
+
if (!options) {
|
|
236
|
+
assertAccount();
|
|
237
|
+
const revision=++openRevision;
|
|
238
|
+
const canvas=await modules.canvas();
|
|
239
|
+
// 切换来源或重置列表后,不让晚到的画布覆盖当前页面。
|
|
240
|
+
if(revision!==openRevision || !isCurrent())return false;
|
|
241
|
+
native={...native,...canvas};
|
|
242
|
+
opened.set(image.sourcePath,image);
|
|
243
|
+
canvasImage=image;refresh();return true;
|
|
244
|
+
}
|
|
245
|
+
if (signal?.aborted || !sameAccount()) return;
|
|
363
246
|
opened.set(image.sourcePath, image);
|
|
364
|
-
if (!options) {canvasImage=image;refresh();return;}
|
|
365
247
|
const {readOnly=false,scope=context.scope,chatGptConversationId}=options;
|
|
366
248
|
const result = native.openImage(scope, {
|
|
367
249
|
chatGptConversationId, alt:image.name, title:image.name, attachmentId:`image-playground:${image.imageGenerationItemId}`,
|
|
@@ -408,8 +290,7 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
408
290
|
assertCurrent();
|
|
409
291
|
if(!attachment.current)attachment.current=(async()=>{
|
|
410
292
|
const file=await reference(image,"upload-file");assertCurrent();
|
|
411
|
-
const
|
|
412
|
-
const uploaded=await native.upload(context.scope,new File([bytes],file.name,{type:"image/png"}),{storeInLibrary:false,beforeRequest:assertCurrent,signal:abort.signal});
|
|
293
|
+
const uploaded=await native.upload(context.scope,uploadFile(file),{storeInLibrary:false,beforeRequest:assertCurrent,signal:abort.signal});
|
|
413
294
|
return {...uploaded,width:image.width,height:image.height,imageReference:true};
|
|
414
295
|
})().catch(error=>{attachment.current=null;throw error;});
|
|
415
296
|
return attachment.current;
|
|
@@ -463,6 +344,7 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
463
344
|
return h(native.CanvasLayout,{assetName:image.name,contentOwnsHeader:true,onClose:requestClose,children:canvas});
|
|
464
345
|
}
|
|
465
346
|
async function chatAbout(image) {
|
|
347
|
+
assertAccount();
|
|
466
348
|
const conversationId = native.createChatDraft(context.scope, []);
|
|
467
349
|
native.switchMode(context.scope, "chat");
|
|
468
350
|
const navigate = context.scope.get(native.routerAtom).prepareNavigation("/", {
|
|
@@ -471,24 +353,31 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
471
353
|
if (!navigate) throw new Error("Native Chat navigation is unavailable");
|
|
472
354
|
await navigate();
|
|
473
355
|
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
474
|
-
|
|
356
|
+
// 导航会正常卸载图库;只把面板移交给目标 Chat 的可见输入框。
|
|
357
|
+
if (signal?.aborted || !sameAccount()) return;
|
|
358
|
+
const composer = codex.composer(null,{purpose:"insertion"})?.editable;
|
|
475
359
|
if (!composer) throw new Error("Native Chat composer is unavailable");
|
|
476
360
|
const chat = environment(composer);
|
|
477
|
-
open(image, {readOnly:true, scope:chat.scope, chatGptConversationId:conversationId});
|
|
361
|
+
await open(image, {readOnly:true, scope:chat.scope, chatGptConversationId:conversationId});
|
|
478
362
|
}
|
|
479
363
|
function ImageCard({image}) {
|
|
480
364
|
const [saved,setSaved] = React.useState(false);
|
|
481
365
|
const [busy,setBusy] = React.useState(false);
|
|
366
|
+
const active=React.useRef(true);
|
|
367
|
+
const favoriteRevision=React.useRef(0);
|
|
368
|
+
React.useEffect(()=>{active.current=true;return()=>{active.current=false;favoriteRevision.current++;};},[]);
|
|
482
369
|
async function perform(action) {
|
|
483
370
|
if (busy) return;
|
|
484
371
|
setBusy(true);
|
|
485
|
-
try {await action();} catch(error) {operationError(error);} finally {setBusy(false);}
|
|
372
|
+
try {assertAccount();await action();} catch(error) {operationError(error);} finally {if(active.current&&isCurrent())setBusy(false);}
|
|
486
373
|
}
|
|
487
374
|
async function refreshFavorite(open) {
|
|
375
|
+
const revision=++favoriteRevision.current;
|
|
488
376
|
if (!open) return;
|
|
489
377
|
try {
|
|
490
378
|
const cached = await reference(image,"reference");
|
|
491
|
-
|
|
379
|
+
const value=cached.libraryFileId ? !!(await favoriteStatus(cached.libraryFileId)) : false;
|
|
380
|
+
if (active.current && isCurrent() && revision===favoriteRevision.current) setSaved(value);
|
|
492
381
|
} catch(error) {console.error("MineCodex favorite status",error);}
|
|
493
382
|
}
|
|
494
383
|
const resolved = native.useImage({src:image.sourcePath, conversationId:image.threadId, hostId:"local", shouldLoadFileDataUrl:true});
|
|
@@ -509,13 +398,17 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
509
398
|
const promptIcon = menuGlyph ? h("span", {className:"inline-flex size-4 items-center justify-center", "aria-hidden":true, dangerouslySetInnerHTML:{__html:menuGlyph.outerHTML}}) : null;
|
|
510
399
|
const menuItems = [
|
|
511
400
|
{id:"chat-about", icon:native.chatIcon, text:context.intl.formatMessage({id:"libraryNext.actions.chatAboutItem",defaultMessage:"Chat about this"}),onSelect:() => {void chatAbout(image).catch(onRetry);}},
|
|
401
|
+
{id:"view-prompts", icon:promptIcon, text:context.intl.formatMessage({id:"minecodex.images.viewPrompts",defaultMessage:"View prompts"}),onSelect:() => {void open(image).then(opened=>{if(opened&&isCurrent())onPrompt(image);}).catch(operationError);}},
|
|
512
402
|
{id:"favorite",icon:native.favoriteIcon(saved),text:saved
|
|
513
403
|
? message("library.favorites.remove","Remove from Favorites")
|
|
514
|
-
: message("library.favorites.add","Add to Favorites"),onSelect:() => {void perform(async () =>
|
|
404
|
+
: message("library.favorites.add","Add to Favorites"),onSelect:() => {void perform(async () => {
|
|
405
|
+
favoriteRevision.current++;
|
|
406
|
+
const value=await cloud.favorite(image);
|
|
407
|
+
if(active.current&&isCurrent()){favoriteRevision.current++;setSaved(value);}
|
|
408
|
+
});}},
|
|
515
409
|
action("download-a-copy", native.downloadIcon, {id:"libraryNext.actions.download", defaultMessage:"Download"}),
|
|
516
410
|
{id:"folder",icon:native.folderIcon,text:message("libraryNext.actions.addToFolder","Add to folder"),onSelect:() => {void perform(() => folder(image));}},
|
|
517
411
|
{id:"delete",icon:native.deleteIcon,tone:"danger",text:message("libraryNext.actions.delete","Delete"),onSelect:()=>confirmRemove([image])},
|
|
518
|
-
{id:"view-prompts", icon:promptIcon, text:context.intl.formatMessage({id:"minecodex.images.viewPrompts",defaultMessage:"View prompts"}),onSelect:() => {open(image);onPrompt(image);}},
|
|
519
412
|
].filter(Boolean);
|
|
520
413
|
const dropdown = h(native.Dropdown, {
|
|
521
414
|
contentWidth:"menu", align:"end", onOpenChange:refreshFavorite,
|
|
@@ -534,17 +427,17 @@ export function createNativeImageSource(codex, { read = async url => (await fetc
|
|
|
534
427
|
},children:h(native.Checkbox,{className:"cursor-interaction",variant:"overlay",checked,disabled:batchBusy,"aria-label":context.intl.formatMessage({id:"libraryNext.selection.item",defaultMessage:"Select {name}"},{name:image.name}),onCheckedChange:value=>select(image,value===true)})});
|
|
535
428
|
return h(native.Card,{name:image.name,selection,selected:checked,selectionActive:selectedIds.size>0,onSelect:event=>{
|
|
536
429
|
if(selectedIds.size || event.shiftKey){select(image,!checked,event.shiftKey);return true;}return false;
|
|
537
|
-
},onOpen:() => onOpen(image,open),actions:dropdown,children:h("span",{className:"relative flex min-h-0 w-full flex-1 items-center justify-center",children:h("span",{className:"relative flex w-full items-center justify-center",style:{aspectRatio:cardRatio},children:picture})})});
|
|
430
|
+
},onOpen:() => {void Promise.resolve(onOpen(image,open)).catch(operationError);},actions:dropdown,children:h("span",{className:"relative flex min-h-0 w-full flex-1 items-center justify-center",children:h("span",{className:"relative flex w-full items-center justify-center",style:{aspectRatio:cardRatio},children:picture})})});
|
|
538
431
|
}
|
|
539
432
|
return {
|
|
540
433
|
render({images,loading}) {
|
|
541
434
|
if (failure) throw failure;
|
|
542
435
|
currentImages=images;currentLoading=loading;
|
|
543
|
-
if(loading){selectedIds=new Set();selectionAnchor=null;removeDialog=null;}
|
|
436
|
+
if(loading){openRevision++;selectedIds=new Set();selectionAnchor=null;removeDialog=null;scope.set(native.nativeSelectedIds,new Set());}
|
|
544
437
|
refresh();
|
|
545
438
|
},
|
|
546
439
|
clearSelection,
|
|
547
|
-
dispose() {disposed=true;document.removeEventListener("keydown",onSelectionKey);root.unmount();},
|
|
440
|
+
dispose() {disposed=true;openRevision++;document.removeEventListener("keydown",onSelectionKey);root.unmount();},
|
|
548
441
|
};
|
|
549
442
|
}
|
|
550
443
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// 工作流只管理云端身份与操作顺序;上传协议和原生组件由 Adapter 提供。
|
|
2
|
-
export function createCloudImageActions({reference,upload,
|
|
2
|
+
export function createCloudImageActions({reference,upload,favoriteStatus,setFavorite,assertAccount}) {
|
|
3
3
|
const pending = new Map();
|
|
4
4
|
function registered(image) {
|
|
5
5
|
if (!pending.has(image.id)) {
|
|
6
6
|
const task = (async () => {
|
|
7
7
|
assertAccount();
|
|
8
8
|
const cached = await reference(image,"reference");
|
|
9
|
-
|
|
9
|
+
assertAccount();
|
|
10
|
+
if (cached.libraryFileId) return {libraryFileId:cached.libraryFileId,created:false};
|
|
10
11
|
const unconfirmed = () => Object.assign(new Error("Library upload is pending or could not be confirmed"),{code:"UPLOAD_UNCONFIRMED"});
|
|
11
12
|
if (cached.status === "uploading") throw unconfirmed();
|
|
12
13
|
const file = await reference(image,"upload-file");
|
|
@@ -18,7 +19,7 @@ export function createCloudImageActions({reference,upload,fileNode,favoriteStatu
|
|
|
18
19
|
// 先保存身份;收藏失败或取消文件夹选择后仍可复用上传结果。
|
|
19
20
|
await reference(image,"register",{libraryFileId,fingerprint:file.fingerprint});
|
|
20
21
|
assertAccount();
|
|
21
|
-
return {
|
|
22
|
+
return {libraryFileId,created:true};
|
|
22
23
|
})();
|
|
23
24
|
pending.set(image.id,task);
|
|
24
25
|
const release = () => {if (pending.get(image.id) === task) pending.delete(image.id);};
|
|
@@ -29,10 +30,11 @@ export function createCloudImageActions({reference,upload,fileNode,favoriteStatu
|
|
|
29
30
|
return {
|
|
30
31
|
registered,
|
|
31
32
|
async favorite(image) {
|
|
32
|
-
const {
|
|
33
|
-
const favoriteId = await favoriteStatus(
|
|
33
|
+
const {libraryFileId} = await registered(image);
|
|
34
|
+
const favoriteId = await favoriteStatus(libraryFileId);
|
|
35
|
+
assertAccount();
|
|
36
|
+
if (!await setFavorite(libraryFileId,favoriteId,!favoriteId)) throw new Error("Library favorite could not be changed");
|
|
34
37
|
assertAccount();
|
|
35
|
-
if (!await setFavorite(entry.node.id,favoriteId,!favoriteId)) throw new Error("Library favorite could not be changed");
|
|
36
38
|
return !favoriteId;
|
|
37
39
|
},
|
|
38
40
|
};
|
|
@@ -183,7 +183,8 @@ export function installLocalImageSource(config, signal, sources, native) {
|
|
|
183
183
|
});
|
|
184
184
|
observer.observe(document.body, {childList:true, subtree:true, attributes:true, attributeFilter:["aria-selected"]});
|
|
185
185
|
void scan();
|
|
186
|
-
|
|
186
|
+
let cleaned = false;
|
|
187
|
+
const cleanup = () => {if(cleaned)return;cleaned=true;observer.disconnect();unmount();pendingPrompt=null;};
|
|
187
188
|
signal.addEventListener("abort", cleanup, {once:true});
|
|
188
189
|
return {
|
|
189
190
|
cleanup,
|
|
@@ -3,6 +3,7 @@ const PANEL_ID = "minecodex-library-prompt";
|
|
|
3
3
|
const zh = () => (document.documentElement.lang || navigator.language || "en").toLowerCase().startsWith("zh");
|
|
4
4
|
const label = (en, cn) => zh() ? cn : en;
|
|
5
5
|
let mounted = null;
|
|
6
|
+
let localImageSource = null;
|
|
6
7
|
const libraryApi=createLibraryApi();
|
|
7
8
|
let scheduled = false;
|
|
8
9
|
let candidate = null;
|
|
@@ -257,6 +258,9 @@ function localPromptRequested(){openPendingLocalPrompt();void scan();}
|
|
|
257
258
|
document.addEventListener("minecodex:image-prompt",localPromptRequested);
|
|
258
259
|
const localeObserver = new MutationObserver(() => mounted?.refreshLocale());
|
|
259
260
|
localeObserver.observe(document.documentElement, { attributes:true, attributeFilter:["lang"] });
|
|
261
|
+
localImageSource = installLocalImageSource(config, signal, createImageSourcesAdapter(config.codex), createNativeImageSource(config.codex, {
|
|
262
|
+
requestHostAction:config.requestHostAction, libraryApi, createCloudImageActions, signal,
|
|
263
|
+
}));
|
|
260
264
|
const observer = new MutationObserver(() => {
|
|
261
265
|
if (scheduled) return;
|
|
262
266
|
scheduled = true;
|
|
@@ -270,7 +274,8 @@ function escape(event) {
|
|
|
270
274
|
closePanel(mounted, true);
|
|
271
275
|
}
|
|
272
276
|
document.addEventListener("keydown", escape, true);
|
|
273
|
-
|
|
277
|
+
let cleaned = false;
|
|
278
|
+
const cleanup = () => {if(cleaned)return;cleaned=true;document.removeEventListener("minecodex:image-prompt",localPromptRequested);candidate?.request.abort();candidate=null;observer.disconnect();localeObserver.disconnect();document.removeEventListener("keydown",escape,true);unmount();localImageSource.cleanup();};
|
|
274
279
|
signal.addEventListener("abort", cleanup, { once: true });
|
|
275
280
|
scan();
|
|
276
281
|
return cleanup;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createCloudImageActions } from "./frontend/cloud-image-actions.mjs";
|
|
2
2
|
import { createImageSourcesAdapter } from "./adapters/codex/image-sources.mjs";
|
|
3
3
|
import { createNativeImageSource } from "./adapters/codex/native-image-source.mjs";
|
|
4
|
+
import {createNativeImageModules, discoverFilePreviewComposerBindings} from "./adapters/codex/native-image-modules.mjs";
|
|
4
5
|
import { installLocalImageSource } from "./frontend/local-image-source.mjs";
|
|
5
6
|
import { createLibraryApi } from "./adapters/codex/library-api.mjs";
|
|
6
7
|
import { createLibraryViewerAdapter } from "./adapters/codex/library-viewer.mjs";
|
|
@@ -9,10 +10,7 @@ import { promptResolverSource } from "./adapters/codex/prompt-resolver-source.mj
|
|
|
9
10
|
|
|
10
11
|
export async function createPageScriptSource() {
|
|
11
12
|
const source=await readFile(new URL("./library-prompt-page.mjs",import.meta.url),"utf8");
|
|
12
|
-
const functions={createCloudImageActions,createImageSourcesAdapter,createNativeImageSource,installLocalImageSource,createLibraryApi,createLibraryViewerAdapter};
|
|
13
|
+
const functions={createCloudImageActions,createImageSourcesAdapter,discoverFilePreviewComposerBindings,createNativeImageModules,createNativeImageSource,installLocalImageSource,createLibraryApi,createLibraryViewerAdapter};
|
|
13
14
|
const declarations=Object.entries(functions).map(([name,fn])=>`const ${name}=${fn.toString()};`).join("\n");
|
|
14
|
-
return `${declarations}
|
|
15
|
-
let localImageSource=null;
|
|
16
|
-
const promptFromConversation=${promptResolverSource()};
|
|
17
|
-
${source.replace("const observer =", "localImageSource=installLocalImageSource(config,signal,createImageSourcesAdapter(config.codex),createNativeImageSource(config.codex,{requestHostAction:config.requestHostAction,libraryApi:createLibraryApi(),createCloudImageActions}));\nconst observer =").replace("return cleanup;", "return () => { cleanup(); localImageSource.cleanup(); };")}`;
|
|
15
|
+
return `${declarations}\nconst promptFromConversation=${promptResolverSource()};\n${source}`;
|
|
18
16
|
}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"zh-CN": "模型滑条"
|
|
8
8
|
},
|
|
9
9
|
"description": {
|
|
10
|
-
"en": "
|
|
11
|
-
"zh-CN": "
|
|
10
|
+
"en": "Switch models and thinking levels. Keep favorites within reach.",
|
|
11
|
+
"zh-CN": "切换模型与思考强度,快速找到常用收藏。"
|
|
12
12
|
},
|
|
13
13
|
"entry": {
|
|
14
14
|
"kind": "composer-model-selector"
|
package/features/notes/DESIGN.md
CHANGED
|
@@ -54,7 +54,7 @@ Header 为 Prompts;右侧依次为排序图标、分类调节图标、常驻
|
|
|
54
54
|
|
|
55
55
|
拖拽排序使用等高 Drop Placeholder,拖影跟随指针并显式按源行圆角裁切,不留下矩形外边界;同组条目以 180ms ease-out 让位,落位淡入 140ms。放到范围外或取消恢复原位,不写入排序。遵循 prefers-reduced-motion。
|
|
56
56
|
|
|
57
|
-
页面选择通用 Page 的 `sidebar:"none"` 分支,自身不带常驻二级侧栏,保留原生 Home、Space、Scheduled sidebar
|
|
57
|
+
页面选择通用 Page 的 `sidebar:"none"` 分支,自身不带常驻二级侧栏,保留原生 Home、Space、Scheduled sidebar 的悬浮叠加与导航行为。关闭页面恢复原页面位置,不改变用户的侧栏展开偏好。原生二级入口(如 Space → Images)提交导航后,通过共享 Page 的 `onNavigate` 关闭 Prompts、编辑浮层并释放底层 inert;保留用户刚选择的目标路由。仅悬浮展开侧栏不退出 Prompts。
|
|
58
58
|
|
|
59
59
|
提示词编辑沿用原生 Project 的复合标题输入:40px 图标段位于标题前,细分隔线,共用圆角边框与 focus-within 状态。图标选择入口不再单独占一行;Title 通过 aria-label 标识。
|
|
60
60
|
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"zh-CN": "提示词"
|
|
8
8
|
},
|
|
9
9
|
"description": {
|
|
10
|
-
"en": "Organize prompts
|
|
11
|
-
"zh-CN": "
|
|
10
|
+
"en": "Organize reusable prompts and insert them with one click.",
|
|
11
|
+
"zh-CN": "整理常用提示词,一键填入输入框。"
|
|
12
12
|
},
|
|
13
13
|
"surfaceUrl": "http://127.0.0.1:47832/",
|
|
14
14
|
"healthUrl": "http://127.0.0.1:47832/api/health",
|
|
@@ -365,7 +365,7 @@ function openPage() {
|
|
|
365
365
|
pageFocus = document.activeElement;
|
|
366
366
|
page = element("section");
|
|
367
367
|
page.setAttribute(MARKER, "page");
|
|
368
|
-
config.ui.page(page, { sidebar:"none" });
|
|
368
|
+
config.ui.page(page, { sidebar:"none", onNavigate:() => closePage(false, false) });
|
|
369
369
|
page.setAttribute("aria-label", text("Prompts", "提示词"));
|
|
370
370
|
document.body.append(page);
|
|
371
371
|
// 只隔离主内容的焦点,保留导航栏与原生窗口控件。
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"label": { "en": "Session Status", "zh-CN": "会话状态" },
|
|
6
6
|
"description": {
|
|
7
|
-
"en": "
|
|
8
|
-
"zh-CN": "
|
|
7
|
+
"en": "See response speed, token usage and context space.",
|
|
8
|
+
"zh-CN": "查看输出速度、Token 用量与上下文占用。"
|
|
9
9
|
},
|
|
10
10
|
"entry": { "kind": "page-script", "script": "src/session-status.mjs", "sourceFactory": "src/injection-source.mjs" },
|
|
11
11
|
"icon": {
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
保留当前任务所在分类与 Project,收起其他分支。再次点击恢复进入聚焦前的展开状态;聚焦期间切换任务跟随路径。用户手动调整分支时退出聚焦并保留手动布局,无撤销浮层。
|
|
6
6
|
|
|
7
7
|
- 入口:展开侧栏的 Activity 与 Search 之间,随原生侧栏一起隐藏。
|
|
8
|
-
-
|
|
9
|
-
- 控件:继承原生 Activity 的 Button 类与 28px
|
|
10
|
-
- 中英文标题:Focus current task / 聚焦当前任务;
|
|
8
|
+
- 图标:整理操作使用 Lucide list-chevrons-down-up;可恢复布局时切换为 list-chevrons-up-down。16px,24px viewBox,描边 1.575 units(实际 1.05px)。
|
|
9
|
+
- 控件:继承原生 Activity 的 Button 类与 28px 热区。两种状态均使用 secondary / ghost,以图标表达下一步动作,不显示蓝色模式选中态;aria-pressed 保留可恢复状态的可访问语义。
|
|
10
|
+
- 中英文标题:Focus current task / 聚焦当前任务;Restore layout / 恢复布局。
|
|
11
11
|
- 折叠通过原生回调执行;React 元数据仅用于读取稳定分类、项目和会话身份。
|
|
12
12
|
- 无可识别当前路径时禁用;Activity 列表展示期间暂停整理。
|
|
13
13
|
- 状态仅作用于当前窗口;卸载不额外改变用户布局。
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
显式使用中性 data-color 与 data-variant,直接复用原生语义 token 适配明暗模式,不写死颜色或背景色。Activity 开启时按钮原生 disabled,暂停聚焦操作,关闭后恢复可用性。
|
|
16
16
|
|
|
17
17
|
Tooltip 由共享组件库通过 `data-minecodex-tooltip` 管理;插件只提供随聚焦状态变化的文案,不维护独立材质、定位或显示事件。克隆入口时移除原控件的 `title` 与 `aria-describedby`。
|
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
"zh-CN": "侧栏聚焦"
|
|
8
8
|
},
|
|
9
9
|
"description": {
|
|
10
|
-
"en": "
|
|
11
|
-
"zh-CN": "
|
|
10
|
+
"en": "Fold unrelated branches to keep the current task in view.",
|
|
11
|
+
"zh-CN": "收起无关分支,专注当前任务路径。"
|
|
12
12
|
},
|
|
13
13
|
"entry": {
|
|
14
14
|
"kind": "page-script",
|
|
15
15
|
"script": "src/sidebar-focus.mjs",
|
|
16
16
|
"sourceFactory": "src/injection-source.mjs"
|
|
17
17
|
},
|
|
18
|
+
"icon": {"viewBox": "0 0 24 24", "markup": "<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" />\n <path d=\"M9 3v18\" />"},
|
|
18
19
|
"placement": {
|
|
19
20
|
"order": 80
|
|
20
21
|
}
|