560856cf22
Deploy / Build Production Runtime Images (push) Has been skipped
Deploy / Deploy Production (push) Has been skipped
Deploy / Production Browser E2E (push) Has been skipped
Deploy / Deploy Staging (push) Failing after 210h26m26s
CI/CD Pipeline / Frontend Lint (push) Failing after 210h27m0s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 210h27m7s
203 lines
4.7 KiB
TypeScript
203 lines
4.7 KiB
TypeScript
/**
|
|
* 剪辑计划编辑器 API
|
|
* 对接后端 /api/v1/templates 路由
|
|
*/
|
|
import apiClient from "./client";
|
|
|
|
/* ──────────── 类型定义 ──────────── */
|
|
|
|
/** 模板模式(后端枚举值) */
|
|
export type TemplateMode = "pip" | "voice_over" | "one_take" | "voice_pip";
|
|
|
|
/** 模式显示名称映射 */
|
|
export const MODE_LABELS: Record<TemplateMode, string> = {
|
|
pip: "画中画",
|
|
voice_over: "人物口播",
|
|
one_take: "一镜到底",
|
|
voice_pip: "口播+混剪",
|
|
};
|
|
|
|
/** 模式颜色映射 */
|
|
export const MODE_COLORS: Record<TemplateMode, string> = {
|
|
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[];
|
|
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<TemplateSegment, "id">[];
|
|
}
|
|
|
|
/** 使用模板生成请求体 */
|
|
export interface GenerateFromTemplatePayload {
|
|
voiceover_duration: number;
|
|
}
|
|
|
|
/** 验证/生成响应 */
|
|
export interface ValidateWarning {
|
|
code: string;
|
|
message: string;
|
|
details?: Record<string, unknown>;
|
|
}
|
|
|
|
/** 使用模板生成响应 */
|
|
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<EditingTemplate[]> => {
|
|
const response = await apiClient.get<ListTemplatesResponse>("/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<EditingTemplate> => {
|
|
const response = await apiClient.get<EditingTemplate>(`/templates/${id}`);
|
|
return response.data;
|
|
};
|
|
|
|
/** 创建模板 */
|
|
export const createEditingTemplate = async (
|
|
data: SaveTemplatePayload,
|
|
): Promise<EditingTemplate> => {
|
|
const response = await apiClient.post<EditingTemplate>("/templates", data);
|
|
return response.data;
|
|
};
|
|
|
|
/** 更新模板 */
|
|
export const updateEditingTemplate = async (
|
|
id: string,
|
|
data: SaveTemplatePayload,
|
|
): Promise<EditingTemplate> => {
|
|
const response = await apiClient.patch<EditingTemplate>(
|
|
`/templates/${id}`,
|
|
data,
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/** 删除模板 */
|
|
export const deleteEditingTemplate = async (id: string): Promise<void> => {
|
|
await apiClient.delete(`/templates/${id}`);
|
|
};
|
|
|
|
/** 获取模板分类列表 */
|
|
export const getTemplateCategories = async (): Promise<TemplateCategory[]> => {
|
|
const response = await apiClient.get<ListCategoriesResponse>(
|
|
"/templates/categories/list",
|
|
);
|
|
return response.data.items;
|
|
};
|
|
|
|
/** 使用模板生成视频(调用 validate 端点) */
|
|
export const generateFromTemplate = async (
|
|
templateId: string,
|
|
data: GenerateFromTemplatePayload,
|
|
): Promise<GenerateFromTemplateResponse> => {
|
|
const response = await apiClient.post<GenerateFromTemplateResponse>(
|
|
`/templates/${templateId}/validate`,
|
|
data,
|
|
);
|
|
return response.data;
|
|
};
|