feat(edit): AI套图生成、图片切七牛
This commit is contained in:
@@ -49,6 +49,7 @@
|
||||
- 包装输入是编辑草稿合并后的有效 g/mm 值,必须已确认;接口为兼容历史值允许最多三位小数,重量至少 1 g,当前编辑页与试算界面只接受正整数。保存时从实际持久化草稿重新合并并核对,不能仅信任客户端摘要。
|
||||
- `mode=target_markup`:目标利润=采购成本×加价率;求解销售价。`fixed_sale`:selling_price 必填,按该售价核算利润。
|
||||
- 百分比费用以销售额为基数;除加价率外每项小于 1、最多六位小数。引擎拒绝总扣费率≥1。划线价=销售价÷(1−折扣率)。
|
||||
- `selling_price` 在编辑页显示为「实际售价」,利润按实际售价计算。`list_price` 显示为「划线价」,两价在试算结果中同行展示;首次上架 Ozon 的 `price` 使用划线价,不发送 `old_price`,上线后用户再在 Ozon 调整实际售价(2026-10-04 维护者要求)。人民币摘要按报价快照换算两价,不使用当前汇率改写历史报价。缺失或低于实际售价的划线价须重新确认报价后发布。
|
||||
- 枚举运输方式×产品类型×佣金档,按最终币种舍入后回验货值、佣金、重量和体积重边界。默认运输不可用时 recommended_option_id=null,不替换为另一方式。
|
||||
- `fx.mode=system` 读取 PostgreSQL 最新 CBR;`policy_fixed` 必须绑定有固定汇率的方案;`one_time_override` 必须明确 rate 和 reason。固定汇率取方案值,不信任客户端覆盖。采用的汇率固定在本次预览中,15 分钟内确认不重新拉取外部汇率。
|
||||
- 只有不存在有效正式佣金规则且无方案或方案允许时,才能 temporary_commission_rate。已有正式规则禁止覆盖。
|
||||
@@ -95,6 +96,8 @@ context 保存规则版本、实际汇率与来源,以及系统汇率记录 ID
|
||||
写入 `{name,note,parameters,allow_commission_override}`。只保存费用、加价、折扣、固定/系统汇率与运输偏好;采购价、包装、手动售价及临时佣金不属于方案持久字段。
|
||||
方案的默认和比较运输必须属于同一可用费率卡;正式环境默认允许有效草稿卡;可通过 `PRICING_ALLOW_DRAFT_RATES=false` 仅使用已审核卡。历史快照不会被方案修改;尚未确认的预览因方案版本改变要求重算。
|
||||
|
||||
2026-10-04 维护者确认:SKU「重新试算」与「按方案计算全部」主动重读目标店铺 `/pricing/catalog`,使用最新 `is_default=true` 方案的完整参数(包含加价、折扣、费用、运输与汇率偏好),替换 SKU 的旧定价参数再生成预览;沿用当前采购成本和有效包装,不自动保存商品。无默认方案或其费率卡不可用时给出明确提示,不静默使用折扣率为 0 的临时参数。已导入 SKU 不重算。自动试算、手工实际售价调整和试算详情内的重新计算继续使用当前 SKU/详情参数。默认方案折扣率为 50% 时,划线价=实际售价÷(1−50%)=实际售价×2。
|
||||
|
||||
## 5. 数据启用与错误
|
||||
|
||||
迁移中的 CEL 目录为 `draft`,覆盖六种产品类型、三种运输方式,香港空运不纳入。平台类目佣金映射仍是正式试算所需数据;草稿物流费率可按上述配置参与报价,页面保留费率来源标识,接口不会回退到未选定的固定佣金。物流费率审核用于形成有证据的已审核版本,不再是草稿报价上架预检的前置条件。
|
||||
|
||||
@@ -94,7 +94,7 @@ Headers:`Idempotency-Key` 必填,页头平台由 header 指定,body 为空
|
||||
|
||||
### 单张 AI 图片编辑(SKU 选图弹窗)
|
||||
|
||||
商品编辑页固定任务入口使用 `GET /api/products/{product_id}/image-tasks?limit=20&cursor=...`。`limit` 默认 20、最大 100,按进行中优先、创建时间倒序、ID 倒序稳定分页,返回 `{ data: ProductImageTask[], meta: { next_cursor, has_more } }`。接口只返回当前工作空间、当前商品的图片 AI 任务;首期包含单张编辑,后续套图任务按同一结构加入。每项包含 `kind: single_ai_edit | ai_suite`、`status`、`phase`、已完成数/总数、创建/完成时间、稳定错误码、`failure_source: pcore | atlas | null`、`failure_stage: task_setup | input_preparation | provider_submission | provider_polling | result_download | result_processing | result_storage | null`、`failure_detail: string | null`、`provider_error_raw: string | null` 与结果素材 ID 数组。失败来源表示 Pcore 接口返回/调用失败或 Atlas 自身处理失败;步骤指出实际出错的位置。`failure_detail` 是最长 500 字符的脱敏诊断摘要,不包含密钥、对象键或预签名 URL。Pcore 失败时 `provider_error_raw` 保留供应商原始错误响应文本(JSON 响应保存错误对象,最多 32,768 字符),便于在当前工作空间的任务列表中查看;没有供应商响应时为 `null`。历史任务未记录的字段返回 `null`,不得按通用错误码猜测失败来源。`GET /api/media-ai-edits/{task_id}` 的单张编辑响应也返回三个诊断字段,供未关闭的编辑弹窗显示。不返回供应商任务 ID、对象键或预览 URL。任务入口每 3 秒刷新运行中的任务,其余时间低频刷新;任务提交后即时刷新。查看结果时再用素材访问接口签发短时预览 URL。单张编辑任务取消沿用 `POST /api/media-ai-edits/{task_id}/cancel`。
|
||||
商品编辑页固定任务入口使用 `GET /api/products/{product_id}/image-tasks?limit=20&cursor=...`。`limit` 默认 20、最大 100,按进行中优先、创建时间倒序、ID 倒序稳定分页,返回 `{ data: ProductImageTask[], meta: { next_cursor, has_more } }`。接口只返回当前工作空间、当前商品的图片 AI 任务;包含单张编辑和套图任务;套图汇总为整套任务,内部子任务不重复展示。每项包含 `kind: single_ai_edit | ai_suite`、`status`、`phase`、已完成数/总数、创建/完成时间、稳定错误码、`failure_source: pcore | atlas | null`、`failure_stage: task_setup | input_preparation | provider_submission | provider_polling | result_download | result_processing | result_storage | null`、`failure_detail: string | null`、`provider_error_raw: string | null` 与结果素材 ID 数组。失败来源表示 Pcore 接口返回/调用失败或 Atlas 自身处理失败;步骤指出实际出错的位置。`failure_detail` 是最长 500 字符的脱敏诊断摘要,不包含密钥、对象键或预签名 URL。Pcore 失败时 `provider_error_raw` 保留供应商原始错误响应文本(JSON 响应保存错误对象,最多 32,768 字符),便于在当前工作空间的任务列表中查看;没有供应商响应时为 `null`。历史任务未记录的字段返回 `null`,不得按通用错误码猜测失败来源。`GET /api/media-ai-edits/{task_id}` 的单张编辑响应也返回三个诊断字段,供未关闭的编辑弹窗显示。不返回供应商任务 ID、对象键或预览 URL。任务入口每 3 秒刷新运行中的任务,其余时间低频刷新;任务提交后即时刷新。查看结果时再用素材访问接口签发短时预览 URL。单张编辑任务取消沿用 `POST /api/media-ai-edits/{task_id}/cancel`;套图取消与失败重试见 [商品 AI 套图](./product-image-suites.md)。
|
||||
|
||||
`POST /api/media-assets/{asset_id}/ai-edits`:必须带 `Idempotency-Key`,请求体带当前工作空间 `product_id`,图片字段只使用正式素材 ID,不接受图片 URL。`asset_id` 为固定主图;`reference_asset_ids` 最多两项,仅作辅助参考。请求支持 `prompt`、`mode: edit | outpaint`、`ratio: 3:4 | 1:1 | 4:5`、图中文字翻译与目标语言、背景模式与描述、五种可改写的视觉风格、商品长宽高及尺寸标注图。`outpaint` 只允许 3:4、单主图,不生成尺寸标注。成功返回 HTTP 202、任务 ID 和 `queued` 状态。
|
||||
|
||||
@@ -283,12 +283,12 @@ Headers:`Idempotency-Key` 必填。Body:
|
||||
|
||||
AI 填报接口只创建异步任务并返回 `202` + `ai_task_id`,不直接改写已保存草稿或发布数据。任务完成后,编辑页自动把校验合格的字段回填到**未保存表单**,再由用户使用现有 `editor-draft` 保存。详细字段规则、冲突策略与中文对照存储见[商品 AI 填报方案](../product/ai-autofill-plan.md)。
|
||||
|
||||
创建 AI 任务前,前端必须先保存当前编辑草稿。服务端从已持久化的 `ai_input` 组装输入,不接收客户端自行拼装的规格。填报任务读取全部 SKU 的规格摘要,并固化商品、profile、采集和类目目录版本;套图和尺寸图任务只读取目标 SKU 的 `effective_physical_spec` / `effective_package_spec`。因此 AI 使用的是用户在编辑页保存后的资料,而不是浏览器临时拼接的采集载荷。
|
||||
创建填报、文案或专用尺寸图 AI 任务前,前端必须先保存当前编辑草稿,服务端从已持久化的 `ai_input` 组装输入。填报任务读取全部 SKU 的规格摘要,并固化商品、profile、采集和类目目录版本。2026-10-03 经维护者确认的套图抽屉采用独立可编辑资料:目标 SKU 必须已经保存;名称、颜色、实物尺寸/重量、采集信息、卖点和逐张方案作为本次套图输入快照保存,不自动改写商品字段或发布规格。此行为及正式接口以 [商品 AI 套图](./product-image-suites.md) 为准;替换此前尚未实现的 profile 级套图规划接口。
|
||||
|
||||
| 接口 | body 核心字段 | 成功结果 |
|
||||
|---|---|---|
|
||||
| `POST /api/product-platform-profiles/{profile_id}/ai/autofills` | 可选 `hint` 与策略开关;Header 必填 `Idempotency-Key` | 一次创建类目、型号、未导入 SKU 货号、可证包装规格及中俄文案的填报任务。Ozon 来源先运行确定性类目匹配;其他来源使用受限类目树候选。 |
|
||||
| `POST /api/product-platform-profiles/{profile_id}/ai/suite-plans` | `variant_ids` 必填、`prompt` 可选 | 每个目标 SKU 明确绑定,创建套图规划/生成任务。 |
|
||||
| `POST /api/products/{product_id}/ai/suite-plans` | `variant_id`、底图、套图资料与配置 | 创建显式绑定一个目标 SKU 的异步逐张规划;生成由 `POST /api/products/{product_id}/ai/image-suites` 提交。详见 [套图契约](./product-image-suites.md)。 |
|
||||
| `POST /api/product-platform-profiles/{profile_id}/variants/{variant_id}/ai/dimension-images` | 可选样式 | 固化该 SKU 合并后的**实物**规格快照;实物规格缺失/待确认返回 `422`。 |
|
||||
|
||||
`GET /api/product-ai-tasks/{ai_task_id}` 返回任务状态、输入版本、脱敏结果或错误;全局填报成功时返回类目或候选、型号、逐 SKU 货号、可证包装建议及 SPU 中俄文案,不生成 SKU 文案。单 SKU 文案由 `POST /api/product-platform-profiles/{profile_id}/variants/{variant_id}/ai/copy` 单独创建任务,在弹窗预览并由用户应用。Ozon 动态类目属性不属于当前 AI 填报范围,仍通过类目确认后加载的 schema 由用户填写。歧义类目仍需用户选择。中文对照与俄文在下一次 `editor-draft` 中一同保存,读取编辑上下文时一同返回。
|
||||
@@ -309,6 +309,12 @@ Headers:`X-Atlas-Platform-Id`、`Idempotency-Key`、`If-Match: "product_revisi
|
||||
|
||||
同一事务创建 `product_publish_jobs` / `product_publish_job_items`、将 listing 置为 `submitting`,写 Outbox(只含 job ID)。冻结商品/profile 版本、店铺、货号、Ozon 类目和属性、十进制价格、逐 SKU 标题/标签/描述、有效包装规格、素材 ID / checksum / 对象身份;签名 URL 不持久化。标题按 `title_mode` 选择,标签/描述按 SKU 覆盖回退。货号最多 50 字符,发布标题最多 500 字符,每 SKU 1–30 张图。首期仅 rFBS,不传 SPU 共用条码、库存、仓库、顶层 description、视频或富内容;中国主体跨境 rFBS 首期省略 `vat`,实际导入与商品信息税率核对后再决定。
|
||||
|
||||
经维护者 2026-10-04 要求,首次上架的 Ozon `price` 使用已确认报价的 `listing.list_price`(划线价),保留十进制金额字符串精度,不发送 `old_price`。`selling_price` 在 Web 显示为「实际售价」,表示后续折扣后的目标售价,利润仍按此金额试算;划线价为初始上架预留折扣空间,商品上线后由用户在 Ozon 调整实际售价。预检与任务创建要求划线价大于 0 且不低于实际售价,缺失或不合法时返回 `LIST_PRICE_REQUIRED` / `LIST_PRICE_INVALID`,要求重新确认报价,不回退为实际售价。折扣率为 0 时两价相同,正常发布。上架确认页同时展示实际售价和划线价(本次上报价)。已创建任务继续使用冻结的价格快照,已导入 SKU 不因编辑草稿而更新远端价格。
|
||||
|
||||
主题标签在编辑与草稿中仍为数组,支持最多 30 个标签。生成 Ozon 属性 `23171` 时,按 SKU 覆盖或 SPU 继承取得有效标签,去除空项并用空格拼成一个 `values[].value`,例如 `tags: ["#boho", "#loft"]` 映射为 `values: [{ "value": "#boho #loft" }]`;无标签时不提交该属性。schema 的 `max_value_count=1` 限制的是属性值条目数,不是其中的标签个数,不得据此拒绝多标签草稿。
|
||||
|
||||
每个数组项表示一个标签,必须以单个 `#` 开头,正文只允许 Unicode 字母、数字和下划线,标签内不得包含空格或其他 `#`;每项最多 30 个字符(含 `#`)。Web 对手工输入显示具体格式提示,草稿仍可保存供后续修改;发布预检及创建任务按所选 SKU 的有效标签(SKU 覆盖或 SPU 继承)检查,不合规时返回 `OZON_HASHTAG_INVALID` 字段问题并阻止发布。未被选中或被 SKU 覆盖的通用标签不阻断当前发布。AI 全局填报和单 SKU 文案提示词采用同一格式要求;输出回填前补齐缺失的 `#`,将词间空白、连字符转为下划线,过滤仍含非法字符、重复或超长的标签,并保留对应中文对照。不自动改写手工及历史草稿标签。
|
||||
|
||||
```json
|
||||
{ "data": { "publish_job": { "id": "uuid", "profile_id": "uuid", "store_id": "uuid", "status": "queued", "item_count": 2, "created_at": "ISO8601", "completed_at": null, "items": [] } } }
|
||||
```
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# 商品 AI 套图
|
||||
|
||||
本次交付负责人为当前集成会话,范围为 Web、API、Worker、contracts、database。经维护者确认三列右侧抽屉设计,新增契约而不改写已有选图/保存接口。依赖商品、SKU、正式素材、operations、outbox 与既有单图生图任务。验收:SKU 归属校验、最多 8 张底图、默认 4 张与可编辑逐张方案、异步智能规划、后台生成/取消/失败重试、结果回填表单及跨租户隔离。
|
||||
|
||||
所有接口要求当前工作空间鉴权。POST 均要求 UUID `Idempotency-Key`;异步创建返回 202 `{ data: ImageSuite }`,包含 `operation`,Outbox 仅保存任务 ID。GET 返回 200 同一资源,预览地址按次签发。接口不自动修改商品草稿。
|
||||
|
||||
- `POST /api/products/:productId/ai/suite-plans`:请求 `ImageSuitePlanInput`;异步调用 DeepSeek,生成 `shots`。失败显式返回任务错误,不将默认方案冒充 AI 规划。
|
||||
|
||||
智能规划请求在 `ImageSuiteInput` 上增加可选 `image_count`(整数 1–20,默认 4),以当前方案总张数作为预算;生成接口不接收此字段。Worker 使用 `.env` 的 `DEEPSEEK_API_KEY`、`DEEPSEEK_MODEL_NAME` 调用 DeepSeek,关闭 thinking 并要求 JSON。规划按当前 SKU 的商品资料、核心卖点/要求、实物参数、风格、比例及文字语言重新分配用途,必须精确返回预算张数,每类最多 5 张。每张承担一个不同的信息重点;首张为白底主图,优先仅 1 张(大预算可增加不同白底角度),其他用途按品类和可信资料选择,不强制插入场景、尺寸或材质图。底图 ID 不代表图像内容已被识别,不能据此编造细节;尺寸图必须有带单位的实物参数。方案经过共享契约和预算/主题校验,无效结果重新请求一次;取消后不回写结果,失败保留前端当前方案。不自动开始生图,用户核对后显式点击开始生成。
|
||||
- `POST /api/products/:productId/ai/image-suites`:请求 `ImageSuiteInput` 加 `shots`;固化资料、规格、风格、配置、逐张方案并创建生图子任务。每张使用不超过 3 张底图,第一张是主体图。
|
||||
- `GET /api/products/:productId/ai/image-suites`:`limit` 默认 20、最大 100、`cursor` 按创建时间及 ID 倒序分页,供重开抽屉恢复任务。
|
||||
- `GET /api/products/:productId/ai/image-suites/:suiteId`:任务详情,包括逐张状态、脱敏失败诊断及结果正式素材。
|
||||
- `POST /api/products/:productId/ai/image-suites/:suiteId/cancel`:取消规划或未完成的生图子任务;成功素材保留,Worker 不再回写取消项。
|
||||
- `POST /api/products/:productId/ai/image-suites/:suiteId/retry`:只重试失败/取消的子任务,保留成功项;运行中的整套返回 409。规划失败重新创建规划任务。
|
||||
|
||||
`ImageSuiteInput`:`variant_id` 必填,必须属于当前商品;`reference_asset_ids` 1–8 个不重复正式图片;`product_name` 必填;`color`、`dimensions`(实物文字)、`weight`(实物文字)、`collected_info`、`selling_points` 可编辑。Web 尺寸由长/宽/高三个 `InputNumber` 组成,默认 CM,可切换 MM/M 并同步换算;提交时仍以带轴名称和单位的字符串保存到 `dimensions`,空轴省略。实物重量同样使用 `InputNumber`,默认 g,可切换 kg 并同步换算;提交到 `weight` 的仍是带所选单位的字符串(例如 `1.25 kg`),为空时不生成重量标注,已填写数值必须大于 0。`collected_info` 展示为「采集信息/描述」,`selling_points` 展示为「核心卖点/要求」;`ratio` 为 3:4(默认)、1:1、4:3;`language` 为 ru(默认)、zh、en、ar;`style_set` 1–5、`style_prompt` 为可选风格描述。
|
||||
|
||||
每个 `shot` 包含唯一 `id`、`category: white | scene | selling | other`、`type`、`theme`、`description`、`evidence` 与可选 `reference_asset_ids`(1–3,必须属于套图底图库)。每类最多 5 张,总共 1–20 张;缺少实物尺寸时不接受尺寸图。默认 4 张:1 白底主图 + 1 场景展示图 + 1 核心卖点图 + 1 细节特写图(分类数量为 1/1/2/0);缺资料时以真实外观/细节取代无依据的尺寸、材质、对比或配件图。
|
||||
|
||||
`ImageSuite` 包含 `id`、`product_id`、`variant_id`、`purpose: plan | generate`、`status: queued | running | succeeded | partial | failed | cancelled`、`input`、`shots`、`items`、`error_code`、`created_at`、`completed_at`、`operation`。`items` 为逐张 `shot_id` + `edit_id` + 既有 `MediaAiEdit`。生成任务已完成但部分失败返回 partial,可保留成功图并重试失败项。统一图片任务入口将整套作为一项 `kind: ai_suite` 展示,子任务不重复出现;成功子图自然进入当前商品 AI 生图记录。
|
||||
|
||||
取消和重试使用整套资源锁;数据库复合外键强制 SKU/商品/工作空间与子任务同商品归属。底图库关联表强制素材工作空间归属;所有表 FORCE RLS。触发器依据子任务事实汇总整套进度及 operation,队列不是事实来源。
|
||||
|
||||
规划请求示例(UUID 为示例值):
|
||||
|
||||
```json
|
||||
{
|
||||
"variant_id": "11111111-1111-4111-8111-111111111111",
|
||||
"reference_asset_ids": ["22222222-2222-4222-8222-222222222222"],
|
||||
"product_name": "可调角度台灯",
|
||||
"collected_info": "灯头可调节角度",
|
||||
"selling_points": "突出角度调节,画面简洁",
|
||||
"image_count": 4,
|
||||
"ratio": "3:4",
|
||||
"language": "ru"
|
||||
}
|
||||
```
|
||||
|
||||
返回 `202 { data: ImageSuite }`,`purpose=plan`、`status=queued`、`input.image_count=4`,不创建生图子任务。轮询完成后 `status=succeeded`、`shots.length=4`;每项包含主题、画面描述、资料依据及参考图 ID。若配置缺失,任务为 failed,错误码为 `DEEPSEEK_NOT_CONFIGURED`,诊断提示检查 Worker 的 API Key 和模型名称。
|
||||
|
||||
规划校验失败分别使用 `AI_SUITE_PLAN_SCHEMA_INVALID`、`AI_SUITE_PLAN_COUNT_MISMATCH`、`AI_SUITE_PLAN_MAIN_IMAGE_INVALID`、`AI_SUITE_PLAN_DUPLICATE_THEME`、`AI_SUITE_PLAN_DIMENSIONS_UNVERIFIED`;结果保存失败使用 `AI_SUITE_PLAN_SAVE_FAILED`,未知执行异常使用 `AI_SUITE_PLAN_FAILED`。Worker 对每次校验失败及最终失败打印安全的结构化日志,包含任务 ID、阶段、尝试序号和字段校验路径;最终诊断保存至 operation.result.failure(内部诊断,不新增 HTTP 字段)。不保存商品资料、模型原文或凭据。第二次规划携带具体校验规则及路径,仍须通过完整校验,不能截断或用默认方案替换。
|
||||
Reference in New Issue
Block a user