minecodex 2.0.4 → 2.0.5

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.
Files changed (38) hide show
  1. package/DESIGN.md +25 -1
  2. package/README.md +4 -2
  3. package/features/images/DESIGN.md +106 -41
  4. package/features/images/README.md +25 -16
  5. package/features/images/codex-feature.json +6 -3
  6. package/features/images/src/adapters/codex/image-prompt.mjs +24 -2
  7. package/features/images/src/adapters/codex/image-sources.mjs +19 -0
  8. package/features/images/src/adapters/codex/library-api.mjs +2 -0
  9. package/features/images/src/adapters/codex/library-viewer.mjs +26 -2
  10. package/features/images/src/adapters/codex/native-image-source.mjs +556 -0
  11. package/features/images/src/backend/local-image-source.mjs +138 -0
  12. package/features/images/src/frontend/cloud-image-actions.mjs +39 -0
  13. package/features/images/src/frontend/local-image-source.mjs +194 -0
  14. package/features/images/src/library-prompt-page.mjs +158 -53
  15. package/features/images/src/library-prompt-source.mjs +11 -3
  16. package/features/images/src/prompt-index.mjs +14 -2
  17. package/features/model-slider/DESIGN.md +2 -0
  18. package/features/model-slider/src/frontend/install.mjs +78 -11
  19. package/features/notes/DESIGN.md +7 -3
  20. package/features/notes/src/frontend/entry.mjs +26 -9
  21. package/features/session-status/DESIGN.md +4 -2
  22. package/features/session-status/src/adapters/codex/session-log.mjs +2 -1
  23. package/features/session-status/src/domain/session-stats.mjs +17 -9
  24. package/features/session-status/src/session-status.mjs +47 -11
  25. package/package.json +1 -1
  26. package/packages/codex-adapter/README.md +2 -1
  27. package/packages/codex-adapter/src/renderer/environment.mjs +24 -6
  28. package/packages/runtime-host/src/codex-injection.mjs +3 -6
  29. package/packages/runtime-host/src/codex-runtime.mjs +16 -5
  30. package/packages/runtime-host/src/feature-registry.mjs +1 -0
  31. package/packages/ui/DESIGN.md +5 -2
  32. package/packages/ui/src/components/button.mjs +2 -0
  33. package/packages/ui/src/components/navigation-rail.mjs +18 -10
  34. package/packages/ui/src/components/page.mjs +9 -2
  35. package/packages/ui/src/install.mjs +3 -1
  36. package/packages/ui/src/native/navigation-rail.mjs +6 -1
  37. package/packages/ui/src/native/page.mjs +23 -0
  38. package/packages/ui/src/native/tokens.mjs +3 -0
package/DESIGN.md CHANGED
@@ -44,7 +44,8 @@ Token 名称是实现依据;括号内数值是本版本观测值。读取实
44
44
  | Cancel 等取消操作(`ghost`) | 透明、无描边;hover 才出现底色 | `--color-text-secondary`、`--color-background-primary-ghost-hover` |
45
45
  | 有实际功能的次级操作(outline,现有API tone为`secondary`) | 透明底、内描边 | `--color-text-primary`、`--color-border-primary-outline`、`--color-background-primary-outline-hover` |
46
46
  | 主操作(solid,现有API tone为`primary`) | 实色底与配对文字色 | `--color-background-primary-solid`、`--color-text-primary-solid`、`--color-background-primary-solid-hover` |
47
- | 警戒操作(现有`danger`) | 警戒色,独立于主操作;插件负责确认业务 | `--color-background-danger-soft-alpha`、`--color-background-danger-soft-alpha-hover`、`--color-text-danger` |
47
+ | 轻量警戒操作(现有`danger`) | 警戒色,独立于主操作;插件负责确认业务 | `--color-background-danger-soft-alpha`、`--color-background-danger-soft-alpha-hover`、`--color-text-danger` |
48
+ | 实色警戒操作(`danger-solid`;原生 React 为 `dangerSolid`) | 用于明确的删除/移除动作,实色警戒底与配对文字;操作范围和二次确认由业务决定 | `--color-background-danger-solid`、`--color-text-danger-solid`;Library 动作条 hover 为 solid 90% alpha |
48
49
 
49
50
  尺寸:`--control-size-lg`(36px)、`--control-font-size-md`(13px)、`--font-weight-normal`(原生按钮400)、`--radius-full`。水平 gutter 为 `--control-gutter-md × --control-gutter-pill-scaling`(15.96px),图文 gap 为 `--button-gap-lg`。焦点使用 `--color-ring`;禁用保留语义并阻止 hover 反馈。
50
51
 
@@ -65,6 +66,12 @@ Dropdown 的 `--shadow-dropdown-overlay` 当前指向 `--shadow-xl-spread`,该
65
66
 
66
67
  Composer菜单的锚点是实际输入框主体(当前语义标记 `data-composer-body`),不是包含diff和请求队列的外层Root。挂载位置、可用高度、zoom与碰撞行为沿用对应原生组件,不为修复坐标问题盲目提高z-index。
67
68
 
69
+ ## Viewer 附属信息面板
70
+
71
+ Codex 26.930.21537(2026-10-02 原生 File info DOM 与计算样式实测):viewer 右侧附属信息面板使用不透明 `bg-surface-elevated-secondary`、`rounded-2xl`(20px)和 Electron `elevation-prominent`;没有容器模糊,也不额外叠加 CSS border 或 `shadow-lg`。该 elevation 自带 0.5px 边缘及两层柔和投影,应直接沿用原生 utility。此分支不同于 Dialog、Dropdown 和页面内容面。
72
+
73
+ 标题栏使用固定高度、原生 text-base/semibold 标题及圆形 `button-toolbar` 的 `bg-text/5` 按钮分支,hover 为 `bg-text/10`。面板宽度、viewer 工具栏避让及正文间距由所选原生信息面板的业务布局确定,在功能 DESIGN.md 记录。Images Prompt 采用当前 File info 分支。
74
+
68
75
  ## Page 与 sidebar
69
76
 
70
77
  页面内容面使用 `--color-surface`,当前不透明、无模糊,`--radius-xl-base`(12px);外部PageSurface使用 `--app-shell-page-surface-shadow`,当前由 `--elevation-stroke-subtle` 与 `--shadow-card` 组成。区分内容面和窗口外壳,不重复施加阴影。
@@ -76,6 +83,18 @@ sidebar规则分状态:
76
83
  | 非peeking sidebar-navigation | `--color-surface` 65%(oklab混合),无CSS模糊、无浮窗阴影;可见桌面程度还取决于窗口材质 |
77
84
  | `data-slate-sidebar-peeking=true` | `--app-shell-sidebar-popover-background`强制alpha=1;`--app-shell-sidebar-popover-radius`当前为radius-xl-base减spacing(8px);`--hairline`、`--app-shell-sidebar-popover-border`内边缘与`--shadow-card` |
78
85
 
86
+ ### 全页面功能的页面壳选择
87
+
88
+ 后续功能开发独立全页面时,先按页面自身的导航需求选择页面壳。没有专属二级导航的页面默认使用无二级侧栏的 Page 分支,以原生 Projects 为行为参照;只有任务确实需要常驻二级导航时才选择带侧栏的布局。不能因入口来自 Home、Space、Scheduled 就继承它们的常驻二级侧栏。
89
+
90
+ 页面是否带常驻二级侧栏,与用户的侧栏展开/收起偏好是两个独立语义。无二级侧栏页面只保留 Navbar,内容从 Navbar 右侧开始,不调用侧栏收起动作,也不修改用户的展开偏好。悬浮 Home、Space、Scheduled 等入口时,原生二级侧栏仍叠加在页面上,页面内容不移位;鼠标可进入操作,离开后沿用原生退出行为。
91
+
92
+ 页面壳、原生导航适配与恢复由共享 UI 维护,功能只声明所需分支并负责自己的内容。新功能不得复制侧栏状态机、直接切换原生侧栏按钮,或另写隐藏侧栏的 CSS。退出页面时区分返回原页面与用户主动选择原生导航;卸载/禁用时释放页面壳并恢复所属状态。
93
+
94
+ 验收至少覆盖:从 Home、Space、Scheduled 进入时无常驻二级侧栏;原有展开偏好不变;三个入口的悬浮侧栏覆盖页面且可操作,内容不移位;退出、原生导航与卸载恢复正确。
95
+
96
+ 原生依据与当前实现:Codex 26.930.21537 的源码与隔离窗口确认该布局由路由决定。当前注入 Page 借用 `/projects` 作为原生页面壳,关闭时恢复原位置,用户主动选择原生导航时交还路由;这是版本相关的适配实现,不是功能的业务路由规范。后续原生接口变化只更新共享 Adapter,不改变上述页面语义。
97
+
79
98
  保留原生的固定展开、收起与hover状态。hover sidebar应能覆盖注入页面,鼠标能进入操作,移回页面后可正常收起;不要通过页面层级、pointer-events或额外遮罩挡住它。原生overflow和contain切换负责浮出与裁切,插件不复制一套侧栏状态机。
80
99
 
81
100
  ## 左侧 Navbar
@@ -147,3 +166,8 @@ Navbar 在入口右侧居中,使用 `data-minecodex-tooltip-side="right"`;
147
166
  这里的规范名称是稳定的项目映射键。Codex 更新名称时补充原生别名,不能跟着重命名插件消费的项目 token。新增实际使用的语义才增加映射;不预建完整的第三方 token 系统。`native/theme.mjs` 为直接注入容器写入同一快照,Runtime Host 将它传给 iframe;卸载恢复原值。主题变化丢失某个 token 时清除旧项目值,继续使用该组件既有的明确默认值或不可用状态,不复用上一主题颜色。
148
167
 
149
168
  这些映射不改变原生组件分类、材质分支和 hover sidebar 层级,也不重写原生 Tailwind utility class。插件复用 native Adapter 返回的原生模板;特殊样式使用项目 token,Dialog、Button、Tooltip 等通用表现继续由组件库维护。
169
+
170
+
171
+ ### Library 多选动作条的实色警戒按钮
172
+
173
+ Codex 26.930.21537(2026-10-02 原生源码与隔离窗口):Library 选中动作条的 Delete 使用 `dangerSolid`、full radius、toolbar size,图标 16px,警戒底与白色配对文字;hover 使用 danger solid 90% alpha,禁用保留原生 40% opacity 与禁止交互。确认弹窗也使用实色警戒提交按钮。共享 DOM 入口为 `ui.action(button, "danger-solid")`,和既有 soft `danger` 分支并存,不迁移原有功能的语义。通用组件只提供视觉和交互状态,删除确认、隐藏/回收站/永久删除的范围由所属功能明确告知。
package/README.md CHANGED
@@ -13,7 +13,7 @@ MineCodex 给 Codex Desktop 增加一组轻量、本地优先的扩展功能。
13
13
  | **Model Slider** | 更清晰、更顺滑的模型切换面板:具备品牌标识、常用收藏与流畅交互 |
14
14
  | **Prompts** | 专页分类管理提示词与调用统计,输入框 Quick Prompts 快速调用 |
15
15
  | **Usage Meter** | 在 Help 与 Account 之间显示剩余额度环,悬浮查看周 / 五小时额度,点击进入 Usage 设置 |
16
- | **Images** | 在原生 Library 图片详情中查看并复制提示词 |
16
+ | **Images** | 在原生 Images 中浏览本地 Image Gen 图片,查看并复制提示词 |
17
17
  | **Hide Upsell Banner** | 隐藏输入框上方的用量提示横幅,仅优化界面展示,不影响正常的用量统计 |
18
18
  | **Session Status** | 在输入框下方查看当前任务的步骤、观测速度、Token 用量与上下文明细 |
19
19
  | **Task Switcher** | 点击聊天标题或标签页前的任务按钮,查看并切换工作中与未读会话 |
@@ -56,7 +56,7 @@ mcx open
56
56
 
57
57
  ## 工作原理
58
58
 
59
- MineCodex 统一管理 Codex 内的菜单入口、主题风格与界面适配。Model Slider、Prompts 各自管理操作界面,Images 增强原生 Library 的提示词查看;Session Status 只读汇总本机 Codex 会话事件,Hide Upsell Banner 优化界面展示。所有功能按需开启,关闭后即停止运行。
59
+ MineCodex 统一管理 Codex 内的菜单入口、主题风格与界面适配。Model Slider、Prompts 各自管理操作界面,Images 在原生 Images 中接入本地生成图片和提示词查看;Session Status 只读汇总本机 Codex 会话事件,Hide Upsell Banner 优化界面展示。所有功能按需开启,关闭后即停止运行。
60
60
 
61
61
  ## 开发
62
62
 
@@ -68,6 +68,8 @@ npm run pack:check
68
68
  npm run pack:audit
69
69
  ```
70
70
 
71
+ 维护者发布流程见 [GitHub Actions 发布说明](docs/npm-publishing.md)。发布工作流仅允许维护者本人触发,使用 npm 可信发布,无需长期访问令牌。
72
+
71
73
  ## 许可
72
74
 
73
75
  MineCodex 是非官方独立扩展项目,与 OpenAI 无关联。项目自有代码以 MIT License 授权;第三方图标、品牌和兼容资产遵循各自的权利说明,见 [`features/model-slider/THIRD_PARTY_NOTICES.md`](features/model-slider/THIRD_PARTY_NOTICES.md) 与 [`features/session-status/vendor/lucide-LICENSE`](features/session-status/vendor/lucide-LICENSE)。
@@ -4,11 +4,33 @@
4
4
 
5
5
  ## Scope
6
6
 
7
- Images enhances Codex's native Library image viewer. It does not inject a sidebar
8
- item after Sites, open its own gallery, import image bytes, or replace native
9
- editing/download controls. The previous standalone gallery and local index code
10
- remain available as legacy utilities, but the feature manifest no longer starts
11
- that service or registers that surface.
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
+ 图库菜单复用原生 mediaOverlay More Button、Dropdown、Menu.Item 与原生图标,按 Chat about this、Add to Favorites/Remove from Favorites、Download、Add to folder、Delete、View prompts 排列。View prompts 的图标约束为菜单使用的 16px,不继承导航栏尺寸。
26
+
27
+ 只有用户点击收藏或文件夹操作时,通过原生 Library upload(required persistence)上传所选 PNG;取得真实 Library ID 后保存账号、用户、本地图片 ID、SHA-256 内容指纹对应关系。收藏调用原生 setLibraryFavorite 并以服务器状态切换,文件夹使用原生 MoveLibraryItemsDialog;不在本地保存收藏或文件夹关系。云端节点读取采用原生 `/files/library/files/{library_file_id}/node` 契约,不能把上传返回的 Library ID 当普通内容 file ID。
28
+
29
+ 取消选择通过原生 toast 服务反馈。该操作首次上传后取消显示“Image saved to Library, but not added to a folder.”;已有云端身份则显示“No folder selected.”。取消不删除云端图片、不移动文件;移动成功后的关闭不误报取消。浏览与打开菜单不上传,收藏状态查询不创建记录。
30
+
31
+ 登记工作流位于 `src/frontend/cloud-image-actions.mjs`。同时操作同一图片复用在途任务,Backend 原子预留避免不同窗口重复上传。已确认的身份跨 Runtime 重启保留;内容变化或账号变化不复用。上传结果不确定时保留预留状态并用原生错误 toast 提醒检查 Library,禁止盲目重新上传;自动恢复不确定记录尚未实现。云端条目删除或访问失效时报告操作失败,不自动重新上传。
32
+
33
+ 原生构建产物和组件导出契约集中在 `src/adapters/codex/native-image-source.mjs`。按实际模块引用发现构建文件,不固定哈希;契约不匹配时保留原生页面并报告能力缺失。`src/frontend/local-image-source.mjs` 仅负责来源选择、分页、搜索与清理,Runtime Host 只鉴权和转发 `local-image-source` 的 list / prompt 与身份登记操作。
12
34
 
13
35
  ## Entry and identity
14
36
 
@@ -18,54 +40,97 @@ originating conversation/message IDs. Non-Library viewers and non-image files
18
40
  are excluded. No title, filename, image position or minified component name is
19
41
  used as identity. Missing native anchors fail closed.
20
42
 
21
- Resolve the prompt before adding the button. Missing/empty prompts, uploaded
22
- references without generated metadata, inaccessible conversations, and unresolved
23
- native API capabilities all leave the toolbar unchanged. There is no disabled
24
- button, loading placeholder, or empty prompt panel.
25
-
26
- The text button is placed immediately before the native percentage/zoom menu and
27
- reuses its live base classes for capsule sizing, colors, border, hover and focus.
28
- Dropdown/zoom variants are excluded: width is max-content, flex is 0 0 auto, and
29
- symmetric inline padding is three native spacing units (12px in this runtime).
30
- The label is Prompt / 提示词, following the native document locale.
43
+ Codex 26.930.21537(2026-10-02 隔离实测):Space 图片详情使用嵌入式视口,Adapter 从其原生 `relative flex h-full min-w-0 flex-1` 容器取得面板承载;浮层分支沿用 presentation 容器。缩放按钮包含隐藏的测量百分比,识别时读取 `innerText`,不读取包含这些测量文字的 `textContent`。
44
+
45
+ Prepare the Prompt panel as soon as the native image has both originating conversation
46
+ and message IDs and its viewer controls are ready; start source-conversation extraction in
47
+ parallel. This is eligibility to look up a prompt, not proof that prompt text exists.
48
+ Images without these source IDs have no View prompts entry. Exact generated-asset
49
+ validation remains part of extraction, so uploaded references never supply prompt text.
50
+
51
+ Opening while extraction is pending shows the complete File info-style header,
52
+ a disabled Copy button, and four static rounded skeleton lines in the body.
53
+ `aria-busy` marks the body; decorative lines are hidden from assistive technology.
54
+ Closing does not cancel prefetch. Success replaces skeletons with selectable text
55
+ and enables Copy. Missing generation text falls back to a traceable original request when available:
56
+ first disclose that the generation prompt is unavailable, label the body “Original
57
+ request”, and enable Copy with the accessible label “Copy original request”. If
58
+ neither kind exists, show the unavailable message and leave Copy disabled. Failure ends with
59
+ a localized message and Retry action. There is no perpetual loading placeholder.
60
+ Changing images or disabling the feature aborts the old request; late results
61
+ cannot change the new panel. Skeletons are static and respect reduced-motion.
62
+
63
+ The entry is a native-style “View prompts” / “查看提示词” menu item in the image
64
+ header's File actions dropdown, immediately before View original chat. There is
65
+ no dedicated right-toolbar button. Adapter validates the menu's current Library
66
+ file and the original-chat link's conversation/message IDs before inserting.
67
+ The item uses the native row classes and existing Prompts glyph; native React
68
+ nodes remain intact. It is recreated when the native menu remounts, without
69
+ refetching prompt content. Arrow navigation includes the injected item; Enter /
70
+ Space activate it. Opening dismisses the native menu, then focuses panel Close.
71
+ Panel Close / Escape return focus to the Image dropdown trigger.
31
72
 
32
73
  ## Prompt panel
33
74
 
34
- The panel follows the native Library Version history implementation inspected in
35
- the isolated Codex runtime on 2026-09-27 (`layout` asset, version-history aside):
36
-
37
- - `absolute inset-x-2 top-toolbar bottom-2 z-30 ms-auto mt-2 max-w-75`.
38
- - `rounded-2xl border border-default bg-surface shadow-lg`.
39
- - Header: `flex items-center justify-between py-2 ps-4 pe-2`;
40
- title: `text-base font-semibold text-default`.
41
- - Body: `min-h-0 flex-1 overflow-y-auto px-3`, with one spacing unit of
42
- top padding (4px). The panel reserves three spacing units below the scroll
43
- viewport (12px), so scrolling text never meets the rounded bottom edge.
44
- - Prompt: `text-sm leading-5 text-default whitespace-pre-wrap select-text`.
45
-
46
- These are native tokens, not a plugin palette or fixed radius. In the inspected
47
- runtime they resolve to 300px width and 20px corners. The close glyph is cloned
48
- from the native viewer control. Long text scrolls inside the panel, remains
49
- selectable, and is rendered with textContent. The header Copy icon button reuses the existing Images Lucide copy/check glyphs
50
- at 16px, with a 32px hit area and an accessible label/tooltip. It writes only the
51
- prompt body to the clipboard and briefly confirms success. No source label or
52
- explanatory metadata appears in the panel. Close and Escape return focus to
53
- the prompt button; Escape does not close the underlying viewer on the same press.
75
+ The panel adopts the native File info accessory-panel branch, inspected in Codex
76
+ 26.930.21537 on 2026-10-02. General material rules are maintained in the root
77
+ DESIGN.md; this feature uses the following native layout:
78
+
79
+ - Right and bottom gutters: two spacing units (8px). Top: `pt-13` plus the
80
+ native wrapper's `pt-px` (53px from the viewer viewport top).
81
+ - Width: 74 spacing units (296px), constrained to the viewport minus both gutters.
82
+ - Header: `flex h-11 shrink-0 items-center justify-between gap-2 py-1 ps-4 pe-2`
83
+ (44px); `h2` contains a `text-base font-semibold` title.
84
+ - Body: `min-h-0 flex-1 overflow-y-auto px-3 py-4` (12px horizontal,
85
+ 16px vertical padding). Prompt paragraph retains `px-1 text-sm leading-5
86
+ text-default whitespace-pre-wrap break-words select-text` for long text.
87
+ - Header controls use the native File info circular `button-toolbar` filled
88
+ ghost branch, with a 28px hit area. The close SVG is cloned from the native viewer.
89
+
90
+ Long text scrolls inside the panel, remains selectable, and is rendered with
91
+ textContent. The header Copy icon button reuses the existing Images Lucide copy/check glyphs
92
+ at 16px, with a 28px hit area and an accessible label/tooltip. It writes only the
93
+ 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
94
+ include the missing-generation disclosure and its distinct label. Close and Escape return focus to
95
+ the Image dropdown trigger; Escape does not close the underlying viewer on the same press.
54
96
 
55
97
  ## Data and lifecycle
56
98
 
57
99
  Read the source conversation through the running renderer's native authenticated
58
100
  API; discover the API export through Library metadata's actual module imports,
59
- without fixed build hashes. No credentials, signed download URLs or image bytes
60
- are copied or sent to another service.
101
+ 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
102
 
62
103
  Match both the originating message and exact image asset pointer, and require
63
- assistant/tool generation metadata. Prefer a recorded generator prompt. Otherwise use only the nearest user ancestor
64
- of that image. Provenance stays internal; the UI simply calls this Prompt / 提示词. Unrelated branches are never used.
104
+ assistant/tool generation metadata. The Prompt viewer prefers a recorded
105
+ generator prompt: the exact asset's `dalle.prompt`, the exact result message's
106
+ `async_task_prompt`, or structured `prompt`/single `prompts` arguments from the
107
+ nearest image-generation tool call in its ancestor chain. Tool text is parsed as
108
+ JSON and never executed. Multi-image tool results do not infer per-image argument
109
+ ownership; unrelated tools, older generation calls and other branches are excluded. A traceable original user request is a different fact and is
110
+ shown only after an explicit missing-generation disclosure and “Original request” label. Unrelated branches are never used.
65
111
 
66
112
  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
113
+ old UI. 等缩放工具栏和视口就绪后再开始解析,避免先到的提示词结果把稍后出现的原生锚点短路;同图的缩放节点或承载改变时重新挂载。 Late results cannot mount against a different image. Disabling the feature
68
114
  removes the observer, keyboard handler, button and panel. Prompt data is retained
69
115
  only for the current viewer; there is no persistent conversation cache.
70
116
 
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. Whitespace prompts follow the same fallback policy. A valid generated image without prompt text can still be imported by the historical utility, while the viewer shows no prompt control.
117
+ 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
118
+ preserves that distinction in fallback disclosure and labels. The historical import utility can retain a
119
+ 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.
120
+
121
+ ### 本地提示词
122
+
123
+ Image Gen 按已索引的精确图片 ID 提取对应 thread 的生成记录。已完成 `image_gen.generation` 的 revisedPrompt 优先于可静态解析的工具请求参数。没有真实生成提示词则明确显示不可用。云端图片保留 generation → 已标明来源的 Original request 回退;已移除没有实测命中的云端 gen_id → 本地文件猜测补充链路。
124
+
125
+ 2026-10-02 菜单核对:网格 More 明确使用原生 Library 的 mediaOverlay 分支,操作通过 leftIcon 传入真实原生图标。图库菜单当前按 Chat about this、Download、View prompts 排列。Chat about this 组合原生模式切换、新 Chat 草稿导航与图片面板链路,不发送消息;导航与面板已隔离验证,图片随消息提交的实际链路未进行发送验收。生成图片通用菜单中的重复查看、新标签、复制和 Finder 不再混入图库菜单。依赖云端 Library 的收藏、分享、重命名、文件夹及删除能力尚未接入本地来源。
126
+
127
+
128
+ ## Image Gen 多选与隐藏
129
+
130
+ Codex 26.930.21537(2026-10-02):卡片通过原生 Card 的 selection、selected、selectionActive 与 onSelect 契约接入;圆形 Checkbox 使用原生 overlay 分支。选中后显示原生边框,支持 Checkbox、选择状态下点击卡片、Shift 区间选择、动作条 Clear selection 与 Escape。查询变化、切换来源、离开页面释放选中状态;分页加载保留已选条目,不污染原生 Your creations/Uploads 的 selection atoms。
131
+
132
+ 缩略卡片沿用已核对的 Library 比例边界,宽高比限制在 1/2 至 20/13。卡片与图片比例分开处理:比例范围内按原比例展示;超出范围使用 object-contain 完整缩放并居中,极宽图上下留白,极窄图左右留白,不裁切图片内容。此显示策略按用户截图与验收要求实现,不将 Library 通用 object-cover 分支视为完整的 Images 展示规范。原图详情不改变比例。不能直接用原始极宽/极窄比例承载 Card,避免 More 与右下 Checkbox 重叠或单张卡片过高。
133
+
134
+ 动作条复用原生 Button、Dropdown、Menu、图标与 Library 布局分支,固定在底部,dark 材质,包含数量、Start chat、Download、Delete、More→Move 和 Clear selection。Start chat 调用原生 Chat 附件上传和草稿导航,不发送消息;Download 使用原生保存流程逐项处理;Move 在明确点击后登记所选图片并使用原生文件夹选择器,选择本身不上传。权限/忙状态属于当前操作,不伪造云端图片身份。
135
+
136
+ Delete 在单张 More 与批量动作条共用一条隐藏流程,复用原生 Dialog、LibraryItemConfirmationContent 和实色警戒确认按钮。确认标题明确限定 Image Gen,正文明确“仅隐藏,本地原图和云端副本保留”。取消不改变任何条目;确认成功后立即移除当前条目、更新选中数、列表总数和分页偏移,并显示原生 toast。隐藏 ID 存于当前 Runtime profile 的 `hidden-image-ids.json`,本地源文件、原始生成提示词及已登记云端身份不修改;刷新和重启后继续隐藏。
@@ -1,27 +1,36 @@
1
- # Images · Library 提示词
1
+ # Images · Image Gen 与图片提示词
2
2
 
3
- Images 在 Codex 原生 **Library → 图片详情**中补充提示词查看能力。
3
+ Images 在 Codex 原生 **Space → Images** 中增加 **Image Gen**,集中浏览本机 Codex 或本地 Work 生成的图片。
4
4
 
5
- - 有可追溯的提示词时,在缩放胶囊左侧显示 **Prompt / 提示词**。
6
- - 点击后,使用原生 Version history 相同布局和 design token 的右侧面板展示内容。
7
- - 面板只展示提示词正文,支持一键复制;优先使用生成提示词,否则追溯该图片的用户输入。
8
- - 未找到提示词、普通上传图片或来源对话不可用时,不显示按钮。
9
- - 切换图片或关闭详情会移除旧内容;Escape 先关闭提示词面板。
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
- 新版不再在 Sites 下方注入独立 Images 入口,也不启动第二套图库服务。
12
- 图片浏览、编辑与下载使用 Codex 原生能力。读取仅在本机原生 Renderer 内完成,
13
- 沿用当前登录态,不复制图片或持久化来源对话。
13
+ 索引在当前 Runtime profile 中维护,读取本机生成目录与会话日志,不迁移或删除源文件。原生组件和 API 契约不匹配时保留原生页面;Codex 更新后的可用性需要重新核验。
14
14
 
15
15
  ## 开发
16
16
 
17
- 统一宿主通过 `codex-feature.json` 注册 `src/library-prompt-page.mjs` 页面脚本。
18
- 原生 DOM 或 API 契约缺失时不注入。视觉契约见 [DESIGN.md](DESIGN.md)。
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/`:原生组件、文件读取、详情、编辑器及云端协议契约。
22
+ - `src/frontend/local-image-source.mjs`:来源选择、搜索、分页和生命周期。
23
+ - Runtime Host:鉴权、功能声明校验与转发。
24
+
25
+ 视觉与能力边界见 [DESIGN.md](DESIGN.md)。
19
26
 
20
27
  ```bash
21
- node --test features/images/test/library-prompt-page.test.mjs
28
+ node --test features/images/test/*.test.mjs
22
29
  ```
23
30
 
24
- 旧 `src/image-library.mjs`、索引、独立 `web/` 与服务脚本暂保留为历史工具,
25
- 不属于当前插件入口;已有本地图片和缓存不会被迁移或删除。
31
+ 独立 `web/` 与旧服务入口保留为已有历史工具,不由当前 manifest 启动;ImageLibrary 与日志索引已由当前本地来源复用。历史图库交接见 [research/images](../../docs/research/images/2026-08-07-gallery-handoff.md)。
32
+
33
+ 2026-10-02 菜单核对:网格 More 明确使用原生 Library 的 mediaOverlay 分支,操作通过 leftIcon 传入真实原生图标。图库菜单按 Chat about this、Add to Favorites、Download、Add to folder、Delete、View prompts 排列。Chat about this 组合原生模式切换、新 Chat 草稿导航与图片面板链路,不发送消息;导航与面板已隔离验证,图片随消息提交的实际链路未进行发送验收。生成图片通用菜单中的重复查看、新标签、复制和 Finder 不再混入图库菜单。Add to Favorites/Remove from Favorites 和 Add to folder 已接入原生云端 Library。首次操作上传所选图片,之后复用账号与内容指纹对应的云端身份;收藏与文件夹归属保存在服务器。取消文件夹选择会显示原生 toast,已上传的图片仍留在 Library。上传结果不确定时不盲目重试;分享、重命名及删除尚未接入。
34
+
26
35
 
27
- 历史图库的交接与视觉取证已归档到 [research/images](../../docs/research/images/2026-08-07-gallery-handoff.md),不作为当前功能规范。历史服务仍可通过本插件的 `npm run service` 手动启动;它不由当前 manifest 加载。保留这些工具不意味着新增或恢复独立图库入口。
36
+ 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": "View image prompts inside the native Library image viewer.",
11
- "zh-CN": "在原生 Library 图片详情中查看并复制提示词。"
10
+ "en": "Browse local Image Gen creations and view prompts inside native Images.",
11
+ "zh-CN": "在原生 Images 中浏览本地 Image Gen 图片,查看并复制提示词。"
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 prompt = part.metadata?.dalle?.prompt;
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
- if (ancestor.message?.author?.role === "user") {
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
- return {read:libraryImage};
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
  }