20 KiB
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 Kitv0.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_keydashscope_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:ModelSpecPydantic 模型(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):
get_model_spec(req.model)取模型规格,未传用默认。- messages = system(
SYSTEM_PROMPT) + user(build_user_prompt(...)),system 完整文本见 copy_ru.py:1-65。 _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 的注释)。
- URL =
- 重试机制(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(
dashscopePython 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。
- wanx2.1-imageedit →
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白名单校验;n14;1.0(默认 0.5,加文字/大改建议 0.8);strength0.0prompt_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)。
区块与流程:
- 水印设置面板:类型 Radio(图片/文字)→ 文字输入 或 水印图预览(圆形贴图);透明度 Slider(0-100)。
- 上传:
Upload.Dragger多图、accept=image/*、beforeUpload返回 false(阻止自动上传,纯前端读文件)→fileToImage(FileReader → dataURL → Image)→ 追加进列表,按图片尺寸算默认水印位置(右下角)。 - 预览网格:每张图一个
WatermarkCanvas(可拖拽水印,坐标相对原图、clampPos限制不越界);卡片操作:加水印/清除水印(toggleWatermark)、AI 生图(openEdit:renderFullRes全分辨率合成水印图 → dataURL → 打开弹窗)、导出 PNG(downloadCanvas,文件名加_watermark后缀)、删除;顶部"全部加水印"(applyToAll重建所有 state)与"清空所有"。 - 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 页面(v1web/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 的主要建设量。