/** * 套图生成 / 单张 AI 生图 / 图片导出 服务层。 * 契约对齐 docs/v2.1/api.md(Phase A 前端先行;带 ★ 的接口待 Phase B 服务端实现)。 * 常量(类型/风格/模型)与 image-suite-studio 的 src/api/client.ts 保持一致。 */ import { api } from './api'; import apiClient from './api'; import type { AxiosRequestConfig } from 'axios'; /** 套图类型白名单(与服务端 prompts/common.py 对齐) */ export const SUITE_TYPE_OPTIONS = [ { value: 'white_bg', label: '白底主图' }, { value: 'key_features', label: '核心卖点图' }, { value: 'selling_pt', label: '卖点图' }, { value: 'material', label: '材质图' }, { value: 'lifestyle', label: '场景展示图' }, { value: 'multi_scene', label: '多场景拼图' }, { value: 'ecommerce_detail', label: '电商详情图' }, { value: 'size_chart', label: '尺寸标注图' }, { value: 'sku_collection', label: 'SKU合集图' }, { value: 'custom', label: '创意图' }, ] as const; /** 出图方案项:一类图 × 数量,可绑定 SKU 规格 */ export interface PlanItem { kind: string; title: string; detail: string; prompt_hint: string; count: number; variant_name?: string | null; } /** 默认方案:7 种基础类型各 1 张(AI 规划前) */ export const DEFAULT_PLAN: PlanItem[] = SUITE_TYPE_OPTIONS .filter((t) => t.value !== 'multi_scene') .slice(0, 7) .map((t) => ({ kind: t.value, title: t.label, detail: '', prompt_hint: '', count: 1, variant_name: null, })); /** 商品外廓尺寸;单位可选 mm 或 cm。 */ export interface ProductDimensions { length?: number; width?: number; height?: number; unit?: 'mm' | 'cm'; } /** 视觉风格(默认提示词可在页面上改写,随生成请求覆盖后端模板) */ export const STYLE_SET_OPTIONS = [ { value: 1, label: '北欧极简', prompt: 'Nordic minimalism: light gray or warm off-white background, soft diffused light, low-saturation palette, generous negative space, restrained and clean composition' }, { value: 2, label: '清新明亮', prompt: 'Fresh and bright: white-to-pale-blue gradient background, high-key lighting, clear luminous colors, airy and uncluttered composition' }, { value: 3, label: '高级感深色', prompt: 'Premium dark aesthetic: deep gray or charcoal background, dramatic side lighting, emphasize material texture and sheen, refined and restrained mood' }, { value: 4, label: '暖调生活', prompt: 'Warm lifestyle: warm beige background, cozy warm lighting, inviting home-living texture, approachable and natural atmosphere' }, { value: 5, label: '纯净棚拍', prompt: 'Clean e-commerce studio: plain light background, even frontal softbox lighting, no distracting props, product centered and prominent' }, ] as const; /** 试算页固定目标平台 Ozon:俄文文案 · 3:4 图片 */ export const PLATFORM_SPECS: Record = { ozon: { lang: '俄文', ratio: '3:4', label: 'Ozon' }, }; /** 生图模型(服务端按模型名路由 provider;同名模型多家中转时 value 用「模型@供应商」区分,label 带供应商名) */ export const IMAGE_MODEL_OPTIONS = [ { value: 'qwen-image-3.0-pro', label: 'qwen-image-3.0-pro(阿里巴巴)', desc: '同步生成,响应快、图文理解强,适合快速批量出图' }, { value: 'wan2.7-image-pro', label: 'wan2.7-image-pro(阿里巴巴)', desc: '异步精修,质感与细节更强,适合高质量电商大片' }, { value: 'wan2.6-image', label: 'wan2.6-image(阿里巴巴)', desc: '通义 2.6 图生图,支持参考图与多图融合,速度更快、稳定性好' }, { value: 'wan2.6-t2i', label: 'wan2.6-t2i(阿里巴巴)', desc: '通义 2.6 纯文生图,不使用参考图(商品外观靠文案描述),速度最快' }, { value: 'gpt-image-2', label: 'gpt-image-2(RightCode)', desc: 'GPT 图像模型,构图与图内文案渲染最强,参考图高保真,单张 1-5 分钟' }, { value: 'gpt-image-2-vip', label: 'gpt-image-2-vip(RightCode)', desc: 'GPT 官逆低价通道,构图与文字渲染强、成本更低,适合大批量出图' }, { value: 'nano-banana', label: 'nano-banana(RightCode)', desc: 'Google Gemini 图像模型,出图极快,图像编辑与风格迁移强,多图融合自然' }, { value: 'nano-banana-2', label: 'nano-banana-2(RightCode)', desc: 'Google 新一代图像模型,画质与文字渲染大幅提升,日常生成与改图的综合首选' }, { value: 'nano-banana-2-lite', label: 'nano-banana-2-lite(RightCode)', desc: 'nano-banana-2 轻量版,约 4 秒/张、成本极低,适合大批量出图与快速试错' }, { value: 'nano-banana-pro', label: 'nano-banana-pro(RightCode)', desc: 'Google 最高保真旗舰,细节最强、支持 4K 输出,适合商业级精修大片' }, { value: 'gpt-image-2@pcore', label: 'gpt-image-2(Pcore)', desc: 'Pcore 中转的 GPT 图像模型,异步任务出图,构图与图内文案渲染强' }, ] as const; export const DEFAULT_IMAGE_MODEL = 'gpt-image-2@pcore'; export interface WatermarkPayload { enabled: boolean; type: 'image' | 'text'; text: string; opacity: number; } export const DEFAULT_WATERMARK: WatermarkPayload = { enabled: false, type: 'text', text: 'Panda Store', opacity: 30, }; /** 传给规划/生成的文本素材(kind 与采集 ScanResult.texts 一致) */ export interface SuiteTextPayload { kind: string; content: string; pairs?: Array<{ key: string; value: string }> | null; } export interface SuitePlanPayload { product_id?: string; texts: SuiteTextPayload[]; sku_variants: string[]; image_stats: Record; platform: string; requirements?: string | null; dimensions?: ProductDimensions | null; } export interface SuiteGeneratePayload { product_id?: string; texts: SuiteTextPayload[]; images: Array<{ url: string; group_key: string; variant_name?: string | null }>; style_set: number; style_prompt?: string | null; requirements?: string | null; plan: PlanItem[]; platform: string; model?: string | null; watermark?: WatermarkPayload; dimensions?: ProductDimensions | null; } export interface SuiteImageInfo { item_id: string; type_id: string; name: string; url: string; status: string; error?: string | null; } export interface SuiteInfo { id: string; status: 'queued' | 'pending' | 'running' | 'done' | 'partial' | 'failed' | 'cancelled'; kind?: 'image_suite' | 'image_edit' | string; style_set: number; platform: string; lang: string; ratio: string; provider?: string; total?: number; completed?: number; failed?: number; cancel_requested?: boolean; created_at?: string | null; started_at?: string | null; completed_at?: string | null; images: SuiteImageInfo[]; error?: string | null; } /** ★ AI 智能规划出图方案 */ export function planSuite(payload: SuitePlanPayload) { return api.post<{ summary: string; items: PlanItem[] }>('/suite/plan', payload); } /** ★ 提交一键生成任务 */ export function generateSuite(payload: SuiteGeneratePayload) { return api.post<{ suite_id: string }>('/suite/generate', payload); } /** ★ 查询套图任务状态(轮询用) */ export function getSuite(suiteId: string, config?: AxiosRequestConfig) { return api.get(`/suites/${suiteId}`, config); } /** 当前商品的后台图片任务,用于刷新页面后恢复进度。 */ export function listProductSuites(productId: string, activeOnly = false, config?: AxiosRequestConfig) { return api.get(`/products/${productId}/suites`, { ...config, params: { active_only: activeOnly } }); } export function cancelSuite(suiteId: string) { return api.post(`/suites/${suiteId}/cancel`); } export function retrySuite(suiteId: string) { return api.post(`/suites/${suiteId}/retry`); } /** 重试任务中单个失败图片;批量水印可据此只补偿失败项。 */ export function retrySuiteItem(suiteId: string, itemId: string) { return api.post(`/suites/${suiteId}/items/${itemId}/retry`); } /** ★ 下载生成结果 ZIP(blob) */ export async function downloadSuiteZip(suiteId: string): Promise { const res = await apiClient.get(`/suites/${suiteId}/zip`, { responseType: 'blob' }); return res.data as Blob; } export interface ImageEditSinglePayload { product_id?: string; /** 兼容旧接口的单张底图;新界面使用 image_urls。 */ image_url?: string; /** 选中的参考底图,可多张。 */ image_urls?: string[]; prompt?: string; /** edit 为常规 AI 编辑;outpaint 只允许生成扩展到 3:4 的新增边缘。 */ mode?: 'edit' | 'outpaint'; model: string; append?: boolean; /** 插入锚点:新素材排在该素材之后(方便与原图对比) */ after_asset_id?: string; /** 单张生成完成后由服务端合成水印;关闭时不传。 */ watermark?: WatermarkPayload; /** 勾选时将当前图按尺寸标注图任务处理。 */ generate_size_chart?: boolean; dimensions?: ProductDimensions | null; } /** ★ 提交单张 AI 生图任务;通过 getSuite(suite_id) 查询进度。 */ export function imageEditSingle(payload: ImageEditSinglePayload) { return api.post<{ suite_id: string }>('/suite/image-edit', payload); } /** 删除一条素材(DB 记录 + 本地文件),用于删除已生成的图片 */ export function deleteAsset(assetId: string) { return api.delete<{ deleted: boolean }>(`/assets/${assetId}`); } export interface ApplyWatermarkPayload { asset_ids: string[]; watermark: WatermarkPayload; } export interface ApplyWatermarkResponse { job_id: string; total: number; } export interface RemoveWatermarkResponse { updated_ids: string[]; failed: Array<{ asset_id: string; error: string }>; } /** 将水印任务加入后台队列;Worker 始终从保留的原图重做,避免重复叠加。 */ export function applyWatermarkToAssets(payload: ApplyWatermarkPayload) { return api.post('/assets/watermark', payload); } /** 取消已选图片的水印,服务端切回保留的无水印原图。 */ export function removeWatermarkFromAssets(assetIds: string[]) { return api.post('/assets/watermark/remove', { asset_ids: assetIds }); } /** 当前商品的批量水印任务,用于刷新页面后恢复进度。 */ export function listProductWatermarkJobs(productId: string, activeOnly = false, config?: AxiosRequestConfig) { return api.get(`/products/${productId}/watermark-jobs`, { ...config, params: { active_only: activeOnly } }); } export interface ExportImagesPayload { title: string; images: Array<{ url: string; groupName: string; variantName?: string | null; key: string }>; /** 生成结果导出时忽略图片分组,统一放在 ZIP 的商品根目录。 */ flat?: boolean; } /** ★ 导出采集图片 ZIP(分组建文件夹,服务端代理下载绕防盗链) */ export async function exportImages(payload: ExportImagesPayload): Promise { const res = await apiClient.post('/export/images', payload, { responseType: 'blob' }); return res.data as Blob; } /** 单张图片下载:服务端代理源站图片,避免防盗链与跨域导致浏览器无法保存。 */ export async function exportImage(payload: ExportImagesPayload['images'][number]): Promise { const res = await apiClient.post('/export/image', payload, { responseType: 'blob' }); return res.data as Blob; } /** 上传补充参考图到商品素材(已有接口:POST /api/materials/bytes) */ export async function uploadProductAsset( productId: string, file: File, groupKey = 'upload', ): Promise<{ asset_id: string; status: string }> { const form = new FormData(); form.append('product_id', productId); form.append('group_key', groupKey); form.append('type', 'img'); form.append('file', file); const res = await apiClient.post('/materials/bytes', form); return res.data as { asset_id: string; status: string }; } export interface VideoJobInfo { id: string; kind: 'slideshow_video'; status: 'queued' | 'running' | 'done' | 'partial' | 'failed' | 'cancelled'; total: number; completed: number; failed: number; source_asset_ids: string[]; slide_seconds: number; duration_seconds?: number | null; width: number; height: number; file_size_bytes?: number | null; created_at?: string | null; error?: string | null; result?: { item_id: string; status: string; url?: string | null; error?: string | null } | null; } export function createVideoJob(productId: string, assetIds: string[]) { return api.post<{ job_id: string }>(`/products/${productId}/video-jobs`, { asset_ids: assetIds }); } export function listVideoJobs(productId: string, config?: AxiosRequestConfig) { return api.get(`/products/${productId}/video-jobs`, config); } export function cancelVideoJob(jobId: string) { return api.post<{ deleted: boolean }>(`/video-jobs/${jobId}/cancel`); } export function retryVideoJob(jobId: string) { return api.post(`/video-jobs/${jobId}/retry`); } export function deleteVideoJob(jobId: string) { return api.delete<{ deleted: boolean }>(`/video-jobs/${jobId}`); } /** 下载视频为 Blob,避免浏览器把 MP4 当作页面打开或预览。 */ export async function downloadVideo(jobId: string): Promise { const res = await apiClient.get(`/video-jobs/${jobId}/download`, { responseType: 'blob' }); return res.data as Blob; }