minecodex 2.0.6 → 2.0.7
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 +5 -1
- package/features/images/DESIGN.md +7 -3
- package/features/images/src/adapters/codex/native-image-modules.mjs +11 -2
- package/features/images/src/adapters/codex/native-image-source.mjs +44 -22
- package/features/images/src/assets/prompts-menu.svg +15 -0
- package/features/images/src/library-prompt-page.mjs +5 -4
- package/features/images/src/library-prompt-source.mjs +2 -1
- package/package.json +1 -1
- package/packages/runtime-host/src/codex-injection.mjs +3 -1
- package/packages/ui/assets/arrow-up-right.svg +2 -3
- package/packages/ui/assets/minecodex-menu-16.svg +7 -0
- package/packages/ui/assets/minecodex-menu-20.svg +7 -0
- package/packages/ui/src/components/account-menu.mjs +16 -2
package/DESIGN.md
CHANGED
|
@@ -183,6 +183,10 @@ macOS 后台服务 App 在明确的原生资源修订 1.0.1 中接入实心图
|
|
|
183
183
|
|
|
184
184
|
### 账号菜单中的 MineCodex 入口
|
|
185
185
|
|
|
186
|
-
Codex 26.930.21537(2026-10-03
|
|
186
|
+
Codex 26.930.21537(2026-10-03 安装包源码与隔离 DOM):`profile-dropdown-items` 的 Settings 使用原生菜单行,紧接 Help 前的 Separator;`onOpenSettings` / `settingsShortcut` 标识其行为,外层 `onClose` 关闭账号面板。MineCodex 行插在 Settings 与 Separator 之间,复制脱离 React 的原生行,保留原始 Settings 及其所属层级。左侧使用实心项目标识,右侧使用 Lucide ArrowUpRight;右侧沿用快捷键区域的间距,图标使用原生 Help 右侧箭头的 `text-tertiary` 色与 opacity 1(当前浅色为 rgba(13,13,13,0.482)),不套左侧图标的 0.75 / hover、focus 时 1 的透明度。隔离 DOM 确认左侧普通态与 Settings 同为 rgb(13,13,13)、opacity 0.75,hover / focus 时为 1。
|
|
187
|
+
|
|
188
|
+
左侧保留原生 CompactSource / LeadingSource 两套结构,只替换其中 SVG;分别继承原生 SVG 的 width、height、class 和 style。菜单标识复用当前 `codex-light-16` / `codex-light-20` 的真实填充外圈路径,引用存于 `assets/brand/references/codex-menu-16.svg` / `codex-menu-20.svg`;内部镐子仍从品牌源生成,由 `npm run build:icons` 输出两套 `packages/ui/assets/minecodex-menu-*.svg`。不能将 24px 品牌外圈缩小代替原生菜单的路径粗细。账号图标沿用原生 `currentColor` 与 `--profile-menu-icon-opacity`(桌面默认 0.75,行 hover / focus 时为 1),不另设固定灰色。此项为安装包源码核对,实际窗口视觉仍待手动验收。
|
|
189
|
+
|
|
190
|
+
右侧箭头的两段几何合并在单个 SVG path 中一次描边,避免 `currentColor` 自带 alpha 时,两个独立 path 在交叉点重复叠色。保留原资源的形状与端点规则;24px viewBox 中 stroke-width 为 1.575,在 16px 显示时实际线宽 1.05px,与原生 Help 箭头的线条粗细对齐。
|
|
187
191
|
|
|
188
192
|
入口随账号菜单重建恢复且只出现一次;补齐 Settings → MineCodex → Help 的上下键导航与 Enter/Space,保留原生 Escape 关闭行为。点击经已认证 Host bridge 调用 `mcx gui`;成功后关闭菜单,失败时显示实际错误。原生 UI 手动验收本次因 Computer Use 拒绝操作应用而未完成;隔离 DOM 契约页验证了位置、明暗颜色、点击、键盘、重建去重与清理。
|
|
@@ -12,6 +12,8 @@ hover sidebars and the native full-page shell remain owned by Codex.
|
|
|
12
12
|
|
|
13
13
|
## Image Gen
|
|
14
14
|
|
|
15
|
+
全屏图片查看器使用原生 Canvas 的 `presentation:"overlay"`,由外层 CanvasLayout 的 `bg-surface` 提供背景。Codex 26.930.21537(2026-10-03 源码与 live DOM):`dialog` / `panel` 分支会在内部画布叠加 `bg-surface-secondary`(当前浅色 #f7f7f7),不能用于这里的全屏画布。保留 standalone、原生 viewer header、底部编辑工具栏和 Composer 接入。
|
|
16
|
+
|
|
15
17
|
Codex 26.930.21537(2026-10-02 隔离实测):通过原生 `variant="browse"` 的 Image sources 契约接入。保留原生标签节点及路由,在插件拥有的 React root 中复用同一个原生 Tabs 组件,追加 Image Gen;退出时恢复原节点。原生上下文由 Adapter 从当前已提交的组件祖先读取,业务层不遍历 Fiber。
|
|
16
18
|
|
|
17
19
|
本地来源复用 ImageLibrary、ThreadMetadataIndex 及文件监听,索引存放在当前 runtime profile;按生成时间倒序,每页 36 张,滚动增量加载,搜索复用原生输入框。稳定身份为 threadId + imageGenerationItemId,不猜测云端 ID 与本地文件的关系。
|
|
@@ -20,11 +22,13 @@ Codex 26.930.21537(2026-10-02 隔离实测):通过原生 `variant="browse"
|
|
|
20
22
|
|
|
21
23
|
点击图片进入原生 Library 主内容 viewport 内的图片画布,保留 navbar、Space 二级侧栏和窗口栏;不打开 side panel,也不使用覆盖整窗的 Dialog。画布、缩放、下载、打开、Markup、Comment、Remove BG、Erase、Resize 和底部 “Ask about this file” 输入框复用原生组件。底部直接使用画布的原生 `renderComposer` / overlay 承载,沿用底部留白、居中、避让和工具状态显隐;不得另建 absolute 底部输入框。进入 Comment 时隐藏大输入框,退出时恢复;原生拖拽工具栏保留位置记忆及 Home 居中重置。初始缩放与每张图的 Zoom to fit 使用原生 `zoom` 契约,`manualZoomPercent` 初始为 `null`,不自算比例,也不继承上一张图的手动缩放。关闭回到原列表并恢复交互,标注中的关闭交由原生编辑器处理。
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
2026-10-03 跟进原生文件预览:底部复用当前 File Composer 的本地任务分支,宽度 `min(48rem, calc(100vw - 2rem))`、`auto-single-line`、`surfaceVariant:"inline"`、隐藏 utility bar 和运行位置控件,输入布局、模型控件、权限、语音及发送按钮均由原生组件决定,不再固定旧 Chat 的单行组件。画布在主内容 viewport 内,因此只在此画布范围内去掉原生 overlay header 容器的窗口交通灯左右避让,保留原生 header 自身 8px inset。背景、缩放、Open、下载、关闭、菜单及编辑状态使用原生 Canvas;按当前产品要求保留黑色编辑行动条。底部预留区通过 ResizeObserver 读取实际 File Composer 高度,加原生 `spacing × 4` 底部 inset;不沿用原生旧输入框的 118px 最小预留。行动条默认位置与拖拽下边界共用此画布尺寸,输入框展开、收起或隐藏时同步更新;默认紧凑输入框 44px 时预留为 60px,行动条与输入框间距为 16px。
|
|
26
|
+
|
|
27
|
+
本地来源以真实路径、来源 thread 和生成项 ID 接入;底部输入框首次发送时通过原生文件服务准备本地临时附件,不上传云端、不伪造 Library 身份。关闭或切换账号后晚到的附件会释放;提交接受后沿用原生本地会话导航。黑色行动条的生成编辑仍在明确提交编辑时通过原生上传准备临时附件,同一画布复用编辑附件,查看不上传到 Library。附件使用原生返回的 `id`;不得把本地 ID 伪装为 cloud file ID。生成编辑调用原生提交能力,接受后沿用原生会话导航。View prompts 通过原生菜单扩展进入已有的 File info 风格面板。以上组件契约已按 Codex 26.930.21537 的产物与隔离窗口检查;实际云端生成结果须单独验证。
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
发送准备只在用户点击发送后进行,不在开始输入时预准备;本地字节转换直接填充 typed array,避免大图片经字符串迭代器生成中间数组。该优化只减少本地准备成本,不代表上传或原生提交网络等待已经缩短。
|
|
26
30
|
|
|
27
|
-
图库菜单复用原生 mediaOverlay More Button、Dropdown、Menu.Item 与原生图标,按 Chat about this、View prompts、Add to Favorites/Remove from Favorites、Download、Add to folder、Delete 排列。View prompts 的图标约束为菜单使用的 16px
|
|
31
|
+
图库菜单复用原生 mediaOverlay More Button、Dropdown、Menu.Item 与原生图标,按 Chat about this、View prompts、Add to Favorites/Remove from Favorites、Download、Add to folder、Delete 排列。View prompts 的图标约束为菜单使用的 16px,不继承导航栏尺寸。项目现有 Lucide book-open 的合并路径图形随 Images 注入,资源位于 `src/assets/prompts-menu.svg`;图库 More、大图菜单和云端菜单共用此资源,不从 Prompts 导航节点临时取图,也不依赖 Notes 功能启用。
|
|
28
32
|
|
|
29
33
|
只有用户点击收藏或文件夹操作时,通过原生 Library upload(required persistence)上传所选 PNG;取得真实 Library ID 后保存账号、用户、本地图片 ID、SHA-256 内容指纹对应关系。收藏调用原生 setLibraryFavorite 并以服务器状态切换,文件夹使用原生 MoveLibraryItemsDialog;不在本地保存收藏或文件夹关系。云端节点读取采用原生 `/files/library/files/{library_file_id}/node` 契约,不能把上传返回的 Library ID 当普通内容 file ID。
|
|
30
34
|
|
|
@@ -10,6 +10,7 @@ export function discoverFilePreviewComposerBindings(source) {
|
|
|
10
10
|
const scopeBody=requireBody(/draftId:[\w$]+,imageDataUrl:/);
|
|
11
11
|
const homeBody=requireBody(/homeComposerController:[\w$]+,homeComposerMode:/);
|
|
12
12
|
const composerBody=requireBody(/lockedLayout:`single-line`/);
|
|
13
|
+
const localBody=requireBody(/prepareLocalConversationAttachments:/);
|
|
13
14
|
const alias=(body,local,call=false)=>{
|
|
14
15
|
const escaped=local?.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");
|
|
15
16
|
const value=escaped&&body.match(new RegExp(`(?:[,;]|let )${escaped}=([\\w$]+)${call?"\\(":"(?=[,;])"}`))?.[1];
|
|
@@ -30,6 +31,12 @@ export function discoverFilePreviewComposerBindings(source) {
|
|
|
30
31
|
composerScope:alias(scopeBody,scopes[0][2]),
|
|
31
32
|
createComposerValue:alias(scopeBody,scopes[0][3],true),
|
|
32
33
|
composerRouteScope:alias(scopeBody,scopes[1][2]),
|
|
34
|
+
FileComposer:localBody.match(/jsx\)\(([\w$]+),\{\.\.\.[\w$]+,aboveComposerContent:/)?.[1],
|
|
35
|
+
fileComposerAvailability:localBody.match(/composerModeAvailability:([\w$]+)/)?.[1],
|
|
36
|
+
localAttachmentService:localBody.match(/await ([\w$]+)\([\w$]+,[\w$]+\)\.createFileAttachment/)?.[1],
|
|
37
|
+
deleteLocalAttachment:localBody.match(/aborted&&\(([\w$]+)\(/)?.[1],
|
|
38
|
+
useFileNavigate:alias(localBody,localBody.match(/([\w$]+)\(([\w$]+)\([\w$]+\),\{state:/)?.[1],true),
|
|
39
|
+
localThreadRoute:localBody.match(/[\w$]+\(([\w$]+)\([\w$]+\),\{state:/)?.[1],
|
|
33
40
|
};
|
|
34
41
|
if(Object.values(roles).some(value=>!value))throw new Error("Native file preview Composer role changed");
|
|
35
42
|
return roles;
|
|
@@ -193,15 +200,17 @@ export function createNativeImageModules({read = async url => (await fetch(url))
|
|
|
193
200
|
const createPortal=(await binding(canvas.source,canvas.url,portalInit))().createPortal;
|
|
194
201
|
// 文件画布使用原生 Home Composer 的控制器、附件 scope 和输入组件。
|
|
195
202
|
const composerLocals=Object.values(discoverFilePreviewComposerBindings(filePreview.source));
|
|
196
|
-
const [useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope] = await Promise.all(composerLocals.map(local=>binding(filePreview.source,filePreview.url,local)));
|
|
203
|
+
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)));
|
|
197
204
|
if (!canvas.exports.t || !canvasLayout.exports.r || !Composer || !ComposerSurface || !ScopeProvider
|
|
198
205
|
|| !composerScope || !composerRouteScope?.parent
|
|
199
|
-
||
|
|
206
|
+
|| !FileComposer || !fileComposerAvailability
|
|
207
|
+
|| [createPortal,useComposerController,useHomeComposer,createComposerValue,localAttachmentService,deleteLocalAttachment,useFileNavigate,localThreadRoute].some(value=>typeof value!=="function")) {
|
|
200
208
|
throw new Error("Native Images canvas contract changed");
|
|
201
209
|
}
|
|
202
210
|
return {
|
|
203
211
|
createPortal,Canvas:canvas.exports.t,CanvasLayout:canvasLayout.exports.r,
|
|
204
212
|
useComposerController,useHomeComposer,Composer,ComposerSurface,ScopeProvider,composerScope,createComposerValue,composerRouteScope,
|
|
213
|
+
FileComposer,fileComposerAvailability,localAttachmentService,deleteLocalAttachment,useFileNavigate,localThreadRoute,
|
|
205
214
|
editImage:component(initial,source=>/async function/.test(source)&&/requireDispatchAcceptance:/.test(source)&&/sourceAttachment:/.test(source)&&/imageVariant/.test(source),"image edit submission"),
|
|
206
215
|
};
|
|
207
216
|
})().catch(error=>{canvasConnecting=null;throw error;});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {createNativeImageModules} from "./native-image-modules.mjs";
|
|
2
2
|
|
|
3
3
|
// 原生呈现与上下文只在此适配;构建产物发现由专用模块提供。
|
|
4
|
-
export function createNativeImageSource(codex, { requestHostAction, libraryApi, createCloudImageActions, signal, modules = createNativeImageModules() } = {}) {
|
|
4
|
+
export function createNativeImageSource(codex, { requestHostAction, libraryApi, createCloudImageActions, promptGlyphSource, signal, modules = createNativeImageModules() } = {}) {
|
|
5
5
|
const connect = modules.gallery;
|
|
6
6
|
const opened = new Map();
|
|
7
7
|
|
|
@@ -82,6 +82,7 @@ export function createNativeImageSource(codex, { requestHostAction, libraryApi,
|
|
|
82
82
|
const context = environment(anchor);
|
|
83
83
|
const {React, ReactDOM} = native;
|
|
84
84
|
const h = React.createElement;
|
|
85
|
+
const promptIcon=h("span",{className:"inline-flex size-4 items-center justify-center","aria-hidden":true,dangerouslySetInnerHTML:{__html:promptGlyphSource}});
|
|
85
86
|
let failure, currentContent, folderDialog, removeDialog, canvasImage, disposed=false, batchBusy=false;
|
|
86
87
|
let currentImages=[],currentLoading=true,selectedIds=new Set(),selectionAnchor=null,openRevision=0;
|
|
87
88
|
const scope = context.libraryScope;
|
|
@@ -251,7 +252,7 @@ export function createNativeImageSource(codex, { requestHostAction, libraryApi,
|
|
|
251
252
|
imageId:image.imageGenerationItemId, sourceConversationId:image.threadId,
|
|
252
253
|
imageSourceContext:{conversationId:image.threadId, hostId:"local"}, imageSource:"generated",
|
|
253
254
|
readOnly, openInEditor:true,
|
|
254
|
-
renderHeaderMenuActions:({renderMenu}) => renderMenu(h(native.Menu.Item, {onSelect:() => onPrompt(image), children:context.intl.formatMessage({id:"minecodex.images.viewPrompts",defaultMessage:"View prompts"})})),
|
|
255
|
+
renderHeaderMenuActions:({renderMenu}) => renderMenu(h(native.Menu.Item, {leftIcon:promptIcon,onSelect:() => onPrompt(image), children:context.intl.formatMessage({id:"minecodex.images.viewPrompts",defaultMessage:"View prompts"})})),
|
|
255
256
|
}, {addToHistory:false});
|
|
256
257
|
if (!result) throw new Error("Native image preview could not open");
|
|
257
258
|
}
|
|
@@ -271,15 +272,28 @@ export function createNativeImageSource(codex, { requestHostAction, libraryApi,
|
|
|
271
272
|
h(native.ScopeProvider,{scope:native.composerRouteScope,value:{pathname:"/",routeKind:"home",routeTemplate:"/",search:"",projectContext:null}},
|
|
272
273
|
h(native.ScopeProvider,{scope:native.composerScope,value:composerValue},h(CanvasSurface,{image,resolved,close}))));
|
|
273
274
|
// 原生文件预览占据主内容 viewport,保留导航、二级侧栏和窗口栏。
|
|
274
|
-
return native.createPortal(h("div",{className:"absolute inset-0 z-40 overflow-hidden", "data-minecodex-image-canvas":image.id,children:
|
|
275
|
+
return native.createPortal(h("div",{className:"absolute inset-0 z-40 overflow-hidden", "data-minecodex-image-canvas":image.id,children:[
|
|
276
|
+
// 画布在主内容内,原生窗口级 overlay 的交通灯避让不适用于此处。
|
|
277
|
+
h("style",{children:'[data-minecodex-image-canvas] .shrink-0:has(>[data-testid="viewer-header"]){padding-left:0!important;padding-right:0!important}'}),content,
|
|
278
|
+
]}),viewport);
|
|
275
279
|
}
|
|
276
280
|
function CanvasSurface({image,resolved,close}) {
|
|
277
281
|
const controller=native.useComposerController();
|
|
282
|
+
const navigate=native.useFileNavigate();
|
|
278
283
|
const home=native.useHomeComposer({homeComposerController:controller,homeComposerMode:"chat"});
|
|
279
284
|
const [abort]=React.useState(()=>new AbortController());
|
|
280
285
|
const [markup,setMarkup]=React.useState(false);
|
|
281
286
|
const [manualZoomPercent,setManualZoomPercent]=React.useState(null);
|
|
282
287
|
const [submitting,setSubmitting]=React.useState(false);
|
|
288
|
+
const [composerHeight,setComposerHeight]=React.useState(0);
|
|
289
|
+
const measureComposer=React.useCallback(node=>{
|
|
290
|
+
const element=node?.querySelector("[data-codex-composer-root]");
|
|
291
|
+
if(!element)return;
|
|
292
|
+
const measure=()=>setComposerHeight(element.getBoundingClientRect().height);
|
|
293
|
+
const observer=new ResizeObserver(measure);
|
|
294
|
+
observer.observe(element);measure();
|
|
295
|
+
return ()=>{observer.disconnect();setComposerHeight(0);};
|
|
296
|
+
},[]);
|
|
283
297
|
const editorRef=React.useRef(null);
|
|
284
298
|
const attachment=React.useRef(null);
|
|
285
299
|
const submissionAccepted=React.useRef(false);
|
|
@@ -319,29 +333,44 @@ export function createNativeImageSource(codex, { requestHostAction, libraryApi,
|
|
|
319
333
|
}catch(error){if(!abort.signal.aborted)operationError(error);return false;}
|
|
320
334
|
finally {if(!abort.signal.aborted)setSubmitting(false);}
|
|
321
335
|
};
|
|
322
|
-
const composer=h(native.ComposerSurface,{placement:{kind:"home"}},h(native.
|
|
323
|
-
|
|
324
|
-
className:"mx-auto w-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
336
|
+
const composer=h("div",{ref:measureComposer,className:"contents"},h(native.ComposerSurface,{placement:{kind:"home"}},h(native.FileComposer,{
|
|
337
|
+
composerController:controller,composerLayoutMode:"auto-single-line",
|
|
338
|
+
className:"mx-auto w-[min(48rem,calc(100vw-2rem))] max-w-full",
|
|
339
|
+
composerModeAvailability:native.fileComposerAvailability,
|
|
340
|
+
hideRunLocationDropdownOverride:true,showUtilityBar:false,surfaceVariant:"inline",
|
|
341
|
+
placeholderText:message("libraryFilePreview.composerPlaceholder","Ask about this file"),
|
|
342
|
+
submitDisabled:submitting,selectedProject:null,
|
|
343
|
+
// 原图仅在发送时成为本地附件,不伪造 Library 身份,也不上传云端。
|
|
344
|
+
prepareLocalConversationAttachments:async ({fileAttachments,hostId})=>{
|
|
345
|
+
assertCurrent();
|
|
346
|
+
const file=await reference(image,"upload-file");assertCurrent();
|
|
347
|
+
const attached=await native.localAttachmentService(context.scope,hostId).createFileAttachment({contents:uploadFile(file),label:file.name});
|
|
348
|
+
if(abort.signal.aborted||!isCurrent()){
|
|
349
|
+
await native.deleteLocalAttachment(context.scope,hostId,attached.path);
|
|
350
|
+
assertCurrent();
|
|
351
|
+
}
|
|
352
|
+
return [...fileAttachments,attached];
|
|
353
|
+
},
|
|
354
|
+
onLocalFirstTurnAccepted:()=>{submissionAccepted.current=true;},
|
|
355
|
+
onLocalConversationCreated:(conversationId,state)=>{submissionAccepted.current=true;close();navigate(native.localThreadRoute(conversationId),{state});},
|
|
356
|
+
})));
|
|
330
357
|
const dataUrl=resolved.dataUrl??resolved.previewSrc;
|
|
331
358
|
const canvas=h(native.Canvas,{
|
|
332
359
|
alt:image.name,title:image.name,src:dataUrl??image.sourcePath,downloadSrc:dataUrl??image.sourcePath,downloadFilename:image.name,
|
|
333
360
|
attachmentId:`image-playground:${image.imageGenerationItemId}`,attachmentSrc:image.sourcePath,
|
|
334
361
|
chatGptConversationId:props.conversationId,
|
|
335
362
|
imageSource:"generated",imageSourceContext:{conversationId:image.threadId,hostId:"local"},
|
|
336
|
-
readOnly:false,presentation:"
|
|
363
|
+
readOnly:false,presentation:"overlay",standalone:true,showViewerHeader:true,onClose:requestClose,
|
|
337
364
|
markupMode:{active:markup,onActiveChange:setMarkup,editorRef,isSubmitting:submitting,standaloneComposer:true},
|
|
338
365
|
onImageEditSubmit:submitEdit,
|
|
339
366
|
zoom:{manualZoomPercent,onZoomPercentChange:setManualZoomPercent,onZoomToFit:()=>setManualZoomPercent(null)},
|
|
340
367
|
renderComposer:visible=>h(React.Activity,{mode:visible?"visible":"hidden",children:composer}),
|
|
341
|
-
renderHeaderMenuActions:({renderMenu})=>renderMenu(h(native.Menu.Item,{onSelect:()=>onPrompt(image),children:message("minecodex.images.viewPrompts","View prompts")})),
|
|
368
|
+
renderHeaderMenuActions:({renderMenu})=>renderMenu(h(native.Menu.Item,{leftIcon:promptIcon,onSelect:()=>onPrompt(image),children:message("minecodex.images.viewPrompts","View prompts")})),
|
|
342
369
|
});
|
|
343
|
-
// 原生 overlay
|
|
344
|
-
|
|
370
|
+
// 原生 overlay 的 118px 下限属于旧输入框;画布和拖拽边界跟随实际高度。
|
|
371
|
+
const reserve=composerHeight>0?`calc(${composerHeight}px + var(--spacing)*4)`:"0px";
|
|
372
|
+
return h("div",{className:"contents",style:{"--right-panel-composer-overlay-reserve":reserve}},
|
|
373
|
+
h(native.CanvasLayout,{assetName:image.name,contentOwnsHeader:true,onClose:requestClose,children:canvas}));
|
|
345
374
|
}
|
|
346
375
|
async function chatAbout(image) {
|
|
347
376
|
assertAccount();
|
|
@@ -389,13 +418,6 @@ export function createNativeImageSource(codex, { requestHostAction, libraryApi,
|
|
|
389
418
|
const item = byId.get(id);
|
|
390
419
|
return item && {...item, icon, text:context.intl.formatMessage(message ?? item.message)};
|
|
391
420
|
};
|
|
392
|
-
const promptGlyph = document.querySelector('[aria-label="Prompts"] svg');
|
|
393
|
-
const menuGlyph = promptGlyph?.cloneNode(true);
|
|
394
|
-
if (menuGlyph) {
|
|
395
|
-
menuGlyph.setAttribute("width","16");menuGlyph.setAttribute("height","16");
|
|
396
|
-
menuGlyph.style.width="16px";menuGlyph.style.height="16px";
|
|
397
|
-
}
|
|
398
|
-
const promptIcon = menuGlyph ? h("span", {className:"inline-flex size-4 items-center justify-center", "aria-hidden":true, dangerouslySetInnerHTML:{__html:menuGlyph.outerHTML}}) : null;
|
|
399
421
|
const menuItems = [
|
|
400
422
|
{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
423
|
{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);}},
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- Derived from Lucide book-open (ISC): merged stroke subpaths for uniform alpha. -->
|
|
2
|
+
<svg
|
|
3
|
+
class="icon-xs shrink-0"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="16"
|
|
6
|
+
height="16"
|
|
7
|
+
viewBox="-1.5 -1.5 27 27"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="1.8"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M12 7v14 M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z" />
|
|
15
|
+
</svg>
|
|
@@ -72,12 +72,13 @@ function syncMenu(record) {
|
|
|
72
72
|
item.dataset.minecodexLibraryPrompt = "menuitem";
|
|
73
73
|
const row = original.firstElementChild.cloneNode(true);
|
|
74
74
|
row.querySelector(".truncate").textContent = label("View prompts", "查看提示词");
|
|
75
|
-
//
|
|
76
|
-
const glyph =
|
|
77
|
-
|
|
75
|
+
// 图标随 Images 注入,不依赖 Prompts 导航入口是否显示。
|
|
76
|
+
const glyph = new DOMParser().parseFromString(promptGlyphSource,"image/svg+xml").documentElement;
|
|
77
|
+
for (const svg of row.querySelectorAll("svg")) {
|
|
78
78
|
const icon = glyph.cloneNode(true);
|
|
79
79
|
icon.setAttribute("width", svg.getAttribute("width"));
|
|
80
80
|
icon.setAttribute("height", svg.getAttribute("height"));
|
|
81
|
+
icon.setAttribute("class", svg.getAttribute("class") ?? "");
|
|
81
82
|
svg.replaceWith(icon);
|
|
82
83
|
}
|
|
83
84
|
item.append(row);
|
|
@@ -259,7 +260,7 @@ document.addEventListener("minecodex:image-prompt",localPromptRequested);
|
|
|
259
260
|
const localeObserver = new MutationObserver(() => mounted?.refreshLocale());
|
|
260
261
|
localeObserver.observe(document.documentElement, { attributes:true, attributeFilter:["lang"] });
|
|
261
262
|
localImageSource = installLocalImageSource(config, signal, createImageSourcesAdapter(config.codex), createNativeImageSource(config.codex, {
|
|
262
|
-
requestHostAction:config.requestHostAction, libraryApi, createCloudImageActions, signal,
|
|
263
|
+
requestHostAction:config.requestHostAction, libraryApi, createCloudImageActions, promptGlyphSource, signal,
|
|
263
264
|
}));
|
|
264
265
|
const observer = new MutationObserver(() => {
|
|
265
266
|
if (scheduled) return;
|
|
@@ -10,7 +10,8 @@ import { promptResolverSource } from "./adapters/codex/prompt-resolver-source.mj
|
|
|
10
10
|
|
|
11
11
|
export async function createPageScriptSource() {
|
|
12
12
|
const source=await readFile(new URL("./library-prompt-page.mjs",import.meta.url),"utf8");
|
|
13
|
+
const promptGlyphSource=await readFile(new URL("./assets/prompts-menu.svg",import.meta.url),"utf8");
|
|
13
14
|
const functions={createCloudImageActions,createImageSourcesAdapter,discoverFilePreviewComposerBindings,createNativeImageModules,createNativeImageSource,installLocalImageSource,createLibraryApi,createLibraryViewerAdapter};
|
|
14
15
|
const declarations=Object.entries(functions).map(([name,fn])=>`const ${name}=${fn.toString()};`).join("\n");
|
|
15
|
-
return `${declarations}\nconst promptFromConversation=${promptResolverSource()};\n${source}`;
|
|
16
|
+
return `${declarations}\nconst promptGlyphSource=${JSON.stringify(promptGlyphSource)};\nconst promptFromConversation=${promptResolverSource()};\n${source}`;
|
|
16
17
|
}
|
package/package.json
CHANGED
|
@@ -10,7 +10,9 @@ import { randomBytes } from "node:crypto";
|
|
|
10
10
|
import { CODEX_THEME_TOKENS, CODEX_THEME_TOKEN_ALIASES, MINECODEX_THEME_BINDINGS } from "../../ui/src/native/tokens.mjs";
|
|
11
11
|
|
|
12
12
|
export const RUNTIME_VERSION = 108;
|
|
13
|
-
const PROJECT_ICON =
|
|
13
|
+
const PROJECT_ICON = Object.fromEntries([16, 20].map(size => [size,
|
|
14
|
+
readFileSync(new URL(`../../ui/assets/minecodex-menu-${size}.svg`, import.meta.url), "utf8"),
|
|
15
|
+
]));
|
|
14
16
|
const EXTERNAL_ICON = readFileSync(new URL("../../ui/assets/arrow-up-right.svg", import.meta.url), "utf8");
|
|
15
17
|
export const HOST_BINDING_NAME = "__codexPersonalHostAction";
|
|
16
18
|
export const CSP_BOOTSTRAP_VERSION = 1;
|
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
viewBox="0 0 24 24"
|
|
6
6
|
fill="none"
|
|
7
7
|
stroke="currentColor"
|
|
8
|
-
stroke-width="
|
|
8
|
+
stroke-width="1.575"
|
|
9
9
|
stroke-linecap="round"
|
|
10
10
|
stroke-linejoin="round"
|
|
11
11
|
>
|
|
12
|
-
<path d="M7
|
|
13
|
-
<path d="M7 17 17 7" />
|
|
12
|
+
<path d="M7 7h10v10M7 17 17 7" />
|
|
14
13
|
</svg>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-brand-icons.mjs from assets/brand/minecodex-icon.svg. -->
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.23992 1.43098C7.46406 1.1031 8.54982 1.45396 9.38542 2.21516C10.5383 1.98366 11.9155 2.29793 12.8093 3.19172C13.6945 4.07741 14.0164 5.30994 13.7829 6.44953C14.5557 7.31992 14.8933 8.54971 14.569 9.76008C14.2447 10.9704 13.3375 11.8664 12.2331 12.2337C11.8657 13.3376 10.9704 14.2443 9.76042 14.5687C8.54999 14.893 7.32025 14.5555 6.44988 13.7825C5.3103 14.0161 4.07777 13.6943 3.19206 12.8089C2.30603 11.9228 1.98129 10.6886 2.2155 9.54817C1.44398 8.67777 1.10733 7.44883 1.43132 6.23957C1.7556 5.02965 2.66205 4.13253 3.76628 3.76496C4.13401 2.66129 5.0304 1.75519 6.23992 1.43098ZM8.84636 3.15461C8.21242 2.48417 7.42544 2.20118 6.51238 2.44563C5.58126 2.69512 4.90916 3.43143 4.69988 4.30989L4.62566 4.62434L4.3112 4.69953C3.43254 4.90862 2.69555 5.58058 2.44597 6.51203C2.19659 7.44329 2.49878 8.39365 3.15495 9.01399L3.38933 9.23567L3.29753 9.54524C3.03946 10.4106 3.25264 11.384 3.93425 12.0657C4.61596 12.7473 5.59015 12.9608 6.45574 12.7025L6.76531 12.6097L6.98699 12.845C7.6075 13.5014 8.55777 13.8045 9.48894 13.555C10.4198 13.3054 11.0903 12.5676 11.2995 11.6888L11.3747 11.3753L11.6891 11.3001C12.568 11.091 13.3058 10.4198 13.5553 9.4886C13.8048 8.55744 13.5017 7.60715 12.8454 6.98664L12.611 6.76496L12.7028 6.45539C12.961 5.59001 12.7483 4.61562 12.0671 3.93391C11.3946 3.26145 10.2567 3.03482 9.37761 3.29719L9.06804 3.38996L8.84636 3.15461Z" fill="currentColor"/>
|
|
4
|
+
<g transform="scale(0.6666666666666666)"><g id="pickaxe" transform="translate(6.24 6.24) scale(.48)" fill="currentColor">
|
|
5
|
+
<path d="M15.686 4.314 16.3 3.7a1 1 0 0 1 1.4 0l2.6 2.6a1 1 0 0 1 0 1.4l-.614.614a12.501 12.501 0 0 1 1.356 10.225 1 1 0 0 1-1.751-.119 22 22 0 0 0-3.393-6.319l-.198.199a1 1 0 0 1-1.4 0l-.219.169L6.619 20.38a1 1 0 1 1-3-3l7.912-7.912.169.232a1 1 0 0 1 0-1.4l.198-.197A22 22 0 0 0 5.58 4.71a1 1 0 0 1-.119-1.752 12.5 12.5 0 0 1 10.225 1.356Z"/>
|
|
6
|
+
</g></g>
|
|
7
|
+
</svg>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-brand-icons.mjs from assets/brand/minecodex-icon.svg. -->
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.79787 1.78095C9.33018 1.37054 10.6892 1.80862 11.7354 2.76044C13.1781 2.47254 14.8994 2.86492 16.0176 3.9831C17.1255 5.09146 17.5294 6.63391 17.2383 8.06025C18.2036 9.15 18.6252 10.6879 18.2197 12.2018C17.8139 13.7157 16.6796 14.8375 15.2988 15.2985C14.838 16.6794 13.7171 17.8134 12.2031 18.2194C10.689 18.6252 9.15041 18.2035 8.06057 17.238C6.63426 17.5291 5.09181 17.1254 3.98342 16.0173C2.87514 14.909 2.4696 13.3659 2.76076 11.9392C1.7958 10.8492 1.37577 9.31111 1.78127 7.79755C2.1869 6.28392 3.31944 5.16128 4.70022 4.69989C5.1615 3.31914 6.28421 2.18664 7.79787 1.78095ZM11.0518 3.94892C10.2613 3.11295 9.28034 2.76056 8.1426 3.06513C6.98179 3.37617 6.14459 4.29507 5.88381 5.39032L5.78908 5.78778L5.39065 5.88251C4.29532 6.14315 3.37656 6.98118 3.06545 8.14228C2.75466 9.30301 3.1314 10.4872 3.94924 11.2604L4.24709 11.5417L4.1299 11.9343C3.80831 13.013 4.07409 14.2271 4.92385 15.0768C5.77357 15.9262 6.98753 16.1917 8.06643 15.8698L8.459 15.7526L8.74025 16.0505C9.51379 16.8686 10.6978 17.2453 11.8584 16.9343C13.0186 16.6232 13.8554 15.7053 14.1162 14.6101L14.211 14.2116L14.6094 14.1169C15.705 13.8562 16.6245 13.0189 16.9356 11.8581C17.2464 10.6976 16.8687 9.51342 16.0508 8.73993L15.753 8.45868L15.8701 8.0661C16.192 6.98728 15.9265 5.77328 15.0772 4.92353C14.2391 4.08548 12.8208 3.80245 11.7246 4.12958L11.333 4.24677L11.0518 3.94892Z" fill="currentColor"/>
|
|
4
|
+
<g transform="scale(0.8333333333333334)"><g id="pickaxe" transform="translate(6.24 6.24) scale(.48)" fill="currentColor">
|
|
5
|
+
<path d="M15.686 4.314 16.3 3.7a1 1 0 0 1 1.4 0l2.6 2.6a1 1 0 0 1 0 1.4l-.614.614a12.501 12.501 0 0 1 1.356 10.225 1 1 0 0 1-1.751-.119 22 22 0 0 0-3.393-6.319l-.198.199a1 1 0 0 1-1.4 0l-.219.169L6.619 20.38a1 1 0 1 1-3-3l7.912-7.912.169.232a1 1 0 0 1 0-1.4l.198-.197A22 22 0 0 0 5.58 4.71a1 1 0 0 1-.119-1.752 12.5 12.5 0 0 1 10.225 1.356Z"/>
|
|
6
|
+
</g></g>
|
|
7
|
+
</svg>
|
|
@@ -4,12 +4,21 @@ export function installAccountMenuEntry(signal, ui, native) {
|
|
|
4
4
|
let node, anchor, entryBindings, opening = false;
|
|
5
5
|
const bindings = new AbortController();
|
|
6
6
|
function svg(source, template = null) {
|
|
7
|
+
if (typeof source !== "string") source = source[Number(template.getAttribute("width"))];
|
|
7
8
|
const result = new DOMParser().parseFromString(source, "image/svg+xml").documentElement;
|
|
8
9
|
result.setAttribute("aria-hidden", "true");
|
|
9
10
|
result.removeAttribute("role");
|
|
10
11
|
result.removeAttribute("aria-labelledby");
|
|
11
12
|
result.querySelectorAll("title, desc").forEach(n => n.remove());
|
|
12
13
|
result.setAttribute("class", template?.getAttribute("class") ?? "icon-xs shrink-0");
|
|
14
|
+
if (template) {
|
|
15
|
+
// 原生 16/20px 图标由外层切换,品牌资源的 24px 不能覆盖它。
|
|
16
|
+
for (const name of ["width", "height", "style"]) {
|
|
17
|
+
const value = template.getAttribute(name);
|
|
18
|
+
if (value == null) result.removeAttribute(name);
|
|
19
|
+
else result.setAttribute(name, value);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
13
22
|
return result;
|
|
14
23
|
}
|
|
15
24
|
async function activate(event) {
|
|
@@ -43,9 +52,14 @@ export function installAccountMenuEntry(signal, ui, native) {
|
|
|
43
52
|
node.setAttribute("role", "menuitem");
|
|
44
53
|
node.tabIndex = -1;
|
|
45
54
|
const [leading, title, trailing] = node.querySelector("div").children;
|
|
46
|
-
|
|
55
|
+
// 保留紧凑/普通图标的原生容器及颜色、透明度和状态规则。
|
|
56
|
+
for (const template of leading.querySelectorAll("svg")) template.replaceWith(svg(icon, template));
|
|
47
57
|
title.textContent = label;
|
|
48
|
-
|
|
58
|
+
// 右侧提示沿用原生 Help 箭头的 tertiary 色,不套左侧图标的透明度。
|
|
59
|
+
trailing.style.color = "inherit";
|
|
60
|
+
const external = svg(externalIcon);
|
|
61
|
+
external.setAttribute("class", "icon-xs shrink-0 text-tertiary");
|
|
62
|
+
trailing.replaceChildren(external);
|
|
49
63
|
node.addEventListener("click", activate, { signal:entryBindings.signal });
|
|
50
64
|
node.addEventListener("pointermove", () => node.focus({ preventScroll:true }), { signal:entryBindings.signal });
|
|
51
65
|
found.item.after(node);
|