diff --git a/apps/web/src/api/editing-planner.ts b/apps/web/src/api/editing-planner.ts deleted file mode 100644 index 6794bed24..000000000 --- a/apps/web/src/api/editing-planner.ts +++ /dev/null @@ -1,243 +0,0 @@ -/** - * 模板编辑器 API - * 对接后端 /api/v1/templates 路由 - */ -import apiClient from "./client" -import type { - WatermarkConfig, - IntroOutroConfig, - PipConfig, - FilterConfig, - ChromaKeyConfig, - StickerConfig, - CoverConfig, -} from "@/pages/editing-planner/types" - -/* ──────────── 类型定义 ──────────── */ - -/** 模板模式(后端枚举值) */ -export type TemplateMode = "pip" | "voice_over" | "one_take" | "voice_pip" - -/** 模式显示名称映射 */ -export const MODE_LABELS: Record = { - pip: "混剪", - voice_over: "人物口播", - one_take: "一镜到底", - voice_pip: "口播+混剪", -} - -/** 模式颜色映射 */ -export const MODE_COLORS: Record = { - pip: "blue", - voice_over: "green", - one_take: "orange", - voice_pip: "purple", -} - -/** 标题配置 */ -export interface TitleConfig { - ai_auto_select: boolean - content: string - font_preset: string - font_color: string - font_size: number - position: string -} - -/** 字幕配置 */ -export interface SubtitleConfig { - enabled: boolean - position: string - font: string - color: string - size: number - animation: string -} - -/** BGM 配置 */ -export interface BgmConfig { - enabled: boolean - music_id: string -} - -/** 模板片段 */ -export interface TemplateSegment { - id?: string - segment_order: number - duration_min: number - duration_max: number - material_type: string | null -} - -/** 剪辑模板 */ -export interface EditingTemplate { - id: string - name: string - mode: TemplateMode - category: string - tags: string[] - title_config: TitleConfig - subtitle_config: SubtitleConfig - bgm_config: BgmConfig - estimated_duration: number - segments: TemplateSegment[] - /** 水印配置(后端就绪后启用) */ - watermark_config?: WatermarkConfig - /** 片头片尾配置(后端就绪后启用) */ - intro_outro_config?: IntroOutroConfig - /** 混剪配置 */ - pip_config?: PipConfig - /** 滤镜调色配置 */ - filter_config?: FilterConfig - /** 绿幕抠像配置 */ - green_screen_config?: ChromaKeyConfig - /** 贴纸配置 */ - sticker_config?: StickerConfig - /** 封面配置 */ - cover_config?: CoverConfig - is_active?: boolean - created_at: string - updated_at: string -} - -/** 模板分类 */ -export interface TemplateCategory { - id: string - name: string - created_at?: string -} - -/** 创建/更新模板请求体 */ -export interface SaveTemplatePayload { - name: string - mode: TemplateMode - category: string - tags: string[] - title_config: TitleConfig - subtitle_config: SubtitleConfig - bgm_config: BgmConfig - estimated_duration: number - segments: Omit[] - /** 水印配置(后端就绪后启用) */ - watermark_config?: WatermarkConfig - /** 片头片尾配置(后端就绪后启用) */ - intro_outro_config?: IntroOutroConfig - /** 混剪配置 */ - pip_config?: PipConfig - /** 滤镜调色配置 */ - filter_config?: FilterConfig - /** 绿幕抠像配置 */ - green_screen_config?: ChromaKeyConfig - /** 贴纸配置 */ - sticker_config?: StickerConfig - /** 封面配置 */ - cover_config?: CoverConfig -} - -/** 使用模板生成请求体 */ -export interface GenerateFromTemplatePayload { - voiceover_duration: number -} - -/** 验证警告详情 */ -export interface ValidationWarningDetails { - /** 相关字段名 */ - field?: string - /** 期望值 */ - expected?: string | number - /** 实际值 */ - actual?: string | number - /** 建议值 */ - suggested?: string | number -} - -/** 验证/生成响应 */ -export interface ValidateWarning { - code: string - message: string - details?: ValidationWarningDetails -} - -/** 使用模板生成响应 */ -export interface GenerateFromTemplateResponse { - template: EditingTemplate - warnings: ValidateWarning[] -} - -/** 列表响应(带分页) */ -export interface ListTemplatesResponse { - items: EditingTemplate[] - total: number -} - -/** 分类列表响应 */ -export interface ListCategoriesResponse { - items: TemplateCategory[] -} - -// ============ API 函数 ============ - -/** 获取模板列表 */ -export const getEditingTemplates = async (params?: { - category?: string - tag?: string - skip?: number - limit?: number -}): Promise => { - const response = await apiClient.get("/templates", { - params: { - skip: params?.skip ?? 0, - limit: params?.limit ?? 50, - }, - }) - let list = response.data.items - if (params?.category) list = list.filter((t) => t.category === params.category) - if (params?.tag) list = list.filter((t) => t.tags.includes(params.tag!)) - return list -} - -/** 获取模板详情 */ -export const getEditingTemplate = async (id: string): Promise => { - const response = await apiClient.get(`/templates/${id}`) - return response.data -} - -/** 创建模板 */ -export const createEditingTemplate = async ( - data: SaveTemplatePayload, -): Promise => { - const response = await apiClient.post("/templates", data) - return response.data -} - -/** 更新模板 */ -export const updateEditingTemplate = async ( - id: string, - data: SaveTemplatePayload, -): Promise => { - const response = await apiClient.patch(`/templates/${id}`, data) - return response.data -} - -/** 删除模板 */ -export const deleteEditingTemplate = async (id: string): Promise => { - await apiClient.delete(`/templates/${id}`) -} - -/** 获取模板分类列表 */ -export const getTemplateCategories = async (): Promise => { - const response = await apiClient.get("/templates/categories/list") - return response.data.items -} - -/** 使用模板生成视频(调用 validate 端点) */ -export const generateFromTemplate = async ( - templateId: string, - data: GenerateFromTemplatePayload, -): Promise => { - const response = await apiClient.post( - `/templates/${templateId}/validate`, - data, - ) - return response.data -} diff --git a/apps/web/src/api/editing-planner/constants.ts b/apps/web/src/api/editing-planner/constants.ts new file mode 100644 index 000000000..5e23f900a --- /dev/null +++ b/apps/web/src/api/editing-planner/constants.ts @@ -0,0 +1,20 @@ +/** + * 模板编辑器常量 + */ +import type { TemplateMode } from "./types" + +/** 模式显示名称映射 */ +export const MODE_LABELS: Record = { + pip: "混剪", + voice_over: "人物口播", + one_take: "一镜到底", + voice_pip: "口播+混剪", +} + +/** 模式颜色映射 */ +export const MODE_COLORS: Record = { + pip: "blue", + voice_over: "green", + one_take: "orange", + voice_pip: "purple", +} diff --git a/apps/web/src/api/editing-planner/index.ts b/apps/web/src/api/editing-planner/index.ts new file mode 100644 index 000000000..117be0f94 --- /dev/null +++ b/apps/web/src/api/editing-planner/index.ts @@ -0,0 +1,36 @@ +/** + * 模板编辑器 API — 目录化入口 + * 保持与原 editing-planner.ts 相同导出,向后兼容 + */ + +// 类型 +export type { + TemplateMode, + TitleConfig, + SubtitleConfig, + BgmConfig, + TemplateSegment, + EditingTemplate, + TemplateCategory, + SaveTemplatePayload, + GenerateFromTemplatePayload, + ValidationWarningDetails, + ValidateWarning, + GenerateFromTemplateResponse, + ListTemplatesResponse, + ListCategoriesResponse, +} from "./types" + +// 常量 +export { MODE_LABELS, MODE_COLORS } from "./constants" + +// API 函数 +export { + getEditingTemplates, + getEditingTemplate, + createEditingTemplate, + updateEditingTemplate, + deleteEditingTemplate, + getTemplateCategories, + generateFromTemplate, +} from "./templates" diff --git a/apps/web/src/api/editing-planner/templates.ts b/apps/web/src/api/editing-planner/templates.ts new file mode 100644 index 000000000..2458de451 --- /dev/null +++ b/apps/web/src/api/editing-planner/templates.ts @@ -0,0 +1,78 @@ +/** + * 模板编辑器 API 函数 + */ +import apiClient from "../client" +import type { + EditingTemplate, + TemplateCategory, + SaveTemplatePayload, + GenerateFromTemplatePayload, + GenerateFromTemplateResponse, + ListTemplatesResponse, + ListCategoriesResponse, +} from "./types" + +/** 获取模板列表 */ +export const getEditingTemplates = async (params?: { + category?: string + tag?: string + skip?: number + limit?: number +}): Promise => { + const response = await apiClient.get("/templates", { + params: { + skip: params?.skip ?? 0, + limit: params?.limit ?? 50, + }, + }) + let list = response.data.items + if (params?.category) list = list.filter((t) => t.category === params.category) + if (params?.tag) list = list.filter((t) => t.tags.includes(params.tag!)) + return list +} + +/** 获取模板详情 */ +export const getEditingTemplate = async (id: string): Promise => { + const response = await apiClient.get(`/templates/${id}`) + return response.data +} + +/** 创建模板 */ +export const createEditingTemplate = async ( + data: SaveTemplatePayload, +): Promise => { + const response = await apiClient.post("/templates", data) + return response.data +} + +/** 更新模板 */ +export const updateEditingTemplate = async ( + id: string, + data: SaveTemplatePayload, +): Promise => { + const response = await apiClient.patch(`/templates/${id}`, data) + return response.data +} + +/** 删除模板 */ +export const deleteEditingTemplate = async (id: string): Promise => { + await apiClient.delete(`/templates/${id}`) +} + +/** 获取模板分类列表 */ +export const getTemplateCategories = async (): Promise => { + const response = await apiClient.get("/templates/categories/list") + return response.data.items +} + +/** 使用模板生成视频(调用 validate 端点) */ +export const generateFromTemplate = async ( + templateId: string, + data: GenerateFromTemplatePayload, +): Promise => { + const response = await apiClient.post( + `/templates/${templateId}/validate`, + data, + ) + return response.data +} diff --git a/apps/web/src/api/editing-planner/types.ts b/apps/web/src/api/editing-planner/types.ts new file mode 100644 index 000000000..a866de307 --- /dev/null +++ b/apps/web/src/api/editing-planner/types.ts @@ -0,0 +1,138 @@ +/** + * 模板编辑器类型定义 + */ +import type { + WatermarkConfig, + IntroOutroConfig, + PipConfig, + FilterConfig, + ChromaKeyConfig, + StickerConfig, + CoverConfig, +} from "@/pages/editing-planner/types" + +/** 模板模式(后端枚举值) */ +export type TemplateMode = "pip" | "voice_over" | "one_take" | "voice_pip" + +/** 标题配置 */ +export interface TitleConfig { + ai_auto_select: boolean + content: string + font_preset: string + font_color: string + font_size: number + position: string +} + +/** 字幕配置 */ +export interface SubtitleConfig { + enabled: boolean + position: string + font: string + color: string + size: number + animation: string +} + +/** BGM 配置 */ +export interface BgmConfig { + enabled: boolean + music_id: string +} + +/** 模板片段 */ +export interface TemplateSegment { + id?: string + segment_order: number + duration_min: number + duration_max: number + material_type: string | null +} + +/** 剪辑模板 */ +export interface EditingTemplate { + id: string + name: string + mode: TemplateMode + category: string + tags: string[] + title_config: TitleConfig + subtitle_config: SubtitleConfig + bgm_config: BgmConfig + estimated_duration: number + segments: TemplateSegment[] + watermark_config?: WatermarkConfig + intro_outro_config?: IntroOutroConfig + pip_config?: PipConfig + filter_config?: FilterConfig + green_screen_config?: ChromaKeyConfig + sticker_config?: StickerConfig + cover_config?: CoverConfig + is_active?: boolean + created_at: string + updated_at: string +} + +/** 模板分类 */ +export interface TemplateCategory { + id: string + name: string + created_at?: string +} + +/** 创建/更新模板请求体 */ +export interface SaveTemplatePayload { + name: string + mode: TemplateMode + category: string + tags: string[] + title_config: TitleConfig + subtitle_config: SubtitleConfig + bgm_config: BgmConfig + estimated_duration: number + segments: Omit[] + watermark_config?: WatermarkConfig + intro_outro_config?: IntroOutroConfig + pip_config?: PipConfig + filter_config?: FilterConfig + green_screen_config?: ChromaKeyConfig + sticker_config?: StickerConfig + cover_config?: CoverConfig +} + +/** 使用模板生成请求体 */ +export interface GenerateFromTemplatePayload { + voiceover_duration: number +} + +/** 验证警告详情 */ +export interface ValidationWarningDetails { + field?: string + expected?: string | number + actual?: string | number + suggested?: string | number +} + +/** 验证警告 */ +export interface ValidateWarning { + code: string + message: string + details?: ValidationWarningDetails +} + +/** 使用模板生成响应 */ +export interface GenerateFromTemplateResponse { + template: EditingTemplate + warnings: ValidateWarning[] +} + +/** 列表响应(带分页) */ +export interface ListTemplatesResponse { + items: EditingTemplate[] + total: number +} + +/** 分类列表响应 */ +export interface ListCategoriesResponse { + items: TemplateCategory[] +}