Files
ozon-seller-kit/studio/src/services/suite.ts
T

269 lines
11 KiB
TypeScript
Raw 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.
/**
* 套图生成 / 单张 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.slice(0, 7).map((t) => ({
kind: t.value,
title: t.label,
detail: '',
prompt_hint: '',
count: 1,
variant_name: null,
}));
/** 视觉风格(默认提示词可在页面上改写,随生成请求覆盖后端模板) */
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<string, { lang: string; ratio: string; label: string }> = {
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<string, number>;
platform: string;
requirements?: string | 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;
}
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<SuiteInfo>(`/suites/${suiteId}`, config);
}
/** 当前商品的后台图片任务,用于刷新页面后恢复进度。 */
export function listProductSuites(productId: string, activeOnly = false, config?: AxiosRequestConfig) {
return api.get<SuiteInfo[]>(`/products/${productId}/suites`, { ...config, params: { active_only: activeOnly } });
}
export function cancelSuite(suiteId: string) {
return api.post<SuiteInfo>(`/suites/${suiteId}/cancel`);
}
export function retrySuite(suiteId: string) {
return api.post<SuiteInfo>(`/suites/${suiteId}/retry`);
}
/** 重试任务中单个失败图片;批量水印可据此只补偿失败项。 */
export function retrySuiteItem(suiteId: string, itemId: string) {
return api.post<SuiteInfo>(`/suites/${suiteId}/items/${itemId}/retry`);
}
/** ★ 下载生成结果 ZIP(blob) */
export async function downloadSuiteZip(suiteId: string): Promise<Blob> {
const res = await apiClient.get(`/suites/${suiteId}/zip`, { responseType: 'blob' });
return res.data as Blob;
}
export interface ImageEditSinglePayload {
product_id?: string;
image_url: string;
prompt?: string;
model: string;
append?: boolean;
/** 插入锚点:新素材排在该素材之后(方便与原图对比) */
after_asset_id?: string;
/** 单张生成完成后由服务端合成水印;关闭时不传。 */
watermark?: WatermarkPayload;
}
/** ★ 提交单张 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<ApplyWatermarkResponse>('/assets/watermark', payload);
}
/** 取消已选图片的水印,服务端切回保留的无水印原图。 */
export function removeWatermarkFromAssets(assetIds: string[]) {
return api.post<RemoveWatermarkResponse>('/assets/watermark/remove', { asset_ids: assetIds });
}
/** 当前商品的批量水印任务,用于刷新页面后恢复进度。 */
export function listProductWatermarkJobs(productId: string, activeOnly = false, config?: AxiosRequestConfig) {
return api.get<SuiteInfo[]>(`/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(分组建文件夹,服务端代理下载绕防盗链) */
export async function exportImages(payload: ExportImagesPayload): Promise<Blob> {
const res = await apiClient.post('/export/images', payload, { responseType: 'blob' });
return res.data as Blob;
}
/** 单张图片下载:服务端代理源站图片,避免防盗链与跨域导致浏览器无法保存。 */
export async function exportImage(payload: ExportImagesPayload['images'][number]): Promise<Blob> {
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 };
}