docs: add AI outfit UI mockup, align style with print module

- New docs/ui-ai-outfit.html + ui-ai-outfit.png: 「2 AI 穿搭」page
  mockup, same chrome/palette/components as docs/ui-v1 (blue-only).
- Center = recent-results thumbnail strip (finished outputs only) +
  detail table; dropped the live single-image preview to avoid the
  concurrent "which row" ambiguity.
- §7: 保存话术 persists template via config_service; 预览最终提示词 is a
  button opening a read-only QDialog rendered from a sample row.
- §8: concurrency is Python threads (ThreadPoolExecutor); Qt only
  signals back to the main thread to refresh UI; default 1 for API limit.
- §10: three-column layout, results-strip rationale, settings 3-col.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 17:11:56 +08:00
co-authored by Claude Opus 4.8
parent c82f870910
commit 31733ed9c9
3 changed files with 404 additions and 8 deletions
+14 -8
View File
@@ -99,7 +99,9 @@ Excel 行 → `OutfitTask` 列表的转换由 `excel_service` 完成;核心只
## 7. 提示词
- 提示词模板存 `~/.cmbot/config/outfit_prompt.txt`,界面可查看/编辑/保存;点「开始」前自动保存一次。
- 占位符:`{title}`(标题)、`{product_id}`(货号);界面提供「插入占位符」按钮与「最终提示词预览」。
- 占位符:`{title}`(标题)、`{product_id}`(货号);界面提供「插入标题」「插入货号」按钮,把占位符插入光标处。
- **「保存话术」按钮**:把当前编辑的模板原文(占位符原样保留,**不**存替换后的结果)经 `config_service` 写入 `outfit_prompt.txt`。用途是"改完先存、暂不开跑"并给用户明确反馈;与"开始前自动保存"并存、互为兜底(保留按钮是有意为之,对非技术用户更安心)。
- **「预览最终提示词」是按钮,点击弹只读预览窗(`QDialog`)**:取一条样本行把占位符替换成真值,显示最终要发送给 AI 的完整提示词。样本行默认取明细表当前选中行、没选则取第一条待处理行;窗内可切换样本行、可复制;缺 `{title}` 占位符时在窗内提示。预览只读,不改话术本身。
- 缺标题占位符时开始前弹窗询问是否继续。
- 默认话术方向(穿搭/上身,区别于旧项目的主图场景,需另调):人物上身实穿、保留衣服款式/版型/颜色/印花、合适身材与场景、电商可用、默认纯净不加促销牛皮癣。
@@ -107,6 +109,7 @@ Excel 行 → `OutfitTask` 列表的转换由 `excel_service` 完成;核心只
复用旧项目策略(`docs/旧ai穿搭项目.md` §5.3):
- **并发由 Python 线程负责,PySide6 不参与**:`requests` 在等网络响应时释放 GIL,所以 `ThreadPoolExecutor` 起多线程跑行任务时多个 HTTP 请求是真并发。Qt 只负责把进度/结果通过 **signal 跨线程排队回主线程**刷新 UI——**子线程绝不直接操作控件**(违反会崩溃/随机出错)。默认并发 1 是迁就中转 API 限流(429),非框架限制,可在界面调高。
- `ThreadPoolExecutor(max_workers=并发数)` 调度行任务;`RateLimiter(新请求间隔)` 限制请求开始间隔;任务间 `单任务冷却`。
- 每行最多「首次 + 重试次数」尝试;限流/429 用短阶梯等待,普通错误短等待。
- 单次请求放子线程 + 主线程秒级检查,**等待 >30 秒持续打心跳日志**;超时按分辨率动态决定(512/1K/2K/4K → 180/240/360/600 秒,可被 `timeout_seconds` 覆盖)。
@@ -121,14 +124,17 @@ Excel 行 → `OutfitTask` 列表的转换由 `excel_service` 完成;核心只
## 10. 界面(「2 AI 穿搭」页签)
把主窗口当前禁用的「2 AI 穿搭」页签启用,做成独立工作页(与「1 添加印花」并列、互不干扰)。控件:
界面效果图见 **`docs/ui-ai-outfit.html`**(浏览器打开)/ **`docs/ui-ai-outfit.png`**,完全沿用 cmbot 现有视觉、与「1 添加印花」严格统一(同 `docs/ui-v1`:浅灰底、`#0067c0` 蓝单一主色、12px 雅黑、3px 圆角、pill 状态徽章;不引入第二识别色)。「生成中」状态用蓝,与印花页「导出中」同色。把主窗口当前禁用的「2 AI 穿搭」页签启用,做成独立工作页(与「1 添加印花」并列、互不干扰)。
- 选择 Excel 文件、选择输出目录(记住上次,存 `app_config.json`)。
- 模型下拉(来自 AI 模型配置)。
- 通用话术编辑 + 保存 + 插入占位符 + 最终提示词预览。
- 设置:重试失败行、并发数、新请求间隔、单任务冷却、重试次数、分辨率、JPG 质量。
- 开始生成 / 停止生成;实时日志;进度与统计(完成/失败/跳过);结束摘要弹窗;导出失败清单。
- 后台用 `QThread` + `Worker(QObject)` + 信号回主线程(与 cmbot 更新检查/导出一致)。
三栏布局:
- **左栏(较宽,设置区)**:选择 Excel 文件、输出目录(记住上次,存 `app_config.json`);数据源概览(共 N 行 / 完成 / 待处理 / 失败);AI 模型下拉(来自 AI 模型配置);通用话术编辑 +「保存话术」+「插入标题/货号」+「预览最终提示词」(均见 §7);生成设置 **3 列**(重试失败行、并发数、新请求间隔、单任务冷却、失败重试、分辨率、JPG 质量)。
- **中栏(结果 + 明细)**:顶部「最近结果」缩略图条——**只展示已完成的人物效果图**,新图自动加到最左、首图标「最新」,单击看大图、右键开所在文件夹;下方「处理明细」表(行 / 标题 / 货号 / 衣服图 / 状态 / 结果或原因),按 Excel 行顺序,状态用 完成 / 失败 / 生成中 / 待处理 / 跳过 徽章。
- **右栏(运行区)**:本次进度环 + 统计(完成 / 失败 / 跳过 / 待处理);开始生成 / 停止生成;导出失败清单 / 打开输出目录;实时日志(含 §8 心跳行)。
> **设计取舍:不做"实时单图大预览"。** 这是"开了走人、回头抽查"的批量工具;单图实时预览在并发时会产生"该显示哪一行"的歧义。改为「最近结果缩略图条」——只展示已落地成品,既保留"早发现话术/模型不对、及时停掉改话术"的价值,又因只显示成品而消除并发歧义。
- 后台用 `QThread` + `Worker(QObject)` + 信号回主线程(与 cmbot 更新检查/导出一致;并发细节见 §8)。
> 页签内容切换:当前页签栏不切换内容面板(见 `docs/07` §4.2)。启用 AI 穿搭需要为页签接一个 `QStackedWidget`(「1 添加印花」=现有工作区,「2 AI 穿搭」=本面板);这部分在实现阶段一并补。
+390
View File
@@ -0,0 +1,390 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>自动化工具 — AI 穿搭页签</title>
<style>
:root{
--win:#f0f0f0;--panel:#fff;--panel-2:#f7f7f7;--line:#d6d6d6;--line-2:#e3e3e3;
--canvas:#9a9da3;--txt:#202020;--txt-2:#555;--txt-3:#888;
--accent:#0067c0;--accent-2:#0078d4;--accent-soft:#cfe4f7;--accent-bg:#e8f1fb;
--green:#107c10;--amber:#b87a00;--red:#c42b1c;--info:#15538f;--r:3px;
--font:"Segoe UI","Microsoft YaHei","微软雅黑",sans-serif;
--mono:"Cascadia Mono","Consolas","Microsoft YaHei",monospace;
}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:var(--font);color:var(--txt);font-size:12px;background:#cfcfcf;display:flex;align-items:center;justify-content:center;padding:24px;min-height:100vh}
.app{width:1920px;height:1080px;background:var(--win);display:grid;grid-template-rows:34px 40px 1fr 26px;border:1px solid #b5b5b5;overflow:hidden;box-shadow:0 18px 50px rgba(0,0,0,.35)}
@media(max-width:2000px){.app{transform:scale(.92)}}
@media(max-width:1800px){.app{transform:scale(.78)}}
@media(max-width:1500px){.app{transform:scale(.62)}}
@media(max-width:1200px){.app{transform:scale(.48)}}
@media(max-width:950px){.app{transform:scale(.34)}}
/* ---- TITLE ---- */
.title{background:#fff;border-bottom:1px solid var(--line);display:flex;align-items:center;padding:0 0 0 12px;height:34px}
.title .ic{width:18px;height:18px;border-radius:3px;background:linear-gradient(135deg,#0078d4,#005a9e);display:grid;place-items:center;color:#fff;font-size:11px;font-weight:700;margin-right:8px}
.title .t{font-size:12px;color:var(--txt)}
.title .ver{font-size:11px;color:var(--txt-3);margin-left:8px}
.title .gear{margin-left:auto;display:flex;align-items:center;gap:6px;height:100%;padding:0 12px;color:var(--txt-2);border-left:1px solid var(--line-2)}
.title .gear:hover{background:#f0f0f0}
.title .win-btns{display:flex;height:34px}
.title .wb{width:46px;display:grid;place-items:center;color:#444;font-size:11px}
.title .wb.close:hover{background:#c42b1c;color:#fff}.title .wb:hover{background:#e6e6e6}
/* ---- TABS ---- */
.tabs{background:var(--win);border-bottom:1px solid var(--line);display:flex;align-items:flex-end;padding:0 12px;gap:4px}
.tab{display:flex;align-items:center;gap:8px;height:32px;padding:0 18px;background:#e4e4e4;border:1px solid var(--line);border-bottom:none;border-radius:6px 6px 0 0;color:var(--txt-2);position:relative;top:1px}
.tab .num{width:18px;height:18px;border-radius:50%;background:#bcbcbc;color:#fff;font-size:11px;font-weight:700;display:grid;place-items:center}
.tab .nm{font-size:13px}
.tab.on{background:#fff;color:var(--txt);border-color:var(--line);font-weight:600}
.tab.on .num{background:var(--accent)}
.tab:not(.on):hover{background:#ececec}
/* ---- BODY GRID ---- */
.body{display:grid;grid-template-columns:558px 1fr 300px;min-height:0}
/* ---- LEFT : data source + settings ---- */
.left{background:var(--panel);border-right:1px solid var(--line);display:flex;flex-direction:column;min-height:0}
.lscroll{flex:1;overflow:auto;padding:12px}
.grp{border:1px solid var(--line);border-radius:var(--r);background:#fff;margin-bottom:14px;position:relative;padding:15px 12px 12px}
.grp-t{position:absolute;top:-8px;left:9px;background:#fff;padding:0 6px;font-size:11px;font-weight:600;color:var(--txt-2)}
.fl label{display:block;color:var(--txt-2);margin-bottom:4px;font-size:11px}
.row{display:flex;gap:8px;margin-bottom:8px}.row:last-child{margin-bottom:0}.fl{flex:1;min-width:0}
.path{display:flex;align-items:center;height:28px;border:1px solid var(--line);border-radius:var(--r);background:#fff}
.path .p{flex:1;padding:0 7px;color:#444;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.path .br{width:30px;height:100%;border-left:1px solid var(--line);display:grid;place-items:center;background:var(--win)}
.sel{display:flex;align-items:center;justify-content:space-between;height:28px;border:1px solid var(--line);border-radius:var(--r);background:#fff;padding:0 8px}
.sel .car{color:#666;font-size:10px}
.inp{display:flex;align-items:center;height:28px;border:1px solid var(--line);border-radius:var(--r);background:#fff;padding:0 7px}
.inp input{border:none;outline:none;width:100%;font:inherit;color:var(--txt);background:none}
.inp .u{color:var(--txt-3);font-size:11px;padding-left:4px}
.inp.focus{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}
.ckline{display:flex;align-items:center;gap:6px;color:var(--txt-2);margin-top:2px}
.cbox{width:14px;height:14px;border:1px solid #999;border-radius:2px;background:#fff;display:inline-block;flex:none}
.cbox.on{background:var(--accent);border-color:var(--accent)}
.cbox.on::after{content:"✓";color:#fff;font-size:9px;line-height:12px;display:block;text-align:center}
/* loaded-excel summary line */
.xlmeta{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}
.chipmeta{font-size:10px;padding:1px 8px;border-radius:9px;background:#eef3f9;color:#2b5b8c}
.chipmeta.done{background:#e3f3e3;color:#2c7a2c}
.chipmeta.wait{background:#eee;color:#666}
.chipmeta.err{background:#fbe4e2;color:#a3271c}
/* prompt editor */
.ta{width:100%;height:128px;overflow:auto;border:1px solid var(--line);border-radius:var(--r);background:#fff;padding:8px 9px;font:12px/1.7 var(--font);color:var(--txt)}
.ph{color:var(--info);font-weight:600}
.promptbar{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.pchip{display:flex;align-items:center;gap:5px;height:26px;padding:0 9px;border:1px solid var(--line);border-radius:13px;background:#fff;color:var(--txt-2);font-size:11px}
.pchip .pp{color:var(--info);font-family:var(--mono)}
.pchip:hover{background:#f3f8fd;border-color:var(--accent-soft)}
.pchip.act{margin-left:auto;border-color:var(--accent-soft);color:var(--accent)}
/* compact settings grid */
.sgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px 10px}
.sgrid .fl{margin:0}
/* ---- CENTER : the transformation thesis + batch table ---- */
.center{background:var(--panel-2);display:grid;grid-template-rows:auto 1fr;min-height:0}
/* results strip — recently finished outputs only (no live/in-flight state) */
.rstrip{padding:14px 16px 0}
.rstrip-h{display:flex;align-items:baseline;gap:10px;margin-bottom:10px}
.rstrip-h .st{font-size:13px;font-weight:600}
.rstrip-h .meta{color:var(--txt-3);font-size:11px}
.rstrip-h .hint{margin-left:auto;color:var(--txt-3);font-size:11px}
.rcards{display:flex;gap:10px;overflow-x:auto;padding-bottom:6px}
.rcard{width:126px;flex:none;border:1px solid var(--line);border-radius:6px;overflow:hidden;background:#fff}
.rcard.new{border-color:var(--accent-soft);box-shadow:0 0 0 2px var(--accent-bg)}
.rcard .rimg{height:150px;position:relative;overflow:hidden;display:grid;place-items:center;background:radial-gradient(120% 90% at 50% 0,#eef4fb 0,#e3edf7 48%,#d8e6f3 100%)}
.rcard .badge-new{position:absolute;top:5px;left:5px;background:var(--accent);color:#fff;font-size:9px;padding:1px 6px;border-radius:8px;z-index:2}
.rcard .rcap{display:flex;align-items:center;gap:6px;padding:5px 8px;border-top:1px solid var(--line-2);font-size:11px}
.rcard .rcap b{font-weight:600}
.rcard .rcap .sz{margin-left:auto;color:var(--txt-3);font-size:10px}
.rcard.pending{opacity:.85}
.rcard.pending .rimg{background:repeating-linear-gradient(45deg,#f1f1f1 0 8px,#f6f6f6 8px 16px);color:var(--txt-3);font-size:11px}
/* mini worn-on-model figure (result thumbnail); per-card color via --tee / inline --prt */
.mfig{position:relative;width:96px;height:132px;filter:drop-shadow(0 5px 12px rgba(0,0,0,.14))}
.mfig .hr{position:absolute;top:1px;left:50%;transform:translateX(-50%);width:35px;height:20px;border-radius:18px 18px 0 0;background:#3a2a22}
.mfig .h{position:absolute;top:6px;left:50%;transform:translateX(-50%);width:28px;height:32px;border-radius:14px 14px 13px 13px;background:#e7c5a8}
.mfig .nk{position:absolute;top:32px;left:50%;transform:translateX(-50%);width:14px;height:11px;background:#e0b89a}
.mfig .ts{position:absolute;top:38px;left:50%;transform:translateX(-50%);width:88px;height:94px;background:var(--tee,#fdfdfb);border-radius:14px 14px 6px 6px;box-shadow:inset 0 4px 12px rgba(0,0,0,.06)}
.mfig .sl,.mfig .sr{position:absolute;top:42px;width:20px;height:56px;background:var(--tee,#fdfdfb);border-radius:10px}
.mfig .sl{left:2px;transform:rotate(8deg)}.mfig .sr{right:2px;transform:rotate(-8deg)}
.mfig .ch{position:absolute;top:60px;left:50%;transform:translateX(-50%);width:40px;height:40px;border-radius:4px;background:var(--prt,transparent)}
/* batch table */
.tablewrap{margin:14px 16px 16px;display:flex;flex-direction:column;min-height:0;border:1px solid var(--line);border-radius:6px;background:#fff;overflow:hidden}
.tbl-h{display:flex;align-items:center;gap:10px;height:34px;padding:0 12px;background:var(--panel-2);border-bottom:1px solid var(--line-2)}
.tbl-h .tt{font-weight:600}
.tbl-h .tmeta{color:var(--txt-3);font-size:11px}
.tbl-h .filters{margin-left:auto;display:flex;gap:6px}
.fbtn{height:24px;padding:0 10px;border:1px solid var(--line);border-radius:12px;background:#fff;color:var(--txt-2);font-size:11px;display:flex;align-items:center;gap:5px}
.fbtn.on{background:var(--accent-bg);border-color:var(--accent-soft);color:var(--accent)}
.tbl-scroll{flex:1;overflow:auto}
table.bt{width:100%;border-collapse:collapse;font-size:11px}
table.bt th{position:sticky;top:0;background:#f4f4f4;border-bottom:1px solid var(--line);text-align:left;font-weight:600;color:var(--txt-2);padding:7px 10px;white-space:nowrap}
table.bt td{border-bottom:1px solid var(--line-2);padding:6px 10px;color:var(--txt);white-space:nowrap}
table.bt tr.sel td{background:var(--accent-bg)}
table.bt tr:hover td{background:#f7faff}
.bidx{color:var(--txt-3);width:34px}
.gthumb{width:22px;height:26px;border-radius:2px;display:inline-block;vertical-align:-7px;margin-right:7px;border:1px solid rgba(0,0,0,.12);background:#f0f0ec}
.gthumb.d{background:#2c3037}
.pill{display:inline-block;padding:1px 9px;border-radius:9px;font-size:10px;font-weight:600}
.pill.done{background:#e3f3e3;color:#2c7a2c}
.pill.run{background:#e0eefc;color:#15538f}
.pill.wait{background:#eee;color:#666}
.pill.skip{background:#f1efe6;color:#8a7a3a}
.pill.err{background:#fbe4e2;color:#a3271c}
.lnk{color:var(--accent)}
.muted{color:var(--txt-3)}
/* ---- RIGHT : run + log ---- */
.right{background:var(--win);border-left:1px solid var(--line);display:flex;flex-direction:column;min-height:0}
.runtop{padding:14px 14px 10px;border-bottom:1px solid var(--line);background:#fff}
.runtop .rt{font-size:11px;font-weight:600;color:var(--txt-2);margin-bottom:10px}
.progwrap{display:flex;align-items:center;gap:14px}
.ring{--p:32;width:78px;height:78px;border-radius:50%;flex:none;
background:conic-gradient(var(--accent) calc(var(--p)*1%),#e8e8e8 0);
display:grid;place-items:center;position:relative}
.ring::before{content:"";position:absolute;inset:9px;border-radius:50%;background:#fff}
.ring .pc{position:relative;font-size:17px;font-weight:700;color:var(--txt)}
.ring .pc small{font-size:10px;color:var(--txt-3);font-weight:400}
.stats{flex:1;display:grid;grid-template-columns:1fr 1fr;gap:7px}
.stat{border:1px solid var(--line);border-radius:var(--r);padding:5px 8px;background:#fafafa}
.stat .v{font-size:15px;font-weight:700;line-height:1.2}
.stat .k{font-size:10px;color:var(--txt-3)}
.stat.done .v{color:var(--green)}.stat.err .v{color:var(--red)}.stat.skip .v{color:var(--amber)}.stat.left .v{color:var(--txt-2)}
.runbtns{padding:12px 14px;border-bottom:1px solid var(--line);display:flex;flex-direction:column;gap:8px;background:var(--win)}
.btn{width:100%;height:36px;border-radius:var(--r);display:flex;align-items:center;justify-content:center;gap:7px;font-size:12px;font-weight:600;border:1px solid var(--line)}
.btn.pri{background:var(--accent);border-color:var(--accent);color:#fff}.btn.pri:hover{background:var(--accent-2)}
.btn.stop{background:#fff;border-color:#e0b4ae;color:var(--red)}.btn.stop:hover{background:#fdf3f2}
.btn.sec{background:#fff}.btn.sec:hover{background:#f3f8fd}
.brow{display:flex;gap:8px}.brow .btn{margin:0}
.logwrap{flex:1;display:flex;flex-direction:column;min-height:0}
.log-h{display:flex;align-items:center;gap:8px;padding:8px 14px;background:var(--panel-2);border-bottom:1px solid var(--line-2)}
.log-h .lt{font-weight:600;color:var(--txt-2);font-size:11px}
.log-h .auto{margin-left:auto;display:flex;align-items:center;gap:5px;color:var(--txt-3);font-size:10px}
.logbody{flex:1;overflow:auto;background:#fbfbfb;padding:8px 10px;font:11px/1.65 var(--mono);color:#444}
.logbody .l{display:flex;gap:8px;white-space:nowrap}
.logbody .ts{color:#aaa;flex:none}
.logbody .ok{color:var(--green)}
.logbody .warn{color:var(--amber)}
.logbody .erl{color:var(--red)}
.logbody .vio{color:var(--info)}
/* ---- STATUS BAR ---- */
.status{background:#fff;border-top:1px solid var(--line);display:flex;align-items:center;padding:0 12px;gap:14px;color:var(--txt-2);font-size:11px}
.status .s{display:flex;align-items:center;gap:5px}.status b{color:var(--txt);font-weight:600}
.dot{width:7px;height:7px;border-radius:50%}.dot.g{background:var(--green)}.dot.a{background:var(--amber)}.dot.v{background:var(--info)}
.status .sp{flex:1}.vsep{width:1px;height:14px;background:var(--line-2)}
</style>
</head>
<body>
<div class="app">
<!-- TITLE -->
<div class="title">
<div class="ic">穿</div><div class="t">自动化工具</div><div class="ver">v1.0.0</div>
<div class="gear"><span style="font-size:14px">⚙️</span>配置</div>
<div class="win-btns"><div class="wb">—</div><div class="wb">▢</div><div class="wb close">✕</div></div>
</div>
<!-- TABS -->
<div class="tabs">
<div class="tab"><span class="num">1</span><span class="nm">添加印花</span></div>
<div class="tab on"><span class="num">2</span><span class="nm">AI 穿搭</span></div>
<div class="tab"><span class="num">3</span><span class="nm">导出上架</span></div>
</div>
<!-- BODY -->
<div class="body">
<!-- LEFT : data source + settings -->
<div class="left">
<div class="lscroll">
<div class="grp"><div class="grp-t">数据源(Excel)</div>
<div class="fl" style="margin-bottom:8px"><label>Excel 文件</label>
<div class="path"><span class="p">D:\\电商\\2026春\\商品表.xlsx</span><span class="br">…</span></div>
</div>
<div class="xlmeta">
<span class="chipmeta">共 60 行</span>
<span class="chipmeta done">已完成 12</span>
<span class="chipmeta wait">待处理 45</span>
<span class="chipmeta err">失败 3</span>
</div>
</div>
<div class="grp"><div class="grp-t">输出</div>
<div class="fl" style="margin-bottom:8px"><label>输出目录</label>
<div class="path"><span class="p">D:\\CMBot\\合并后的图片</span><span class="br">…</span></div>
</div>
<div class="fl"><label>AI 模型</label>
<div class="sel"><span>Nano Banana 2 · 中转</span><span class="car">▼</span></div>
</div>
</div>
<div class="grp"><div class="grp-t">通用话术</div>
<div class="ta">为商品「<span class="ph">{title}</span>」(货号 <span class="ph">{product_id}</span>)生成人物上身实穿效果图:真人模特正面穿着这件衣服,完整保留款式、版型、颜色与印花图案,自然光、纯色棚拍背景,电商主图风格,不加文字与促销标签。</div>
<div class="promptbar">
<span class="pchip">插入<span class="pp">{title}</span></span>
<span class="pchip">插入<span class="pp">{product_id}</span></span>
<span class="pchip act">⤢ 预览最终提示词</span>
</div>
<div class="promptbar" style="margin-top:8px">
<span class="pchip" style="flex:1;justify-content:center;color:var(--accent);border-color:var(--accent-soft)">💾 保存话术</span>
</div>
</div>
<div class="grp"><div class="grp-t">生成设置</div>
<div class="ckline" style="margin-bottom:12px"><span class="cbox on"></span>重试上次失败的行(失败 3)</div>
<div class="sgrid">
<div class="fl"><label>并发数</label><div class="inp"><input value="1"></div></div>
<div class="fl"><label>新请求间隔</label><div class="inp"><input value="2"><span class="u">秒</span></div></div>
<div class="fl"><label>单任务冷却</label><div class="inp"><input value="1"><span class="u">秒</span></div></div>
<div class="fl"><label>失败重试</label><div class="inp"><input value="2"><span class="u">次</span></div></div>
<div class="fl"><label>分辨率</label><div class="sel"><span>1K</span><span class="car">▼</span></div></div>
<div class="fl"><label>JPG 质量</label><div class="sel"><span>均衡 85</span><span class="car">▼</span></div></div>
</div>
</div>
</div>
</div>
<!-- CENTER : transformation thesis + batch table -->
<div class="center">
<div class="rstrip">
<div class="rstrip-h">
<span class="st">最近结果</span>
<span class="meta">已完成 12 张 · 新图自动加到最左</span>
<span class="hint">单击看大图 · 右键打开所在文件夹</span>
</div>
<div class="rcards">
<div class="rcard new" style="--tee:#2c3037">
<div class="rimg"><span class="badge-new">最新</span>
<div class="mfig"><div class="hr"></div><div class="h"></div><div class="nk"></div><div class="sl"></div><div class="sr"></div><div class="ts"></div><div class="ch" style="background:repeating-linear-gradient(45deg,#eee 0 5px,#bbb 5px 10px)"></div></div>
</div>
<div class="rcap"><b>TY028</b><span class="sz">1.8 MB</span></div>
</div>
<div class="rcard" style="--tee:#f4efe6">
<div class="rimg"><div class="mfig"><div class="hr"></div><div class="h"></div><div class="nk"></div><div class="sl"></div><div class="sr"></div><div class="ts"></div><div class="ch" style="background:conic-gradient(from 20deg,#ff6b6b,#feca57,#48dbfb,#ff9ff3,#ff6b6b)"></div></div></div>
<div class="rcap"><b>TY027</b><span class="sz">1.6 MB</span></div>
</div>
<div class="rcard" style="--tee:#2f4660">
<div class="rimg"><div class="mfig"><div class="hr"></div><div class="h"></div><div class="nk"></div><div class="sl"></div><div class="sr"></div><div class="ts"></div><div class="ch" style="background:radial-gradient(circle at 50% 50%,#ff7e5f,#feb47b)"></div></div></div>
<div class="rcap"><b>TY026</b><span class="sz">1.9 MB</span></div>
</div>
<div class="rcard" style="--tee:#3a623d">
<div class="rimg"><div class="mfig"><div class="hr"></div><div class="h"></div><div class="nk"></div><div class="sl"></div><div class="sr"></div><div class="ts"></div><div class="ch" style="background:linear-gradient(120deg,#5f27cd,#48dbfb)"></div></div></div>
<div class="rcap"><b>TY025</b><span class="sz">1.5 MB</span></div>
</div>
<div class="rcard" style="--tee:#6e2a2a">
<div class="rimg"><div class="mfig"><div class="hr"></div><div class="h"></div><div class="nk"></div><div class="sl"></div><div class="sr"></div><div class="ts"></div><div class="ch" style="background:repeating-linear-gradient(45deg,#333 0 6px,#eee 6px 12px)"></div></div></div>
<div class="rcap"><b>TY024</b><span class="sz">2.0 MB</span></div>
</div>
<div class="rcard" style="--tee:#bba258">
<div class="rimg"><div class="mfig"><div class="hr"></div><div class="h"></div><div class="nk"></div><div class="sl"></div><div class="sr"></div><div class="ts"></div><div class="ch" style="background:radial-gradient(circle at 30% 30%,#10ac84,#222f3e)"></div></div></div>
<div class="rcap"><b>TY023</b><span class="sz">1.7 MB</span></div>
</div>
<div class="rcard" style="--tee:#5a7a9a">
<div class="rimg"><div class="mfig"><div class="hr"></div><div class="h"></div><div class="nk"></div><div class="sl"></div><div class="sr"></div><div class="ts"></div><div class="ch" style="background:conic-gradient(from 20deg,#ff6b6b,#feca57,#48dbfb,#ff9ff3,#ff6b6b)"></div></div></div>
<div class="rcap"><b>TY022</b><span class="sz">1.6 MB</span></div>
</div>
<div class="rcard pending">
<div class="rimg">待生成</div>
<div class="rcap"><b>TY031</b><span class="sz muted">—</span></div>
</div>
</div>
</div>
<div class="tablewrap">
<div class="tbl-h">
<span class="tt">处理明细</span>
<span class="tmeta">按 Excel 行顺序 · 每完成一行即写回并保存</span>
<div class="filters">
<span class="fbtn on">全部 60</span>
<span class="fbtn">失败 3</span>
<span class="fbtn">跳过 0</span>
</div>
</div>
<div class="tbl-scroll">
<table class="bt">
<thead><tr>
<th class="bidx">行</th><th>标题</th><th>货号</th><th>衣服图</th><th>状态</th><th>结果 / 原因</th>
</tr></thead>
<tbody>
<tr><td class="bidx">2</td><td>纯棉宽松短袖T恤</td><td>TY027</td><td><span class="gthumb"></span>TY027_白底.png</td><td><span class="pill done">完成</span></td><td><span class="lnk">合并后的图片\\TY027.jpg</span></td></tr>
<tr><td class="bidx">3</td><td>美式复古印花卫衣</td><td>TY028</td><td><span class="gthumb d"></span>TY028_白底.png</td><td><span class="pill done">完成</span></td><td><span class="lnk">合并后的图片\\TY028.jpg</span></td></tr>
<tr><td class="bidx">4</td><td>高腰显瘦牛仔裤</td><td>TY029</td><td><span class="gthumb" style="background:#4a6b8a"></span>TY029_白底.png</td><td><span class="pill err">失败</span></td><td class="muted">衣服图打不开:文件不存在</td></tr>
<tr class="sel"><td class="bidx">13</td><td>碎花连衣裙夏季新款</td><td>TY030</td><td><span class="gthumb"></span>TY030_白底.png</td><td><span class="pill run">生成中</span></td><td class="muted">第 1 次尝试 · 78%</td></tr>
<tr><td class="bidx">14</td><td>设计感小众衬衫</td><td>TY031</td><td><span class="gthumb d"></span>TY031_白底.png</td><td><span class="pill wait">待处理</span></td><td class="muted">—</td></tr>
<tr><td class="bidx">15</td><td>—</td><td>—</td><td class="muted">(标题/货号为空)</td><td><span class="pill skip">跳过</span></td><td class="muted">字段不完整,安全跳过</td></tr>
<tr><td class="bidx">16</td><td>休闲百搭运动短裤</td><td>TY033</td><td><span class="gthumb" style="background:#3a623d"></span>TY033_白底.png</td><td><span class="pill wait">待处理</span></td><td class="muted">—</td></tr>
<tr><td class="bidx">17</td><td>法式茶歇方领上衣</td><td>TY034</td><td><span class="gthumb"></span>TY034_白底.png</td><td><span class="pill wait">待处理</span></td><td class="muted">—</td></tr>
<tr><td class="bidx">18</td><td>oversize 落肩外套</td><td>TY035</td><td><span class="gthumb d"></span>TY035_白底.png</td><td><span class="pill wait">待处理</span></td><td class="muted">—</td></tr>
<tr><td class="bidx">19</td><td>冰丝防晒空调衫</td><td>TY036</td><td><span class="gthumb" style="background:#bba258"></span>TY036_白底.png</td><td><span class="pill wait">待处理</span></td><td class="muted">—</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- RIGHT : run + log -->
<div class="right">
<div class="runtop">
<div class="rt">本次进度</div>
<div class="progwrap">
<div class="ring" style="--p:25"><span class="pc">15<small>/60</small></span></div>
<div class="stats">
<div class="stat done"><div class="v">12</div><div class="k">完成</div></div>
<div class="stat err"><div class="v">1</div><div class="k">失败</div></div>
<div class="stat skip"><div class="v">2</div><div class="k">跳过</div></div>
<div class="stat left"><div class="v">45</div><div class="k">待处理</div></div>
</div>
</div>
</div>
<div class="runbtns">
<div class="btn stop">■ 停止生成</div>
<div class="brow">
<div class="btn sec">⬇ 导出失败清单</div>
<div class="btn sec">📂 打开输出目录</div>
</div>
</div>
<div class="logwrap">
<div class="log-h"><span class="lt">实时日志</span><span class="auto"><span class="cbox on" style="width:12px;height:12px"></span>自动滚动</span></div>
<div class="logbody">
<div class="l"><span class="ts">15:42:01</span><span>读取 商品表.xlsx:60 行,待处理 47</span></div>
<div class="l"><span class="ts">15:42:01</span><span class="warn">跳过第 9 行:标题/货号为空</span></div>
<div class="l"><span class="ts">15:42:02</span><span>开始生成,并发 1,分辨率 1K</span></div>
<div class="l"><span class="ts">15:42:18</span><span class="ok">✓ TY027 完成 → TY027.jpg(1.6 MB)</span></div>
<div class="l"><span class="ts">15:42:40</span><span class="ok">✓ TY028 完成 → TY028.jpg(1.8 MB)</span></div>
<div class="l"><span class="ts">15:43:02</span><span class="erl">✕ TY029 失败:衣服图打不开(文件不存在)</span></div>
<div class="l"><span class="ts">15:43:03</span><span class="muted">已写回 Excel E/F 列并保存</span></div>
<div class="l"><span class="ts">15:43:20</span><span class="vio">↻ TY030 生成中…(已用 14s,请求进行中)</span></div>
<div class="l"><span class="ts">15:43:34</span><span class="vio">· TY030 等待中转响应(心跳)</span></div>
</div>
</div>
</div>
</div>
<!-- STATUS -->
<div class="status">
<div class="s"><span class="dot v"></span>数据源:<b>商品表.xlsx</b></div><div class="vsep"></div>
<div class="s">模型:<b>Nano Banana 2</b></div><div class="vsep"></div>
<div class="s">输出:<b>合并后的图片</b></div>
<div class="sp"></div>
<div class="s"><span class="dot a"></span>生成中 15/60</div><div class="vsep"></div>
<div class="s" style="color:#c42b1c">1 项失败</div><div class="vsep"></div>
<div class="s" style="color:var(--green)">✓ 已写回 TY028.jpg</div>
</div>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB