# 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.0(main.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 一律 502(detail 截断 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 schema(titles_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) URL;data 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`。 - 无密钥 → 500;SDK 异常/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 7(createBrowserRouter)、antd 6.1(zhCN)、axios、Vite 7(SWC 插件)、TS 5.9;`@` 别名 → `src`。 - dev server:端口 8900(strictPort、open),`/api` 代理到 `http://127.0.0.1:8800`(vite.config.ts:17-22)。 ### 5.2 页面与路由 | 文件 | 内容 | |---|---| | src/main.tsx | createRoot 挂载 | | src/App.tsx | ConfigProvider(zhCN、主色 #8b5cf6、圆角 8)+ AntdApp + RouterProvider | | src/router/index.tsx | `/` 与 `/ai-image` → AiImagePage(MainLayout 内);`*` → 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 AiImagePage(pages/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 ImageEditModal(pages/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.ts:TextElement/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 的主要建设量。**