ci: Prettier纳入两层防御体系 (#520)
CI Build & Deploy Pipeline / Staging E2E Tests (push) Failing after 2m50s
CI Build & Deploy Pipeline / Production Browser E2E (push) Failing after 1437h19m24s
CI Build & Deploy Pipeline / Build Production Web Image (push) Failing after 1437h19m26s
CI Build & Deploy Pipeline / Deploy Production (push) Failing after 1437h19m25s
CI Build & Deploy Pipeline / Build Production API Image (push) Failing after 1437h19m26s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1437h21m16s
CI Build & Deploy Pipeline / Build Staging API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Failing after 1437h51m29s
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Failing after 2m50s
CI Build & Deploy Pipeline / Production Browser E2E (push) Failing after 1437h19m24s
CI Build & Deploy Pipeline / Build Production Web Image (push) Failing after 1437h19m26s
CI Build & Deploy Pipeline / Deploy Production (push) Failing after 1437h19m25s
CI Build & Deploy Pipeline / Build Production API Image (push) Failing after 1437h19m26s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1437h21m16s
CI Build & Deploy Pipeline / Build Staging API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Failing after 1437h51m29s
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
This commit was merged in pull request #520.
This commit is contained in:
+297
-342
@@ -2,8 +2,8 @@
|
||||
* 剪辑计划 API — 对接后端 Edit Plans Schema
|
||||
* 字段名严格匹配后端 API 响应
|
||||
*/
|
||||
import apiClient from "./client";
|
||||
import type { AssetItem } from "./assets";
|
||||
import apiClient from "./client"
|
||||
import type { AssetItem } from "./assets"
|
||||
import type {
|
||||
WatermarkConfig,
|
||||
IntroOutroConfig,
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
ChromaKeyConfig,
|
||||
StickerConfig,
|
||||
CoverConfig,
|
||||
} from "@/pages/editing-planner/types";
|
||||
} from "@/pages/editing-planner/types"
|
||||
|
||||
/* ============================================================
|
||||
* 后端 API 类型(严格匹配后端 Schema)
|
||||
@@ -20,203 +20,203 @@ import type {
|
||||
|
||||
/** 剪辑计划状态枚举 */
|
||||
export type EditPlanStatus =
|
||||
"draft" | "editing" | "rendering" | "completed" | "failed" | "cancelled";
|
||||
"draft" | "editing" | "rendering" | "completed" | "failed" | "cancelled"
|
||||
|
||||
/** 标题配置(对齐后端 title_config) */
|
||||
export interface TitleConfig {
|
||||
ai_auto_select: boolean;
|
||||
content: string;
|
||||
font_preset: string;
|
||||
font_color: string;
|
||||
font_size: number;
|
||||
position: string;
|
||||
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;
|
||||
enabled: boolean
|
||||
position: string
|
||||
font: string
|
||||
color: string
|
||||
size: number
|
||||
animation: string
|
||||
}
|
||||
|
||||
/** BGM 配置 */
|
||||
export interface BgmConfig {
|
||||
enabled: boolean;
|
||||
music_id: string;
|
||||
enabled: boolean
|
||||
music_id: string
|
||||
}
|
||||
|
||||
/** 片段 TTS 配置 */
|
||||
export interface SegmentTtsConfig {
|
||||
mode: string;
|
||||
text: string;
|
||||
voice_id: string;
|
||||
speed: number;
|
||||
pitch: number;
|
||||
volume: number;
|
||||
subtitle_sync: boolean;
|
||||
mode: string
|
||||
text: string
|
||||
voice_id: string
|
||||
speed: number
|
||||
pitch: number
|
||||
volume: number
|
||||
subtitle_sync: boolean
|
||||
}
|
||||
|
||||
/** 片段裁剪配置 */
|
||||
export interface SegmentTrimConfig {
|
||||
start_time: number;
|
||||
end_time: number;
|
||||
start_time: number
|
||||
end_time: number
|
||||
}
|
||||
|
||||
/** 片段转场配置 */
|
||||
export interface SegmentTransitionConfig {
|
||||
type: string;
|
||||
duration: number;
|
||||
type: string
|
||||
duration: number
|
||||
}
|
||||
|
||||
/** 剪辑计划中的单个片段(config 内部 segments 项) */
|
||||
export interface EditPlanSegment {
|
||||
segment_order: number;
|
||||
duration_min: number;
|
||||
duration_max: number;
|
||||
material_type: string;
|
||||
transition?: SegmentTransitionConfig;
|
||||
playback_speed?: number;
|
||||
tts_config?: SegmentTtsConfig;
|
||||
trim_config?: SegmentTrimConfig;
|
||||
segment_order: number
|
||||
duration_min: number
|
||||
duration_max: number
|
||||
material_type: string
|
||||
transition?: SegmentTransitionConfig
|
||||
playback_speed?: number
|
||||
tts_config?: SegmentTtsConfig
|
||||
trim_config?: SegmentTrimConfig
|
||||
}
|
||||
|
||||
/** 剪辑计划 config 完整类型(对齐后端 config JSON 结构) */
|
||||
export interface EditPlanConfig {
|
||||
title_config?: TitleConfig;
|
||||
subtitle_config?: SubtitleConfig;
|
||||
bgm_config?: BgmConfig;
|
||||
estimated_duration?: number;
|
||||
segments?: EditPlanSegment[];
|
||||
watermark_config?: WatermarkConfig;
|
||||
intro_outro_config?: IntroOutroConfig;
|
||||
pip_config?: PipConfig;
|
||||
filter_config?: FilterConfig;
|
||||
green_screen_config?: ChromaKeyConfig;
|
||||
sticker_config?: StickerConfig;
|
||||
cover_config?: CoverConfig;
|
||||
title_config?: TitleConfig
|
||||
subtitle_config?: SubtitleConfig
|
||||
bgm_config?: BgmConfig
|
||||
estimated_duration?: number
|
||||
segments?: EditPlanSegment[]
|
||||
watermark_config?: WatermarkConfig
|
||||
intro_outro_config?: IntroOutroConfig
|
||||
pip_config?: PipConfig
|
||||
filter_config?: FilterConfig
|
||||
green_screen_config?: ChromaKeyConfig
|
||||
sticker_config?: StickerConfig
|
||||
cover_config?: CoverConfig
|
||||
/** 前端扩展:关联的素材 ID 列表 */
|
||||
asset_ids?: string[];
|
||||
asset_ids?: string[]
|
||||
/** 配音 ID */
|
||||
voice_id?: string;
|
||||
voice_id?: string
|
||||
/** 克隆音色档案 ID */
|
||||
voice_clone_profile_id?: string;
|
||||
voice_clone_profile_id?: string
|
||||
/** 自定义配音音频 URL */
|
||||
custom_audio_url?: string;
|
||||
custom_audio_url?: string
|
||||
/** 自定义配音文本 */
|
||||
custom_text?: string;
|
||||
custom_text?: string
|
||||
/** 视频比例 */
|
||||
ratio?: string;
|
||||
ratio?: string
|
||||
/** 视频风格 */
|
||||
style?: string;
|
||||
style?: string
|
||||
/** 目标时长(秒) */
|
||||
duration?: number;
|
||||
duration?: number
|
||||
/** 是否自动生成字幕 */
|
||||
auto_subtitles?: boolean;
|
||||
auto_subtitles?: boolean
|
||||
/** 是否启用 BGM */
|
||||
bgm?: boolean;
|
||||
bgm?: boolean
|
||||
/** 生成数量 */
|
||||
generate_count?: number;
|
||||
generate_count?: number
|
||||
/** 素材模式 */
|
||||
material_mode?: string;
|
||||
material_mode?: string
|
||||
}
|
||||
|
||||
/** 剪辑计划(后端响应) */
|
||||
export interface EditPlan {
|
||||
id: string;
|
||||
template_id: string;
|
||||
name: string;
|
||||
status: EditPlanStatus;
|
||||
total_duration: number;
|
||||
id: string
|
||||
template_id: string
|
||||
name: string
|
||||
status: EditPlanStatus
|
||||
total_duration: number
|
||||
/** 生成视频数量(后端 EditPlanResponse.result_count) */
|
||||
result_count: number;
|
||||
config: EditPlanConfig;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
result_count: number
|
||||
config: EditPlanConfig
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
/** 创建剪辑计划请求(后端要求 template_id + name 必填) */
|
||||
export interface CreateEditPlanRequest {
|
||||
template_id: string;
|
||||
name: string;
|
||||
config?: EditPlanConfig;
|
||||
total_duration?: number;
|
||||
template_id: string
|
||||
name: string
|
||||
config?: EditPlanConfig
|
||||
total_duration?: number
|
||||
/** 来源剪辑计划 ID(从剪辑计划跳转到一键生成时关联) */
|
||||
source_edit_plan_id?: string;
|
||||
source_edit_plan_id?: string
|
||||
}
|
||||
|
||||
/** 更新剪辑计划请求 */
|
||||
export interface UpdateEditPlanRequest {
|
||||
name?: string;
|
||||
config?: EditPlanConfig;
|
||||
total_duration?: number;
|
||||
status?: EditPlanStatus;
|
||||
name?: string
|
||||
config?: EditPlanConfig
|
||||
total_duration?: number
|
||||
status?: EditPlanStatus
|
||||
}
|
||||
|
||||
/** 生成响应 */
|
||||
export interface GenerateResponse {
|
||||
plan_id: string;
|
||||
plan_status: EditPlanStatus;
|
||||
generation_task_id: string;
|
||||
clip_count: number;
|
||||
plan_id: string
|
||||
plan_status: EditPlanStatus
|
||||
generation_task_id: string
|
||||
clip_count: number
|
||||
}
|
||||
|
||||
/** 剪辑计划关联的生成记录(实际是 GenerationTask 对象) */
|
||||
export interface EditPlanGeneration {
|
||||
id: string; // 即 generation_task_id
|
||||
source_edit_plan_id: string;
|
||||
template_id: string;
|
||||
asset_ids: string[];
|
||||
status: EditPlanStatus;
|
||||
progress: number;
|
||||
result_count: number;
|
||||
error_message: string;
|
||||
error_info: Record<string, unknown>;
|
||||
logs: Array<Record<string, unknown>>;
|
||||
retry_count: number;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
id: string // 即 generation_task_id
|
||||
source_edit_plan_id: string
|
||||
template_id: string
|
||||
asset_ids: string[]
|
||||
status: EditPlanStatus
|
||||
progress: number
|
||||
result_count: number
|
||||
error_message: string
|
||||
error_info: Record<string, unknown>
|
||||
logs: Array<Record<string, unknown>>
|
||||
retry_count: number
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
/** 片段生成状态 */
|
||||
export interface ClipStatusItem {
|
||||
clip_id: string;
|
||||
clip_type: string;
|
||||
order: number;
|
||||
status: string;
|
||||
asset_id?: string;
|
||||
text_content?: string;
|
||||
duration?: number;
|
||||
clip_id: string
|
||||
clip_type: string
|
||||
order: number
|
||||
status: string
|
||||
asset_id?: string
|
||||
text_content?: string
|
||||
duration?: number
|
||||
}
|
||||
|
||||
/** 生成状态轮询响应 */
|
||||
export interface GenerationStatusResponse {
|
||||
plan_id: string;
|
||||
plan_status: EditPlanStatus;
|
||||
generation_task_id?: string;
|
||||
clips: ClipStatusItem[];
|
||||
plan_id: string
|
||||
plan_status: EditPlanStatus
|
||||
generation_task_id?: string
|
||||
clips: ClipStatusItem[]
|
||||
}
|
||||
|
||||
/** 生成视频详情(对应后端 GeneratedVideoResponse) */
|
||||
export interface GeneratedVideo {
|
||||
id: string;
|
||||
project_id?: string;
|
||||
generation_task_id?: string;
|
||||
name: string;
|
||||
file_url: string;
|
||||
file_size?: number;
|
||||
duration?: number;
|
||||
thumbnail_url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
fps?: number;
|
||||
status: string;
|
||||
review_status?: string;
|
||||
download_url?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
id: string
|
||||
project_id?: string
|
||||
generation_task_id?: string
|
||||
name: string
|
||||
file_url: string
|
||||
file_size?: number
|
||||
duration?: number
|
||||
thumbnail_url?: string
|
||||
width?: number
|
||||
height?: number
|
||||
fps?: number
|
||||
status: string
|
||||
review_status?: string
|
||||
download_url?: string
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -225,51 +225,51 @@ export interface GeneratedVideo {
|
||||
|
||||
/** AI 推荐请求 */
|
||||
export interface AIRecommendRequest {
|
||||
asset_ids: string[];
|
||||
editing_mode?: string;
|
||||
target_duration?: number;
|
||||
asset_ids: string[]
|
||||
editing_mode?: string
|
||||
target_duration?: number
|
||||
}
|
||||
|
||||
/** AI 推荐单个片段 */
|
||||
export interface AIRecommendClipItem {
|
||||
clip_type: string;
|
||||
order: number;
|
||||
text_content: string;
|
||||
duration: number;
|
||||
transition_effect: string;
|
||||
asset_id: string;
|
||||
start_time: number;
|
||||
config: EditPlanConfig;
|
||||
clip_type: string
|
||||
order: number
|
||||
text_content: string
|
||||
duration: number
|
||||
transition_effect: string
|
||||
asset_id: string
|
||||
start_time: number
|
||||
config: EditPlanConfig
|
||||
}
|
||||
|
||||
/** AI 推荐响应 */
|
||||
export interface AIRecommendResponse {
|
||||
plan_id: string;
|
||||
clips: AIRecommendClipItem[];
|
||||
config: EditPlanConfig;
|
||||
total_duration: number;
|
||||
confidence: number;
|
||||
plan_id: string
|
||||
clips: AIRecommendClipItem[]
|
||||
config: EditPlanConfig
|
||||
total_duration: number
|
||||
confidence: number
|
||||
}
|
||||
|
||||
/** AI 封面生成请求 */
|
||||
export interface GenerateCoverRequest {
|
||||
asset_ids: string[];
|
||||
cover_type?: "ai_frame" | "manual" | "upload" | "ai_regenerate";
|
||||
frame_time?: number;
|
||||
asset_ids: string[]
|
||||
cover_type?: "ai_frame" | "manual" | "upload" | "ai_regenerate"
|
||||
frame_time?: number
|
||||
}
|
||||
|
||||
/** AI 封面生成响应 */
|
||||
export interface GenerateCoverResponse {
|
||||
plan_id: string;
|
||||
cover: CoverResult;
|
||||
plan_id: string
|
||||
cover: CoverResult
|
||||
}
|
||||
|
||||
/** 封面生成结果 */
|
||||
export interface CoverResult {
|
||||
scheme?: string;
|
||||
asset_id?: string;
|
||||
frame_time?: number;
|
||||
thumbnail_url?: string;
|
||||
scheme?: string
|
||||
asset_id?: string
|
||||
frame_time?: number
|
||||
thumbnail_url?: string
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -293,30 +293,30 @@ export interface TransitionEffect {
|
||||
| "wipe_up"
|
||||
| "wipe_down"
|
||||
| "circlecrop"
|
||||
| "rectcrop";
|
||||
duration: number; // 转场时长(秒)
|
||||
| "rectcrop"
|
||||
duration: number // 转场时长(秒)
|
||||
/** 播放速度倍率 */
|
||||
playback_speed?: number;
|
||||
playback_speed?: number
|
||||
}
|
||||
|
||||
/** 素材库资产(UI 层类型,映射自后端 AssetResponse) */
|
||||
export interface MediaAsset {
|
||||
id: string;
|
||||
name: string;
|
||||
type: "video" | "image" | "audio";
|
||||
id: string
|
||||
name: string
|
||||
type: "video" | "image" | "audio"
|
||||
/** 缩略图 URL */
|
||||
thumbnail_url?: string;
|
||||
thumbnail_url?: string
|
||||
/** 时长(秒),仅 video/audio */
|
||||
duration?: number;
|
||||
duration?: number
|
||||
/** 文件大小(字节) */
|
||||
size?: number;
|
||||
size?: number
|
||||
/** 标签 */
|
||||
tags: string[];
|
||||
created_at: string;
|
||||
tags: string[]
|
||||
created_at: string
|
||||
/** 质量分 0-100 */
|
||||
quality_score?: number;
|
||||
quality_score?: number
|
||||
/** 分类状态 */
|
||||
classification_status?: "pending" | "processing" | "completed" | "failed";
|
||||
classification_status?: "pending" | "processing" | "completed" | "failed"
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -325,42 +325,38 @@ export interface MediaAsset {
|
||||
|
||||
/** 剪辑计划列表查询参数 */
|
||||
export interface EditPlanListParams {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
template_id?: string;
|
||||
status?: string;
|
||||
page?: number
|
||||
page_size?: number
|
||||
template_id?: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
/** 剪辑计划列表分页响应 */
|
||||
export interface EditPlanListResponse {
|
||||
items: EditPlan[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
items: EditPlan[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
/** 获取剪辑计划列表(支持分页和筛选) */
|
||||
export async function getEditPlans(
|
||||
params?: EditPlanListParams,
|
||||
): Promise<EditPlanListResponse> {
|
||||
export async function getEditPlans(params?: EditPlanListParams): Promise<EditPlanListResponse> {
|
||||
const response = await apiClient.get<EditPlanListResponse>("/edit-plans", {
|
||||
params,
|
||||
});
|
||||
return response.data;
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 获取单个剪辑计划 */
|
||||
export async function getEditPlan(planId: string): Promise<EditPlan> {
|
||||
const response = await apiClient.get(`/edit-plans/${planId}`);
|
||||
return response.data;
|
||||
const response = await apiClient.get(`/edit-plans/${planId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 创建剪辑计划 */
|
||||
export async function createEditPlan(
|
||||
data: CreateEditPlanRequest,
|
||||
): Promise<EditPlan> {
|
||||
const response = await apiClient.post("/edit-plans", data);
|
||||
return response.data;
|
||||
export async function createEditPlan(data: CreateEditPlanRequest): Promise<EditPlan> {
|
||||
const response = await apiClient.post("/edit-plans", data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 更新剪辑计划 */
|
||||
@@ -368,31 +364,25 @@ export async function updateEditPlan(
|
||||
planId: string,
|
||||
data: UpdateEditPlanRequest,
|
||||
): Promise<EditPlan> {
|
||||
const response = await apiClient.put(`/edit-plans/${planId}`, data);
|
||||
return response.data;
|
||||
const response = await apiClient.put(`/edit-plans/${planId}`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 删除剪辑计划 */
|
||||
export async function deleteEditPlan(planId: string): Promise<void> {
|
||||
await apiClient.delete(`/edit-plans/${planId}`);
|
||||
await apiClient.delete(`/edit-plans/${planId}`)
|
||||
}
|
||||
|
||||
/** 触发剪辑计划生成 */
|
||||
export async function generateEditPlan(
|
||||
planId: string,
|
||||
): Promise<GenerateResponse> {
|
||||
const response = await apiClient.post(`/edit-plans/${planId}/generate`);
|
||||
return response.data;
|
||||
export async function generateEditPlan(planId: string): Promise<GenerateResponse> {
|
||||
const response = await apiClient.post(`/edit-plans/${planId}/generate`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 获取剪辑计划生成状态(轮询用) */
|
||||
export async function getGenerationStatus(
|
||||
planId: string,
|
||||
): Promise<GenerationStatusResponse> {
|
||||
const response = await apiClient.get(
|
||||
`/edit-plans/${planId}/generation-status`,
|
||||
);
|
||||
return response.data;
|
||||
export async function getGenerationStatus(planId: string): Promise<GenerationStatusResponse> {
|
||||
const response = await apiClient.get(`/edit-plans/${planId}/generation-status`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** AI 推荐片段方案 */
|
||||
@@ -400,11 +390,8 @@ export async function aiRecommendClips(
|
||||
planId: string,
|
||||
data: AIRecommendRequest,
|
||||
): Promise<AIRecommendResponse> {
|
||||
const response = await apiClient.post(
|
||||
`/edit-plans/${planId}/ai-recommend`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
const response = await apiClient.post(`/edit-plans/${planId}/ai-recommend`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** AI 生成封面 */
|
||||
@@ -412,32 +399,25 @@ export async function generateCover(
|
||||
planId: string,
|
||||
data: GenerateCoverRequest,
|
||||
): Promise<GenerateCoverResponse> {
|
||||
const response = await apiClient.post(
|
||||
`/edit-plans/${planId}/generate-cover`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
const response = await apiClient.post(`/edit-plans/${planId}/generate-cover`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 获取剪辑计划关联的生成记录 */
|
||||
export async function getEditPlanGenerations(
|
||||
planId: string,
|
||||
): Promise<EditPlanGeneration[]> {
|
||||
const response = await apiClient.get(`/edit-plans/${planId}/generations`);
|
||||
return response.data.items || [];
|
||||
export async function getEditPlanGenerations(planId: string): Promise<EditPlanGeneration[]> {
|
||||
const response = await apiClient.get(`/edit-plans/${planId}/generations`)
|
||||
return response.data.items || []
|
||||
}
|
||||
|
||||
/** 获取生成任务的视频结果列表 */
|
||||
export async function getGenerationTaskResults(
|
||||
taskId: string,
|
||||
): Promise<GeneratedVideo[]> {
|
||||
const response = await apiClient.get(`/generation/tasks/${taskId}/results`);
|
||||
return response.data.items || response.data || [];
|
||||
export async function getGenerationTaskResults(taskId: string): Promise<GeneratedVideo[]> {
|
||||
const response = await apiClient.get(`/generation/tasks/${taskId}/results`)
|
||||
return response.data.items || response.data || []
|
||||
}
|
||||
|
||||
/** 取消生成任务 */
|
||||
export async function cancelGeneration(planId: string): Promise<void> {
|
||||
await apiClient.post(`/edit-plans/${planId}/cancel`);
|
||||
await apiClient.post(`/edit-plans/${planId}/cancel`)
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -445,66 +425,66 @@ export async function cancelGeneration(planId: string): Promise<void> {
|
||||
* ============================================================ */
|
||||
|
||||
/** 片段状态 */
|
||||
export type EditPlanClipStatus = "pending" | "processing" | "ready" | "failed";
|
||||
export type EditPlanClipStatus = "pending" | "processing" | "ready" | "failed"
|
||||
|
||||
/** 剪辑片段(后端响应) */
|
||||
export interface EditPlanClip {
|
||||
id: string;
|
||||
plan_id: string;
|
||||
clip_type: string; // main / intro / outro / overlay / background / b_roll 等
|
||||
order: number;
|
||||
asset_id: string;
|
||||
text_content: string;
|
||||
start_time: number;
|
||||
duration: number;
|
||||
transition_effect: string;
|
||||
transition_duration: number;
|
||||
playback_speed: number;
|
||||
status: EditPlanClipStatus;
|
||||
config: Record<string, unknown>;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
id: string
|
||||
plan_id: string
|
||||
clip_type: string // main / intro / outro / overlay / background / b_roll 等
|
||||
order: number
|
||||
asset_id: string
|
||||
text_content: string
|
||||
start_time: number
|
||||
duration: number
|
||||
transition_effect: string
|
||||
transition_duration: number
|
||||
playback_speed: number
|
||||
status: EditPlanClipStatus
|
||||
config: Record<string, unknown>
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
/** 创建片段请求 */
|
||||
export interface CreateEditPlanClipRequest {
|
||||
clip_type: string;
|
||||
order: number;
|
||||
asset_id?: string;
|
||||
text_content?: string;
|
||||
start_time?: number;
|
||||
duration?: number;
|
||||
transition_effect?: string;
|
||||
transition_duration?: number;
|
||||
playback_speed?: number;
|
||||
config?: Record<string, unknown>;
|
||||
clip_type: string
|
||||
order: number
|
||||
asset_id?: string
|
||||
text_content?: string
|
||||
start_time?: number
|
||||
duration?: number
|
||||
transition_effect?: string
|
||||
transition_duration?: number
|
||||
playback_speed?: number
|
||||
config?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/** 更新片段请求 */
|
||||
export interface UpdateEditPlanClipRequest {
|
||||
clip_type?: string;
|
||||
order?: number;
|
||||
asset_id?: string;
|
||||
text_content?: string;
|
||||
start_time?: number;
|
||||
duration?: number;
|
||||
transition_effect?: string;
|
||||
transition_duration?: number;
|
||||
playback_speed?: number;
|
||||
config?: Record<string, unknown>;
|
||||
clip_type?: string
|
||||
order?: number
|
||||
asset_id?: string
|
||||
text_content?: string
|
||||
start_time?: number
|
||||
duration?: number
|
||||
transition_effect?: string
|
||||
transition_duration?: number
|
||||
playback_speed?: number
|
||||
config?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/** 片段列表响应 */
|
||||
export interface EditPlanClipListResponse {
|
||||
items: EditPlanClip[];
|
||||
total: number;
|
||||
items: EditPlanClip[]
|
||||
total: number
|
||||
}
|
||||
|
||||
/** 片段列表查询参数 */
|
||||
export interface EditPlanClipListParams {
|
||||
status?: string;
|
||||
skip?: number;
|
||||
limit?: number;
|
||||
status?: string
|
||||
skip?: number
|
||||
limit?: number
|
||||
}
|
||||
|
||||
/** 获取片段列表 */
|
||||
@@ -512,22 +492,16 @@ export async function getEditPlanClips(
|
||||
planId: string,
|
||||
params?: EditPlanClipListParams,
|
||||
): Promise<EditPlanClipListResponse> {
|
||||
const response = await apiClient.get<EditPlanClipListResponse>(
|
||||
`/edit-plans/${planId}/clips`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
const response = await apiClient.get<EditPlanClipListResponse>(`/edit-plans/${planId}/clips`, {
|
||||
params,
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 获取单个片段详情 */
|
||||
export async function getEditPlanClip(
|
||||
planId: string,
|
||||
clipId: string,
|
||||
): Promise<EditPlanClip> {
|
||||
const response = await apiClient.get<EditPlanClip>(
|
||||
`/edit-plans/${planId}/clips/${clipId}`,
|
||||
);
|
||||
return response.data;
|
||||
export async function getEditPlanClip(planId: string, clipId: string): Promise<EditPlanClip> {
|
||||
const response = await apiClient.get<EditPlanClip>(`/edit-plans/${planId}/clips/${clipId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 创建片段 */
|
||||
@@ -535,11 +509,8 @@ export async function createEditPlanClip(
|
||||
planId: string,
|
||||
data: CreateEditPlanClipRequest,
|
||||
): Promise<EditPlanClip> {
|
||||
const response = await apiClient.post<EditPlanClip>(
|
||||
`/edit-plans/${planId}/clips`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
const response = await apiClient.post<EditPlanClip>(`/edit-plans/${planId}/clips`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 更新片段 */
|
||||
@@ -548,19 +519,13 @@ export async function updateEditPlanClip(
|
||||
clipId: string,
|
||||
data: UpdateEditPlanClipRequest,
|
||||
): Promise<EditPlanClip> {
|
||||
const response = await apiClient.put<EditPlanClip>(
|
||||
`/edit-plans/${planId}/clips/${clipId}`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
const response = await apiClient.put<EditPlanClip>(`/edit-plans/${planId}/clips/${clipId}`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 删除片段 */
|
||||
export async function deleteEditPlanClip(
|
||||
planId: string,
|
||||
clipId: string,
|
||||
): Promise<void> {
|
||||
await apiClient.delete(`/edit-plans/${planId}/clips/${clipId}`);
|
||||
export async function deleteEditPlanClip(planId: string, clipId: string): Promise<void> {
|
||||
await apiClient.delete(`/edit-plans/${planId}/clips/${clipId}`)
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -569,30 +534,30 @@ export async function deleteEditPlanClip(
|
||||
|
||||
/** 重排序条目 */
|
||||
export interface ClipReorderItem {
|
||||
clip_id: string;
|
||||
new_order: number;
|
||||
clip_id: string
|
||||
new_order: number
|
||||
}
|
||||
|
||||
/** 重排序响应 */
|
||||
export interface ClipReorderResponse {
|
||||
success: boolean;
|
||||
updated_count: number;
|
||||
message: string;
|
||||
success: boolean
|
||||
updated_count: number
|
||||
message: string
|
||||
}
|
||||
|
||||
/** 批量删除响应 */
|
||||
export interface ClipBatchDeleteResponse {
|
||||
success: boolean;
|
||||
deleted_count: number;
|
||||
message: string;
|
||||
success: boolean
|
||||
deleted_count: number
|
||||
message: string
|
||||
}
|
||||
|
||||
/** 从素材批量创建响应 */
|
||||
export interface ClipsFromAssetsResponse {
|
||||
success: boolean;
|
||||
created_count: number;
|
||||
message: string;
|
||||
clip_ids: string[];
|
||||
success: boolean
|
||||
created_count: number
|
||||
message: string
|
||||
clip_ids: string[]
|
||||
}
|
||||
|
||||
/** 片段重排序(拖拽排序后一次性提交) */
|
||||
@@ -603,8 +568,8 @@ export async function reorderEditPlanClips(
|
||||
const response = await apiClient.post<ClipReorderResponse>(
|
||||
`/edit-plans/${planId}/clips/reorder`,
|
||||
{ items },
|
||||
);
|
||||
return response.data;
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 批量删除片段 */
|
||||
@@ -615,8 +580,8 @@ export async function batchDeleteEditPlanClips(
|
||||
const response = await apiClient.post<ClipBatchDeleteResponse>(
|
||||
`/edit-plans/${planId}/clips/batch-delete`,
|
||||
{ clip_ids: clipIds },
|
||||
);
|
||||
return response.data;
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 从素材批量创建片段(追加到时间线末尾) */
|
||||
@@ -628,8 +593,8 @@ export async function createClipsFromAssets(
|
||||
const response = await apiClient.post<ClipsFromAssetsResponse>(
|
||||
`/edit-plans/${planId}/clips/from-assets`,
|
||||
{ asset_ids: assetIds, clip_type: clipType },
|
||||
);
|
||||
return response.data;
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -638,40 +603,32 @@ export async function createClipsFromAssets(
|
||||
|
||||
/** 复制计划请求 */
|
||||
export interface CopyEditPlanRequest {
|
||||
name?: string;
|
||||
project_id?: string;
|
||||
name?: string
|
||||
project_id?: string
|
||||
}
|
||||
|
||||
/** 复制剪辑计划(含所有片段配置) */
|
||||
export async function copyEditPlan(
|
||||
planId: string,
|
||||
data?: CopyEditPlanRequest,
|
||||
): Promise<EditPlan> {
|
||||
const response = await apiClient.post<EditPlan>(
|
||||
`/edit-plans/${planId}/copy`,
|
||||
data || {},
|
||||
);
|
||||
return response.data;
|
||||
export async function copyEditPlan(planId: string, data?: CopyEditPlanRequest): Promise<EditPlan> {
|
||||
const response = await apiClient.post<EditPlan>(`/edit-plans/${planId}/copy`, data || {})
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取素材库列表 — 调用 GET /api/v1/assets?library_id=xxx
|
||||
* 将后端 AssetResponse 映射为前端 MediaAsset 类型
|
||||
*/
|
||||
export async function getMediaAssets(
|
||||
libraryId?: string,
|
||||
): Promise<MediaAsset[]> {
|
||||
export async function getMediaAssets(libraryId?: string): Promise<MediaAsset[]> {
|
||||
const response = await apiClient.get("/assets", {
|
||||
params: libraryId ? { library_id: libraryId } : undefined,
|
||||
});
|
||||
const items: AssetItem[] = response.data.items || [];
|
||||
return items.map(mapAssetToMediaAsset);
|
||||
})
|
||||
const items: AssetItem[] = response.data.items || []
|
||||
return items.map(mapAssetToMediaAsset)
|
||||
}
|
||||
|
||||
/** 获取单个素材 */
|
||||
export async function getMediaAsset(id: string): Promise<MediaAsset> {
|
||||
const response = await apiClient.get(`/assets/${id}`);
|
||||
return mapAssetToMediaAsset(response.data);
|
||||
const response = await apiClient.get(`/assets/${id}`)
|
||||
return mapAssetToMediaAsset(response.data)
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -679,17 +636,15 @@ export async function getMediaAsset(id: string): Promise<MediaAsset> {
|
||||
* ============================================================ */
|
||||
|
||||
function inferMediaType(mimeType: string): "video" | "image" | "audio" {
|
||||
if (mimeType.startsWith("video/")) return "video";
|
||||
if (mimeType.startsWith("image/")) return "image";
|
||||
return "audio";
|
||||
if (mimeType.startsWith("video/")) return "video"
|
||||
if (mimeType.startsWith("image/")) return "image"
|
||||
return "audio"
|
||||
}
|
||||
|
||||
function mapAssetToMediaAsset(asset: AssetItem): MediaAsset {
|
||||
// 优先取顶层 duration,其次从 metadata 回退
|
||||
const metaDuration =
|
||||
typeof asset.metadata?.duration === "number"
|
||||
? asset.metadata.duration
|
||||
: undefined;
|
||||
typeof asset.metadata?.duration === "number" ? asset.metadata.duration : undefined
|
||||
return {
|
||||
id: asset.id,
|
||||
name: asset.name,
|
||||
@@ -701,7 +656,7 @@ function mapAssetToMediaAsset(asset: AssetItem): MediaAsset {
|
||||
created_at: asset.created_at ?? "",
|
||||
quality_score: asset.quality_score ?? undefined,
|
||||
classification_status: asset.classification_status ?? undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -710,9 +665,9 @@ function mapAssetToMediaAsset(asset: AssetItem): MediaAsset {
|
||||
|
||||
/** 转场效果选项(14 种预设) */
|
||||
export const TRANSITION_OPTIONS: {
|
||||
value: TransitionEffect["type"];
|
||||
label: string;
|
||||
icon: string;
|
||||
value: TransitionEffect["type"]
|
||||
label: string
|
||||
icon: string
|
||||
}[] = [
|
||||
{ value: "none", label: "无转场", icon: "⊘" },
|
||||
{ value: "cut", label: "硬切", icon: "✂" },
|
||||
@@ -729,7 +684,7 @@ export const TRANSITION_OPTIONS: {
|
||||
{ value: "wipe_down", label: "下擦除", icon: "▾̄" },
|
||||
{ value: "circlecrop", label: "圆形裁切", icon: "●" },
|
||||
{ value: "rectcrop", label: "矩形裁切", icon: "■" },
|
||||
];
|
||||
]
|
||||
|
||||
/** 素材类型标签 */
|
||||
export const MATERIAL_TYPE_LABELS: Record<string, string> = {
|
||||
@@ -737,7 +692,7 @@ export const MATERIAL_TYPE_LABELS: Record<string, string> = {
|
||||
image: "图片",
|
||||
audio: "音频",
|
||||
voiceover: "配音",
|
||||
};
|
||||
}
|
||||
|
||||
/** 素材类型图标 */
|
||||
export const MATERIAL_TYPE_ICONS: Record<string, string> = {
|
||||
@@ -745,7 +700,7 @@ export const MATERIAL_TYPE_ICONS: Record<string, string> = {
|
||||
image: "🖼️",
|
||||
audio: "🎵",
|
||||
voiceover: "🎙️",
|
||||
};
|
||||
}
|
||||
|
||||
/** 计划状态标签 */
|
||||
export const PLAN_STATUS_LABELS: Record<EditPlanStatus, string> = {
|
||||
@@ -755,17 +710,17 @@ export const PLAN_STATUS_LABELS: Record<EditPlanStatus, string> = {
|
||||
completed: "已完成",
|
||||
failed: "失败",
|
||||
cancelled: "已取消",
|
||||
};
|
||||
}
|
||||
|
||||
/** 质量分筛选选项 */
|
||||
export const QUALITY_OPTIONS: {
|
||||
value: string;
|
||||
label: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
value: string
|
||||
label: string
|
||||
min?: number
|
||||
max?: number
|
||||
}[] = [
|
||||
{ value: "all", label: "全部质量" },
|
||||
{ value: "high", label: "高质量 (80-100)", min: 80, max: 100 },
|
||||
{ value: "medium", label: "中质量 (50-79)", min: 50, max: 79 },
|
||||
{ value: "low", label: "低质量 (0-49)", min: 0, max: 49 },
|
||||
];
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user