docs(tasks): refine custom gateway product suite plan

This commit is contained in:
chengma
2026-07-20 17:23:23 +08:00
parent cd2dfe97ef
commit 7734b20434
5 changed files with 266 additions and 114 deletions
+39 -48
View File
@@ -1,68 +1,61 @@
---
id: T-678
title: 自定义网关锁定 OpenAI 图片接口规范并支持多图输入
title: 自定义网关图片模型锁定 OpenAI 图片编辑接口并支持多图输入
phase: 7
deps: [T-677]
status: TODO
created: 2026-07-20
---
# T-678 自定义网关锁定 OpenAI 图片接口规范并支持多图输入
# T-678 自定义网关图片模型锁定 OpenAI 图片编辑接口并支持多图输入
## 问题 / 背景
T-677 把⑤的自定义网关(`ai.backend=direct`)重新放出来后,暴露了 direct 生图路径的两个结构问题。
T-677 恢复了⑤的自定义网关(`ai.backend=direct`)。当前 `api_type` 同时用于文字与图片模型:文字模型需要 `chat/completions`,而图片模型的 `chat` / `auto` 分支则依赖非标准聚合网关把图片塞进对话响应。将 `appconfig.API_TYPES` 全局收敛成单一值会直接破坏自定义网关生文,因此必须按模型类别收敛。
**当前的 `chat` 接口类型并不是 OpenAI 规范。** 标准 OpenAI 的 `chat/completions` 不返回生成的图片。本项目的 chat 生图之所以能工作,是因为 `_find_image_ref()`(`app/ai.py:2667`)是一个非常宽容的递归搜刮器,会在 `choices`、`message`、`content`、`data`、`images`、`files` 等结构里到处寻找 `b64_json` / `image_base64` / `url`。这套逻辑是为把生成图塞进 chat 响应的**非标准中转聚合网关**准备的。`api_type=auto` 同样不做接口探测,`chat` 与 `auto` 实际走同一条对话请求,T-677 只能在 UI 上写一段"自动不会探测接口"的说明来遮盖这个事实。
当前 direct 图片编辑也只能提交单张图:`_image_edit_body()` 的 `files` 是单键字典,`_multipart_body()` 不能表达重复字段。商品套图需要按顺序提交“主体图 + 参考图”,其前置能力应在本任务完成。
**direct 只能提交单张输入图。** `_image_chat_payload()`(`app/ai.py:2557`)的 `content` 数组只放一个 `image_url`;`_image_edit_body()`(`app/ai.py:2569`)的 `files` 是字典且只有一个 `"image"` 键,`_multipart_body()` 按字典遍历,结构上无法发送重复字段名。因此 T-679 要让商品套图在自定义网关下使用「主图 + 参考图」时,无法复用现有请求构造。
**本任务存在时间窗口,晚做代价显著上升。** T-529 之后⑤的自定义模型 UI 被 `_on_backend_changed()` 无条件 `setVisible(False)` 硬藏,普通用户没有入口配置 `chat` 类型模型;该入口在 T-677(2026-07-20)才重新放出。因此当前装机量中几乎不存在可用的 `chat`/`auto` 直连配置,收敛接口规范的迁移成本接近于零。等 T-677 上线一段时间、用户配出一批 chat 模型后再收敛,就会变成真正的破坏性变更。
本任务锁定的外部契约是 OpenAI Image API 的图片编辑接口:`POST /v1/images/edits`、multipart 重复 `image[]` 文件字段、响应从 `data[0].b64_json` 或 `data[0].url` 读取。第一张为主体图是本项目的业务约定;OpenAI 文档只明确多图及遮罩作用于首图,不能据此声称模型一定保持主体一致性。
## 方案
### 1. 接口规范收敛
### 1. 按类别收敛模型接口
- 自定义网关只支持 OpenAI 图片编辑接口(`/v1/images/edits` 形式的 multipart 请求)。`appconfig.API_TYPES`(`app/appconfig.py:41`)由 `{"chat", "images_edits", "auto"}` 收敛为单一取值。
- 实现前必须按当前 OpenAI 官方文档核实多图输入字段名与模型支持范围(预期为 `image[]` 配合 `gpt-image-1`),并把核实结果与文档版本写入执行记录。**文档与实现不一致时不得猜字段**,按 T-596 既有约定停下并记录缺口。
- 删除 `_image_chat_payload()`(`app/ai.py:2557`)及其在 `gen_cover` 中的分支(`app/ai.py:348-386`)。
- `_find_image_ref()`(`app/ai.py:2667`)收敛为按标准响应结构取图(`data[].b64_json` / `data[].url`),不再跨 `choices`/`message`/`content` 递归搜刮。保留 data URL 与 base64 的解码分支和 `_download_image()` 回退。
- 保留全局 `API_TYPES = {"chat", "images_edits", "auto"}`,避免影响 `category=text` 的既有生文请求。
- 新增“图片编辑能力”校验:仅 `category=image` 且 `api_type=images_edits` 的模型可用于②生成封面与后续⑥商品套图;文字模型保持既有 `chat` / `auto` 行为。
- 图片模型的 `chat` / `auto` 存量配置原样保存、可查看,但在⑤显示“当前图片模型不支持 OpenAI 图片编辑接口”的中文状态,并在②、⑥预检中阻止提交。不得静默迁移、禁用或删除,避免触发“至少一个 image 模型启用”的既有校验冲突。
- ⑤根据模型类别展示接口类型:文字模型保留现有可选项;图片模型只允许选择并显示“OpenAI 图片编辑接口”。不再以全局文案宣称 `chat/completions` 永远不能生成图片,而是说明其不符合本项目锁定的图片编辑响应契约。
### 2. 多图输入能力
### 2. 图片编辑请求与响应契约
- `_image_edit_body()` 接受有序图片路径列表而不是单个路径:第一张为主体图,其余为参考图,按核实后的字段名发送。
- `_multipart_body()`(`app/ai.py:2586`)的 `files` 由字典改为有序序列,支持同名重复字段。
- 保留单图调用形式:②生成封面继续只发一张图,请求形式收敛但**行为不变**,不得因本任务改变②的生图结果口径或计费口径。
- 本任务只提供多图能力,**不改商品套图**;套图接入由 T-679 完成。
- 删除 direct 图片生成的 `_image_chat_payload()` 分支;`category=image` 的 direct 请求一律走 `/v1/images/edits` multipart,文字请求不在本任务范围内。
- `_image_edit_body()` 接受有序本地路径序列;multipart 用重复的 `image[]` 字段提交,首项为主体图,其余为参考图。保留单图入口,②仍只提交一张图。
- 请求体固定一张输出(`n=1`);套图数量仍由“一个 job 一次请求”表达,不能让一次响应内的多图破坏单 job 历史、重试与计费语义。
- `_find_image_ref()` 收敛为只读取标准 `data` 数组中的 `b64_json` / `url`。保留合法 data URL、base64 和 HTTP(S) URL 下载分支;非标准响应必须给出中文错误,不能递归猜测 `choices`、`message` 等字段。
- 继续使用现有 URL 安全校验;不得因兼容自定义网关放开 `file:`、本地路径或其他非 HTTP(S) 返回地址。
### 3. 存量配置迁移
### 3. 配置、预检与回归
- `ai_models.json` 中 `api_type` 为 `chat` 或 `auto` 的存量模型不得静默改写为新取值——请求形式不同,静默迁移会让用户在不知情的情况下把请求打到不支持的端点。
- 载入时把这类模型标记为不可用,在⑤模型列表中以中文说明其原因(如「接口类型已不再支持,请改为 OpenAI 图片编辑接口并确认服务商支持」),并在②的自定义网关预检中作为"去⑤补齐"的一种原因。
- 沿用 v3.1 约定:允许先保存不完整或不可用配置,不在切换来源或保存时弹阻断式校验。
### 4. ⑤ UI 与文案
- 移除或降级 T-677 新增的「接口类型」下拉:只剩一种取值时不应继续以可选控件呈现。同步删除 T-677 为 `auto` 写的"不会探测接口"帮助文案。
- ⑤自定义面板补充说明:自定义网关只支持 OpenAI 图片编辑接口,只做 chat 生图的中转聚合服务不可用。文案不得暴露内部实现细节或密钥。
- 保留既有 `objectName`、配置键、日志字段和 `app/ai.py` 中文错误消息中的 `cmhub` 字样(沿用 T-677 第 5 节边界)。
- 使用图片模型前,预检同时检查启用状态、`images_edits` 接口类型、URL、模型名、密钥和超时;错误只给中文可操作说明,不暴露密钥、完整上游 URL 或原始响应体。
- ②已有的重试、并发、日志、“自定义网关不计点数”口径不变;本任务不改变默认网关及其 cmhub 生图路径。
- 在执行记录中记录 OpenAI 官方文档核实日期、`image[]` 字段、标准响应字段和支持的尺寸范围;外部兼容服务若不满足该契约,第一版明确不支持,不做自动探测或私有适配。
## 验收要点
- [ ] `API_TYPES` 只剩 OpenAI 图片编辑接口一种取值;`chat` 与 `auto` 的请求构造代码已删除,不留死分支。
- [ ] `_find_image_ref()` 只按标准响应结构取图,不再跨 `choices`/`message` 搜刮;非标准响应给出可读中文错误而不是静默取错字段。
- [ ] `_image_edit_body()` 与 `_multipart_body()` 支持有序多图,第一张为主体图;单图调用路径行为不变。
- [ ] ②自定义网关生成封面的结果、重试、并发、日志和"不计点数"文案不回归。
- [ ] 存量 `chat`/`auto` 模型不被静默改写,在⑤显示不可用原因,在②预检中作为补齐提示出现。
- [ ] ⑤不再显示多取值的接口类型控件与 `auto` 帮助文案;新增的规范限制说明为中文且不暴露实现细节。
- [ ] 默认网关(cmhub)的生文、生图、图片理解、异步 submit/poll、幂等键、计费与③更新蝦皮全部不受影响。
- [ ] 自定义网关文字模型仍可走原有 `chat` / `auto` 生文路径;全局 `API_TYPES` 未被收窄。
- [ ] `category=image` 仅在 `api_type=images_edits` 时可提交图片生成;存量 `chat` / `auto` 图片模型保留且不被静默改写。
- [ ] multipart 使用有序重复 `image[]` 字段;单图与多图均有字段、顺序与首图主体的单元测试。
- [ ] 图片响应仅接受 `data[].b64_json` / `data[].url`;非标准响应有可读中文错误,非法 URL 不会下载。
- [ ] ②单图生成的结果、重试、并发、日志和不计点数口径不回归。
- [ ] ⑤的图片模型限制和②预检均为中文,不泄露密钥、完整上游 URL 或原始响应体。
- [ ] 默认网关的生文、生图、图片理解、异步 submit/poll、幂等键、计费与③更新蝦皮不受影响。
## 测试与文档
- `tests/test_ai.py`:现有 4 处 `api_type` 断言(:63、:75、:307、:348)改为新规范;新增多图请求体断言(字段名、顺序、主体图在首位)与非标准响应的错误路径。
- `tests/test_appconfig.py`:现有 10 处 `api_type` 断言(:542 起)改为新取值;补存量 `chat`/`auto` 模型载入后标记不可用且不被改写的用例。
- `tests/test_gui.py`:⑤接口类型控件移除、不可用模型的中文提示、②预检把不可用模型作为补齐原因。
- 更新 `docs/cmhub-integration-design.md` 修订说明(T-677 有限放开 direct,本任务进一步把 direct 收敛到 OpenAI 规范)、`docs/routes.md`、`docs/api.md`。
- `tests/test_ai.py`:文字 `chat` / `auto` 回归、图片 `images_edits` 单图与多图 `image[]` 请求、`n=1`、标准响应和非法/非标准响应错误路径。
- `tests/test_appconfig.py`:按类别的可用性校验、存量 `chat` / `auto` 图片模型不被改写、文字模型保存与加载不回归。
- `tests/test_gui.py`:⑤按类别显示接口类型、②预检拦截不可用图片模型且文案中文。
- 更新 `docs/cmhub-integration-design.md`、`docs/routes.md`、`docs/api.md`,标明图片编辑的固定契约和 T-679 的依赖关系。
## 验证
@@ -76,20 +69,18 @@ git diff --check
## 边界(不改什么)
- 不改商品套图(`app/image_studio_generation.py`)与图片理解(`analyze_product_images()`),套图接入见 T-679。
- 不新增 vision 类别,`appconfig.CATEGORIES` 保持 `{"text", "image"}`;⑥「AI 帮写」在自定义网关下继续不可用。
- 不改 cmhub 的请求协议、重试与超时策略、异步 submit/poll 状态机、幂等键、预扣退点账本。
- 不新增 provider 适配、接口自动探测或任意上游 URL 放行。
- 不改商品套图;其接入拆分为 T-679a、T-679b、T-679c,由 T-679 集成验收。
- 不新增 provider 私有适配、接口自动探测、任意 URL 放行或图片理解的自定义网关路径。
- 不改 SQLite schema、Chrome/CDP、Excel 回写、蝦皮更新流程。
- 不解除 T-596 的 BYOK 门禁;本任务仍属 direct 过渡形态范围内。
- 不改 cmhub 的请求协议、重试与超时策略、异步 submit/poll 状态机、幂等键、预扣退点账本。
- 不解除 T-596 的 BYOK 门禁;本任务仍属客户端直连的过渡形态。
## 关联
- T-677:⑤生成网关来源切换(本任务收敛其放出的 direct 接口形态)。
- T-679:商品套图接入自定义网关(依赖本任务的多图能力)。
- T-529:默认 cmhub 网关并隐藏 AI 后端选择(其隐藏期造就了本任务的低成本迁移窗口)。
- T-596:BYOK 总设计与启动门禁(`status: BLOCKED`;正式形态仍要求经 cmhub 代理,本任务不解除该门禁)。
- T-677:⑤生成网关来源切换。
- T-679a:套图多来源任务状态机,依赖本任务的多图请求能力。
- T-596:BYOK 总设计与启动门禁。
## 执行记录
(做完在这里写:改了什么文件、跑了什么验证命令及结果、遇到的阻塞、关键决策;务必记录 OpenAI 多图字段名的核实来源与文档日期。)
(做完在这里写:改了什么文件、跑了什么验证命令及结果、官方接口契约核实来源与日期、遇到的阻塞和关键决策。)
+24 -66
View File
@@ -1,80 +1,42 @@
---
id: T-679
title: 商品套图接入自定义网关(主图 + 参考图)
title: 商品套图接入自定义网关(集成验收)
phase: 7
deps: [T-677, T-678]
deps: [T-679a, T-679b, T-679c]
status: TODO
created: 2026-07-20
---
# T-679 商品套图接入自定义网关(主图 + 参考图)
# T-679 商品套图接入自定义网关(集成验收)
## 问题 / 背景
T-677 在自定义网关(`ai.backend=direct`)下停用了⑥商品套图的新建、重试与「AI 帮写」,只保留已提交默认网关任务的取回入口。现在需要让⑥的生成能力也能走自定义网关,使 cmhub 上游不稳或用户自备算力时,套图不再是唯一断链的模块。
商品套图从只支持默认网关扩展为支持自定义网关,涉及持久化 job 来源、同步请求、停止与失败语义、生成确认、历史与已提交默认网关任务恢复。原任务同时修改这些层次,风险和验收面过大,现拆为 T-679a、T-679b、T-679c;本任务只负责三项完成后的跨层集成验收。
相关代码事实:
## 集成要求
- `app/image_studio_generation.py` 的来源耦合只有两处:`_runtime()`(:35)写死 `ai._cmhub_runtime(config, "image", ...)`,以及 `_submit_or_resume_job()`(:401)的 cmhub 提交与 `_poll_job()`(:325 调用)轮询。建 job、并发槽、停止、输出路径、存资产、重试删除历史、进度事件**已经与来源无关**。
- **direct 是同步路径**:`_call_with_retry()` 返回后 `_extract_image_bytes()` 取字节、`_save_jpeg()` 落盘(`app/ai.py:387-390`)。因此自定义网关不需要异步状态机,而是**跳过** submit/poll 两步,不是补齐它们。
- 已提交任务的回收守卫(`app/image_studio_generation.py:200`)判断条件是 `getattr(job, "task_id", None) and not _is_default_gateway_job(job)`,**只拦有 `task_id` 的 job**。direct job 天然没有 `task_id`,自动不被拦截,该守卫无需修改即可继续保护已扣点的 cmhub 任务。
- `create_generation_jobs()`(:115)目前把 `generation_source` 与 `provider` 硬编码为 `"cmhub"`,接入多来源后必须写入真实来源,否则回收守卫的判断依据失效。
**能力边界必须如实表达**:cmhub 的多图契约中,首图为主体且由服务端强制追加"保留主体"规则,客户端无法覆盖(T-658 已确认)。OpenAI 图片编辑接口的多图输入没有等价契约,主体保留只能写入提示词由模型自行遵守。对白底图这类"必须仍是同一件商品"的场景,两者不等价,不得因为支持多图就宣称效果相同。
## 方案
### 1. 来源分派(两处接缝)
- `_runtime()`(:35)改为按来源返回带稳定来源标记的运行时对象:默认网关沿用 `ai._cmhub_runtime()`;自定义网关解析本轮实际使用的图像模型,复用 T-677 的 `ai.freeze_runtime_config()` 快照与 `ai._role_model("image", ...)`。运行时对象仅存在于内存,密钥不得写入 SQLite、运行日志、异常正文或 UI。
- `_submit_or_resume_job()`(:401)按来源分支:默认网关保持现有异步提交;自定义网关走 T-678 提供的多图同步请求,直接返回图片字节。
- `_run_one_job()`(:284)在自定义网关下跳过 `_poll_job()`(:325),直接进入保存分支。`request_result` 的契约需要能同时表达"远程 URL 待下载"(cmhub)与"字节已在手"(direct),两种形态都要能走到 :329 的保存与 :344 的 `add_asset()`。
- `create_generation_jobs()`(:115)写入真实 `generation_source` / `provider`,不再硬编码 `"cmhub"`。
- `_ensure_new_submission_allowed()`(:47)放开自定义网关;三层守卫(`create_generation_jobs()`、`ProductSuiteGenerateWorker`、`_submit_or_resume_job()`)继续使用同一守卫函数,改为按来源判断可用性而不是逐层各写一遍。
### 2. 主图与参考图
- 未勾选「每张上传图分别作为主图生成」时,自定义网关按 T-678 的多图请求提交:第一张为主体图,其余为参考图,数量上限按所选模型与服务商实际限制处理并在超限时给出中文提示。
- 勾选逐图主图时维持现有每图单独提交的语义,不引入跨 SKU 混图。
- 主体保留只能通过提示词表达:把"第一张为商品主体、必须保持商品本体不变,其余仅供风格与场景参考"拼进发送给自定义网关的提示词。**不得**声称与默认网关等价。
- ⑥在自定义网关下显示中文说明:参考图效果取决于所选模型,主体一致性弱于默认网关。
### 3. 停止、超时与并发
- **停止会变迟钝**:direct 是一次同步 HTTP 请求,请求发出后没有中断点,`should_stop` 只能在两张图之间生效。UI 必须如实说明"正在停止,需等待当前图片返回",不得让用户误判为卡死;不得用 `processEvents()` 或强杀线程伪装成即时取消。
- 自定义网关不使用 `CMHUB_IMAGE_SUBMIT/POLL/READ_TIMEOUT`(:446、:513、:485)这套为异步设计的常量,改用模型自身配置的超时秒数。
- 并发沿用现有 `MAX_CMHUB_IMAGE_STUDIO_WORKERS = 5` 与全局信号量(:15-16),本任务不调整取值,避免把针对 cmhub 的保护与用户自备服务的限流混为一谈。
- 套图传入的是 `aspect_ratio`(如 `1:1`),OpenAI 图片编辑接口吃的是尺寸文本,需要一层明确换算;换算规则与不支持的比例回退口径要有测试。
### 4. 费用口径与不变部分
- ⑥在自定义网关下不显示"预计消耗 X 点",改用 T-677 在②已定的口径「自定义网关(不计点数,费用由服务商收取)」,不得显示 `0 点`或沿用旧余额。默认网关下点数预估与余额展示不变。
- 「AI 帮写」在自定义网关下继续不可用:`appconfig.CATEGORIES` 无 vision 类别,自定义模型没有图片理解配置位。入口保持置灰并说明仅默认网关支持。
- 已提交默认网关任务的「继续查询已提交图片」入口与回收守卫(:200)保持不变;切到自定义网关不得影响已扣点任务的轮询、下载与保存。
- 默认网关任务继续使用 `generation_source="cmhub"`、`provider="cmhub"`、异步 task ID、轮询、幂等键和点数账本。
- 自定义网关任务使用 `generation_source="direct"`、`provider="openai_images_edits"`、`task_id=NULL`、同步单图请求;不得进入 cmhub 轮询或恢复流程。
- 同一项目可以同时显示历史 cmhub 与 direct 任务;界面仅显示“默认网关 / 自定义网关”,不显示密钥、完整 URL 或模型名。
- T-678 的图片编辑契约、T-679a 的任务语义、T-679b 的交互预检和 T-679c 的恢复/历史语义必须一致。
## 验收要点
- [ ] 自定义网关下⑥可新建并完成套图生成;job 的 `generation_source` / `provider` 记录真实来源,`task_id` 为空。
- [ ] 未勾选逐图主图时按主图 + 参考图提交,首图为主体;勾选时维持每图单独提交。
- [ ] 切换来源后,此前已提交的默认网关任务仍可继续查询、下载并保存,不被拒绝、不新建任务、不二次扣点;回收守卫逻辑未被削弱。
- [ ] 运行中保存⑤来源或修改模型,不影响已启动任务的来源快照;密钥只存在于 worker 内存,未出现在 DB、日志、异常正文或 UI。
- [ ] 自定义网关下停止在当前图片返回后生效,UI 有"正在停止"的中文说明,不出现伪即时取消或卡死观感。
- [ ] 自定义网关使用模型自身超时;`aspect_ratio` 到尺寸的换算与不支持比例的回退有明确行为。
- [ ] ⑥在自定义网关下显示不计点数口径与主体一致性弱化说明;默认网关下点数预估不回归。
- [ ] 「AI 帮写」在自定义网关下仍不可用并给出中文原因。
- [ ] 默认网关的套图生成、重试、删除撤销、历史、导出与③更新蝦皮全部不回归。
- [ ] 两种来源均可完成各自允许的新建任务;切换⑤设置不会改变已启动 worker 的来源快照。
- [ ] 已提交的默认网关任务在切换到自定义网关后仍可继续查询、下载并保存,绝不二次提交或扣点。
- [ ] 自定义网关不会访问 cmhub 模型、余额、价格、提交或轮询接口;默认网关行为不回归。
- [ ] 直连失败、停止、程序中断、手动重试和历史展示符合 T-679a/T-679c 的语义,用户不会误以为未完成任务可自动恢复。
- [ ] ⑥生成确认、进度、结果卡片和历史页面的来源、比例、费用与主体一致性提示均为中文且一致。
- [ ] ③更新蝦皮、②生成封面、⑥AI 帮写以及默认网关套图全量回归通过。
## 测试与文档
- `tests/test_image_studio_generation.py`:来源分派、direct 同步路径跳过轮询、多图请求顺序与主体图位置、`generation_source`/`provider` 写入、已提交 cmhub 任务在来源切换后仍可恢复、无 `task_id` 与来源不匹配的 job 被拒绝、比例换算、停止在图片边界生效。
- `tests/test_gui.py`:⑥在两种来源下的入口可用性、点数与主体一致性文案、停止提示、AI 帮写仍不可用、运行中保存设置不改变本轮来源。
- `tests/test_ai.py`:套图复用的多图请求构造与超时取值。
- 更新 `docs/routes.md`、`docs/04-architecture.md`、`docs/api.md`;如⑥文案或流程变化,同步 `docs/ui/` 对应效果图并在 `docs/ui/README.md` 登记。
- 跑 T-679a/T-679b/T-679c 的全部验证,再补两来源混合项目、运行中保存⑤设置、切换来源后继续查询旧 cmhub 任务的集成测试。
- 更新 `docs/04-architecture.md`、`docs/routes.md`、`docs/api.md` 与⑥效果图说明。
## 验证
```bash
py -3.10 -m unittest tests.test_image_studio_generation tests.test_gui tests.test_ai
py -3.10 -m unittest discover -s tests
py -3.10 -m ruff check app tests main.py
py -3.10 -m compileall app main.py
@@ -83,20 +45,16 @@ git diff --check
## 边界(不改什么)
- 不新增 vision 类别或图片理解的自定义网关路径;「AI 帮写」仍为默认网关独占。
- 不改 cmhub 的请求协议、异步 submit/poll 状态机、幂等键、预扣退点账本与已提交任务回收守卫。
- 不调整套图并发上限与全局信号量取值。
- 不新增 provider 适配、接口自动探测或任意上游 URL 放行(接口规范由 T-678 锁定)。
- 不改 SQLite schema、Chrome/CDP、Excel 回写、蝦皮更新流程。
- 不解除 T-596 的 BYOK 门禁;本任务仍属客户端直连的过渡形态。
- 本任务不新增功能;只整合并验证 T-679a/T-679b/T-679c 已定义的能力。
- 不新增图片理解的自定义网关路径,不改 cmhub 协议、Chrome/CDP、Excel 或蝦皮更新流程。
## 关联
- T-678:自定义网关锁定 OpenAI 规范并支持多图输入(本任务的前置能力)。
- T-677:⑤生成网关来源切换(本任务**有意放宽**其"不给商品套图新增自定义网关路径"的边界;放宽理由为套图是当前唯一在来源切换后完全断链的模块)。
- T-658:商品套图多图参考提交规则(cmhub 首图主体、服务端强制保留的契约来源)。
- T-596:BYOK 总设计与启动门禁(`status: BLOCKED`,正式形态要求经 cmhub 代理并收口 direct)。
- T-678:自定义网关图片编辑协议。
- T-679a:多来源 job 状态机。
- T-679b:商品套图 GUI 与生成确认。
- T-679c:中断恢复、历史与重试。
## 执行记录
(做完在这里写:改了什么文件、跑了什么验证命令及结果、遇到的阻塞、关键决策。)
(做完在这里写:集成测试覆盖、验证命令和结果、发现的跨层问题及处理决定。)
+82
View File
@@ -0,0 +1,82 @@
---
id: T-679a
title: 商品套图自定义网关多来源任务状态机
phase: 7
deps: [T-678]
status: TODO
created: 2026-07-20
---
# T-679a 商品套图自定义网关多来源任务状态机
## 问题 / 背景
`app/image_studio_generation.py` 当前把运行时、提交、轮询和 job 来源硬编码为 cmhub。自定义网关是无 task ID 的同步图片编辑请求,不能复用 cmhub 的异步恢复模型;若只在现有分支上打补丁,停止、超时或程序中断时会出现已扣费图片丢失、误轮询或重复生成。
## 方案
### 1. 来源与运行快照
- 按 worker 启动时冻结的配置解析来源,不能读取运行中被⑤保存的新配置。
- 固定持久化语义:默认网关为 `generation_source="cmhub"`、`provider="cmhub"`;自定义网关为 `generation_source="direct"`、`provider="openai_images_edits"`。不得把用户填写的 URL、模型名或密钥写入来源字段、SQLite、日志或 UI。
- 自定义网关 job 的 `task_id` 必须为空;默认网关 job 的 task ID 只能由 cmhub 提交返回后写入。
### 2. 同步直连执行
- 复用 T-678 的有序多图图片编辑请求;每个 job 固定 `n=1`,直接取得图片字节后保存资产,不调用 `_poll_job()`。
- 默认网关路径的提交、轮询、下载和幂等键保持不变。分派必须按 job 的持久化来源而非当前设置判断,避免混合项目误走错误通道。
- 直连结果资产可不填 `remote_url`;不得为了填该字段额外拼接、记录或暴露上游地址。
- 将 `MAX_CMHUB_IMAGE_STUDIO_WORKERS` 改名为来源中立的并发常量,数值仍为 5;默认和直连共用全局上限,防止一次生成压垮本机或上游。
### 3. 停止、失败与不确定计费
- 停止只取消尚未发起的 job。已发出的同步请求不可强杀;如果已收到图片字节,必须先保存资产并标成功,停止只阻止后续 job。
- 直连请求提交后发生读超时、连接中断或进程退出时,客户端无法确认服务商是否已受理或计费:不自动重试,不自动恢复,不把该 job 放入“继续查询”。标失败并提供后续手动“重新生成”入口。
- 程序启动或进入项目时,遗留 `running` 的 direct job 统一标为失败,中文原因说明“程序中断,无法确认生成结果,请手动重新生成”;不得自动补发。
- 直连超时取冻结模型的 `connect_timeout_seconds` 和 `timeout_seconds`;若后者未设置,使用项目已有的受限默认值。不得使用 cmhub submit/poll/read timeout 常量。
### 4. 比例与参考图
- 套图比例固定映射:`1:1 -> 1024x1024`;`3:4`、`9:16 -> 1024x1536`;`4:3`、`16:9 -> 1536x1024`。后四种为近似映射,返回资产仍保存用户选择的原始比例。
- 任务层向调用方返回“是否近似比例”的结构化结果,T-679b 负责在确认框中告知用户;不得静默伪造精确比例。
- 首图主体与其余参考图的规则必须使用已冻结、已持久化的 job prompt;不能在 HTTP 调用层临时拼接,避免确认预览、历史 prompt 与实际请求不一致。
## 验收要点
- [ ] 可创建并执行 direct job,字段值精确为 `direct` / `openai_images_edits` / 空 `task_id`。
- [ ] direct 请求不调用 cmhub 提交、轮询、余额、价格或下载路径;cmhub job 行为不变。
- [ ] 已返回字节的 direct job 即使用户点击停止也会保存成功;未开始 job 才会取消。
- [ ] direct 不发生自动重试;遗留 `running` direct job 不会自动重发或进入继续查询。
- [ ] 比例映射、近似标记、`n=1`、多图顺序和 prompt 一致性有纯逻辑测试。
- [ ] 直连模型 URL、密钥与原始响应不进入 SQLite、事件日志或异常面向用户的文案。
## 测试与文档
- `tests/test_image_studio_generation.py`:来源分派、状态转移、停止边界、超时、遗留运行任务、比例映射、混合 job 防串路与无自动重试。
- `tests/test_image_studio.py`:来源字段和中断恢复的数据访问。
- 更新 `docs/04-architecture.md`、`docs/api.md`:记录两种来源 job 生命周期。
## 验证
```bash
py -3.10 -m unittest tests.test_image_studio_generation tests.test_image_studio tests.test_ai
py -3.10 -m ruff check app tests main.py
py -3.10 -m compileall app main.py
git diff --check
```
## 边界(不改什么)
- 不修改⑥按钮可用性、确认框、价格展示、历史 UI;见 T-679b/T-679c。
- 不改 cmhub 协议、幂等键和点数账本;不新增自动重试、provider 私有适配或接口探测。
- 不改图片理解、Chrome/CDP、Excel 或蝦皮更新流程。
## 关联
- T-678:直连图片编辑请求和多图契约。
- T-679b:消费本任务返回的来源、比例与停止语义。
- T-679c:消费本任务的中断状态与重试语义。
## 执行记录
(做完在这里写:变更文件、状态机决策、验证命令及结果。)
+61
View File
@@ -0,0 +1,61 @@
---
id: T-679b
title: 商品套图自定义网关生成入口与确认交互
phase: 7
deps: [T-679a]
status: TODO
created: 2026-07-20
---
# T-679b 商品套图自定义网关生成入口与确认交互
## 问题 / 背景
T-677 按安全边界禁用了自定义网关下⑥的新建套图、重试和价格流程。T-679a 提供来源正确的同步执行能力后,GUI 必须只在配置可用时开放入口,并用清晰中文说明主体一致性、比例近似、费用和停止边界。
## 方案
- `ProductSuiteGenerateWorker` 在启动时冻结包含当前来源所需配置的快照;自定义网关允许创建 job,默认网关保留现有路径。新 job 的来源必须由冻结快照决定。
- 自定义网关预检复用 T-678 的图片模型能力检查;未配置、接口类型不符、密钥为空、模型禁用或超时无效时,阻止生成并引导到⑤补齐。
- 自定义网关不请求 cmhub 模型目录、余额、价格或点数预估;确认框显示“自定义网关不计点数,实际费用以服务商为准”。默认网关确认和计费展示不变。
- 生成确认框以实际 job specs 展示:生成数量、主图/参考图规则、逐图模式、所选比例;若 T-679a 返回近似比例,明确显示实际输出尺寸和“接近比例生成”。
- 自定义网关确认框说明“首图作为主要商品参考,参考图效果取决于模型,主体一致性可能弱于默认网关”;该文案不承诺模型必然保留主体。
- 点击停止后,显示“正在停止,等待当前图片返回后结束”;禁用新的提交入口但不冻结整个 GUI,完成后恢复正常状态。
- 结果日志、失败提示只显示中文业务信息和来源名“自定义网关”,不显示完整 URL、模型名、密钥或原始上游正文。
- 「AI 帮写」继续只支持默认网关;自定义网关下置灰并提示中文原因。
## 验收要点
- [ ] 自定义网关配置合格时可创建、确认并启动套图生成;不合格时在提交前阻止。
- [ ] 自定义网关流程不访问 cmhub 价格/余额/模型接口,且不显示“0 点”或过期余额。
- [ ] 确认框完整提示数量、主参考图、参考图、逐图模式、比例映射、费用与主体一致性差异。
- [ ] 停止中的视觉与按钮状态准确,GUI 其余非破坏性操作不被整窗冻结。
- [ ] 默认网关确认、价格、余额、AI 帮写和原有套图生成不回归。
## 测试与文档
- `tests/test_product_suite_gui.py`、`tests/test_gui.py`:两来源入口、预检、确认文案、近似比例、停止状态、无 cmhub 价格请求、AI 帮写边界。
- 更新 `docs/ui/` ⑥效果图及 `docs/ui/README.md`,同步 `docs/routes.md`。
## 验证
```bash
py -3.10 -m unittest tests.test_product_suite_gui tests.test_gui tests.test_workers
py -3.10 -m ruff check app tests main.py
py -3.10 -m compileall app main.py
git diff --check
```
## 边界(不改什么)
- 不改 T-679a 的 HTTP、job 状态机或恢复数据。
- 不开放自定义网关 AI 帮写,不改 cmhub 协议、Chrome/CDP、Excel 或蝦皮更新。
## 关联
- T-679a:多来源任务状态机。
- T-679c:失败卡片、历史和手动重新生成的后续交互。
## 执行记录
(做完在这里写:变更文件、界面决策、验证命令及结果。)
+60
View File
@@ -0,0 +1,60 @@
---
id: T-679c
title: 商品套图多来源恢复、重试与历史展示
phase: 7
deps: [T-679a, T-679b]
status: TODO
created: 2026-07-20
---
# T-679c 商品套图多来源恢复、重试与历史展示
## 问题 / 背景
默认网关异步任务可凭 `task_id` 继续查询;自定义网关同步请求无法在程序中断后确认远端状态。两类 job 在同一项目中并存时,错误的“恢复”或自动重试会造成重复计费、错误轮询或用户误判结果。
## 方案
- 「继续查询已提交图片」仅列出并执行 `generation_source=cmhub`、`provider=cmhub` 且存在 `task_id` 的 job;无 task ID 的 direct job 永不出现在该入口。
- direct 失败卡片只提供“重新生成”,点击后必须再次经过 T-679b 的确认流程,并明确提示“服务商可能对上次未确认请求已计费,本次可能再次收费”。不得后台自动重试或直接复用旧 job。
- 项目载入、历史弹窗和结果卡片以中文显示来源“默认网关 / 自定义网关”;来源是历史事实,切换⑤设置后不得改写。
- 运行中的 direct job 因程序中断被标失败后,历史中保留失败记录和可手动重试入口;不得清除或伪装成已取消。
- 默认网关历史、导出、删除撤销和继续查询维持原有行为;混合来源时每个入口严格按来源过滤。
## 验收要点
- [ ] 继续查询入口只恢复有 cmhub task ID 的默认网关 job;切换来源后仍能恢复旧任务。
- [ ] direct 失败重试必须二次确认,并显示可能重复计费提示;不会自动提交。
- [ ] 历史与结果卡片准确显示中文来源,且不会暴露 URL、模型、密钥或上游原始错误。
- [ ] 同一项目混合来源的历史、导出、删除和重试不会串路。
- [ ] 默认网关的历史生成、导出本轮、继续查询、删除撤销不回归。
## 测试与文档
- `tests/test_product_suite_gui.py`、`tests/test_image_studio_generation.py`:来源过滤、直连失败二次确认、程序中断记录、混合项目历史和默认任务恢复。
- 更新 `docs/04-architecture.md`、`docs/routes.md`、`docs/ui/` 对应说明。
## 验证
```bash
py -3.10 -m unittest tests.test_product_suite_gui tests.test_image_studio_generation tests.test_gui
py -3.10 -m ruff check app tests main.py
py -3.10 -m compileall app main.py
git diff --check
```
## 边界(不改什么)
- 不改 T-679a 的直连请求与状态写入规则,不改 T-679b 的新建任务预检和确认内容。
- 不新增自动恢复、自动重试、provider 私有适配或图片理解直连路径。
- 不改 cmhub 协议、Chrome/CDP、Excel 或蝦皮更新。
## 关联
- T-679a:同步 direct job 状态语义。
- T-679b:生成确认和用户提示。
- T-679:完成本任务后的集成验收。
## 执行记录
(做完在这里写:变更文件、恢复与重试决策、验证命令及结果。)