/** * 素材相关 API * Phase 1 重构:去掉 project_id,素材直接归属用户 */ import apiClient from "./client"; import { getOrCreateDefaultProject } from "./projects"; /** 素材条目 */ export interface AssetItem { id: string; library_id: string; name: string; storage_key: string; mime_type: string; metadata: Record; file_size?: number; file_url?: string; thumbnail_url?: string; status?: string; classification_status?: string | null; quality_score?: number | null; created_at?: string; } /** 素材库 */ export interface AssetLibraryItem { id: string; name: string; kind: "video" | "voice" | "image"; asset_count?: number; total_size?: number; created_at?: string; } /** 入库任务 */ export interface IngestJob { id: string; library_id: string; storage_key: string; status: "pending" | "processing" | "completed" | "failed"; error_message: string; result_asset_id: string; } /** 分类任务 */ export interface ClassificationJob { id: string; asset_id: string; status: "pending" | "processing" | "completed" | "failed"; classification: string; confidence: number; error_message: string; } /** 素材诊断信息 */ export interface AssetDiagnosis { readiness_score: number; readiness_label: string; total_assets: number; ready_assets: number; video_assets: number; image_assets: number; voice_assets: number; total_duration_seconds: number; estimated_video_count: number; used_assets: number; unused_assets: number; pending_review_assets: number; smart_views: Array<{ key: string; label: string; count: number; description: string; }>; gaps: Array<{ key: string; severity: "critical" | "warning" | "info"; message: string; recommendation: string; }>; } // ─── 素材诊断 ────────────────────────────────────────────── /** 获取素材诊断信息(可选 asset_id 查单素材,否则全局诊断) */ export const getAssetDiagnosis = async ( assetId?: string, ): Promise => { const params: Record = {}; if (assetId) params.asset_id = assetId; const response = await apiClient.get("/asset-diagnosis", { params }); return response.data; }; // ─── 素材库 ──────────────────────────────────────────────── /** 获取当前用户的所有素材库 */ export const getAssetLibraries = async (): Promise => { const response = await apiClient.get("/asset-libraries"); return response.data.items || []; }; /** 创建素材库(自动获取或创建默认项目以提供 project_id) */ export const createAssetLibrary = async (data: { name: string; kind: "video" | "voice" | "image"; }): Promise => { // 后端要求 project_id,前端自动管理默认项目 const project = await getOrCreateDefaultProject(); const response = await apiClient.post("/asset-libraries", { project_id: project.id, ...data, }); return response.data; }; /** 删除素材库 */ export const deleteAssetLibrary = async (libraryId: string): Promise => { await apiClient.delete(`/asset-libraries/${libraryId}`); }; // ─── 素材 ────────────────────────────────────────────────── /** 获取素材库下的所有素材 */ export const getAssets = async (libraryId: string): Promise => { const response = await apiClient.get("/assets", { params: { library_id: libraryId }, }); return response.data.items || []; }; /** 更新素材审核状态 */ export const updateAssetReviewStatus = async ( assetId: string, reviewStatus: "pending_review" | "approved" | "rejected", ): Promise => { const response = await apiClient.patch(`/assets/${assetId}/review`, { review_status: reviewStatus, }); return response.data; }; /** 删除素材 */ export const deleteAsset = async (assetId: string): Promise => { await apiClient.delete(`/assets/${assetId}`); }; // ─── 上传 ────────────────────────────────────────────────── /** 表单上传素材(小文件) */ export const uploadAsset = async ( formData: FormData, ): Promise<{ storage_key: string; ingest_job_id: string; url: string }> => { const response = await apiClient.post("/upload", formData, { headers: { "Content-Type": "multipart/form-data" }, timeout: 30 * 60 * 1000, }); return response.data; }; /** 预签名直传准备 */ export const prepareDirectUpload = async (data: { project_id: string; library_id: string; filename: string; content_type: string; file_size: number; }): Promise<{ upload_url: string; method: string; storage_key: string; expires_at: string; fields: Record; max_size_bytes: number; }> => { const response = await apiClient.post("/upload/direct/prepare", data); return response.data; }; /** 直传完成确认 */ export const completeDirectUpload = async (data: { project_id: string; library_id: string; storage_key: string; }): Promise<{ storage_key: string; ingest_job_id: string }> => { const response = await apiClient.post("/upload/direct/complete", data); return response.data; }; /** 直传上传(大文件推荐) */ export const uploadAssetDirect = async (data: { file: File; library_id: string; }): Promise<{ storage_key: string; ingest_job_id: string }> => { // 后端要求 project_id,前端自动获取默认项目 const project = await getOrCreateDefaultProject(); const prepared = await prepareDirectUpload({ project_id: project.id, library_id: data.library_id, filename: data.file.name, content_type: data.file.type || "application/octet-stream", file_size: data.file.size, }); const directForm = new FormData(); Object.entries(prepared.fields).forEach(([key, value]) => directForm.append(key, value), ); directForm.append("file", data.file); const uploadResponse = await fetch(prepared.upload_url, { method: prepared.method, body: directForm, }); if (!uploadResponse.ok) { throw new Error(`OSS direct upload failed: ${uploadResponse.status}`); } return completeDirectUpload({ project_id: project.id, library_id: data.library_id, storage_key: prepared.storage_key, }); }; // ─── 入库 / 分类任务 ─────────────────────────────────────── /** 查询入库任务状态 */ export const getIngestJob = async (jobId: string): Promise => { const response = await apiClient.get(`/ingest-jobs/${jobId}`); return response.data; }; /** 提交素材分类任务 */ export const submitClassificationJob = async (data: { asset_id: string; }): Promise => { const response = await apiClient.post("/classification-jobs", data); return response.data; }; /** 查询分类任务状态 */ export const getClassificationJob = async ( jobId: string, ): Promise => { const response = await apiClient.get(`/classification-jobs/${jobId}`); return response.data; };