import { http } from "@/services/api/request"; import { normalizeAssetCategory, type AssetCategory } from "@/lib/asset-category"; import type { GenerationTask } from "@/services/api/task-center"; export type Project = { id: string; userId: string; name: string; type: string; aspectRatio: string; sourceType: string; description: string; coverResourceId?: string; stylePresetId: string; styleProfileJson?: string; defaultImageModel?: string; defaultVideoModel?: string; status: "active" | "archived" | string; revision: number; createdAt: string; updatedAt: string; }; export type ProjectCanvas = { id: string; projectId?: string; title: string; createdAt: string; updatedAt: string; }; export type CanvasUnitLink = { id: string; projectId: string; canvasId: string; unitId: string; role: string; createdAt: string; }; export type ProjectUnit = { id: string; projectId: string; kind: "chapter" | "episode" | string; title: string; sourceText: string; wordCount: number; status: "draft" | "ready" | "completed" | string; position: number; createdAt: string; updatedAt: string; }; export type ProjectAsset = { id: string; title: string; mediaType: string; category: AssetCategory; status: string; primaryVersionId?: string; versionCount: number; usages: string[]; folderId?: string; position: number; storageKey?: string; durationMs?: number; previewText?: string; updatedAt: string; character?: CharacterCardSummary; source?: string; }; export type ProjectAssetFolder = { id: string; projectId: string; parentId?: string; name: string; style: "glass" | "stacked" | "midnight" | "paper" | "cinema" | "compact" | string; theme: "aurora" | "obsidian" | "ember" | "pearl" | string; position: number; createdAt: string; updatedAt: string; }; export type CharacterRepresentation = { id: string; resourceId: string; mediaType: string; role: "primary" | "front" | "side" | "back" | "turnaround_sheet" | "expression_sheet" | string; }; export type VoiceProfile = { id: string; name: string; provider: string; voiceKey: string; language: string; timbre: string; sampleResourceId?: string; compatibleModels: string[]; status: string; }; export type CharacterCardSummary = { versionId: string; version: number; definition: Record; representations: CharacterRepresentation[]; voice?: { profile: VoiceProfile; instructions: string }; visualStatus: "missing" | "partial" | "ready" | string; voiceStatus: "missing" | "ready" | "unavailable" | string; }; export type ProjectCharacterDetail = { asset: ProjectAsset; character: CharacterCardSummary; }; export type ProjectAssetCandidate = { id: string; projectId: string; unitId?: string; shotId?: string; name: string; category: AssetCategory; status: "pending_confirmation" | "confirmed" | "ignored" | string; detailsJson: string; resolvedAssetId?: string; createdAt: string; updatedAt: string; }; export type ProjectShot = { id: string; projectId: string; unitId?: string; currentRevisionId?: string; title: string; description: string; position: number; durationMs: number; status: string; createdAt: string; updatedAt: string; }; export type ShotRevision = { id: string; shotId: string; version: number; plotDescription: string; action: string; dialogue: string; shotSize: string; cameraAngle: string; cameraMovement: string; durationMs: number; imagePrompt: string; videoPrompt: string; negativePrompt: string; continuityNotes: string; actionBeatsJson: string; createdBy?: string; createdAt: string; }; export type ShotArtifact = { id: string; projectId: string; unitId?: string; shotId: string; revisionId?: string; taskId?: string; type: "storyboard" | "action_board" | "start_frame" | "end_frame" | "video" | "audio" | "subtitle" | "delivery" | string; version: number; resourceId?: string; status: "pending" | "running" | "ready" | "failed" | "stale" | string; selected: boolean; metadataJson: string; createdAt: string; updatedAt: string; }; export type ShotRevisionInput = { plotDescription: string; action?: string; dialogue?: string; shotSize?: string; cameraAngle?: string; cameraMovement?: string; durationMs?: number; imagePrompt?: string; videoPrompt?: string; negativePrompt?: string; continuityNotes?: string; actionBeats?: Array>; }; export type ShotAssetReference = { id: string; shotId: string; assetVersionId: string; role: "reference" | "start_frame" | "end_frame" | "keyframe" | "storyboard" | "output" | string; status: string; createdAt: string; asset?: ProjectAsset; referencedVersion?: { id: string; assetId: string; version: number; representations: CharacterRepresentation[]; }; }; export type WorkflowStep = { id: string; workflowInstanceId: string; stepKey: string; name: string; position: number; status: "pending" | "ready" | "running" | "review" | "completed" | "failed" | "skipped" | string; error?: string; updatedAt: string; }; export type ProjectWorkflow = { instance: { id: string; projectId: string; unitId?: string; scope: string; status: string; revision: number }; steps: WorkflowStep[]; }; export type ProjectSummary = { project: Project; canvasCount: number; assetCount: number; unitCount: number; completedUnitCount: number; }; export type ProjectListPage = { projects: ProjectSummary[]; page: number; pageSize: number; total: number; hasMore: boolean; }; export type ProjectDetail = { project: Project; units: ProjectUnit[]; canvases: ProjectCanvas[]; canvasUnitLinks: CanvasUnitLink[]; unitCanvasCounts?: Record; assets: ProjectAsset[]; assetFolders: ProjectAssetFolder[]; workflows: ProjectWorkflow[]; shots: ProjectShot[]; shotRevisions: ShotRevision[]; shotArtifacts: ShotArtifact[]; shotReferences: ShotAssetReference[]; assetCandidates: ProjectAssetCandidate[]; tasks: GenerationTask[]; }; export type ProjectCore = { project: Project }; export type ProjectOverviewMetrics = { unitCount: number; completedUnitCount: number; totalWordCount: number; unitsWithoutText: number; unitsWithoutShots: number; canvasCount: number; assetCount: number; shotCount: number; pendingCandidateCount: number; readyStoryboardCount: number; readyPrevizCount: number; readyVideoCount: number; renderSucceededCount: number; staleArtifactCount: number; }; export type ProjectOverviewUnit = { unit: ProjectUnit; shotCount: number; candidateCount: number; canvasCount: number; }; export type ProjectOverview = { metrics: ProjectOverviewMetrics; units: ProjectOverviewUnit[] }; export type ProjectUnitWorkspace = { unit: ProjectUnit; workflows: ProjectWorkflow[]; shots: ProjectShot[]; shotRevisions: ShotRevision[]; shotArtifacts: ShotArtifact[]; shotReferences: ShotAssetReference[]; assetCandidates: ProjectAssetCandidate[]; assets: ProjectAsset[]; tasks: GenerationTask[]; }; export type ProjectCanvasPage = { canvases: ProjectCanvas[]; canvasUnitLinks: CanvasUnitLink[]; page: number; pageSize: number; total: number; hasMore: boolean; }; export type ProjectAssetPage = { assets: ProjectAsset[]; categoryCounts: Record; folderCounts: Record; page: number; pageSize: number; total: number; hasMore: boolean; }; export type ProjectAssetCandidatePage = { candidates: ProjectAssetCandidate[]; page: number; pageSize: number; total: number; hasMore: boolean; }; export function listProjects(): Promise<{ projects: ProjectSummary[] }>; export function listProjects(params: { page: number; pageSize: number }): Promise; export function listProjects(params?: { page: number; pageSize: number }) { return http.get<{ projects: ProjectSummary[] } | ProjectListPage>("/projects", params ? { params: { page: params.page, pageSize: params.pageSize } } : undefined); } export function getProject(id: string) { return http.get(`/projects/${encodeURIComponent(id)}`).then(normalizeProjectDetail); } export function getProjectCore(id: string) { return http.get(`/projects/${encodeURIComponent(id)}/core`); } export function listProjectUnits(projectId: string) { return http.get<{ units: ProjectUnit[]; canvasCounts: Record }>(`/projects/${encodeURIComponent(projectId)}/units`); } export function getProjectOverview(projectId: string) { return http.get(`/projects/${encodeURIComponent(projectId)}/overview`); } export function getProjectUnitWorkspace(projectId: string, unitId: string) { return http.get(`/projects/${encodeURIComponent(projectId)}/units/${encodeURIComponent(unitId)}/workspace`); } export function listProjectCanvases(projectId: string, page = 1, pageSize = 40) { return http.get(`/projects/${encodeURIComponent(projectId)}/canvases`, { params: { page, pageSize } }); } export function listProjectAssetsPage(projectId: string, options: { page?: number; pageSize?: number; category?: string; mediaType?: string; status?: string; folderId?: string; query?: string } = {}) { return http.get(`/projects/${encodeURIComponent(projectId)}/assets`, { params: { page: options.page || 1, pageSize: options.pageSize || 40, category: options.category || undefined, mediaType: options.mediaType || undefined, status: options.status || undefined, folderId: options.folderId, q: options.query || undefined, } }).then((page) => ({ ...page, assets: page.assets.map(normalizeProjectAsset) })); } export function listProjectAssets(projectId: string) { return http.get<{ assets: ProjectAsset[] }>(`/projects/${encodeURIComponent(projectId)}/assets`).then((result) => ({ assets: result.assets.map(normalizeProjectAsset) })); } export function listProjectAssetCandidates(projectId: string, options: { page?: number; pageSize?: number; unitId?: string; status?: string; category?: string; query?: string } = {}) { return http.get(`/projects/${encodeURIComponent(projectId)}/asset-candidates`, { params: { page: options.page || 1, pageSize: options.pageSize || 100, unitId: options.unitId || undefined, status: options.status || undefined, category: options.category || undefined, q: options.query || undefined, } }).then((page) => ({ ...page, candidates: page.candidates.map(normalizeProjectAssetCandidate) })); } function normalizeProjectAsset(asset: ProjectAsset): ProjectAsset { return { ...asset, category: normalizeAssetCategory(asset.category), usages: Array.isArray(asset.usages) ? asset.usages : [] }; } function normalizeProjectAssetCandidate(candidate: ProjectAssetCandidate): ProjectAssetCandidate { return { ...candidate, category: normalizeAssetCategory(candidate.category) }; } function normalizeProjectDetail(detail: ProjectDetail): ProjectDetail { const workflows = Array.isArray(detail.workflows) ? detail.workflows.map((workflow) => ({ ...workflow, steps: Array.isArray(workflow.steps) ? workflow.steps : [], })) : []; const assets = Array.isArray(detail.assets) ? detail.assets.map((asset) => ({ ...normalizeProjectAsset(asset), ...(asset.character ? { character: { ...asset.character, representations: Array.isArray(asset.character.representations) ? asset.character.representations : [], }, } : {}), })) : []; return { ...detail, units: Array.isArray(detail.units) ? detail.units : [], canvases: Array.isArray(detail.canvases) ? detail.canvases : [], canvasUnitLinks: Array.isArray(detail.canvasUnitLinks) ? detail.canvasUnitLinks : [], unitCanvasCounts: detail.unitCanvasCounts || {}, assets, assetFolders: Array.isArray(detail.assetFolders) ? detail.assetFolders : [], workflows, shots: Array.isArray(detail.shots) ? detail.shots : [], shotRevisions: Array.isArray(detail.shotRevisions) ? detail.shotRevisions : [], shotArtifacts: Array.isArray(detail.shotArtifacts) ? detail.shotArtifacts : [], shotReferences: Array.isArray(detail.shotReferences) ? detail.shotReferences : [], assetCandidates: Array.isArray(detail.assetCandidates) ? detail.assetCandidates.map(normalizeProjectAssetCandidate) : [], tasks: Array.isArray(detail.tasks) ? detail.tasks : [], }; } export function createProject(input: { name: string; type: string; aspectRatio: string; sourceType: string; description?: string; stylePresetId?: string; styleProfileJson?: string; defaultImageModel?: string; defaultVideoModel?: string }) { return http.post<{ project: Project }>("/projects", input); } export function updateProject(projectId: string, input: Partial>) { return http.patch<{ project: Project }>(`/projects/${encodeURIComponent(projectId)}`, input); } export function deleteProject(projectId: string) { return http.delete<{ id: string }>(`/projects/${encodeURIComponent(projectId)}`); } export function createProjectUnit(projectId: string, input: { kind: string; title: string; sourceText?: string; position?: number }) { return http.post<{ unit: ProjectUnit }>(`/projects/${encodeURIComponent(projectId)}/units`, input); } export function getProjectUnit(projectId: string, unitId: string) { return http.get<{ unit: ProjectUnit }>(`/projects/${encodeURIComponent(projectId)}/units/${encodeURIComponent(unitId)}`); } export function importProjectUnits(projectId: string, units: Array<{ kind: string; title: string; sourceText?: string }>) { return http.post<{ units: ProjectUnit[] }>(`/projects/${encodeURIComponent(projectId)}/units/import`, { units }); } export function reorderProjectUnits(projectId: string, unitIds: string[]) { return http.patch<{ unitIds: string[] }>(`/projects/${encodeURIComponent(projectId)}/units/reorder`, { unitIds }); } export function updateProjectUnit(projectId: string, unitId: string, input: { title?: string; sourceText: string; status?: ProjectUnit["status"] }) { return http.patch<{ unit: ProjectUnit }>(`/projects/${encodeURIComponent(projectId)}/units/${encodeURIComponent(unitId)}`, input); } export function deleteProjectUnit(projectId: string, unitId: string) { return http.delete<{ id: string }>(`/projects/${encodeURIComponent(projectId)}/units/${encodeURIComponent(unitId)}`); } export function linkCanvasUnit(projectId: string, input: { canvasId: string; unitId: string; role?: string }) { return http.post<{ link: { id: string; projectId: string; canvasId: string; unitId: string; role: string } }>(`/projects/${encodeURIComponent(projectId)}/canvas-links`, input); } export function unlinkCanvasUnit(projectId: string, canvasId: string, unitId: string) { return http.delete<{ canvasId: string; unitId: string }>(`/projects/${encodeURIComponent(projectId)}/canvas-links/${encodeURIComponent(canvasId)}/units/${encodeURIComponent(unitId)}`); } export function unlinkCanvasProject(projectId: string, canvasId: string) { return http.delete<{ canvasId: string }>(`/projects/${encodeURIComponent(projectId)}/canvases/${encodeURIComponent(canvasId)}`); } export function linkProjectAsset(projectId: string, input: { assetId: string; category: AssetCategory; folderId?: string; title?: string; source?: "uploaded" | "canvas" }, signal?: AbortSignal) { return http.post<{ asset: ProjectAsset }>(`/projects/${encodeURIComponent(projectId)}/assets`, input, { signal }); } export function unlinkProjectAsset(projectId: string, assetId: string) { return http.delete<{ id: string }>(`/projects/${encodeURIComponent(projectId)}/assets/${encodeURIComponent(assetId)}`); } export function updateProjectAssetCategory(projectId: string, assetId: string, category: AssetCategory, signal?: AbortSignal) { return http.patch<{ asset: ProjectAsset }>(`/projects/${encodeURIComponent(projectId)}/assets/${encodeURIComponent(assetId)}`, { category }, { signal }); } export function moveProjectAsset(projectId: string, assetId: string, folderId: string, signal?: AbortSignal) { return http.patch<{ asset: ProjectAsset }>(`/projects/${encodeURIComponent(projectId)}/assets/${encodeURIComponent(assetId)}`, { folderId }, { signal }); } export function listProjectAssetFolders(projectId: string, signal?: AbortSignal) { return http.get<{ folders: ProjectAssetFolder[] }>(`/projects/${encodeURIComponent(projectId)}/asset-folders`, { signal }); } export function createProjectAssetFolder(projectId: string, input: { name: string; parentId?: string; style?: ProjectAssetFolder["style"]; theme?: ProjectAssetFolder["theme"] }) { return http.post<{ folder: ProjectAssetFolder }>(`/projects/${encodeURIComponent(projectId)}/asset-folders`, input); } export function updateProjectAssetFolder(projectId: string, folderId: string, input: { name?: string; parentId?: string; style?: ProjectAssetFolder["style"]; theme?: ProjectAssetFolder["theme"] }) { return http.patch<{ folder: ProjectAssetFolder }>(`/projects/${encodeURIComponent(projectId)}/asset-folders/${encodeURIComponent(folderId)}`, input); } export function deleteProjectAssetFolder(projectId: string, folderId: string) { return http.delete<{ id: string }>(`/projects/${encodeURIComponent(projectId)}/asset-folders/${encodeURIComponent(folderId)}`); } export function createProjectAssetVersion(projectId: string, assetId: string, input: { prompt?: string; definitionJson?: string; note?: string }) { return http.post<{ version: { id: string; assetId: string; version: number; status: string } }>(`/projects/${encodeURIComponent(projectId)}/assets/${encodeURIComponent(assetId)}/versions`, input); } export function listVoiceProfiles() { return http.get<{ profiles: VoiceProfile[] }>("/voice-profiles"); } export function createProjectCharacter(projectId: string, input: { name: string; definition?: Record }) { return http.post(`/projects/${encodeURIComponent(projectId)}/characters`, input); } export function getProjectCharacter(projectId: string, assetId: string) { return http.get(`/projects/${encodeURIComponent(projectId)}/characters/${encodeURIComponent(assetId)}`); } export function updateProjectCharacter(projectId: string, assetId: string, input: { name: string; definition: Record }) { return http.patch(`/projects/${encodeURIComponent(projectId)}/characters/${encodeURIComponent(assetId)}`, input); } export function replaceProjectCharacterRepresentations(projectId: string, assetId: string, representations: Array<{ role: string; resourceId: string; metadata?: Record }>) { return http.put(`/projects/${encodeURIComponent(projectId)}/characters/${encodeURIComponent(assetId)}/representations`, { representations }); } export function bindProjectCharacterVoice(projectId: string, assetId: string, input: { voiceProfileId?: string; sampleResourceId?: string; voiceName?: string; instructions?: string }) { return http.put(`/projects/${encodeURIComponent(projectId)}/characters/${encodeURIComponent(assetId)}/voice`, input); } export function unbindProjectCharacterVoice(projectId: string, assetId: string) { return http.delete(`/projects/${encodeURIComponent(projectId)}/characters/${encodeURIComponent(assetId)}/voice`); } export function createUnitWorkflow(projectId: string, unitId: string) { return http.post<{ workflow: ProjectWorkflow }>(`/projects/${encodeURIComponent(projectId)}/workflows`, { unitId }); } export function saveProjectShot(projectId: string, input: { id?: string; unitId?: string; title: string; description?: string; position?: number; durationMs?: number; status?: string; revision?: Partial }) { return http.post<{ shot: ProjectShot }>(`/projects/${encodeURIComponent(projectId)}/shots`, input); } export function deleteProjectShot(projectId: string, shotId: string) { return http.delete<{ deleted: boolean }>(`/projects/${encodeURIComponent(projectId)}/shots/${encodeURIComponent(shotId)}`); } export function replaceProjectUnitShots(projectId: string, unitId: string, shots: Array<{ title: string; description: string; durationMs: number; revision?: Partial; assetVersionIds?: string[] }>, expectedShotIds?: string[]) { return http.put<{ shots: ProjectShot[] }>(`/projects/${encodeURIComponent(projectId)}/units/${encodeURIComponent(unitId)}/shots`, { shots, ...(expectedShotIds ? { expectedShotIds } : {}) }); } export function linkShotAsset(projectId: string, shotId: string, input: { assetVersionId: string; role: ShotAssetReference["role"] }) { return http.post<{ reference: ShotAssetReference }>(`/projects/${encodeURIComponent(projectId)}/shots/${encodeURIComponent(shotId)}/assets`, input); } export function unlinkShotAsset(projectId: string, shotId: string, referenceId: string) { return http.delete<{ unlinked: boolean }>(`/projects/${encodeURIComponent(projectId)}/shots/${encodeURIComponent(shotId)}/assets/${encodeURIComponent(referenceId)}`); } export function createShotRevision(projectId: string, shotId: string, input: ShotRevisionInput) { return http.post<{ shot: ProjectShot; revision: ShotRevision }>(`/projects/${encodeURIComponent(projectId)}/shots/${encodeURIComponent(shotId)}/revisions`, input); } export function createProjectAssetCandidates(projectId: string, candidates: Array<{ unitId?: string; shotId?: string; name: string; category: AssetCategory; details?: Record }>, source?: "chapter_character_extract" | "agent") { return http.post<{ candidates: ProjectAssetCandidate[] }>(`/projects/${encodeURIComponent(projectId)}/asset-candidates`, { candidates, source }); } export function confirmProjectAssetCandidate(projectId: string, candidateId: string, assetId?: string) { return http.post<{ asset: ProjectAsset }>(`/projects/${encodeURIComponent(projectId)}/asset-candidates/${encodeURIComponent(candidateId)}/confirm`, { assetId: assetId || "" }); } export function updateWorkflowStep(projectId: string, stepId: string, input: { status: string; outputJson?: string; error?: string }) { return http.patch<{ step: WorkflowStep }>(`/projects/${encodeURIComponent(projectId)}/workflow-steps/${encodeURIComponent(stepId)}`, input); } export function registerProjectTaskOutput(projectId: string, stepId: string, input: { taskId: string; canvasId?: string; unitId?: string; shotId?: string; artifactType?: string; assetVersionId?: string; resourceId?: string; mediaType?: string; role?: string; metadataJson?: string; outputJson?: string }) { return http.post<{ step: WorkflowStep }>(`/projects/${encodeURIComponent(projectId)}/workflow-steps/${encodeURIComponent(stepId)}/task-output`, input); }