docs(tasks): split AI studio implementation plan

This commit is contained in:
chengma
2026-07-11 11:10:43 +08:00
parent 1a2e010997
commit 9e4a6def92
12 changed files with 568 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
---
id: T-586
title: AI工场数据地基:项目、图片资产、异步任务与终选顺序 SQLite 模型
phase: 7
deps: [T-538]
status: TODO
created: 2026-07-11
---
## 问题 / 背景
⑥ AI工场需要同时管理多个账号/商品、蝦皮原图、迭代生成图、多个在途 cmhub task、主图/详情图终选顺序和重启续查。只靠 `data/images/pool/<slug>/<item_id>/` 文件名无法可靠恢复来源、任务状态和排序,因此采用“SQLite 保存状态事实、目录保存图片字节”。完整产品口径见 Obsidian《图片精修-主图详情图生成方案》和 `docs/ui/tab6-ai-studio-v3.svg`。
## 方案
- `app/db.py` 增加 ad-hoc 兼容迁移和数据对象:
- `image_studio_projects`:账号、商品 ID、目标主图数/详情数、草稿提示词、项目状态、软删除/时间字段;同一账号+商品唯一。
- `image_studio_assets`:项目、类别(原图/生成主图/生成详情图)、远程 URL、本地路径、比例、父资产、生成提示词、可用/缺失状态。
- `image_studio_jobs`:资产候选、生成来源、task key/id、Provider、类型、状态、错误、重试和时间字段。
- `image_studio_selections`:项目、`main/detail`、连续 position、asset;类别+位置唯一,同一资产在同类别唯一。
- 新增独立 service 模块(命名按实现确定)封装 CRUD、事务排序、路径计算和状态转换,GUI 不直接写 SQL。
- 图片目录固定在 `data/images/pool/<slug>/<item_id>/{originals,generated,exports}/`,所有路径继续通过 `appconfig` 从 `data/` 解析。
- 数据写入顺序保证崩溃可恢复:先建 job/key,submit 后立即写 task_id,图片完整落盘后才创建/启用 asset。
## 验收要点
- 历史 DB 启动可自动补表/索引且不影响既有 tasks/batches/accounts。
- 同账号+商品重复创建返回已有项目;不同账号可绑定不同商品项目。
- 资产来源、父资产、比例和本地路径可完整读回;文件缺失可标记但不物理删记录。
- job 支持 `pending/submitted/running/succeeded/failed` 及 task key/id 生命周期,重启后可列出待续查任务。
- 终选位置连续、并发写入事务安全、同资产同类别不重复;允许同资产各出现一次 main/detail。
- 纯逻辑测试覆盖迁移、唯一约束、CRUD、状态转换、排序和路径隔离;运行全量 unittest、ruff、compileall、`git diff --check`。
## 边界(不改什么)
- 不做 GUI、CDP、网络下载、AI 请求、导出或蝦皮写操作。
- 不把图片 BLOB 写 SQLite,不采用目录扫描猜状态。
- 不提供本地图片导入,不物理删除项目历史。
## 执行记录
(完成后记录实现文件、迁移决策与验证结果。)
+39
View File
@@ -0,0 +1,39 @@
---
id: T-587
title: AI工场商品项目入口与 CDP 只读拉取蝦皮原主图 URL
phase: 7
deps: [T-586, T-563]
status: TODO
created: 2026-07-11
---
## 问题 / 背景
AI工场以“账号 + 商品 ID”建立项目,并从该账号 Chrome 的商品详情页只读取得全部主图 URL。该流程必须复用已验证 CDP、登录检测、商品失效 toast 和 tab 清理纪律,不能另写页面选择器或修改蝦皮商品。
## 方案
- 新增项目入口服务:校验账号、商品 ID 和重复项目;重复时打开已有项目。
- 抽取/复用 `app.editor` 图片列表读取能力,返回有序 `{src, index}`,不要求上传 input,不下载图片、不改标题/图片。
- 按①采集的只读边界确保账号 Chrome 就绪:已打开复用,未打开才启动;只检测登录,不自动登录、不填密码。
- 商品页后台打开,不主动抢焦点;只关闭本轮自动新建 tab,复用用户已有 tab 不关闭。
- 详情页失效、无权限、未登录、CDP 超时继续上浮结构化中文原因和 Shopee 原始 toast。
- 抓取结果写项目的原图 URL 快照,刷新时按 URL/顺序更新,不创建本地 asset,下载留给 T-588。
## 验收要点
- mock 覆盖已开 Chrome 复用、未开启动、明确未登录、暂时不确定登录、商品失效和成功读 1..9 张 URL。
- 成功路径不调用标题修改、文件上传、拖拽或更新提交。
- URL 顺序与蝦皮主图顺序一致;重复刷新幂等。
- 新建失败 tab 清理、复用 tab 保留;`bring_to_front=False`。
- 涉及 CDP 变更时同步 `docs/04-architecture.md` 并在测试商品做只读实跑;不点击更新。
## 边界(不改什么)
- 不下载/显示图片,不做照片池 GUI,不写蝦皮。
- 不更改现有①采集和③更新行为。
- 不绕过登录、验证码、风控或权限检查。
## 执行记录
(完成后记录 mock、只读实跑与 tab 清理结果。)
+38
View File
@@ -0,0 +1,38 @@
---
id: T-588
title: AI工场远程缩略图与蝦皮原图进入照片池
phase: 7
deps: [T-587, T-546]
status: TODO
created: 2026-07-11
---
## 问题 / 背景
拉取商品后要低成本浏览远程主图:缩略图只进内存,用户单击才下载原图落照片池。网络请求不能卡 GUI,失败不能阻断其他缩略图,也不能因为显示缩略图就把全部原图写盘。
## 方案
- 实现异步缩略图 loader(QNetworkAccessManager 或受控线程池+signal),含内存缓存、并发上限、取消、单图重试和占位状态。
- 实探并记录 Shopee CDN 缩略图尺寸后缀、Referer、Content-Type 和重定向事实;不可用时安全回退原 URL 的受限读取。
- 复用现有无系统代理、安全超时、大小上限和图片解码校验;只允许 http/https、公网地址。
- 单击蝦皮原图时下载原图到 `originals/`,先写临时文件并校验解码,再原子替换目标并创建原图 asset;重复单击幂等。
- 明确不增加“导入本地图片”入口;照片池来源仅蝦皮原图和后续 AI 生成图。
## 验收要点
- 缩略图加载不在 GUI 主线程,不落盘;单图失败/超时显示中文状态且其他图继续。
- 缓存命中不重复请求;切项目或关闭窗口可取消未完成加载。
- 单击后原图真实落盘、asset 可用;失败不留下半文件或可用 asset。
- 非图片、超限、内网 URL、坏图片和重定向边界有测试。
- CDN 两项事实有真实探查记录,测试不依赖公网。
## 边界(不改什么)
- 不做 AI 生成、终选、导出或蝦皮写操作。
- 不自动下载全部原图,不加入本地图片导入。
- 不在日志记录 Cookie、带敏感查询串的完整 URL 或图片字节。
## 执行记录
(完成后记录 CDN 实探、缓存和下载验证。)
+37
View File
@@ -0,0 +1,37 @@
---
id: T-589
title: AI工场独立提示词模板数据层与完整 CRUD
phase: 7
deps: [T-583]
status: TODO
created: 2026-07-11
---
## 问题 / 背景
AI工场只有一个完整提示词框,不再区分“主提示词/每张动作词”,但需要较窄模板下拉和“新建、重命名、保存、删除”。模板不能与②封面模板混用,避免不同业务语义互相覆盖。
## 方案
- `appconfig` 增加 `image_studio_prompts_dir()`,固定为 `data/prompts/image_studio/`。
- 复用 T-583 通用模板 CRUD,不复制另一套文件名校验、重名保护或删除逻辑。
- 新建输入不重复名称;重命名只改名;保存把当前完整文本写入选中模板;删除二次确认。
- 不解析模板内容、不拆段、不按行生成;模板只是把完整文本载入编辑框。
- 当前项目草稿提示词由 T-586 项目字段保存,选择模板只覆盖编辑框/草稿,不自动改历史 job。
## 验收要点
- AI工场模板与标题/封面模板目录完全隔离,CRUD 和中文错误完整。
- 空名、非法路径、重名、目标不存在、删除最后模板均有确定行为。
- 选择模板返回原始完整文本;换行不触发任务拆分。
- 测试只用临时目录,不写真实 data;通用模板既有测试不回归。
## 边界(不改什么)
- 本任务只做路径/数据层,不做 GUI 和 AI 请求。
- 不复制②模板 CRUD,不引入本地图片或提示词自动补写。
- 不把 AI工场模板打成②封面模板的别名。
## 执行记录
(完成后记录模板路径、复用点和测试结果。)
+40
View File
@@ -0,0 +1,40 @@
---
id: T-590
title: AI工场托管模式多图异步生成编排与重启续查
phase: 7
deps: [T-586, T-564]
status: TODO
created: 2026-07-11
---
## 问题 / 背景
AI工场一次可生成 N 张,且每张都要独立计费、失败、重试、停止和续查。T-564 只为普通 tasks 表的一张封面保存一组 task_id/key,不能承载同商品多轮、多源图、多候选任务,需要把 cmhub submit/poll/download 能力复用到 image_studio_jobs。
## 方案
- 抽取可复用的 cmhub 异步生图客户端/服务,以持久化回调或 job repository 注入 task key/id;既有②路径行为保持兼容。
- 对同一源 asset 和同一完整提示词创建 N 条 job:一次调用一张,不按行拆词、不自动补动作、不保证去重。
- 每条 job 在 submit 前落稳定 key,202 后立即落 task_id;poll 成功下载校验后创建 generated asset,记录 main/detail、比例、父 asset、提示词和计费元数据。
- 并发沿用当前最大 5 的 submit+poll/下载双池保护;逐条事件回调供 GUI 显示。
- 停止不删除已提交任务:未开始 job 取消,已提交 job 停止等待并保留 task_id,下次继续查询。
- 本任务只实现 cmhub 托管来源;数据结构预留 `generation_source/provider`,自定义模型由 T-594 解锁。
## 验收要点
- N=3 产生 3 个不同 job/key/task,使用完全相同 prompt/source;每个成功独立进池。
- submit 响应后、poll 前 task_id 已落库;重启续查不再次 POST、不重复扣点。
- 单张失败不回滚成功图;下载失败不重新提交生成。
- 停止、总预算超时、服务端 failed/expired、幂等冲突和点数不足生命周期正确。
- 计费事件按 job 记录;全程不阻塞 GUI 主线程。
- 既有② AI生成 T-564 路径全量测试不回归。
## 边界(不改什么)
- 不做 BYOK、自定义 Provider、自动去重或自动重生成。
- 不做 GUI、终选、导出、CDP 或蝦皮写操作。
- 不改变 cmhub 单次调用一张、预扣/失败退点契约。
## 执行记录
(完成后记录抽取边界、任务生命周期和回归测试。)
+41
View File
@@ -0,0 +1,41 @@
---
id: T-591
title: ⑥ AI工场主界面、照片池、大图预览与生成控制
phase: 7
deps: [T-588, T-589, T-590]
status: TODO
created: 2026-07-11
---
## 问题 / 背景
需要把已完成的数据、拉图、模板和生成服务接入第六个 Tab。当前视觉口径是 `docs/ui/tab6-ai-studio-v3.svg`:左商品轨、中照片池、右生成设置、底部终选盘;本任务先完成主界面和照片池/生成,不实现终选拖放与导出细节。
## 方案
- 主窗口新增 `⑥ AI工场`,不缩小既有 Tab 可点击区域;更新必要的 routes/架构 UI 文档。
- 左轨支持项目切换/新增,顶部显示当前账号+商品、拉取主图、打开项目文件夹。
- 原主图抽屉异步显示;单击加入池,双击大图。
- 照片池显示原/主/详角标、比例、排队/生成/失败状态;单击设源图、双击大图、右键移除(引用中/任务中禁用并说明)。
- 右侧使用单一提示词框和较窄模板下拉,按钮为新建/重命名/保存/删除;程序不显示“主提示词/每张动作词”label。
- 类型、数量、比例、生成来源和计费区域按 v3;T-594 前自定义来源隐藏或明确禁用,不伪装可用。
- 所有网络/CDP/AI/图片解码走 worker/QThread 或异步 Qt API,主线程只更新 UI。
## 验收要点
- 六个 Tab 顺序和中文文案正确,在 1180×760、960×640 及 Windows 缩放下无重叠/截断。
- 任意原图/池图双击打开大图;单击仍执行区域业务动作,不冲突。
- 合并提示词、模板 CRUD、源图选择、N 张生成、停止、进度和逐图失败可用。
- 切项目不会串图片/任务/提示词;运行中关键控件禁用并可安全恢复。
- 不存在“导入本地图片”按钮或隐藏入口。
- GUI offscreen 测试覆盖 objectName、布局结构、信号和状态;人工截图与 SVG 对照。
## 边界(不改什么)
- 不实现终选拖放排序(T-592)、导出(T-593)或 BYOK(T-594)。
- 不自动上传蝦皮,不增加本地图片导入。
- 不在 widget 槽函数直接写 SQL 或做网络请求。
## 执行记录
(完成后记录 GUI 文件、视口截图和测试。)
+39
View File
@@ -0,0 +1,39 @@
---
id: T-592
title: AI工场主图/详情图终选拖放、插入排序与比例轻提示
phase: 7
deps: [T-591]
status: TODO
created: 2026-07-11
---
## 问题 / 背景
照片池是候选集合,导出需要有序的主图和详情图集合。终选必须支持从池拖入、落在占用槽时插入顺延、槽内重排、移出终选和跨类别复用,同时保证数据库与 UI 顺序一致。
## 方案
- 底部全宽终选盘显示目标和当前数量,默认主图 9、详情 12,目标可配置。
- 从池拖到空槽直接放入;拖到占用槽按位置插入并顺延;满额时拒绝超出并提示。
- 槽内拖动原子重排;Delete/右键只移出终选,不删除 pool asset。
- 同一 asset 在同类别最多一次,可同时各用于一次 main/detail。
- 主图/详情图推荐比例不匹配显示黄色边框+tooltip,不硬拦。
- 所有槽位保持连续,无中间空洞;双击槽图查看大图。
- 使用 T-586 service 事务更新 selection,拖放失败时 UI 回滚到 DB 顺序。
## 验收要点
- empty insert、occupied insert+shift、same-list reorder、remove、满额和重复拖入均有测试。
- 跨 main/detail 允许,同类别重复拒绝;比例提示不阻止保存。
- 重启后顺序与退出前一致;并发/异常不产生重复 position 或空洞。
- 运行中图片尚未可用时不能拖入;文件缺失资产有明确禁用状态。
## 边界(不改什么)
- 不导出文件、不删除照片池资产、不上传蝦皮。
- 不自动裁剪、转比例或替用户决定主图/详情图。
- 不实现撤销历史;失败只恢复当前持久化顺序。
## 执行记录
(完成后记录拖放模型、事务和 GUI 测试。)
+42
View File
@@ -0,0 +1,42 @@
---
id: T-593
title: AI工场部分导出、JPEG 转码与已存在商品目录安全处理
phase: 7
deps: [T-592]
status: TODO
created: 2026-07-11
---
## 问题 / 背景
用户可在主图/详情图未满目标数量时导出当前终选。导出要让用户选择父目录,再创建商品 ID 子目录;目标已存在不能简单合并,否则旧序号图片会残留,也不能递归清空可能包含用户文件的目录。
## 方案
- 点击导出用目录选择框选父目录,在其下创建 `<商品ID>/`。
- 按连续终选顺序实际转码并写:`商品ID_主图_1.jpg`、`商品ID_详情图_1.jpg`;质量沿用应用图片质量设置,透明图按已验证规则铺白底。
- 主/详情总数为 0 禁止;其他未满组合均允许,确认框显示 X/目标数。
- 导出前预检全部源文件和解码,使用 staging 写完整后再发布,失败不留下半套结果。
- 目标目录存在时只提供:
- 覆盖本软件导出的图片:仅匹配当前商品命名规则的旧主图/详情图文件,保留其他文件;默认仍需确认。
- 新建带时间目录:`商品ID_YYYYMMDD_HHMMSS/`。
- 取消(默认)。
- 不提供合并;成功后提示实际路径和数量,并提供打开目录。
## 验收要点
- 只主图、只详情、两类部分、满额和零图片场景均正确。
- PNG/WebP 真正输出可解码 JPEG,不是改后缀;序号与终选顺序一致。
- 已存在目录三选正确;覆盖只删除受管命名文件,用户其他文件保持字节不变。
- 某源图缺失/损坏时导出前失败,原目标不被部分改写。
- 路径穿越、非法商品 ID、只读目录、同秒时间目录冲突和文件锁有中文错误。
## 边界(不改什么)
- 不递归清空用户选择目录,不提供合并。
- 不要求达到 9/12,不上传或修改蝦皮。
- 不导出原始任务密钥、提示词、日志或 SQLite 数据。
## 执行记录
(完成后记录文件安全测试和人工导出验证。)
+46
View File
@@ -0,0 +1,46 @@
---
id: T-594
title: AI工场自定义模型(BYOK)授权代理与异步生图接入
phase: 7
deps: [T-590]
status: BLOCKED
created: 2026-07-11
---
## 问题 / 背景
cmhub 托管生图上游存在稳定性风险,AI工场正式发布前需要“自定义模型”逃生口。但自定义 Key 仍必须经过软件套餐/设备授权和 cmhub 异步任务代理,不能恢复客户端 direct 直连或同步长等待。
当前阻塞:cmhub 尚未完成《卡密设备绑定与套餐授权方案》V1.5 所定义的 device credential、BYOK Provider 白名单、加密 task secret、零点数使用记录和 submit/poll 契约。契约和测试环境落地前,本任务不编码、不猜字段。
## 解除阻塞条件
- cmhub 提供版本化 API 文档、错误码、至少一个受支持 Provider、测试凭证和 mock/测试环境。
- 明确 BYOK title/image 请求、task secret 生命周期、终态清理、幂等键和计费字段。
- 服务端确认 Key 不进入 request_payload、CallRecord、队列日志、遥测或异常监控。
## 方案
- ⑤/AI工场增加显式“cmhub托管/自定义模型”来源;不自动切换。
- 自定义 Key 本地使用 Windows DPAPI 保存,UI 仅掩码、替换、清除;不回显明文。
- 所有请求带 device credential/product,服务端先校验套餐和设备;BYOK `points_cost=0`,显示“费用由模型服务商收取”。
- 生图仍 submit→poll→下载,job task_id 持久化续查;不走同步长等待。
- 正式发行版禁用普通用户通过 config 开启 direct 后端。
## 验收要点
- 无有效套餐/设备时 BYOK submit 被服务端拒绝;普通 API Key/direct 配置不能绕过。
- Key 不出现在 DB 普通字段、任务快照、日志、命令行、错误或导出;DPAPI 文件复制到另一用户/设备不可直接解密。
- 托管失败不静默用 BYOK,BYOK 失败不扣 cmhub 点数。
- 自定义生图可重启续查、下载失败不重 submit;Provider/域名/参数严格白名单。
- 全部测试使用占位 Key,不提交真实密钥。
## 边界(不改什么)
- 阻塞解除前不实现临时兼容接口。
- 不允许任意上游 URL,不做客户端直连,不同步长等待。
- 不改 cmhub 托管预扣/退点账本。
## 执行记录
BLOCKED:等待 cmhub V1.5 授权与 BYOK 异步契约。
+40
View File
@@ -0,0 +1,40 @@
---
id: T-595
title: AI工场端到端集成、真实只读验收与产品文档收口
phase: 7
deps: [T-593, T-594]
status: TODO
created: 2026-07-11
---
## 问题 / 背景
AI工场横跨 DB、CDP、远程图片、cmhub 异步生成、GUI 拖放和本地导出。各子任务通过后仍需端到端验收,确认跨模块状态恢复、两种生成来源、部分导出和打包环境一致,并将提案文档升级为当前产品事实。
## 方案
- 串起:新增/恢复项目 → 只读拉原图 → 单击进池 → 设源图 → 同提示词 N 张生成 → 重启续查 → 拖入终选排序 → 部分导出 → 打开目录。
- 托管与自定义来源分别做 mock 全链和成本可控实测;只读 CDP 实跑不点击更新、不上传蝦皮。
- 验证程序强退/重启时 submitted/running job、下载中间态、终选和草稿提示词恢复。
- 核对 1180×760、960×640 和 Windows 缩放,生成 Playwright/Qt 截图与 `tab6-ai-studio-v3.svg` 对照。
- 更新 `docs/04-architecture.md`、`docs/routes.md`、`docs/api.md`、UI README、用户排障和打包说明;Obsidian 方案标记实现状态。
- 确认公开发行包不包含 data、真实 Key、测试图片或 direct 普通入口。
## 验收要点
- 完整闭环可由运营独立完成,所有失败均为中文且能继续/重试,不需要改 DB。
- 同项目多轮生成、多个项目切换和重启不串图、不重复扣费、不丢 task_id。
- 终选顺序与导出文件顺序一致;部分导出和已存在目录三选实测通过。
- 托管/BYOK 计费提示和授权边界准确,不静默切换。
- 现有五个 Tab、②生成、③更新、账号、设置、打包和全量单测不回归。
- 真实操作仅只读商品页和本地导出;CDP 自动回传不在本任务验收。
## 边界(不改什么)
- 不实现主图9张/详情图多媒体框自动回传蝦皮。
- 不加入本地图片导入、自动去重重生或自动动作词。
- 不以人工改 SQLite 作为验收步骤。
## 执行记录
(完成后记录端到端场景、真实只读验收、截图和全量回归。)
+1
View File
@@ -13,5 +13,6 @@
| [tab5-settings.svg](tab5-settings.svg) | ⑤ 设置:AI 模型/Key、路径、端口 |
| [tab6-image-studio.svg](tab6-image-studio.svg) | ⑥ 图片精修(提案·初版):商品列表、原主图九宫、照片池、生成控制、终选排序 |
| [tab6-image-studio-v2.svg](tab6-image-studio-v2.svg) | ⑥ 图片精修(提案·重排):细图标轨、照片池放大、提示词中心整列、全宽出片盘 |
| [tab6-ai-studio-v3.svg](tab6-ai-studio-v3.svg) | ⑥ AI工场(需求定稿·v3):合并提示词、完整模板 CRUD、异步任务照片池、部分导出与终选排序 |
> 仅为线框效果图,最终样式以实现为准。带 `-T584`/`-v2` 等后缀的为**改版提案**,未定稿。界面职责与流程见 [../routes.md](../routes.md)。
+163
View File
@@ -0,0 +1,163 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="820" viewBox="0 0 1280 820" font-family="'Segoe UI','Microsoft YaHei',sans-serif">
<defs>
<style>
.window{fill:#f4f6f9;stroke:#cfd6df}.chrome{fill:#263449}.title{fill:#fff;font-size:14px;font-weight:600}
.tabs{fill:#e8ebf0}.tab{fill:#596271;font-size:13px}.tab-active{fill:#1769d2;font-size:13px;font-weight:600}
.panel{fill:#fff;stroke:#dfe4ea}.section{fill:#202938;font-size:13px;font-weight:600}.label{fill:#303948;font-size:12px}
.muted{fill:#7b8492;font-size:10px}.tiny{fill:#7b8492;font-size:9px}.input{fill:#fff;stroke:#bcc5d0}.textarea{fill:#fbfcfe;stroke:#bcc5d0}
.button{fill:#fff;stroke:#bcc5d0}.button-text{fill:#303948;font-size:11px}.primary{fill:#1769d2}.primary-text{fill:#fff;font-size:12px;font-weight:600}
.danger{fill:#fff;stroke:#d84a4a}.danger-text{fill:#b4232c;font-size:11px}.segment{fill:#fff;stroke:#bcc5d0}.segment-active{fill:#1769d2}
.segment-text{fill:#303948;font-size:11px}.segment-text-active{fill:#fff;font-size:11px;font-weight:600}
.thumb{stroke:#c9d1db}.thumb-selected{stroke:#1769d2;stroke-width:3}.empty{fill:#fbfcfe;stroke:#bfc8d3;stroke-dasharray:4 3}
.main-badge{fill:#1769d2}.detail-badge{fill:#16845b}.origin-badge{fill:#697586}.badge-text{fill:#fff;font-size:9px;font-weight:600}
.success{fill:#137a4a}.warning{fill:#9b6814}.info-box{fill:#eef6ff;stroke:#b9d7fa}.export-box{fill:#f1f8f4;stroke:#afd8c1}
</style>
<linearGradient id="photoA" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#dce9f7"/><stop offset="1" stop-color="#aac6e5"/></linearGradient>
<linearGradient id="photoB" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#f2e1d4"/><stop offset="1" stop-color="#d5b39d"/></linearGradient>
<linearGradient id="photoC" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#dceee4"/><stop offset="1" stop-color="#9ec9af"/></linearGradient>
</defs>
<rect class="window" x="0.5" y="0.5" width="1279" height="819" rx="8"/>
<rect class="chrome" x="0.5" y="0.5" width="1279" height="40" rx="8"/><rect class="chrome" x="0.5" y="20" width="1279" height="21"/>
<text class="title" x="18" y="26">蝦皮圈優化助手 · AI 商品图片工作台</text>
<circle cx="1206" cy="20" r="6" fill="#d7dde6"/><circle cx="1228" cy="20" r="6" fill="#f4bd4d"/><circle cx="1250" cy="20" r="6" fill="#55b96b"/>
<rect class="tabs" x="0.5" y="41" width="1279" height="38"/>
<rect x="666" y="45" width="116" height="34" fill="#f4f6f9"/><rect x="666" y="45" width="116" height="3" fill="#1769d2"/>
<text class="tab" x="30" y="68">① 导入采集</text><text class="tab" x="168" y="68">② AI生成</text>
<text class="tab" x="296" y="68">③ 更新蝦皮</text><text class="tab" x="434" y="68">④ 账号管理</text>
<text class="tab" x="572" y="68">⑤ 设置</text><text class="tab-active" x="690" y="68">⑥ AI工场</text>
<!-- 商品轨 -->
<g transform="translate(16,94)">
<rect class="panel" width="60" height="692" rx="6"/>
<rect class="button" x="10" y="10" width="40" height="26" rx="5"/><text class="button-text" x="24" y="28">▶</text>
<text class="tiny" x="15" y="52">商品</text>
<rect x="10" y="62" width="40" height="40" rx="7" fill="url(#photoA)" stroke="#1769d2" stroke-width="2"/><circle cx="46" cy="66" r="5" fill="#1769d2"/>
<text class="tiny" x="13" y="116">女装店</text>
<rect x="10" y="128" width="40" height="40" rx="7" fill="url(#photoB)" stroke="#d7dde6"/><circle cx="46" cy="132" r="5" fill="#16845b"/>
<text class="tiny" x="16" y="182">my店</text>
<rect x="10" y="194" width="40" height="40" rx="7" fill="url(#photoC)" stroke="#d7dde6"/><circle cx="46" cy="198" r="5" fill="#a4adba"/>
<text class="tiny" x="14" y="248">饰品店</text>
<rect class="button" x="10" y="266" width="40" height="40" rx="7" stroke-dasharray="4 3"/><text x="23" y="292" fill="#1769d2" font-size="19">+</text>
<text class="tiny" x="10" y="672">点击切换</text><text class="tiny" x="10" y="686">项目</text>
</g>
<!-- 顶部操作 -->
<g transform="translate(84,94)">
<rect class="panel" width="1180" height="36" rx="6"/>
<rect class="primary" x="8" y="5" width="108" height="26" rx="5"/><text class="primary-text" x="22" y="23">+ 新增商品</text>
<text class="section" x="134" y="23">女装店 · 51100639510</text><text class="muted" x="338" y="23">项目已自动保存</text>
<rect class="button" x="894" y="5" width="128" height="26" rx="5"/><text class="button-text" x="908" y="22">拉取蝦皮主图</text>
<rect class="button" x="1030" y="5" width="142" height="26" rx="5"/><text class="button-text" x="1043" y="22">打开项目文件夹</text>
</g>
<!-- 原图抽屉 -->
<g transform="translate(84,142)">
<rect class="panel" width="712" height="72" rx="6"/>
<text class="section" x="14" y="22">蝦皮原主图(9)</text><text class="muted" x="126" y="22">单击加入照片池 · 双击查看大图</text><text class="label" x="688" y="22">▾</text>
<g transform="translate(14,30)">
<rect class="thumb" width="34" height="34" rx="4" fill="url(#photoA)"/><rect class="thumb" x="40" width="34" height="34" rx="4" fill="url(#photoB)"/>
<rect class="thumb" x="80" width="34" height="34" rx="4" fill="url(#photoC)"/><rect class="thumb" x="120" width="34" height="34" rx="4" fill="#d9dff0"/>
<rect class="thumb" x="160" width="34" height="34" rx="4" fill="#f1dedf"/><rect class="thumb" x="200" width="34" height="34" rx="4" fill="#d9e6f0"/>
<rect class="thumb" x="240" width="34" height="34" rx="4" fill="#e8ecd7"/><rect class="thumb" x="280" width="34" height="34" rx="4" fill="#e8dcf0"/>
<rect class="thumb" x="320" width="34" height="34" rx="4" fill="#eee3d2"/>
</g>
<text class="tiny" x="390" y="53">缩略图异步加载到内存,加入池时才保存原图</text>
</g>
<!-- 照片池 -->
<g transform="translate(84,224)">
<rect class="panel" width="712" height="396" rx="6"/>
<text class="section" x="14" y="24">照片池(12)</text><text class="muted" x="112" y="24">单击设为源图 · 双击查看大图 · 拖到下方终选槽 · 右键移除</text>
<g transform="translate(14,38)">
<rect class="thumb-selected" width="96" height="96" rx="5" fill="url(#photoA)"/><rect class="origin-badge" width="22" height="15" rx="3"/><text class="badge-text" x="6" y="11">原</text>
<rect class="primary" x="26" y="78" width="44" height="18" rx="3"/><text class="badge-text" x="35" y="91">源图</text>
<rect class="thumb" x="108" width="96" height="96" rx="5" fill="url(#photoB)"/><rect class="origin-badge" x="108" width="22" height="15" rx="3"/><text class="badge-text" x="114" y="11">原</text>
<rect class="thumb" x="216" width="96" height="96" rx="5" fill="#cce2f4"/><rect class="main-badge" x="216" width="22" height="15" rx="3"/><text class="badge-text" x="222" y="11">主</text>
<rect class="thumb" x="324" width="96" height="96" rx="5" fill="#d7eaf5"/><rect class="main-badge" x="324" width="22" height="15" rx="3"/><text class="badge-text" x="330" y="11">主</text>
<rect class="thumb" x="432" width="96" height="96" rx="5" fill="#edf1f4"/><rect x="450" y="4" width="60" height="88" rx="3" fill="url(#photoC)"/>
<rect class="detail-badge" x="432" width="22" height="15" rx="3"/><text class="badge-text" x="438" y="11">详</text><text class="tiny" x="480" y="92">3:4</text>
<rect class="thumb" x="540" width="96" height="96" rx="5" fill="#e6e0f1"/><rect class="main-badge" x="540" width="22" height="15" rx="3"/><text class="badge-text" x="546" y="11">主</text>
</g>
<g transform="translate(14,148)">
<rect class="thumb" width="96" height="96" rx="5" fill="#d9eee2"/><rect class="detail-badge" width="22" height="15" rx="3"/><text class="badge-text" x="6" y="11">详</text>
<rect class="thumb" x="108" width="96" height="96" rx="5" fill="#f0e2d5"/><rect class="main-badge" x="108" width="22" height="15" rx="3"/><text class="badge-text" x="114" y="11">主</text>
<rect class="thumb" x="216" width="96" height="96" rx="5" fill="#edf1f4"/><rect x="240" y="4" width="48" height="88" rx="3" fill="#ded5ea"/>
<rect class="detail-badge" x="216" width="22" height="15" rx="3"/><text class="badge-text" x="222" y="11">详</text><text class="tiny" x="258" y="92">9:16</text>
<rect class="thumb" x="324" width="96" height="96" rx="5" fill="#e8ddd2"/><rect class="detail-badge" x="324" width="22" height="15" rx="3"/><text class="badge-text" x="330" y="11">详</text>
<rect x="432" width="96" height="96" rx="5" fill="#fbfcfe" stroke="#7eadf0" stroke-dasharray="5 4"/>
<circle cx="480" cy="40" r="15" fill="none" stroke="#d8e6f8" stroke-width="4"/><path d="M480 25 A15 15 0 0 1 495 40" fill="none" stroke="#1769d2" stroke-width="4" stroke-linecap="round"/>
<text class="muted" x="446" y="76" fill="#1769d2">生成中 2/3</text><text class="tiny" x="446" y="90">task 已保存</text>
<rect class="empty" x="540" width="96" height="96" rx="5"/><text class="muted" x="568" y="48">排队中</text><text class="tiny" x="561" y="66">可停止续查</text>
</g>
<line x1="14" y1="266" x2="698" y2="266" stroke="#edf0f4"/>
<text class="muted" x="14" y="290">图片文件存目录;项目、资产、task_id 与终选顺序实时写 SQLite</text>
<text class="muted" x="14" y="312">同一图片每类最多一次 · 可同时用于主图和详情图 · 比例不推荐时黄框提示</text>
</g>
<!-- 生成设置 -->
<g transform="translate(808,142)">
<rect class="panel" width="456" height="478" rx="6"/>
<text class="section" x="14" y="24">生成设置</text><text class="muted" x="86" y="24">同一提示词与源图提交 N 个独立任务</text>
<rect class="thumb-selected" x="14" y="36" width="78" height="78" rx="5" fill="url(#photoA)"/><text class="tiny" x="24" y="128">当前源图 · 双击预览</text>
<rect class="input" x="108" y="36" width="158" height="26" rx="5"/><text class="label" x="118" y="53">韩版女装棚拍 ▼</text>
<rect class="button" x="272" y="36" width="54" height="26" rx="5"/><text class="button-text" x="286" y="53">新建</text>
<rect class="button" x="332" y="36" width="54" height="26" rx="5"/><text class="button-text" x="340" y="53">重命名</text>
<rect class="button" x="272" y="68" width="54" height="26" rx="5"/><text class="button-text" x="286" y="85">保存</text>
<rect class="danger" x="332" y="68" width="54" height="26" rx="5"/><text class="danger-text" x="346" y="85">删除</text>
<text class="muted" x="398" y="52">模板</text><text class="tiny" x="398" y="68">独立于②</text>
<rect class="textarea" x="14" y="146" width="428" height="142" rx="6"/>
<text class="label" x="26" y="168" fill="#596779">输入要提交给模型的完整提示词</text>
<text class="label" x="26" y="194">纯白背景棚拍,模特上身展示,保留衣服主体细节,</text>
<text class="label" x="26" y="214">突出韩版宽松版型,生成不同站姿与展示角度,</text>
<text class="label" x="26" y="234">电商主图风格,画面干净,商品颜色准确。</text>
<text class="tiny" x="26" y="272">程序原样提交,不按行拆分,也不自动添加动作词</text>
<text class="label" x="14" y="322">类型</text>
<rect class="segment-active" x="48" y="306" width="52" height="26" rx="5"/><text class="segment-text-active" x="61" y="323">主图</text>
<rect class="segment" x="100" y="306" width="62" height="26" rx="5"/><text class="segment-text" x="111" y="323">详情图</text>
<text class="label" x="180" y="322">数量</text><rect class="input" x="214" y="306" width="44" height="26" rx="5"/><text class="label" x="232" y="323">3</text>
<text class="label" x="278" y="322">比例</text><rect class="input" x="312" y="306" width="82" height="26" rx="5"/><text class="label" x="324" y="323">1:1 ▼</text>
<text class="label" x="14" y="360">生成来源</text><rect class="input" x="78" y="344" width="152" height="26" rx="5"/><text class="label" x="90" y="361">cmhub 托管模型 ▼</text>
<text class="muted" x="244" y="360">自定义模型时显示服务商计费</text>
<rect class="info-box" x="14" y="382" width="428" height="36" rx="5"/><text class="label" x="26" y="398">预计 30 点(3 × 10) · 余额 3480 点</text><text class="tiny" x="26" y="412">不会在失败后静默切换生成来源</text>
<rect class="primary" x="14" y="428" width="300" height="38" rx="6"/><text class="primary-text" x="112" y="452">▶ 生成 3 张主图</text>
<rect class="button" x="322" y="428" width="120" height="38" rx="6"/><text class="button-text" x="369" y="452">停止</text>
</g>
<!-- 终选盘 -->
<g transform="translate(84,632)">
<rect class="panel" width="1180" height="150" rx="6"/>
<text class="section" x="14" y="26">终选与排序</text><text class="label" x="110" y="26">主图 <tspan fill="#1769d2" font-weight="600">5/9</tspan> · 详情 <tspan fill="#16845b" font-weight="600">3/12</tspan></text>
<text class="muted" x="250" y="26">从照片池拖入 · 落在已占槽时插入顺延 · 槽内拖动排序 · 双击看大图</text>
<text class="label" x="14" y="68">主图</text>
<g transform="translate(52,42)">
<rect class="thumb" width="48" height="48" rx="4" fill="#cce2f4"/><rect class="main-badge" y="36" width="48" height="12" rx="2"/><text class="badge-text" x="8" y="46">封面①</text>
<rect class="thumb" x="56" width="48" height="48" rx="4" fill="url(#photoA)"/><rect class="thumb" x="112" width="48" height="48" rx="4" fill="#d7eaf5"/>
<rect class="thumb" x="168" width="48" height="48" rx="4" fill="#c9e8f4"/><rect class="thumb" x="224" width="48" height="48" rx="4" fill="#e4def0"/>
<rect class="empty" x="280" width="48" height="48" rx="4"/><rect class="empty" x="336" width="48" height="48" rx="4"/>
<rect class="empty" x="392" width="48" height="48" rx="4"/><rect class="empty" x="448" width="48" height="48" rx="4"/>
</g>
<text class="label" x="14" y="124">详情</text>
<g transform="translate(52,104)">
<rect class="thumb" width="36" height="36" rx="4" fill="#d9eee2"/><rect class="thumb" x="44" width="36" height="36" rx="4" fill="url(#photoC)"/>
<rect class="thumb" x="88" width="36" height="36" rx="4" fill="#dce9d5"/><rect class="empty" x="132" width="36" height="36" rx="4"/>
<rect class="empty" x="176" width="36" height="36" rx="4"/><rect class="empty" x="220" width="36" height="36" rx="4"/>
<rect class="empty" x="264" width="36" height="36" rx="4"/><rect class="empty" x="308" width="36" height="36" rx="4"/>
<rect class="empty" x="352" width="36" height="36" rx="4"/><rect class="empty" x="396" width="36" height="36" rx="4"/>
<rect class="empty" x="440" width="36" height="36" rx="4"/><rect class="empty" x="484" width="36" height="36" rx="4"/>
</g>
<line x1="850" y1="18" x2="850" y2="134" stroke="#e5e9ee"/>
<rect class="export-box" x="872" y="20" width="292" height="58" rx="6"/>
<text class="label success" x="888" y="40">当前已选 8 张,可以部分导出</text>
<text class="tiny" x="888" y="56">主图 5/9 · 详情 3/12 · 序号按终选顺序连续</text>
<text class="tiny" x="888" y="70">目标目录存在:覆盖软件图片 / 新建时间目录 / 取消</text>
<rect class="primary" x="872" y="88" width="184" height="38" rx="6"/><text class="primary-text" x="904" y="112">导出到文件夹</text>
<rect class="button" x="1064" y="88" width="100" height="38" rx="6"/><text class="button-text" x="1081" y="112">打开导出目录</text>
</g>
<text class="muted" x="84" y="806">v3 · ⑥ AI工场 · 不导入本地图片 · 文件名:商品ID_主图_1.jpg / 商品ID_详情图_1.jpg · v1 导出后手动上传蝦皮</text>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB