Files
ozon-seller-kit/docs/v2/capability-inventory.md

219 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ozon Seller Kit 现有能力清单(V2 方案设计输入)
> 依据代码逐文件核对生成(server/ 与 studio/,忽略 node_modules/.venv/__pycache__/.output)。
> 数据来源文件:`ozon-seller-kit/server/**` 与 `ozon-seller-kit/studio/src/**`,全部行号以当前工作区为准。
---
## 一、后端 API 清单
FastAPI 应用入口 `server/main.py`
- 应用名 `Ozon Seller Kit` v0.1.0main.py:13)。
- CORS 中间件:仅当 `cors_origin_list` 非空时启用,`allow_credentials=True`,方法/请求头全放行(main.py:16-23)。
- 挂载三个路由:`api/ai``api/image``api/ozon`main.py:25-27)。
- `GET /api/health``{"status":"ok"}`main.py:30-32)。
- 若仓库根 `web/`v1 工具台,冻结)存在,则 `app.mount("/", StaticFiles(html=True))` 静态托管(main.py:11, 35-36)——即生产形态下 FastAPI 同源托管前端。
| 方法 | 路径 | 功能 | 关键入参 | 关键出参 |
|---|---|---|---|---|
| GET | `/api/health` | 健康检查(main.py:30-32 | 无 | `{status: "ok"}` |
| GET | `/api/ai/models` | 列出可用模型(ai.py:10-12 → models_catalog.list_model_options | 无 | `{default: str, models: [{id, label}]}` |
| POST | `/api/ai/copy` | 生成 Ozon 俄文商品文案(ai.py:15-17 → deepseek.generate_copy | `CopyRequest``source_text`(≥10字符)、`product_name``model_code``model`(可选) | `CopyResponse``titles_ru/zh[2]``description_ru/zh``tags_ru/zh``model``usage{prompt_tokens,completion_tokens}` |
| POST | `/api/image/edit` | AI 图生图/图像编辑(image.py:9-11 → image_edit.edit_image | `ImageEditRequest``base_image`(dataURL/公网URL)、`prompt``model`(白名单)、`mask_image?``function?``n`(1-4)、`size?``seed?``style?``prompt_extend``strength?` | `ImageEditResponse``task_id``results[{url(24h), image_base64(dataURL)}]``image_count``request_id` |
| — | `/api/ozon/*` | **空占位**ozon.py:3-5):仅定义前缀与注释 `# Phase 3: Ozon Seller API product upload`,无任何端点 | — | — |
前端调用面:`studio/src/services/image.ts` 只调 `/api/image/edit``/api/ai/*` 目前只有 v1 工具台 `web/js/ai-copy.js`233 行 models、299 行 copy,用原生 fetch)在消费。
---
## 二、配置与模型目录机制
### 2.1 环境变量(server/config/settings.py + 仓库根 `.env`
- `load_dotenv` 与 pydantic-settings 均指向仓库根 `.env`settings.py:8-9, 16-17`parents[2]` 上跳两级)。
- 字段(settings.py:21-29):
- `deepseek_api_key` / `openai_api_key` / `dashscope_api_key`
- `dashscope_base_http_api_url`(华北2北京业务空间专用,普通 API Key 留空)
- `host=127.0.0.1``port=8800`uvicorn 启动参数)
- `cors_origins`(逗号分隔)→ 属性 `cors_origin_list`settings.py:31-35
- `get_settings()``@lru_cache`settings.py:38-40)。
- `.env.example` 给出全部变量名:`DEEPSEEK_API_KEY``DASHSCOPE_API_KEY``DASHSCOPE_BASE_HTTP_API_URL``HOST``PORT``CORS_ORIGINS`
### 2.2 模型目录(server/config/models.yaml + services/models_catalog.py
- YAML 结构:`default`(默认模型 id+ `models[]`,每项含 `id/label/provider/api_model/base_url/api_key_env/max_tokens/params`
- 密钥**不写入 yaml**,只引用环境变量名(`api_key_env: DEEPSEEK_API_KEY`)。
- 当前仅两个 deepseek 模型(deepseek-v4-flash 默认、deepseek-v4-pro),均 `base_url=https://api.deepseek.com``max_tokens=4000``params.thinking.type=disabled`(关闭思维链,防止推理耗尽 token 正文为空)。
- `models_catalog.py`
- `ModelSpec` Pydantic 模型(16-25 行),`params` 为任意 dict、直接并入请求体。
- `load_models_file()``@lru_cache`43-54 行),校验 default 在列表中、列表非空;文件缺失抛 RuntimeError。
- `list_model_options()`57-62 行)→ 前端下拉用 `{id,label}`
- `get_model_spec(model_id)`65-74 行):空值回落 default,未知 id 抛 400。
- `resolve_api_key(spec)`77-83 行):按 `api_key_env` 读环境变量,缺失抛 500。
- 设计要点:**模型即配置**——新增模型只需改 yaml + 加环境变量,代码零改动(deepseek 类);这是 V2 可直接继承的机制。
### 2.3 依赖(server/requirements.txt
`fastapi>=0.115``uvicorn[standard]>=0.32``httpx>=0.27``pydantic-settings>=2.6``python-dotenv``PyYAML``dashscope>=1.23.8`
---
## 三、AI 文案服务细节(services/deepseek.py + prompts/copy_ru.py
### 3.1 调用链
`POST /api/ai/copy``generate_copy(req)`deepseek.py:144-181):
1. `get_model_spec(req.model)` 取模型规格,未传用默认。
2. messages = system(`SYSTEM_PROMPT`) + user(`build_user_prompt(...)`)system 完整文本见 copy_ru.py:1-65。
3. `_chat_once(spec, messages)`deepseek.py:93-141):
- URL = `base_url + /chat/completions`Bearer 认证。
- payload`model=api_model``temperature=0.45`(事实稳定、营销留少量变化)、`max_tokens``response_format={type:"json_object"}``**spec.params`
- httpx 超时 90s;网络错误/HTTP≥400 一律 502detail 截断 500 字符)。
- 解析 `body["choices"][0]["message"]["content"]`;若 `finish_reason=="length"` 且正文为空 → 502 并提示调 max_tokens 或关思维链(131-138 行,对应 yaml 中 thinking disabled 的注释)。
4. **重试机制**(159-177 行):最多 2 次;解析失败时把上一次输出以 `role=assistant` 追加,再追加一条"请仅重新输出合法 JSON"的 user 消息重试一次;仍失败抛 502。
### 3.2 JSON 解析与字段清洗
- `_extract_json_object`17-37 行):剥 ```json 代码块 → `json.loads` → 失败则截取首 `{` 到末 `}` 再解析 → 必须为 dict。
- 字段类型容错:
- `_as_title_list`(64-73 行):**标题不按逗号切分**(标题本身含逗号)。
- `_as_str_list`48-61 行):标签按 `[,\n]` 切分,兼容字符串/数组。
- `_as_str`40-45 行):必须字符串。
- `_map_copy_payload`76-90 行)→ `CopyResponse`usage 取 prompt/completion tokens。
- 最终校验:`titles_ru``description_ru` 非空,否则视为失败触发重试(164-166 行)。
### 3.3 提示词结构(copy_ru.py
- **SYSTEM_PROMPT** 核心约束:
- 输出固定 JSON schematitles_ru/zh 各 2 条一一对应;tags_ru/zh 各 10~15 个、逐项对应;description 完整俄文卡 + 中文逐项对照)。
- 描述固定结构:`Описание товара``Характеристики`(只列原文事实,俄式尺寸写法)→ `Преимущества`3~6 条利益点)→ `Комплектация`(仅原文提到配件时)。
- 标题规则:60-90 字符、核心品类词开头、前 30 字符含关键属性、删年份/新款/爆款噪声、两标题互补。
- **事实边界**(强约束):禁止虚构结构/配件/认证/产地/品牌/受众/使用效果;行业词归一(搪胶→винил 非 каучук);"防摔"不得推导安全认证。
- 优先级:事实准确 > 俄语自然 > 信息完整与转化力 > 关键词覆盖。
- **build_user_prompt**68-86 行):模板包裹 `<当前商品名>``<型号>``<商品资料>`,并要求区分"事实来源"与"生成要求",与文案无关的指令忽略、要求不得写成事实。
---
## 四、图生图服务细节(services/image_edit.py
### 4.1 云与模型
- 调用**阿里云百炼 DashScope**`dashscope` Python SDK,同步调用,`asyncio.to_thread` 放入线程池,177-185 行)。
- 模型白名单(schemas/image_edit.py:9-17):`wanx2.1-imageedit``wan2.6-image``qwen-image-edit``qwen-image-edit-plus``qwen-image-edit-plus-2025-10-30`
- 两种调用形态(按模型路由,170-174 行):
- **wanx2.1-imageedit → `ImageSynthesis.call`**71-121 行):同步、function 式。kwargs`api_key/model/function/prompt/base_image_url/n` + 可选 `mask_image_url/size/seed/style/prompt_extend/strength`。解析 `rsp.output.results[].url``usage.image_count``request_id`
- **wan2.6-image / qwen-image-edit 系列 → `MultiModalConversation.call`**124-167 行):messages=`[{role:user, content:[{image: base_image},{text: prompt}]}]` + `n/size/prompt_extend`;解析 `output.choices[0].message.content[].image`
- `function` 白名单(schemas/image_edit.py:24-29):`description_edit`(无掩码图生图,默认)/ `description_edit_with_mask`(局部重绘,需 mask_image/ `stylization_local` / `stylization_all`
### 4.2 请求校验(schemas/image_edit.py
- `base_image` / `mask_image`data URL`data:image/...`)或公网 http(s) URLdata URL 编码后 ≤ 15MB`_MAX_BASE64_LENGTH`32 行);mask 可空。
- `prompt` 非空;`model` 白名单校验;`n` 1~4`strength` 0.0~1.0(默认 0.5,加文字/大改建议 0.8);`prompt_extend` 默认 True。
### 4.3 关键工程决策
- **服务端代理下载 → data URL**(`_download_to_data_url`,29-48 行):阿里云结果 URL 未开放 CORS,前端直接绘 canvas 会污染画布无法导出;服务端下载后转 `data:{content-type};base64,...`,上限 20MB`_MAX_PROXY_BYTES`23 行),失败回退空串、前端用 `url`
- 无密钥 → 500SDK 异常/HTTP 非 200 → 502`_fail`60-64 行)。
- `_apply_base_url`(51-57 行):仅北京业务空间需设 `dashscope.base_http_api_url`
- 文件头注释明确:**当前不落盘、不存储图片**,将来接七牛云可在返回 URL 后加"下载并转存"步骤,api 层与前端契约不动(image_edit.py:1-9)——这是刻意的扩展点。
---
## 五、studio 前端页面/组件/功能清单与交互流程
### 5.1 技术栈(studio/package.json + vite.config.ts
- React 19.2、react-router 7createBrowserRouter)、antd 6.1zhCN)、axios、Vite 7SWC 插件)、TS 5.9`@` 别名 → `src`
- dev server:端口 8900strictPort、open),`/api` 代理到 `http://127.0.0.1:8800`vite.config.ts:17-22)。
### 5.2 页面与路由
| 文件 | 内容 |
|---|---|
| src/main.tsx | createRoot 挂载 |
| src/App.tsx | ConfigProviderzhCN、主色 #8b5cf6、圆角 8+ AntdApp + RouterProvider |
| src/router/index.tsx | `/``/ai-image` → AiImagePageMainLayout 内);`*` → Navigate `/` |
| src/layouts/MainLayout.tsx | 固定 Sider(240, 深色, 可折叠) + Header(页面标题/副标题) + Content(Outlet)<768px 切 Drawer 移动菜单 |
| src/layouts/SidebarMenu.tsx | antd Menu"主要功能"分组,点击 navigate |
| src/layouts/menuConfig.tsx | **仅一个菜单项**`/ai-image`「AI 图生图」(subtitle:上传图片、加水印、用万相模型进行图生图编辑);`getPageInfo` 路径→标题映射 |
### 5.3 AiImagePagepages/ai-image/AiImagePage.tsx)——主页面
状态:图片列表 `ImageItem[]`id/fileName/image/dataUrl/state)、全局水印设置(type=image|text、text 默认 'Panda Store'、opacity 默认 30、水印图 `/imgs/watermark.jpg` 预加载为 HTMLImageElement)、编辑弹窗(modalOpen/editing)。
区块与流程:
1. **水印设置面板**:类型 Radio(图片/文字)→ 文字输入 或 水印图预览(圆形贴图);透明度 Slider(0-100)。
2. **上传**`Upload.Dragger` 多图、`accept=image/*``beforeUpload` 返回 false(阻止自动上传,纯前端读文件)→ `fileToImage`FileReader → dataURL → Image)→ 追加进列表,按图片尺寸算默认水印位置(右下角)。
3. **预览网格**:每张图一个 `WatermarkCanvas`(可拖拽水印,坐标相对原图、`clampPos` 限制不越界);卡片操作:加水印/清除水印(`toggleWatermark`)、**AI 生图**`openEdit``renderFullRes` 全分辨率合成水印图 → dataURL → 打开弹窗)、导出 PNG(`downloadCanvas`,文件名加 `_watermark` 后缀)、删除;顶部"全部加水印"(`applyToAll` 重建所有 state)与"清空所有"。
4. **AI 生图弹窗** = ImageEditModal(见下)。
### 5.4 ImageEditModalpages/ai-image/components/ImageEditModal.tsx)——编辑工作台 Drawer
右抽屉(`min(1240px, 96vw)`),每次打开重置状态;三段式布局 + 底部 AI 指令条:
- **左:底图切换 Thumb 列表**——「原图」+ 每次 AI 生成的结果图(`AiResult{id,image,dataUrl,url}`),点击切换当前底图(标注可叠加在 AI 结果上继续编辑)。
- **中:AnnotationCanvas**(预览最大宽 900)——标注画布,Pointer 交互:拖拽移动、旋转手柄旋转、四角手柄缩放(文字改 fontSize、标尺等比改 length/tSize/lineWidth/labelFontSize);命中检测 `hitTest`(旋转手柄 → 四角 → body);`onBeginInteraction` 每次交互开始时压撤销快照。
- **右:属性编辑**——Tabs(文字/标尺);添加文字/标尺按钮;选中元素属性面板 ElementPropsPanel**样式预设**:选中元素可"保存预设"localStorage key `ozon_annotation_presets_v1`,只存样式不含内容/位置),新建元素/选中元素可套用预设;撤销/清空/导出 PNG。
- **底部 AI 条**TextArea 指令(Enter 快捷生成)+ 模型 Select(5 个模型带中文说明)+「AI 生成」按钮。
- **runAi**195-228 行):`editImage({base_image: currentDataUrl, prompt, model, n:1, strength:0.8, prompt_extend:true})` → 取 `results[0]`,优先 `image_base64` 否则 `url` → 解码为 Image 追加为结果底图并自动切换。出错用 `apiErrorMessage` 提示。
- **exportImage**230-240 行):新 canvas 合成底图 + 全部标注元素 → PNG 下载。
### 5.5 组件与工具细节
| 文件 | 能力 |
|---|---|
| components/WatermarkCanvas.tsx | 单图水印预览(PREVIEW_MAX_WIDTH=360),命中水印区域拖拽,坐标映射回原图,`clampPos` 防越界 |
| components/AnnotationCanvas.tsx | 标注画布(预览最大宽 900):绘制底图+元素+选中框;Pointer 交互(move/rotate/resize);交互开始回调存撤销 |
| components/ElementPropsPanel.tsx | 文字:内容/字体(9 种)/字号/加粗/文字色/描边色/描边宽;标尺:长度/线色/线宽/T字大小/标签文字/标签色/标签字号;公共:透明度/旋转 |
| utils/watermark.ts | `WATERMARK_SCALE=0.15`(图片水印直径比)、`WATERMARK_MARGIN=10``WATERMARK_TEXT_SCALE=0.051``getWatermarkSize/clampPos/defaultPos``drawWatermarked`:图片水印圆形裁剪+全局透明度,文字水印先绘离屏层再合成(避免描边透出);`renderFullRes` 全分辨率合成 |
| utils/annotation.ts | `FONT_FAMILIES``HANDLE_RADIUS=7``ROTATE_GAP=26``measureText/elementBox/toLocal/hitTest``drawElement/drawText/drawRuler/drawSelectionHandles``createTextElement/createRulerElement` 工厂(默认值随图宽缩放) |
| utils/image.ts | `fileToImage``canvasToDataUrl``downloadCanvas` |
| types/image.ts | `WatermarkType/WatermarkState/ImageItem/ImageEditRequest/ImageEditResponse`(与后端 schema 字段一致) |
| types/annotation.ts | `TextElement/RulerElement`(中心点/旋转/透明度 + 各自样式字段) |
| services/api.ts | axios 实例:baseURL=`envConfig.apiBaseUrl`(默认 `/api`)、**timeout 120s**`api.get/post` 解包 `res.data``apiErrorMessage` 提取 FastAPI `detail` |
| services/image.ts | `editImage(payload)``POST /image/edit` |
| config/env.ts | `VITE_API_BASE_URL`(默认 `/api`)、`VITE_APP_NAME``debug=import.meta.env.DEV` |
### 5.6 前后端衔接方式
- 前端**只调 `/api/image/edit`**services/image.ts);`/api/ai/*` 暂无 studio 页面(v1 `web/js/ai-copy.js` 在消费,v1 已冻结)。
- 图片以 data URL 在请求体内传输(15MB 上限),响应以服务端代理的 `image_base64` 为主、`url` 兜底——专为规避阿里云结果 URL 无 CORS 的画布污染问题。
---
## 六、可直接复用到 V2 vs 需要重写的部分
### 6.1 可直接复用(成熟、结构清晰、低耦合)
| 资产 | 理由 |
|---|---|
| 模型目录机制(models.yaml + models_catalog.py | "模型即配置":换/加 LLM 只改 yaml+env,代码零改动;`resolve_api_key` 按 env 名取密钥,安全;V2 加多厂商直接扩展 |
| DeepSeek 调用骨架(deepseek.py 的 `_chat_once` + `_extract_json_object` + 重试纠错循环) | 通用性强:JSON 输出强制、代码块剥离、容错截取、失败追加修正消息重试一次;可抽象为通用 "JSON 任务 LLM 调用器" 供 V2 任意生成任务复用 |
| 文案 schema 与提示词(schemas/copy.py + prompts/copy_ru.py | 领域逻辑已打磨(事实边界、俄语标题/描述结构、中俄对照);V2 若保留文案生成,整套平移 |
| 图生图服务(services/image_edit.py + schemas/image_edit.py | 多模型路由(ImageSynthesis vs MultiModalConversation)、服务端代理下载规避 CORS、线程池隔离同步 SDK、响应契约(task_id/request_id 已预留异步字段);注释已规划"返回后接七牛转存"扩展点,与 V2 存储需求天然衔接 |
| 前端 API 层(services/api.ts + services/image.ts + config/env.ts | axios 封装(120s 超时、detail 提取)与端点封装可原样复用;新增端点照 image.ts 模式加即可 |
| 水印/图片/标注工具集(utils/watermark.ts、utils/image.ts、utils/annotation.ts | 纯 canvas 数学、框架无关、按原图坐标系建模(预览缩放/全分辨率导出分离),可直接搬入 V2 |
| 标注数据模型(types/annotation.tsTextElement/RulerElement | 字段设计合理(中心点+旋转+透明度+样式),可直接作为 V2 标注/叠加元素的数据契约种子,甚至与后端共享 schema |
| 布局壳(MainLayout + SidebarMenu + menuConfig | 菜单配置驱动、移动端适配完整;V2 加页面只改 menuConfig + router |
| 水印批处理主流程(AiImagePage 上传→全局水印→批量套用→导出) | 完整闭环,可整体作为 V2 的一个功能模块迁入 |
### 6.2 需要重写/新建(当前缺失或形态不适配 V2)
| 资产 | 现状与重写理由 |
|---|---|
| Ozon 对接(api/ozon.py | 纯占位,V2 若做"发布到 Ozon"需从零实现 Seller API(token 管理、类目树、商品上传、图片上传等) |
| 前端 AI 文案页 | studio 完全没有文案 UI;后端 `/api/ai/copy` 就绪,V2 需新建页面(可参考 v1 web/js/ai-copy.js 的表单交互) |
| 服务端文件/任务持久化 | 现状:图不落盘、无上传端点、无 DB、无任务队列;图生图同步等待(前端 120s 超时)。V2 若引入"商品文件夹"契约(README 所述四部分衔接点)与异步任务,需新增上传/资产/任务/持久化体系 |
| ImageEditModal 状态管理 | 编辑+AI 生成+撤销+预设全在组件本地 useState;V2 若需跨步骤工作流/历史/多页共享,需抽出 store(如 zustand/redux)与后端任务状态同步 |
| AiImagePage 批量状态 | 纯内存 useState,刷新即失;V2 按商品文件夹组织时需要持久化/恢复会话 |
| 鉴权与错误治理 | 无任何鉴权、无统一错误码规范(全部 HTTPException detail 字符串);V2 多用户/上线需补齐 |
| 水印可配置性 | 水印图硬编码 `/imgs/watermark.jpg`、比例/字号为常量;V2 应支持自定义水印资源与配置 |
| 路由/菜单 | 仅 1 页;V2 多页面需按功能域重组(当前结构太薄,扩展时建议直接重构而非继续堆叠) |
| 测试与文档链 | 无自动化测试;契约靠 README/docs 维护。V2 建议引入 schema 共享(README 中 `packages/schema` 待建项)避免前后端字段漂移 |
### 6.3 一句话结论
**后端资产(模型目录、LLM 调用骨架、图生图多模型服务、schema)质量高且刻意留了扩展点(异步字段、七牛转存注释),可大幅平移;前端可平移的是纯 canvas 工具层与布局壳,而所有"产品化"能力——Ozon 对接、文案 UI、持久化/资产、异步任务、鉴权、状态管理——目前为空或雏形,是 V2 的主要建设量。**