minecodex 2.0.4 → 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 +40 -1
- package/README.md +9 -3
- 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 +117 -41
- package/features/images/README.md +26 -16
- package/features/images/codex-feature.json +6 -3
- package/features/images/src/adapters/codex/image-prompt.mjs +24 -2
- package/features/images/src/adapters/codex/image-sources.mjs +19 -0
- package/features/images/src/adapters/codex/library-api.mjs +2 -0
- package/features/images/src/adapters/codex/library-viewer.mjs +26 -2
- package/features/images/src/adapters/codex/native-image-modules.mjs +211 -0
- package/features/images/src/adapters/codex/native-image-source.mjs +449 -0
- package/features/images/src/backend/local-image-source.mjs +138 -0
- package/features/images/src/frontend/cloud-image-actions.mjs +41 -0
- package/features/images/src/frontend/local-image-source.mjs +195 -0
- package/features/images/src/library-prompt-page.mjs +163 -53
- package/features/images/src/library-prompt-source.mjs +9 -3
- package/features/images/src/prompt-index.mjs +14 -2
- package/features/model-slider/DESIGN.md +2 -0
- package/features/model-slider/codex-feature.json +2 -2
- package/features/model-slider/src/frontend/install.mjs +78 -11
- package/features/notes/DESIGN.md +7 -3
- package/features/notes/codex-feature.json +2 -2
- package/features/notes/src/frontend/entry.mjs +26 -9
- package/features/session-status/DESIGN.md +4 -2
- package/features/session-status/codex-feature.json +2 -2
- package/features/session-status/src/adapters/codex/session-log.mjs +2 -1
- package/features/session-status/src/domain/session-stats.mjs +17 -9
- package/features/session-status/src/session-status.mjs +47 -11
- 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/codex-adapter/README.md +2 -1
- package/packages/codex-adapter/src/renderer/environment.mjs +24 -6
- package/packages/runtime-host/README.md +10 -5
- package/packages/runtime-host/src/codex-injection.mjs +13 -9
- package/packages/runtime-host/src/codex-runtime.mjs +26 -5
- package/packages/runtime-host/src/feature-registry.mjs +1 -0
- package/packages/ui/DESIGN.md +9 -2
- 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/button.mjs +2 -0
- package/packages/ui/src/components/navigation-rail.mjs +18 -10
- package/packages/ui/src/components/page.mjs +9 -2
- package/packages/ui/src/foundation/layers.mjs +1 -0
- package/packages/ui/src/install.mjs +6 -1
- package/packages/ui/src/native/account-menu.mjs +21 -0
- package/packages/ui/src/native/navigation-rail.mjs +6 -1
- package/packages/ui/src/native/page.mjs +38 -0
- package/packages/ui/src/native/tokens.mjs +4 -0
|
@@ -9,10 +9,10 @@ Toolbar 入口,由 `CodexRuntimeHost` 在渲染进程内注入一段脚本。
|
|
|
9
9
|
功能范围:
|
|
10
10
|
|
|
11
11
|
- 仅隐藏横幅的 GUI 显示(`aside` 卡片,`display: none`);
|
|
12
|
-
-
|
|
12
|
+
- 以 `aside` 和中英文 Codex 额度耗尽标题识别;通用标题 `You’re out of usage`(也支持直引号)还须带原生用量横幅关闭按钮,避免误隐藏普通通知;不依赖卡片 class 或购买按钮文案;
|
|
13
13
|
- 不拦截、不篡改额度查询接口(`account/rateLimits/read` 与 app-server
|
|
14
14
|
JSON-RPC 流量保持原样);
|
|
15
|
-
-
|
|
15
|
+
- 横幅样式被重新渲染覆盖后,由 `MutationObserver` 重新隐藏;只在样式变化时写入,避免观察器循环。节点改作普通通知或离开文档时释放隐藏状态;
|
|
16
16
|
- 注入运行时随 RuntimeHost 卸载自动清理(恢复元素样式、断开 observer)。
|
|
17
17
|
|
|
18
18
|
## 验证
|
|
@@ -23,6 +23,8 @@ npm test
|
|
|
23
23
|
|
|
24
24
|
## 适配说明
|
|
25
25
|
|
|
26
|
-
这是非官方 CDP / DOM
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
这是非官方 CDP / DOM 适配。横幅识别与隐藏逻辑由本功能的 page-script 维护,Runtime Host 负责注入和卸载。
|
|
27
|
+
|
|
28
|
+
Codex 26.930.21537(2026-10-02 原生源码核对):默认横幅和 recovery 分支均使用 `aside` / `h3`,标题来自后端 `banner.title`。通用标题目前仅在同时具有 `Dismiss usage banner`、`不再显示用量横幅`、`關閉用量橫幅` 或 `關閉用量橫額` 关闭语义时隐藏;没有该控件或其他未知标题时保守保留。
|
|
29
|
+
|
|
30
|
+
行为测试覆盖标题边界、动态内容、重复隐藏、节点复用及卸载后的排队扫描。隔离浏览器 DOM 已验证隐藏和样式覆盖后的恢复;真实账户额度耗尽横幅尚未做端到端验收。
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"zh-CN": "隐藏用量横幅"
|
|
8
8
|
},
|
|
9
9
|
"description": {
|
|
10
|
-
"en": "Hide
|
|
11
|
-
"zh-CN": "
|
|
10
|
+
"en": "Hide usage upsell banners above the composer.",
|
|
11
|
+
"zh-CN": "隐藏输入框上方的额度推广横幅。"
|
|
12
12
|
},
|
|
13
13
|
"entry": {
|
|
14
14
|
"kind": "page-script",
|
|
@@ -8,29 +8,49 @@
|
|
|
8
8
|
// touches the rate-limit query path: `account/rateLimits/read` and the
|
|
9
9
|
// app-server JSON-RPC traffic stay intact.
|
|
10
10
|
const HIDDEN_MARKER = "data-codex-upsell-banner-hidden";
|
|
11
|
+
const hiddenBanners = new Map();
|
|
12
|
+
let disposed = false;
|
|
11
13
|
|
|
12
14
|
function isUpsellBanner(element) {
|
|
13
15
|
if (!(element instanceof window.HTMLElement)) return false;
|
|
14
|
-
const text = (element.
|
|
16
|
+
const text = (element.textContent ?? "").trim();
|
|
15
17
|
if (!text) return false;
|
|
16
18
|
const title = (
|
|
17
|
-
element.querySelector("h1, h2, h3, [role='heading']")?.
|
|
19
|
+
element.querySelector("h1, h2, h3, [role='heading']")?.textContent
|
|
18
20
|
?? text.split("\n", 1)[0]
|
|
19
21
|
).trim();
|
|
20
22
|
return /out of Codex(?: and Work)? usage/i.test(title)
|
|
21
23
|
|| /out of Codex messages/i.test(title)
|
|
22
24
|
|| /Codex (?:message )?usage limit reached/i.test(title)
|
|
23
25
|
|| /Codex.*(?:用量|使用量|使用额度|额度|消息).*(?:用完|用尽|耗尽|已达|达到.*上限|上限)/i.test(title)
|
|
24
|
-
|| /(?:已达到|达到|已用完|已用尽|耗尽).{0,20}Codex.{0,20}(?:用量|使用量|额度|消息|上限)/i.test(title)
|
|
26
|
+
|| /(?:已达到|达到|已用完|已用尽|耗尽).{0,20}Codex.{0,20}(?:用量|使用量|额度|消息|上限)/i.test(title)
|
|
27
|
+
// 通用标题须同时具有原生用量横幅的关闭语义,不能仅按耗尽文案隐藏通知。
|
|
28
|
+
|| (/^you['’]re out of usage[.!]?$/i.test(title)
|
|
29
|
+
&& [...element.querySelectorAll("button[aria-label]")].some(button =>
|
|
30
|
+
/^(?:Dismiss usage banner|不再显示用量横幅|關閉用量橫幅|關閉用量橫額)$/i.test(button.getAttribute("aria-label") ?? "")));
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
function hideBanner(element) {
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
if (!hiddenBanners.has(element)) hiddenBanners.set(element, {
|
|
35
|
+
display:element.style.getPropertyValue("display"), priority:element.style.getPropertyPriority("display"),
|
|
36
|
+
});
|
|
37
|
+
if (!element.hasAttribute(HIDDEN_MARKER)) element.setAttribute(HIDDEN_MARKER, "");
|
|
38
|
+
// 标记不代表样式仍有效;只在需要时写入,避免 observer 自触发循环。
|
|
39
|
+
if (element.style.display !== "none") element.style.display = "none";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function restoreBanner(element) {
|
|
43
|
+
const original = hiddenBanners.get(element);
|
|
44
|
+
element.removeAttribute(HIDDEN_MARKER);
|
|
45
|
+
if (element.style.display === "none") {
|
|
46
|
+
if (original.display) element.style.setProperty("display", original.display, original.priority);
|
|
47
|
+
else element.style.removeProperty("display");
|
|
48
|
+
}
|
|
49
|
+
hiddenBanners.delete(element);
|
|
31
50
|
}
|
|
32
51
|
|
|
33
52
|
function hideVisibleBanners(root = document) {
|
|
53
|
+
for (const element of hiddenBanners.keys()) if (!element.isConnected || !isUpsellBanner(element)) restoreBanner(element);
|
|
34
54
|
if (root.matches?.("aside") && isUpsellBanner(root)) hideBanner(root);
|
|
35
55
|
for (const element of root.querySelectorAll("aside")) {
|
|
36
56
|
if (isUpsellBanner(element)) hideBanner(element);
|
|
@@ -47,7 +67,7 @@ function rescanBanners() {
|
|
|
47
67
|
rescanQueued = true;
|
|
48
68
|
queueMicrotask(() => {
|
|
49
69
|
rescanQueued = false;
|
|
50
|
-
hideVisibleBanners(document);
|
|
70
|
+
if (!disposed) hideVisibleBanners(document);
|
|
51
71
|
});
|
|
52
72
|
}
|
|
53
73
|
|
|
@@ -59,14 +79,12 @@ observer.observe(document.documentElement, {
|
|
|
59
79
|
childList: true,
|
|
60
80
|
subtree: true,
|
|
61
81
|
attributes: true,
|
|
62
|
-
attributeFilter: ["style", "class", "hidden"],
|
|
82
|
+
attributeFilter: ["style", "class", "hidden", "aria-label"],
|
|
63
83
|
characterData: true,
|
|
64
84
|
});
|
|
65
85
|
|
|
66
86
|
return () => {
|
|
87
|
+
disposed = true;
|
|
67
88
|
observer.disconnect();
|
|
68
|
-
for (const element of
|
|
69
|
-
element.removeAttribute(HIDDEN_MARKER);
|
|
70
|
-
element.style.display = "";
|
|
71
|
-
}
|
|
89
|
+
for (const element of hiddenBanners.keys()) restoreBanner(element);
|
|
72
90
|
};
|
|
@@ -4,11 +4,35 @@
|
|
|
4
4
|
|
|
5
5
|
## Scope
|
|
6
6
|
|
|
7
|
-
Images enhances Codex's native Library image viewer
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
Images enhances Codex's native Library image viewer, reached through Space → Images.
|
|
8
|
+
It also appends **Image Gen** after Your creations and Uploads in the native Image sources tablist.
|
|
9
|
+
This tab contains images generated locally by Codex or local Work. Browsing does not upload them; explicit Favorites/folder actions persist the selected image in native cloud Library.
|
|
10
|
+
The native tabs, content nodes and routes are retained. Leaving Image Gen restores native selection and content;
|
|
11
|
+
hover sidebars and the native full-page shell remain owned by Codex.
|
|
12
|
+
|
|
13
|
+
## Image Gen
|
|
14
|
+
|
|
15
|
+
Codex 26.930.21537(2026-10-02 隔离实测):通过原生 `variant="browse"` 的 Image sources 契约接入。保留原生标签节点及路由,在插件拥有的 React root 中复用同一个原生 Tabs 组件,追加 Image Gen;退出时恢复原节点。原生上下文由 Adapter 从当前已提交的组件祖先读取,业务层不遍历 Fiber。
|
|
16
|
+
|
|
17
|
+
本地来源复用 ImageLibrary、ThreadMetadataIndex 及文件监听,索引存放在当前 runtime profile;按生成时间倒序,每页 36 张,滚动增量加载,搜索复用原生输入框。稳定身份为 threadId + imageGenerationItemId,不猜测云端 ID 与本地文件的关系。
|
|
18
|
+
|
|
19
|
+
加载使用原生 Library Skeleton,排布使用原生 Masonry,悬浮效果使用原生 Library Card,菜单使用原生 Dropdown、Button 与 Menu。图库本地文件通过原生生成图片 resolver 读取;浏览时 Backend 提供索引中的来源路径,图片由原生 resolver 读取;显式云端登记才读取该索引 ID 对应的完整字节并校验内容指纹,不接受 Renderer 任意路径读取,不维护第二套图库加载协议。
|
|
20
|
+
|
|
21
|
+
点击图片进入原生 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
|
+
|
|
23
|
+
本地来源以真实路径、来源 thread 和生成项 ID 接入;首次发送或提交编辑时才通过原生上传准备临时附件,同一画布复用附件,查看不上传到 Library。附件使用原生返回的 `id`;不得把本地 ID 伪装为 cloud file ID。生成编辑调用原生提交能力,接受后沿用原生会话导航。View prompts 通过原生菜单扩展进入已有的 File info 风格面板。以上组件契约已按 Codex 26.930.21537 的产物与隔离窗口检查;实际云端生成结果须单独验证。
|
|
24
|
+
|
|
25
|
+
发送准备保持用户点击发送后才上传,不在开始输入时预上传;本地字节转换直接填充 typed array,避免大图片经字符串迭代器生成中间数组。该优化只减少本地准备成本,不代表上传或原生提交网络等待已经缩短。
|
|
26
|
+
|
|
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,不继承导航栏尺寸。
|
|
28
|
+
|
|
29
|
+
只有用户点击收藏或文件夹操作时,通过原生 Library upload(required persistence)上传所选 PNG;取得真实 Library ID 后保存账号、用户、本地图片 ID、SHA-256 内容指纹对应关系。收藏调用原生 setLibraryFavorite 并以服务器状态切换,文件夹使用原生 MoveLibraryItemsDialog;不在本地保存收藏或文件夹关系。云端节点读取采用原生 `/files/library/files/{library_file_id}/node` 契约,不能把上传返回的 Library ID 当普通内容 file ID。
|
|
30
|
+
|
|
31
|
+
取消选择通过原生 toast 服务反馈。该操作首次上传后取消显示“Image saved to Library, but not added to a folder.”;已有云端身份则显示“No folder selected.”。取消不删除云端图片、不移动文件;移动成功后的关闭不误报取消。浏览与打开菜单不上传,收藏状态查询不创建记录。
|
|
32
|
+
|
|
33
|
+
登记工作流位于 `src/frontend/cloud-image-actions.mjs`。同时操作同一图片复用在途任务,Backend 原子预留避免不同窗口重复上传。已确认的身份跨 Runtime 重启保留;内容变化或账号变化不复用。上传结果不确定时保留预留状态并用原生错误 toast 提醒检查 Library,禁止盲目重新上传;自动恢复不确定记录尚未实现。云端条目删除或访问失效时报告操作失败,不自动重新上传。
|
|
34
|
+
|
|
35
|
+
原生构建产物和组件导出契约集中在 `src/adapters/codex/native-image-source.mjs`。按实际模块引用发现构建文件,不固定哈希;契约不匹配时保留原生页面并报告能力缺失。`src/frontend/local-image-source.mjs` 仅负责来源选择、分页、搜索与清理,Runtime Host 只鉴权和转发 `local-image-source` 的 list / prompt 与身份登记操作。
|
|
12
36
|
|
|
13
37
|
## Entry and identity
|
|
14
38
|
|
|
@@ -18,54 +42,106 @@ originating conversation/message IDs. Non-Library viewers and non-image files
|
|
|
18
42
|
are excluded. No title, filename, image position or minified component name is
|
|
19
43
|
used as identity. Missing native anchors fail closed.
|
|
20
44
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
Codex 26.930.21537(2026-10-02 隔离实测):Space 图片详情使用嵌入式视口,Adapter 从其原生 `relative flex h-full min-w-0 flex-1` 容器取得面板承载;浮层分支沿用 presentation 容器。缩放按钮包含隐藏的测量百分比,识别时读取 `innerText`,不读取包含这些测量文字的 `textContent`。
|
|
46
|
+
|
|
47
|
+
Prepare the Prompt panel as soon as the native image has both originating conversation
|
|
48
|
+
and message IDs and its viewer controls are ready; start source-conversation extraction in
|
|
49
|
+
parallel. This is eligibility to look up a prompt, not proof that prompt text exists.
|
|
50
|
+
Images without these source IDs have no View prompts entry. Exact generated-asset
|
|
51
|
+
validation remains part of extraction, so uploaded references never supply prompt text.
|
|
52
|
+
|
|
53
|
+
Opening while extraction is pending shows the complete File info-style header,
|
|
54
|
+
a disabled Copy button, and four static rounded skeleton lines in the body.
|
|
55
|
+
`aria-busy` marks the body; decorative lines are hidden from assistive technology.
|
|
56
|
+
Closing does not cancel prefetch. Success replaces skeletons with selectable text
|
|
57
|
+
and enables Copy. Missing generation text falls back to a traceable original request when available:
|
|
58
|
+
first disclose that the generation prompt is unavailable, label the body “Original
|
|
59
|
+
request”, and enable Copy with the accessible label “Copy original request”. If
|
|
60
|
+
neither kind exists, show the unavailable message and leave Copy disabled. Failure ends with
|
|
61
|
+
a localized message and Retry action. There is no perpetual loading placeholder.
|
|
62
|
+
Changing images or disabling the feature aborts the old request; late results
|
|
63
|
+
cannot change the new panel. Skeletons are static and respect reduced-motion.
|
|
64
|
+
|
|
65
|
+
The entry is a native-style “View prompts” / “查看提示词” menu item in the image
|
|
66
|
+
header's File actions dropdown, immediately before View original chat. There is
|
|
67
|
+
no dedicated right-toolbar button. Adapter validates the menu's current Library
|
|
68
|
+
file and the original-chat link's conversation/message IDs before inserting.
|
|
69
|
+
The item uses the native row classes and existing Prompts glyph; native React
|
|
70
|
+
nodes remain intact. It is recreated when the native menu remounts, without
|
|
71
|
+
refetching prompt content. Arrow navigation includes the injected item; Enter /
|
|
72
|
+
Space activate it. Opening dismisses the native menu, then focuses panel Close.
|
|
73
|
+
Panel Close / Escape return focus to the Image dropdown trigger.
|
|
31
74
|
|
|
32
75
|
## Prompt panel
|
|
33
76
|
|
|
34
|
-
The panel
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
the
|
|
77
|
+
The panel adopts the native File info accessory-panel branch, inspected in Codex
|
|
78
|
+
26.930.21537 on 2026-10-02. General material rules are maintained in the root
|
|
79
|
+
DESIGN.md; this feature uses the following native layout:
|
|
80
|
+
|
|
81
|
+
- Right and bottom gutters: two spacing units (8px). Top: `pt-13` plus the
|
|
82
|
+
native wrapper's `pt-px` (53px from the viewer viewport top).
|
|
83
|
+
- Width: 74 spacing units (296px), constrained to the viewport minus both gutters.
|
|
84
|
+
- Header: `flex h-11 shrink-0 items-center justify-between gap-2 py-1 ps-4 pe-2`
|
|
85
|
+
(44px); `h2` contains a `text-base font-semibold` title.
|
|
86
|
+
- Body: `min-h-0 flex-1 overflow-y-auto px-3 py-4` (12px horizontal,
|
|
87
|
+
16px vertical padding). Prompt paragraph retains `px-1 text-sm leading-5
|
|
88
|
+
text-default whitespace-pre-wrap break-words select-text` for long text.
|
|
89
|
+
- Header controls use the native File info circular `button-toolbar` filled
|
|
90
|
+
ghost branch, with a 28px hit area. The close SVG is cloned from the native viewer.
|
|
91
|
+
|
|
92
|
+
Long text scrolls inside the panel, remains selectable, and is rendered with
|
|
93
|
+
textContent. The header Copy icon button reuses the existing Images Lucide copy/check glyphs
|
|
94
|
+
at 16px, with a 28px hit area and an accessible label/tooltip. It writes only the
|
|
95
|
+
generation prompt or original-request body to the clipboard and briefly confirms success. A generation prompt needs no source label; an original-request fallback must
|
|
96
|
+
include the missing-generation disclosure and its distinct label. Close and Escape return focus to
|
|
97
|
+
the Image dropdown trigger; Escape does not close the underlying viewer on the same press.
|
|
54
98
|
|
|
55
99
|
## Data and lifecycle
|
|
56
100
|
|
|
57
101
|
Read the source conversation through the running renderer's native authenticated
|
|
58
102
|
API; discover the API export through Library metadata's actual module imports,
|
|
59
|
-
without fixed build hashes.
|
|
60
|
-
are copied or sent to another service.
|
|
103
|
+
without fixed build hashes. Prompt extraction does not copy credentials or signed download URLs. Explicit Favorites/folder actions upload the selected indexed image bytes through native Library; no third-party service receives them.
|
|
61
104
|
|
|
62
105
|
Match both the originating message and exact image asset pointer, and require
|
|
63
|
-
assistant/tool generation metadata.
|
|
64
|
-
|
|
106
|
+
assistant/tool generation metadata. The Prompt viewer prefers a recorded
|
|
107
|
+
generator prompt: the exact asset's `dalle.prompt`, the exact result message's
|
|
108
|
+
`async_task_prompt`, or structured `prompt`/single `prompts` arguments from the
|
|
109
|
+
nearest image-generation tool call in its ancestor chain. Tool text is parsed as
|
|
110
|
+
JSON and never executed. Multi-image tool results do not infer per-image argument
|
|
111
|
+
ownership; unrelated tools, older generation calls and other branches are excluded. A traceable original user request is a different fact and is
|
|
112
|
+
shown only after an explicit missing-generation disclosure and “Original request” label. Unrelated branches are never used.
|
|
65
113
|
|
|
66
114
|
Changing images or leaving the viewer aborts the pending lookup and removes the
|
|
67
|
-
old UI. Late results cannot mount against a different image. Disabling the feature
|
|
115
|
+
old UI. 等缩放工具栏和视口就绪后再开始解析,避免先到的提示词结果把稍后出现的原生锚点短路;同图的缩放节点或承载改变时重新挂载。 Late results cannot mount against a different image. Disabling the feature
|
|
68
116
|
removes the observer, keyboard handler, button and panel. Prompt data is retained
|
|
69
117
|
only for the current viewer; there is no persistent conversation cache.
|
|
70
118
|
|
|
71
|
-
The viewer and retained cloud import utility share `src/domain/library-prompt.mjs`. The source factory serializes that pure rule into the viewer script; the cloud adapter serializes the same function into its evaluator call.
|
|
119
|
+
The viewer and retained cloud import utility share `src/domain/library-prompt.mjs`. The source factory serializes that pure rule into the viewer script; the cloud adapter serializes the same function into its evaluator call. The shared resolver distinguishes generation from request provenance; the viewer
|
|
120
|
+
preserves that distinction in fallback disclosure and labels. The historical import utility can retain a
|
|
121
|
+
traceable request with its distinct provenance. A valid generated image without prompt text can still be imported by the historical utility, while the viewer shows no prompt control.
|
|
122
|
+
|
|
123
|
+
### 本地提示词
|
|
124
|
+
|
|
125
|
+
Image Gen 按已索引的精确图片 ID 提取对应 thread 的生成记录。已完成 `image_gen.generation` 的 revisedPrompt 优先于可静态解析的工具请求参数。没有真实生成提示词则明确显示不可用。云端图片保留 generation → 已标明来源的 Original request 回退;已移除没有实测命中的云端 gen_id → 本地文件猜测补充链路。
|
|
126
|
+
|
|
127
|
+
2026-10-02 菜单核对:网格 More 使用原生 Library 的 mediaOverlay 分支,操作通过 leftIcon 传入真实原生图标。菜单按 Chat about this、View prompts、Add to Favorites/Remove from Favorites、Download、Add to folder、Delete 排列。收藏和文件夹使用真实云端 Library 身份;Delete 仅持久隐藏 Image Gen 条目。Chat about this 组合原生模式切换、新 Chat 草稿导航与图片面板链路,不发送消息;图片随消息提交的实际链路未进行发送验收。生成图片通用菜单中的重复查看、新标签、复制和 Finder 不混入图库菜单。Image Gen 不提供 Rename 与 Share。
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
## Image Gen 多选与隐藏
|
|
131
|
+
|
|
132
|
+
Codex 26.930.21537(2026-10-02):卡片通过原生 Card 的 selection、selected、selectionActive 与 onSelect 契约接入;圆形 Checkbox 使用原生 overlay 分支。选中后显示原生边框,支持 Checkbox、选择状态下点击卡片、Shift 区间选择、动作条 Clear selection 与 Escape。查询变化、切换来源、离开页面释放选中状态;分页加载保留已选条目,不污染原生 Your creations/Uploads 的 selection atoms。
|
|
133
|
+
|
|
134
|
+
缩略卡片沿用已核对的 Library 比例边界,宽高比限制在 1/2 至 20/13。卡片与图片比例分开处理:比例范围内按原比例展示;超出范围使用 object-contain 完整缩放并居中,极宽图上下留白,极窄图左右留白,不裁切图片内容。此显示策略按用户截图与验收要求实现,不将 Library 通用 object-cover 分支视为完整的 Images 展示规范。原图详情不改变比例。不能直接用原始极宽/极窄比例承载 Card,避免 More 与右下 Checkbox 重叠或单张卡片过高。
|
|
135
|
+
|
|
136
|
+
动作条复用原生 Button、Dropdown、Menu、图标与 Library 布局分支,固定在底部,dark 材质,包含数量、Start chat、Download、Delete、More→Move 和 Clear selection。Start chat 调用原生 Chat 附件上传和草稿导航,不发送消息;Download 使用原生保存流程逐项处理;Move 在明确点击后登记所选图片并使用原生文件夹选择器,选择本身不上传。权限/忙状态属于当前操作,不伪造云端图片身份。
|
|
137
|
+
|
|
138
|
+
Delete 在单张 More 与批量动作条共用一条隐藏流程,复用原生 Dialog、LibraryItemConfirmationContent 和实色警戒确认按钮。确认标题明确限定 Image Gen,正文明确“仅隐藏,本地原图和云端副本保留”。取消不改变任何条目;确认成功后立即移除当前条目、更新选中数、列表总数和分页偏移,并显示原生 toast。隐藏 ID 存于当前 Runtime profile 的 `hidden-image-ids.json`,本地源文件、原始生成提示词及已登记云端身份不修改;刷新和重启后继续隐藏。
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
## 维护接口(2026-10-02)
|
|
142
|
+
|
|
143
|
+
`adapters/codex/native-image-modules.mjs` 拥有构建产物读取、import 引用与组件发现。导航、图库、画布分别连接,画布与 Composer 在用户打开图片时加载;画布契约失效通过操作反馈报告,不移除仍可用的图库。Composer 角色按文件预览实际调用关系解析,不固定压缩变量名。
|
|
144
|
+
|
|
145
|
+
`adapters/codex/native-image-source.mjs` 拥有 React 呈现、上下文、原生动作与 UI 生命周期。异步结果进入对话框、收藏状态或原生修改动作前核对挂载实例及账号;成功上传的云端身份仍须登记,不能因页面关闭丢失已完成上传。普通 workflow 只接收/返回 `libraryFileId` 与 `created`,原生文件节点和 MoveDialog entries 由 Adapter 构建。
|
|
146
|
+
|
|
147
|
+
`library-prompt-source.mjs` 只序列化显式依赖;`library-prompt-page.mjs` 直接安装本地来源并组合清理,不通过普通语句的文本替换接线。清理为幂等操作,手动卸载与 signal abort 不重复释放。
|
|
@@ -1,27 +1,37 @@
|
|
|
1
|
-
# Images ·
|
|
1
|
+
# Images · Image Gen 与图片提示词
|
|
2
2
|
|
|
3
|
-
Images 在 Codex 原生 **
|
|
3
|
+
Images 在 Codex 原生 **Space → Images** 中增加 **Image Gen**,集中浏览本机 Codex 或本地 Work 生成的图片。
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
5
|
+
- Image Gen 位于 Your creations、Uploads 后,图片按生成时间倒序显示,支持搜索和滚动加载。
|
|
6
|
+
- 标签、加载骨架、图片排布、悬浮卡片和菜单复用原生组件。
|
|
7
|
+
- 点击图片进入原生详情,复用缩放、打开、下载以及可用的 Remove BG、Erase、Resize 编辑入口。
|
|
8
|
+
- 本地图库菜单支持 Chat about this、Add to Favorites、Download、Add to folder、Delete 和 View prompts。仅点击收藏/文件夹操作才上传所选图片到原生 Library,浏览不上传;云端收藏和文件夹关系由服务器管理。
|
|
9
|
+
- 图片菜单中的 **View prompts / 查看提示词** 打开与原生 File info 一致的右侧面板;加载时显示骨架并禁用复制。
|
|
10
|
+
- 本地图片按精确生成记录读取提示词。云端图片优先显示真实生成提示词;只有原始用户请求时,明确告知差异并标注 Original request,再提供复制。
|
|
11
|
+
- 切换图片会移除旧提示词;Escape 先关闭提示词面板。
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
图片浏览、编辑与下载使用 Codex 原生能力。读取仅在本机原生 Renderer 内完成,
|
|
13
|
-
沿用当前登录态,不复制图片或持久化来源对话。
|
|
13
|
+
索引在当前 Runtime profile 中维护,读取本机生成目录与会话日志,不迁移或删除源文件。原生组件和 API 契约不匹配时保留原生页面;Codex 更新后的可用性需要重新核验。
|
|
14
14
|
|
|
15
15
|
## 开发
|
|
16
16
|
|
|
17
|
-
统一宿主通过 `codex-feature.json`
|
|
18
|
-
|
|
17
|
+
统一宿主通过 `codex-feature.json` 与 `src/library-prompt-source.mjs` 组装页面脚本。
|
|
18
|
+
|
|
19
|
+
- `src/backend/local-image-source.mjs`:本地索引、提示词与云端身份映射/上传预留。
|
|
20
|
+
- `src/frontend/cloud-image-actions.mjs`:首次云端登记、去重与收藏工作流;只返回云端身份,不解释原生文件节点。
|
|
21
|
+
- `src/adapters/codex/native-image-modules.mjs`:原生构建产物发现,按导航/图库/画布能力分别连接。
|
|
22
|
+
- `src/adapters/codex/native-image-source.mjs`:原生呈现、上下文及异步动作归属。其余 Adapter 维护 Library API、图片来源与提示词协议。
|
|
23
|
+
- `src/frontend/local-image-source.mjs`:来源选择、搜索、分页和生命周期。
|
|
24
|
+
- Runtime Host:鉴权、功能声明校验与转发。
|
|
25
|
+
|
|
26
|
+
视觉与能力边界见 [DESIGN.md](DESIGN.md)。
|
|
19
27
|
|
|
20
28
|
```bash
|
|
21
|
-
node --test features/images/test
|
|
29
|
+
node --test features/images/test/*.test.mjs
|
|
22
30
|
```
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
独立 `web/` 与旧服务入口保留为已有历史工具,不由当前 manifest 启动;ImageLibrary 与日志索引已由当前本地来源复用。历史图库交接见 [research/images](../../docs/research/images/2026-08-07-gallery-handoff.md)。
|
|
33
|
+
|
|
34
|
+
2026-10-02 菜单核对:网格 More 明确使用原生 Library 的 mediaOverlay 分支,操作通过 leftIcon 传入真实原生图标。图库菜单按 Chat about this、View prompts、Add to Favorites/Remove from Favorites、Download、Add to folder、Delete 排列。Chat about this 组合原生模式切换、新 Chat 草稿导航与图片面板链路,不发送消息;导航与面板已隔离验证,图片随消息提交的实际链路未进行发送验收。生成图片通用菜单中的重复查看、新标签、复制和 Finder 不再混入图库菜单。Add to Favorites/Remove from Favorites 和 Add to folder 已接入原生云端 Library。首次操作上传所选图片,之后复用账号与内容指纹对应的云端身份;收藏与文件夹归属保存在服务器。取消文件夹选择会显示原生 toast,已上传的图片仍留在 Library。上传结果不确定时不盲目重试;Image Gen 不提供分享与重命名;Delete 的隐藏范围见下文。
|
|
35
|
+
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
Image Gen 支持原生圆形多选框、Shift 区间选择和底部动作条。Delete 会先二次确认,仅从 Image Gen 隐藏;本地原图和云端副本保留。隐藏状态由 MineCodex 当前 Runtime profile 持久维护,确认后立即生效,刷新/重启不再显示。缩略卡片沿用 Library 的 1:2 至 20:13 比例范围;极宽和极窄图片在卡片内完整缩放、居中留白,不裁切内容。
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"zh-CN": "图片"
|
|
8
8
|
},
|
|
9
9
|
"description": {
|
|
10
|
-
"en": "
|
|
11
|
-
"zh-CN": "
|
|
10
|
+
"en": "Browse generated images and copy their prompts.",
|
|
11
|
+
"zh-CN": "浏览本地生成图片,查看与复制提示词。"
|
|
12
12
|
},
|
|
13
13
|
"icon": {
|
|
14
14
|
"viewBox": "0 0 24 24",
|
|
@@ -18,5 +18,8 @@
|
|
|
18
18
|
"kind": "page-script",
|
|
19
19
|
"script": "src/library-prompt-page.mjs",
|
|
20
20
|
"sourceFactory": "src/library-prompt-source.mjs"
|
|
21
|
-
}
|
|
21
|
+
},
|
|
22
|
+
"hostActions": [
|
|
23
|
+
"local-image-source"
|
|
24
|
+
]
|
|
22
25
|
}
|
|
@@ -6,13 +6,35 @@ export function imagePromptFacts(file, conversation) {
|
|
|
6
6
|
&& [file.file_id, `sediment://${file.file_id}`].includes(part.asset_pointer));
|
|
7
7
|
if (!part || !["assistant", "tool"].includes(node.message.author?.role)
|
|
8
8
|
|| !(part.metadata?.generation?.gen_id || part.metadata?.dalle?.gen_id)) return null;
|
|
9
|
-
const
|
|
9
|
+
const recorded = [part.metadata?.dalle?.prompt, node.message.metadata?.async_task_prompt];
|
|
10
|
+
const prompt = recorded.find(value => typeof value === "string" && value.trim());
|
|
10
11
|
if (typeof prompt === "string" && prompt.trim()) return { generated:true, generationPrompt:prompt, requestText:null };
|
|
12
|
+
// 只沿当前图片祖先链查找实际生图调用;同一结果含多图时不猜参数归属。
|
|
13
|
+
const imageParts = node.message.content.parts.filter(value => value?.content_type === "image_asset_pointer");
|
|
11
14
|
let ancestor = conversation.mapping[node.parent];
|
|
12
15
|
const visited = new Set();
|
|
16
|
+
let imageCallChecked = false;
|
|
13
17
|
while (ancestor && !visited.has(ancestor.id)) {
|
|
14
18
|
visited.add(ancestor.id);
|
|
15
|
-
|
|
19
|
+
const message = ancestor.message;
|
|
20
|
+
const recipient = message?.recipient;
|
|
21
|
+
const imageCall = message?.author?.role === "assistant" && typeof recipient === "string"
|
|
22
|
+
&& (recipient === node.message.author?.name || ["image_gen", "image_gen.text2im", "dalle", "dalle.text2im"].includes(recipient) || recipient.startsWith("t2uay3k."));
|
|
23
|
+
if (imageCall && !imageCallChecked) {
|
|
24
|
+
imageCallChecked = true;
|
|
25
|
+
const content = message.content;
|
|
26
|
+
const text = content?.content_type === "code" ? content.text
|
|
27
|
+
: ["text", "multimodal_text"].includes(content?.content_type) ? content.parts?.filter(value => typeof value === "string").join("\n") : null;
|
|
28
|
+
try {
|
|
29
|
+
const args = JSON.parse(text);
|
|
30
|
+
const value = typeof args?.prompt === "string" ? args.prompt
|
|
31
|
+
: Array.isArray(args?.prompts) && args.prompts.length === 1 ? args.prompts[0] : null;
|
|
32
|
+
if (imageParts.length === 1 && typeof value === "string" && value.trim()) return { generated:true, generationPrompt:value, requestText:null };
|
|
33
|
+
} catch { /* 不执行工具文本,也不从非结构化描述猜测参数。 */ }
|
|
34
|
+
// 最近一次生图调用缺参数时停止,不串到更早的生成任务。
|
|
35
|
+
// 继续读取独立的原始请求事实,但不再接受更早的工具参数。
|
|
36
|
+
}
|
|
37
|
+
if (message?.author?.role === "user") {
|
|
16
38
|
const text = ancestor.message.content?.parts?.filter(part => typeof part === "string").join("\n");
|
|
17
39
|
return { generated:true, generationPrompt:null, requestText:text?.trim() ? text : null };
|
|
18
40
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function createImageSourcesAdapter(codex) {
|
|
2
|
+
const isImageNavigation = props => props?.variant === "browse"
|
|
3
|
+
&& props.tabs?.some(item => item.key === "generated")
|
|
4
|
+
&& props.tabs?.some(item => item.key === "uploaded");
|
|
5
|
+
|
|
6
|
+
return {read() {
|
|
7
|
+
const tabs = [...document.querySelectorAll('[role="tablist"]')].find(node =>
|
|
8
|
+
!node.closest("[data-minecodex-image-navigation]")
|
|
9
|
+
&& codex.props(node, isImageNavigation));
|
|
10
|
+
if (!tabs) return null;
|
|
11
|
+
const navigation = tabs.parentElement?.parentElement;
|
|
12
|
+
const content = navigation?.nextElementSibling;
|
|
13
|
+
const shell = navigation?.parentElement;
|
|
14
|
+
if (!content || !shell) return null;
|
|
15
|
+
const props = codex.props(tabs, isImageNavigation);
|
|
16
|
+
const search = shell.querySelector('input[type="search"],input[placeholder="Search"]');
|
|
17
|
+
return {tabs, navigation, content, shell, search, selectedKey:props.selectedKey};
|
|
18
|
+
}};
|
|
19
|
+
}
|
|
@@ -48,6 +48,8 @@ export function createLibraryApi({read:readSource=async url=>(await fetch(url)).
|
|
|
48
48
|
return (await downloadCapability)(file,signal);
|
|
49
49
|
}
|
|
50
50
|
return {
|
|
51
|
+
fileNode:async(id,signal)=>(await connect()).api.safeGet("/files/library/files/{library_file_id}/node",{parameters:{path:{library_file_id:id}},signal}),
|
|
52
|
+
favoriteStatus:async(id,signal)=>(await connect()).api.safePost("/files/library/favorites/status",{requestBody:{target_refs:[{type:"library_file",library_file_id:id}]},signal}),
|
|
51
53
|
conversation:async(id,signal)=>(await connect()).api.safeGet("/conversation/{conversation_id}",{parameters:{path:{conversation_id:id}},signal}),
|
|
52
54
|
list:async(cursor,signal)=>(await connect()).api.safePost("/files/library",{requestBody:{limit:50,cursor:cursor ?? null,include_hidden_files:false,include_saved_entities:true},signal}),
|
|
53
55
|
download,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export function createLibraryViewerAdapter(codex) {
|
|
1
|
+
export function createLibraryViewerAdapter(codex, imageSource = null) {
|
|
2
2
|
function libraryImage(header) {
|
|
3
|
+
const local=imageSource?.readViewer(header);
|
|
4
|
+
if(local)return {kind:"local",id:local.id,threadId:local.threadId,itemId:local.imageGenerationItemId};
|
|
3
5
|
let fiber = codex.currentFiber(header);
|
|
4
6
|
let selected = null;
|
|
5
7
|
for (let depth = 0; fiber && depth < 80; depth++, fiber = fiber.return) {
|
|
@@ -14,5 +16,27 @@ function libraryImage(header) {
|
|
|
14
16
|
return Object.fromEntries(["id", "file_id", "current_version_number", "origination_thread_id", "origination_message_id"].map(key => [key, selected[key]]));
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
function toolbar(header) {
|
|
20
|
+
// 原生缩放菜单含隐藏的尺寸测量文字,只读取实际显示的百分比。
|
|
21
|
+
const zoom = [...header.querySelectorAll('button[aria-haspopup="menu"]')]
|
|
22
|
+
.find(button => /^\d+(?:\.\d+)?%$/.test(button.innerText.trim()));
|
|
23
|
+
const group = zoom?.parentElement?.parentElement;
|
|
24
|
+
// Space 的嵌入式图片视口与浮层是原生两种承载方式。
|
|
25
|
+
const host = header.closest('.relative.flex.h-full.min-w-0.flex-1') ?? header.closest('[role="presentation"]');
|
|
26
|
+
const trigger = header.querySelector('[data-viewer-header-collapsible]')?.closest("button");
|
|
27
|
+
return zoom && group && host && trigger ? {zoom,group,host,trigger} : null;
|
|
28
|
+
}
|
|
29
|
+
function promptMenu(file) {
|
|
30
|
+
if(file.kind === "local") return null;
|
|
31
|
+
for (const menu of document.querySelectorAll('[role="menu"][data-state="open"]')) {
|
|
32
|
+
if (!menu.getBoundingClientRect().width || libraryImage(menu)?.id !== file.id) continue;
|
|
33
|
+
const original = [...menu.querySelectorAll('a[role="menuitem"][href]')].find(item => {
|
|
34
|
+
const url = new URL(item.getAttribute("href"), location.href);
|
|
35
|
+
return url.pathname === `/c/${file.origination_thread_id}` && url.searchParams.get("messageId") === file.origination_message_id;
|
|
36
|
+
});
|
|
37
|
+
if (original) return {menu,original};
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return {read:libraryImage,toolbar,promptMenu};
|
|
18
42
|
}
|