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

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:
2026-07-18 18:08:19 +08:00
committed by auto-approve-bot
parent 1ee779a566
commit 0fcb77b991
145 changed files with 10310 additions and 12869 deletions
+297 -342
View File
@@ -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 },
];
]