diff --git a/backend/app/api/v1/routes/film/common.py b/backend/app/api/v1/routes/film/common.py index 776a5a72..768c3599 100644 --- a/backend/app/api/v1/routes/film/common.py +++ b/backend/app/api/v1/routes/film/common.py @@ -82,6 +82,7 @@ class TaskStatusRead(BaseModel): started_at_ts: float | None = Field(None, description="任务开始执行时间戳") finished_at_ts: float | None = Field(None, description="任务结束时间戳") elapsed_ms: int | None = Field(None, description="任务累计执行耗时(毫秒)") + error: str = Field("", description="失败时的错误信息(成功/进行中通常为空)") class TaskListItemRead(BaseModel): diff --git a/backend/app/api/v1/routes/film/task_status.py b/backend/app/api/v1/routes/film/task_status.py index efa9c5d1..8d19d53a 100644 --- a/backend/app/api/v1/routes/film/task_status.py +++ b/backend/app/api/v1/routes/film/task_status.py @@ -148,6 +148,7 @@ async def get_task_status( started_at_ts=view.started_at_ts, finished_at_ts=view.finished_at_ts, elapsed_ms=view.elapsed_ms, + error=view.error or "", ) ) diff --git a/backend/app/api/v1/routes/llm.py b/backend/app/api/v1/routes/llm.py index 8c6a769a..a9a4eea0 100644 --- a/backend/app/api/v1/routes/llm.py +++ b/backend/app/api/v1/routes/llm.py @@ -10,11 +10,14 @@ from app.schemas.common import ApiResponse, PaginatedData, created_response, empty_response, success_response from app.schemas.llm import ( ImageGenerationOptionsRead, + ModelChatTestRead, + ModelChatTestRequest, ModelCreate, ModelRead, ModelSettingsRead, ModelSettingsUpdate, ModelUpdate, + ModelVerifyRead, ProviderCreate, ProviderRead, ProviderSupportedRead, @@ -38,6 +41,8 @@ update_model_settings as update_model_settings_service, update_provider as update_provider_service, ) +from app.services.llm.model_chat_test import chat_test_with_model as chat_test_with_model_service +from app.services.llm.model_verify import verify_model_config as verify_model_config_service router = APIRouter() @@ -45,7 +50,8 @@ PROVIDER_ORDER_FIELDS = {"name", "created_at", "updated_at"} MODEL_ORDER_FIELDS = {"name", "category", "created_at", "updated_at"} DEFAULT_PAGE_SIZE = 10 -MAX_PAGE_SIZE = 100 +# 管理端列表可能一次拉较多供应商/模型(如筛选项、统计);略高于通用列表上限 100。 +MAX_PAGE_SIZE = 200 # ---------- Provider ---------- @@ -211,6 +217,39 @@ async def create_model( return created_response(ModelRead.model_validate(model)) +@router.post( + "/models/{model_id}/verify", + response_model=ApiResponse[ModelVerifyRead], + summary="验证模型配置(同步探测,不创建生成任务)", +) +async def verify_llm_model( + model_id: str, + db: AsyncSession = Depends(get_db), +) -> ApiResponse[ModelVerifyRead]: + """对已保存模型做一次短超时探测:文本走极小对话;图/视频走列表接口鉴权与模型名匹配。""" + data = await verify_model_config_service(db, model_id=model_id) + return success_response(data) + + +@router.post( + "/models/{model_id}/chat-test", + response_model=ApiResponse[ModelChatTestRead], + summary="文本模型试聊(管理页调试发消息)", +) +async def chat_test_llm_model( + model_id: str, + body: ModelChatTestRequest, + db: AsyncSession = Depends(get_db), +) -> ApiResponse[ModelChatTestRead]: + """对已保存的文本模型发送一条用户消息并返回模型回复(不计入业务任务)。""" + data = await chat_test_with_model_service( + db, + model_id=model_id, + user_message=body.message, + ) + return success_response(data) + + @router.get( "/models/{model_id}", response_model=ApiResponse[ModelRead], diff --git a/backend/app/api/v1/routes/studio/chapters.py b/backend/app/api/v1/routes/studio/chapters.py index 4000045b..2dd5c613 100644 --- a/backend/app/api/v1/routes/studio/chapters.py +++ b/backend/app/api/v1/routes/studio/chapters.py @@ -2,7 +2,7 @@ from __future__ import annotations -from fastapi import APIRouter, Depends, Query, status +from fastapi import APIRouter, Body, Depends, HTTPException, Query, status from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession @@ -21,13 +21,128 @@ patch_model, require_entity, ) +from app.api.v1.routes.film.common import TaskCreated, _CreateOnlyTask +from app.core.task_manager import DeliveryMode, SqlAlchemyTaskStore, TaskManager +from app.models.task_links import GenerationTaskLink +from app.schemas.studio.chapter_timeline import ( + ChapterTimelineExportRequest, + ChapterTimelineRead, + ChapterTimelineWrite, +) from app.schemas.studio.projects import ChapterCreate, ChapterRead, ChapterUpdate +from app.services.studio.chapter_timeline import ( + TimelineLayoutConflictError, + build_timeline_read, + replace_timeline_segments, +) +from app.services.studio.chapter_timeline_export import ( + EXPORT_RELATION_TYPE, + EXPORT_RESOURCE_TYPE, + ensure_timeline_exportable, + find_active_chapter_timeline_export_task_id, +) +from app.tasks.execute_task import enqueue_task_execution router = APIRouter() CHAPTER_ORDER_FIELDS = {"index", "title", "created_at", "updated_at", "storyboard_count", "status"} +@router.get( + "/{chapter_id}/timeline", + response_model=ApiResponse[ChapterTimelineRead], + summary="获取章节剪辑时间线(含镜头成片解析状态)", +) +async def get_chapter_timeline( + chapter_id: str, + db: AsyncSession = Depends(get_db), +) -> ApiResponse[ChapterTimelineRead]: + await get_or_404(db, Chapter, chapter_id, detail=entity_not_found("Chapter")) + data = await build_timeline_read(db, chapter_id) + return success_response(data) + + +@router.put( + "/{chapter_id}/timeline", + response_model=ApiResponse[ChapterTimelineRead], + summary="全量保存章节剪辑时间线片段顺序", +) +async def put_chapter_timeline( + chapter_id: str, + body: ChapterTimelineWrite, + db: AsyncSession = Depends(get_db), +) -> ApiResponse[ChapterTimelineRead]: + await get_or_404(db, Chapter, chapter_id, detail=entity_not_found("Chapter")) + try: + data = await replace_timeline_segments(db, chapter_id, body) + except TimelineLayoutConflictError as exc: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail={ + "message": "layout_version conflict", + "server_layout_version": exc.server_version, + "client_layout_version": exc.client_version, + }, + ) from exc + except ValueError as exc: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc + return success_response(data) + + +@router.post( + "/{chapter_id}/timeline/export", + response_model=ApiResponse[TaskCreated], + status_code=status.HTTP_201_CREATED, + summary="发起章节时间线拼接导出任务", +) +async def post_chapter_timeline_export( + chapter_id: str, + body: ChapterTimelineExportRequest | None = Body(default=None), + db: AsyncSession = Depends(get_db), +) -> ApiResponse[TaskCreated]: + await get_or_404(db, Chapter, chapter_id, detail=entity_not_found("Chapter")) + read = await build_timeline_read(db, chapter_id) + try: + ensure_timeline_exportable(read) + except ValueError as exc: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc + + active = await find_active_chapter_timeline_export_task_id(db, chapter_id) + if active: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail={ + "message": "章节时间线导出任务进行中", + "task_id": active, + }, + ) + + eff = body or ChapterTimelineExportRequest() + store = SqlAlchemyTaskStore(db) + tm = TaskManager(store=store, strategies={}) + task_record = await tm.create( + task=_CreateOnlyTask(), + mode=DeliveryMode.async_polling, + task_kind="chapter_timeline_export", + run_args={ + "chapter_id": chapter_id, + "encode_mode": eff.encode_mode.value, + "idempotency_key": eff.idempotency_key, + }, + ) + db.add( + GenerationTaskLink( + task_id=task_record.id, + resource_type=EXPORT_RESOURCE_TYPE, + relation_type=EXPORT_RELATION_TYPE, + relation_entity_id=chapter_id, + ), + ) + await db.commit() + enqueue_task_execution(task_record.id) + return created_response(TaskCreated(task_id=task_record.id)) + + @router.get( "", response_model=ApiResponse[PaginatedData[ChapterRead]], diff --git a/backend/app/api/v1/routes/studio/projects.py b/backend/app/api/v1/routes/studio/projects.py index 16d324b9..4937ef1c 100644 --- a/backend/app/api/v1/routes/studio/projects.py +++ b/backend/app/api/v1/routes/studio/projects.py @@ -28,6 +28,7 @@ ProjectUpdate, StyleOption, ) +from app.schemas.studio.timeline import TimelineClipRead router = APIRouter() @@ -124,6 +125,24 @@ async def create_project( return created_response(ProjectRead.model_validate(obj)) +@router.get( + "/{project_id}/timeline", + response_model=ApiResponse[list[TimelineClipRead]], + summary="项目时间线片段列表", +) +async def list_project_timeline( + project_id: str, + db: AsyncSession = Depends(get_db), +) -> ApiResponse[list[TimelineClipRead]]: + """返回项目关联的时间线片段。 + + 说明:`timeline_clips` 表当前无 `project_id` 字段,无法在数据库层按项目过滤; + 在引入归属字段或关联表之前,对已存在的项目返回空列表(接口可用,不再 404)。 + """ + await get_or_404(db, Project, project_id, detail=entity_not_found("Project")) + return success_response([]) + + @router.get( "/{project_id}", response_model=ApiResponse[ProjectRead], diff --git a/backend/app/core/contracts/image_generation.py b/backend/app/core/contracts/image_generation.py index 56f26d1a..84e38f41 100644 --- a/backend/app/core/contracts/image_generation.py +++ b/backend/app/core/contracts/image_generation.py @@ -110,7 +110,7 @@ class ImageGenerationResult(BaseModel): model_config = ConfigDict(extra="ignore") images: list[ImageItem] = Field(..., description="图片列表") - provider: ProviderKey = Field(..., description="供应商标识:openai | volcengine") + provider: ProviderKey = Field(..., description="供应商标识:openai | volcengine | aliyun_bailian") provider_task_id: Optional[str] = Field( None, description="供应商内部任务 ID(若存在),用于调试/追踪", diff --git a/backend/app/core/contracts/provider.py b/backend/app/core/contracts/provider.py index 209efd5f..e45b70f4 100644 --- a/backend/app/core/contracts/provider.py +++ b/backend/app/core/contracts/provider.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing import Literal -ProviderKey = Literal["openai", "volcengine"] +ProviderKey = Literal["openai", "volcengine", "aliyun_bailian"] @dataclass(frozen=True, slots=True) diff --git a/backend/app/core/integrations/aliyun/__init__.py b/backend/app/core/integrations/aliyun/__init__.py new file mode 100644 index 00000000..fabb6bc2 --- /dev/null +++ b/backend/app/core/integrations/aliyun/__init__.py @@ -0,0 +1,8 @@ +"""阿里云相关出站适配(DashScope 等)。""" + +from __future__ import annotations + +from app.core.integrations.aliyun.dashscope_images import DashScopeImageApiAdapter +from app.core.integrations.aliyun.dashscope_videos import DashScopeVideoApiAdapter + +__all__ = ["DashScopeImageApiAdapter", "DashScopeVideoApiAdapter"] diff --git a/backend/app/core/integrations/aliyun/dashscope_images.py b/backend/app/core/integrations/aliyun/dashscope_images.py new file mode 100644 index 00000000..032e83e7 --- /dev/null +++ b/backend/app/core/integrations/aliyun/dashscope_images.py @@ -0,0 +1,408 @@ +"""阿里云百炼 DashScope 原生文生图 HTTP。 + +与 OpenAI ``/images/generations`` 不同,北京地域常用: +- **同步**:``POST /api/v1/services/aigc/multimodal-generation/generation``(如 wan2.6 / qwen-image 系列)。 +- **异步**:``POST /api/v1/services/aigc/text2image/image-synthesis`` + ``GET /api/v1/tasks/{task_id}`` + (如 wanx-v1;须携带 ``X-DashScope-Async: enable``)。 + +文档: +https://help.aliyun.com/zh/model-studio/developer-reference/text-to-image-api-reference +https://www.alibabacloud.com/help/zh/model-studio/text-to-image-v2-api-reference +""" + +from __future__ import annotations + +import asyncio +import json +import time +from typing import Any + +import httpx + +from app.core.contracts.image_generation import ( + ImageGenerationInput, + ImageGenerationResult, + ImageItem, +) +from app.core.contracts.provider import ProviderConfig +from app.core.integrations.http_logging import log_image_http_request, log_image_http_response + + +def dashscope_http_origin(base_url: str | None) -> str: + """将兼容模式 ``.../compatible-mode/v1`` 等配置还原为 DashScope API 根 URL。 + + 原生文生图路径挂在 ``/api/v1/services/...`` 下,不能与 OpenAI compatible-mode 混用。 + """ + default = "https://dashscope.aliyuncs.com" + raw = (base_url or "").strip().rstrip("/") + if not raw: + return default + if "compatible-mode" in raw: + prefix = raw.split("/compatible-mode", 1)[0].rstrip("/") + return prefix or default + if "/api/v1" in raw: + prefix = raw.split("/api/v1", 1)[0].rstrip("/") + return prefix or default + return raw + + +def _use_wanx_async_api(model: str | None) -> bool: + """万相 V1(wanx-*)HTTP 仅支持异步创建 + 任务查询。""" + m = (model or "").strip().lower() + if not m: + return False + return m.startswith("wanx") + + +def _clamp_prompt(text: str, max_chars: int) -> str: + t = (text or "").strip() + return t if len(t) <= max_chars else t[:max_chars] + + +def _split_dashscope_prompt_and_negative(raw: str) -> tuple[str, str]: + """从整段提示词中拆分「正面描述」与「负面提示」,供 DashScope ``negative_prompt`` 使用。 + + 识别规则:任意一行包含「负面提示」或以 ``negative prompt`` 开头(不区分大小写), + 则从该行之前为正面、之后为负面;若该行冒号后仍有正文,也并入负面。 + 未识别到标记时,全文作为正面,负面为空(由模型默认处理)。 + """ + text = (raw or "").replace("\r\n", "\n").strip() + if not text: + return "", "" + lines = text.split("\n") + split_idx: int | None = None + for i, line in enumerate(lines): + s = line.strip() + if "负面提示" in s: + split_idx = i + break + low = s.lower() + if low.startswith("negative prompt") or low.startswith("negative:"): + split_idx = i + break + if split_idx is None: + return text, "" + + positive_lines = lines[:split_idx] + header = lines[split_idx].strip() + tail_lines = lines[split_idx + 1 :] + + extra_from_header = "" + for sep in (":", ":"): + if sep in header: + _, rest = header.split(sep, 1) + extra_from_header = rest.strip() + break + + neg_parts: list[str] = [] + if extra_from_header: + neg_parts.append(extra_from_header) + neg_parts.extend(tail_lines) + negative = "\n".join(neg_parts).strip() + positive = "\n".join(positive_lines).strip() + return positive, negative + + +def _dashscope_http_error_detail(response: httpx.Response) -> str | None: + """解析 DashScope HTTP 错误体(常见为顶层 ``code`` / ``message``)。""" + try: + data = response.json() + except Exception: # noqa: BLE001 + return None + if not isinstance(data, dict): + return None + msg = data.get("message") + code = data.get("code") + if msg is not None or code is not None: + parts = [str(x) for x in (code, msg) if x is not None and str(x).strip()] + if parts: + return ": ".join(parts) if len(parts) > 1 else parts[0] + err = data.get("error") + if isinstance(err, dict): + em = err.get("message") + ec = err.get("code") + parts = [str(x) for x in (ec, em) if x is not None and str(x).strip()] + if parts: + return ": ".join(parts) if len(parts) > 1 else parts[0] + return None + + +def _dashscope_multimodal_size(inp: ImageGenerationInput) -> str: + """映射为 ``宽*高``(wan2.6 / qwen-image 文档格式)。""" + if inp.size: + return str(inp.size).replace("x", "*").replace("X", "*") + ratio = inp.target_ratio or "1:1" + # 与万相 2.x 文档常见比例推荐对齐(像素在文档约束内可调) + ratio_map: dict[str, str] = { + "1:1": "1280*1280", + "3:4": "1104*1472", + "4:3": "1472*1104", + "9:16": "960*1696", + "16:9": "1696*960", + "21:9": "1696*720", + "3:2": "1472*992", + "2:3": "992*1472", + } + return ratio_map.get(ratio, "1280*1280") + + +def _safe_json_for_log(obj: Any, limit: int = 1200) -> str: + try: + s = json.dumps(obj, ensure_ascii=False) + except Exception: # noqa: BLE001 + return "" + return s if len(s) <= limit else s[:limit] + "...(truncated)" + + +def _build_multimodal_user_content(inp: ImageGenerationInput, *, positive_prompt: str) -> list[dict[str, str]]: + """构造 DashScope multimodal user content。 + + - 首项固定为 text; + - 参考图按顺序拼到 content(仅接受 image_url)。 + """ + content: list[dict[str, str]] = [{"text": _clamp_prompt(positive_prompt, 2100)}] + # qwen-image-2.0-2in1 等模型限制 0~3 张参考图;统一在适配层做上限保护。 + for ref in (inp.images or [])[:3]: + img_url = (ref.image_url or "").strip() + if not img_url: + continue + content.append({"image": img_url}) + return content + + +class DashScopeImageApiAdapter: + """DashScope 文生图:自动选择 multimodal 同步或 wanx 异步轮询。""" + + async def generate( + self, + *, + cfg: ProviderConfig, + inp: ImageGenerationInput, + timeout_s: float, + ) -> ImageGenerationResult: + origin = dashscope_http_origin(cfg.base_url) + model = (inp.model or "").strip() + if not model: + raise RuntimeError("DashScope image generation requires model name (configure image model in LLM settings)") + + headers_base: dict[str, str] = { + "Authorization": f"Bearer {cfg.api_key}", + "Content-Type": "application/json", + } + + # wanx 异步轮询可能显著长于单次 HTTP;客户端总超时放宽。 + client_timeout = max(float(timeout_s), 180.0) + async with httpx.AsyncClient(timeout=client_timeout) as client: + if _use_wanx_async_api(inp.model): + return await self._generate_wanx_async( + client, + origin=origin, + headers=headers_base, + inp=inp, + poll_timeout_s=float(timeout_s), + ) + return await self._generate_multimodal_sync(client, origin=origin, headers=headers_base, inp=inp) + + async def _generate_multimodal_sync( + self, + client: Any, + *, + origin: str, + headers: dict[str, str], + inp: ImageGenerationInput, + ) -> ImageGenerationResult: + """wan2.6 / qwen-image 等多模态文生图(单次 HTTP 返回 URL)。""" + url = f"{origin.rstrip('/')}/api/v1/services/aigc/multimodal-generation/generation" + pos, neg = _split_dashscope_prompt_and_negative(inp.prompt) + if not pos and not neg: + pos = (inp.prompt or "").strip() + elif not pos and neg: + # 仅识别到负面段时给最小正向提示,避免把负面全文当作 messages.text + pos = "人像摄影,高画质,细节丰富,符合画面描述" + body: dict[str, Any] = { + "model": (inp.model or "").strip(), + "input": { + "messages": [ + { + "role": "user", + "content": _build_multimodal_user_content(inp, positive_prompt=pos), + } + ] + }, + "parameters": { + "prompt_extend": True, + "watermark": bool(inp.watermark) if inp.watermark is not None else False, + "n": max(1, min(int(inp.n), 4)), + "negative_prompt": _clamp_prompt(neg, 1500), + "size": _dashscope_multimodal_size(inp), + }, + } + if inp.seed is not None: + body["parameters"]["seed"] = int(inp.seed) + + log_image_http_request( + provider="aliyun_bailian", + method="POST", + url=url, + headers=headers, + body_log=_safe_json_for_log(body), + ) + t0 = time.perf_counter() + r = await client.post(url, headers=headers, json=body) + dt_ms = int((time.perf_counter() - t0) * 1000) + resp_text = r.text or "" + log_image_http_response( + provider="aliyun_bailian", + status_code=r.status_code, + elapsed_ms=dt_ms, + resp_headers=dict(r.headers), + resp_text=resp_text[:4000], + ) + if r.status_code >= 400: + detail = _dashscope_http_error_detail(r) + if detail: + raise RuntimeError(detail) + r.raise_for_status() + data = r.json() + if data.get("code"): + raise RuntimeError(f"DashScope multimodal error: {data.get('code')} — {data.get('message')}") + return _parse_multimodal_sync_response(data) + + async def _generate_wanx_async( + self, + client: Any, + *, + origin: str, + headers: dict[str, str], + inp: ImageGenerationInput, + poll_timeout_s: float, + ) -> ImageGenerationResult: + """wanx-v1:异步创建 + 轮询 ``/api/v1/tasks/{task_id}``。""" + create_url = f"{origin.rstrip('/')}/api/v1/services/aigc/text2image/image-synthesis" + hdr = { + **headers, + "X-DashScope-Async": "enable", + } + pos, neg = _split_dashscope_prompt_and_negative(inp.prompt) + if not pos and not neg: + pos = (inp.prompt or "").strip() + elif not pos and neg: + pos = "人像摄影,高画质,细节丰富" + wanx_params: dict[str, Any] = { + "style": "", + "size": _dashscope_multimodal_size(inp), + "n": max(1, min(int(inp.n), 4)), + } + if neg: + wanx_params["negative_prompt"] = _clamp_prompt(neg, 800) + body: dict[str, Any] = { + "model": (inp.model or "").strip(), + "input": {"prompt": _clamp_prompt(pos, 800)}, + "parameters": wanx_params, + } + if inp.images: + first = inp.images[0] + ref = (first.image_url or "").strip() + if ref: + body["input"]["ref_image"] = ref + body["parameters"]["ref_strength"] = 1.0 + body["parameters"]["ref_mode"] = "repaint" + + log_image_http_request( + provider="aliyun_bailian", + method="POST", + url=create_url, + headers=hdr, + body_log=_safe_json_for_log(body), + ) + t0 = time.perf_counter() + r = await client.post(create_url, headers=hdr, json=body) + dt_ms = int((time.perf_counter() - t0) * 1000) + resp_text = r.text or "" + log_image_http_response( + provider="aliyun_bailian", + status_code=r.status_code, + elapsed_ms=dt_ms, + resp_headers=dict(r.headers), + resp_text=resp_text[:4000], + ) + if r.status_code >= 400: + detail = _dashscope_http_error_detail(r) + if detail: + raise RuntimeError(detail) + r.raise_for_status() + data = r.json() + if data.get("code"): + raise RuntimeError(f"DashScope wanx create error: {data.get('code')} — {data.get('message')}") + task_id = str((data.get("output") or {}).get("task_id") or "").strip() + if not task_id: + raise RuntimeError(f"DashScope wanx missing task_id: {data!r}") + + poll_headers = {"Authorization": headers["Authorization"]} + poll_deadline = time.monotonic() + max(60.0, poll_timeout_s) + query_url = f"{origin.rstrip('/')}/api/v1/tasks/{task_id}" + while time.monotonic() < poll_deadline: + await asyncio.sleep(2.0) + pr = await client.get(query_url, headers=poll_headers) + pt = pr.text or "" + log_image_http_response( + provider="aliyun_bailian", + status_code=pr.status_code, + elapsed_ms=0, + resp_headers=dict(pr.headers), + resp_text=pt[:4000], + ) + pr.raise_for_status() + pdata = pr.json() + if pdata.get("code") and (pdata.get("output") or {}).get("task_status") != "SUCCEEDED": + raise RuntimeError(f"DashScope task query error: {pdata.get('code')} — {pdata.get('message')}") + out = pdata.get("output") or {} + st = str(out.get("task_status") or "") + if st == "SUCCEEDED": + return _parse_wanx_poll_response(pdata, provider_task_id=task_id) + if st in {"FAILED", "CANCELED", "UNKNOWN"}: + raise RuntimeError(f"DashScope wanx task {st}: {pdata!r}") + + raise TimeoutError(f"DashScope wanx task polling timed out: task_id={task_id}") + + +def _parse_multimodal_sync_response(data: dict[str, Any]) -> ImageGenerationResult: + """解析 multimodal-generation 同步响应 ``output.choices[].message.content[]``。""" + images: list[ImageItem] = [] + output = data.get("output") or {} + for ch in output.get("choices") or []: + if not isinstance(ch, dict): + continue + msg = ch.get("message") or {} + for block in msg.get("content") or []: + if not isinstance(block, dict): + continue + # 文档示例多为 type=image;qwen-image 线上实际可能仅返回 {"image": "https://..."} + img_url = block.get("image") + if img_url and (block.get("type") in (None, "image")): + images.append(ImageItem(url=str(img_url), b64_json=None)) + if not images: + raise RuntimeError(f"DashScope multimodal response has no image URL: {data!r}") + return ImageGenerationResult( + images=images, + provider="aliyun_bailian", + provider_task_id=None, + status="succeeded", + ) + + +def _parse_wanx_poll_response(data: dict[str, Any], *, provider_task_id: str) -> ImageGenerationResult: + out = data.get("output") or {} + raw_results = out.get("results") or [] + images: list[ImageItem] = [] + for item in raw_results: + if isinstance(item, dict) and item.get("url"): + images.append(ImageItem(url=str(item["url"]), b64_json=None)) + if not images: + raise RuntimeError(f"DashScope wanx poll response has no image URL: {data!r}") + return ImageGenerationResult( + images=images, + provider="aliyun_bailian", + provider_task_id=provider_task_id, + status=str(out.get("task_status") or "succeeded"), + ) diff --git a/backend/app/core/integrations/aliyun/dashscope_videos.py b/backend/app/core/integrations/aliyun/dashscope_videos.py new file mode 100644 index 00000000..fa6de0dc --- /dev/null +++ b/backend/app/core/integrations/aliyun/dashscope_videos.py @@ -0,0 +1,229 @@ +"""阿里云百炼 DashScope 原生视频生成 HTTP 适配。""" + +from __future__ import annotations + +import logging +from typing import Any, Literal + +from app.core.contracts.provider import ProviderConfig +from app.core.contracts.video_generation import VideoGenerationInput, _strip_optional_b64 +from app.core.integrations.aliyun.dashscope_images import dashscope_http_origin +from app.core.integrations.openai.video_payload import to_image_data_url + +logger = logging.getLogger(__name__) + + +def _dashscope_video_error_detail(response: Any) -> str | None: + """从 DashScope 错误体提取可读错误信息。""" + try: + data = response.json() + except Exception: # noqa: BLE001 + return None + if not isinstance(data, dict): + return None + code = data.get("code") + message = data.get("message") + parts = [str(item) for item in (code, message) if item is not None and str(item).strip()] + if parts: + return ": ".join(parts) if len(parts) > 1 else parts[0] + err = data.get("error") + if isinstance(err, dict): + ecode = err.get("code") + emsg = err.get("message") + eparts = [str(item) for item in (ecode, emsg) if item is not None and str(item).strip()] + if eparts: + return ": ".join(eparts) if len(eparts) > 1 else eparts[0] + return None + + +def _ratio_to_dashscope_size(ratio: str) -> str: + """将业务 ratio 映射为 DashScope 常见 size。""" + mapping = { + "16:9": "1280*720", + "4:3": "960*720", + "1:1": "960*960", + "3:4": "720*960", + "9:16": "720*1280", + "21:9": "1680*720", + } + return mapping.get(ratio, "1280*720") + + +def _dashscope_video_mode( + model: str | None, + *, + has_image_refs: bool, +) -> Literal["t2v", "i2v", "ref_video"]: + """按模型名称推断 DashScope 视频任务形态,决定 input.media 是否携带图片/视频。 + + 说明:不同模型对 input.media 的要求差异很大。百炼 HTTP 接口中,帧图需使用 + type=first_frame / last_frame(见模型文档),禁止使用旧字段 reference_image。 + """ + m = (model or "").strip().lower() + if not m: + # 未带模型名时不可猜测为图生视频,否则易与上游「仅接受文生 / 需视频参考」的校验冲突。 + return "t2v" + + # 参考视频 / 视频续写等:media 中需要 reference_video(公网 URL),不能仅用图片冒充。 + if any( + k in m + for k in ( + "v2v", + "video2video", + "video-to-video", + "ref2video", + "reference2video", + "reference-video", + "reference_video", + ) + ): + return "ref_video" + if "reference" in m and "student" in m: + return "ref_video" + + if any(k in m for k in ("i2v", "img2video", "image-to-video", "kf2v")): + return "i2v" + if any(k in m for k in ("t2v", "text-to-video", "text2video")): + return "t2v" + + # 未识别名称:默认文生视频(不附带 reference_image)。仅当名称明确含 i2v 等关键字时才走图生视频; + # 若这里把「无关键字的文生模型 + 分镜帧图」当成 i2v,会只传图片而触发 + #「At least one video item is required in media list」等错误。 + return "t2v" + + +def _build_dashscope_video_body(input_: VideoGenerationInput) -> dict[str, Any]: + """构建 DashScope 视频生成请求体。""" + input_payload: dict[str, Any] = {} + prompt = (input_.prompt or "").strip() + if prompt: + input_payload["prompt"] = prompt + + key_frame = _strip_optional_b64(input_.key_frame_base64) + first_frame = _strip_optional_b64(input_.first_frame_base64) + last_frame = _strip_optional_b64(input_.last_frame_base64) + has_image_refs = bool(first_frame or last_frame or key_frame) + + mode = _dashscope_video_mode(input_.model, has_image_refs=has_image_refs) + if mode == "t2v" and has_image_refs: + logger.debug( + "DashScope video: model %r uses text-to-video request path; skipping frame media in body", + (input_.model or "").strip() or None, + ) + + if mode == "ref_video": + raise RuntimeError( + "当前模型属于「参考视频 / 视频类参考」能力:请求体 input.media 中需要至少一条 " + "type=reference_video 的视频 URL;当前业务仅支持帧图参考,无法调用该模型。" + "请在模型管理中将默认视频模型切换为文生视频(名称通常含 t2v)或图生视频(含 i2v)。" + ) + + # 文生视频:不要附带帧 media;否则部分模型会校验失败。 + # 图生视频:input.media 每项 type 须为百炼文档允许的枚举(如 first_frame、last_frame)。 + if mode == "i2v": + media_items: list[dict[str, str]] = [] + if first_frame: + media_items.append({"type": "first_frame", "url": to_image_data_url(first_frame)}) + if last_frame: + media_items.append({"type": "last_frame", "url": to_image_data_url(last_frame)}) + if key_frame and not first_frame and not last_frame: + media_items.append({"type": "first_frame", "url": to_image_data_url(key_frame)}) + if media_items: + input_payload["media"] = media_items + + parameters: dict[str, Any] = { + "size": _ratio_to_dashscope_size(input_.ratio), + "ratio": input_.ratio, + } + if input_.seconds is not None: + parameters["duration"] = int(input_.seconds) + if input_.seed is not None: + parameters["seed"] = int(input_.seed) + if input_.watermark is not None: + parameters["watermark"] = bool(input_.watermark) + + body: dict[str, Any] = { + "model": (input_.model or "").strip(), + "input": input_payload, + "parameters": parameters, + } + return body + + +def _dashscope_task_failure_detail(meta: dict[str, Any]) -> str | None: + """提取任务失败时的 code/message,便于直接反馈给前端。""" + if not isinstance(meta, dict): + return None + output = meta.get("output") + if not isinstance(output, dict): + return None + code = output.get("code") + message = output.get("message") + parts = [str(item) for item in (code, message) if item is not None and str(item).strip()] + if not parts: + return None + return ": ".join(parts) if len(parts) > 1 else parts[0] + + +class DashScopeVideoApiAdapter: + """DashScope 视频生成:创建异步任务 + 查询任务状态。""" + + async def create_video_task( + self, + *, + cfg: ProviderConfig, + input_: VideoGenerationInput, + timeout_s: float, + ) -> str: + try: + import httpx + except ImportError as exc: # pragma: no cover + raise RuntimeError("httpx is required for video generation tasks") from exc + + origin = dashscope_http_origin(cfg.base_url) + create_url = f"{origin.rstrip('/')}/api/v1/services/aigc/video-generation/video-synthesis" + headers = { + "Authorization": f"Bearer {cfg.api_key}", + "Content-Type": "application/json", + "X-DashScope-Async": "enable", + } + body = _build_dashscope_video_body(input_) + + async with httpx.AsyncClient(timeout=timeout_s) as client: + response = await client.post(create_url, headers=headers, json=body) + if response.status_code >= 400: + detail = _dashscope_video_error_detail(response) + if detail: + raise RuntimeError(detail) + response.raise_for_status() + data: dict[str, Any] = response.json() + output = data.get("output") if isinstance(data.get("output"), dict) else {} + task_id = str(output.get("task_id") or data.get("task_id") or data.get("id") or "").strip() + if not task_id: + raise RuntimeError(f"DashScope video create missing task_id: {data!r}") + return task_id + + async def get_video_task( + self, + *, + cfg: ProviderConfig, + task_id: str, + timeout_s: float, + ) -> dict[str, Any]: + try: + import httpx + except ImportError as exc: # pragma: no cover + raise RuntimeError("httpx is required for video generation tasks") from exc + + origin = dashscope_http_origin(cfg.base_url) + query_url = f"{origin.rstrip('/')}/api/v1/tasks/{task_id}" + headers = {"Authorization": f"Bearer {cfg.api_key}"} + + async with httpx.AsyncClient(timeout=timeout_s) as client: + response = await client.get(query_url, headers=headers) + if response.status_code >= 400: + detail = _dashscope_video_error_detail(response) + if detail: + raise RuntimeError(detail) + response.raise_for_status() + return response.json() diff --git a/backend/app/core/integrations/volcengine/images.py b/backend/app/core/integrations/volcengine/images.py index fe272f11..7773f282 100644 --- a/backend/app/core/integrations/volcengine/images.py +++ b/backend/app/core/integrations/volcengine/images.py @@ -5,6 +5,8 @@ import time from typing import Any +import httpx + from app.core.integrations.http_logging import ( json_dumps_for_log, log_image_http_request, @@ -21,6 +23,24 @@ from app.core.integrations.volcengine.image_capabilities import validate_volcengine_image_options +def _volcengine_http_error_detail(response: httpx.Response) -> str | None: + """从方舟错误 JSON 中提取可读说明(优先于 httpx 默认文案)。""" + try: + data = response.json() + except Exception: # noqa: BLE001 + return None + err = data.get("error") + if isinstance(err, dict): + code = err.get("code") + msg = err.get("message") + parts = [str(x) for x in (code, msg) if x] + if parts: + return ": ".join(parts) if len(parts) > 1 else parts[0] + if isinstance(err, str) and err.strip(): + return err.strip() + return None + + class VolcengineImageApiAdapter: """火山图片生成 HTTP;无状态,可单测替换。""" @@ -31,11 +51,6 @@ async def generate( inp: ImageGenerationInput, timeout_s: float, ) -> ImageGenerationResult: - try: - import httpx - except ImportError as e: # pragma: no cover - raise RuntimeError("httpx is required for image generation tasks") from e - base_url = (cfg.base_url or "https://ark.cn-beijing.volces.com/api/v3").rstrip("/") resolved_size = resolve_image_size( provider="volcengine", @@ -78,6 +93,10 @@ async def generate( resp_headers=dict(r.headers), resp_text=resp_text, ) + if r.status_code >= 400: + detail = _volcengine_http_error_detail(r) + if detail: + raise RuntimeError(detail) r.raise_for_status() data = r.json() diff --git a/backend/app/core/storage.py b/backend/app/core/storage.py index 047dde95..fa5f11d8 100644 --- a/backend/app/core/storage.py +++ b/backend/app/core/storage.py @@ -41,7 +41,9 @@ def _build_s3_client(): region_name=settings.s3_region_name, aws_access_key_id=settings.s3_access_key_id, aws_secret_access_key=settings.s3_secret_access_key, - config=BotoConfig(s3={"addressing_style": "virtual"}), + # 对 MinIO/RustFS 等自建 S3 兼容服务,优先使用 path-style, + # 避免解析出 . 导致容器内 DNS 失败。 + config=BotoConfig(s3={"addressing_style": "path"}), ) return client diff --git a/backend/app/core/tasks/bootstrap.py b/backend/app/core/tasks/bootstrap.py index 942f5334..4cd341e2 100644 --- a/backend/app/core/tasks/bootstrap.py +++ b/backend/app/core/tasks/bootstrap.py @@ -10,8 +10,10 @@ TASK_ADAPTER_SPECS = ( ("image_generation", "openai", ImageGenerationTask._build_openai_impl), ("image_generation", "volcengine", ImageGenerationTask._build_volcengine_impl), + ("image_generation", "aliyun_bailian", ImageGenerationTask._build_aliyun_bailian_impl), ("video_generation", "openai", VideoGenerationTask._build_openai_impl), ("video_generation", "volcengine", VideoGenerationTask._build_volcengine_impl), + ("video_generation", "aliyun_bailian", VideoGenerationTask._build_aliyun_bailian_impl), ) diff --git a/backend/app/core/tasks/image_generation_tasks.py b/backend/app/core/tasks/image_generation_tasks.py index ca71508b..709033f1 100644 --- a/backend/app/core/tasks/image_generation_tasks.py +++ b/backend/app/core/tasks/image_generation_tasks.py @@ -8,6 +8,7 @@ from abc import ABC, abstractmethod from typing import Any, AsyncIterator +from app.core.integrations.aliyun.dashscope_images import DashScopeImageApiAdapter from app.core.integrations.openai.images import OpenAIImageApiAdapter from app.core.integrations.volcengine.images import VolcengineImageApiAdapter from app.core.contracts.image_generation import ( @@ -31,6 +32,7 @@ "AbstractImageGenerationTask", "OpenAIImageGenerationTask", "VolcengineImageGenerationTask", + "DashScopeImageGenerationTask", "ImageGenerationTask", ] @@ -143,6 +145,33 @@ async def _poll_and_get_result(self) -> ImageGenerationResult: return self._deferred +class DashScopeImageGenerationTask(AbstractImageGenerationTask): + """阿里云百炼 DashScope 原生文生图:委托 `DashScopeImageApiAdapter`。""" + + def __init__( + self, + *, + adapter: DashScopeImageApiAdapter | None = None, + provider_config: ProviderConfig, + input_: ImageGenerationInput, + timeout_s: float = 180.0, + ) -> None: + super().__init__(provider_config=provider_config, input_=input_, timeout_s=timeout_s) + self._adapter = adapter or DashScopeImageApiAdapter() + self._deferred: ImageGenerationResult | None = None + + async def _create_task(self) -> None: + self._deferred = await self._adapter.generate( + cfg=self._cfg, + inp=self._input, + timeout_s=self._timeout_s, + ) + + async def _poll_and_get_result(self) -> ImageGenerationResult: + assert self._deferred is not None + return self._deferred + + class ImageGenerationTask(BaseTask): """按 provider 分派到 OpenAI / 火山实现;对外构造函数与原先一致。""" @@ -189,6 +218,21 @@ def _build_volcengine_impl( timeout_s=timeout_s, ) + @staticmethod + def _build_aliyun_bailian_impl( + *, + provider_config: ProviderConfig, + input_: ImageGenerationInput, + timeout_s: float = 180.0, + ) -> AbstractImageGenerationTask: + # 外部默认 60s 对 wanx 异步轮询过短,保证下限。 + effective = max(float(timeout_s), 180.0) + return DashScopeImageGenerationTask( + provider_config=provider_config, + input_=input_, + timeout_s=effective, + ) + async def run(self, *args: Any, **kwargs: Any) -> AsyncIterator[Any] | None: # type: ignore[override] return await self._impl.run(*args, **kwargs) diff --git a/backend/app/core/tasks/video_generation_tasks.py b/backend/app/core/tasks/video_generation_tasks.py index 885203d0..2d60a4ad 100644 --- a/backend/app/core/tasks/video_generation_tasks.py +++ b/backend/app/core/tasks/video_generation_tasks.py @@ -9,6 +9,7 @@ from abc import ABC, abstractmethod from typing import Any, AsyncIterator +from app.core.integrations.aliyun.dashscope_videos import DashScopeVideoApiAdapter, _dashscope_task_failure_detail from app.core.integrations.openai.video import OpenAIVideoApiAdapter from app.core.integrations.volcengine.video import VolcengineVideoApiAdapter from app.core.contracts.provider import ProviderConfig @@ -22,6 +23,7 @@ "AbstractVideoGenerationTask", "OpenAIVideoGenerationTask", "VolcengineVideoGenerationTask", + "DashScopeVideoGenerationTask", "VideoGenerationTask", ] @@ -206,6 +208,91 @@ async def _poll_and_get_result(self) -> VideoGenerationResult: ) +def _extract_dashscope_video_url(meta: dict[str, Any]) -> str | None: + """从 DashScope 任务查询响应中提取视频 URL。""" + for key in ("video_url", "url"): + value = meta.get(key) + if isinstance(value, str) and value.strip(): + return value.strip() + + output = meta.get("output") + if isinstance(output, dict): + for key in ("video_url", "url"): + value = output.get(key) + if isinstance(value, str) and value.strip(): + return value.strip() + results = output.get("results") + if isinstance(results, list): + for item in results: + if not isinstance(item, dict): + continue + for key in ("video_url", "url"): + value = item.get(key) + if isinstance(value, str) and value.strip(): + return value.strip() + return None + + +class DashScopeVideoGenerationTask(AbstractVideoGenerationTask): + """DashScope 原生视频:创建异步任务并轮询 `/api/v1/tasks/{task_id}`。""" + + def __init__( + self, + *, + adapter: DashScopeVideoApiAdapter | None = None, + provider_config: ProviderConfig, + input_: VideoGenerationInput, + poll_interval_s: float = 3.0, + timeout_s: float = 600.0, + ) -> None: + super().__init__( + provider_config=provider_config, + input_=input_, + poll_interval_s=poll_interval_s, + timeout_s=timeout_s, + ) + self._adapter = adapter or DashScopeVideoApiAdapter() + + async def _create_task(self) -> None: + self._provider_task_id = await self._adapter.create_video_task( + cfg=self._cfg, + input_=self._input, + timeout_s=self._timeout_s, + ) + + async def _poll_and_get_result(self) -> VideoGenerationResult: + task_id = self._provider_task_id or "" + if not task_id: + raise RuntimeError("DashScope poll missing provider task id") + + while True: + meta = await self._adapter.get_video_task( + cfg=self._cfg, + task_id=task_id, + timeout_s=self._timeout_s, + ) + output = meta.get("output") if isinstance(meta.get("output"), dict) else {} + status_val = str(output.get("task_status") or meta.get("status") or "").upper() + + if status_val in {"SUCCEEDED", "COMPLETED"}: + video_url = _extract_dashscope_video_url(meta) + if not video_url: + raise RuntimeError(f"DashScope video task succeeded but no video url: {meta!r}") + return VideoGenerationResult( + url=video_url, + file_id=None, + provider_task_id=task_id, + provider="aliyun_bailian", + status=status_val, + ) + if status_val in {"FAILED", "CANCELED", "CANCELLED", "UNKNOWN"}: + detail = _dashscope_task_failure_detail(meta) + if detail: + raise RuntimeError(detail) + raise RuntimeError(f"DashScope video task not succeeded: status={status_val!r} meta={meta!r}") + await self._sleep_poll() + + class VideoGenerationTask(BaseTask): """按 provider 分派到 OpenAI / 火山实现;对外构造函数签名保持不变。""" @@ -258,6 +345,23 @@ def _build_volcengine_impl( timeout_s=timeout_s, ) + @staticmethod + def _build_aliyun_bailian_impl( + *, + provider_config: ProviderConfig, + input_: VideoGenerationInput, + poll_interval_s: float = 3.0, + timeout_s: float = 120.0, + ) -> AbstractVideoGenerationTask: + # DashScope 视频任务一般耗时更长,保证超时时间不低于 10 分钟。 + effective_timeout = max(float(timeout_s), 600.0) + return DashScopeVideoGenerationTask( + provider_config=provider_config, + input_=input_, + poll_interval_s=poll_interval_s, + timeout_s=effective_timeout, + ) + async def run(self, *args: Any, **kwargs: Any) -> AsyncIterator[Any] | None: # type: ignore[override] return await self._impl.run(*args, **kwargs) diff --git a/backend/app/models/studio.py b/backend/app/models/studio.py index c3f55b77..f65c2822 100644 --- a/backend/app/models/studio.py +++ b/backend/app/models/studio.py @@ -34,6 +34,7 @@ ShotDialogLine, ShotFrameImage, ) +from app.models.studio_timeline_chapter import ChapterTimelineSegment, ChapterTimelineState from app.models.types import ( AssetQualityLevel, AssetViewAngle, @@ -106,4 +107,6 @@ "FileItem", "FileUsage", "TimelineClip", + "ChapterTimelineState", + "ChapterTimelineSegment", ] diff --git a/backend/app/models/studio_projects.py b/backend/app/models/studio_projects.py index d61d0c6a..df5f0e58 100644 --- a/backend/app/models/studio_projects.py +++ b/backend/app/models/studio_projects.py @@ -12,6 +12,7 @@ if TYPE_CHECKING: from app.models.studio_assets import Actor, Character, Costume, Prop, Scene from app.models.studio_shots import Shot + from app.models.studio_timeline_chapter import ChapterTimelineSegment, ChapterTimelineState class Project(Base, TimestampMixin): @@ -123,6 +124,20 @@ class Chapter(Base, TimestampMixin): cascade="all, delete-orphan", passive_deletes=True, ) + timeline_state: Mapped["ChapterTimelineState | None"] = relationship( + "ChapterTimelineState", + back_populates="chapter", + uselist=False, + cascade="all, delete-orphan", + passive_deletes=True, + ) + timeline_segments: Mapped[list["ChapterTimelineSegment"]] = relationship( + "ChapterTimelineSegment", + back_populates="chapter", + cascade="all, delete-orphan", + passive_deletes=True, + order_by="ChapterTimelineSegment.position", + ) __table_args__ = ( UniqueConstraint("project_id", "index", name="uq_chapters_project_index"), diff --git a/backend/app/models/studio_shots.py b/backend/app/models/studio_shots.py index 8e71855a..a5ce5323 100644 --- a/backend/app/models/studio_shots.py +++ b/backend/app/models/studio_shots.py @@ -1,6 +1,7 @@ from __future__ import annotations from datetime import datetime +from typing import TYPE_CHECKING from sqlalchemy import JSON, Boolean, DateTime, ForeignKey, Index, Integer, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -20,6 +21,10 @@ VFXType, ) +if TYPE_CHECKING: + from app.models.studio_projects import Chapter + from app.models.studio_timeline_chapter import ChapterTimelineSegment + class Shot(Base,TimestampMixin): """镜头表(基础信息)。 @@ -72,6 +77,13 @@ class Shot(Base,TimestampMixin): generated_video_file: Mapped["FileItem | None"] = relationship( foreign_keys=[generated_video_file_id] ) + timeline_segment: Mapped["ChapterTimelineSegment | None"] = relationship( + "ChapterTimelineSegment", + back_populates="shot", + uselist=False, + cascade="all, delete-orphan", + passive_deletes=True, + ) detail: Mapped["ShotDetail"] = relationship( back_populates="shot", cascade="all, delete-orphan", diff --git a/backend/app/models/studio_timeline_chapter.py b/backend/app/models/studio_timeline_chapter.py new file mode 100644 index 00000000..3ba5c602 --- /dev/null +++ b/backend/app/models/studio_timeline_chapter.py @@ -0,0 +1,99 @@ +"""章节级视频时间线 ORM(与旧 timeline_clips 语义分离)。 + +每章节一条可选状态行用于 layout_version;片段表绑定 shot,导出时从镜头成片解析文件。 +""" + +from __future__ import annotations + +from datetime import datetime, timezone +from typing import TYPE_CHECKING + +from sqlalchemy import DateTime, ForeignKey, Integer, String, UniqueConstraint, func +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from app.core.db import Base +from app.models.base import TimestampMixin + +if TYPE_CHECKING: + from app.models.studio_projects import Chapter + from app.models.studio_shots import Shot + + +class ChapterTimelineState(Base): + """章节时间线保存状态(版本号用于乐观锁)。""" + + __tablename__ = "chapter_timeline_states" + + chapter_id: Mapped[str] = mapped_column( + String(64), + ForeignKey("chapters.id", ondelete="CASCADE"), + primary_key=True, + comment="章节 ID", + ) + updated_at: Mapped[object] = mapped_column( + DateTime(timezone=True), + nullable=False, + default=lambda: datetime.now(timezone.utc), + server_default=func.now(), + onupdate=lambda: datetime.now(timezone.utc), + comment="最近保存时间(UTC)", + ) + layout_version: Mapped[int] = mapped_column( + Integer, + nullable=False, + default=1, + server_default="1", + comment="布局版本(每次成功保存递增)", + ) + + chapter: Mapped["Chapter"] = relationship("Chapter", back_populates="timeline_state") + + +class ChapterTimelineSegment(Base, TimestampMixin): + """章节时间线上的一个镜头片段(顺序由 position 表达)。""" + + __tablename__ = "chapter_timeline_segments" + + id: Mapped[str] = mapped_column(String(64), primary_key=True, comment="片段行 ID") + chapter_id: Mapped[str] = mapped_column( + String(64), + ForeignKey("chapters.id", ondelete="CASCADE"), + nullable=False, + index=True, + comment="所属章节", + ) + shot_id: Mapped[str] = mapped_column( + String(64), + ForeignKey("shots.id", ondelete="CASCADE"), + nullable=False, + index=True, + comment="对应镜头", + ) + position: Mapped[int] = mapped_column(Integer, nullable=False, comment="从 0 起的播放顺序") + trim_start_ms: Mapped[int | None] = mapped_column(Integer, nullable=True, comment="入点毫秒(可选)") + trim_end_ms: Mapped[int | None] = mapped_column(Integer, nullable=True, comment="出点毫秒(可选)") + # 历史 SQL 已存在无默认值版本;这里补 ORM 侧默认,避免 INSERT 依赖 DB default。 + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + nullable=False, + default=lambda: datetime.now(timezone.utc), + server_default=func.now(), + ) + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + nullable=False, + default=lambda: datetime.now(timezone.utc), + onupdate=lambda: datetime.now(timezone.utc), + server_default=func.now(), + ) + + chapter: Mapped["Chapter"] = relationship("Chapter", back_populates="timeline_segments") + shot: Mapped["Shot"] = relationship("Shot", back_populates="timeline_segment") + + __table_args__ = ( + UniqueConstraint("chapter_id", "shot_id", name="uq_chapter_shot"), + UniqueConstraint("chapter_id", "position", name="uq_chapter_position"), + ) + + +__all__ = ["ChapterTimelineState", "ChapterTimelineSegment"] diff --git a/backend/app/models/types.py b/backend/app/models/types.py index 77cf2d8b..c5d2df71 100644 --- a/backend/app/models/types.py +++ b/backend/app/models/types.py @@ -144,6 +144,7 @@ class FileUsageKind(str, Enum): shot_frame = "shot_frame" generated_video = "generated_video" + chapter_master_video = "chapter_master_video" character_image = "character_image" asset_image = "asset_image" task_link = "task_link" diff --git a/backend/app/schemas/llm.py b/backend/app/schemas/llm.py index 9b288bb4..09b0594b 100644 --- a/backend/app/schemas/llm.py +++ b/backend/app/schemas/llm.py @@ -131,6 +131,37 @@ class ModelRead(ModelBase): id: str = Field(..., description="模型 ID") +class ModelVerifyRead(BaseModel): + """模型配置验证结果(同步探测;不含任何密钥明文)。""" + + ok: bool = Field(..., description="是否探测通过") + category: ModelCategoryKey = Field(..., description="被验证模型的类别") + message: str = Field(..., description="面向用户的主提示") + elapsed_ms: int = Field(..., description="服务端探测耗时(毫秒)") + detail: dict[str, Any] | None = Field( + None, + description="脱敏后的诊断信息(如 provider_key、上游 HTTP 状态、回复摘要);无 RBAC 时仍不得包含密钥", + ) + + +class ModelChatTestRequest(BaseModel): + """模型试聊请求(仅文本模型)。""" + + message: str = Field( + ..., + min_length=1, + max_length=8000, + description="用户输入;服务端会做长度截断与超时保护", + ) + + +class ModelChatTestRead(BaseModel): + """模型试聊响应(仅返回正文与耗时,不回显密钥)。""" + + reply: str = Field("", description="模型回复正文") + elapsed_ms: int = Field(..., description="服务端调用耗时(毫秒)") + + class ModelSettingsBase(BaseModel): """模型全局设置通用字段。""" diff --git a/backend/app/schemas/studio/chapter_timeline.py b/backend/app/schemas/studio/chapter_timeline.py new file mode 100644 index 00000000..9cea9425 --- /dev/null +++ b/backend/app/schemas/studio/chapter_timeline.py @@ -0,0 +1,80 @@ +"""章节视频时间线读写与导出相关 Schema(与 OpenAPI 契约对齐)。""" + +from __future__ import annotations + +from enum import Enum + +from pydantic import BaseModel, Field + +_PREVIEW_NOTE_DEFAULT = ( + "剪辑页支持顺序预览与入出点裁剪;导出将按裁剪拼接成片。" +) + + +class TimelineClipStatus(str, Enum): + """片段成片解析状态(展示用)。""" + + ready = "ready" + missing_video = "missing_video" + file_missing = "file_missing" + + +class ChapterTimelineSegmentWrite(BaseModel): + """保存时间线时的一段(顺序由数组顺序表达)。""" + + shot_id: str = Field(..., description="镜头 ID") + trim_start_ms: int | None = Field( + None, + ge=0, + description="裁剪入点毫秒(可选);与 trim_end_ms 均为空表示全长;否则区间为左闭右开 [start,end)", + ) + trim_end_ms: int | None = Field( + None, + ge=0, + description="裁剪出点毫秒(exclusive,可选);为空则默认为源成片时长", + ) + + +class ChapterTimelineWrite(BaseModel): + """全量替换章节时间线片段。""" + + layout_version: int | None = Field(None, description="与 GET 返回一致时可校验乐观锁") + segments: list[ChapterTimelineSegmentWrite] = Field(default_factory=list) + + +class ChapterTimelineSegmentRead(BaseModel): + """时间线片段读取模型(含成片文件解析状态)。""" + + id: str = Field(..., description="片段行 ID;尚未落库的合成行可为空字符串") + shot_id: str + position: int = Field(..., ge=0) + trim_start_ms: int | None = Field(None, description="已保存入点毫秒;null 表示从 0") + trim_end_ms: int | None = Field(None, description="已保存出点毫秒(exclusive);null 表示至片尾") + clip_status: TimelineClipStatus + file_id: str | None = None + label: str = Field("", description="镜头标题等展示字段") + + +class ChapterTimelineRead(BaseModel): + """章节时间线读取模型。""" + + layout_version: int = Field(1, ge=1) + segments: list[ChapterTimelineSegmentRead] = Field(default_factory=list) + preview_note: str = Field(default=_PREVIEW_NOTE_DEFAULT, description="连续预览能力说明") + + +class ChapterTimelineEncodeMode(str, Enum): + """导出编码策略。""" + + uniform_transcode = "uniform_transcode" + lossless_concat_only = "lossless_concat_only" + + +class ChapterTimelineExportRequest(BaseModel): + """发起章节时间线导出任务。""" + + idempotency_key: str | None = Field(None, description="可选幂等键") + encode_mode: ChapterTimelineEncodeMode = Field( + default=ChapterTimelineEncodeMode.uniform_transcode, + description="uniform_transcode:统一转码拼接;lossless_concat_only:仅当片段编码一致时无损拼接", + ) diff --git a/backend/app/schemas/studio/files.py b/backend/app/schemas/studio/files.py index 052a1c62..5b2f3448 100644 --- a/backend/app/schemas/studio/files.py +++ b/backend/app/schemas/studio/files.py @@ -35,7 +35,10 @@ class FileUsageWrite(BaseModel): shot_id: str | None = Field(None, description="镜头 ID") usage_kind: str = Field( ..., - description="用途:shot_frame / generated_video / character_image / asset_image / upload / api 等", + description=( + "用途:shot_frame / generated_video / chapter_master_video / " + "character_image / asset_image / upload / api 等" + ), ) source_ref: str | None = Field(None, description="幂等键(可选)") diff --git a/backend/app/schemas/studio/timeline.py b/backend/app/schemas/studio/timeline.py new file mode 100644 index 00000000..393b8b2d --- /dev/null +++ b/backend/app/schemas/studio/timeline.py @@ -0,0 +1,21 @@ +"""时间线片段 API 模型(与 `timeline_clips` 表字段对齐)。""" + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from app.models.types import TimelineClipType + + +class TimelineClipRead(BaseModel): + """时间线片段只读模型。""" + + model_config = ConfigDict(from_attributes=True) + + id: str = Field(..., description="片段 ID") + type: TimelineClipType = Field(..., description="片段类型:video / audio") + source_id: str = Field(..., description="来源素材 ID(逻辑引用)") + label: str = Field(..., description="轨道展示标签") + start: int = Field(..., description="起始时间(秒)") + end: int = Field(..., description="结束时间(秒)") + track: int = Field(..., description="轨道号") diff --git a/backend/app/services/film/generated_video.py b/backend/app/services/film/generated_video.py index 481a648e..2ff6649f 100644 --- a/backend/app/services/film/generated_video.py +++ b/backend/app/services/film/generated_video.py @@ -16,7 +16,7 @@ from app.core.tasks import VideoGenerationTask from app.models.llm import Model, ModelCategoryKey, ModelSettings from app.models.task_links import GenerationTaskLink -from app.models.studio import FileItem, Shot, ShotDetail, ShotFrameType +from app.models.studio import FileItem, Shot, ShotDetail, ShotFrameImage, ShotFrameType from app.models.types import FileUsageKind from app.services.common import entity_not_found from app.services.llm.provider_resolver import resolve_provider_config_by_model @@ -127,6 +127,25 @@ async def load_provider_config_by_model(db: AsyncSession, model: Model) -> Provi ) +async def _pick_fallback_reference_frame_data_url(db: AsyncSession, *, shot_id: str) -> str | None: + """当提交参数未携带参考图时,回退选取分镜已存在的一张帧图。""" + stmt = select(ShotFrameImage).where(ShotFrameImage.shot_detail_id == shot_id) + rows = (await db.execute(stmt)).scalars().all() + if not rows: + return None + order = { + ShotFrameType.key: 0, + ShotFrameType.first: 1, + ShotFrameType.last: 2, + } + sorted_rows = sorted(rows, key=lambda item: order.get(item.frame_type, 99)) + for row in sorted_rows: + if not row.file_id: + continue + return await file_id_to_data_url(db, file_id=str(row.file_id)) + return None + + def _normalize_optional_text(value: str | None) -> str | None: """归一化可选文本参数:空字符串视为未设置。""" normalized = (value or "").strip() @@ -174,6 +193,16 @@ async def build_run_args( frame_data_urls = [await file_id_to_data_url(db, file_id=file_id) for file_id in submission.images] frame_map = {ft: frame_data_urls[i] for i, ft in enumerate(required_frames)} + first_frame_b64 = frame_map.get(ShotFrameType.first) + last_frame_b64 = frame_map.get(ShotFrameType.last) + key_frame_b64 = frame_map.get(ShotFrameType.key) + if provider_cfg.provider == "aliyun_bailian": + # 帧图用于名称含 i2v 等的图生视频模型;文生视频模型在适配层会忽略帧图,但仍尽量补齐便于切换默认模型。 + if not any([first_frame_b64, last_frame_b64, key_frame_b64]): + fallback_ref = await _pick_fallback_reference_frame_data_url(db, shot_id=shot_id) + if fallback_ref: + key_frame_b64 = fallback_ref + run_args = { "shot_id": shot_id, "provider": provider_cfg.provider, @@ -181,9 +210,9 @@ async def build_run_args( "base_url": provider_cfg.base_url, "input": { "prompt": final_prompt, - "first_frame_base64": frame_map.get(ShotFrameType.first), - "last_frame_base64": frame_map.get(ShotFrameType.last), - "key_frame_base64": frame_map.get(ShotFrameType.key), + "first_frame_base64": first_frame_b64, + "last_frame_base64": last_frame_b64, + "key_frame_base64": key_frame_b64, "model": model.name, "ratio": resolved_ratio, "seconds": shot_detail.duration, diff --git a/backend/app/services/llm/__init__.py b/backend/app/services/llm/__init__.py index cd99fde1..b1f82a47 100644 --- a/backend/app/services/llm/__init__.py +++ b/backend/app/services/llm/__init__.py @@ -1,3 +1,4 @@ +from app.services.llm.model_verify import verify_model_config from app.services.llm.manage import ( create_model, create_provider, @@ -16,6 +17,7 @@ ) from app.services.llm.resolver import ( build_default_text_llm, + build_chat_model_for_model, build_chat_model_from_provider, get_default_model_by_category, get_model_by_category, @@ -30,6 +32,7 @@ register_many, register_provider, resolve_provider_key_from_name, + try_resolve_provider_key_from_name, ) from app.services.llm.provider_resolver import ( ResolvedProviderConfig, @@ -38,6 +41,7 @@ ) __all__ = [ + "verify_model_config", "create_model", "create_provider", "get_default_model_by_category", @@ -56,6 +60,7 @@ "update_model", "update_model_settings", "update_provider", + "build_chat_model_for_model", "build_chat_model_from_provider", "build_default_text_llm", "ProviderSpec", @@ -66,6 +71,7 @@ "register_many", "register_provider", "resolve_provider_key_from_name", + "try_resolve_provider_key_from_name", "resolve_provider_config", "resolve_provider_config_by_model", ] diff --git a/backend/app/services/llm/manage.py b/backend/app/services/llm/manage.py index 015be1f3..b53e3b40 100644 --- a/backend/app/services/llm/manage.py +++ b/backend/app/services/llm/manage.py @@ -29,7 +29,7 @@ from app.services.llm.provider_registry import ( is_provider_category_supported, list_registered_providers, - resolve_provider_key_from_name, + try_resolve_provider_key_from_name, ) from app.bootstrap import bootstrap_all_registries from app.services.common import ( @@ -300,7 +300,15 @@ async def get_video_generation_options( model = await get_or_404(db, Model, model_id, detail=entity_not_found("Model")) provider = await get_or_404(db, Provider, model.provider_id, detail=entity_not_found("Provider")) - provider_key = resolve_provider_key_from_name(provider.name) + provider_key = try_resolve_provider_key_from_name(provider.name) + if provider_key is None: + return VideoGenerationOptionsRead( + provider=provider.name, + model_id=model.id, + model_name=model.name, + allowed_ratios=["16:9"], + default_ratio="16:9", + ) capability = resolve_video_capability(provider=provider_key, model=model.name) allowed_ratios = sorted(capability.allowed_ratios or {"16:9"}) default_ratio = resolve_default_ratio(provider=provider_key, model=model.name) or allowed_ratios[0] @@ -334,7 +342,16 @@ async def get_image_generation_options( model = await get_or_404(db, Model, model_id, detail=entity_not_found("Model")) provider = await get_or_404(db, Provider, model.provider_id, detail=entity_not_found("Provider")) - provider_key = resolve_provider_key_from_name(provider.name) + provider_key = try_resolve_provider_key_from_name(provider.name) + if provider_key is None: + return ImageGenerationOptionsRead( + provider=provider.name, + model_id=model.id, + model_name=model.name, + supported_ratios=sorted(DEFAULT_VIDEO_REFERENCE_RATIO_SIZE_MAP.keys()), + default_resolution_profile="standard", + ratio_size_profiles=DEFAULT_VIDEO_REFERENCE_RATIO_SIZE_MAP, + ) capability = resolve_image_capability(provider=provider_key, model=model.name) ratio_size_profiles = capability.ratio_size_profiles or DEFAULT_VIDEO_REFERENCE_RATIO_SIZE_MAP supported_ratios = sorted(capability.supported_ratios or ratio_size_profiles.keys()) @@ -375,7 +392,9 @@ def _ensure_provider_supports_category(*, provider: Provider, category: ModelCat if isinstance(category, ModelCategoryKey) else ModelCategoryKey((str(category or "")).strip().lower()) ) - provider_key = resolve_provider_key_from_name(provider.name) + provider_key = try_resolve_provider_key_from_name(provider.name) + if provider_key is None: + return if not is_provider_category_supported(provider_key, normalized_category): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/backend/app/services/llm/model_chat_test.py b/backend/app/services/llm/model_chat_test.py new file mode 100644 index 00000000..246a7dd7 --- /dev/null +++ b/backend/app/services/llm/model_chat_test.py @@ -0,0 +1,80 @@ +"""文本模型试聊:按已保存模型发起一次真实对话请求(管理页调试用,非生产任务)。""" + +from __future__ import annotations + +import asyncio +import time + +from fastapi import HTTPException, status +from langchain_core.messages import HumanMessage +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.llm import ModelCategoryKey, Provider +from app.schemas.llm import ModelChatTestRead +from app.services.llm.manage import get_model +from app.services.llm.provider_resolver import resolve_provider_config_from_provider +from app.services.llm.resolver import build_chat_model_for_model + +# 试聊允许略长于验证探测,但仍需上限以免拖死 worker +_CHAT_TIMEOUT_S = 120.0 +_MAX_REPLY_TOKENS = 2048 + + +def _elapsed_ms(t0: float) -> int: + return int((time.perf_counter() - t0) * 1000) + + +async def chat_test_with_model( + db: AsyncSession, + *, + model_id: str, + user_message: str, +) -> ModelChatTestRead: + """对单条已保存**文本**模型发送一条用户消息并返回模型回复。""" + text = (user_message or "").strip() + if not text: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="message is empty") + + t0 = time.perf_counter() + model = await get_model(db, model_id=model_id) + if model.category != ModelCategoryKey.text: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Chat test is only available for text models", + ) + + provider = await db.get(Provider, model.provider_id) + if provider is None: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail=f"Provider not found for model_id={model_id}", + ) + + # 与验证路径一致:先确认供应商解析可用(自定义无适配器等会在此失败) + resolve_provider_config_from_provider(provider=provider, category=ModelCategoryKey.text) + + try: + llm = build_chat_model_for_model(provider=provider, model=model, thinking=False) + bounded = llm.bind(max_tokens=_MAX_REPLY_TOKENS) + msg = await asyncio.wait_for( + bounded.ainvoke([HumanMessage(content=text)]), + timeout=_CHAT_TIMEOUT_S, + ) + raw = getattr(msg, "content", "") or "" + if isinstance(raw, list): + reply = str(raw) + else: + reply = str(raw) + return ModelChatTestRead(reply=reply, elapsed_ms=_elapsed_ms(t0)) + except asyncio.TimeoutError: + raise HTTPException( + status_code=status.HTTP_504_GATEWAY_TIMEOUT, + detail="Chat test timed out", + ) from None + except HTTPException: + raise + except Exception as exc: # noqa: BLE001 + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail=str(exc)[:800], + ) from exc diff --git a/backend/app/services/llm/model_verify.py b/backend/app/services/llm/model_verify.py new file mode 100644 index 00000000..46b35c2a --- /dev/null +++ b/backend/app/services/llm/model_verify.py @@ -0,0 +1,284 @@ +"""已保存模型的同步配置验证(文本极小调用;图/视频走轻量 HTTP 列表探测)。 + +用于模型管理页的「测试」入口:不写入任务中心、不触发真实成片/成图。 +""" + +from __future__ import annotations + +import asyncio +import time + +import httpx +from fastapi import HTTPException +from langchain_core.messages import HumanMessage +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.llm import Model, ModelCategoryKey, Provider +from app.schemas.llm import ModelVerifyRead +from app.services.llm.manage import get_model +from app.services.llm.provider_resolver import ( + ResolvedProviderConfig, + resolve_provider_config_from_provider, +) +from app.services.llm.resolver import build_chat_model_for_model + +# 单次验证上限(秒);与 ModelSettings.api_timeout 解耦,避免配置过大拖死请求线程 +_VERIFY_TIMEOUT_S = 25.0 + + +def _elapsed_ms(t0: float) -> int: + return int((time.perf_counter() - t0) * 1000) + + +def _collect_model_ids(obj: object, out: list[str]) -> None: + """从任意 JSON 结构中收集疑似模型 id 的字符串(兼容 OpenAI / 方舟等嵌套结构)。""" + if isinstance(obj, dict): + for key, val in obj.items(): + if key == "id" and isinstance(val, str) and val.strip(): + out.append(val.strip()) + else: + _collect_model_ids(val, out) + return + if isinstance(obj, list): + for item in obj: + _collect_model_ids(item, out) + + +def _model_list_contains(ids: list[str], model_name: str) -> bool: + name = (model_name or "").strip() + if not name: + return False + if name in ids: + return True + return any(name in x for x in ids if x) + + +def _classify_llm_error(exc: BaseException) -> str: + """将文本调用异常归并为少量用户可读大类。""" + text = str(exc).lower() + if "401" in text or "unauthorized" in text or "invalid api key" in text: + return "鉴权失败:请检查供应商 API Key 是否有效" + if "403" in text or "permission" in text: + return "权限不足:请检查账户或模型访问权限" + if "404" in text or "not found" in text: + return "模型或资源不存在:请检查模型名称是否与上游一致" + if "timeout" in text or "timed out" in text: + return "请求超时:请检查网络或上游服务状态" + if "connection" in text or "connect" in text: + return "网络不可用:无法连接上游服务" + return f"调用失败:{exc!s}"[:400] + + +async def _probe_text(model: Model, provider: Provider, cfg: ResolvedProviderConfig) -> ModelVerifyRead: + """文本:极小对话请求,确认密钥与模型名可用。""" + t0 = time.perf_counter() + try: + llm = build_chat_model_for_model(provider=provider, model=model, thinking=False) + bounded = llm.bind(max_tokens=8) + msg = await bounded.ainvoke([HumanMessage(content="ping")]) + raw = getattr(msg, "content", "") or "" + if isinstance(raw, list): + preview = str(raw)[:120] + else: + preview = str(raw)[:120] + return ModelVerifyRead( + ok=True, + category=model.category, + message="验证通过", + elapsed_ms=_elapsed_ms(t0), + detail={ + "provider_key": cfg.provider_key, + "model_name": model.name, + "reply_preview": preview, + }, + ) + except Exception as exc: # noqa: BLE001 — 探测路径需兜底归类 + return ModelVerifyRead( + ok=False, + category=model.category, + message=_classify_llm_error(exc), + elapsed_ms=_elapsed_ms(t0), + detail={ + "provider_key": cfg.provider_key, + "model_name": model.name, + "error": str(exc)[:500], + }, + ) + + +async def _probe_models_list_http( # pylint: disable=too-many-return-statements + *, + cfg: ResolvedProviderConfig, + model_name: str, + category: ModelCategoryKey, +) -> ModelVerifyRead: + """图像/视频:GET {base}/models,校验 Bearer 与模型名是否出现在列表中(不发起生成)。""" + t0 = time.perf_counter() + base = (cfg.base_url or "").strip().rstrip("/") + if not base: + return ModelVerifyRead( + ok=False, + category=category, + message="Base URL 未配置,无法探测上游", + elapsed_ms=_elapsed_ms(t0), + detail={"provider_key": cfg.provider_key, "model_name": model_name}, + ) + url = f"{base}/models" + try: + async with httpx.AsyncClient(timeout=_VERIFY_TIMEOUT_S) as client: + response = await client.get( + url, + headers={"Authorization": f"Bearer {cfg.api_key}"}, + ) + except httpx.TimeoutException: + return ModelVerifyRead( + ok=False, + category=category, + message="探测超时:请检查网络或 Base URL", + elapsed_ms=_elapsed_ms(t0), + detail={"provider_key": cfg.provider_key, "endpoint": url}, + ) + except httpx.RequestError as exc: + return ModelVerifyRead( + ok=False, + category=category, + message=f"网络错误:{exc!s}"[:400], + elapsed_ms=_elapsed_ms(t0), + detail={"provider_key": cfg.provider_key, "endpoint": url}, + ) + + if response.status_code in (401, 403): + return ModelVerifyRead( + ok=False, + category=category, + message="鉴权失败:请检查 API Key 与 Base URL", + elapsed_ms=_elapsed_ms(t0), + detail={ + "provider_key": cfg.provider_key, + "http_status": response.status_code, + "endpoint": url, + }, + ) + if response.status_code == 404: + return ModelVerifyRead( + ok=False, + category=category, + message="上游未提供模型列表接口(404):请检查 Base URL 是否指向 API 根路径(含 /v1 或 /api/v3 等)", + elapsed_ms=_elapsed_ms(t0), + detail={ + "provider_key": cfg.provider_key, + "http_status": 404, + "endpoint": url, + }, + ) + if response.status_code >= 400: + snippet = (response.text or "")[:300] + return ModelVerifyRead( + ok=False, + category=category, + message=f"上游返回错误 HTTP {response.status_code}", + elapsed_ms=_elapsed_ms(t0), + detail={ + "provider_key": cfg.provider_key, + "http_status": response.status_code, + "body_preview": snippet, + }, + ) + + try: + payload = response.json() + except Exception: # noqa: BLE001 + return ModelVerifyRead( + ok=False, + category=category, + message="上游返回非 JSON,无法解析模型列表", + elapsed_ms=_elapsed_ms(t0), + detail={"provider_key": cfg.provider_key, "http_status": response.status_code}, + ) + + ids: list[str] = [] + _collect_model_ids(payload, ids) + if not ids: + return ModelVerifyRead( + ok=False, + category=category, + message="未能从上游响应中解析出模型 ID 列表,请检查供应商类型与 Base URL", + elapsed_ms=_elapsed_ms(t0), + detail={"provider_key": cfg.provider_key, "http_status": response.status_code}, + ) + if not _model_list_contains(ids, model_name): + return ModelVerifyRead( + ok=False, + category=category, + message=f"模型列表未找到「{model_name}」:请确认模型名称与上游控制台一致", + elapsed_ms=_elapsed_ms(t0), + detail={ + "provider_key": cfg.provider_key, + "model_name": model_name, + "listed_count": len(ids), + }, + ) + + return ModelVerifyRead( + ok=True, + category=category, + message="验证通过(已鉴权且模型名出现在上游列表中)", + elapsed_ms=_elapsed_ms(t0), + detail={ + "provider_key": cfg.provider_key, + "model_name": model_name, + "http_status": response.status_code, + "endpoint": url, + }, + ) + + +async def verify_model_config(db: AsyncSession, *, model_id: str) -> ModelVerifyRead: + """对单条已保存模型执行配置验证;``model_id`` 不存在时抛出 ``HTTPException(404)``。""" + t0 = time.perf_counter() + model = await get_model(db, model_id=model_id) + provider = await db.get(Provider, model.provider_id) + if provider is None: + return ModelVerifyRead( + ok=False, + category=model.category, + message="供应商不存在或已删除,请先修复模型关联", + elapsed_ms=_elapsed_ms(t0), + detail={"model_id": model.id, "provider_id": model.provider_id}, + ) + + try: + cfg = resolve_provider_config_from_provider(provider=provider, category=model.category) + except HTTPException as exc: + detail = exc.detail + msg = detail if isinstance(detail, str) else str(detail) + return ModelVerifyRead( + ok=False, + category=model.category, + message=f"配置未通过检查:{msg}"[:500], + elapsed_ms=_elapsed_ms(t0), + detail={"reason": "provider_resolution", "http_status": exc.status_code}, + ) + + try: + if model.category == ModelCategoryKey.text: + result = await asyncio.wait_for( + _probe_text(model, provider, cfg), + timeout=_VERIFY_TIMEOUT_S, + ) + else: + result = await asyncio.wait_for( + _probe_models_list_http(cfg=cfg, model_name=model.name, category=model.category), + timeout=_VERIFY_TIMEOUT_S, + ) + except asyncio.TimeoutError: + return ModelVerifyRead( + ok=False, + category=model.category, + message="验证超时,请检查网络或稍后再试", + elapsed_ms=_elapsed_ms(t0), + detail={"provider_key": cfg.provider_key}, + ) + + # 总耗时覆盖子步骤(更接近用户感知) + return result.model_copy(update={"elapsed_ms": _elapsed_ms(t0)}) diff --git a/backend/app/services/llm/provider_bootstrap.py b/backend/app/services/llm/provider_bootstrap.py index 0e0fe250..f312c048 100644 --- a/backend/app/services/llm/provider_bootstrap.py +++ b/backend/app/services/llm/provider_bootstrap.py @@ -22,14 +22,16 @@ def bootstrap_builtin_providers() -> None: key="volcengine", display_name="火山引擎", aliases=("火山引擎", "volcengine", "volc", "doubao", "bytedance", "ark"), - supported_categories=(ModelCategoryKey.image, ModelCategoryKey.video), + # 火山方舟文本模型同样走 OpenAI-compatible Chat Completions。 + supported_categories=(ModelCategoryKey.text, ModelCategoryKey.image, ModelCategoryKey.video), default_base_url="https://ark.cn-beijing.volces.com/api/v3", ), ProviderSpec( key="aliyun_bailian", display_name="阿里百炼", aliases=("阿里百炼", "aliyun", "bailian", "dashscope"), - supported_categories=(ModelCategoryKey.text,), + # 图片 / 视频:在兼容域名下使用与 OpenAI 相同的 images、videos 路径;是否开通以控制台与地域为准。 + supported_categories=(ModelCategoryKey.text, ModelCategoryKey.image, ModelCategoryKey.video), default_base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", ), ] diff --git a/backend/app/services/llm/provider_registry.py b/backend/app/services/llm/provider_registry.py index 5b85dc70..a17c06a2 100644 --- a/backend/app/services/llm/provider_registry.py +++ b/backend/app/services/llm/provider_registry.py @@ -101,6 +101,18 @@ def resolve_provider_key_from_name(name: str) -> str: ) +def try_resolve_provider_key_from_name(name: str) -> str | None: + """解析已注册供应商 key;未知名称视为自定义供应商并返回 None。""" + try: + return resolve_provider_key_from_name(name) + except HTTPException as exc: + if exc.status_code == status.HTTP_503_SERVICE_UNAVAILABLE and str(exc.detail).startswith( + "Unsupported provider name:" + ): + return None + raise + + def is_provider_category_supported(provider_key: str, category: ModelCategoryKey) -> bool: spec = get_provider_spec(provider_key) return category in spec.supported_categories diff --git a/backend/app/services/llm/provider_resolver.py b/backend/app/services/llm/provider_resolver.py index ed4eb439..e9c4ab19 100644 --- a/backend/app/services/llm/provider_resolver.py +++ b/backend/app/services/llm/provider_resolver.py @@ -11,7 +11,7 @@ from app.services.llm.provider_registry import ( get_provider_spec, is_provider_category_supported, - resolve_provider_key_from_name, + try_resolve_provider_key_from_name, ) @@ -58,7 +58,12 @@ def resolve_provider_config_from_provider( ) -> ResolvedProviderConfig: bootstrap_all_registries() _validate_provider_status(provider) - provider_key = resolve_provider_key_from_name(provider.name) + provider_key = try_resolve_provider_key_from_name(provider.name) + if provider_key is None: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail=f"Custom provider has no registered task adapter: provider_id={provider.id}", + ) if not is_provider_category_supported(provider_key, category): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -91,19 +96,20 @@ def resolve_effective_base_url( category: ModelCategoryKey, provider_key: str | None = None, ) -> str | None: - """按类别解析 Provider 实际 base_url:类别覆盖 > 通用 > 内置默认。""" + """按类别解析 Provider 实际 base_url;自定义供应商没有内置默认值。""" bootstrap_all_registries() - key = provider_key or resolve_provider_key_from_name(provider.name) - spec = get_provider_spec(key) + key = provider_key or try_resolve_provider_key_from_name(provider.name) + spec = get_provider_spec(key) if key else None + default_base_url = spec.default_base_url if spec else None common_base = (provider.base_url or "").strip() image_base = (provider.image_base_url or "").strip() video_base = (provider.video_base_url or "").strip() if category == ModelCategoryKey.image: - return image_base or common_base or spec.default_base_url + return image_base or common_base or default_base_url if category == ModelCategoryKey.video: - return video_base or common_base or spec.default_base_url + return video_base or common_base or default_base_url # text 仍使用通用 base_url;保留内置默认值兜底。 - return common_base or spec.default_base_url + return common_base or default_base_url async def resolve_provider_config_by_model( diff --git a/backend/app/services/llm/resolver.py b/backend/app/services/llm/resolver.py index a0792ac0..3cf90e79 100644 --- a/backend/app/services/llm/resolver.py +++ b/backend/app/services/llm/resolver.py @@ -102,6 +102,30 @@ async def get_provider_by_model_or_id(db: AsyncSession, model_or_id: Model | str return provider +def build_chat_model_for_model( + *, + provider: Provider, + model: Model, + thinking: bool = False, +) -> BaseChatModel: + """按已解析的 Model + Provider 构造 ChatOpenAI(用于脚本外显式模型,如配置验证)。 + + 与 ``build_chat_model_from_provider`` 不同:后者会选取该供应商下任意一条最新文本模型, + 本函数严格使用传入的 ``model``(须为 ``text`` 类别)。 + """ + if model.category != ModelCategoryKey.text: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Not a text model: model_id={model.id} category={model.category.value}", + ) + return _build_chat_openai_model( + provider=provider, + model=model, + thinking=thinking, + import_error_detail="Install langchain-openai to verify text model configuration", + ) + + async def build_chat_model_from_provider( db: AsyncSession, provider_or_id: Provider | str, diff --git a/backend/app/services/studio/chapter_timeline.py b/backend/app/services/studio/chapter_timeline.py new file mode 100644 index 00000000..eba29e7b --- /dev/null +++ b/backend/app/services/studio/chapter_timeline.py @@ -0,0 +1,192 @@ +"""章节视频时间线:读取合并默认镜头顺序与持久化片段,以及全量保存。""" + +from __future__ import annotations + +import uuid +from collections.abc import Sequence + +from sqlalchemy import delete, select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.studio import ( + ChapterTimelineSegment, + ChapterTimelineState, + FileItem, + FileType, + Shot, +) +from app.services.studio.chapter_timeline_media import probe_video_duration_ms_from_storage +from app.services.studio.chapter_timeline_trim import ( + resolve_effective_trim_ms, + validate_effective_trim_ms, +) +from app.schemas.studio.chapter_timeline import ( + ChapterTimelineRead, + ChapterTimelineSegmentRead, + ChapterTimelineWrite, + TimelineClipStatus, +) + + +class TimelineLayoutConflictError(Exception): + """客户端提交的 layout_version 与服务器不一致(乐观锁冲突)。""" + + def __init__(self, *, server_version: int, client_version: int) -> None: + self.server_version = server_version + self.client_version = client_version + super().__init__( + f"layout_version mismatch: server={server_version}, client={client_version}", + ) + + +def _clip_status_and_file_id( + shot: Shot, + files_by_id: dict[str, FileItem], +) -> tuple[TimelineClipStatus, str | None]: + fid = shot.generated_video_file_id + if not fid: + return TimelineClipStatus.missing_video, None + item = files_by_id.get(fid) + if item is None or item.type != FileType.video: + return TimelineClipStatus.file_missing, fid + return TimelineClipStatus.ready, fid + + +def _merge_shot_order( + segments_db: Sequence[ChapterTimelineSegment], + shots_sorted: Sequence[Shot], +) -> list[tuple[ChapterTimelineSegment | None, Shot]]: + """先按已保存 position 排列镜头,再将未出现在时间线中的镜头按 index 追加。""" + by_shot: dict[str, Shot] = {s.id: s for s in shots_sorted} + ordered: list[tuple[ChapterTimelineSegment | None, Shot]] = [] + seen: set[str] = set() + for seg in sorted(segments_db, key=lambda x: x.position): + sh = by_shot.get(seg.shot_id) + if sh is None: + continue + ordered.append((seg, sh)) + seen.add(seg.shot_id) + for sh in sorted(shots_sorted, key=lambda x: x.index): + if sh.id not in seen: + ordered.append((None, sh)) + seen.add(sh.id) + return ordered + + +async def build_timeline_read(db: AsyncSession, chapter_id: str) -> ChapterTimelineRead: + """构造章节时间线读取视图(假设 chapter 已存在)。""" + state = await db.get(ChapterTimelineState, chapter_id) + layout_version = int(state.layout_version) if state else 1 + + shots_res = await db.execute(select(Shot).where(Shot.chapter_id == chapter_id).order_by(Shot.index)) + shots = list(shots_res.scalars().all()) + + seg_res = await db.execute( + select(ChapterTimelineSegment).where(ChapterTimelineSegment.chapter_id == chapter_id), + ) + segments_db = list(seg_res.scalars().all()) + + file_ids = [s.generated_video_file_id for s in shots if s.generated_video_file_id] + files_by_id: dict[str, FileItem] = {} + if file_ids: + fres = await db.execute(select(FileItem).where(FileItem.id.in_(file_ids))) + for fi in fres.scalars().all(): + files_by_id[fi.id] = fi + + merged = _merge_shot_order(segments_db, shots) + reads: list[ChapterTimelineSegmentRead] = [] + for position, (seg_row, shot) in enumerate(merged): + status, fid = _clip_status_and_file_id(shot, files_by_id) + sid = seg_row.id if seg_row else "" + trim_start = seg_row.trim_start_ms if seg_row else None + trim_end = seg_row.trim_end_ms if seg_row else None + reads.append( + ChapterTimelineSegmentRead( + id=sid, + shot_id=shot.id, + position=position, + trim_start_ms=trim_start, + trim_end_ms=trim_end, + clip_status=status, + file_id=fid, + label=shot.title, + ), + ) + + return ChapterTimelineRead( + layout_version=layout_version, + segments=reads, + ) + + +async def replace_timeline_segments( + db: AsyncSession, + chapter_id: str, + body: ChapterTimelineWrite, +) -> ChapterTimelineRead: + """事务内全量替换片段表并递增 layout_version。 + + - 校验镜头归属本章节且无重复 shot_id; + - 若提供 layout_version,须与当前服务端版本一致。 + """ + shots_res = await db.execute(select(Shot).where(Shot.chapter_id == chapter_id)) + shots = {s.id: s for s in shots_res.scalars().all()} + + seen_shots: set[str] = set() + for row in body.segments: + if row.shot_id not in shots: + raise ValueError(f"shot_id 不属于该章节: {row.shot_id}") + if row.shot_id in seen_shots: + raise ValueError(f"重复的 shot_id: {row.shot_id}") + seen_shots.add(row.shot_id) + + for row in body.segments: + if row.trim_start_ms is None and row.trim_end_ms is None: + continue + shot = shots[row.shot_id] + fid = shot.generated_video_file_id + if not fid: + raise ValueError(f"镜头尚无成片,无法设置裁剪: shot_id={row.shot_id}") + file_obj = await db.get(FileItem, fid) + if file_obj is None or file_obj.type != FileType.video or not file_obj.storage_key: + raise ValueError(f"镜头成片文件无效,无法设置裁剪: shot_id={row.shot_id}") + dur_ms = await probe_video_duration_ms_from_storage(file_obj.storage_key) + if dur_ms <= 0: + raise ValueError(f"无法解析镜头成片时长: shot_id={row.shot_id}") + effective = resolve_effective_trim_ms(dur_ms, row.trim_start_ms, row.trim_end_ms) + if effective is None: + continue + validate_effective_trim_ms(dur_ms, effective, shot_id=row.shot_id) + + state = await db.get(ChapterTimelineState, chapter_id) + current_v = int(state.layout_version) if state else 1 + if body.layout_version is not None and body.layout_version != current_v: + raise TimelineLayoutConflictError(server_version=current_v, client_version=body.layout_version) + + await db.execute(delete(ChapterTimelineSegment).where(ChapterTimelineSegment.chapter_id == chapter_id)) + + for position, row in enumerate(body.segments): + db.add( + ChapterTimelineSegment( + id=str(uuid.uuid4()), + chapter_id=chapter_id, + shot_id=row.shot_id, + position=position, + trim_start_ms=row.trim_start_ms, + trim_end_ms=row.trim_end_ms, + ), + ) + + new_version = current_v + 1 + if state is None: + db.add( + ChapterTimelineState( + chapter_id=chapter_id, + layout_version=new_version, + ), + ) + else: + state.layout_version = new_version + + await db.flush() + return await build_timeline_read(db, chapter_id) diff --git a/backend/app/services/studio/chapter_timeline_export.py b/backend/app/services/studio/chapter_timeline_export.py new file mode 100644 index 00000000..064e3927 --- /dev/null +++ b/backend/app/services/studio/chapter_timeline_export.py @@ -0,0 +1,51 @@ +"""章节时间线导出:活跃任务检测、可导出性校验与任务入队参数。""" + +from __future__ import annotations + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.task import GenerationTask, GenerationTaskStatus +from app.models.task_links import GenerationTaskLink +from app.schemas.studio.chapter_timeline import ChapterTimelineRead, TimelineClipStatus + + +EXPORT_TASK_KIND = "chapter_timeline_export" +EXPORT_RESOURCE_TYPE = "video" +EXPORT_RELATION_TYPE = "chapter_timeline_export" + + +async def find_active_chapter_timeline_export_task_id( + db: AsyncSession, + chapter_id: str, +) -> str | None: + """若存在进行中的章节时间线导出任务则返回其 task_id,否则 None。""" + stmt = ( + select(GenerationTask.id) + .join(GenerationTaskLink, GenerationTaskLink.task_id == GenerationTask.id) + .where( + GenerationTask.task_kind == EXPORT_TASK_KIND, + GenerationTaskLink.resource_type == EXPORT_RESOURCE_TYPE, + GenerationTaskLink.relation_type == EXPORT_RELATION_TYPE, + GenerationTaskLink.relation_entity_id == chapter_id, + GenerationTask.status.in_( + ( + GenerationTaskStatus.pending, + GenerationTaskStatus.running, + ), + ), + ) + .limit(1) + ) + return (await db.execute(stmt)).scalar_one_or_none() + + +def ensure_timeline_exportable(read: ChapterTimelineRead) -> None: + """导出前置校验:时间线非空且全部片段具备可用成片文件。""" + if not read.segments: + raise ValueError("时间线为空,无法导出") + for seg in read.segments: + if seg.clip_status != TimelineClipStatus.ready: + raise ValueError( + f"存在未就绪片段:shot_id={seg.shot_id} status={seg.clip_status.value}", + ) diff --git a/backend/app/services/studio/chapter_timeline_export_task.py b/backend/app/services/studio/chapter_timeline_export_task.py new file mode 100644 index 00000000..7093fce3 --- /dev/null +++ b/backend/app/services/studio/chapter_timeline_export_task.py @@ -0,0 +1,322 @@ +"""章节时间线导出异步 Runner:校验 chapter→shot→file,拼接 FFmpeg,写入成片与 FileUsage。""" + +from __future__ import annotations + +import asyncio +import tempfile +import uuid +from pathlib import Path + +from sqlalchemy import select + +from app.core import storage +from app.core.db import async_session_maker +from app.core.task_manager import SqlAlchemyTaskStore +from app.core.task_manager.types import TaskStatus +from app.models.studio import Chapter, FileItem, FileType, Shot +from app.models.task_links import GenerationTaskLink +from app.models.types import FileUsageKind +from app.schemas.studio.chapter_timeline import ChapterTimelineSegmentRead +from app.services.studio.chapter_timeline import build_timeline_read +from app.services.studio.chapter_timeline_export import ( + EXPORT_RELATION_TYPE, + EXPORT_RESOURCE_TYPE, + ensure_timeline_exportable, +) +from app.services.studio.chapter_timeline_media import ffprobe_local_file, probe_duration_and_audio +from app.services.studio.chapter_timeline_trim import ( + is_lossless_compatible_trim, + source_duration_ms_from_seconds, + trim_seconds_for_ffmpeg, +) +from app.services.studio.file_usages import upsert_file_usage +from app.services.worker.async_task_support import cancel_if_requested_async +from app.services.worker.task_logging import log_task_event, log_task_failure + + +def _video_stream_meta(probe: dict) -> dict | None: + for stream in probe.get("streams") or []: + if stream.get("codec_type") == "video": + return stream + return None + + +def _collect_lossless_identity(meta: dict) -> tuple[str, int, int, str]: + """返回用于无损拼接比对的 (codec, width, height, pix_fmt)。""" + return ( + str(meta.get("codec_name") or ""), + int(meta.get("width") or 0), + int(meta.get("height") or 0), + str(meta.get("pix_fmt") or ""), + ) + + +def build_uniform_transcode_concat_filter(segments: list[tuple[float, float, bool]]) -> str: + """构造 concat 的 filter_complex:每段先 trim 再拼接音画。 + + ``segments``:每项为 ``(trim_start_s, trim_end_s, has_audio)``;无音轨时用 ``anullsrc`` 补满 ``trim`` 后时长。 + """ + n = len(segments) + if n == 0: + raise ValueError("empty segment list for concat filter") + parts: list[str] = [] + for i, (start_s, end_s, has_audio) in enumerate(segments): + clip_dur = end_s - start_s + if clip_dur <= 0: + raise ValueError(f"invalid trim duration at index {i}: {clip_dur}") + parts.append( + f"[{i}:v:0]trim=start={start_s:.6f}:end={end_s:.6f},setpts=PTS-STARTPTS,setsar=1[v{i}]", + ) + if has_audio: + parts.append( + f"[{i}:a:0]atrim=start={start_s:.6f}:end={end_s:.6f},asetpts=PTS-STARTPTS," + f"aformat=sample_fmts=fltp:sample_rates=48000:channel_layouts=stereo[a{i}]", + ) + else: + parts.append( + f"anullsrc=channel_layout=stereo:sample_rate=48000," + f"atrim=end={clip_dur:.6f},asetpts=PTS-STARTPTS[a{i}]", + ) + concat_in = "".join(f"[v{i}][a{i}]" for i in range(n)) + parts.append(f"{concat_in}concat=n={n}:v=1:a=1[outv][outa]") + return ";".join(parts) + + +async def _ffmpeg_concat_copy(list_file: Path, output: Path) -> None: + proc = await asyncio.create_subprocess_exec( + "ffmpeg", + "-y", + "-f", + "concat", + "-safe", + "0", + "-i", + str(list_file), + "-c", + "copy", + str(output), + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + _, stderr = await proc.communicate() + if proc.returncode != 0: + msg = (stderr or b"").decode("utf-8", errors="replace")[:4000] + raise RuntimeError(f"ffmpeg concat copy 失败: {msg}") + + +async def _ffmpeg_concat_reencode( + inputs: list[Path], + timeline_segments: list[ChapterTimelineSegmentRead], + output: Path, +) -> None: + """统一转码拼接:每段按 trim 裁剪后输出 H.264 + AAC。""" + if len(inputs) != len(timeline_segments): + raise RuntimeError("片段文件数与时间线条目数不一致") + + specs: list[tuple[float, float, bool]] = [] + for path, seg in zip(inputs, timeline_segments, strict=True): + probe = await ffprobe_local_file(path) + dur_s, has_audio = probe_duration_and_audio(probe) + if dur_s <= 0: + raise RuntimeError(f"无法解析片段时长: {path}") + start_s, end_s = trim_seconds_for_ffmpeg(dur_s, seg.trim_start_ms, seg.trim_end_ms) + if end_s - start_s <= 0: + raise RuntimeError(f"裁剪后时长无效: shot_id={seg.shot_id}") + specs.append((start_s, end_s, has_audio)) + + args: list[str] = ["ffmpeg", "-y"] + for p in inputs: + args += ["-i", str(p)] + filt = build_uniform_transcode_concat_filter(specs) + args += [ + "-filter_complex", + filt, + "-map", + "[outv]", + "-map", + "[outa]", + "-c:v", + "libx264", + "-c:a", + "aac", + "-b:a", + "192k", + "-pix_fmt", + "yuv420p", + "-movflags", + "+faststart", + str(output), + ] + proc = await asyncio.create_subprocess_exec( + *args, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + _, stderr = await proc.communicate() + if proc.returncode != 0: + msg = (stderr or b"").decode("utf-8", errors="replace")[:4000] + raise RuntimeError(f"ffmpeg 统一转码拼接失败: {msg}") + + +async def run_chapter_timeline_export_task(task_id: str, run_args: dict) -> None: + """执行章节时间线导出:不信任 run_args 中的 file_id,仅使用 chapter_id / encode_mode。""" + chapter_id = str(run_args.get("chapter_id") or "").strip() + encode_mode = str(run_args.get("encode_mode") or "uniform_transcode").strip() + if not chapter_id: + raise RuntimeError("run_args 缺少 chapter_id") + if encode_mode not in {"uniform_transcode", "lossless_concat_only"}: + raise RuntimeError(f"不支持的 encode_mode: {encode_mode}") + + async with async_session_maker() as session: + try: + store = SqlAlchemyTaskStore(session) + await store.set_status(task_id, TaskStatus.running) + await store.set_progress(task_id, 5) + await session.commit() + log_task_event("chapter_timeline_export", task_id, "running") + + if await cancel_if_requested_async(store=store, task_id=task_id, session=session): + log_task_event("chapter_timeline_export", task_id, "cancelled", stage="before_execute") + return + + read = await build_timeline_read(session, chapter_id) + ensure_timeline_exportable(read) + chapter = await session.get(Chapter, chapter_id) + if chapter is None: + raise RuntimeError(f"章节不存在: {chapter_id}") + project_id = chapter.project_id + + ordered_files: list[tuple[Shot, FileItem]] = [] + for seg in read.segments: + shot = await session.get(Shot, seg.shot_id) + if shot is None or shot.chapter_id != chapter_id: + raise RuntimeError(f"镜头不属于该章节: {seg.shot_id}") + fid = shot.generated_video_file_id + if not fid: + raise RuntimeError(f"镜头缺少成片文件: {seg.shot_id}") + file_obj = await session.get(FileItem, fid) + if file_obj is None or file_obj.type != FileType.video or not file_obj.storage_key: + raise RuntimeError(f"镜头成片文件无效: {seg.shot_id}") + ordered_files.append((shot, file_obj)) + + if not ordered_files: + raise RuntimeError("时间线为空,无法导出") + + await store.set_progress(task_id, 20) + await session.commit() + + with tempfile.TemporaryDirectory(prefix=f"jf-ch-export-{task_id}-") as tmp: + tmp_path = Path(tmp) + local_inputs: list[Path] = [] + for idx, (_shot, file_obj) in enumerate(ordered_files): + data = await storage.download_file(key=file_obj.storage_key) + if not data: + raise RuntimeError(f"无法下载镜头成片: {file_obj.id}") + dest = tmp_path / f"clip_{idx:04d}.mp4" + dest.write_bytes(data) + local_inputs.append(dest) + + out_file = tmp_path / "master.mp4" + + if encode_mode == "lossless_concat_only": + for seg, p in zip(read.segments, local_inputs, strict=True): + probe = await ffprobe_local_file(p) + dur_s, _ = probe_duration_and_audio(probe) + dur_ms = source_duration_ms_from_seconds(dur_s) + if not is_lossless_compatible_trim(dur_ms, seg.trim_start_ms, seg.trim_end_ms): + raise RuntimeError( + "lossless_concat_only 不支持裁剪片段,请改用 uniform_transcode", + ) + identities: list[tuple[str, int, int, str]] = [] + for p in local_inputs: + probe = await ffprobe_local_file(p) + vmeta = _video_stream_meta(probe) + if vmeta is None: + raise RuntimeError("lossless_concat_only:输入缺少视频流") + identities.append(_collect_lossless_identity(vmeta)) + first = identities[0] + if any(x != first for x in identities): + raise RuntimeError( + "lossless_concat_only:片段视频编码/分辨率/像素格式不一致,无法无损拼接", + ) + list_file = tmp_path / "concat.txt" + lines: list[str] = [] + for p in local_inputs: + escaped = str(p).replace("'", "'\\''") + lines.append(f"file '{escaped}'") + list_file.write_text("\n".join(lines), encoding="utf-8") + await _ffmpeg_concat_copy(list_file, out_file) + else: + await _ffmpeg_concat_reencode(local_inputs, read.segments, out_file) + + master_bytes = out_file.read_bytes() + if not master_bytes: + raise RuntimeError("导出结果为空文件") + + out_key = f"generated-videos/chapters/{chapter_id}/timeline/{uuid.uuid4().hex}.mp4" + info = await storage.upload_file( + key=out_key, + data=master_bytes, + content_type="video/mp4", + extra_args={"ACL": "public-read"}, + ) + + new_file_id = str(uuid.uuid4()) + session.add( + FileItem( + id=new_file_id, + type=FileType.video, + name=f"chapter-{chapter_id}-timeline-master", + thumbnail=info.url, + tags=[], + storage_key=out_key, + ), + ) + + link_stmt = ( + select(GenerationTaskLink) + .where( + GenerationTaskLink.task_id == task_id, + GenerationTaskLink.resource_type == EXPORT_RESOURCE_TYPE, + GenerationTaskLink.relation_type == EXPORT_RELATION_TYPE, + GenerationTaskLink.relation_entity_id == chapter_id, + ) + .limit(1) + ) + link_row = (await session.execute(link_stmt)).scalars().first() + if link_row is not None: + link_row.file_id = new_file_id + + await upsert_file_usage( + session, + file_id=new_file_id, + project_id=project_id, + chapter_id=chapter_id, + shot_id=None, + usage_kind=FileUsageKind.chapter_master_video, + source_ref=f"chapter:{chapter_id}:timeline_export:{task_id}", + ) + + await store.set_result( + task_id, + { + "file_id": new_file_id, + "chapter_id": chapter_id, + "encode_mode": encode_mode, + }, + ) + if await cancel_if_requested_async(store=store, task_id=task_id, session=session): + log_task_event("chapter_timeline_export", task_id, "cancelled", stage="after_persist") + return + await store.set_progress(task_id, 100) + await store.set_status(task_id, TaskStatus.succeeded) + await session.commit() + log_task_event("chapter_timeline_export", task_id, "succeeded") + except Exception as exc: # noqa: BLE001 + await session.rollback() + async with async_session_maker() as s2: + store = SqlAlchemyTaskStore(s2) + await store.set_error(task_id, str(exc)) + await store.set_status(task_id, TaskStatus.failed) + await s2.commit() + log_task_failure("chapter_timeline_export", task_id, str(exc)) diff --git a/backend/app/services/studio/chapter_timeline_media.py b/backend/app/services/studio/chapter_timeline_media.py new file mode 100644 index 00000000..617d040a --- /dev/null +++ b/backend/app/services/studio/chapter_timeline_media.py @@ -0,0 +1,79 @@ +"""章节时间线相关媒体探测:ffprobe 解析时长与音轨存在性。""" + +from __future__ import annotations + +import asyncio +import json +import tempfile +from pathlib import Path + +from app.core import storage + + +async def _read_json_subprocess(cmd: list[str]) -> dict: + proc = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await proc.communicate() + if proc.returncode != 0: + msg = (stderr or b"").decode("utf-8", errors="replace")[:2000] + raise RuntimeError(f"子进程失败 ({proc.returncode}): {msg}") + return json.loads(stdout.decode("utf-8")) + + +async def ffprobe_local_file(path: Path) -> dict: + """对本地文件执行 ffprobe,返回 JSON(含 format 与 streams)。""" + return await _read_json_subprocess( + [ + "ffprobe", + "-v", + "quiet", + "-print_format", + "json", + "-show_format", + "-show_streams", + str(path), + ], + ) + + +def probe_duration_and_audio(probe: dict) -> tuple[float, bool]: + """从 ffprobe JSON 得到时长(秒)及是否存在音频流。""" + fmt = probe.get("format") or {} + dur_raw = fmt.get("duration") + try: + dur_s = float(dur_raw) if dur_raw is not None else 0.0 + except (TypeError, ValueError): + dur_s = 0.0 + has_audio = any(s.get("codec_type") == "audio" for s in (probe.get("streams") or [])) + return dur_s, has_audio + + +async def probe_video_duration_ms_from_storage(storage_key: str) -> int: + """下载存储对象到临时文件并 ffprobe,返回成片时长毫秒(用于 PUT 裁剪校验)。""" + from app.services.studio.chapter_timeline_trim import source_duration_ms_from_seconds + + data = await storage.download_file(key=storage_key) + if not data: + msg = "无法下载成片文件,无法校验裁剪范围" + raise ValueError(msg) + tmp_path: Path | None = None + try: + with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp: + tmp.write(data) + tmp_path = Path(tmp.name) + probe = await ffprobe_local_file(tmp_path) + dur_s, _ = probe_duration_and_audio(probe) + return source_duration_ms_from_seconds(dur_s) + finally: + if tmp_path is not None: + tmp_path.unlink(missing_ok=True) + + +__all__ = [ + "ffprobe_local_file", + "probe_duration_and_audio", + "probe_video_duration_ms_from_storage", +] diff --git a/backend/app/services/studio/chapter_timeline_trim.py b/backend/app/services/studio/chapter_timeline_trim.py new file mode 100644 index 00000000..e4eb4b88 --- /dev/null +++ b/backend/app/services/studio/chapter_timeline_trim.py @@ -0,0 +1,96 @@ +"""章节时间线裁剪:毫秒坐标解析与校验(PUT 保存与 FFmpeg 导出共用)。 + +约定: +- ``trim_start_ms`` / ``trim_end_ms`` 均为可选;**皆为 None 表示使用镜头成片全长**。 +- 只要至少一端非空,即进入裁剪语义:**入点毫秒 inclusive,出点毫秒 exclusive**, playable 区间为 ``[start_ms, end_ms)``。 +- 某一端为 None 时:入点默认 0,出点默认源成片时长(毫秒)。 +""" + +from __future__ import annotations + +# ffprobe duration 与毫秒取整之间的允许误差(毫秒) +_DURATION_MS_SLACK = 2 + + +def source_duration_ms_from_seconds(duration_s: float) -> int: + """将 ffprobe format.duration 转为毫秒(四舍五入),用于与 trim 坐标对齐。""" + if duration_s <= 0: + return 0 + return max(0, int(round(float(duration_s) * 1000))) + + +def resolve_effective_trim_ms( + source_duration_ms: int, + trim_start_ms: int | None, + trim_end_ms: int | None, +) -> tuple[int, int] | None: + """若双 None 表示全长,返回 None;否则返回 ``(start_ms, end_ms)``(左闭右开)。""" + if trim_start_ms is None and trim_end_ms is None: + return None + start_ms = 0 if trim_start_ms is None else int(trim_start_ms) + end_ms = source_duration_ms if trim_end_ms is None else int(trim_end_ms) + return start_ms, end_ms + + +def validate_effective_trim_ms( + source_duration_ms: int, + effective: tuple[int, int], + *, + shot_id: str, +) -> None: + """校验裁剪区间落在 ``[0, source_duration_ms]``(出点允许 slack)内且长度为正。""" + start_ms, end_ms = effective + if start_ms < 0 or end_ms < 0: + msg = f"裁剪毫秒不可为负: shot_id={shot_id}" + raise ValueError(msg) + if start_ms >= end_ms: + msg = f"裁剪入点必须小于出点: shot_id={shot_id} start_ms={start_ms} end_ms={end_ms}" + raise ValueError(msg) + if end_ms > source_duration_ms + _DURATION_MS_SLACK: + msg = ( + f"裁剪出点超出成片时长: shot_id={shot_id} end_ms={end_ms} " + f"source_duration_ms={source_duration_ms}" + ) + raise ValueError(msg) + if start_ms > source_duration_ms + _DURATION_MS_SLACK: + msg = ( + f"裁剪入点超出成片时长: shot_id={shot_id} start_ms={start_ms} " + f"source_duration_ms={source_duration_ms}" + ) + raise ValueError(msg) + + +def is_lossless_compatible_trim( + source_duration_ms: int, + trim_start_ms: int | None, + trim_end_ms: int | None, +) -> bool: + """无损拼接仅接受全长片段(不允许实质裁剪)。""" + effective = resolve_effective_trim_ms(source_duration_ms, trim_start_ms, trim_end_ms) + if effective is None: + return True + start_ms, end_ms = effective + return start_ms <= 0 and end_ms >= source_duration_ms - _DURATION_MS_SLACK + + +def trim_seconds_for_ffmpeg( + duration_s: float, + trim_start_ms: int | None, + trim_end_ms: int | None, +) -> tuple[float, float]: + """返回 FFmpeg ``trim`` / ``atrim`` 使用的 ``start,end``(秒)。全长时用 probe 浮点时长减小舍入误差。""" + dur_ms = source_duration_ms_from_seconds(duration_s) + effective = resolve_effective_trim_ms(dur_ms, trim_start_ms, trim_end_ms) + if effective is None: + return 0.0, float(duration_s) + start_ms, end_ms = effective + return start_ms / 1000.0, end_ms / 1000.0 + + +__all__ = [ + "is_lossless_compatible_trim", + "resolve_effective_trim_ms", + "source_duration_ms_from_seconds", + "trim_seconds_for_ffmpeg", + "validate_effective_trim_ms", +] diff --git a/backend/app/services/studio/image_task_runner.py b/backend/app/services/studio/image_task_runner.py index 9b1b7849..f2e45766 100644 --- a/backend/app/services/studio/image_task_runner.py +++ b/backend/app/services/studio/image_task_runner.py @@ -341,6 +341,11 @@ async def run_image_generation_task( await task.run() result = await task.get_result() if result is None: + # AbstractImageGenerationTask 会将适配层异常吞入 status.error,避免此处丢失真实原因(如上游 404)。 + status_payload = await task.status() + upstream_err = str(status_payload.get("error") or "").strip() + if upstream_err: + raise RuntimeError(upstream_err) raise RuntimeError("Image generation task returned no result") if await cancel_if_requested_async(store=store, task_id=task_id, session=session): log_task_event("image_generation", task_id, "cancelled", stage="after_execute") diff --git a/backend/app/services/worker/task_registry.py b/backend/app/services/worker/task_registry.py index ba7035c4..fafc748a 100644 --- a/backend/app/services/worker/task_registry.py +++ b/backend/app/services/worker/task_registry.py @@ -9,6 +9,7 @@ from __future__ import annotations from app.services.film.generated_video import run_video_generation_task +from app.services.studio.chapter_timeline_export_task import run_chapter_timeline_export_task from app.services.film.shot_frame_prompt_tasks import run_shot_frame_prompt_task from app.services.script_processing_worker import ( CharacterPortraitTaskExecutor, @@ -58,6 +59,14 @@ def resolve(self, task_kind: str) -> AbstractWorkerTaskExecutor: timeout_seconds=3600.0, ), ) +task_executor_registry.register( + "chapter_timeline_export", + AbstractAsyncDelegatingExecutor( + task_kind="chapter_timeline_export", + runner=run_chapter_timeline_export_task, + timeout_seconds=7200.0, + ), +) task_executor_registry.register( "image_generation", AbstractAsyncDelegatingExecutor( diff --git a/backend/init_db.py b/backend/init_db.py index 2d36988d..3a4b6419 100644 --- a/backend/init_db.py +++ b/backend/init_db.py @@ -1,11 +1,14 @@ import asyncio from app.core.db import init_db, close_db +from app.core.storage import init_storage async def _main() -> None: - """初始化数据库,创建所有表。""" + """初始化数据库并确保对象存储 bucket 存在。""" await init_db() + # backend-init-db 容器启动时一并确保对象存储 bucket,避免运行期首次上传才暴露 NoSuchBucket。 + init_storage() await close_db() diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 7700b3b3..d4f032d9 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [ "fastapi>=0.115.0", "uvicorn[standard]>=0.32.0", "langchain>=0.3.0", + "langchain-openai>=0.2.0", "langchain-core>=0.3.0", "langgraph>=0.2.0", "httpx>=0.27.0", diff --git a/backend/scripts/verify_dashscope_image.py b/backend/scripts/verify_dashscope_image.py new file mode 100644 index 00000000..9429a280 --- /dev/null +++ b/backend/scripts/verify_dashscope_image.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""线下验证阿里云百炼 DashScope 文生图(不走 Celery / DB)。 + +成功标准:HTTP 200 且解析出至少一张图片 URL。 + +用法(在 backend 目录):: + + export DASHSCOPE_API_KEY='你的API-Key' + uv run python scripts/verify_dashscope_image.py + +可选:: + + export DASHSCOPE_BASE_URL='https://dashscope.aliyuncs.com' # 默认北京 + uv run python scripts/verify_dashscope_image.py --model qwen-image-2.0-pro + +若本脚本失败,请把终端完整输出(含 request_id)对照控制台用量/内容审核后再排应用层问题。 +""" + +from __future__ import annotations + +import argparse +import asyncio +import os +import sys +from pathlib import Path + +# 保证可导入 app(脚本在 backend/scripts/) +_BACKEND_ROOT = Path(__file__).resolve().parent.parent +if str(_BACKEND_ROOT) not in sys.path: + sys.path.insert(0, str(_BACKEND_ROOT)) + +VERIFY_PROMPT = """视觉风格:现实 +画面风格:真人都市 + +高质量电影级现实人像摄影,超详细专业演员写真: +25 岁男性,面容清俊克制,肤质细腻白皙,五官端正立体。黑色短发修剪整齐,眼神深邃锐利且带有内在的压迫感。身穿浅蓝色衬衫搭配深灰色修身西装外套,领口整洁,整体造型温和朴素,干净利落不张扬。身材修长挺拔,站姿沉稳,气质中完美融合表面的温和与隐忍的大佬气场,背景呈现现代都市办公环境,光线明亮柔和,突出人物面部细节与精神面貌。 + +镜头方向:正面 +画面要求: +- 超高细节,8k分辨率,极致锐度与纹理 +- 电影感浅景深,f/1.4大光圈,背景虚化自然明显 +- 专业商业人像摄影风格 + 当代电影剧照质感 +- 自然生动表情,富有故事感和角色沉浸感 +- 优秀构图,经典三分法或黄金分割构图 +- 完美贴合现实视觉语言与真人都市整体氛围,风格高度一致 + +负面提示(强烈负面): +low quality, worst quality, blurry, deformed, bad anatomy, bad hands, missing fingers, extra limbs, poorly drawn face, bad proportions, watermark, text, logo, signature, overexposed, underexposed, plastic skin, doll, lowres, jpeg artifacts, grainycartoon, 3d render, cgi, illustration, painting, sketch, anime""" + + +async def _main() -> int: + parser = argparse.ArgumentParser(description="Verify DashScope text-to-image (Aliyun Bailian)") + parser.add_argument( + "--model", + default=os.environ.get("DASHSCOPE_IMAGE_MODEL", "qwen-image-2.0-pro"), + help="DashScope 图片模型名(默认 qwen-image-2.0-pro 或环境变量 DASHSCOPE_IMAGE_MODEL)", + ) + args = parser.parse_args() + + api_key = (os.environ.get("DASHSCOPE_API_KEY") or "").strip() + if not api_key: + print("错误:请设置环境变量 DASHSCOPE_API_KEY", file=sys.stderr) + return 2 + + from app.core.contracts.image_generation import ImageGenerationInput + from app.core.contracts.provider import ProviderConfig + from app.core.integrations.aliyun.dashscope_images import DashScopeImageApiAdapter + + base_url = (os.environ.get("DASHSCOPE_BASE_URL") or "").strip() or "https://dashscope.aliyuncs.com/compatible-mode/v1" + cfg = ProviderConfig(provider="aliyun_bailian", api_key=api_key, base_url=base_url) + inp = ImageGenerationInput(prompt=VERIFY_PROMPT, model=args.model, n=1, watermark=False) + + print(f"model={args.model}") + print("说明: base_url 若含 compatible-mode,请求会规范到 DashScope 根域名再调用文生图 API") + try: + result = await DashScopeImageApiAdapter().generate(cfg=cfg, inp=inp, timeout_s=180.0) + except Exception as exc: # noqa: BLE001 + print(f"失败: {exc}", file=sys.stderr) + return 1 + + if not result.images or not result.images[0].url: + print("失败: 无图片 URL", file=sys.stderr) + return 1 + + print("成功: 首张图 URL(可复制到浏览器验证):") + print(result.images[0].url) + return 0 + + +if __name__ == "__main__": + raise SystemExit(asyncio.run(_main())) diff --git a/backend/sql/009-chapter-timeline.sql b/backend/sql/009-chapter-timeline.sql new file mode 100644 index 00000000..1cec7660 --- /dev/null +++ b/backend/sql/009-chapter-timeline.sql @@ -0,0 +1,78 @@ +-- 章节时间线:状态行(乐观锁版本)与片段表(每章节内每镜至多一条,按 position 排序) + +SET @has_chapter_timeline_states = ( + SELECT COUNT(*) + FROM information_schema.TABLES + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'chapter_timeline_states' +); + +SET @create_chapter_timeline_states = IF( + @has_chapter_timeline_states = 0, + 'CREATE TABLE chapter_timeline_states ( + chapter_id VARCHAR(64) NOT NULL COMMENT ''章节 ID'', + updated_at DATETIME(6) NOT NULL COMMENT ''最近保存时间(UTC)'', + layout_version INT NOT NULL DEFAULT 1 COMMENT ''布局版本(乐观锁,每次成功保存递增)'', + PRIMARY KEY (chapter_id), + CONSTRAINT fk_cts_chapter FOREIGN KEY (chapter_id) REFERENCES chapters(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4', + 'SELECT 1' +); +PREPARE stmt_cts FROM @create_chapter_timeline_states; +EXECUTE stmt_cts; +DEALLOCATE PREPARE stmt_cts; + +-- 兼容早期未带 DEFAULT 的状态表结构:确保 updated_at 自动赋值 +SET @fix_cts_updated_at_default = IF( + @has_chapter_timeline_states = 1, + 'ALTER TABLE chapter_timeline_states + MODIFY updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT ''最近保存时间(UTC)''', + 'SELECT 1' +); +PREPARE stmt_fix_cts_ts FROM @fix_cts_updated_at_default; +EXECUTE stmt_fix_cts_ts; +DEALLOCATE PREPARE stmt_fix_cts_ts; + +SET @has_chapter_timeline_segments = ( + SELECT COUNT(*) + FROM information_schema.TABLES + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'chapter_timeline_segments' +); + +SET @create_chapter_timeline_segments = IF( + @has_chapter_timeline_segments = 0, + 'CREATE TABLE chapter_timeline_segments ( + id VARCHAR(64) NOT NULL COMMENT ''片段行 ID(UUID)'', + chapter_id VARCHAR(64) NOT NULL COMMENT ''所属章节'', + shot_id VARCHAR(64) NOT NULL COMMENT ''对应镜头'', + position INT NOT NULL COMMENT ''从 0 起的播放顺序'', + trim_start_ms INT NULL COMMENT ''入点毫秒(P2;空表示从头)'', + trim_end_ms INT NULL COMMENT ''出点毫秒(P2;空表示到尾)'', + created_at DATETIME(6) NOT NULL COMMENT ''创建时间'', + updated_at DATETIME(6) NOT NULL COMMENT ''更新时间'', + PRIMARY KEY (id), + UNIQUE KEY uq_chapter_shot (chapter_id, shot_id), + UNIQUE KEY uq_chapter_position (chapter_id, position), + KEY ix_segment_chapter (chapter_id), + KEY ix_segment_shot (shot_id), + CONSTRAINT fk_seg_chapter FOREIGN KEY (chapter_id) REFERENCES chapters(id) ON DELETE CASCADE, + CONSTRAINT fk_seg_shot FOREIGN KEY (shot_id) REFERENCES shots(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4', + 'SELECT 1' +); +PREPARE stmt_seg FROM @create_chapter_timeline_segments; +EXECUTE stmt_seg; +DEALLOCATE PREPARE stmt_seg; + +-- 兼容早期未带 DEFAULT 的表结构:确保 created_at/updated_at 可自动赋值 +SET @fix_seg_timestamp_default = IF( + @has_chapter_timeline_segments = 1, + 'ALTER TABLE chapter_timeline_segments + MODIFY created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT ''创建时间'', + MODIFY updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT ''更新时间''', + 'SELECT 1' +); +PREPARE stmt_fix_seg_ts FROM @fix_seg_timestamp_default; +EXECUTE stmt_fix_seg_ts; +DEALLOCATE PREPARE stmt_fix_seg_ts; diff --git a/backend/tests/core/integrations/test_dashscope_images.py b/backend/tests/core/integrations/test_dashscope_images.py new file mode 100644 index 00000000..82a623a2 --- /dev/null +++ b/backend/tests/core/integrations/test_dashscope_images.py @@ -0,0 +1,109 @@ +"""DashScope 文生图响应解析单测。""" + +from __future__ import annotations + +from app.core.integrations.aliyun.dashscope_images import ( + _build_multimodal_user_content, + _parse_multimodal_sync_response, + _split_dashscope_prompt_and_negative, +) +from app.core.contracts.image_generation import ImageGenerationInput, InputImageRef + + +def test_split_prompt_extracts_negative_section() -> None: + """含「负面提示」段落时,应拆到 parameters.negative_prompt(与 Studio 长提示一致)。""" + raw = """画面:都市人像 + +负面提示(强烈负面): +blur, low quality""" + pos, neg = _split_dashscope_prompt_and_negative(raw) + assert "都市人像" in pos + assert "blur" in neg + assert "负面提示" not in pos + + +def test_split_prompt_no_marker_returns_full_as_positive() -> None: + pos, neg = _split_dashscope_prompt_and_negative("仅正面描述") + assert pos == "仅正面描述" + assert neg == "" + + +def test_build_multimodal_user_content_includes_reference_images_in_order() -> None: + inp = ImageGenerationInput( + prompt="主提示词", + model="qwen-image-2.0-pro", + images=[ + InputImageRef(image_url="data:image/png;base64,aaa"), + InputImageRef(image_url="https://example.com/ref-2.png"), + ], + ) + content = _build_multimodal_user_content(inp, positive_prompt="正向提示") + assert content[0]["text"] == "正向提示" + assert content[1]["image"] == "data:image/png;base64,aaa" + assert content[2]["image"] == "https://example.com/ref-2.png" + + +def test_build_multimodal_user_content_limits_reference_images_to_three() -> None: + inp = ImageGenerationInput( + prompt="主提示词", + model="qwen-image-2.0-pro", + images=[ + InputImageRef(image_url="https://example.com/ref-1.png"), + InputImageRef(image_url="https://example.com/ref-2.png"), + InputImageRef(image_url="https://example.com/ref-3.png"), + InputImageRef(image_url="https://example.com/ref-4.png"), + ], + ) + content = _build_multimodal_user_content(inp, positive_prompt="正向提示") + image_items = [item["image"] for item in content if "image" in item] + assert image_items == [ + "https://example.com/ref-1.png", + "https://example.com/ref-2.png", + "https://example.com/ref-3.png", + ] + + +def test_parse_multimodal_sync_accepts_content_without_type_field() -> None: + """qwen-image 等模型可能返回 content 项仅含 image URL,无 type 字段。""" + data = { + "output": { + "choices": [ + { + "finish_reason": "stop", + "message": { + "role": "assistant", + "content": [ + { + "image": "https://example.com/out.png?sig=1", + } + ], + }, + } + ] + }, + "usage": {"height": 1280, "image_count": 1, "width": 1280}, + "request_id": "req-1", + } + result = _parse_multimodal_sync_response(data) + assert len(result.images) == 1 + assert result.images[0].url == "https://example.com/out.png?sig=1" + assert result.provider == "aliyun_bailian" + + +def test_parse_multimodal_sync_still_accepts_explicit_type_image() -> None: + """兼容文档示例:content 项带 type=image。""" + data = { + "output": { + "choices": [ + { + "message": { + "content": [ + {"type": "image", "image": "https://example.com/a.png"}, + ], + }, + } + ] + }, + } + result = _parse_multimodal_sync_response(data) + assert result.images[0].url == "https://example.com/a.png" diff --git a/backend/tests/core/integrations/test_image_adapters.py b/backend/tests/core/integrations/test_image_adapters.py index 395b7801..d6ee6942 100644 --- a/backend/tests/core/integrations/test_image_adapters.py +++ b/backend/tests/core/integrations/test_image_adapters.py @@ -147,6 +147,31 @@ def handler(request: httpx.Request) -> httpx.Response: assert result.images[0].url == "https://volc.example/v.mp4" +@pytest.mark.asyncio +async def test_volcengine_image_adapter_surfaces_json_error_body(monkeypatch: pytest.MonkeyPatch) -> None: + """HTTP 4xx 时优先抛出方舟返回的 message,便于任务错误落库与前端展示。""" + + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response( + 404, + json={ + "error": { + "code": "InvalidEndpointOrModel.NotFound", + "message": "The model or endpoint X does not exist or you do not have access to it.", + } + }, + ) + + _patch_httpx_client(monkeypatch, httpx.MockTransport(handler)) + cfg = ProviderConfig(provider="volcengine", api_key="ak-test") + inp = ImageGenerationInput(prompt="hi", model="bad-model", n=1) + with pytest.raises(RuntimeError) as exc_info: + await VolcengineImageApiAdapter().generate(cfg=cfg, inp=inp, timeout_s=30.0) + msg = str(exc_info.value) + assert "InvalidEndpointOrModel.NotFound" in msg + assert "does not exist" in msg + + @pytest.mark.asyncio async def test_openai_image_adapter_rejects_unsupported_watermark(monkeypatch: pytest.MonkeyPatch) -> None: """当能力配置不支持 watermark 时,adapter 在发请求前直接拒绝。""" diff --git a/backend/tests/core/integrations/test_video_adapters.py b/backend/tests/core/integrations/test_video_adapters.py index 0ca249e6..2fc8982f 100644 --- a/backend/tests/core/integrations/test_video_adapters.py +++ b/backend/tests/core/integrations/test_video_adapters.py @@ -8,6 +8,7 @@ import pytest from pydantic import ValidationError +from app.core.integrations.aliyun.dashscope_videos import DashScopeVideoApiAdapter from app.core.integrations.openai.video import OpenAIVideoApiAdapter from app.core.integrations.volcengine.video import VolcengineVideoApiAdapter from app.core.contracts.provider import ProviderConfig @@ -89,6 +90,136 @@ def handler(request: httpx.Request) -> httpx.Response: assert meta["content"]["video_url"] == "https://v.example/out.mp4" +@pytest.mark.asyncio +async def test_dashscope_video_create_and_get_task(monkeypatch: pytest.MonkeyPatch) -> None: + def handler(request: httpx.Request) -> httpx.Response: + if request.method == "POST": + assert str(request.url).endswith("/api/v1/services/aigc/video-generation/video-synthesis") + assert request.headers.get("X-DashScope-Async") == "enable" + payload = json.loads(request.content.decode()) + assert payload["model"] == "wanx2.1-t2v-plus" + assert payload["input"]["prompt"] == "一只猫在奔跑" + # 文生视频模型(名称含 t2v):不应附带帧 media。 + assert "media" not in payload["input"] + assert payload["parameters"]["duration"] == 6 + assert payload["parameters"]["size"] == "1280*720" + assert payload["parameters"]["ratio"] == "16:9" + return httpx.Response(200, json={"output": {"task_id": "task-123"}}) + if request.method == "GET": + assert str(request.url).endswith("/api/v1/tasks/task-123") + return httpx.Response( + 200, + json={ + "output": { + "task_status": "SUCCEEDED", + "video_url": "https://example.com/out.mp4", + } + }, + ) + return httpx.Response(500) + + _patch_httpx_client(monkeypatch, httpx.MockTransport(handler)) + cfg = ProviderConfig(provider="aliyun_bailian", api_key="ak-test", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1") + inp = VideoGenerationInput.model_validate({ + "prompt": "一只猫在奔跑", + "ratio": "16:9", + "seconds": 6, + "model": "wanx2.1-t2v-plus", + "key_frame_base64": "aGVsbG8=", + }) + adapter = DashScopeVideoApiAdapter() + task_id = await adapter.create_video_task(cfg=cfg, input_=inp, timeout_s=30.0) + assert task_id == "task-123" + meta = await adapter.get_video_task(cfg=cfg, task_id=task_id, timeout_s=30.0) + assert meta["output"]["task_status"] == "SUCCEEDED" + + +@pytest.mark.asyncio +async def test_dashscope_i2v_media_uses_first_frame_type(monkeypatch: pytest.MonkeyPatch) -> None: + def handler(request: httpx.Request) -> httpx.Response: + if request.method == "POST": + payload = json.loads(request.content.decode()) + assert payload["model"] == "wanx2.6-i2v-flash" + assert payload["input"]["media"][0]["type"] == "first_frame" + assert payload["input"]["media"][0]["url"].startswith("data:image/png;base64,") + return httpx.Response(200, json={"output": {"task_id": "task-i2v"}}) + return httpx.Response(500) + + _patch_httpx_client(monkeypatch, httpx.MockTransport(handler)) + cfg = ProviderConfig(provider="aliyun_bailian", api_key="ak-test", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1") + inp = VideoGenerationInput.model_validate({ + "prompt": "镜头推进", + "ratio": "16:9", + "model": "wanx2.6-i2v-flash", + "key_frame_base64": "aGVsbG8=", + }) + task_id = await DashScopeVideoApiAdapter().create_video_task(cfg=cfg, input_=inp, timeout_s=30.0) + assert task_id == "task-i2v" + + +@pytest.mark.asyncio +async def test_dashscope_kf2v_maps_first_and_last_frame_types(monkeypatch: pytest.MonkeyPatch) -> None: + def handler(request: httpx.Request) -> httpx.Response: + if request.method == "POST": + payload = json.loads(request.content.decode()) + assert payload["model"] == "wan2.2-kf2v-flash" + media = payload["input"]["media"] + assert len(media) == 2 + assert media[0]["type"] == "first_frame" + assert media[1]["type"] == "last_frame" + return httpx.Response(200, json={"output": {"task_id": "task-kf2v"}}) + return httpx.Response(500) + + _patch_httpx_client(monkeypatch, httpx.MockTransport(handler)) + cfg = ProviderConfig(provider="aliyun_bailian", api_key="ak-test", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1") + inp = VideoGenerationInput.model_validate({ + "prompt": "过渡", + "ratio": "16:9", + "model": "wan2.2-kf2v-flash", + "first_frame_base64": "Zmlyc3Q=", + "last_frame_base64": "bGFzdA==", + }) + task_id = await DashScopeVideoApiAdapter().create_video_task(cfg=cfg, input_=inp, timeout_s=30.0) + assert task_id == "task-kf2v" + + +@pytest.mark.asyncio +async def test_dashscope_ambiguous_model_omits_image_media(monkeypatch: pytest.MonkeyPatch) -> None: + """名称未包含 t2v/i2v 等关键字时,不得以帧图冒充图生视频(否则会触发百炼 media 校验错误)。""" + + def handler(request: httpx.Request) -> httpx.Response: + if request.method == "POST": + payload = json.loads(request.content.decode()) + assert payload["model"] == "wan2.6-standard" + assert "media" not in payload["input"] + return httpx.Response(200, json={"output": {"task_id": "task-t2v-default"}}) + return httpx.Response(500) + + _patch_httpx_client(monkeypatch, httpx.MockTransport(handler)) + cfg = ProviderConfig(provider="aliyun_bailian", api_key="ak-test", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1") + inp = VideoGenerationInput.model_validate({ + "prompt": "奔跑", + "ratio": "16:9", + "model": "wan2.6-standard", + "key_frame_base64": "aGVsbG8=", + }) + task_id = await DashScopeVideoApiAdapter().create_video_task(cfg=cfg, input_=inp, timeout_s=30.0) + assert task_id == "task-t2v-default" + + +@pytest.mark.asyncio +async def test_dashscope_ref_video_model_raises_without_video_url() -> None: + cfg = ProviderConfig(provider="aliyun_bailian", api_key="ak-test", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1") + inp = VideoGenerationInput.model_validate({ + "prompt": "续写", + "ratio": "16:9", + "model": "wanx-v2v-demo", + "key_frame_base64": "aGVsbG8=", + }) + with pytest.raises(RuntimeError, match="参考视频"): + await DashScopeVideoApiAdapter().create_video_task(cfg=cfg, input_=inp, timeout_s=30.0) + + def test_video_input_seed_bounds_validation() -> None: # 边界值应可通过:-1 以及 uint32 最大值 VideoGenerationInput.model_validate({"prompt": "ok", "ratio": "16:9", "seed": -1}) diff --git a/backend/tests/models/test_chapter_timeline_import.py b/backend/tests/models/test_chapter_timeline_import.py new file mode 100644 index 00000000..7aab1e57 --- /dev/null +++ b/backend/tests/models/test_chapter_timeline_import.py @@ -0,0 +1,22 @@ +"""章节时间线 ORM 注册烟雾测试(metadata 含表定义)。""" + +from __future__ import annotations + +import importlib + +from app.core.db import Base + + +def test_chapter_timeline_models_registered_in_metadata() -> None: + importlib.import_module("app.models.studio") + + table_names = Base.metadata.tables.keys() + assert "chapter_timeline_states" in table_names + assert "chapter_timeline_segments" in table_names + + +def test_chapter_timeline_models_importable_from_studio_package() -> None: + from app.models.studio import ChapterTimelineSegment, ChapterTimelineState + + assert ChapterTimelineState.__tablename__ == "chapter_timeline_states" + assert ChapterTimelineSegment.__tablename__ == "chapter_timeline_segments" diff --git a/backend/tests/services/test_chapter_timeline_export_service.py b/backend/tests/services/test_chapter_timeline_export_service.py new file mode 100644 index 00000000..9baf2910 --- /dev/null +++ b/backend/tests/services/test_chapter_timeline_export_service.py @@ -0,0 +1,121 @@ +"""章节时间线导出:活跃任务查询与可导出性校验。""" + +from __future__ import annotations + +import pytest +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine + +from app.core.db import Base +from app.models.studio import Chapter, Project, ProjectStyle, ProjectVisualStyle +from app.models.task import GenerationDeliveryMode, GenerationTask, GenerationTaskStatus +from app.models.task_links import GenerationTaskLink +from app.schemas.studio.chapter_timeline import ( + ChapterTimelineRead, + ChapterTimelineSegmentRead, + TimelineClipStatus, +) +from app.services.studio.chapter_timeline_export import ( + ensure_timeline_exportable, + find_active_chapter_timeline_export_task_id, +) + + +async def _session() -> tuple[AsyncSession, object]: + engine = create_async_engine("sqlite+aiosqlite:///:memory:", future=True) + maker = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + return maker(), engine + + +async def _seed(db: AsyncSession) -> None: + db.add_all( + [ + Project( + id="p1", + name="P", + description="", + style=ProjectStyle.real_people_city, + visual_style=ProjectVisualStyle.live_action, + ), + Chapter(id="c1", project_id="p1", index=1, title="第一章"), + ], + ) + await db.commit() + + +@pytest.mark.asyncio +async def test_find_active_returns_pending_export_task_id() -> None: + db, engine = await _session() + async with db: + await _seed(db) + db.add_all( + [ + GenerationTask( + id="t-exp", + mode=GenerationDeliveryMode.async_polling, + task_kind="chapter_timeline_export", + status=GenerationTaskStatus.pending, + payload={}, + ), + GenerationTaskLink( + task_id="t-exp", + resource_type="video", + relation_type="chapter_timeline_export", + relation_entity_id="c1", + ), + ], + ) + await db.commit() + active = await find_active_chapter_timeline_export_task_id(db, "c1") + assert active == "t-exp" + await engine.dispose() + + +@pytest.mark.asyncio +async def test_find_active_ignores_succeeded_tasks() -> None: + db, engine = await _session() + async with db: + await _seed(db) + db.add_all( + [ + GenerationTask( + id="t-done", + mode=GenerationDeliveryMode.async_polling, + task_kind="chapter_timeline_export", + status=GenerationTaskStatus.succeeded, + payload={}, + ), + GenerationTaskLink( + task_id="t-done", + resource_type="video", + relation_type="chapter_timeline_export", + relation_entity_id="c1", + ), + ], + ) + await db.commit() + assert await find_active_chapter_timeline_export_task_id(db, "c1") is None + await engine.dispose() + + +def test_ensure_timeline_exportable_raises_on_empty() -> None: + with pytest.raises(ValueError, match="时间线为空"): + ensure_timeline_exportable(ChapterTimelineRead(layout_version=1, segments=[])) + + +def test_ensure_timeline_exportable_raises_on_not_ready() -> None: + read = ChapterTimelineRead( + layout_version=1, + segments=[ + ChapterTimelineSegmentRead( + id="", + shot_id="s1", + position=0, + clip_status=TimelineClipStatus.missing_video, + label="", + ), + ], + ) + with pytest.raises(ValueError, match="未就绪"): + ensure_timeline_exportable(read) diff --git a/backend/tests/services/test_chapter_timeline_export_task.py b/backend/tests/services/test_chapter_timeline_export_task.py new file mode 100644 index 00000000..9494e556 --- /dev/null +++ b/backend/tests/services/test_chapter_timeline_export_task.py @@ -0,0 +1,22 @@ +"""章节时间线导出 Runner 中的纯函数单测。""" + +from __future__ import annotations + +import pytest + +from app.services.studio.chapter_timeline_export_task import build_uniform_transcode_concat_filter + + +def test_build_uniform_transcode_concat_includes_audio_concat() -> None: + filt = build_uniform_transcode_concat_filter([(0.0, 3.0, True), (0.0, 2.5, False)]) + assert "concat=n=2:v=1:a=1" in filt + assert "[outa]" in filt + assert "anullsrc=" in filt + assert "atrim=end=2.500000" in filt + assert "[0:a:0]atrim=" in filt + assert "trim=start=" in filt + + +def test_build_uniform_transcode_concat_rejects_empty() -> None: + with pytest.raises(ValueError, match="empty"): + build_uniform_transcode_concat_filter([]) diff --git a/backend/tests/services/test_chapter_timeline_service.py b/backend/tests/services/test_chapter_timeline_service.py new file mode 100644 index 00000000..ef8e9075 --- /dev/null +++ b/backend/tests/services/test_chapter_timeline_service.py @@ -0,0 +1,246 @@ +"""章节时间线服务单元测试(sqlite 内存库)。""" + +from __future__ import annotations + +import pytest +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine + +from app.core.db import Base +from app.models.studio import ( + Chapter, + ChapterTimelineSegment, + ChapterTimelineState, + FileItem, + FileType, + Project, + ProjectStyle, + ProjectVisualStyle, + Shot, +) +from app.schemas.studio.chapter_timeline import ChapterTimelineWrite +from app.services.studio.chapter_timeline import ( + TimelineLayoutConflictError, + build_timeline_read, + replace_timeline_segments, +) + + +async def _make_session() -> tuple[AsyncSession, object]: + engine = create_async_engine("sqlite+aiosqlite:///:memory:", future=True) + session_local = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + return session_local(), engine + + +async def _seed_minimal_chapter(db: AsyncSession) -> None: + db.add_all( + [ + Project( + id="p1", + name="P", + description="", + style=ProjectStyle.real_people_city, + visual_style=ProjectVisualStyle.live_action, + ), + Chapter(id="c1", project_id="p1", index=1, title="第一章"), + Shot(id="s1", chapter_id="c1", index=0, title="镜1"), + Shot(id="s2", chapter_id="c1", index=1, title="镜2"), + ], + ) + await db.commit() + + +@pytest.mark.asyncio +async def test_build_timeline_merges_saved_order_then_remaining_shots_by_index() -> None: + db, engine = await _make_session() + async with db: + await _seed_minimal_chapter(db) + db.add_all( + [ + ChapterTimelineSegment( + id="seg-b", + chapter_id="c1", + shot_id="s2", + position=0, + ), + ChapterTimelineSegment( + id="seg-a", + chapter_id="c1", + shot_id="s1", + position=1, + ), + ], + ) + await db.commit() + + read = await build_timeline_read(db, "c1") + assert [x.shot_id for x in read.segments] == ["s2", "s1"] + await engine.dispose() + + +@pytest.mark.asyncio +async def test_build_timeline_default_shot_index_when_no_segments() -> None: + db, engine = await _make_session() + async with db: + await _seed_minimal_chapter(db) + read = await build_timeline_read(db, "c1") + assert [x.shot_id for x in read.segments] == ["s1", "s2"] + assert all(x.id == "" for x in read.segments) + await engine.dispose() + + +@pytest.mark.asyncio +async def test_clip_status_missing_video_and_ready() -> None: + db, engine = await _make_session() + async with db: + await _seed_minimal_chapter(db) + db.add( + FileItem( + id="fv1", + type=FileType.video, + name="v", + thumbnail="", + tags=[], + storage_key="k", + ), + ) + s1 = await db.get(Shot, "s1") + assert s1 is not None + s1.generated_video_file_id = "fv1" + await db.commit() + + read = await build_timeline_read(db, "c1") + by_shot = {x.shot_id: x for x in read.segments} + assert by_shot["s1"].clip_status.value == "ready" + assert by_shot["s2"].clip_status.value == "missing_video" + await engine.dispose() + + +@pytest.mark.asyncio +async def test_replace_timeline_rejects_unknown_and_duplicate_shots() -> None: + db, engine = await _make_session() + async with db: + await _seed_minimal_chapter(db) + with pytest.raises(ValueError, match="不属于该章节"): + await replace_timeline_segments( + db, + "c1", + ChapterTimelineWrite(segments=[{"shot_id": "other"}]), + ) + with pytest.raises(ValueError, match="重复"): + await replace_timeline_segments( + db, + "c1", + ChapterTimelineWrite( + segments=[ + {"shot_id": "s1"}, + {"shot_id": "s1"}, + ], + ), + ) + await engine.dispose() + + +@pytest.mark.asyncio +async def test_replace_timeline_layout_version_conflict() -> None: + db, engine = await _make_session() + async with db: + await _seed_minimal_chapter(db) + db.add(ChapterTimelineState(chapter_id="c1", layout_version=3)) + await db.commit() + with pytest.raises(TimelineLayoutConflictError): + await replace_timeline_segments( + db, + "c1", + ChapterTimelineWrite(layout_version=1, segments=[{"shot_id": "s1"}]), + ) + await engine.dispose() + + +@pytest.mark.asyncio +async def test_replace_timeline_bumps_version_and_persists_positions() -> None: + db, engine = await _make_session() + async with db: + await _seed_minimal_chapter(db) + out = await replace_timeline_segments( + db, + "c1", + ChapterTimelineWrite(segments=[{"shot_id": "s2"}, {"shot_id": "s1"}]), + ) + assert out.layout_version == 2 + assert [x.shot_id for x in out.segments] == ["s2", "s1"] + + state = await db.get(ChapterTimelineState, "c1") + assert state is not None + assert state.layout_version == 2 + + seg_res = await db.execute( + select(ChapterTimelineSegment).where(ChapterTimelineSegment.chapter_id == "c1"), + ) + assert len(list(seg_res.scalars().all())) == 2 + await engine.dispose() + + +@pytest.mark.asyncio +async def test_replace_timeline_rejects_trim_when_no_generated_video() -> None: + db, engine = await _make_session() + async with db: + await _seed_minimal_chapter(db) + with pytest.raises(ValueError, match="尚无成片"): + await replace_timeline_segments( + db, + "c1", + ChapterTimelineWrite( + segments=[ + {"shot_id": "s1", "trim_start_ms": 0, "trim_end_ms": 1000}, + {"shot_id": "s2"}, + ], + ), + ) + await engine.dispose() + + +@pytest.mark.asyncio +async def test_replace_timeline_accepts_trim_when_video_present(monkeypatch: pytest.MonkeyPatch) -> None: + async def _fake_probe(_storage_key: str) -> int: + return 5000 + + monkeypatch.setattr( + "app.services.studio.chapter_timeline.probe_video_duration_ms_from_storage", + _fake_probe, + ) + + db, engine = await _make_session() + async with db: + await _seed_minimal_chapter(db) + db.add( + FileItem( + id="fv1", + type=FileType.video, + name="v", + thumbnail="", + tags=[], + storage_key="key1", + ), + ) + s1 = await db.get(Shot, "s1") + assert s1 is not None + s1.generated_video_file_id = "fv1" + await db.commit() + + out = await replace_timeline_segments( + db, + "c1", + ChapterTimelineWrite( + segments=[ + {"shot_id": "s1", "trim_start_ms": 500, "trim_end_ms": 4500}, + {"shot_id": "s2"}, + ], + ), + ) + row_s1 = next(x for x in out.segments if x.shot_id == "s1") + assert row_s1.trim_start_ms == 500 + assert row_s1.trim_end_ms == 4500 + await engine.dispose() diff --git a/backend/tests/services/test_chapter_timeline_trim.py b/backend/tests/services/test_chapter_timeline_trim.py new file mode 100644 index 00000000..54f8bd74 --- /dev/null +++ b/backend/tests/services/test_chapter_timeline_trim.py @@ -0,0 +1,48 @@ +"""chapter_timeline_trim 纯函数单测。""" + +from __future__ import annotations + +import pytest + +from app.services.studio.chapter_timeline_trim import ( + is_lossless_compatible_trim, + resolve_effective_trim_ms, + trim_seconds_for_ffmpeg, + validate_effective_trim_ms, +) + + +def test_resolve_full_when_both_none() -> None: + assert resolve_effective_trim_ms(5000, None, None) is None + + +def test_resolve_partial_defaults() -> None: + assert resolve_effective_trim_ms(10_000, 1000, None) == (1000, 10_000) + assert resolve_effective_trim_ms(10_000, None, 8000) == (0, 8000) + + +def test_validate_effective_trim_ok() -> None: + validate_effective_trim_ms(5000, (0, 5000), shot_id="s1") + + +def test_validate_rejects_inverted_range() -> None: + with pytest.raises(ValueError, match="入点必须小于出点"): + validate_effective_trim_ms(5000, (3000, 1000), shot_id="s1") + + +def test_trim_seconds_full_uses_float_duration() -> None: + start_s, end_s = trim_seconds_for_ffmpeg(10.026, None, None) + assert start_s == 0.0 + assert abs(end_s - 10.026) < 1e-9 + + +def test_trim_seconds_with_ms() -> None: + start_s, end_s = trim_seconds_for_ffmpeg(10.0, 500, 2500) + assert start_s == 0.5 + assert end_s == 2.5 + + +def test_lossless_compatible() -> None: + assert is_lossless_compatible_trim(1000, None, None) is True + assert is_lossless_compatible_trim(1000, 0, 1000) is True + assert is_lossless_compatible_trim(1000, 100, 900) is False diff --git a/backend/tests/test_chapter_timeline_api_responses.py b/backend/tests/test_chapter_timeline_api_responses.py new file mode 100644 index 00000000..e36592a8 --- /dev/null +++ b/backend/tests/test_chapter_timeline_api_responses.py @@ -0,0 +1,162 @@ +"""章节时间线 API 响应壳与错误码测试。""" + +from __future__ import annotations + +from collections.abc import AsyncGenerator + +from fastapi.testclient import TestClient + +from app.api.v1.routes.studio import chapters as chapters_route +from app.dependencies import get_db +from app.main import app +from app.models.studio import Chapter +from app.services.studio.chapter_timeline import TimelineLayoutConflictError +from app.schemas.studio.chapter_timeline import ( + ChapterTimelineRead, + ChapterTimelineSegmentRead, + TimelineClipStatus, +) + + +class _DummyDB: + pass + + +def _override_db(db: _DummyDB): + async def _get_db() -> AsyncGenerator[_DummyDB, None]: + yield db + + return _get_db + + +def test_get_chapter_timeline_returns_success_envelope(client: TestClient, monkeypatch) -> None: + db = _DummyDB() + + async def _fake_build(_session, chapter_id: str) -> ChapterTimelineRead: + assert chapter_id == "c1" + return ChapterTimelineRead( + layout_version=1, + segments=[ + ChapterTimelineSegmentRead( + id="", + shot_id="s1", + position=0, + clip_status=TimelineClipStatus.missing_video, + label="镜1", + ), + ], + ) + + monkeypatch.setattr(chapters_route, "build_timeline_read", _fake_build) + + async def _fake_get_or_404(_db, model, _eid, **_kwargs): + if model is Chapter: + return object() + return None + + monkeypatch.setattr(chapters_route, "get_or_404", _fake_get_or_404) + + app.dependency_overrides[get_db] = _override_db(db) + try: + response = client.get("/api/v1/studio/chapters/c1/timeline") + finally: + app.dependency_overrides.clear() + + assert response.status_code == 200 + body = response.json() + assert body["code"] == 200 + assert body["data"]["layout_version"] == 1 + assert body["data"]["segments"][0]["shot_id"] == "s1" + + +class _DummyDbWithChapter: + """占位:满足 Depends 注入形状。""" + + +def test_put_chapter_timeline_layout_conflict_returns_409(client: TestClient, monkeypatch) -> None: + db = _DummyDbWithChapter() + + async def _fake_replace(*_args, **_kwargs): + raise TimelineLayoutConflictError(server_version=2, client_version=1) + + monkeypatch.setattr(chapters_route, "replace_timeline_segments", _fake_replace) + + async def _fake_get_or_404(_db, model, _eid, **_kwargs): + if model is Chapter: + return object() + return None + + monkeypatch.setattr(chapters_route, "get_or_404", _fake_get_or_404) + + app.dependency_overrides[get_db] = _override_db(db) + try: + response = client.put( + "/api/v1/studio/chapters/c1/timeline", + json={"segments": [{"shot_id": "s1"}], "layout_version": 1}, + ) + finally: + app.dependency_overrides.clear() + + assert response.status_code == 409 + body = response.json() + assert body["code"] == 409 + assert body["data"] is None + assert "server_layout_version" in body["message"] or "conflict" in body["message"].lower() + + +def test_put_chapter_timeline_bad_request_returns_400(client: TestClient, monkeypatch) -> None: + db = _DummyDbWithChapter() + + async def _fake_replace(*_args, **_kwargs): + raise ValueError("shot_id 不属于该章节: x") + + monkeypatch.setattr(chapters_route, "replace_timeline_segments", _fake_replace) + + async def _fake_get_or_404(_db, model, _eid, **_kwargs): + if model is Chapter: + return object() + return None + + monkeypatch.setattr(chapters_route, "get_or_404", _fake_get_or_404) + + app.dependency_overrides[get_db] = _override_db(db) + try: + response = client.put( + "/api/v1/studio/chapters/c1/timeline", + json={"segments": [{"shot_id": "x"}]}, + ) + finally: + app.dependency_overrides.clear() + + assert response.status_code == 400 + err = response.json() + assert err["code"] == 400 + assert "不属于" in err["message"] or "shot_id" in err["message"] + + +def test_put_chapter_timeline_success_returns_envelope(client: TestClient, monkeypatch) -> None: + db = _DummyDbWithChapter() + + async def _fake_replace(*_args, **_kwargs): + return ChapterTimelineRead(layout_version=2, segments=[]) + + monkeypatch.setattr(chapters_route, "replace_timeline_segments", _fake_replace) + + async def _fake_get_or_404(_db, model, _eid, **_kwargs): + if model is Chapter: + return object() + return None + + monkeypatch.setattr(chapters_route, "get_or_404", _fake_get_or_404) + + app.dependency_overrides[get_db] = _override_db(db) + try: + response = client.put( + "/api/v1/studio/chapters/c1/timeline", + json={"segments": [{"shot_id": "s1"}]}, + ) + finally: + app.dependency_overrides.clear() + + assert response.status_code == 200 + assert response.json()["data"]["layout_version"] == 2 diff --git a/backend/tests/test_llm_api_responses.py b/backend/tests/test_llm_api_responses.py index 395c7039..8867d6f4 100644 --- a/backend/tests/test_llm_api_responses.py +++ b/backend/tests/test_llm_api_responses.py @@ -207,13 +207,29 @@ def test_list_supported_providers_returns_capability_matrix(client: TestClient) assert "aliyun_bailian" in keys -def test_list_supported_providers_text_contains_aliyun_bailian(client: TestClient) -> None: +def test_list_supported_providers_text_contains_aliyun_bailian_and_volcengine(client: TestClient) -> None: response = client.get("/api/v1/llm/providers/supported", params={"category": "text"}) assert response.status_code == 200 body = response.json() assert body["code"] == 200 keys = {item["key"] for item in (body["data"] or [])} assert "aliyun_bailian" in keys + assert "volcengine" in keys + + +def test_list_supported_providers_image_contains_aliyun_bailian(client: TestClient) -> None: + response = client.get("/api/v1/llm/providers/supported", params={"category": "image"}) + assert response.status_code == 200 + body = response.json() + assert body["code"] == 200 + keys = {item["key"] for item in (body["data"] or [])} + assert "aliyun_bailian" in keys + for item in body["data"] or []: + if item["key"] == "aliyun_bailian": + assert "image" in item["supported_categories"] + break + else: + pytest.fail("aliyun_bailian not in image provider list") def test_list_supported_providers_can_filter_by_category(client: TestClient) -> None: @@ -226,6 +242,14 @@ def test_list_supported_providers_can_filter_by_category(client: TestClient) -> assert "video" in item["supported_categories"] +def test_list_supported_providers_video_contains_aliyun_bailian(client: TestClient) -> None: + response = client.get("/api/v1/llm/providers/supported", params={"category": "video"}) + assert response.status_code == 200 + body = response.json() + keys = {item["key"] for item in (body["data"] or [])} + assert "aliyun_bailian" in keys + + def test_get_video_generation_options_returns_ratio_capability(client: TestClient) -> None: db = _FakeLlmDB() _seed_video_model(db) diff --git a/backend/tests/test_llm_manage.py b/backend/tests/test_llm_manage.py index 5d02edac..f5b8e945 100644 --- a/backend/tests/test_llm_manage.py +++ b/backend/tests/test_llm_manage.py @@ -1,6 +1,5 @@ from __future__ import annotations -from fastapi import HTTPException import pytest from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine @@ -12,6 +11,7 @@ create_model, create_provider, get_image_generation_options, + get_video_generation_options, get_or_create_settings, list_models_paginated, update_model, @@ -143,36 +143,107 @@ async def test_list_models_paginated_returns_filtered_items() -> None: @pytest.mark.asyncio -async def test_create_model_rejects_unsupported_category_for_provider() -> None: +async def test_create_model_allows_volcengine_text_category() -> None: db, engine = await _build_session() async with db: await create_provider( db, body=ProviderCreate( - id="p-bailian", - name="阿里百炼", - base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", + id="p-volc", + name="火山引擎", + base_url="https://ark.cn-beijing.volces.com/api/v3", api_key="k", ), ) - with pytest.raises(HTTPException) as exc_info: - await create_model( - db, - body=ModelCreate( - id="m-video-invalid", - name="qwen-vl-video", - category=ModelCategoryKey.video, - provider_id="p-bailian", - ), - ) - assert exc_info.value.status_code == 400 - assert "does not support category=video" in str(exc_info.value.detail) + created = await create_model( + db, + body=ModelCreate( + id="m-text-valid", + name="doubao-text", + category=ModelCategoryKey.text, + provider_id="p-volc", + ), + ) + + assert created.provider_id == "p-volc" + assert created.category == ModelCategoryKey.text + await engine.dispose() + + +@pytest.mark.asyncio +async def test_create_model_allows_custom_provider_category_binding() -> None: + db, engine = await _build_session() + async with db: + await create_provider( + db, + body=ProviderCreate( + id="p-custom", + name="自定义网关", + base_url="https://gateway.example/v1", + api_key="k", + ), + ) + + created = await create_model( + db, + body=ModelCreate( + id="m-custom-video", + name="custom-video-model", + category=ModelCategoryKey.video, + provider_id="p-custom", + ), + ) + + assert created.provider_id == "p-custom" + assert created.category == ModelCategoryKey.video + await engine.dispose() + + +@pytest.mark.asyncio +async def test_update_model_allows_switch_to_volcengine_for_text_model() -> None: + db, engine = await _build_session() + async with db: + await create_provider( + db, + body=ProviderCreate( + id="p-openai", + name="OpenAI", + base_url="https://api.openai.com/v1", + api_key="k", + ), + ) + await create_provider( + db, + body=ProviderCreate( + id="p-volc", + name="火山引擎", + base_url="https://ark.cn-beijing.volces.com/api/v3", + api_key="k", + ), + ) + await create_model( + db, + body=ModelCreate( + id="m-text-ok", + name="gpt-4", + category=ModelCategoryKey.text, + provider_id="p-openai", + ), + ) + + updated = await update_model( + db, + model_id="m-text-ok", + body=ModelUpdate(provider_id="p-volc"), + ) + assert updated.provider_id == "p-volc" + assert updated.category == ModelCategoryKey.text await engine.dispose() @pytest.mark.asyncio -async def test_update_model_rejects_switch_to_unsupported_provider_category_combo() -> None: +async def test_update_model_allows_switch_to_custom_provider() -> None: db, engine = await _build_session() async with db: await create_provider( @@ -187,30 +258,29 @@ async def test_update_model_rejects_switch_to_unsupported_provider_category_comb await create_provider( db, body=ProviderCreate( - id="p-bailian", - name="阿里百炼", - base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", + id="p-custom", + name="自定义网关", + base_url="https://gateway.example/v1", api_key="k", ), ) await create_model( db, body=ModelCreate( - id="m-video-ok", + id="m-video", name="sora", category=ModelCategoryKey.video, provider_id="p-openai", ), ) - with pytest.raises(HTTPException) as exc_info: - await update_model( - db, - model_id="m-video-ok", - body=ModelUpdate(provider_id="p-bailian"), - ) - assert exc_info.value.status_code == 400 - assert "does not support category=video" in str(exc_info.value.detail) + updated = await update_model( + db, + model_id="m-video", + body=ModelUpdate(provider_id="p-custom"), + ) + + assert updated.provider_id == "p-custom" await engine.dispose() @@ -249,3 +319,54 @@ async def test_get_image_generation_options_uses_default_image_model_capability( assert options.ratio_size_profiles["9:16"]["standard"] == "1600x2848" assert options.ratio_size_profiles["21:9"]["high"] == "4704x2016" await engine.dispose() + + +@pytest.mark.asyncio +async def test_generation_options_fall_back_for_custom_default_models() -> None: + db, engine = await _build_session() + async with db: + await create_provider( + db, + body=ProviderCreate( + id="p-custom", + name="自定义网关", + base_url="https://gateway.example/v1", + api_key="k", + ), + ) + await create_model( + db, + body=ModelCreate( + id="m-custom-image", + name="custom-image", + category=ModelCategoryKey.image, + provider_id="p-custom", + ), + ) + await create_model( + db, + body=ModelCreate( + id="m-custom-video", + name="custom-video", + category=ModelCategoryKey.video, + provider_id="p-custom", + ), + ) + await update_model_settings( + db, + body=ModelSettingsUpdate( + default_image_model_id="m-custom-image", + default_video_model_id="m-custom-video", + ), + ) + + image_options = await get_image_generation_options(db) + video_options = await get_video_generation_options(db) + + assert image_options.provider == "自定义网关" + assert image_options.default_resolution_profile == "standard" + assert image_options.ratio_size_profiles["16:9"]["standard"] == "1792x1024" + assert video_options.provider == "自定义网关" + assert video_options.allowed_ratios == ["16:9"] + assert video_options.default_ratio == "16:9" + await engine.dispose() diff --git a/backend/tests/test_llm_model_verify.py b/backend/tests/test_llm_model_verify.py new file mode 100644 index 00000000..ce6fe492 --- /dev/null +++ b/backend/tests/test_llm_model_verify.py @@ -0,0 +1,288 @@ +"""模型配置验证:service 与 API 信封测试。""" + +from __future__ import annotations + +from collections.abc import AsyncGenerator +from typing import Any + +import pytest +from fastapi import HTTPException +from fastapi.testclient import TestClient +from langchain_core.messages import AIMessage +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine + +from app.api.v1.routes import llm as llm_routes +from app.core.db import Base +from app.dependencies import get_db +from app.main import app +from app.models.llm import Model, ModelCategoryKey, Provider, ProviderStatus +from app.schemas.llm import ProviderCreate +from app.services.llm import manage as llm_manage +from app.services.llm import model_chat_test +from app.services.llm import model_verify + + +async def _memory_session() -> tuple[AsyncSession, object]: + engine = create_async_engine("sqlite+aiosqlite:///:memory:", future=True) + session_local = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + return session_local(), engine + + +@pytest.mark.asyncio +async def test_verify_model_not_found_raises_404() -> None: + db, engine = await _memory_session() + async with db: + with pytest.raises(HTTPException) as exc: + await model_verify.verify_model_config(db, model_id="missing") + assert exc.value.status_code == 404 + await engine.dispose() + + +@pytest.mark.asyncio +async def test_verify_text_success_mocked_llm(monkeypatch: pytest.MonkeyPatch) -> None: + db, engine = await _memory_session() + async with db: + await llm_manage.create_provider( + db, + body=ProviderCreate( + id="p_openai", + name="OpenAI", + base_url="https://api.openai.com/v1", + api_key="sk-test", + ), + ) + db.add( + Model( + id="m1", + name="gpt-4o-mini", + category=ModelCategoryKey.text, + provider_id="p_openai", + ) + ) + await db.commit() + + class _FakeLLM: + async def ainvoke(self, *_a: object, **_kw: object) -> AIMessage: + return AIMessage(content="pong") + + def bind(self, **_kw: object) -> _FakeLLM: + return self + + monkeypatch.setattr( + model_verify, + "build_chat_model_for_model", + lambda **kwargs: _FakeLLM(), + ) + + result = await model_verify.verify_model_config(db, model_id="m1") + assert result.ok is True + assert result.category == ModelCategoryKey.text + assert result.detail and result.detail.get("reply_preview") == "pong" + await engine.dispose() + + +@pytest.mark.asyncio +async def test_verify_image_openai_list_contains_model(monkeypatch: pytest.MonkeyPatch) -> None: + db, engine = await _memory_session() + async with db: + await llm_manage.create_provider( + db, + body=ProviderCreate( + id="p_openai", + name="OpenAI", + base_url="https://api.openai.com/v1", + api_key="sk-test", + ), + ) + db.add( + Model( + id="mi1", + name="dall-e-3", + category=ModelCategoryKey.image, + provider_id="p_openai", + ) + ) + await db.commit() + + class _FakeResp: + status_code = 200 + text = "{}" + + def json(self) -> dict[str, Any]: + return {"data": [{"id": "dall-e-3"}, {"id": "dall-e-2"}]} + + class _FakeClient: + def __init__(self, *args: object, **kwargs: object) -> None: + pass + + async def __aenter__(self) -> _FakeClient: + return self + + async def __aexit__(self, *args: object) -> None: + pass + + async def get(self, *args: object, **kwargs: object) -> _FakeResp: + return _FakeResp() + + monkeypatch.setattr(model_verify.httpx, "AsyncClient", _FakeClient) + + result = await model_verify.verify_model_config(db, model_id="mi1") + assert result.ok is True + assert result.category == ModelCategoryKey.image + await engine.dispose() + + +@pytest.mark.asyncio +async def test_verify_image_model_name_missing_in_list(monkeypatch: pytest.MonkeyPatch) -> None: + db, engine = await _memory_session() + async with db: + await llm_manage.create_provider( + db, + body=ProviderCreate( + id="p_openai", + name="OpenAI", + base_url="https://api.openai.com/v1", + api_key="sk-test", + ), + ) + db.add( + Model( + id="mi2", + name="unknown-model", + category=ModelCategoryKey.image, + provider_id="p_openai", + ) + ) + await db.commit() + + class _FakeResp: + status_code = 200 + text = "{}" + + def json(self) -> dict[str, Any]: + return {"data": [{"id": "dall-e-3"}]} + + class _FakeClient: + def __init__(self, *args: object, **kwargs: object) -> None: + pass + + async def __aenter__(self) -> _FakeClient: + return self + + async def __aexit__(self, *args: object) -> None: + pass + + async def get(self, *args: object, **kwargs: object) -> _FakeResp: + return _FakeResp() + + monkeypatch.setattr(model_verify.httpx, "AsyncClient", _FakeClient) + + result = await model_verify.verify_model_config(db, model_id="mi2") + assert result.ok is False + assert "未找到" in result.message + await engine.dispose() + + +@pytest.mark.asyncio +async def test_chat_test_rejects_non_text_model() -> None: + db, engine = await _memory_session() + async with db: + await llm_manage.create_provider( + db, + body=ProviderCreate( + id="p_openai", + name="OpenAI", + base_url="https://api.openai.com/v1", + api_key="sk-test", + ), + ) + db.add( + Model( + id="m_image", + name="dall-e-3", + category=ModelCategoryKey.image, + provider_id="p_openai", + ) + ) + await db.commit() + + with pytest.raises(HTTPException) as exc: + await model_chat_test.chat_test_with_model( + db, + model_id="m_image", + user_message="hi", + ) + assert exc.value.status_code == 400 + await engine.dispose() + + +@pytest.mark.asyncio +async def test_verify_provider_disabled_returns_fail() -> None: + db, engine = await _memory_session() + async with db: + await llm_manage.create_provider( + db, + body=ProviderCreate( + id="p_openai", + name="OpenAI", + base_url="https://api.openai.com/v1", + api_key="sk-test", + status=ProviderStatus.disabled, + ), + ) + db.add( + Model( + id="m2", + name="gpt-4o-mini", + category=ModelCategoryKey.text, + provider_id="p_openai", + ) + ) + await db.commit() + + result = await model_verify.verify_model_config(db, model_id="m2") + assert result.ok is False + assert "配置未通过检查" in result.message or "disabled" in result.message.lower() + await engine.dispose() + + +def test_verify_llm_model_api_envelope(client: TestClient, monkeypatch: pytest.MonkeyPatch) -> None: + from app.schemas.llm import ModelCategoryKey, ModelVerifyRead + + async def _fake_verify(_db: AsyncSession, *, model_id: str) -> ModelVerifyRead: + assert model_id == "m_x" + return ModelVerifyRead( + ok=True, + category=ModelCategoryKey.text, + message="验证通过", + elapsed_ms=12, + detail={"model_name": "x"}, + ) + + monkeypatch.setattr(llm_routes, "verify_model_config_service", _fake_verify) + + class _MinimalDB: + async def commit(self) -> None: + return None + + async def rollback(self) -> None: + return None + + async def close(self) -> None: + return None + + async def _override_db() -> AsyncGenerator[_MinimalDB, None]: + yield _MinimalDB() + + app.dependency_overrides[get_db] = _override_db + try: + res = client.post("/api/v1/llm/models/m_x/verify") + assert res.status_code == 200 + body = res.json() + assert body.get("code") == 200 + assert body.get("data", {}).get("ok") is True + finally: + app.dependency_overrides.pop(get_db, None) + monkeypatch.undo() diff --git a/backend/tests/test_llm_resolver.py b/backend/tests/test_llm_resolver.py index a163a5e5..4704ca29 100644 --- a/backend/tests/test_llm_resolver.py +++ b/backend/tests/test_llm_resolver.py @@ -230,3 +230,25 @@ def test_resolve_effective_base_url_prefers_category_specific_url() -> None: == "https://video-gateway.example/v1" ) + +def test_resolve_effective_base_url_supports_custom_provider() -> None: + provider = Provider( + id="p-custom", + name="自定义网关", + base_url="https://gateway.example/v1", + image_base_url="https://image-gateway.example/v1", + api_key="k", + ) + + assert ( + resolve_effective_base_url(provider=provider, category=ModelCategoryKey.text) + == "https://gateway.example/v1" + ) + assert ( + resolve_effective_base_url(provider=provider, category=ModelCategoryKey.image) + == "https://image-gateway.example/v1" + ) + assert ( + resolve_effective_base_url(provider=provider, category=ModelCategoryKey.video) + == "https://gateway.example/v1" + ) diff --git a/backend/tests/test_studio_api_responses.py b/backend/tests/test_studio_api_responses.py index 58c41f9a..fd1e85c2 100644 --- a/backend/tests/test_studio_api_responses.py +++ b/backend/tests/test_studio_api_responses.py @@ -229,6 +229,31 @@ def test_get_project_not_found_returns_api_response(client: TestClient) -> None: assert response.json() == {"code": 404, "message": "Project not found", "data": None, "meta": None} +def test_list_project_timeline_returns_empty_envelope(client: TestClient) -> None: + db = _FakeStudioDB() + _seed_project(db, "proj-timeline") + app.dependency_overrides[get_db] = _override_db(db) + try: + response = client.get("/api/v1/studio/projects/proj-timeline/timeline") + finally: + app.dependency_overrides.clear() + + assert response.status_code == 200 + assert response.json() == {"code": 200, "message": "success", "data": [], "meta": None} + + +def test_list_project_timeline_not_found_returns_api_response(client: TestClient) -> None: + db = _FakeStudioDB() + app.dependency_overrides[get_db] = _override_db(db) + try: + response = client.get("/api/v1/studio/projects/missing/timeline") + finally: + app.dependency_overrides.clear() + + assert response.status_code == 404 + assert response.json() == {"code": 404, "message": "Project not found", "data": None, "meta": None} + + def test_delete_project_returns_empty_envelope(client: TestClient) -> None: db = _FakeStudioDB() _seed_project(db, "proj-delete") diff --git a/backend/tests/test_task_execute.py b/backend/tests/test_task_execute.py index 1d2dc2f8..3a3c6903 100644 --- a/backend/tests/test_task_execute.py +++ b/backend/tests/test_task_execute.py @@ -400,8 +400,11 @@ def execute(self, ctx: WorkerTaskContext, run_args: dict[str, object]) -> dict[s def test_task_executor_registry_resolves_sync_and_async_executor_types() -> None: divide_executor = task_executor_registry.resolve("script_divide") video_executor = task_executor_registry.resolve("video_generation") + chapter_export_executor = task_executor_registry.resolve("chapter_timeline_export") assert isinstance(divide_executor, AbstractWorkerTaskExecutor) assert divide_executor.task_kind == "script_divide" assert isinstance(video_executor, AbstractAsyncDelegatingExecutor) assert video_executor.task_kind == "video_generation" + assert isinstance(chapter_export_executor, AbstractAsyncDelegatingExecutor) + assert chapter_export_executor.task_kind == "chapter_timeline_export" diff --git a/backend/tests/test_task_registry.py b/backend/tests/test_task_registry.py index be38b21d..c1914aa3 100644 --- a/backend/tests/test_task_registry.py +++ b/backend/tests/test_task_registry.py @@ -2,8 +2,14 @@ import pytest +from app.core.contracts.image_generation import ImageGenerationInput +from app.core.contracts.provider import ProviderConfig +from app.core.contracts.video_generation import VideoGenerationInput from app.core.task_manager.types import BaseTask +from app.core.tasks.bootstrap import bootstrap_task_adapters +from app.core.tasks.image_generation_tasks import DashScopeImageGenerationTask, ImageGenerationTask from app.core.tasks.registry import register_task_adapter, resolve_task_adapter +from app.core.tasks.video_generation_tasks import DashScopeVideoGenerationTask, VideoGenerationTask class _DummyTask(BaseTask): @@ -61,3 +67,42 @@ def test_resolve_task_adapter_raises_for_unknown_key() -> None: with pytest.raises(ValueError) as exc_info: resolve_task_adapter("not_registered_kind", "not_registered_provider") assert "Unsupported provider/task adapter" in str(exc_info.value) + + +def test_image_generation_aliyun_bailian_maps_to_dashscope_impl() -> None: + """百炼图片任务使用 DashScope 原生文生图适配。""" + bootstrap_task_adapters() + factory = resolve_task_adapter("image_generation", "aliyun_bailian") + assert factory is ImageGenerationTask._build_aliyun_bailian_impl + task = factory( + provider_config=ProviderConfig( + provider="aliyun_bailian", + api_key="x", + base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", + ), + input_=ImageGenerationInput( + prompt="test", + model="qwen-image-test", + ), + ) + assert isinstance(task, DashScopeImageGenerationTask) + + +def test_video_generation_aliyun_bailian_maps_to_dashscope_impl() -> None: + """百炼视频任务应走 DashScope 原生视频接口实现。""" + bootstrap_task_adapters() + factory = resolve_task_adapter("video_generation", "aliyun_bailian") + assert factory is VideoGenerationTask._build_aliyun_bailian_impl + task = factory( + provider_config=ProviderConfig( + provider="aliyun_bailian", + api_key="x", + base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", + ), + input_=VideoGenerationInput( + prompt="test", + ratio="16:9", + model="wanx2.1-t2v-plus", + ), + ) + assert isinstance(task, DashScopeVideoGenerationTask) diff --git a/backend/tests/test_task_status_api_responses.py b/backend/tests/test_task_status_api_responses.py index 209a85bc..a3a906cc 100644 --- a/backend/tests/test_task_status_api_responses.py +++ b/backend/tests/test_task_status_api_responses.py @@ -421,4 +421,5 @@ async def get_status_view(self, _task_id: str): "started_at_ts": 100.0, "finished_at_ts": None, "elapsed_ms": 2500, + "error": "", } diff --git a/backend/uv.lock b/backend/uv.lock index e4f7e6c8..fe1d858f 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -620,6 +620,7 @@ dependencies = [ { name = "jinja2" }, { name = "langchain" }, { name = "langchain-core" }, + { name = "langchain-openai" }, { name = "langgraph" }, { name = "pydantic" }, { name = "pydantic-settings" }, @@ -659,6 +660,7 @@ requires-dist = [ { name = "jinja2" }, { name = "langchain", specifier = ">=0.3.0" }, { name = "langchain-core", specifier = ">=0.3.0" }, + { name = "langchain-openai", specifier = ">=0.2.0" }, { name = "langchain-openai", marker = "extra == 'dev'", specifier = ">=0.2.0" }, { name = "langgraph", specifier = ">=0.2.0" }, { name = "pydantic", specifier = ">=2.0.0" }, diff --git a/deploy/compose/docker-compose.yml b/deploy/compose/docker-compose.yml index a0aa492d..2c6e43e3 100644 --- a/deploy/compose/docker-compose.yml +++ b/deploy/compose/docker-compose.yml @@ -147,7 +147,8 @@ services: condition: service_completed_successfully mysql-init-sql: condition: service_completed_successfully - command: ["uv", "run", "celery", "-A", "app.core.celery_app:celery_app", "worker", "-l", "info"] + # 直接使用镜像内已安装依赖的虚拟环境,避免运行时 `uv run` 临时拉包导致队列长时间 pending。 + command: [".venv/bin/celery", "-A", "app.core.celery_app:celery_app", "worker", "-l", "info"] front: build: diff --git a/deploy/docker/backend.Dockerfile b/deploy/docker/backend.Dockerfile index 1c6449a0..48a613ef 100644 --- a/deploy/docker/backend.Dockerfile +++ b/deploy/docker/backend.Dockerfile @@ -7,8 +7,9 @@ ENV PYTHONDONTWRITEBYTECODE=1 \ WORKDIR /app # System deps for common wheels / TLS +# ffmpeg/ffprobe:章节时间线导出 Worker 与 API 共用镜像时需可用的拼接探测工具 RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates curl \ + && apt-get install -y --no-install-recommends ca-certificates curl ffmpeg \ && rm -rf /var/lib/apt/lists/* # Install uv (Python package manager) diff --git a/front/openapi.json b/front/openapi.json index 4f29e89a..c6f021ad 100644 --- a/front/openapi.json +++ b/front/openapi.json @@ -1 +1,19565 @@ -{"openapi":"3.1.0","info":{"title":"Jellyfish API","version":"0.1.0"},"paths":{"/api/v1/health":{"get":{"tags":["health"],"summary":"V1 Health","operationId":"v1_health_api_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_dict_"}}}}}}},"/api/v1/film/tasks/video/preview-prompt":{"post":{"tags":["film"],"summary":"视频提示词预览","description":"预览视频生成的提示词与自动关联参考图。","operationId":"preview_video_generation_prompt_api_v1_film_tasks_video_preview_prompt_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoGenerationTaskRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_VideoPromptPreviewResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/film/tasks/video":{"post":{"tags":["film"],"summary":"视频生成(任务版)","description":"创建视频生成任务并后台执行,结果通过 /tasks/{task_id}/result 获取。","operationId":"create_video_generation_task_api_v1_film_tasks_video_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoGenerationTaskRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TaskCreated_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/film/tasks/shot-frame-prompts":{"post":{"tags":["film"],"summary":"镜头分镜帧提示词生成(任务版)","operationId":"create_shot_frame_prompt_task_api_v1_film_tasks_shot_frame_prompts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotFramePromptRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TaskCreated_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/film/tasks":{"get":{"tags":["film"],"summary":"全局任务列表(任务中心)","operationId":"list_tasks_api_v1_film_tasks_get","parameters":[{"name":"statuses","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/TaskStatus"}},{"type":"null"}],"description":"按任务状态过滤,可多选","title":"Statuses"},"description":"按任务状态过滤,可多选"},{"name":"task_kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按 task_kind 过滤","title":"Task Kind"},"description":"按 task_kind 过滤"},{"name":"relation_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按 relation_type 过滤","title":"Relation Type"},"description":"按 relation_type 过滤"},{"name":"relation_entity_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按 relation_entity_id 过滤","title":"Relation Entity Id"},"description":"按 relation_entity_id 过滤"},{"name":"recent_seconds","in":"query","required":false,"schema":{"type":"integer","maximum":86400,"minimum":0,"description":"默认返回最近结束任务的时间窗口(秒)","default":300,"title":"Recent Seconds"},"description":"默认返回最近结束任务的时间窗口(秒)"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"页码","default":1,"title":"Page"},"description":"页码"},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"每页条数","default":20,"title":"Page Size"},"description":"每页条数"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_TaskListItemRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/film/tasks/{task_id}/status":{"get":{"tags":["film"],"summary":"查询任务状态/进度(轮询)","operationId":"get_task_status_api_v1_film_tasks__task_id__status_get","parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TaskStatusRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/film/tasks/{task_id}/result":{"get":{"tags":["film"],"summary":"获取任务结果","operationId":"get_task_result_api_v1_film_tasks__task_id__result_get","parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TaskResultRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/film/tasks/{task_id}/cancel":{"post":{"tags":["film"],"summary":"请求取消任务","operationId":"cancel_task_api_v1_film_tasks__task_id__cancel_post","parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskCancelRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TaskCancelRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/film/task-links/adopt":{"patch":{"tags":["film"],"summary":"更新任务关联的采用状态(仅可正向变更)","description":"将指定任务链接的状态设为 accepted;已采用不可改为未采用。","operationId":"adopt_task_link_api_v1_film_task_links_adopt_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskLinkAdoptRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TaskLinkAdoptRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/film/task-links":{"get":{"tags":["film"],"summary":"生成任务关联列表(分页,支持多条件过滤)","operationId":"list_task_links_api_v1_film_task_links_get","parameters":[{"name":"resource_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按 resource_type 过滤","title":"Resource Type"},"description":"按 resource_type 过滤"},{"name":"relation_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按 relation_type 过滤","title":"Relation Type"},"description":"按 relation_type 过滤"},{"name":"relation_entity_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按 relation_entity_id 过滤","title":"Relation Entity Id"},"description":"按 relation_entity_id 过滤"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按关联状态过滤(accepted/todo/rejected)","title":"Status"},"description":"按关联状态过滤(accepted/todo/rejected)"},{"name":"task_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按 task_id 过滤","title":"Task Id"},"description":"按 task_id 过滤"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"排序字段:updated_at/created_at/id/status","title":"Order"},"description":"排序字段:updated_at/created_at/id/status"},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","description":"是否倒序;默认 true","default":true,"title":"Is Desc"},"description":"是否倒序;默认 true"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"页码","default":1,"title":"Page"},"description":"页码"},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"每页条数","default":10,"title":"Page Size"},"description":"每页条数"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_GenerationTaskLinkRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["film"],"summary":"创建生成任务关联","operationId":"create_task_link_api_v1_film_task_links_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerationTaskLinkCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_GenerationTaskLinkRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/film/task-links/{link_id}":{"get":{"tags":["film"],"summary":"获取生成任务关联详情","operationId":"get_task_link_api_v1_film_task_links__link_id__get","parameters":[{"name":"link_id","in":"path","required":true,"schema":{"type":"integer","title":"Link Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_GenerationTaskLinkRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["film"],"summary":"更新生成任务关联(不支持直接修改 is_adopted)","operationId":"update_task_link_api_v1_film_task_links__link_id__patch","parameters":[{"name":"link_id","in":"path","required":true,"schema":{"type":"integer","title":"Link Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerationTaskLinkUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_GenerationTaskLinkRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["film"],"summary":"删除生成任务关联","operationId":"delete_task_link_api_v1_film_task_links__link_id__delete","parameters":[{"name":"link_id","in":"path","required":true,"schema":{"type":"integer","title":"Link Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/llm/providers":{"get":{"tags":["llm"],"summary":"列出模型供应商(分页)","operationId":"list_providers_api_v1_llm_providers_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"关键字,过滤 name/description","title":"Q"},"description":"关键字,过滤 name/description"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"排序字段:name, created_at, updated_at","title":"Order"},"description":"排序字段:name, created_at, updated_at"},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","description":"是否倒序","default":false,"title":"Is Desc"},"description":"是否倒序"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"页码","default":1,"title":"Page"},"description":"页码"},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"每页条数","default":10,"title":"Page Size"},"description":"每页条数"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_ProviderRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["llm"],"summary":"创建模型供应商","operationId":"create_provider_api_v1_llm_providers_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ProviderRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/llm/providers/supported":{"get":{"tags":["llm"],"summary":"列出系统支持的供应商能力","operationId":"list_supported_providers_api_v1_llm_providers_supported_get","parameters":[{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/ModelCategoryKey"},{"type":"null"}],"description":"按模型类别过滤:text/image/video","title":"Category"},"description":"按模型类别过滤:text/image/video"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_list_ProviderSupportedRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/llm/image-generation-options":{"get":{"tags":["llm"],"summary":"获取当前默认图片模型的关键帧规格选项","operationId":"get_image_generation_options_api_v1_llm_image_generation_options_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ImageGenerationOptionsRead_"}}}}}}},"/api/v1/llm/video-generation-options":{"get":{"tags":["llm"],"summary":"获取当前默认视频模型的动态比例选项","operationId":"get_video_generation_options_api_v1_llm_video_generation_options_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_VideoGenerationOptionsRead_"}}}}}}},"/api/v1/llm/providers/{provider_id}":{"get":{"tags":["llm"],"summary":"获取单个模型供应商","operationId":"get_provider_api_v1_llm_providers__provider_id__get","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ProviderRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["llm"],"summary":"更新模型供应商","operationId":"update_provider_api_v1_llm_providers__provider_id__patch","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ProviderRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["llm"],"summary":"删除模型供应商","operationId":"delete_provider_api_v1_llm_providers__provider_id__delete","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/llm/models":{"get":{"tags":["llm"],"summary":"列出模型(分页)","operationId":"list_models_api_v1_llm_models_get","parameters":[{"name":"provider_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按供应商过滤","title":"Provider Id"},"description":"按供应商过滤"},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/ModelCategoryKey"},{"type":"null"}],"description":"按模型类别过滤","title":"Category"},"description":"按模型类别过滤"},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"关键字,过滤 name/description","title":"Q"},"description":"关键字,过滤 name/description"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"排序字段:name, category, created_at, updated_at","title":"Order"},"description":"排序字段:name, category, created_at, updated_at"},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","description":"是否倒序","default":false,"title":"Is Desc"},"description":"是否倒序"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"页码","default":1,"title":"Page"},"description":"页码"},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"每页条数","default":10,"title":"Page Size"},"description":"每页条数"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_ModelRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["llm"],"summary":"创建模型","operationId":"create_model_api_v1_llm_models_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ModelRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/llm/models/{model_id}":{"get":{"tags":["llm"],"summary":"获取单个模型","operationId":"get_model_api_v1_llm_models__model_id__get","parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","title":"Model Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ModelRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["llm"],"summary":"更新模型","operationId":"update_model_api_v1_llm_models__model_id__patch","parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","title":"Model Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ModelRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["llm"],"summary":"删除模型","operationId":"delete_model_api_v1_llm_models__model_id__delete","parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","title":"Model Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/llm/model-settings":{"get":{"tags":["llm"],"summary":"获取模型全局设置(单例)","operationId":"get_model_settings_api_v1_llm_model_settings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ModelSettingsRead_"}}}}}},"put":{"tags":["llm"],"summary":"更新模型全局设置(单例)","operationId":"update_model_settings_api_v1_llm_model_settings_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelSettingsUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ModelSettingsRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/projects/style-options":{"get":{"tags":["studio/projects"],"summary":"获取项目风格候选项","operationId":"get_project_style_options_api_v1_studio_projects_style_options_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ProjectStyleOptionsRead_"}}}}}}},"/api/v1/studio/projects":{"get":{"tags":["studio/projects"],"summary":"项目列表(分页)","operationId":"list_projects_api_v1_studio_projects_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"关键字,过滤 name/description","title":"Q"},"description":"关键字,过滤 name/description"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"排序字段","title":"Order"},"description":"排序字段"},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","description":"是否倒序","default":false,"title":"Is Desc"},"description":"是否倒序"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_ProjectRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["studio/projects"],"summary":"创建项目","operationId":"create_project_api_v1_studio_projects_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ProjectRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/projects/{project_id}":{"get":{"tags":["studio/projects"],"summary":"获取项目","operationId":"get_project_api_v1_studio_projects__project_id__get","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ProjectRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["studio/projects"],"summary":"更新项目","operationId":"update_project_api_v1_studio_projects__project_id__patch","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ProjectRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["studio/projects"],"summary":"删除项目","operationId":"delete_project_api_v1_studio_projects__project_id__delete","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/chapters":{"get":{"tags":["studio/chapters"],"summary":"章节列表(分页)","operationId":"list_chapters_api_v1_studio_chapters_get","parameters":[{"name":"project_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按项目过滤","title":"Project Id"},"description":"按项目过滤"},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"关键字,过滤 title/summary","title":"Q"},"description":"关键字,过滤 title/summary"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"排序字段","title":"Order"},"description":"排序字段"},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","description":"是否倒序","default":false,"title":"Is Desc"},"description":"是否倒序"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_ChapterRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["studio/chapters"],"summary":"创建章节","operationId":"create_chapter_api_v1_studio_chapters_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChapterCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ChapterRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/chapters/{chapter_id}":{"get":{"tags":["studio/chapters"],"summary":"获取章节","operationId":"get_chapter_api_v1_studio_chapters__chapter_id__get","parameters":[{"name":"chapter_id","in":"path","required":true,"schema":{"type":"string","title":"Chapter Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ChapterRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["studio/chapters"],"summary":"更新章节","operationId":"update_chapter_api_v1_studio_chapters__chapter_id__patch","parameters":[{"name":"chapter_id","in":"path","required":true,"schema":{"type":"string","title":"Chapter Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChapterUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ChapterRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["studio/chapters"],"summary":"删除章节","operationId":"delete_chapter_api_v1_studio_chapters__chapter_id__delete","parameters":[{"name":"chapter_id","in":"path","required":true,"schema":{"type":"string","title":"Chapter Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots":{"get":{"tags":["studio/shots"],"summary":"镜头列表(分页)","operationId":"list_shots_api_v1_studio_shots_get","parameters":[{"name":"chapter_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按章节过滤","title":"Chapter Id"},"description":"按章节过滤"},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"关键字,过滤 title/script_excerpt","title":"Q"},"description":"关键字,过滤 title/script_excerpt"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order"}},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Is Desc"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_ShotRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["studio/shots"],"summary":"创建镜头","operationId":"create_shot_api_v1_studio_shots_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/runtime-summary":{"get":{"tags":["studio/shots"],"summary":"按章节获取镜头运行时任务态摘要","operationId":"list_shot_runtime_summary_api_v1_studio_shots_runtime_summary_get","parameters":[{"name":"chapter_id","in":"query","required":true,"schema":{"type":"string","description":"章节 ID","title":"Chapter Id"},"description":"章节 ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_list_ShotRuntimeSummaryRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/{shot_id}/extraction-draft":{"get":{"tags":["studio/shots"],"summary":"分镜详情:按镜头关联拼装 StudioScriptExtractionDraft","operationId":"get_shot_extraction_draft_api_v1_studio_shots__shot_id__extraction_draft_get","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_StudioScriptExtractionDraft_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/{shot_id}/extracted-candidates":{"get":{"tags":["studio/shots"],"summary":"获取镜头提取候选项","operationId":"get_shot_extracted_candidates_api_v1_studio_shots__shot_id__extracted_candidates_get","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_list_ShotExtractedCandidateRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/{shot_id}/extracted-dialogue-candidates":{"get":{"tags":["studio/shots"],"summary":"获取镜头提取对白候选项","operationId":"get_shot_extracted_dialogue_candidates_api_v1_studio_shots__shot_id__extracted_dialogue_candidates_get","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_list_ShotExtractedDialogueCandidateRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/{shot_id}/assets-overview":{"get":{"tags":["studio/shots"],"summary":"获取镜头资产总览(已关联资产 + 提取候选)","operationId":"get_shot_assets_overview_api_api_v1_studio_shots__shot_id__assets_overview_get","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotAssetsOverviewRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/{shot_id}/preparation-state":{"get":{"tags":["studio/shots"],"summary":"获取镜头准备页聚合状态","operationId":"get_shot_preparation_state_api_api_v1_studio_shots__shot_id__preparation_state_get","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotPreparationStateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/{shot_id}/preparation-link":{"post":{"tags":["studio/shots"],"summary":"准备页关联现有实体并返回最新聚合状态","operationId":"link_existing_asset_for_preparation_api_api_v1_studio_shots__shot_id__preparation_link_post","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotPreparationLinkRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotPreparationMutationResultRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/{shot_id}/video-prompt-preview":{"get":{"tags":["studio/shots"],"summary":"预览镜头视频提示词","operationId":"preview_shot_video_prompt_api_v1_studio_shots__shot_id__video_prompt_preview_get","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}},{"name":"template_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"指定视频提示词模板 ID;不传则使用默认模板","title":"Template Id"},"description":"指定视频提示词模板 ID;不传则使用默认模板"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotVideoPromptPreviewRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/{shot_id}/video-readiness":{"get":{"tags":["studio/shots"],"summary":"获取镜头视频生成准备度","operationId":"get_shot_video_readiness_api_api_v1_studio_shots__shot_id__video_readiness_get","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}},{"name":"reference_mode","in":"query","required":false,"schema":{"type":"string","description":"参考模式:first/last/key/first_last/first_last_key/text_only","default":"text_only","title":"Reference Mode"},"description":"参考模式:first/last/key/first_last/first_last_key/text_only"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotVideoReadinessRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/{shot_id}/skip-extraction":{"patch":{"tags":["studio/shots"],"summary":"设置是否跳过镜头信息提取","operationId":"update_shot_skip_extraction_api_v1_studio_shots__shot_id__skip_extraction_patch","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotSkipExtractionUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotPreparationMutationResultRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/extracted-candidates/{candidate_id}/link":{"patch":{"tags":["studio/shots"],"summary":"确认并关联镜头提取候选项","operationId":"link_extracted_candidate_api_v1_studio_shots_extracted_candidates__candidate_id__link_patch","parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"integer","title":"Candidate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotExtractedCandidateLinkRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotPreparationMutationResultRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/extracted-candidates/{candidate_id}/ignore":{"patch":{"tags":["studio/shots"],"summary":"忽略镜头提取候选项","operationId":"ignore_extracted_candidate_api_v1_studio_shots_extracted_candidates__candidate_id__ignore_patch","parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"integer","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotPreparationMutationResultRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/extracted-dialogue-candidates/{candidate_id}/accept":{"patch":{"tags":["studio/shots"],"summary":"接受镜头提取对白候选项","operationId":"accept_extracted_dialogue_candidate_api_v1_studio_shots_extracted_dialogue_candidates__candidate_id__accept_patch","parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"integer","title":"Candidate Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ShotExtractedDialogueCandidateAcceptRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotPreparationMutationResultRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/extracted-dialogue-candidates/{candidate_id}/ignore":{"patch":{"tags":["studio/shots"],"summary":"忽略镜头提取对白候选项","operationId":"ignore_extracted_dialogue_candidate_api_v1_studio_shots_extracted_dialogue_candidates__candidate_id__ignore_patch","parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"integer","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotPreparationMutationResultRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/{shot_id}":{"get":{"tags":["studio/shots"],"summary":"获取镜头","operationId":"get_shot_api_v1_studio_shots__shot_id__get","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["studio/shots"],"summary":"更新镜头","operationId":"update_shot_api_v1_studio_shots__shot_id__patch","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["studio/shots"],"summary":"删除镜头","operationId":"delete_shot_api_v1_studio_shots__shot_id__delete","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shots/{shot_id}/linked-assets":{"get":{"tags":["studio/shots"],"summary":"获取镜头关联的角色/道具/场景/服装(分页)","operationId":"list_shot_linked_assets_api_v1_studio_shots__shot_id__linked_assets_get","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_ShotLinkedAssetItem__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-details":{"get":{"tags":["studio/shot-details"],"summary":"镜头细节列表(分页)","operationId":"list_shot_details_api_v1_studio_shot_details_get","parameters":[{"name":"shot_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按镜头过滤(id 同 shot_id)","title":"Shot Id"},"description":"按镜头过滤(id 同 shot_id)"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order"}},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Is Desc"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_ShotDetailRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["studio/shot-details"],"summary":"创建镜头细节","operationId":"create_shot_detail_api_v1_studio_shot_details_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotDetailCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotDetailRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-details/{shot_id}":{"get":{"tags":["studio/shot-details"],"summary":"获取镜头细节","operationId":"get_shot_detail_api_v1_studio_shot_details__shot_id__get","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotDetailRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["studio/shot-details"],"summary":"更新镜头细节","operationId":"update_shot_detail_api_v1_studio_shot_details__shot_id__patch","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotDetailUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotDetailRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["studio/shot-details"],"summary":"删除镜头细节","operationId":"delete_shot_detail_api_v1_studio_shot_details__shot_id__delete","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-dialog-lines":{"get":{"tags":["studio/shot-dialog-lines"],"summary":"镜头对话行列表(分页)","operationId":"list_shot_dialog_lines_api_v1_studio_shot_dialog_lines_get","parameters":[{"name":"shot_detail_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按镜头细节过滤","title":"Shot Detail Id"},"description":"按镜头细节过滤"},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"关键字,过滤 text","title":"Q"},"description":"关键字,过滤 text"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order"}},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Is Desc"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_ShotDialogLineRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["studio/shot-dialog-lines"],"summary":"创建镜头对话行","operationId":"create_shot_dialog_line_api_v1_studio_shot_dialog_lines_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotDialogLineCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotDialogLineRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-dialog-lines/{line_id}":{"patch":{"tags":["studio/shot-dialog-lines"],"summary":"更新镜头对话行","operationId":"update_shot_dialog_line_api_v1_studio_shot_dialog_lines__line_id__patch","parameters":[{"name":"line_id","in":"path","required":true,"schema":{"type":"integer","title":"Line Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotDialogLineUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotDialogLineRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["studio/shot-dialog-lines"],"summary":"删除镜头对话行","operationId":"delete_shot_dialog_line_api_v1_studio_shot_dialog_lines__line_id__delete","parameters":[{"name":"line_id","in":"path","required":true,"schema":{"type":"integer","title":"Line Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-links/{entity_type}":{"get":{"tags":["studio/shot-links"],"summary":"项目-章节-镜头-实体关联列表(分页)","operationId":"list_project_entity_links_api_v1_studio_shot_links__entity_type__get","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","title":"Entity Type"}},{"name":"project_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id"}},{"name":"chapter_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id"}},{"name":"shot_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shot Id"}},{"name":"asset_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset Id"}},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order"}},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Is Desc"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_Any__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-links/actor":{"post":{"tags":["studio/shot-links"],"summary":"创建项目-章节-镜头-演员关联","operationId":"create_project_actor_link_api_v1_studio_shot_links_actor_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetLinkCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ProjectActorLinkRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-links/actor/{link_id}":{"delete":{"tags":["studio/shot-links"],"summary":"删除项目-章节-镜头-演员关联","operationId":"delete_project_actor_link_api_v1_studio_shot_links_actor__link_id__delete","parameters":[{"name":"link_id","in":"path","required":true,"schema":{"type":"integer","title":"Link Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-links/scene":{"post":{"tags":["studio/shot-links"],"summary":"创建项目-章节-镜头-场景关联","operationId":"create_project_scene_link_api_v1_studio_shot_links_scene_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetLinkCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ProjectSceneLinkRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-links/scene/{link_id}":{"delete":{"tags":["studio/shot-links"],"summary":"删除项目-章节-镜头-场景关联","operationId":"delete_project_scene_link_api_v1_studio_shot_links_scene__link_id__delete","parameters":[{"name":"link_id","in":"path","required":true,"schema":{"type":"integer","title":"Link Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-links/prop":{"post":{"tags":["studio/shot-links"],"summary":"创建项目-章节-镜头-道具关联","operationId":"create_project_prop_link_api_v1_studio_shot_links_prop_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetLinkCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ProjectPropLinkRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-links/prop/{link_id}":{"delete":{"tags":["studio/shot-links"],"summary":"删除项目-章节-镜头-道具关联","operationId":"delete_project_prop_link_api_v1_studio_shot_links_prop__link_id__delete","parameters":[{"name":"link_id","in":"path","required":true,"schema":{"type":"integer","title":"Link Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-links/costume":{"post":{"tags":["studio/shot-links"],"summary":"创建项目-章节-镜头-服装关联","operationId":"create_project_costume_link_api_v1_studio_shot_links_costume_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetLinkCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ProjectCostumeLinkRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-links/costume/{link_id}":{"delete":{"tags":["studio/shot-links"],"summary":"删除项目-章节-镜头-服装关联","operationId":"delete_project_costume_link_api_v1_studio_shot_links_costume__link_id__delete","parameters":[{"name":"link_id","in":"path","required":true,"schema":{"type":"integer","title":"Link Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-frame-images":{"get":{"tags":["studio/shot-frame-images"],"summary":"镜头分镜帧图片列表(分页)","operationId":"list_shot_frame_images_api_v1_studio_shot_frame_images_get","parameters":[{"name":"shot_detail_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按镜头细节过滤","title":"Shot Detail Id"},"description":"按镜头细节过滤"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order"}},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Is Desc"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_ShotFrameImageRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["studio/shot-frame-images"],"summary":"创建镜头分镜帧图片","operationId":"create_shot_frame_image_api_v1_studio_shot_frame_images_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotFrameImageCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotFrameImageRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-frame-images/{image_id}":{"patch":{"tags":["studio/shot-frame-images"],"summary":"更新镜头分镜帧图片","operationId":"update_shot_frame_image_api_v1_studio_shot_frame_images__image_id__patch","parameters":[{"name":"image_id","in":"path","required":true,"schema":{"type":"integer","title":"Image Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotFrameImageUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotFrameImageRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["studio/shot-frame-images"],"summary":"删除镜头分镜帧图片","operationId":"delete_shot_frame_image_api_v1_studio_shot_frame_images__image_id__delete","parameters":[{"name":"image_id","in":"path","required":true,"schema":{"type":"integer","title":"Image Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/entities/existence-check":{"post":{"tags":["studio/entities"],"summary":"批量检测资产名称是否存在(模糊匹配,不分页)","operationId":"check_entity_names_existence_api_v1_studio_entities_existence_check_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityNameExistenceCheckRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_EntityNameExistenceCheckResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/entities/{entity_type}":{"get":{"tags":["studio/entities"],"summary":"统一实体列表(分页)","operationId":"list_entities_api_v1_studio_entities__entity_type__get","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","title":"Entity Type"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"关键字,过滤 name/description","title":"Q"},"description":"关键字,过滤 name/description"},{"name":"style","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"题材/风格(单值)","title":"Style"},"description":"题材/风格(单值)"},{"name":"visual_style","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"画面表现形式(单值:真人/动漫)","title":"Visual Style"},"description":"画面表现形式(单值:真人/动漫)"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order"}},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Is Desc"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_dict_str__Any___"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["studio/entities"],"summary":"统一创建实体","operationId":"create_entity_api_v1_studio_entities__entity_type__post","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","title":"Entity Type"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_dict_str__Any__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/entities/{entity_type}/{entity_id}":{"get":{"tags":["studio/entities"],"summary":"统一获取实体","operationId":"get_entity_api_v1_studio_entities__entity_type___entity_id__get","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","title":"Entity Type"}},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","title":"Entity Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_dict_str__Any__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["studio/entities"],"summary":"统一更新实体","operationId":"update_entity_api_v1_studio_entities__entity_type___entity_id__patch","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","title":"Entity Type"}},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","title":"Entity Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_dict_str__Any__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["studio/entities"],"summary":"统一删除实体","operationId":"delete_entity_api_v1_studio_entities__entity_type___entity_id__delete","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","title":"Entity Type"}},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","title":"Entity Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/entities/{entity_type}/{entity_id}/images":{"get":{"tags":["studio/entities"],"summary":"统一实体图片列表(分页)","operationId":"list_entity_images_api_v1_studio_entities__entity_type___entity_id__images_get","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","title":"Entity Type"}},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","title":"Entity Id"}},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order"}},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Is Desc"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_dict_str__Any___"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["studio/entities"],"summary":"统一创建实体图片","operationId":"create_entity_image_api_v1_studio_entities__entity_type___entity_id__images_post","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","title":"Entity Type"}},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","title":"Entity Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_dict_str__Any__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/entities/{entity_type}/{entity_id}/images/{image_id}":{"patch":{"tags":["studio/entities"],"summary":"统一更新实体图片","operationId":"update_entity_image_api_v1_studio_entities__entity_type___entity_id__images__image_id__patch","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","title":"Entity Type"}},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","title":"Entity Id"}},{"name":"image_id","in":"path","required":true,"schema":{"type":"integer","title":"Image Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_dict_str__Any__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["studio/entities"],"summary":"统一删除实体图片","operationId":"delete_entity_image_api_v1_studio_entities__entity_type___entity_id__images__image_id__delete","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","title":"Entity Type"}},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","title":"Entity Id"}},{"name":"image_id","in":"path","required":true,"schema":{"type":"integer","title":"Image Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/prompts":{"get":{"tags":["studio/prompts"],"summary":"提示词模板列表(分页)","operationId":"list_prompt_templates_api_v1_studio_prompts_get","parameters":[{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/PromptCategory"},{"type":"null"}],"description":"按类别过滤","title":"Category"},"description":"按类别过滤"},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"关键字,过滤 name","title":"Q"},"description":"关键字,过滤 name"},{"name":"is_default","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"过滤是否为默认","title":"Is Default"},"description":"过滤是否为默认"},{"name":"is_system","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"过滤是否为系统预置","title":"Is System"},"description":"过滤是否为系统预置"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order"}},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Is Desc"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_PromptTemplateRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["studio/prompts"],"summary":"创建提示词模板","operationId":"create_prompt_template_api_v1_studio_prompts_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptTemplateCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PromptTemplateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/prompts/categories":{"get":{"tags":["studio/prompts"],"summary":"获取提示词类别枚举(含中文映射)","operationId":"list_prompt_categories_api_v1_studio_prompts_categories_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_list_PromptCategoryOptionRead__"}}}}}}},"/api/v1/studio/prompts/{template_id}":{"get":{"tags":["studio/prompts"],"summary":"获取提示词模板详情","operationId":"get_prompt_template_api_v1_studio_prompts__template_id__get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PromptTemplateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["studio/prompts"],"summary":"局部更新提示词模板","operationId":"update_prompt_template_api_v1_studio_prompts__template_id__patch","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptTemplateUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PromptTemplateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["studio/prompts"],"summary":"删除提示词模板","operationId":"delete_prompt_template_api_v1_studio_prompts__template_id__delete","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/files":{"get":{"tags":["studio/files"],"summary":"文件列表(分页)","operationId":"list_files_api_api_v1_studio_files_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"关键字,过滤 name","title":"Q"},"description":"关键字,过滤 name"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order"}},{"name":"is_desc","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Is Desc"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Page Size"}},{"name":"project_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"按 file_usages 限定项目;提供后仅返回该项目下有关联记录的文件","title":"Project Id"},"description":"按 file_usages 限定项目;提供后仅返回该项目下有关联记录的文件"},{"name":"chapter_title","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"章节标题(精确匹配,与 project_id 联用)","title":"Chapter Title"},"description":"章节标题(精确匹配,与 project_id 联用)"},{"name":"shot_title","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"镜头标题(精确匹配,与 project_id 联用)","title":"Shot Title"},"description":"镜头标题(精确匹配,与 project_id 联用)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PaginatedData_FileRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/files/upload":{"post":{"tags":["studio/files"],"summary":"上传文件并创建 FileItem 记录","operationId":"upload_file_api_api_v1_studio_files_upload_post","parameters":[{"name":"name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_file_api_api_v1_studio_files_upload_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_FileRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/files/{file_id}/download":{"get":{"tags":["studio/files"],"summary":"下载文件二进制内容","operationId":"download_file_api_api_v1_studio_files__file_id__download_get","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string","title":"File Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/files/{file_id}/storage-info":{"get":{"tags":["studio/files"],"summary":"获取对象存储详情(head_object)","operationId":"get_file_storage_info_api_api_v1_studio_files__file_id__storage_info_get","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string","title":"File Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_dict_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/files/{file_id}":{"get":{"tags":["studio/files"],"summary":"获取文件详情(元信息 + file_usages)","operationId":"get_file_detail_api_v1_studio_files__file_id__get","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string","title":"File Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_FileDetailRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["studio/files"],"summary":"更新文件元信息","operationId":"update_file_meta_api_v1_studio_files__file_id__patch","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string","title":"File Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_FileRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["studio/files"],"summary":"删除文件(记录 + 存储对象)","operationId":"delete_file_api_api_v1_studio_files__file_id__delete","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string","title":"File Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_NoneType_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/image-tasks/actors/{actor_id}/image-tasks":{"post":{"tags":["studio/image-tasks"],"summary":"演员图片生成(任务版)","description":"为指定演员创建图片生成任务,并通过 `GenerationTaskLink` 关联。","operationId":"create_actor_image_generation_task_api_v1_studio_image_tasks_actors__actor_id__image_tasks_post","parameters":[{"name":"actor_id","in":"path","required":true,"schema":{"type":"string","title":"Actor Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudioImageTaskRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TaskCreated_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/image-tasks/actors/{actor_id}/render-prompt":{"post":{"tags":["studio/image-tasks"],"summary":"演员图片提示词渲染","operationId":"render_actor_image_prompt_api_v1_studio_image_tasks_actors__actor_id__render_prompt_post","parameters":[{"name":"actor_id","in":"path","required":true,"schema":{"type":"string","title":"Actor Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudioImageTaskRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_RenderedPromptResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/image-tasks/assets/{asset_type}/{asset_id}/image-tasks":{"post":{"tags":["studio/image-tasks"],"summary":"道具/场景/服装图片生成(任务版)","description":"为道具/场景/服装创建图片生成任务。\n\n- asset_type: prop / scene / costume\n- path 参数 asset_id 为对应资产 ID\n- body.image_id 必须为该资产下对应图片表记录的 ID(PropImage/SceneImage/CostumeImage)","operationId":"create_asset_image_generation_task_api_v1_studio_image_tasks_assets__asset_type___asset_id__image_tasks_post","parameters":[{"name":"asset_type","in":"path","required":true,"schema":{"type":"string","title":"Asset Type"}},{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","title":"Asset Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudioImageTaskRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TaskCreated_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/image-tasks/assets/{asset_type}/{asset_id}/render-prompt":{"post":{"tags":["studio/image-tasks"],"summary":"道具/场景/服装图片提示词渲染","operationId":"render_asset_image_prompt_api_v1_studio_image_tasks_assets__asset_type___asset_id__render_prompt_post","parameters":[{"name":"asset_type","in":"path","required":true,"schema":{"type":"string","title":"Asset Type"}},{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","title":"Asset Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudioImageTaskRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_RenderedPromptResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/image-tasks/characters/{character_id}/image-tasks":{"post":{"tags":["studio/image-tasks"],"summary":"角色图片生成(任务版)","description":"为角色创建图片生成任务(对应 CharacterImage 业务)。\n\n- path 参数 character_id 为 Character.id\n- body.image_id 必须为该角色下的 CharacterImage.id","operationId":"create_character_image_generation_task_api_v1_studio_image_tasks_characters__character_id__image_tasks_post","parameters":[{"name":"character_id","in":"path","required":true,"schema":{"type":"string","title":"Character Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudioImageTaskRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TaskCreated_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/image-tasks/characters/{character_id}/render-prompt":{"post":{"tags":["studio/image-tasks"],"summary":"角色图片提示词渲染","operationId":"render_character_image_prompt_api_v1_studio_image_tasks_characters__character_id__render_prompt_post","parameters":[{"name":"character_id","in":"path","required":true,"schema":{"type":"string","title":"Character Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudioImageTaskRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_RenderedPromptResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/image-tasks/shot/{shot_id}/frame-image-tasks":{"post":{"tags":["studio/image-tasks"],"summary":"镜头分镜帧图片生成(任务版)","description":"为镜头分镜帧图片生成任务(基于 `shot_id + frame_type` 自动定位数据)。","operationId":"create_shot_frame_image_generation_task_api_v1_studio_image_tasks_shot__shot_id__frame_image_tasks_post","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotFrameImageTaskRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TaskCreated_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/image-tasks/shot/{shot_id}/frame-render-prompt":{"post":{"tags":["studio/image-tasks"],"summary":"镜头分镜帧提示词渲染","operationId":"render_shot_frame_prompt_api_v1_studio_image_tasks_shot__shot_id__frame_render_prompt_post","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotFramePromptRenderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_RenderedShotFramePromptRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/studio/shot-character-links":{"get":{"tags":["studio/shot-character-links"],"summary":"查询镜头角色关联列表(ShotCharacterLink)","operationId":"list_shot_character_links_api_v1_studio_shot_character_links_get","parameters":[{"name":"shot_id","in":"query","required":true,"schema":{"type":"string","description":"镜头 ID","title":"Shot Id"},"description":"镜头 ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_list_ShotCharacterLinkRead__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["studio/shot-character-links"],"summary":"创建/更新镜头角色关联(ShotCharacterLink)","operationId":"upsert_shot_character_link_api_v1_studio_shot_character_links_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShotCharacterLinkCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ShotCharacterLinkRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/divide-async":{"post":{"tags":["script-processing"],"summary":"异步将剧本分割为多个镜头","description":"创建章节分镜提取任务并立即返回 task_id;前端可通过任务状态接口轮询。","operationId":"divide_script_async_api_v1_script_processing_divide_async_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptDividerRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AsyncTaskCreateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/divide":{"post":{"tags":["script-processing"],"summary":"将剧本分割为多个镜头","description":"输入完整剧本文本,输出分镜列表(index/start_line/end_line/script_excerpt/shot_name/time_of_day)。注意:此阶段不强制稳定ID,角色以“称呼/名字”弱信息输出,稳定ID在合并阶段统一分配。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 divide-async。","operationId":"divide_script_api_v1_script_processing_divide_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptDividerRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ScriptDivisionResult_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/merge-entities-async":{"post":{"tags":["script-processing"],"summary":"异步合并多镜头的实体信息","description":"创建实体合并任务并立即返回 task_id;当前保留为预备能力,尚无真实前端入口。","operationId":"merge_entities_async_api_v1_script_processing_merge_entities_async_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityMergerRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AsyncTaskCreateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/merge-entities":{"post":{"tags":["script-processing"],"summary":"合并多镜头的实体信息","description":"输入全部分镜提取结果(可选带上脚本分镜与历史实体库),输出合并后的实体库:角色库/地点库/场景库/道具库(静态画像 + 变体列表)。该步骤会统一分配稳定ID(如 char_001/loc_001/prop_001/scene_001)。当提供 previous_merge 与 conflict_resolutions 时,将进行冲突重试合并,优先消解 conflicts 并尽量保持 ID 稳定。当前接口保留为预备能力,尚无真实前端入口。","operationId":"merge_entities_api_v1_script_processing_merge_entities_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityMergerRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_EntityMergeResult_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/analyze-variants-async":{"post":{"tags":["script-processing"],"summary":"异步分析服装/外形变体","description":"创建变体分析任务并立即返回 task_id;当前保留为预备能力,尚无真实前端入口。","operationId":"analyze_variants_async_api_v1_script_processing_analyze_variants_async_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VariantAnalysisRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AsyncTaskCreateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/analyze-variants":{"post":{"tags":["script-processing"],"summary":"分析服装/外形变体","description":"检测角色服装/外形变化,构建演变时间线,生成章节变体建议列表与变体建议。当前接口保留为预备能力,尚无真实前端入口。","operationId":"analyze_variants_api_v1_script_processing_analyze_variants_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VariantAnalysisRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_VariantAnalysisResult_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/check-consistency-async":{"post":{"tags":["script-processing"],"summary":"异步检查角色混淆一致性(基于原文)","description":"创建一致性检查任务并立即返回 task_id;前端可通过任务状态接口轮询。","operationId":"check_consistency_async_api_v1_script_processing_check_consistency_async_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptConsistencyCheckRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AsyncTaskCreateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/check-consistency":{"post":{"tags":["script-processing"],"summary":"检查角色混淆一致性(基于原文)","description":"检测同一角色在不同段落/镜头被赋予不同身份/行为主体导致混淆,并给出修改建议。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 check-consistency-async。","operationId":"check_consistency_api_v1_script_processing_check_consistency_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptConsistencyCheckRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ScriptConsistencyCheckResult_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/analyze-character-portrait-async":{"post":{"tags":["script-processing"],"summary":"异步分析人物画像缺失信息","description":"创建人物画像分析任务并立即返回 task_id;前端可通过任务状态接口轮询。","operationId":"analyze_character_portrait_async_api_v1_script_processing_analyze_character_portrait_async_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CharacterPortraitAnalysisRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AsyncTaskCreateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/analyze-character-portrait":{"post":{"tags":["script-processing"],"summary":"分析人物画像缺失信息","description":"根据原文人物上下文与人物描述,判断缺少哪些关键信息,并给出优化后的人物画像描述。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 analyze-character-portrait-async。","operationId":"analyze_character_portrait_api_v1_script_processing_analyze_character_portrait_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CharacterPortraitAnalysisRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_CharacterPortraitAnalysisResult_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/analyze-prop-info-async":{"post":{"tags":["script-processing"],"summary":"异步分析道具信息缺失项","description":"创建道具信息分析任务并立即返回 task_id;前端可通过任务状态接口轮询。","operationId":"analyze_prop_info_async_api_v1_script_processing_analyze_prop_info_async_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropInfoAnalysisRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AsyncTaskCreateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/analyze-prop-info":{"post":{"tags":["script-processing"],"summary":"分析道具信息缺失项","description":"根据原文道具上下文与道具描述,判断缺少哪些关键信息,并给出优化后的可生成道具描述。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 analyze-prop-info-async。","operationId":"analyze_prop_info_api_v1_script_processing_analyze_prop_info_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropInfoAnalysisRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PropInfoAnalysisResult_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/analyze-scene-info-async":{"post":{"tags":["script-processing"],"summary":"异步分析场景信息缺失项","description":"创建场景信息分析任务并立即返回 task_id;前端可通过任务状态接口轮询。","operationId":"analyze_scene_info_async_api_v1_script_processing_analyze_scene_info_async_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SceneInfoAnalysisRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AsyncTaskCreateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/analyze-scene-info":{"post":{"tags":["script-processing"],"summary":"分析场景信息缺失项","description":"根据原文场景上下文与场景描述,判断缺少哪些关键信息,并给出优化后的可生成场景描述。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 analyze-scene-info-async。","operationId":"analyze_scene_info_api_v1_script_processing_analyze_scene_info_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SceneInfoAnalysisRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_SceneInfoAnalysisResult_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/analyze-costume-info-async":{"post":{"tags":["script-processing"],"summary":"异步分析服装信息缺失项","description":"创建服装信息分析任务并立即返回 task_id;前端可通过任务状态接口轮询。","operationId":"analyze_costume_info_async_api_v1_script_processing_analyze_costume_info_async_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CostumeInfoAnalysisRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AsyncTaskCreateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/analyze-costume-info":{"post":{"tags":["script-processing"],"summary":"分析服装信息缺失项","description":"根据原文服装上下文与服装描述,判断缺少哪些关键信息,并给出优化后的可生成服装描述。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 analyze-costume-info-async。","operationId":"analyze_costume_info_api_v1_script_processing_analyze_costume_info_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CostumeInfoAnalysisRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_CostumeInfoAnalysisResult_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/optimize-script-async":{"post":{"tags":["script-processing"],"summary":"异步基于一致性检查优化剧本","description":"创建剧本优化任务并立即返回 task_id;前端可通过任务状态接口轮询。","operationId":"optimize_script_async_api_v1_script_processing_optimize_script_async_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptOptimizeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AsyncTaskCreateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/optimize-script":{"post":{"tags":["script-processing"],"summary":"基于一致性检查优化剧本","description":"将一致性检查输出及原文作为输入,生成优化后的剧本(尽量少改,只改与角色混淆 issues 相关段落)。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 optimize-script-async。","operationId":"optimize_script_api_v1_script_processing_optimize_script_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptOptimizeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ScriptOptimizationResult_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/simplify-script":{"post":{"tags":["script-processing"],"summary":"智能精简剧本","description":"在保留剧情主体并保证剧情连续的前提下精简剧本文本。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 simplify-script-async。","operationId":"simplify_script_api_v1_script_processing_simplify_script_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptSimplifyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ScriptSimplificationResult_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/simplify-script-async":{"post":{"tags":["script-processing"],"summary":"异步智能精简剧本","description":"创建剧本精简任务并立即返回 task_id;前端可通过任务状态接口轮询。","operationId":"simplify_script_async_api_v1_script_processing_simplify_script_async_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptSimplifyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AsyncTaskCreateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/extract-async":{"post":{"tags":["script-processing"],"summary":"异步项目级信息提取(最终输出)","description":"创建项目级信息提取任务并立即返回 task_id;前端可通过任务状态接口轮询。","operationId":"extract_script_async_api_v1_script_processing_extract_async_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptExtractRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AsyncTaskCreateRead_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/script-processing/extract":{"post":{"tags":["script-processing"],"summary":"项目级信息提取(最终输出)","description":"输入分镜结果(可选带一致性检查结果),输出可导入 Studio 的草稿结构(name-based,ID 由导入接口生成)。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 extract-async。","operationId":"extract_script_api_v1_script_processing_extract_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptExtractRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_StudioScriptExtractionDraft_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health","description":"健康检查。","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"ActionBeatPhaseRead":{"properties":{"text":{"type":"string","title":"Text","description":"动作拍点原文"},"phase":{"type":"string","enum":["trigger","peak","aftermath"],"title":"Phase","description":"推断阶段:触发 / 峰值 / 收束"}},"type":"object","required":["text","phase"],"title":"ActionBeatPhaseRead","description":"动作拍点的轻量阶段推断结果。"},"ApiResponse_AsyncTaskCreateRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/AsyncTaskCreateRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[AsyncTaskCreateRead]"},"ApiResponse_ChapterRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ChapterRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ChapterRead]"},"ApiResponse_CharacterPortraitAnalysisResult_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/CharacterPortraitAnalysisResult"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[CharacterPortraitAnalysisResult]"},"ApiResponse_CostumeInfoAnalysisResult_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/CostumeInfoAnalysisResult"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[CostumeInfoAnalysisResult]"},"ApiResponse_EntityMergeResult_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/EntityMergeResult"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[EntityMergeResult]"},"ApiResponse_EntityNameExistenceCheckResponse_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/EntityNameExistenceCheckResponse"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[EntityNameExistenceCheckResponse]"},"ApiResponse_FileDetailRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/FileDetailRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[FileDetailRead]"},"ApiResponse_FileRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/FileRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[FileRead]"},"ApiResponse_GenerationTaskLinkRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/GenerationTaskLinkRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[GenerationTaskLinkRead]"},"ApiResponse_ImageGenerationOptionsRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ImageGenerationOptionsRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ImageGenerationOptionsRead]"},"ApiResponse_ModelRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ModelRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ModelRead]"},"ApiResponse_ModelSettingsRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ModelSettingsRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ModelSettingsRead]"},"ApiResponse_NoneType_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"type":"null","title":"Data","description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[NoneType]"},"ApiResponse_PaginatedData_Any__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_Any_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[Any]]"},"ApiResponse_PaginatedData_ChapterRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_ChapterRead_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[ChapterRead]]"},"ApiResponse_PaginatedData_FileRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_FileRead_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[FileRead]]"},"ApiResponse_PaginatedData_GenerationTaskLinkRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_GenerationTaskLinkRead_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[GenerationTaskLinkRead]]"},"ApiResponse_PaginatedData_ModelRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_ModelRead_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[ModelRead]]"},"ApiResponse_PaginatedData_ProjectRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_ProjectRead_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[ProjectRead]]"},"ApiResponse_PaginatedData_PromptTemplateRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_PromptTemplateRead_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[PromptTemplateRead]]"},"ApiResponse_PaginatedData_ProviderRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_ProviderRead_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[ProviderRead]]"},"ApiResponse_PaginatedData_ShotDetailRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_ShotDetailRead_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[ShotDetailRead]]"},"ApiResponse_PaginatedData_ShotDialogLineRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_ShotDialogLineRead_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[ShotDialogLineRead]]"},"ApiResponse_PaginatedData_ShotFrameImageRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_ShotFrameImageRead_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[ShotFrameImageRead]]"},"ApiResponse_PaginatedData_ShotLinkedAssetItem__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_ShotLinkedAssetItem_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[ShotLinkedAssetItem]]"},"ApiResponse_PaginatedData_ShotRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_ShotRead_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[ShotRead]]"},"ApiResponse_PaginatedData_TaskListItemRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_TaskListItemRead_"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[TaskListItemRead]]"},"ApiResponse_PaginatedData_dict_str__Any___":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PaginatedData_dict_str__Any__"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PaginatedData[dict[str, Any]]]"},"ApiResponse_ProjectActorLinkRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ProjectActorLinkRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ProjectActorLinkRead]"},"ApiResponse_ProjectCostumeLinkRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ProjectCostumeLinkRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ProjectCostumeLinkRead]"},"ApiResponse_ProjectPropLinkRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ProjectPropLinkRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ProjectPropLinkRead]"},"ApiResponse_ProjectRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ProjectRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ProjectRead]"},"ApiResponse_ProjectSceneLinkRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ProjectSceneLinkRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ProjectSceneLinkRead]"},"ApiResponse_ProjectStyleOptionsRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ProjectStyleOptionsRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ProjectStyleOptionsRead]"},"ApiResponse_PromptTemplateRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PromptTemplateRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PromptTemplateRead]"},"ApiResponse_PropInfoAnalysisResult_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/PropInfoAnalysisResult"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[PropInfoAnalysisResult]"},"ApiResponse_ProviderRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ProviderRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ProviderRead]"},"ApiResponse_RenderedPromptResponse_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/RenderedPromptResponse"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[RenderedPromptResponse]"},"ApiResponse_RenderedShotFramePromptRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/RenderedShotFramePromptRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[RenderedShotFramePromptRead]"},"ApiResponse_SceneInfoAnalysisResult_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/SceneInfoAnalysisResult"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[SceneInfoAnalysisResult]"},"ApiResponse_ScriptConsistencyCheckResult_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ScriptConsistencyCheckResult"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ScriptConsistencyCheckResult]"},"ApiResponse_ScriptDivisionResult_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ScriptDivisionResult"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ScriptDivisionResult]"},"ApiResponse_ScriptOptimizationResult_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ScriptOptimizationResult"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ScriptOptimizationResult]"},"ApiResponse_ScriptSimplificationResult_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ScriptSimplificationResult"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ScriptSimplificationResult]"},"ApiResponse_ShotAssetsOverviewRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ShotAssetsOverviewRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ShotAssetsOverviewRead]"},"ApiResponse_ShotCharacterLinkRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ShotCharacterLinkRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ShotCharacterLinkRead]"},"ApiResponse_ShotDetailRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ShotDetailRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ShotDetailRead]"},"ApiResponse_ShotDialogLineRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ShotDialogLineRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ShotDialogLineRead]"},"ApiResponse_ShotFrameImageRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ShotFrameImageRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ShotFrameImageRead]"},"ApiResponse_ShotPreparationMutationResultRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ShotPreparationMutationResultRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ShotPreparationMutationResultRead]"},"ApiResponse_ShotPreparationStateRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ShotPreparationStateRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ShotPreparationStateRead]"},"ApiResponse_ShotRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ShotRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ShotRead]"},"ApiResponse_ShotVideoPromptPreviewRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ShotVideoPromptPreviewRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ShotVideoPromptPreviewRead]"},"ApiResponse_ShotVideoReadinessRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/ShotVideoReadinessRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[ShotVideoReadinessRead]"},"ApiResponse_StudioScriptExtractionDraft_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/StudioScriptExtractionDraft"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[StudioScriptExtractionDraft]"},"ApiResponse_TaskCancelRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/TaskCancelRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[TaskCancelRead]"},"ApiResponse_TaskCreated_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/TaskCreated"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[TaskCreated]"},"ApiResponse_TaskLinkAdoptRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/TaskLinkAdoptRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[TaskLinkAdoptRead]"},"ApiResponse_TaskResultRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/TaskResultRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[TaskResultRead]"},"ApiResponse_TaskStatusRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/TaskStatusRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[TaskStatusRead]"},"ApiResponse_VariantAnalysisResult_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/VariantAnalysisResult"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[VariantAnalysisResult]"},"ApiResponse_VideoGenerationOptionsRead_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/VideoGenerationOptionsRead"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[VideoGenerationOptionsRead]"},"ApiResponse_VideoPromptPreviewResponse_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"$ref":"#/components/schemas/VideoPromptPreviewResponse"},{"type":"null"}],"description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[VideoPromptPreviewResponse]"},"ApiResponse_dict_":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Data","description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[dict]"},"ApiResponse_dict_str__Any__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Data","description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[dict[str, Any]]"},"ApiResponse_list_PromptCategoryOptionRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"items":{"$ref":"#/components/schemas/PromptCategoryOptionRead"},"type":"array"},{"type":"null"}],"title":"Data","description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[list[PromptCategoryOptionRead]]"},"ApiResponse_list_ProviderSupportedRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"items":{"$ref":"#/components/schemas/ProviderSupportedRead"},"type":"array"},{"type":"null"}],"title":"Data","description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[list[ProviderSupportedRead]]"},"ApiResponse_list_ShotCharacterLinkRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"items":{"$ref":"#/components/schemas/ShotCharacterLinkRead"},"type":"array"},{"type":"null"}],"title":"Data","description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[list[ShotCharacterLinkRead]]"},"ApiResponse_list_ShotExtractedCandidateRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"items":{"$ref":"#/components/schemas/ShotExtractedCandidateRead"},"type":"array"},{"type":"null"}],"title":"Data","description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[list[ShotExtractedCandidateRead]]"},"ApiResponse_list_ShotExtractedDialogueCandidateRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"items":{"$ref":"#/components/schemas/ShotExtractedDialogueCandidateRead"},"type":"array"},{"type":"null"}],"title":"Data","description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[list[ShotExtractedDialogueCandidateRead]]"},"ApiResponse_list_ShotRuntimeSummaryRead__":{"properties":{"code":{"type":"integer","title":"Code","description":"与 HTTP 状态码一致","default":200},"message":{"type":"string","title":"Message","description":"提示信息","default":"success"},"data":{"anyOf":[{"items":{"$ref":"#/components/schemas/ShotRuntimeSummaryRead"},"type":"array"},{"type":"null"}],"title":"Data","description":"实际数据"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta","description":"附加元信息"}},"type":"object","title":"ApiResponse[list[ShotRuntimeSummaryRead]]"},"AsyncTaskCreateRead":{"properties":{"task_id":{"type":"string","title":"Task Id","description":"任务 ID"},"status":{"$ref":"#/components/schemas/TaskStatus","description":"任务状态"},"reused":{"type":"boolean","title":"Reused","description":"是否复用了当前业务实体已有的活跃任务","default":false},"relation_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relation Type","description":"业务关联类型"},"relation_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relation Entity Id","description":"业务关联实体 ID"}},"type":"object","required":["task_id","status"],"title":"AsyncTaskCreateRead"},"Body_upload_file_api_api_v1_studio_files_upload_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"要上传的二进制文件"},"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id","description":"可选:写入 file_usages 的项目 ID"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id"},"shot_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shot Id"},"usage_kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage Kind","description":"与 project_id 同时提供时写入 file_usages"},"source_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Ref"}},"type":"object","required":["file"],"title":"Body_upload_file_api_api_v1_studio_files_upload_post"},"CameraAngle":{"type":"string","enum":["EYE_LEVEL","HIGH_ANGLE","LOW_ANGLE","BIRD_EYE","DUTCH","OVER_SHOULDER"],"title":"CameraAngle","description":"机位角度(与 `app.schemas.skills.common.CameraAngle` 对齐,存英文 code)。"},"CameraMovement":{"type":"string","enum":["STATIC","PAN","TILT","DOLLY_IN","DOLLY_OUT","TRACK","CRANE","HANDHELD","STEADICAM","ZOOM_IN","ZOOM_OUT"],"title":"CameraMovement","description":"运镜方式(与 `app.schemas.skills.common.CameraMovement` 对齐,存英文 code)。"},"CameraShotType":{"type":"string","enum":["ECU","CU","MCU","MS","MLS","LS","ELS"],"title":"CameraShotType","description":"景别(与 `app.schemas.skills.common.ShotType` 对齐,存英文 code)。"},"ChapterCreate":{"properties":{"project_id":{"type":"string","title":"Project Id","description":"所属项目 ID"},"index":{"type":"integer","title":"Index","description":"章节序号(项目内唯一)"},"title":{"type":"string","title":"Title","description":"章节标题"},"summary":{"type":"string","title":"Summary","description":"章节摘要","default":""},"raw_text":{"type":"string","title":"Raw Text","description":"章节原文","default":""},"condensed_text":{"type":"string","title":"Condensed Text","description":"精简原文","default":""},"storyboard_count":{"type":"integer","title":"Storyboard Count","description":"分镜数量","default":0},"status":{"$ref":"#/components/schemas/ChapterStatus","description":"章节状态","default":"draft"},"id":{"type":"string","title":"Id","description":"章节 ID"}},"type":"object","required":["project_id","index","title","id"],"title":"ChapterCreate"},"ChapterRead":{"properties":{"project_id":{"type":"string","title":"Project Id","description":"所属项目 ID"},"index":{"type":"integer","title":"Index","description":"章节序号(项目内唯一)"},"title":{"type":"string","title":"Title","description":"章节标题"},"summary":{"type":"string","title":"Summary","description":"章节摘要","default":""},"raw_text":{"type":"string","title":"Raw Text","description":"章节原文","default":""},"condensed_text":{"type":"string","title":"Condensed Text","description":"精简原文","default":""},"storyboard_count":{"type":"integer","title":"Storyboard Count","description":"分镜数量","default":0},"status":{"$ref":"#/components/schemas/ChapterStatus","description":"章节状态","default":"draft"},"id":{"type":"string","title":"Id"},"shot_count":{"type":"integer","title":"Shot Count","description":"分镜数(shots 条数聚合)","default":0}},"type":"object","required":["project_id","index","title","id"],"title":"ChapterRead"},"ChapterStatus":{"type":"string","enum":["draft","shooting","done"],"title":"ChapterStatus","description":"章节生产状态。"},"ChapterUpdate":{"properties":{"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id"},"index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Index"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"raw_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raw Text"},"condensed_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Condensed Text"},"storyboard_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Storyboard Count"},"status":{"anyOf":[{"$ref":"#/components/schemas/ChapterStatus"},{"type":"null"}]}},"type":"object","title":"ChapterUpdate"},"CharacterPortraitAnalysisRequest":{"properties":{"relation_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relation Entity Id","description":"任务关联实体 ID(资产页恢复任务可选)"},"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id","description":"项目 ID(异步任务关联可选)"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(异步任务关联可选)"},"character_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Character Context","description":"原文人物上下文(可为空;用于提供额外背景,帮助判断缺失信息)"},"character_description":{"type":"string","minLength":1,"title":"Character Description","description":"原文人物描述"}},"type":"object","required":["character_description"],"title":"CharacterPortraitAnalysisRequest","description":"人物画像缺失信息分析请求。"},"CharacterPortraitAnalysisResult":{"properties":{"issues":{"items":{"type":"string"},"type":"array","title":"Issues"},"optimized_description":{"type":"string","title":"Optimized Description"}},"additionalProperties":false,"type":"object","required":["issues","optimized_description"],"title":"CharacterPortraitAnalysisResult","description":"根据原文人物描述,分析缺少的信息,并给出优化后的可生成画像描述。"},"CostumeInfoAnalysisRequest":{"properties":{"relation_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relation Entity Id","description":"任务关联实体 ID(资产页恢复任务可选)"},"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id","description":"项目 ID(异步任务关联可选)"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(异步任务关联可选)"},"costume_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Costume Context","description":"原文服装上下文(可为空;用于提供额外背景,帮助判断缺失信息)"},"costume_description":{"type":"string","minLength":1,"title":"Costume Description","description":"原文服装描述"}},"type":"object","required":["costume_description"],"title":"CostumeInfoAnalysisRequest","description":"服装信息缺失分析请求。"},"CostumeInfoAnalysisResult":{"properties":{"issues":{"items":{"type":"string"},"type":"array","title":"Issues"},"optimized_description":{"type":"string","title":"Optimized Description"}},"additionalProperties":false,"type":"object","required":["issues","optimized_description"],"title":"CostumeInfoAnalysisResult","description":"根据原文服装/造型描述,分析缺少的信息,并给出优化后的可生成服装描述。"},"CostumeTimeline":{"properties":{"character_id":{"type":"string","title":"Character Id","description":"角色稳定ID"},"character_name":{"type":"string","title":"Character Name","description":"角色名称"},"timeline_entries":{"items":{"$ref":"#/components/schemas/CostumeTimelineEntry"},"type":"array","title":"Timeline Entries","description":"时间线条目"}},"additionalProperties":false,"type":"object","required":["character_id","character_name"],"title":"CostumeTimeline","description":"单角色的服装演变时间线。"},"CostumeTimelineEntry":{"properties":{"shot_index":{"type":"integer","minimum":1.0,"title":"Shot Index","description":"镜头序号"},"scene_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scene Id","description":"可选:所属场景稳定ID(若已可推断)"},"costume_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Costume Note","description":"服装/外形要点(简短)"},"changes":{"items":{"type":"string"},"type":"array","title":"Changes","description":"与上一条相比的变化点"},"evidence":{"items":{"$ref":"#/components/schemas/EvidenceSpan"},"type":"array","title":"Evidence","description":"原文依据(可选)"}},"additionalProperties":false,"type":"object","required":["shot_index"],"title":"CostumeTimelineEntry","description":"单角色的服装演变时间线条目。"},"DialogueLineMode":{"type":"string","enum":["DIALOGUE","VOICE_OVER","OFF_SCREEN","PHONE"],"title":"DialogueLineMode","description":"对白模式(与 `app.schemas.skills.common.DialogueLineMode` 对齐,存英文 code)。"},"EntityEntry":{"properties":{"id":{"type":"string","title":"Id","description":"实体稳定ID(合并阶段分配)"},"name":{"type":"string","title":"Name","description":"实体名称"},"type":{"type":"string","enum":["character","scene","prop","location"],"title":"Type","description":"实体类型"},"normalized_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Normalized Name","description":"归一化名称(来自文本,可选)"},"aliases":{"items":{"type":"string"},"type":"array","title":"Aliases","description":"别名/称呼(来自文本,可选)"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"基础画像/描述(忠实文本,简短)"},"confidence":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Confidence","description":"合并确定度 0-1(可选)"},"first_appearance":{"anyOf":[{"$ref":"#/components/schemas/EvidenceSpan"},{"type":"null"}],"description":"首次出场证据(可选)"},"costume_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Costume Note","description":"服装/造型描述(可选,便于变体与资产关联)"},"traits":{"items":{"type":"string"},"type":"array","title":"Traits","description":"性格/特征词(可选)"},"location_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Type","description":"地点类型:房间/街道/森林/车厢等(可选)"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"道具类别(可选:weapon/document/vehicle/clothing/device/magic_item/other)"},"owner_character_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Character Id","description":"拥有者角色ID(可选)"},"evidence":{"items":{"$ref":"#/components/schemas/EvidenceSpan"},"type":"array","title":"Evidence","description":"支撑该实体画像的证据片段(可选)"},"first_shot":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Shot","description":"首次出现的镜头序号"},"appearances":{"items":{"type":"integer"},"type":"array","title":"Appearances","description":"出现镜头列表"},"variants":{"items":{"$ref":"#/components/schemas/EntityVariant"},"type":"array","title":"Variants","description":"变体列表"}},"additionalProperties":false,"type":"object","required":["id","name","type"],"title":"EntityEntry","description":"合并后的实体条目(脚本处理中间态)。"},"EntityLibrary":{"properties":{"characters":{"items":{"$ref":"#/components/schemas/EntityEntry"},"type":"array","title":"Characters","description":"角色库"},"locations":{"items":{"$ref":"#/components/schemas/EntityEntry"},"type":"array","title":"Locations","description":"地点库"},"scenes":{"items":{"$ref":"#/components/schemas/EntityEntry"},"type":"array","title":"Scenes","description":"场景库"},"props":{"items":{"$ref":"#/components/schemas/EntityEntry"},"type":"array","title":"Props","description":"道具库"},"total_entries":{"type":"integer","minimum":0.0,"title":"Total Entries","description":"总实体数"}},"additionalProperties":false,"type":"object","required":["total_entries"],"title":"EntityLibrary","description":"合并后的实体库(脚本处理中间态)。"},"EntityMergeResult":{"properties":{"merged_library":{"$ref":"#/components/schemas/EntityLibrary","description":"合并后的实体库"},"merge_stats":{"additionalProperties":true,"type":"object","title":"Merge Stats","description":"合并统计信息"},"conflicts":{"items":{"type":"string"},"type":"array","title":"Conflicts","description":"发现的冲突/待处理项"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"合并说明"}},"additionalProperties":false,"type":"object","required":["merged_library"],"title":"EntityMergeResult","description":"实体合并结果(脚本处理中间态)。"},"EntityMergerRequest":{"properties":{"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id","description":"项目 ID(异步任务关联可选)"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(异步任务关联可选)"},"all_shot_extractions":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"All Shot Extractions","description":"所有镜头提取结果(ShotElementExtractionResult 的序列化形式)"},"historical_library":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Historical Library","description":"历史实体库(可选,用于增量合并)"},"script_division":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Script Division","description":"脚本分镜结果(可选;ScriptDivisionResult 序列化),用于定位与统计"},"previous_merge":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Previous Merge","description":"上一次合并结果(可选;EntityMergeResult 序列化),用于冲突重试合并"},"conflict_resolutions":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Conflict Resolutions","description":"冲突解决建议列表(可选;用于冲突重试合并)"}},"type":"object","required":["all_shot_extractions"],"title":"EntityMergerRequest","description":"实体合并请求。"},"EntityNameExistenceCheckRequest":{"properties":{"project_id":{"type":"string","minLength":1,"title":"Project Id","description":"项目 ID(必填)"},"shot_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shot Id","description":"镜头 ID(可选;不传则 linked_to_shot 恒为 false)"},"character_names":{"items":{"type":"string"},"type":"array","title":"Character Names","description":"角色名称列表"},"prop_names":{"items":{"type":"string"},"type":"array","title":"Prop Names","description":"道具名称列表"},"scene_names":{"items":{"type":"string"},"type":"array","title":"Scene Names","description":"场景名称列表"},"costume_names":{"items":{"type":"string"},"type":"array","title":"Costume Names","description":"服装名称列表"}},"additionalProperties":false,"type":"object","required":["project_id"],"title":"EntityNameExistenceCheckRequest","description":"批量检测项目内/全局资产名称是否存在(模糊匹配)。"},"EntityNameExistenceCheckResponse":{"properties":{"characters":{"items":{"$ref":"#/components/schemas/EntityNameExistenceItem"},"type":"array","title":"Characters"},"props":{"items":{"$ref":"#/components/schemas/EntityNameExistenceItem"},"type":"array","title":"Props"},"scenes":{"items":{"$ref":"#/components/schemas/EntityNameExistenceItem"},"type":"array","title":"Scenes"},"costumes":{"items":{"$ref":"#/components/schemas/EntityNameExistenceItem"},"type":"array","title":"Costumes"}},"additionalProperties":false,"type":"object","title":"EntityNameExistenceCheckResponse","description":"批量存在性检测结果(按资产类型分组)。"},"EntityNameExistenceItem":{"properties":{"name":{"type":"string","title":"Name","description":"输入名称(原样回传)"},"exists":{"type":"boolean","title":"Exists","description":"数据库中是否存在(模糊命中)"},"linked_to_project":{"type":"boolean","title":"Linked To Project","description":"是否已关联到该项目(角色等同于 exists)"},"linked_to_shot":{"type":"boolean","title":"Linked To Shot","description":"是否已关联到请求中的 shot(未传 shot_id 时为 false)","default":false},"asset_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset Id","description":"命中的资产 ID(如 prop_id/scene_id/costume_id/character_id)"},"link_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Link Id","description":"若已关联到项目,对应 Project*Link 的 id;否则为空"}},"additionalProperties":false,"type":"object","required":["name","exists","linked_to_project"],"title":"EntityNameExistenceItem","description":"单个名称的存在性结果。"},"EntityVariant":{"properties":{"variant_key":{"type":"string","title":"Variant Key","description":"变体键(例如 outfit_v1、wounded_state 等)"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"变体描述(简短)"},"affected_shots":{"items":{"type":"integer"},"type":"array","title":"Affected Shots","description":"涉及镜头序号"},"evidence":{"items":{"$ref":"#/components/schemas/EvidenceSpan"},"type":"array","title":"Evidence","description":"原文依据(可选)"}},"additionalProperties":false,"type":"object","required":["variant_key"],"title":"EntityVariant","description":"实体变体条目(最小可用结构,便于服装/外形演变)。"},"EvidenceSpan":{"properties":{"chunk_id":{"type":"string","title":"Chunk Id","description":"输入文本块的唯一ID(例如 chapter1_p03)"},"start_char":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Start Char","description":"在该 chunk 中的起始字符位置(可选)"},"end_char":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"End Char","description":"在该 chunk 中的结束字符位置(可选)"},"quote":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quote","description":"不超过200字的原文摘录(可选,便于人工审核)"}},"additionalProperties":false,"type":"object","required":["chunk_id"],"title":"EvidenceSpan","description":"可追溯证据:原文定位(chunk + 起止位置/摘录),用于审核与回查。"},"FileDetailRead":{"properties":{"id":{"type":"string","title":"Id","description":"文件 ID"},"type":{"$ref":"#/components/schemas/FileTypeEnum","description":"文件类型"},"name":{"type":"string","title":"Name","description":"文件名/标题"},"thumbnail":{"type":"string","title":"Thumbnail","description":"缩略图 URL/路径","default":""},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"标签"},"usages":{"items":{"$ref":"#/components/schemas/FileUsageRead"},"type":"array","title":"Usages"}},"type":"object","required":["id","type","name"],"title":"FileDetailRead","description":"含 file_usages 列表(详情接口)。"},"FileRead":{"properties":{"id":{"type":"string","title":"Id","description":"文件 ID"},"type":{"$ref":"#/components/schemas/FileTypeEnum","description":"文件类型"},"name":{"type":"string","title":"Name","description":"文件名/标题"},"thumbnail":{"type":"string","title":"Thumbnail","description":"缩略图 URL/路径","default":""},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"标签"}},"type":"object","required":["id","type","name"],"title":"FileRead"},"FileTypeEnum":{"type":"string","enum":["image","video"],"title":"FileTypeEnum"},"FileUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"thumbnail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"usage":{"anyOf":[{"$ref":"#/components/schemas/FileUsageWrite"},{"type":"null"}],"description":"若提供则 upsert 一条 file_usages"}},"type":"object","title":"FileUpdate"},"FileUsageRead":{"properties":{"id":{"type":"integer","title":"Id"},"file_id":{"type":"string","title":"File Id"},"project_id":{"type":"string","title":"Project Id"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id"},"shot_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shot Id"},"usage_kind":{"type":"string","title":"Usage Kind"},"source_ref":{"type":"string","title":"Source Ref"}},"type":"object","required":["id","file_id","project_id","chapter_id","shot_id","usage_kind","source_ref"],"title":"FileUsageRead"},"FileUsageWrite":{"properties":{"project_id":{"type":"string","title":"Project Id","description":"项目 ID"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID"},"shot_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shot Id","description":"镜头 ID"},"usage_kind":{"type":"string","title":"Usage Kind","description":"用途:shot_frame / generated_video / character_image / asset_image / upload / api 等"},"source_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Ref","description":"幂等键(可选)"}},"type":"object","required":["project_id","usage_kind"],"title":"FileUsageWrite","description":"写入 file_usages 的关联信息(与 FileItem 一并提交)。"},"FrameGuidanceDecisionRead":{"properties":{"text":{"type":"string","title":"Text","description":"guidance 原文"},"category":{"type":"string","title":"Category","description":"guidance 分类,如 summary / continuity / composition / screen"},"reason_tag":{"type":"string","title":"Reason Tag","description":"简短原因标签,如 首帧保空间 / 关键帧保轴线","default":""},"reason":{"type":"string","title":"Reason","description":"该 guidance 被保留或压缩的原因说明"}},"type":"object","required":["text","category","reason"],"title":"FrameGuidanceDecisionRead","description":"分镜帧 guidance 的保留/压缩决策结果。"},"GenerationTaskLinkCreate":{"properties":{"task_id":{"type":"string","title":"Task Id","description":"生成任务 ID"},"resource_type":{"type":"string","title":"Resource Type","description":"生成资源类型(如 image/video/text/task_link)"},"relation_type":{"type":"string","title":"Relation Type","description":"业务类型(如 prop/costume/scene 等)"},"relation_entity_id":{"type":"string","title":"Relation Entity Id","description":"关联业务实体 ID"},"file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Id","description":"关联产物文件 ID(files.id;适用于图片/音频/视频)"},"status":{"type":"string","title":"Status","description":"关联状态:accepted=已采用、todo=待操作、rejected=未采用;默认 todo","default":"todo"}},"type":"object","required":["task_id","resource_type","relation_type","relation_entity_id"],"title":"GenerationTaskLinkCreate","description":"创建生成任务关联请求体。"},"GenerationTaskLinkRead":{"properties":{"task_id":{"type":"string","title":"Task Id","description":"生成任务 ID"},"resource_type":{"type":"string","title":"Resource Type","description":"生成资源类型(如 image/video/text/task_link)"},"relation_type":{"type":"string","title":"Relation Type","description":"业务类型(如 prop/costume/scene 等)"},"relation_entity_id":{"type":"string","title":"Relation Entity Id","description":"关联业务实体 ID"},"file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Id","description":"关联产物文件 ID(files.id;适用于图片/音频/视频)"},"status":{"type":"string","title":"Status","description":"关联状态:accepted=已采用、todo=待操作、rejected=未采用"},"id":{"type":"integer","title":"Id","description":"关联行 ID"}},"type":"object","required":["task_id","resource_type","relation_type","relation_entity_id","status","id"],"title":"GenerationTaskLinkRead","description":"生成任务关联返回体。"},"GenerationTaskLinkUpdate":{"properties":{"resource_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resource Type","description":"生成资源类型(如 image/video/text/task_link)"},"relation_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relation Type","description":"业务类型(如 prop/costume/scene 等)"},"relation_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relation Entity Id","description":"关联业务实体 ID"},"file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Id","description":"关联产物文件 ID(files.id;适用于图片/音频/视频)"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"关联状态:accepted=已采用、todo=待操作、rejected=未采用"}},"type":"object","title":"GenerationTaskLinkUpdate","description":"更新生成任务关联请求体(不包含 is_adopted,采用状态由专用接口正向变更)。"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ImageGenerationOptionsRead":{"properties":{"provider":{"type":"string","title":"Provider","description":"供应商稳定键"},"model_id":{"type":"string","title":"Model Id","description":"默认图片模型 ID"},"model_name":{"type":"string","title":"Model Name","description":"默认图片模型名称"},"supported_ratios":{"items":{"type":"string"},"type":"array","title":"Supported Ratios","description":"当前模型支持的目标比例"},"default_resolution_profile":{"type":"string","title":"Default Resolution Profile","description":"当前模型默认分辨率档位"},"ratio_size_profiles":{"additionalProperties":{"additionalProperties":{"type":"string"},"type":"object"},"type":"object","title":"Ratio Size Profiles","description":"按比例和分辨率档位映射得到的像素尺寸"}},"type":"object","required":["provider","model_id","model_name","default_resolution_profile"],"title":"ImageGenerationOptionsRead","description":"当前默认图片模型对应的关键帧规格选项。"},"LogLevel":{"type":"string","enum":["debug","info","warn","error"],"title":"LogLevel","description":"全局日志级别。"},"ModelCategoryKey":{"type":"string","enum":["text","image","video"],"title":"ModelCategoryKey","description":"模型类别:文本/图片/视频。"},"ModelCreate":{"properties":{"name":{"type":"string","title":"Name","description":"模型名称"},"category":{"$ref":"#/components/schemas/ModelCategoryKey","description":"模型类别:text/image/video"},"provider_id":{"type":"string","title":"Provider Id","description":"所属供应商 ID"},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"模型参数(JSON)"},"description":{"type":"string","title":"Description","description":"说明","default":""},"created_by":{"type":"string","title":"Created By","description":"创建人","default":""},"id":{"type":"string","title":"Id","description":"模型 ID"}},"type":"object","required":["name","category","provider_id","id"],"title":"ModelCreate","description":"创建模型请求体。"},"ModelRead":{"properties":{"name":{"type":"string","title":"Name","description":"模型名称"},"category":{"$ref":"#/components/schemas/ModelCategoryKey","description":"模型类别:text/image/video"},"provider_id":{"type":"string","title":"Provider Id","description":"所属供应商 ID"},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"模型参数(JSON)"},"description":{"type":"string","title":"Description","description":"说明","default":""},"created_by":{"type":"string","title":"Created By","description":"创建人","default":""},"id":{"type":"string","title":"Id","description":"模型 ID"}},"type":"object","required":["name","category","provider_id","id"],"title":"ModelRead","description":"对外返回的模型信息。"},"ModelSettingsRead":{"properties":{"default_text_model_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Text Model Id","description":"默认文本模型 ID"},"default_image_model_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Image Model Id","description":"默认图片模型 ID"},"default_video_model_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Video Model Id","description":"默认视频模型 ID"},"api_timeout":{"type":"integer","title":"Api Timeout","description":"API 超时(秒)","default":30},"log_level":{"$ref":"#/components/schemas/LogLevel","description":"日志级别","default":"info"},"id":{"type":"integer","title":"Id","description":"设置行 ID(通常为 1)"}},"type":"object","required":["id"],"title":"ModelSettingsRead","description":"对外返回的模型全局设置。"},"ModelSettingsUpdate":{"properties":{"default_text_model_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Text Model Id","description":"默认文本模型 ID"},"default_image_model_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Image Model Id","description":"默认图片模型 ID"},"default_video_model_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Video Model Id","description":"默认视频模型 ID"},"api_timeout":{"type":"integer","title":"Api Timeout","description":"API 超时(秒)","default":30},"log_level":{"$ref":"#/components/schemas/LogLevel","description":"日志级别","default":"info"}},"type":"object","title":"ModelSettingsUpdate","description":"更新或保存模型全局设置请求体。"},"ModelUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"模型名称"},"category":{"anyOf":[{"$ref":"#/components/schemas/ModelCategoryKey"},{"type":"null"}],"description":"模型类别"},"provider_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id","description":"所属供应商 ID"},"params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Params","description":"模型参数(JSON)"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"说明"}},"type":"object","title":"ModelUpdate","description":"更新模型请求体(全部可选)。"},"PaginatedData_Any_":{"properties":{"items":{"items":{},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[Any]"},"PaginatedData_ChapterRead_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ChapterRead"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[ChapterRead]"},"PaginatedData_FileRead_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/FileRead"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[FileRead]"},"PaginatedData_GenerationTaskLinkRead_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GenerationTaskLinkRead"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[GenerationTaskLinkRead]"},"PaginatedData_ModelRead_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ModelRead"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[ModelRead]"},"PaginatedData_ProjectRead_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ProjectRead"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[ProjectRead]"},"PaginatedData_PromptTemplateRead_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/PromptTemplateRead"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[PromptTemplateRead]"},"PaginatedData_ProviderRead_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ProviderRead"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[ProviderRead]"},"PaginatedData_ShotDetailRead_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ShotDetailRead"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[ShotDetailRead]"},"PaginatedData_ShotDialogLineRead_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ShotDialogLineRead"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[ShotDialogLineRead]"},"PaginatedData_ShotFrameImageRead_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ShotFrameImageRead"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[ShotFrameImageRead]"},"PaginatedData_ShotLinkedAssetItem_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ShotLinkedAssetItem"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[ShotLinkedAssetItem]"},"PaginatedData_ShotRead_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ShotRead"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[ShotRead]"},"PaginatedData_TaskListItemRead_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TaskListItemRead"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[TaskListItemRead]"},"PaginatedData_dict_str__Any__":{"properties":{"items":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Items","description":"当前页数据"},"pagination":{"$ref":"#/components/schemas/Pagination","description":"分页信息"}},"type":"object","required":["items","pagination"],"title":"PaginatedData[dict[str, Any]]"},"Pagination":{"properties":{"page":{"type":"integer","title":"Page","description":"当前页,从 1 开始"},"page_size":{"type":"integer","title":"Page Size","description":"每页条数"},"total":{"type":"integer","title":"Total","description":"总条数"},"max_page":{"type":"integer","title":"Max Page","description":"最大页码"}},"type":"object","required":["page","page_size","total","max_page"],"title":"Pagination","description":"分页信息。"},"ProjectActorLinkRead":{"properties":{"id":{"type":"integer","title":"Id","description":"关联行 ID"},"project_id":{"type":"string","title":"Project Id","description":"项目 ID"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(可空)"},"shot_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shot Id","description":"镜头 ID(可空)"},"actor_id":{"type":"string","title":"Actor Id"},"thumbnail":{"type":"string","title":"Thumbnail","description":"演员缩略图下载地址","default":""}},"type":"object","required":["id","project_id","actor_id"],"title":"ProjectActorLinkRead"},"ProjectAssetLinkCreate":{"properties":{"project_id":{"type":"string","title":"Project Id"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id"},"shot_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shot Id"},"asset_id":{"type":"string","title":"Asset Id"}},"type":"object","required":["project_id","asset_id"],"title":"ProjectAssetLinkCreate"},"ProjectCostumeLinkRead":{"properties":{"id":{"type":"integer","title":"Id","description":"关联行 ID"},"project_id":{"type":"string","title":"Project Id","description":"项目 ID"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(可空)"},"shot_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shot Id","description":"镜头 ID(可空)"},"costume_id":{"type":"string","title":"Costume Id"},"thumbnail":{"type":"string","title":"Thumbnail","description":"服装缩略图下载地址","default":""}},"type":"object","required":["id","project_id","costume_id"],"title":"ProjectCostumeLinkRead"},"ProjectCreate":{"properties":{"name":{"type":"string","title":"Name","description":"项目名称"},"description":{"type":"string","title":"Description","description":"项目简介","default":""},"style":{"$ref":"#/components/schemas/ProjectStyle","description":"题材/风格","examples":["真人都市","真人科幻","真人古装","动漫科幻","动漫3D","国漫","水墨画"]},"visual_style":{"$ref":"#/components/schemas/ProjectVisualStyle","description":"画面表现形式","default":"现实"},"seed":{"type":"integer","title":"Seed","description":"随机种子","default":0},"unify_style":{"type":"boolean","title":"Unify Style","description":"是否统一风格","default":true},"progress":{"type":"integer","title":"Progress","description":"进度百分比(0-100)","default":0},"default_video_ratio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Video Ratio","description":"项目级默认视频比例;分镜未覆盖时生效"},"stats":{"additionalProperties":true,"type":"object","title":"Stats","description":"聚合统计(JSON)"},"id":{"type":"string","title":"Id","description":"项目 ID"}},"type":"object","required":["name","style","id"],"title":"ProjectCreate"},"ProjectPropLinkRead":{"properties":{"id":{"type":"integer","title":"Id","description":"关联行 ID"},"project_id":{"type":"string","title":"Project Id","description":"项目 ID"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(可空)"},"shot_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shot Id","description":"镜头 ID(可空)"},"prop_id":{"type":"string","title":"Prop Id"},"thumbnail":{"type":"string","title":"Thumbnail","description":"道具缩略图下载地址","default":""}},"type":"object","required":["id","project_id","prop_id"],"title":"ProjectPropLinkRead"},"ProjectRead":{"properties":{"name":{"type":"string","title":"Name","description":"项目名称"},"description":{"type":"string","title":"Description","description":"项目简介","default":""},"style":{"$ref":"#/components/schemas/ProjectStyle","description":"题材/风格","examples":["真人都市","真人科幻","真人古装","动漫科幻","动漫3D","国漫","水墨画"]},"visual_style":{"$ref":"#/components/schemas/ProjectVisualStyle","description":"画面表现形式","default":"现实"},"seed":{"type":"integer","title":"Seed","description":"随机种子","default":0},"unify_style":{"type":"boolean","title":"Unify Style","description":"是否统一风格","default":true},"progress":{"type":"integer","title":"Progress","description":"进度百分比(0-100)","default":0},"default_video_ratio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Video Ratio","description":"项目级默认视频比例;分镜未覆盖时生效"},"stats":{"additionalProperties":true,"type":"object","title":"Stats","description":"聚合统计(JSON)"},"id":{"type":"string","title":"Id"}},"type":"object","required":["name","style","id"],"title":"ProjectRead"},"ProjectSceneLinkRead":{"properties":{"id":{"type":"integer","title":"Id","description":"关联行 ID"},"project_id":{"type":"string","title":"Project Id","description":"项目 ID"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(可空)"},"shot_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shot Id","description":"镜头 ID(可空)"},"scene_id":{"type":"string","title":"Scene Id"},"thumbnail":{"type":"string","title":"Thumbnail","description":"场景缩略图下载地址","default":""}},"type":"object","required":["id","project_id","scene_id"],"title":"ProjectSceneLinkRead"},"ProjectStyle":{"type":"string","enum":["真人都市","真人科幻","真人古装","动漫科幻","动漫3D","国漫","水墨画"],"title":"ProjectStyle","description":"项目题材/风格维度(不用于区分真人/动漫)。"},"ProjectStyleOptionsRead":{"properties":{"visual_styles":{"items":{"$ref":"#/components/schemas/StyleOption"},"type":"array","title":"Visual Styles","description":"视觉风格可选项"},"styles_by_visual_style":{"additionalProperties":{"items":{"$ref":"#/components/schemas/StyleOption"},"type":"array"},"type":"object","title":"Styles By Visual Style","description":"按视觉风格分组的视频风格选项"},"default_style_by_visual_style":{"additionalProperties":{"type":"string"},"type":"object","title":"Default Style By Visual Style","description":"各视觉风格默认视频风格"}},"type":"object","title":"ProjectStyleOptionsRead","description":"项目风格候选项。"},"ProjectUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"style":{"anyOf":[{"$ref":"#/components/schemas/ProjectStyle"},{"type":"null"}],"description":"题材/风格","examples":["真人都市","真人科幻","真人古装","动漫科幻","动漫3D","国漫","水墨画"]},"visual_style":{"anyOf":[{"$ref":"#/components/schemas/ProjectVisualStyle"},{"type":"null"}]},"seed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Seed"},"unify_style":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Unify Style"},"progress":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Progress"},"default_video_ratio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Video Ratio"},"stats":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Stats"}},"type":"object","title":"ProjectUpdate"},"ProjectVisualStyle":{"type":"string","enum":["现实","动漫"],"title":"ProjectVisualStyle","description":"画面表现形式维度:用于区分现实/动漫等。"},"PromptCategory":{"type":"string","enum":["frame_head_image","frame_tail_image","frame_key_image","frame_head_prompt","frame_tail_prompt","frame_key_prompt","video_prompt","storyboard_prompt","bgm","sfx","character_image_front","character_image_other","actor_image_front","actor_image_other","prop_image_front","prop_image_other","scene_image_front","scene_image_other","costume_image_front","costume_image_other","combined"],"title":"PromptCategory","description":"提示词模板类别。"},"PromptCategoryOptionRead":{"properties":{"value":{"$ref":"#/components/schemas/PromptCategory","description":"类别枚举值"},"label":{"type":"string","title":"Label","description":"中文名称"},"description":{"type":"string","title":"Description","description":"类别简介","default":""}},"type":"object","required":["value","label"],"title":"PromptCategoryOptionRead","description":"提示词类别选项(枚举值 + 中文标签 + 简介)。"},"PromptTemplateCreate":{"properties":{"category":{"$ref":"#/components/schemas/PromptCategory","description":"模板类别"},"name":{"type":"string","title":"Name","description":"模板名称"},"content":{"type":"string","title":"Content","description":"模板内容"},"preview":{"type":"string","title":"Preview","description":"预览文案","default":""},"variables":{"items":{"type":"string"},"type":"array","title":"Variables","description":"变量名列表"},"is_default":{"type":"boolean","title":"Is Default","description":"是否为默认提示词","default":false}},"type":"object","required":["category","name","content"],"title":"PromptTemplateCreate","description":"创建提示词模板。id 由后端自动生成;is_system 不可由客户端设置。"},"PromptTemplateRead":{"properties":{"id":{"type":"string","title":"Id","description":"模板 ID"},"category":{"$ref":"#/components/schemas/PromptCategory","description":"模板类别"},"name":{"type":"string","title":"Name","description":"模板名称"},"preview":{"type":"string","title":"Preview","description":"预览文案"},"content":{"type":"string","title":"Content","description":"模板内容"},"variables":{"items":{"type":"string"},"type":"array","title":"Variables","description":"变量名列表"},"is_default":{"type":"boolean","title":"Is Default","description":"是否为默认提示词"},"is_system":{"type":"boolean","title":"Is System","description":"是否为系统预置"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"创建时间"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"最后更新时间"}},"type":"object","required":["id","category","name","preview","content","variables","is_default","is_system","created_at","updated_at"],"title":"PromptTemplateRead","description":"读取提示词模板(含全部字段)。"},"PromptTemplateUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"模板名称"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content","description":"模板内容"},"preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preview","description":"预览文案"},"variables":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Variables","description":"变量名列表(整体替换)"},"is_default":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Default","description":"是否为默认提示词"}},"type":"object","title":"PromptTemplateUpdate","description":"局部更新提示词模板。不含 id / is_system。"},"PropInfoAnalysisRequest":{"properties":{"relation_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relation Entity Id","description":"任务关联实体 ID(资产页恢复任务可选)"},"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id","description":"项目 ID(异步任务关联可选)"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(异步任务关联可选)"},"prop_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prop Context","description":"原文道具上下文(可为空;用于提供额外背景,帮助判断缺失信息)"},"prop_description":{"type":"string","minLength":1,"title":"Prop Description","description":"原文道具描述"}},"type":"object","required":["prop_description"],"title":"PropInfoAnalysisRequest","description":"道具信息缺失分析请求。"},"PropInfoAnalysisResult":{"properties":{"issues":{"items":{"type":"string"},"type":"array","title":"Issues"},"optimized_description":{"type":"string","title":"Optimized Description"}},"additionalProperties":false,"type":"object","required":["issues","optimized_description"],"title":"PropInfoAnalysisResult","description":"根据原文道具描述,分析缺少的信息,并给出优化后的可生成道具描述。"},"ProviderCreate":{"properties":{"name":{"type":"string","title":"Name","description":"供应商名称"},"base_url":{"type":"string","title":"Base Url","description":"文本/通用 API Base URL"},"image_base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Base Url","description":"图片能力 API Base URL(可选覆盖)"},"video_base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Base Url","description":"视频能力 API Base URL(可选覆盖)"},"description":{"type":"string","title":"Description","description":"说明","default":""},"status":{"$ref":"#/components/schemas/ProviderStatus","description":"状态:active/testing/disabled","default":"testing"},"created_by":{"type":"string","title":"Created By","description":"创建人","default":""},"id":{"type":"string","title":"Id","description":"供应商 ID"},"api_key":{"type":"string","title":"Api Key","description":"API Key(敏感,不在响应中回显)","default":""},"api_secret":{"type":"string","title":"Api Secret","description":"API Secret(敏感,不在响应中回显)","default":""}},"type":"object","required":["name","base_url","id"],"title":"ProviderCreate","description":"创建供应商时的请求体,允许填写敏感字段。"},"ProviderRead":{"properties":{"name":{"type":"string","title":"Name","description":"供应商名称"},"base_url":{"type":"string","title":"Base Url","description":"文本/通用 API Base URL"},"image_base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Base Url","description":"图片能力 API Base URL(可选覆盖)"},"video_base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Base Url","description":"视频能力 API Base URL(可选覆盖)"},"description":{"type":"string","title":"Description","description":"说明","default":""},"status":{"$ref":"#/components/schemas/ProviderStatus","description":"状态:active/testing/disabled","default":"testing"},"created_by":{"type":"string","title":"Created By","description":"创建人","default":""},"id":{"type":"string","title":"Id","description":"供应商 ID"}},"type":"object","required":["name","base_url","id"],"title":"ProviderRead","description":"对外返回的供应商信息(不包含 api_key/api_secret)。"},"ProviderStatus":{"type":"string","enum":["active","testing","disabled"],"title":"ProviderStatus","description":"供应商启用状态。"},"ProviderSupportedRead":{"properties":{"key":{"type":"string","title":"Key","description":"供应商稳定键"},"display_name":{"type":"string","title":"Display Name","description":"供应商展示名"},"aliases":{"items":{"type":"string"},"type":"array","title":"Aliases","description":"可识别别名"},"supported_categories":{"items":{"$ref":"#/components/schemas/ModelCategoryKey"},"type":"array","title":"Supported Categories","description":"支持的模型类别"},"default_base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Base Url","description":"默认 API Base URL"},"requires_api_key":{"type":"boolean","title":"Requires Api Key","description":"是否要求 api_key","default":true},"requires_api_secret":{"type":"boolean","title":"Requires Api Secret","description":"是否要求 api_secret","default":false},"is_experimental":{"type":"boolean","title":"Is Experimental","description":"是否实验性供应商","default":false}},"type":"object","required":["key","display_name"],"title":"ProviderSupportedRead","description":"系统支持的供应商能力清单。"},"ProviderUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"供应商名称"},"base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base Url","description":"文本/通用 API Base URL"},"image_base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Base Url","description":"图片能力 API Base URL(可选覆盖)"},"video_base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Base Url","description":"视频能力 API Base URL(可选覆盖)"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"说明"},"status":{"anyOf":[{"$ref":"#/components/schemas/ProviderStatus"},{"type":"null"}],"description":"状态:active/testing/disabled"},"api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key","description":"API Key(敏感,不在响应中回显)"},"api_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Secret","description":"API Secret(敏感,不在响应中回显)"}},"type":"object","title":"ProviderUpdate","description":"更新供应商时的可选字段。"},"RenderedPromptResponse":{"properties":{"prompt":{"type":"string","title":"Prompt","description":"渲染后的提示词(已套用模板与变量替换)"},"images":{"items":{"type":"string"},"type":"array","title":"Images","description":"参考图 file_id 列表(自动选择;顺序有效)"}},"type":"object","required":["prompt"],"title":"RenderedPromptResponse"},"RenderedShotFramePromptRead":{"properties":{"base_prompt":{"type":"string","title":"Base Prompt","description":"原始基础提示词(不含图片映射说明)"},"rendered_prompt":{"type":"string","title":"Rendered Prompt","description":"最终提交给模型的提示词(含图片映射说明)"},"selected_guidance":{"items":{"type":"string"},"type":"array","title":"Selected Guidance","description":"最终 prompt 实际保留的 guidance 列表"},"dropped_guidance":{"items":{"type":"string"},"type":"array","title":"Dropped Guidance","description":"本次渲染中被压缩掉的 guidance 列表"},"selected_guidance_details":{"items":{"$ref":"#/components/schemas/FrameGuidanceDecisionRead"},"type":"array","title":"Selected Guidance Details","description":"最终保留 guidance 的决策详情"},"dropped_guidance_details":{"items":{"$ref":"#/components/schemas/FrameGuidanceDecisionRead"},"type":"array","title":"Dropped Guidance Details","description":"被压缩 guidance 的决策详情"},"images":{"items":{"type":"string"},"type":"array","title":"Images","description":"最终参考图 file_id 列表,顺序与 mappings 一致"},"mappings":{"items":{"$ref":"#/components/schemas/ShotFramePromptMappingRead"},"type":"array","title":"Mappings","description":"图片与实体名称的映射关系,顺序与 images 完全一致"}},"type":"object","required":["base_prompt","rendered_prompt"],"title":"RenderedShotFramePromptRead","description":"关键帧最终生成提示词渲染结果。"},"SceneInfoAnalysisRequest":{"properties":{"relation_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relation Entity Id","description":"任务关联实体 ID(资产页恢复任务可选)"},"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id","description":"项目 ID(异步任务关联可选)"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(异步任务关联可选)"},"scene_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scene Context","description":"原文场景上下文(可为空;用于提供额外背景,帮助判断缺失信息)"},"scene_description":{"type":"string","minLength":1,"title":"Scene Description","description":"原文场景描述"}},"type":"object","required":["scene_description"],"title":"SceneInfoAnalysisRequest","description":"场景信息缺失分析请求。"},"SceneInfoAnalysisResult":{"properties":{"issues":{"items":{"type":"string"},"type":"array","title":"Issues"},"optimized_description":{"type":"string","title":"Optimized Description"}},"additionalProperties":false,"type":"object","required":["issues","optimized_description"],"title":"SceneInfoAnalysisResult","description":"根据原文场景描述,分析缺少的信息,并给出优化后的可生成场景描述。"},"ScriptConsistencyCheckRequest":{"properties":{"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id","description":"项目 ID(异步任务关联可选)"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(异步任务关联可选)"},"script_text":{"type":"string","minLength":1,"title":"Script Text","description":"完整剧本文本"}},"type":"object","required":["script_text"],"title":"ScriptConsistencyCheckRequest","description":"一致性检查请求(角色混淆)。"},"ScriptConsistencyCheckResult":{"properties":{"issues":{"items":{"$ref":"#/components/schemas/ScriptConsistencyIssue"},"type":"array","title":"Issues","description":"问题列表"},"has_issues":{"type":"boolean","title":"Has Issues","description":"是否发现问题"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary","description":"总结(可选)"}},"additionalProperties":false,"type":"object","required":["has_issues"],"title":"ScriptConsistencyCheckResult","description":"基于原文的一致性检查结果(聚焦角色混淆)。"},"ScriptConsistencyIssue":{"properties":{"issue_type":{"type":"string","const":"character_confusion","title":"Issue Type","description":"固定为角色混淆类问题","default":"character_confusion"},"character_candidates":{"items":{"type":"string"},"type":"array","title":"Character Candidates","description":"涉及的角色候选(名字/称呼/ID 皆可,优先用原文称呼)"},"description":{"type":"string","title":"Description","description":"问题描述(为什么会混淆)"},"suggestion":{"type":"string","title":"Suggestion","description":"修改建议(如何改写以消除混淆)"},"affected_lines":{"anyOf":[{"additionalProperties":{"type":"integer"},"type":"object"},{"type":"null"}],"title":"Affected Lines","description":"受影响的行号范围,形如 {start_line: x, end_line: y}"},"evidence":{"items":{"$ref":"#/components/schemas/EvidenceSpan"},"type":"array","title":"Evidence","description":"原文依据(可选)"}},"additionalProperties":false,"type":"object","required":["description","suggestion"],"title":"ScriptConsistencyIssue","description":"角色混淆类一致性问题:同一角色在不同镜头被赋予不同身份/行为主体导致混淆。"},"ScriptDividerRequest":{"properties":{"script_text":{"type":"string","minLength":1,"title":"Script Text","description":"完整剧本文本"},"write_to_db":{"type":"boolean","title":"Write To Db","description":"是否将分镜写入数据库(AI Studio shots 表)","default":false},"chapter_id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(write_to_db=true 时必填)"}},"type":"object","required":["script_text"],"title":"ScriptDividerRequest","description":"剧本分镜请求。"},"ScriptDivisionResult":{"properties":{"shots":{"items":{"$ref":"#/components/schemas/ShotDivision"},"type":"array","title":"Shots","description":"分镜列表"},"total_shots":{"type":"integer","minimum":0.0,"title":"Total Shots","description":"总镜头数"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"拆分说明或建议(可选)"}},"additionalProperties":false,"type":"object","required":["total_shots"],"title":"ScriptDivisionResult","description":"剧本分镜结果:镜头列表(每镜起止行号+预览文本)。"},"ScriptExtractRequest":{"properties":{"project_id":{"type":"string","minLength":1,"title":"Project Id","description":"项目 ID"},"chapter_id":{"type":"string","minLength":1,"title":"Chapter Id","description":"章节 ID"},"script_division":{"additionalProperties":true,"type":"object","title":"Script Division","description":"分镜结果(ScriptDivisionResult 序列化)"},"consistency":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Consistency","description":"一致性检查结果(可选;ScriptConsistencyCheckResult 序列化)"},"refresh_cache":{"type":"boolean","title":"Refresh Cache","description":"是否跳过后端缓存并强制重新提取","default":false}},"type":"object","required":["project_id","chapter_id","script_division"],"title":"ScriptExtractRequest","description":"项目级信息提取请求(最终输出)。"},"ScriptOptimizationResult":{"properties":{"optimized_script_text":{"type":"string","title":"Optimized Script Text","description":"优化后的剧本文本"},"change_summary":{"type":"string","title":"Change Summary","description":"改动摘要(只围绕 issues)"}},"additionalProperties":false,"type":"object","required":["optimized_script_text","change_summary"],"title":"ScriptOptimizationResult","description":"剧本优化输出:仅在发现角色混淆问题时使用。"},"ScriptOptimizeRequest":{"properties":{"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id","description":"项目 ID(异步任务关联可选)"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(异步任务关联可选)"},"script_text":{"type":"string","minLength":1,"title":"Script Text","description":"原文剧本文本"},"consistency":{"additionalProperties":true,"type":"object","title":"Consistency","description":"一致性检查输出(ScriptConsistencyCheckResult 序列化)"}},"type":"object","required":["script_text","consistency"],"title":"ScriptOptimizeRequest","description":"剧本优化请求(基于一致性检查结果)。"},"ScriptSimplificationResult":{"properties":{"simplified_script_text":{"type":"string","title":"Simplified Script Text","description":"精简后的剧本文本"},"simplification_summary":{"type":"string","title":"Simplification Summary","description":"精简策略摘要(说明删改原则)"}},"additionalProperties":false,"type":"object","required":["simplified_script_text","simplification_summary"],"title":"ScriptSimplificationResult","description":"剧本精简输出:在保留剧情主体与连续性的前提下压缩篇幅。"},"ScriptSimplifyRequest":{"properties":{"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id","description":"项目 ID(异步任务关联可选)"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(异步任务关联可选)"},"script_text":{"type":"string","minLength":1,"title":"Script Text","description":"原文剧本文本"}},"type":"object","required":["script_text"],"title":"ScriptSimplifyRequest","description":"智能精简剧本请求。"},"ShotAssetOverviewItem":{"properties":{"key":{"type":"string","title":"Key","description":"合并键:type:name"},"type":{"type":"string","enum":["character","prop","scene","costume"],"title":"Type","description":"实体类型:character/prop/scene/costume"},"name":{"type":"string","title":"Name","description":"资产名称"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"候选描述(来自 extraction payload)"},"thumbnail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail","description":"缩略图"},"file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Id","description":"缩略图或参考图文件 ID"},"source":{"type":"string","enum":["linked","candidate","both"],"title":"Source","description":"来源:linked/candidate/both"},"candidate_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Candidate Id","description":"候选项 ID"},"candidate_status":{"anyOf":[{"$ref":"#/components/schemas/ShotCandidateStatus"},{"type":"null"}],"description":"候选确认状态"},"linked_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linked Entity Id","description":"当前已关联实体 ID"},"linked_image_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Linked Image Id","description":"当前已关联实体的 image 行 ID"},"is_linked":{"type":"boolean","title":"Is Linked","description":"当前是否已关联到镜头"}},"type":"object","required":["key","type","name","source","is_linked"],"title":"ShotAssetOverviewItem","description":"分镜资产总览项:统一返回已关联资产与提取候选的合并视图。"},"ShotAssetsOverviewRead":{"properties":{"shot_id":{"type":"string","title":"Shot Id","description":"镜头 ID"},"skip_extraction":{"type":"boolean","title":"Skip Extraction","description":"是否明确跳过提取"},"status":{"$ref":"#/components/schemas/ShotStatus","description":"镜头流程状态"},"summary":{"$ref":"#/components/schemas/ShotAssetsOverviewSummary","description":"总览统计"},"items":{"items":{"$ref":"#/components/schemas/ShotAssetOverviewItem"},"type":"array","title":"Items","description":"资产总览项"}},"type":"object","required":["shot_id","skip_extraction","status","summary"],"title":"ShotAssetsOverviewRead"},"ShotAssetsOverviewSummary":{"properties":{"linked_count":{"type":"integer","title":"Linked Count","description":"已关联项数量"},"pending_count":{"type":"integer","title":"Pending Count","description":"待确认候选数量"},"ignored_count":{"type":"integer","title":"Ignored Count","description":"已忽略候选数量"},"total_count":{"type":"integer","title":"Total Count","description":"总项数(含 ignored)"}},"type":"object","required":["linked_count","pending_count","ignored_count","total_count"],"title":"ShotAssetsOverviewSummary"},"ShotCandidateStatus":{"type":"string","enum":["pending","linked","ignored"],"title":"ShotCandidateStatus","description":"镜头提取候选确认状态。"},"ShotCandidateType":{"type":"string","enum":["character","scene","prop","costume"],"title":"ShotCandidateType","description":"镜头提取候选类型。"},"ShotCharacterLinkCreate":{"properties":{"shot_id":{"type":"string","title":"Shot Id"},"character_id":{"type":"string","title":"Character Id"},"index":{"type":"integer","title":"Index","default":0},"note":{"type":"string","title":"Note","default":""}},"type":"object","required":["shot_id","character_id"],"title":"ShotCharacterLinkCreate"},"ShotCharacterLinkRead":{"properties":{"id":{"type":"integer","title":"Id","description":"关联行 ID"},"shot_id":{"type":"string","title":"Shot Id","description":"镜头 ID"},"character_id":{"type":"string","title":"Character Id","description":"角色 ID"},"index":{"type":"integer","title":"Index","description":"镜头内角色排序","default":0},"note":{"type":"string","title":"Note","description":"备注","default":""}},"type":"object","required":["id","shot_id","character_id"],"title":"ShotCharacterLinkRead"},"ShotCreate":{"properties":{"id":{"type":"string","title":"Id","description":"镜头 ID"},"chapter_id":{"type":"string","title":"Chapter Id","description":"所属章节 ID"},"index":{"type":"integer","title":"Index","description":"镜头序号(章节内唯一)"},"title":{"type":"string","title":"Title","description":"镜头标题"},"thumbnail":{"type":"string","title":"Thumbnail","description":"缩略图 URL/路径","default":""},"status":{"$ref":"#/components/schemas/ShotStatus","description":"镜头状态","default":"pending"},"skip_extraction":{"type":"boolean","title":"Skip Extraction","description":"是否明确跳过信息提取","default":false},"script_excerpt":{"type":"string","title":"Script Excerpt","description":"剧本摘录","default":""},"generated_video_file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated Video File Id","description":"已生成视频关联的文件 ID(files.id,type=video)"}},"type":"object","required":["id","chapter_id","index","title"],"title":"ShotCreate"},"ShotDetailCreate":{"properties":{"id":{"type":"string","title":"Id","description":"镜头 ID(与 shots.id 共享主键)"},"camera_shot":{"$ref":"#/components/schemas/CameraShotType","description":"景别"},"angle":{"$ref":"#/components/schemas/CameraAngle","description":"机位角度"},"movement":{"$ref":"#/components/schemas/CameraMovement","description":"运镜方式"},"scene_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scene Id","description":"关联场景 ID(可空)"},"duration":{"type":"integer","title":"Duration","description":"时长(秒)","default":0},"override_video_ratio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Override Video Ratio","description":"分镜级视频比例覆盖;为空表示继承项目默认"},"mood_tags":{"items":{"type":"string"},"type":"array","title":"Mood Tags","description":"情绪标签"},"atmosphere":{"type":"string","title":"Atmosphere","description":"氛围描述","default":""},"follow_atmosphere":{"type":"boolean","title":"Follow Atmosphere","description":"是否沿用氛围","default":true},"has_bgm":{"type":"boolean","title":"Has Bgm","description":"是否包含 BGM","default":false},"vfx_type":{"$ref":"#/components/schemas/VFXType","description":"视效类型","default":"NONE"},"vfx_note":{"type":"string","title":"Vfx Note","description":"视效说明","default":""},"action_beats":{"items":{"type":"string"},"type":"array","title":"Action Beats","description":"动作拍点(按时间顺序排列)"},"first_frame_prompt":{"type":"string","title":"First Frame Prompt","description":"镜头分镜首帧提示词","default":""},"last_frame_prompt":{"type":"string","title":"Last Frame Prompt","description":"镜头分镜尾帧提示词","default":""},"key_frame_prompt":{"type":"string","title":"Key Frame Prompt","description":"镜头分镜关键帧提示词","default":""}},"type":"object","required":["id","camera_shot","angle","movement"],"title":"ShotDetailCreate"},"ShotDetailRead":{"properties":{"id":{"type":"string","title":"Id","description":"镜头 ID(与 shots.id 共享主键)"},"camera_shot":{"$ref":"#/components/schemas/CameraShotType","description":"景别"},"angle":{"$ref":"#/components/schemas/CameraAngle","description":"机位角度"},"movement":{"$ref":"#/components/schemas/CameraMovement","description":"运镜方式"},"scene_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scene Id","description":"关联场景 ID(可空)"},"duration":{"type":"integer","title":"Duration","description":"时长(秒)","default":0},"override_video_ratio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Override Video Ratio","description":"分镜级视频比例覆盖;为空表示继承项目默认"},"mood_tags":{"items":{"type":"string"},"type":"array","title":"Mood Tags","description":"情绪标签"},"atmosphere":{"type":"string","title":"Atmosphere","description":"氛围描述","default":""},"follow_atmosphere":{"type":"boolean","title":"Follow Atmosphere","description":"是否沿用氛围","default":true},"has_bgm":{"type":"boolean","title":"Has Bgm","description":"是否包含 BGM","default":false},"vfx_type":{"$ref":"#/components/schemas/VFXType","description":"视效类型","default":"NONE"},"vfx_note":{"type":"string","title":"Vfx Note","description":"视效说明","default":""},"action_beats":{"items":{"type":"string"},"type":"array","title":"Action Beats","description":"动作拍点(按时间顺序排列)"},"first_frame_prompt":{"type":"string","title":"First Frame Prompt","description":"镜头分镜首帧提示词","default":""},"last_frame_prompt":{"type":"string","title":"Last Frame Prompt","description":"镜头分镜尾帧提示词","default":""},"key_frame_prompt":{"type":"string","title":"Key Frame Prompt","description":"镜头分镜关键帧提示词","default":""}},"type":"object","required":["id","camera_shot","angle","movement"],"title":"ShotDetailRead"},"ShotDetailUpdate":{"properties":{"camera_shot":{"anyOf":[{"$ref":"#/components/schemas/CameraShotType"},{"type":"null"}]},"angle":{"anyOf":[{"$ref":"#/components/schemas/CameraAngle"},{"type":"null"}]},"movement":{"anyOf":[{"$ref":"#/components/schemas/CameraMovement"},{"type":"null"}]},"scene_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scene Id"},"duration":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration"},"override_video_ratio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Override Video Ratio"},"mood_tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Mood Tags"},"atmosphere":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Atmosphere"},"follow_atmosphere":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Follow Atmosphere"},"has_bgm":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Bgm"},"vfx_type":{"anyOf":[{"$ref":"#/components/schemas/VFXType"},{"type":"null"}]},"vfx_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vfx Note"},"action_beats":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Action Beats"},"first_frame_prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Frame Prompt"},"last_frame_prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Frame Prompt"},"key_frame_prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key Frame Prompt"}},"type":"object","title":"ShotDetailUpdate"},"ShotDialogLineCreate":{"properties":{"shot_detail_id":{"type":"string","title":"Shot Detail Id"},"index":{"type":"integer","title":"Index","default":0},"text":{"type":"string","title":"Text"},"line_mode":{"$ref":"#/components/schemas/DialogueLineMode","default":"DIALOGUE"},"speaker_character_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Character Id"},"target_character_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Character Id"},"speaker_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Name"},"target_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Name"}},"type":"object","required":["shot_detail_id","text"],"title":"ShotDialogLineCreate"},"ShotDialogLineRead":{"properties":{"id":{"type":"integer","title":"Id","description":"对话行 ID"},"shot_detail_id":{"type":"string","title":"Shot Detail Id","description":"所属镜头细节 ID"},"index":{"type":"integer","title":"Index","description":"行号(镜头内排序)","default":0},"text":{"type":"string","title":"Text","description":"台词内容"},"line_mode":{"$ref":"#/components/schemas/DialogueLineMode","description":"对白模式","default":"DIALOGUE"},"speaker_character_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Character Id","description":"说话角色 ID"},"target_character_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Character Id","description":"听者角色 ID"},"speaker_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Name","description":"说话角色名称(用于回填关联;可空)"},"target_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Name","description":"听者角色名称(用于回填关联;可空)"}},"type":"object","required":["id","shot_detail_id","text"],"title":"ShotDialogLineRead"},"ShotDialogLineUpdate":{"properties":{"index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Index"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"line_mode":{"anyOf":[{"$ref":"#/components/schemas/DialogueLineMode"},{"type":"null"}]},"speaker_character_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Character Id"},"target_character_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Character Id"},"speaker_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Name"},"target_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Name"}},"type":"object","title":"ShotDialogLineUpdate"},"ShotDialogueCandidateStatus":{"type":"string","enum":["pending","accepted","ignored"],"title":"ShotDialogueCandidateStatus","description":"镜头对白提取候选确认状态。"},"ShotDivision":{"properties":{"index":{"type":"integer","minimum":1.0,"title":"Index","description":"镜头序号(章节内唯一)"},"start_line":{"type":"integer","minimum":1.0,"title":"Start Line","description":"起始行号(1-based)"},"end_line":{"type":"integer","minimum":1.0,"title":"End Line","description":"结束行号(1-based)"},"script_excerpt":{"type":"string","title":"Script Excerpt","description":"镜头对应的剧本摘录/文本"},"shot_name":{"type":"string","title":"Shot Name","description":"镜头名称(分镜名/镜头标题)","default":""},"time_of_day":{"anyOf":[{"type":"string","enum":["DAY","NIGHT","DAWN","DUSK","UNKNOWN","日","夜","黎明","黄昏","不明","未知"]},{"type":"null"}],"title":"Time Of Day","description":"时间(日/夜/未知等,可选)"}},"additionalProperties":false,"type":"object","required":["index","start_line","end_line","script_excerpt"],"title":"ShotDivision","description":"剧本分镜中的单镜信息:行号 + 预览文本(可选弱语义)。"},"ShotExtractedCandidateLinkRequest":{"properties":{"linked_entity_id":{"type":"string","title":"Linked Entity Id","description":"确认关联到的实体 ID"}},"type":"object","required":["linked_entity_id"],"title":"ShotExtractedCandidateLinkRequest"},"ShotExtractedCandidateRead":{"properties":{"id":{"type":"integer","title":"Id","description":"候选项 ID"},"shot_id":{"type":"string","title":"Shot Id","description":"所属镜头 ID"},"candidate_type":{"$ref":"#/components/schemas/ShotCandidateType","description":"候选类型"},"candidate_name":{"type":"string","title":"Candidate Name","description":"提取出的候选名称"},"candidate_status":{"$ref":"#/components/schemas/ShotCandidateStatus","description":"候选确认状态"},"linked_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linked Entity Id","description":"已关联实体 ID"},"source":{"type":"string","title":"Source","description":"候选来源"},"payload":{"additionalProperties":true,"type":"object","title":"Payload","description":"候选附加信息"},"confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Confirmed At","description":"确认时间"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"创建时间"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"更新时间"}},"type":"object","required":["id","shot_id","candidate_type","candidate_name","candidate_status","source","created_at","updated_at"],"title":"ShotExtractedCandidateRead"},"ShotExtractedDialogueCandidateAcceptRequest":{"properties":{"index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Index","description":"写入对白行时使用的排序;为空则使用候选排序"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"接受时可覆盖对白文本"},"line_mode":{"anyOf":[{"$ref":"#/components/schemas/DialogueLineMode"},{"type":"null"}],"description":"接受时可覆盖对白模式"},"speaker_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Name","description":"接受时可覆盖说话角色名称"},"target_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Name","description":"接受时可覆盖听者角色名称"}},"type":"object","title":"ShotExtractedDialogueCandidateAcceptRequest"},"ShotExtractedDialogueCandidateRead":{"properties":{"id":{"type":"integer","title":"Id","description":"对白候选项 ID"},"shot_id":{"type":"string","title":"Shot Id","description":"所属镜头 ID"},"index":{"type":"integer","title":"Index","description":"镜头内对白候选排序"},"text":{"type":"string","title":"Text","description":"提取出的对白文本"},"line_mode":{"$ref":"#/components/schemas/DialogueLineMode","description":"对白模式"},"speaker_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Name","description":"说话角色名称"},"target_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Name","description":"听者角色名称"},"candidate_status":{"$ref":"#/components/schemas/ShotDialogueCandidateStatus","description":"对白候选确认状态"},"linked_dialog_line_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Linked Dialog Line Id","description":"已接受后关联的对白行 ID"},"source":{"type":"string","title":"Source","description":"候选来源"},"payload":{"additionalProperties":true,"type":"object","title":"Payload","description":"候选附加信息"},"confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Confirmed At","description":"确认时间"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"创建时间"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"更新时间"}},"type":"object","required":["id","shot_id","index","text","line_mode","candidate_status","source","created_at","updated_at"],"title":"ShotExtractedDialogueCandidateRead"},"ShotExtractionSummaryRead":{"properties":{"state":{"type":"string","enum":["not_extracted","extracted_empty","extracted_pending","extracted_resolved","skipped"],"title":"State","description":"镜头提取确认状态摘要"},"has_extracted":{"type":"boolean","title":"Has Extracted","description":"是否已执行过提取"},"last_extracted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Extracted At","description":"最近一次提取完成时间"},"asset_candidate_total":{"type":"integer","title":"Asset Candidate Total","description":"资产候选总数","default":0},"dialogue_candidate_total":{"type":"integer","title":"Dialogue Candidate Total","description":"对白候选总数","default":0},"pending_asset_count":{"type":"integer","title":"Pending Asset Count","description":"待确认资产候选数","default":0},"pending_dialogue_count":{"type":"integer","title":"Pending Dialogue Count","description":"待确认对白候选数","default":0}},"type":"object","required":["state","has_extracted"],"title":"ShotExtractionSummaryRead"},"ShotFrameImageCreate":{"properties":{"shot_detail_id":{"type":"string","title":"Shot Detail Id"},"frame_type":{"$ref":"#/components/schemas/ShotFrameType"},"file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Id"},"width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width"},"height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height"},"format":{"type":"string","title":"Format","default":"png"}},"type":"object","required":["shot_detail_id","frame_type"],"title":"ShotFrameImageCreate"},"ShotFrameImageRead":{"properties":{"id":{"type":"integer","title":"Id","description":"图片行 ID"},"shot_detail_id":{"type":"string","title":"Shot Detail Id","description":"所属镜头细节 ID"},"frame_type":{"$ref":"#/components/schemas/ShotFrameType","description":"帧类型:first/last/key"},"file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Id","description":"关联的 FileItem ID(可为空,允许先创建占位)"},"width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width","description":"宽(px)"},"height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height","description":"高(px)"},"format":{"type":"string","title":"Format","description":"格式","default":"png"}},"type":"object","required":["id","shot_detail_id","frame_type"],"title":"ShotFrameImageRead"},"ShotFrameImageTaskRequest":{"properties":{"model_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Id","description":"可选模型 ID(models.id);不传则使用 ModelSettings.default_image_model_id;Provider 由模型关联反查"},"frame_type":{"$ref":"#/components/schemas/ShotFrameType","description":"first | last | key"},"prompt":{"type":"string","minLength":1,"title":"Prompt","description":"提示词(由前端传入,创建任务接口必填)。"},"images":{"items":{"$ref":"#/components/schemas/ShotLinkedAssetItem"},"type":"array","title":"Images","description":"参考资产条目列表(可多张,顺序有效)。后端会使用 item.file_id 作为参考图;无效条目会被跳过。"},"target_ratio":{"type":"string","enum":["16:9","4:3","1:1","3:4","9:16","21:9","3:2","2:3"],"title":"Target Ratio","description":"目标视频画幅比例;关键帧将按该画幅生成,以提升后续视频参考稳定性"},"resolution_profile":{"anyOf":[{"type":"string","enum":["standard","high"]},{"type":"null"}],"title":"Resolution Profile","description":"关键帧输出分辨率档位,默认 standard","default":"standard"}},"type":"object","required":["frame_type","prompt","target_ratio"],"title":"ShotFrameImageTaskRequest","description":"镜头分镜帧图片生成请求体:只根据 `shot_id + frame_type` 定位 ShotFrameImage。\n\n用于替代旧接口中通过 `image_id` 直接传入 ShotFrameImage.id 的方式。"},"ShotFrameImageUpdate":{"properties":{"frame_type":{"anyOf":[{"$ref":"#/components/schemas/ShotFrameType"},{"type":"null"}]},"file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Id"},"width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width"},"height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height"},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format"}},"type":"object","title":"ShotFrameImageUpdate"},"ShotFramePromptMappingRead":{"properties":{"token":{"type":"string","title":"Token","description":"提示词中的图片占位 token,如 图1 / 图2"},"type":{"type":"string","enum":["character","prop","scene","costume"],"title":"Type","description":"实体类型:character/prop/scene/costume"},"id":{"type":"string","title":"Id","description":"实体 ID(如 character_id/prop_id/scene_id/costume_id)"},"name":{"type":"string","title":"Name","description":"实体名称"},"file_id":{"type":"string","title":"File Id","description":"本次渲染与生成使用的文件 ID"}},"type":"object","required":["token","type","id","name","file_id"],"title":"ShotFramePromptMappingRead","description":"关键帧提示词渲染后的图片映射关系。"},"ShotFramePromptRenderRequest":{"properties":{"frame_type":{"$ref":"#/components/schemas/ShotFrameType","description":"first | last | key"},"prompt":{"type":"string","minLength":1,"title":"Prompt","description":"原始基础提示词。渲染接口要求显式传入,用于生成最终提示词。"},"images":{"items":{"$ref":"#/components/schemas/ShotLinkedAssetItem"},"type":"array","title":"Images","description":"参考资产条目列表(可多张,顺序有效)。后端会使用 item.file_id 作为参考图;无效条目会被跳过。"}},"type":"object","required":["frame_type","prompt"],"title":"ShotFramePromptRenderRequest","description":"镜头分镜帧提示词渲染请求体。"},"ShotFramePromptRequest":{"properties":{"shot_id":{"type":"string","title":"Shot Id","description":"镜头 ID"},"frame_type":{"type":"string","title":"Frame Type","description":"first | last | key"}},"type":"object","required":["shot_id","frame_type"],"title":"ShotFramePromptRequest","description":"镜头分镜帧提示词生成任务请求。"},"ShotFrameType":{"type":"string","enum":["first","last","key"],"title":"ShotFrameType","description":"镜头分镜帧类型:首帧/尾帧/关键帧。"},"ShotLinkedAssetItem":{"properties":{"type":{"type":"string","enum":["character","prop","scene","costume"],"title":"Type","description":"实体类型:character/prop/scene/costume"},"id":{"type":"string","title":"Id","description":"实体 ID(如 character_id/prop_id/scene_id/costume_id)"},"image_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Image Id","description":"最佳缩略图对应的 image 行 ID(如 PropImage.id);无图则为 null"},"file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Id","description":"最佳缩略图对应的文件 ID(files.id);用于参考图输入;无图则为 null"},"name":{"type":"string","title":"Name","description":"实体名称"},"thumbnail":{"type":"string","title":"Thumbnail","description":"缩略图下载地址(/api/v1/studio/files/{file_id}/download)","default":""}},"type":"object","required":["type","id","name"],"title":"ShotLinkedAssetItem","description":"按分镜聚合返回的关联资产条目(角色/道具/场景/服装)。"},"ShotPreparationLinkEntityType":{"type":"string","enum":["character","scene","prop","costume"],"title":"ShotPreparationLinkEntityType"},"ShotPreparationLinkRequest":{"properties":{"project_id":{"type":"string","title":"Project Id","description":"项目 ID"},"chapter_id":{"type":"string","title":"Chapter Id","description":"章节 ID"},"entity_type":{"$ref":"#/components/schemas/ShotPreparationLinkEntityType","description":"准备页关联的实体类型"},"linked_entity_id":{"type":"string","title":"Linked Entity Id","description":"要关联的实体 ID"}},"type":"object","required":["project_id","chapter_id","entity_type","linked_entity_id"],"title":"ShotPreparationLinkRequest"},"ShotPreparationMutationAction":{"type":"string","enum":["link_asset_candidate","ignore_asset_candidate","accept_dialogue_candidate","ignore_dialogue_candidate","skip_extraction","resume_extraction"],"title":"ShotPreparationMutationAction"},"ShotPreparationMutationResultRead":{"properties":{"action":{"$ref":"#/components/schemas/ShotPreparationMutationAction","description":"本次执行的准备页动作"},"state":{"$ref":"#/components/schemas/ShotPreparationStateRead","description":"动作完成后的最新准备页聚合状态"}},"type":"object","required":["action","state"],"title":"ShotPreparationMutationResultRead","description":"准备页命令执行后的统一响应。"},"ShotPreparationStateRead":{"properties":{"shot":{"$ref":"#/components/schemas/ShotRead","description":"当前镜头最新状态"},"assets_overview":{"$ref":"#/components/schemas/ShotAssetsOverviewRead","description":"资产确认区聚合状态"},"dialogue_candidates":{"items":{"$ref":"#/components/schemas/ShotExtractedDialogueCandidateRead"},"type":"array","title":"Dialogue Candidates","description":"当前待处理/已存在的对白候选"},"saved_dialogue_lines":{"items":{"$ref":"#/components/schemas/ShotDialogLineRead"},"type":"array","title":"Saved Dialogue Lines","description":"当前已保存的对白行"},"pending_confirm_count":{"type":"integer","title":"Pending Confirm Count","description":"当前仍待确认的总数量(资产 + 对白)"},"basic_info_ready":{"type":"boolean","title":"Basic Info Ready","description":"标题与剧本摘录是否已补齐"},"semantic_defaults_ready":{"type":"boolean","title":"Semantic Defaults Ready","description":"镜头语言默认值是否已确认"},"action_beats_ready":{"type":"boolean","title":"Action Beats Ready","description":"动作拍点是否已确认"},"action_beats_count":{"type":"integer","title":"Action Beats Count","description":"当前已确认动作拍点数量","default":0},"action_beat_phases":{"items":{"$ref":"#/components/schemas/ActionBeatPhaseRead"},"type":"array","title":"Action Beat Phases","description":"当前动作拍点的阶段推断结果"},"ready_for_generation":{"type":"boolean","title":"Ready For Generation","description":"当前镜头是否已完成准备,可进入后续生成"}},"type":"object","required":["shot","assets_overview","pending_confirm_count","basic_info_ready","semantic_defaults_ready","action_beats_ready","ready_for_generation"],"title":"ShotPreparationStateRead","description":"分镜准备页聚合状态。"},"ShotPromptAssetRef":{"properties":{"type":{"type":"string","enum":["character","prop","scene","costume"],"title":"Type","description":"资产类型"},"name":{"type":"string","title":"Name","description":"资产名称"},"description":{"type":"string","title":"Description","description":"资产描述或提取候选描述","default":""},"file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Id","description":"可作为参考图的文件 ID"},"thumbnail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail","description":"缩略图"}},"type":"object","required":["type","name"],"title":"ShotPromptAssetRef","description":"用于提示词渲染的镜头资产引用。"},"ShotPromptCameraInfo":{"properties":{"camera_shot":{"type":"string","title":"Camera Shot","description":"景别","default":""},"angle":{"type":"string","title":"Angle","description":"机位角度","default":""},"movement":{"type":"string","title":"Movement","description":"运镜方式","default":""},"duration":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration","description":"镜头时长(秒)"}},"type":"object","title":"ShotPromptCameraInfo","description":"用于提示词渲染的镜头语言信息。"},"ShotRead":{"properties":{"id":{"type":"string","title":"Id","description":"镜头 ID"},"chapter_id":{"type":"string","title":"Chapter Id","description":"所属章节 ID"},"index":{"type":"integer","title":"Index","description":"镜头序号(章节内唯一)"},"title":{"type":"string","title":"Title","description":"镜头标题"},"thumbnail":{"type":"string","title":"Thumbnail","description":"缩略图 URL/路径","default":""},"status":{"$ref":"#/components/schemas/ShotStatus","description":"镜头状态","default":"pending"},"skip_extraction":{"type":"boolean","title":"Skip Extraction","description":"是否明确跳过信息提取","default":false},"script_excerpt":{"type":"string","title":"Script Excerpt","description":"剧本摘录","default":""},"generated_video_file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated Video File Id","description":"已生成视频关联的文件 ID(files.id,type=video)"},"last_extracted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Extracted At","description":"最近一次完成信息提取的时间"},"extraction":{"$ref":"#/components/schemas/ShotExtractionSummaryRead","description":"镜头提取状态摘要"}},"type":"object","required":["id","chapter_id","index","title","extraction"],"title":"ShotRead"},"ShotRuntimeSummaryRead":{"properties":{"shot_id":{"type":"string","title":"Shot Id","description":"镜头 ID"},"has_active_tasks":{"type":"boolean","title":"Has Active Tasks","description":"是否存在进行中的关联任务"},"has_active_video_tasks":{"type":"boolean","title":"Has Active Video Tasks","description":"是否存在进行中的视频任务"},"has_active_prompt_tasks":{"type":"boolean","title":"Has Active Prompt Tasks","description":"是否存在进行中的提示词任务"},"has_active_frame_tasks":{"type":"boolean","title":"Has Active Frame Tasks","description":"是否存在进行中的分镜帧图片任务"},"active_task_count":{"type":"integer","title":"Active Task Count","description":"进行中的唯一任务数"}},"type":"object","required":["shot_id","has_active_tasks","has_active_video_tasks","has_active_prompt_tasks","has_active_frame_tasks","active_task_count"],"title":"ShotRuntimeSummaryRead"},"ShotSemanticSuggestion":{"properties":{"camera_shot":{"anyOf":[{"$ref":"#/components/schemas/CameraShotType"},{"type":"null"}],"description":"建议景别"},"angle":{"anyOf":[{"$ref":"#/components/schemas/CameraAngle"},{"type":"null"}],"description":"建议机位"},"movement":{"anyOf":[{"$ref":"#/components/schemas/CameraMovement"},{"type":"null"}],"description":"建议运镜"},"duration":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Duration","description":"建议时长(秒)"},"action_beats":{"items":{"type":"string"},"type":"array","title":"Action Beats","description":"按时间顺序排列的动作拍点"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"不确定项说明"}},"additionalProperties":false,"type":"object","title":"ShotSemanticSuggestion","description":"镜头语义默认建议:用于准备阶段初始化镜头语言与动作拍点。"},"ShotSkipExtractionUpdate":{"properties":{"skip":{"type":"boolean","title":"Skip","description":"是否明确跳过信息提取"}},"type":"object","required":["skip"],"title":"ShotSkipExtractionUpdate"},"ShotStatus":{"type":"string","enum":["pending","generating","ready"],"title":"ShotStatus","description":"镜头生成状态(更多是“生产流程”而非剧情状态)。"},"ShotUpdate":{"properties":{"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id"},"index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Index"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"thumbnail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail"},"status":{"anyOf":[{"$ref":"#/components/schemas/ShotStatus"},{"type":"null"}]},"skip_extraction":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Skip Extraction"},"script_excerpt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Script Excerpt"},"generated_video_file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated Video File Id"}},"type":"object","title":"ShotUpdate"},"ShotVideoPromptPackRead":{"properties":{"shot_id":{"type":"string","title":"Shot Id","description":"镜头 ID"},"title":{"type":"string","title":"Title","description":"镜头标题","default":""},"script_excerpt":{"type":"string","title":"Script Excerpt","description":"剧本摘录","default":""},"action_beats":{"items":{"type":"string"},"type":"array","title":"Action Beats","description":"动作/场景要点"},"action_beat_phases":{"items":{"$ref":"#/components/schemas/ActionBeatPhaseRead"},"type":"array","title":"Action Beat Phases","description":"动作拍点的阶段推断结果"},"previous_shot_summary":{"type":"string","title":"Previous Shot Summary","description":"上一镜头摘要,用于提示词连续性约束","default":""},"next_shot_goal":{"type":"string","title":"Next Shot Goal","description":"下一镜头目标,用于提示词连续性约束","default":""},"continuity_guidance":{"type":"string","title":"Continuity Guidance","description":"当前镜头与相邻镜头的承接建议","default":""},"composition_anchor":{"type":"string","title":"Composition Anchor","description":"当前镜头的构图与空间锚点建议","default":""},"screen_direction_guidance":{"type":"string","title":"Screen Direction Guidance","description":"当前镜头的人物朝向、视线与左右轴线建议","default":""},"dialogue_summary":{"type":"string","title":"Dialogue Summary","description":"对白摘要","default":""},"characters":{"items":{"$ref":"#/components/schemas/ShotPromptAssetRef"},"type":"array","title":"Characters","description":"角色引用"},"scene":{"anyOf":[{"$ref":"#/components/schemas/ShotPromptAssetRef"},{"type":"null"}],"description":"场景引用"},"props":{"items":{"$ref":"#/components/schemas/ShotPromptAssetRef"},"type":"array","title":"Props","description":"道具引用"},"costumes":{"items":{"$ref":"#/components/schemas/ShotPromptAssetRef"},"type":"array","title":"Costumes","description":"服装引用"},"camera":{"$ref":"#/components/schemas/ShotPromptCameraInfo","description":"镜头语言"},"atmosphere":{"type":"string","title":"Atmosphere","description":"氛围描述","default":""},"visual_style":{"type":"string","title":"Visual Style","description":"项目视觉风格","default":""},"style":{"type":"string","title":"Style","description":"项目题材/风格","default":""},"negative_prompt":{"type":"string","title":"Negative Prompt","description":"默认负面提示词","default":""}},"type":"object","required":["shot_id"],"title":"ShotVideoPromptPackRead","description":"视频提示词渲染前的标准上下文包。"},"ShotVideoPromptPreviewRead":{"properties":{"shot_id":{"type":"string","title":"Shot Id","description":"镜头 ID"},"template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Id","description":"使用的提示词模板 ID"},"template_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Name","description":"使用的提示词模板名称"},"rendered_prompt":{"type":"string","title":"Rendered Prompt","description":"渲染后的提示词"},"pack":{"$ref":"#/components/schemas/ShotVideoPromptPackRead","description":"渲染上下文包"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings","description":"渲染时发现的非阻塞提示"}},"type":"object","required":["shot_id","rendered_prompt","pack"],"title":"ShotVideoPromptPreviewRead","description":"视频提示词预览结果。"},"ShotVideoReadinessCheck":{"properties":{"key":{"type":"string","title":"Key","description":"检查项 key"},"ok":{"type":"boolean","title":"Ok","description":"是否通过"},"message":{"type":"string","title":"Message","description":"面向前端展示的说明"}},"type":"object","required":["key","ok","message"],"title":"ShotVideoReadinessCheck","description":"单项视频生成准备度检查结果。"},"ShotVideoReadinessRead":{"properties":{"shot_id":{"type":"string","title":"Shot Id","description":"镜头 ID"},"reference_mode":{"type":"string","title":"Reference Mode","description":"参考模式"},"ready":{"type":"boolean","title":"Ready","description":"是否满足当前 reference_mode 下的视频生成条件"},"checks":{"items":{"$ref":"#/components/schemas/ShotVideoReadinessCheck"},"type":"array","title":"Checks","description":"准备度检查项"}},"type":"object","required":["shot_id","reference_mode","ready"],"title":"ShotVideoReadinessRead","description":"镜头视频生成准备度。"},"StudioAssetDraft":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"资产 ID(已落库时回填,如 scene_id / prop_id / costume_id)"},"file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Id","description":"关联的文件 ID(可空)"},"thumbnail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail","description":"缩略图下载地址(可空)"},"name":{"type":"string","title":"Name","description":"名称(同项目内建议唯一)"},"description":{"type":"string","title":"Description","description":"描述","default":""},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"标签"},"prompt_template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt Template Id","description":"提示词模板 ID(可空)"},"view_count":{"type":"integer","minimum":1.0,"title":"View Count","description":"计划生成视角图数量","default":1}},"additionalProperties":false,"type":"object","required":["name"],"title":"StudioAssetDraft","description":"Studio 资产草稿(Scene/Prop/Costume)。\n\n导入 API 未传 id 时由服务端生成;分镜详情回填时可带 scene_id/prop_id/costume_id。"},"StudioCharacterDraft":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"角色 ID(已落库时回填 character_id)"},"file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Id","description":"关联的文件 ID(可空)"},"thumbnail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail","description":"缩略图下载地址(可空)"},"index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Index","description":"镜头内角色排序(shot_character_links.index)"},"name":{"type":"string","title":"Name","description":"角色名称(同项目内建议唯一)"},"description":{"type":"string","title":"Description","description":"角色描述","default":""},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"标签(可选)"},"costume_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Costume Name","description":"服装名称(可选,导入时映射到 costume_id)"},"prop_names":{"items":{"type":"string"},"type":"array","title":"Prop Names","description":"角色常用道具名称列表(可选)"}},"additionalProperties":false,"type":"object","required":["name"],"title":"StudioCharacterDraft","description":"Studio 角色草稿。\n\n导入 API 未传 id 时由服务端生成;分镜详情回填时可带 character_id。"},"StudioImageTaskRequest":{"properties":{"model_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Id","description":"可选模型 ID(models.id);不传则使用 ModelSettings.default_image_model_id;Provider 由模型关联反查"},"image_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Image Id","description":"图片模型 ID,如 ActorImage.id / SceneImage.id / PropImage.id 等;必须与路径主体 ID 匹配"},"prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt","description":"提示词(由前端传入)。创建任务接口必填;render-prompt 接口可不传"},"images":{"items":{"type":"string"},"type":"array","title":"Images","description":"参考图 file_id 列表(可多张,顺序有效)。创建任务接口会基于 file_id 从数据中解析为参考图"}},"type":"object","title":"StudioImageTaskRequest","description":"Studio 专用图片任务请求体:可选模型 ID,不传则用默认图片模型;供应商由模型反查。\n\nimage_id 表示具体的图片模型 ID,例如:\n- 演员图片:ActorImage.id\n- 场景图片:SceneImage.id\n- 道具图片:PropImage.id\n- 服装图片:CostumeImage.id\n- 角色图片:CharacterImage.id\n- 分镜帧图片:ShotFrameImage.id"},"StudioScriptExtractionDraft":{"properties":{"project_id":{"type":"string","title":"Project Id","description":"项目 ID(必填)"},"chapter_id":{"type":"string","title":"Chapter Id","description":"章节 ID(必填,用于创建 shots/links)"},"script_text":{"type":"string","title":"Script Text","description":"剧本文本(可为优化后版本)"},"characters":{"items":{"$ref":"#/components/schemas/StudioCharacterDraft"},"type":"array","title":"Characters"},"scenes":{"items":{"$ref":"#/components/schemas/StudioAssetDraft"},"type":"array","title":"Scenes"},"props":{"items":{"$ref":"#/components/schemas/StudioAssetDraft"},"type":"array","title":"Props"},"costumes":{"items":{"$ref":"#/components/schemas/StudioAssetDraft"},"type":"array","title":"Costumes"},"shots":{"items":{"$ref":"#/components/schemas/StudioShotDraft"},"type":"array","title":"Shots","description":"镜头草稿列表"}},"additionalProperties":false,"type":"object","required":["project_id","chapter_id","script_text"],"title":"StudioScriptExtractionDraft","description":"用于导入 Studio 的提取结果草稿(name-based)。"},"StudioShotDraft":{"properties":{"index":{"type":"integer","minimum":1.0,"title":"Index","description":"镜头序号(章节内唯一)"},"title":{"type":"string","title":"Title","description":"镜头标题"},"script_excerpt":{"type":"string","title":"Script Excerpt","description":"剧本摘录","default":""},"scene_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scene Name","description":"场景名称(可选)"},"character_names":{"items":{"type":"string"},"type":"array","title":"Character Names","description":"本镜出现角色名称列表"},"prop_names":{"items":{"type":"string"},"type":"array","title":"Prop Names","description":"本镜关键道具名称列表"},"costume_names":{"items":{"type":"string"},"type":"array","title":"Costume Names","description":"本镜服装名称列表"},"dialogue_lines":{"items":{"$ref":"#/components/schemas/StudioShotDraftDialogueLine"},"type":"array","title":"Dialogue Lines","description":"对白列表"},"actions":{"items":{"type":"string"},"type":"array","title":"Actions","description":"动作/场景描述"},"semantic_suggestion":{"anyOf":[{"$ref":"#/components/schemas/ShotSemanticSuggestion"},{"type":"null"}],"description":"镜头语言默认建议与动作拍点候选"}},"additionalProperties":false,"type":"object","required":["index","title"],"title":"StudioShotDraft","description":"镜头草稿:不含 shot_id,由导入 API 生成;引用实体用 name。"},"StudioShotDraftDialogueLine":{"properties":{"index":{"type":"integer","minimum":0.0,"title":"Index","description":"镜头内排序","default":0},"text":{"type":"string","title":"Text","description":"台词内容"},"line_mode":{"type":"string","enum":["DIALOGUE","VOICE_OVER","OFF_SCREEN","PHONE"],"title":"Line Mode","description":"对白模式","default":"DIALOGUE"},"speaker_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Name","description":"说话角色名称(可空)"},"target_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Name","description":"听者角色名称(可空)"}},"additionalProperties":false,"type":"object","required":["text"],"title":"StudioShotDraftDialogueLine","description":"镜头对白草稿:speaker/target 使用角色 name,导入时映射为 character_id。"},"StyleOption":{"properties":{"value":{"type":"string","title":"Value","description":"选项值"},"label":{"type":"string","title":"Label","description":"选项展示文案"}},"type":"object","required":["value","label"],"title":"StyleOption","description":"通用下拉选项。"},"TaskCancelRead":{"properties":{"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/TaskStatus"},"cancel_requested":{"type":"boolean","title":"Cancel Requested","description":"是否已登记取消请求"},"cancel_requested_at_ts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cancel Requested At Ts","description":"请求取消时间戳"},"effective_immediately":{"type":"boolean","title":"Effective Immediately","description":"是否已立即取消完成","default":false}},"type":"object","required":["task_id","status","cancel_requested"],"title":"TaskCancelRead"},"TaskCancelRequest":{"properties":{"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"取消原因(可选)"}},"type":"object","title":"TaskCancelRequest"},"TaskCreated":{"properties":{"task_id":{"type":"string","title":"Task Id","description":"任务 ID"}},"type":"object","required":["task_id"],"title":"TaskCreated"},"TaskLinkAdoptRead":{"properties":{"task_id":{"type":"string","title":"Task Id"},"link_type":{"type":"string","title":"Link Type","description":"project | chapter | shot"},"entity_id":{"type":"string","title":"Entity Id","description":"项目/章节/镜头 ID"},"is_adopted":{"type":"boolean","title":"Is Adopted","description":"是否采用(仅可正向变更为 true)"}},"type":"object","required":["task_id","link_type","entity_id","is_adopted"],"title":"TaskLinkAdoptRead","description":"采用状态更新结果。"},"TaskLinkAdoptRequest":{"properties":{"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id","description":"绑定项目 ID(可选)"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"绑定章节 ID(可选)"},"shot_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shot Id","description":"绑定镜头 ID(可选)"},"task_id":{"type":"string","title":"Task Id","description":"任务 ID"}},"type":"object","required":["task_id"],"title":"TaskLinkAdoptRequest","description":"更新采用状态请求:task_id + 三选一绑定对象(project_id/chapter_id/shot_id)。"},"TaskListItemRead":{"properties":{"task_id":{"type":"string","title":"Task Id"},"task_kind":{"type":"string","title":"Task Kind","description":"业务任务类型"},"status":{"$ref":"#/components/schemas/TaskStatus"},"progress":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Progress"},"cancel_requested":{"type":"boolean","title":"Cancel Requested","description":"是否已请求取消","default":false},"cancel_requested_at_ts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cancel Requested At Ts","description":"请求取消时间戳"},"started_at_ts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Started At Ts","description":"任务开始执行时间戳"},"finished_at_ts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Finished At Ts","description":"任务结束时间戳"},"elapsed_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Elapsed Ms","description":"任务累计执行耗时(毫秒)"},"created_at_ts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Created At Ts","description":"任务创建时间戳"},"updated_at_ts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Updated At Ts","description":"任务更新时间戳"},"executor_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Executor Type","description":"执行器类型,如 celery"},"executor_task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Executor Task Id","description":"执行器侧任务 ID"},"relation_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relation Type","description":"业务关联类型"},"relation_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relation Entity Id","description":"业务关联实体 ID"},"resource_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resource Type","description":"资源类型"},"navigate_relation_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Navigate Relation Type","description":"前端默认跳转关联类型"},"navigate_relation_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Navigate Relation Entity Id","description":"前端默认跳转关联实体 ID"}},"type":"object","required":["task_id","task_kind","status","progress"],"title":"TaskListItemRead"},"TaskResultRead":{"properties":{"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/TaskStatus"},"progress":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Progress"},"result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Result"},"error":{"type":"string","title":"Error","default":""},"cancel_requested":{"type":"boolean","title":"Cancel Requested","description":"是否已请求取消","default":false},"cancel_requested_at_ts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cancel Requested At Ts","description":"请求取消时间戳"},"started_at_ts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Started At Ts","description":"任务开始执行时间戳"},"finished_at_ts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Finished At Ts","description":"任务结束时间戳"},"elapsed_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Elapsed Ms","description":"任务累计执行耗时(毫秒)"}},"type":"object","required":["task_id","status","progress"],"title":"TaskResultRead"},"TaskStatus":{"type":"string","enum":["pending","running","streaming","succeeded","failed","cancelled"],"title":"TaskStatus","description":"任务状态枚举。"},"TaskStatusRead":{"properties":{"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/TaskStatus"},"progress":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Progress"},"cancel_requested":{"type":"boolean","title":"Cancel Requested","description":"是否已请求取消","default":false},"cancel_requested_at_ts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cancel Requested At Ts","description":"请求取消时间戳"},"started_at_ts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Started At Ts","description":"任务开始执行时间戳"},"finished_at_ts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Finished At Ts","description":"任务结束时间戳"},"elapsed_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Elapsed Ms","description":"任务累计执行耗时(毫秒)"}},"type":"object","required":["task_id","status","progress"],"title":"TaskStatusRead"},"VFXType":{"type":"string","enum":["NONE","PARTICLES","VOLUMETRIC_FOG","CG_DOUBLE","DIGITAL_ENVIRONMENT","MATTE_PAINTING","FIRE_SMOKE","WATER_SIM","DESTRUCTION","ENERGY_MAGIC","COMPOSITING_CLEANUP","SLOW_MOTION_TIME","OTHER"],"title":"VFXType","description":"视效类型(与 `app.schemas.skills.common.VFXType` 对齐,存英文 code)。"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VariantAnalysisRequest":{"properties":{"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id","description":"项目 ID(异步任务关联可选)"},"chapter_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chapter Id","description":"章节 ID(异步任务关联可选)"},"merged_library":{"additionalProperties":true,"type":"object","title":"Merged Library","description":"合并后的实体库(EntityLibrary 的序列化形式;来自 EntityMerger 输出的 merged_library)"},"all_shot_extractions":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"All Shot Extractions","description":"所有镜头提取结果"},"script_division":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Script Division","description":"脚本分镜结果(可选;ScriptDivisionResult 序列化),用于章节/段落分组"}},"type":"object","required":["merged_library","all_shot_extractions"],"title":"VariantAnalysisRequest","description":"变体分析请求。"},"VariantAnalysisResult":{"properties":{"costume_timelines":{"items":{"$ref":"#/components/schemas/CostumeTimeline"},"type":"array","title":"Costume Timelines","description":"各角色服装演变时间线"},"variant_suggestions":{"items":{"$ref":"#/components/schemas/VariantSuggestion"},"type":"array","title":"Variant Suggestions","description":"变体建议列表"},"chapter_variants":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Chapter Variants","description":"章节变体建议"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"分析说明"}},"additionalProperties":false,"type":"object","title":"VariantAnalysisResult","description":"变体分析结果。"},"VariantSuggestion":{"properties":{"entity_id":{"type":"string","title":"Entity Id","description":"实体ID"},"entity_name":{"type":"string","title":"Entity Name","description":"实体名称"},"entity_type":{"type":"string","title":"Entity Type","description":"实体类型(character/scene/prop/location)"},"suggestion":{"type":"string","title":"Suggestion","description":"变体建议说明"},"affected_shots":{"items":{"type":"integer"},"type":"array","title":"Affected Shots","description":"涉及的镜头"},"evidence":{"items":{"$ref":"#/components/schemas/EvidenceSpan"},"type":"array","title":"Evidence","description":"原文依据(可选)"}},"additionalProperties":false,"type":"object","required":["entity_id","entity_name","entity_type","suggestion"],"title":"VariantSuggestion","description":"变体建议。"},"VideoGenerationOptionsRead":{"properties":{"provider":{"type":"string","title":"Provider","description":"供应商稳定键"},"model_id":{"type":"string","title":"Model Id","description":"默认视频模型 ID"},"model_name":{"type":"string","title":"Model Name","description":"默认视频模型名称"},"allowed_ratios":{"items":{"type":"string"},"type":"array","title":"Allowed Ratios","description":"当前模型允许的比例选项"},"default_ratio":{"type":"string","title":"Default Ratio","description":"当前模型默认比例"}},"type":"object","required":["provider","model_id","model_name","default_ratio"],"title":"VideoGenerationOptionsRead","description":"当前默认视频模型对应的生成参数选项。"},"VideoGenerationTaskRequest":{"properties":{"shot_id":{"type":"string","title":"Shot Id","description":"镜头 ID"},"reference_mode":{"type":"string","enum":["first","last","key","first_last","first_last_key","text_only"],"title":"Reference Mode","description":"参考模式:first | last | key | first_last | first_last_key | text_only"},"prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt","description":"视频提示词(text_only 必填)"},"images":{"items":{"type":"string"},"type":"array","title":"Images","description":"参考图 file_id 列表,数量需与 reference_mode 严格匹配"},"ratio":{"type":"string","enum":["16:9","4:3","1:1","3:4","9:16","21:9"],"title":"Ratio","description":"视频画幅比例,如 16:9 / 9:16"}},"type":"object","required":["shot_id","reference_mode","ratio"],"title":"VideoGenerationTaskRequest","description":"视频生成任务请求。"},"VideoPromptPreviewResponse":{"properties":{"prompt":{"type":"string","title":"Prompt","description":"最终用于视频生成的提示词"},"images":{"items":{"type":"string"},"type":"array","title":"Images","description":"关联参考图 file_id 列表"},"pack":{"anyOf":[{"$ref":"#/components/schemas/ShotVideoPromptPackRead"},{"type":"null"}],"description":"视频提示词预览上下文包"}},"type":"object","required":["prompt"],"title":"VideoPromptPreviewResponse"}}}} \ No newline at end of file +{ + "openapi": "3.1.0", + "info": { + "title": "Jellyfish API", + "version": "0.1.0" + }, + "paths": { + "/api/v1/health": { + "get": { + "tags": [ + "health" + ], + "summary": "V1 Health", + "operationId": "v1_health_api_v1_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + } + } + } + }, + "/api/v1/film/tasks/video/preview-prompt": { + "post": { + "tags": [ + "film" + ], + "summary": "视频提示词预览", + "description": "预览视频生成的提示词与自动关联参考图。", + "operationId": "preview_video_generation_prompt_api_v1_film_tasks_video_preview_prompt_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VideoGenerationTaskRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_VideoPromptPreviewResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/film/tasks/video": { + "post": { + "tags": [ + "film" + ], + "summary": "视频生成(任务版)", + "description": "创建视频生成任务并后台执行,结果通过 /tasks/{task_id}/result 获取。", + "operationId": "create_video_generation_task_api_v1_film_tasks_video_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VideoGenerationTaskRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskCreated_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/film/tasks/shot-frame-prompts": { + "post": { + "tags": [ + "film" + ], + "summary": "镜头分镜帧提示词生成(任务版)", + "operationId": "create_shot_frame_prompt_task_api_v1_film_tasks_shot_frame_prompts_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotFramePromptRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskCreated_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/film/tasks": { + "get": { + "tags": [ + "film" + ], + "summary": "全局任务列表(任务中心)", + "operationId": "list_tasks_api_v1_film_tasks_get", + "parameters": [ + { + "name": "statuses", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskStatus" + } + }, + { + "type": "null" + } + ], + "description": "按任务状态过滤,可多选", + "title": "Statuses" + }, + "description": "按任务状态过滤,可多选" + }, + { + "name": "task_kind", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按 task_kind 过滤", + "title": "Task Kind" + }, + "description": "按 task_kind 过滤" + }, + { + "name": "relation_type", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按 relation_type 过滤", + "title": "Relation Type" + }, + "description": "按 relation_type 过滤" + }, + { + "name": "relation_entity_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按 relation_entity_id 过滤", + "title": "Relation Entity Id" + }, + "description": "按 relation_entity_id 过滤" + }, + { + "name": "recent_seconds", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 86400, + "minimum": 0, + "description": "默认返回最近结束任务的时间窗口(秒)", + "default": 300, + "title": "Recent Seconds" + }, + "description": "默认返回最近结束任务的时间窗口(秒)" + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "页码", + "default": 1, + "title": "Page" + }, + "description": "页码" + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "每页条数", + "default": 20, + "title": "Page Size" + }, + "description": "每页条数" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_TaskListItemRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/film/tasks/{task_id}/status": { + "get": { + "tags": [ + "film" + ], + "summary": "查询任务状态/进度(轮询)", + "operationId": "get_task_status_api_v1_film_tasks__task_id__status_get", + "parameters": [ + { + "name": "task_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Task Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskStatusRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/film/tasks/{task_id}/result": { + "get": { + "tags": [ + "film" + ], + "summary": "获取任务结果", + "operationId": "get_task_result_api_v1_film_tasks__task_id__result_get", + "parameters": [ + { + "name": "task_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Task Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskResultRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/film/tasks/{task_id}/cancel": { + "post": { + "tags": [ + "film" + ], + "summary": "请求取消任务", + "operationId": "cancel_task_api_v1_film_tasks__task_id__cancel_post", + "parameters": [ + { + "name": "task_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Task Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskCancelRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskCancelRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/film/task-links/adopt": { + "patch": { + "tags": [ + "film" + ], + "summary": "更新任务关联的采用状态(仅可正向变更)", + "description": "将指定任务链接的状态设为 accepted;已采用不可改为未采用。", + "operationId": "adopt_task_link_api_v1_film_task_links_adopt_patch", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskLinkAdoptRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskLinkAdoptRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/film/task-links": { + "get": { + "tags": [ + "film" + ], + "summary": "生成任务关联列表(分页,支持多条件过滤)", + "operationId": "list_task_links_api_v1_film_task_links_get", + "parameters": [ + { + "name": "resource_type", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按 resource_type 过滤", + "title": "Resource Type" + }, + "description": "按 resource_type 过滤" + }, + { + "name": "relation_type", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按 relation_type 过滤", + "title": "Relation Type" + }, + "description": "按 relation_type 过滤" + }, + { + "name": "relation_entity_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按 relation_entity_id 过滤", + "title": "Relation Entity Id" + }, + "description": "按 relation_entity_id 过滤" + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按关联状态过滤(accepted/todo/rejected)", + "title": "Status" + }, + "description": "按关联状态过滤(accepted/todo/rejected)" + }, + { + "name": "task_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按 task_id 过滤", + "title": "Task Id" + }, + "description": "按 task_id 过滤" + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "排序字段:updated_at/created_at/id/status", + "title": "Order" + }, + "description": "排序字段:updated_at/created_at/id/status" + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "description": "是否倒序;默认 true", + "default": true, + "title": "Is Desc" + }, + "description": "是否倒序;默认 true" + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "页码", + "default": 1, + "title": "Page" + }, + "description": "页码" + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "每页条数", + "default": 10, + "title": "Page Size" + }, + "description": "每页条数" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_GenerationTaskLinkRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "film" + ], + "summary": "创建生成任务关联", + "operationId": "create_task_link_api_v1_film_task_links_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerationTaskLinkCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_GenerationTaskLinkRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/film/task-links/{link_id}": { + "get": { + "tags": [ + "film" + ], + "summary": "获取生成任务关联详情", + "operationId": "get_task_link_api_v1_film_task_links__link_id__get", + "parameters": [ + { + "name": "link_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Link Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_GenerationTaskLinkRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "film" + ], + "summary": "更新生成任务关联(不支持直接修改 is_adopted)", + "operationId": "update_task_link_api_v1_film_task_links__link_id__patch", + "parameters": [ + { + "name": "link_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Link Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerationTaskLinkUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_GenerationTaskLinkRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "film" + ], + "summary": "删除生成任务关联", + "operationId": "delete_task_link_api_v1_film_task_links__link_id__delete", + "parameters": [ + { + "name": "link_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Link Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/llm/providers": { + "get": { + "tags": [ + "llm" + ], + "summary": "列出模型供应商(分页)", + "operationId": "list_providers_api_v1_llm_providers_get", + "parameters": [ + { + "name": "q", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "关键字,过滤 name/description", + "title": "Q" + }, + "description": "关键字,过滤 name/description" + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "排序字段:name, created_at, updated_at", + "title": "Order" + }, + "description": "排序字段:name, created_at, updated_at" + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "description": "是否倒序", + "default": false, + "title": "Is Desc" + }, + "description": "是否倒序" + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "页码", + "default": 1, + "title": "Page" + }, + "description": "页码" + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 200, + "minimum": 1, + "description": "每页条数", + "default": 10, + "title": "Page Size" + }, + "description": "每页条数" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_ProviderRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "llm" + ], + "summary": "创建模型供应商", + "operationId": "create_provider_api_v1_llm_providers_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ProviderRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/llm/providers/supported": { + "get": { + "tags": [ + "llm" + ], + "summary": "列出系统支持的供应商能力", + "operationId": "list_supported_providers_api_v1_llm_providers_supported_get", + "parameters": [ + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelCategoryKey" + }, + { + "type": "null" + } + ], + "description": "按模型类别过滤:text/image/video", + "title": "Category" + }, + "description": "按模型类别过滤:text/image/video" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_ProviderSupportedRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/llm/image-generation-options": { + "get": { + "tags": [ + "llm" + ], + "summary": "获取当前默认图片模型的关键帧规格选项", + "operationId": "get_image_generation_options_api_v1_llm_image_generation_options_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ImageGenerationOptionsRead_" + } + } + } + } + } + } + }, + "/api/v1/llm/video-generation-options": { + "get": { + "tags": [ + "llm" + ], + "summary": "获取当前默认视频模型的动态比例选项", + "operationId": "get_video_generation_options_api_v1_llm_video_generation_options_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_VideoGenerationOptionsRead_" + } + } + } + } + } + } + }, + "/api/v1/llm/providers/{provider_id}": { + "get": { + "tags": [ + "llm" + ], + "summary": "获取单个模型供应商", + "operationId": "get_provider_api_v1_llm_providers__provider_id__get", + "parameters": [ + { + "name": "provider_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Provider Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ProviderRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "llm" + ], + "summary": "更新模型供应商", + "operationId": "update_provider_api_v1_llm_providers__provider_id__patch", + "parameters": [ + { + "name": "provider_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Provider Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ProviderRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "llm" + ], + "summary": "删除模型供应商", + "operationId": "delete_provider_api_v1_llm_providers__provider_id__delete", + "parameters": [ + { + "name": "provider_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Provider Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/llm/models": { + "get": { + "tags": [ + "llm" + ], + "summary": "列出模型(分页)", + "operationId": "list_models_api_v1_llm_models_get", + "parameters": [ + { + "name": "provider_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按供应商过滤", + "title": "Provider Id" + }, + "description": "按供应商过滤" + }, + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelCategoryKey" + }, + { + "type": "null" + } + ], + "description": "按模型类别过滤", + "title": "Category" + }, + "description": "按模型类别过滤" + }, + { + "name": "q", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "关键字,过滤 name/description", + "title": "Q" + }, + "description": "关键字,过滤 name/description" + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "排序字段:name, category, created_at, updated_at", + "title": "Order" + }, + "description": "排序字段:name, category, created_at, updated_at" + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "description": "是否倒序", + "default": false, + "title": "Is Desc" + }, + "description": "是否倒序" + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "页码", + "default": 1, + "title": "Page" + }, + "description": "页码" + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 200, + "minimum": 1, + "description": "每页条数", + "default": 10, + "title": "Page Size" + }, + "description": "每页条数" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_ModelRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "llm" + ], + "summary": "创建模型", + "operationId": "create_model_api_v1_llm_models_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ModelRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/llm/models/{model_id}/verify": { + "post": { + "tags": [ + "llm" + ], + "summary": "验证模型配置(同步探测,不创建生成任务)", + "description": "对已保存模型做一次短超时探测:文本走极小对话;图/视频走列表接口鉴权与模型名匹配。", + "operationId": "verify_llm_model_api_v1_llm_models__model_id__verify_post", + "parameters": [ + { + "name": "model_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Model Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ModelVerifyRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/llm/models/{model_id}/chat-test": { + "post": { + "tags": [ + "llm" + ], + "summary": "文本模型试聊(管理页调试发消息)", + "description": "对已保存的文本模型发送一条用户消息并返回模型回复(不计入业务任务)。", + "operationId": "chat_test_llm_model_api_v1_llm_models__model_id__chat_test_post", + "parameters": [ + { + "name": "model_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Model Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelChatTestRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ModelChatTestRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/llm/models/{model_id}": { + "get": { + "tags": [ + "llm" + ], + "summary": "获取单个模型", + "operationId": "get_model_api_v1_llm_models__model_id__get", + "parameters": [ + { + "name": "model_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Model Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ModelRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "llm" + ], + "summary": "更新模型", + "operationId": "update_model_api_v1_llm_models__model_id__patch", + "parameters": [ + { + "name": "model_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Model Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ModelRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "llm" + ], + "summary": "删除模型", + "operationId": "delete_model_api_v1_llm_models__model_id__delete", + "parameters": [ + { + "name": "model_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Model Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/llm/model-settings": { + "get": { + "tags": [ + "llm" + ], + "summary": "获取模型全局设置(单例)", + "operationId": "get_model_settings_api_v1_llm_model_settings_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ModelSettingsRead_" + } + } + } + } + } + }, + "put": { + "tags": [ + "llm" + ], + "summary": "更新模型全局设置(单例)", + "operationId": "update_model_settings_api_v1_llm_model_settings_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelSettingsUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ModelSettingsRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/projects/style-options": { + "get": { + "tags": [ + "studio/projects" + ], + "summary": "获取项目风格候选项", + "operationId": "get_project_style_options_api_v1_studio_projects_style_options_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ProjectStyleOptionsRead_" + } + } + } + } + } + } + }, + "/api/v1/studio/projects": { + "get": { + "tags": [ + "studio/projects" + ], + "summary": "项目列表(分页)", + "operationId": "list_projects_api_v1_studio_projects_get", + "parameters": [ + { + "name": "q", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "关键字,过滤 name/description", + "title": "Q" + }, + "description": "关键字,过滤 name/description" + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "排序字段", + "title": "Order" + }, + "description": "排序字段" + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "description": "是否倒序", + "default": false, + "title": "Is Desc" + }, + "description": "是否倒序" + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Page Size" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_ProjectRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "studio/projects" + ], + "summary": "创建项目", + "operationId": "create_project_api_v1_studio_projects_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ProjectRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/projects/{project_id}/timeline": { + "get": { + "tags": [ + "studio/projects" + ], + "summary": "项目时间线片段列表", + "description": "返回项目关联的时间线片段。\n\n说明:`timeline_clips` 表当前无 `project_id` 字段,无法在数据库层按项目过滤;\n在引入归属字段或关联表之前,对已存在的项目返回空列表(接口可用,不再 404)。", + "operationId": "list_project_timeline_api_v1_studio_projects__project_id__timeline_get", + "parameters": [ + { + "name": "project_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Project Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_TimelineClipRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/projects/{project_id}": { + "get": { + "tags": [ + "studio/projects" + ], + "summary": "获取项目", + "operationId": "get_project_api_v1_studio_projects__project_id__get", + "parameters": [ + { + "name": "project_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Project Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ProjectRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "studio/projects" + ], + "summary": "更新项目", + "operationId": "update_project_api_v1_studio_projects__project_id__patch", + "parameters": [ + { + "name": "project_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Project Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ProjectRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "studio/projects" + ], + "summary": "删除项目", + "operationId": "delete_project_api_v1_studio_projects__project_id__delete", + "parameters": [ + { + "name": "project_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Project Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/chapters/{chapter_id}/timeline": { + "get": { + "tags": [ + "studio/chapters" + ], + "summary": "获取章节剪辑时间线(含镜头成片解析状态)", + "operationId": "get_chapter_timeline_api_v1_studio_chapters__chapter_id__timeline_get", + "parameters": [ + { + "name": "chapter_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Chapter Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ChapterTimelineRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "put": { + "tags": [ + "studio/chapters" + ], + "summary": "全量保存章节剪辑时间线片段顺序", + "operationId": "put_chapter_timeline_api_v1_studio_chapters__chapter_id__timeline_put", + "parameters": [ + { + "name": "chapter_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Chapter Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChapterTimelineWrite" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ChapterTimelineRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/chapters/{chapter_id}/timeline/export": { + "post": { + "tags": [ + "studio/chapters" + ], + "summary": "发起章节时间线拼接导出任务", + "operationId": "post_chapter_timeline_export_api_v1_studio_chapters__chapter_id__timeline_export_post", + "parameters": [ + { + "name": "chapter_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Chapter Id" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ChapterTimelineExportRequest" + }, + { + "type": "null" + } + ], + "title": "Body" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskCreated_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/chapters": { + "get": { + "tags": [ + "studio/chapters" + ], + "summary": "章节列表(分页)", + "operationId": "list_chapters_api_v1_studio_chapters_get", + "parameters": [ + { + "name": "project_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按项目过滤", + "title": "Project Id" + }, + "description": "按项目过滤" + }, + { + "name": "q", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "关键字,过滤 title/summary", + "title": "Q" + }, + "description": "关键字,过滤 title/summary" + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "排序字段", + "title": "Order" + }, + "description": "排序字段" + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "description": "是否倒序", + "default": false, + "title": "Is Desc" + }, + "description": "是否倒序" + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Page Size" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_ChapterRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "studio/chapters" + ], + "summary": "创建章节", + "operationId": "create_chapter_api_v1_studio_chapters_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChapterCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ChapterRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/chapters/{chapter_id}": { + "get": { + "tags": [ + "studio/chapters" + ], + "summary": "获取章节", + "operationId": "get_chapter_api_v1_studio_chapters__chapter_id__get", + "parameters": [ + { + "name": "chapter_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Chapter Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ChapterRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "studio/chapters" + ], + "summary": "更新章节", + "operationId": "update_chapter_api_v1_studio_chapters__chapter_id__patch", + "parameters": [ + { + "name": "chapter_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Chapter Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChapterUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ChapterRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "studio/chapters" + ], + "summary": "删除章节", + "operationId": "delete_chapter_api_v1_studio_chapters__chapter_id__delete", + "parameters": [ + { + "name": "chapter_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Chapter Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots": { + "get": { + "tags": [ + "studio/shots" + ], + "summary": "镜头列表(分页)", + "operationId": "list_shots_api_v1_studio_shots_get", + "parameters": [ + { + "name": "chapter_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按章节过滤", + "title": "Chapter Id" + }, + "description": "按章节过滤" + }, + { + "name": "q", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "关键字,过滤 title/script_excerpt", + "title": "Q" + }, + "description": "关键字,过滤 title/script_excerpt" + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Order" + } + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Is Desc" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Page Size" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_ShotRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "studio/shots" + ], + "summary": "创建镜头", + "operationId": "create_shot_api_v1_studio_shots_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/runtime-summary": { + "get": { + "tags": [ + "studio/shots" + ], + "summary": "按章节获取镜头运行时任务态摘要", + "operationId": "list_shot_runtime_summary_api_v1_studio_shots_runtime_summary_get", + "parameters": [ + { + "name": "chapter_id", + "in": "query", + "required": true, + "schema": { + "type": "string", + "description": "章节 ID", + "title": "Chapter Id" + }, + "description": "章节 ID" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_ShotRuntimeSummaryRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/{shot_id}/extraction-draft": { + "get": { + "tags": [ + "studio/shots" + ], + "summary": "分镜详情:按镜头关联拼装 StudioScriptExtractionDraft", + "operationId": "get_shot_extraction_draft_api_v1_studio_shots__shot_id__extraction_draft_get", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_StudioScriptExtractionDraft_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/{shot_id}/extracted-candidates": { + "get": { + "tags": [ + "studio/shots" + ], + "summary": "获取镜头提取候选项", + "operationId": "get_shot_extracted_candidates_api_v1_studio_shots__shot_id__extracted_candidates_get", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_ShotExtractedCandidateRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/{shot_id}/extracted-dialogue-candidates": { + "get": { + "tags": [ + "studio/shots" + ], + "summary": "获取镜头提取对白候选项", + "operationId": "get_shot_extracted_dialogue_candidates_api_v1_studio_shots__shot_id__extracted_dialogue_candidates_get", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_ShotExtractedDialogueCandidateRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/{shot_id}/assets-overview": { + "get": { + "tags": [ + "studio/shots" + ], + "summary": "获取镜头资产总览(已关联资产 + 提取候选)", + "operationId": "get_shot_assets_overview_api_api_v1_studio_shots__shot_id__assets_overview_get", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotAssetsOverviewRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/{shot_id}/preparation-state": { + "get": { + "tags": [ + "studio/shots" + ], + "summary": "获取镜头准备页聚合状态", + "operationId": "get_shot_preparation_state_api_api_v1_studio_shots__shot_id__preparation_state_get", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotPreparationStateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/{shot_id}/preparation-link": { + "post": { + "tags": [ + "studio/shots" + ], + "summary": "准备页关联现有实体并返回最新聚合状态", + "operationId": "link_existing_asset_for_preparation_api_api_v1_studio_shots__shot_id__preparation_link_post", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotPreparationLinkRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotPreparationMutationResultRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/{shot_id}/video-prompt-preview": { + "get": { + "tags": [ + "studio/shots" + ], + "summary": "预览镜头视频提示词", + "operationId": "preview_shot_video_prompt_api_v1_studio_shots__shot_id__video_prompt_preview_get", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + }, + { + "name": "template_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "指定视频提示词模板 ID;不传则使用默认模板", + "title": "Template Id" + }, + "description": "指定视频提示词模板 ID;不传则使用默认模板" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotVideoPromptPreviewRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/{shot_id}/video-readiness": { + "get": { + "tags": [ + "studio/shots" + ], + "summary": "获取镜头视频生成准备度", + "operationId": "get_shot_video_readiness_api_api_v1_studio_shots__shot_id__video_readiness_get", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + }, + { + "name": "reference_mode", + "in": "query", + "required": false, + "schema": { + "type": "string", + "description": "参考模式:first/last/key/first_last/first_last_key/text_only", + "default": "text_only", + "title": "Reference Mode" + }, + "description": "参考模式:first/last/key/first_last/first_last_key/text_only" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotVideoReadinessRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/{shot_id}/skip-extraction": { + "patch": { + "tags": [ + "studio/shots" + ], + "summary": "设置是否跳过镜头信息提取", + "operationId": "update_shot_skip_extraction_api_v1_studio_shots__shot_id__skip_extraction_patch", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotSkipExtractionUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotPreparationMutationResultRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/extracted-candidates/{candidate_id}/link": { + "patch": { + "tags": [ + "studio/shots" + ], + "summary": "确认并关联镜头提取候选项", + "operationId": "link_extracted_candidate_api_v1_studio_shots_extracted_candidates__candidate_id__link_patch", + "parameters": [ + { + "name": "candidate_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Candidate Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotExtractedCandidateLinkRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotPreparationMutationResultRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/extracted-candidates/{candidate_id}/ignore": { + "patch": { + "tags": [ + "studio/shots" + ], + "summary": "忽略镜头提取候选项", + "operationId": "ignore_extracted_candidate_api_v1_studio_shots_extracted_candidates__candidate_id__ignore_patch", + "parameters": [ + { + "name": "candidate_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Candidate Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotPreparationMutationResultRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/extracted-dialogue-candidates/{candidate_id}/accept": { + "patch": { + "tags": [ + "studio/shots" + ], + "summary": "接受镜头提取对白候选项", + "operationId": "accept_extracted_dialogue_candidate_api_v1_studio_shots_extracted_dialogue_candidates__candidate_id__accept_patch", + "parameters": [ + { + "name": "candidate_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Candidate Id" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotExtractedDialogueCandidateAcceptRequest" + }, + { + "type": "null" + } + ], + "title": "Body" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotPreparationMutationResultRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/extracted-dialogue-candidates/{candidate_id}/ignore": { + "patch": { + "tags": [ + "studio/shots" + ], + "summary": "忽略镜头提取对白候选项", + "operationId": "ignore_extracted_dialogue_candidate_api_v1_studio_shots_extracted_dialogue_candidates__candidate_id__ignore_patch", + "parameters": [ + { + "name": "candidate_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Candidate Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotPreparationMutationResultRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/{shot_id}": { + "get": { + "tags": [ + "studio/shots" + ], + "summary": "获取镜头", + "operationId": "get_shot_api_v1_studio_shots__shot_id__get", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "studio/shots" + ], + "summary": "更新镜头", + "operationId": "update_shot_api_v1_studio_shots__shot_id__patch", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "studio/shots" + ], + "summary": "删除镜头", + "operationId": "delete_shot_api_v1_studio_shots__shot_id__delete", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shots/{shot_id}/linked-assets": { + "get": { + "tags": [ + "studio/shots" + ], + "summary": "获取镜头关联的角色/道具/场景/服装(分页)", + "operationId": "list_shot_linked_assets_api_v1_studio_shots__shot_id__linked_assets_get", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Page Size" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_ShotLinkedAssetItem__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-details": { + "get": { + "tags": [ + "studio/shot-details" + ], + "summary": "镜头细节列表(分页)", + "operationId": "list_shot_details_api_v1_studio_shot_details_get", + "parameters": [ + { + "name": "shot_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按镜头过滤(id 同 shot_id)", + "title": "Shot Id" + }, + "description": "按镜头过滤(id 同 shot_id)" + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Order" + } + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Is Desc" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Page Size" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_ShotDetailRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "studio/shot-details" + ], + "summary": "创建镜头细节", + "operationId": "create_shot_detail_api_v1_studio_shot_details_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotDetailCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotDetailRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-details/{shot_id}": { + "get": { + "tags": [ + "studio/shot-details" + ], + "summary": "获取镜头细节", + "operationId": "get_shot_detail_api_v1_studio_shot_details__shot_id__get", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotDetailRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "studio/shot-details" + ], + "summary": "更新镜头细节", + "operationId": "update_shot_detail_api_v1_studio_shot_details__shot_id__patch", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotDetailUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotDetailRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "studio/shot-details" + ], + "summary": "删除镜头细节", + "operationId": "delete_shot_detail_api_v1_studio_shot_details__shot_id__delete", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-dialog-lines": { + "get": { + "tags": [ + "studio/shot-dialog-lines" + ], + "summary": "镜头对话行列表(分页)", + "operationId": "list_shot_dialog_lines_api_v1_studio_shot_dialog_lines_get", + "parameters": [ + { + "name": "shot_detail_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按镜头细节过滤", + "title": "Shot Detail Id" + }, + "description": "按镜头细节过滤" + }, + { + "name": "q", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "关键字,过滤 text", + "title": "Q" + }, + "description": "关键字,过滤 text" + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Order" + } + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Is Desc" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Page Size" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_ShotDialogLineRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "studio/shot-dialog-lines" + ], + "summary": "创建镜头对话行", + "operationId": "create_shot_dialog_line_api_v1_studio_shot_dialog_lines_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotDialogLineCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotDialogLineRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-dialog-lines/{line_id}": { + "patch": { + "tags": [ + "studio/shot-dialog-lines" + ], + "summary": "更新镜头对话行", + "operationId": "update_shot_dialog_line_api_v1_studio_shot_dialog_lines__line_id__patch", + "parameters": [ + { + "name": "line_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Line Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotDialogLineUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotDialogLineRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "studio/shot-dialog-lines" + ], + "summary": "删除镜头对话行", + "operationId": "delete_shot_dialog_line_api_v1_studio_shot_dialog_lines__line_id__delete", + "parameters": [ + { + "name": "line_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Line Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-links/{entity_type}": { + "get": { + "tags": [ + "studio/shot-links" + ], + "summary": "项目-章节-镜头-实体关联列表(分页)", + "operationId": "list_project_entity_links_api_v1_studio_shot_links__entity_type__get", + "parameters": [ + { + "name": "entity_type", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Type" + } + }, + { + "name": "project_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id" + } + }, + { + "name": "chapter_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id" + } + }, + { + "name": "shot_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Shot Id" + } + }, + { + "name": "asset_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Asset Id" + } + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Order" + } + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Is Desc" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Page Size" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_Any__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-links/actor": { + "post": { + "tags": [ + "studio/shot-links" + ], + "summary": "创建项目-章节-镜头-演员关联", + "operationId": "create_project_actor_link_api_v1_studio_shot_links_actor_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectAssetLinkCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ProjectActorLinkRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-links/actor/{link_id}": { + "delete": { + "tags": [ + "studio/shot-links" + ], + "summary": "删除项目-章节-镜头-演员关联", + "operationId": "delete_project_actor_link_api_v1_studio_shot_links_actor__link_id__delete", + "parameters": [ + { + "name": "link_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Link Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-links/scene": { + "post": { + "tags": [ + "studio/shot-links" + ], + "summary": "创建项目-章节-镜头-场景关联", + "operationId": "create_project_scene_link_api_v1_studio_shot_links_scene_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectAssetLinkCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ProjectSceneLinkRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-links/scene/{link_id}": { + "delete": { + "tags": [ + "studio/shot-links" + ], + "summary": "删除项目-章节-镜头-场景关联", + "operationId": "delete_project_scene_link_api_v1_studio_shot_links_scene__link_id__delete", + "parameters": [ + { + "name": "link_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Link Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-links/prop": { + "post": { + "tags": [ + "studio/shot-links" + ], + "summary": "创建项目-章节-镜头-道具关联", + "operationId": "create_project_prop_link_api_v1_studio_shot_links_prop_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectAssetLinkCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ProjectPropLinkRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-links/prop/{link_id}": { + "delete": { + "tags": [ + "studio/shot-links" + ], + "summary": "删除项目-章节-镜头-道具关联", + "operationId": "delete_project_prop_link_api_v1_studio_shot_links_prop__link_id__delete", + "parameters": [ + { + "name": "link_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Link Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-links/costume": { + "post": { + "tags": [ + "studio/shot-links" + ], + "summary": "创建项目-章节-镜头-服装关联", + "operationId": "create_project_costume_link_api_v1_studio_shot_links_costume_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectAssetLinkCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ProjectCostumeLinkRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-links/costume/{link_id}": { + "delete": { + "tags": [ + "studio/shot-links" + ], + "summary": "删除项目-章节-镜头-服装关联", + "operationId": "delete_project_costume_link_api_v1_studio_shot_links_costume__link_id__delete", + "parameters": [ + { + "name": "link_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Link Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-frame-images": { + "get": { + "tags": [ + "studio/shot-frame-images" + ], + "summary": "镜头分镜帧图片列表(分页)", + "operationId": "list_shot_frame_images_api_v1_studio_shot_frame_images_get", + "parameters": [ + { + "name": "shot_detail_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按镜头细节过滤", + "title": "Shot Detail Id" + }, + "description": "按镜头细节过滤" + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Order" + } + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Is Desc" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Page Size" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_ShotFrameImageRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "studio/shot-frame-images" + ], + "summary": "创建镜头分镜帧图片", + "operationId": "create_shot_frame_image_api_v1_studio_shot_frame_images_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotFrameImageCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotFrameImageRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-frame-images/{image_id}": { + "patch": { + "tags": [ + "studio/shot-frame-images" + ], + "summary": "更新镜头分镜帧图片", + "operationId": "update_shot_frame_image_api_v1_studio_shot_frame_images__image_id__patch", + "parameters": [ + { + "name": "image_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Image Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotFrameImageUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotFrameImageRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "studio/shot-frame-images" + ], + "summary": "删除镜头分镜帧图片", + "operationId": "delete_shot_frame_image_api_v1_studio_shot_frame_images__image_id__delete", + "parameters": [ + { + "name": "image_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Image Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/entities/existence-check": { + "post": { + "tags": [ + "studio/entities" + ], + "summary": "批量检测资产名称是否存在(模糊匹配,不分页)", + "operationId": "check_entity_names_existence_api_v1_studio_entities_existence_check_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNameExistenceCheckRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_EntityNameExistenceCheckResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/entities/{entity_type}": { + "get": { + "tags": [ + "studio/entities" + ], + "summary": "统一实体列表(分页)", + "operationId": "list_entities_api_v1_studio_entities__entity_type__get", + "parameters": [ + { + "name": "entity_type", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Type" + } + }, + { + "name": "q", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "关键字,过滤 name/description", + "title": "Q" + }, + "description": "关键字,过滤 name/description" + }, + { + "name": "style", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "题材/风格(单值)", + "title": "Style" + }, + "description": "题材/风格(单值)" + }, + { + "name": "visual_style", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "画面表现形式(单值:真人/动漫)", + "title": "Visual Style" + }, + "description": "画面表现形式(单值:真人/动漫)" + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Order" + } + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Is Desc" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Page Size" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_dict_str__Any___" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "studio/entities" + ], + "summary": "统一创建实体", + "operationId": "create_entity_api_v1_studio_entities__entity_type__post", + "parameters": [ + { + "name": "entity_type", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Type" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Body" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_str__Any__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/entities/{entity_type}/{entity_id}": { + "get": { + "tags": [ + "studio/entities" + ], + "summary": "统一获取实体", + "operationId": "get_entity_api_v1_studio_entities__entity_type___entity_id__get", + "parameters": [ + { + "name": "entity_type", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Type" + } + }, + { + "name": "entity_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_str__Any__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "studio/entities" + ], + "summary": "统一更新实体", + "operationId": "update_entity_api_v1_studio_entities__entity_type___entity_id__patch", + "parameters": [ + { + "name": "entity_type", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Type" + } + }, + { + "name": "entity_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Body" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_str__Any__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "studio/entities" + ], + "summary": "统一删除实体", + "operationId": "delete_entity_api_v1_studio_entities__entity_type___entity_id__delete", + "parameters": [ + { + "name": "entity_type", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Type" + } + }, + { + "name": "entity_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/entities/{entity_type}/{entity_id}/images": { + "get": { + "tags": [ + "studio/entities" + ], + "summary": "统一实体图片列表(分页)", + "operationId": "list_entity_images_api_v1_studio_entities__entity_type___entity_id__images_get", + "parameters": [ + { + "name": "entity_type", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Type" + } + }, + { + "name": "entity_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Id" + } + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Order" + } + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Is Desc" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Page Size" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_dict_str__Any___" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "studio/entities" + ], + "summary": "统一创建实体图片", + "operationId": "create_entity_image_api_v1_studio_entities__entity_type___entity_id__images_post", + "parameters": [ + { + "name": "entity_type", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Type" + } + }, + { + "name": "entity_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Body" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_str__Any__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/entities/{entity_type}/{entity_id}/images/{image_id}": { + "patch": { + "tags": [ + "studio/entities" + ], + "summary": "统一更新实体图片", + "operationId": "update_entity_image_api_v1_studio_entities__entity_type___entity_id__images__image_id__patch", + "parameters": [ + { + "name": "entity_type", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Type" + } + }, + { + "name": "entity_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Id" + } + }, + { + "name": "image_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Image Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Body" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_str__Any__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "studio/entities" + ], + "summary": "统一删除实体图片", + "operationId": "delete_entity_image_api_v1_studio_entities__entity_type___entity_id__images__image_id__delete", + "parameters": [ + { + "name": "entity_type", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Type" + } + }, + { + "name": "entity_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Entity Id" + } + }, + { + "name": "image_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Image Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/prompts": { + "get": { + "tags": [ + "studio/prompts" + ], + "summary": "提示词模板列表(分页)", + "operationId": "list_prompt_templates_api_v1_studio_prompts_get", + "parameters": [ + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/PromptCategory" + }, + { + "type": "null" + } + ], + "description": "按类别过滤", + "title": "Category" + }, + "description": "按类别过滤" + }, + { + "name": "q", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "关键字,过滤 name", + "title": "Q" + }, + "description": "关键字,过滤 name" + }, + { + "name": "is_default", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "过滤是否为默认", + "title": "Is Default" + }, + "description": "过滤是否为默认" + }, + { + "name": "is_system", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "过滤是否为系统预置", + "title": "Is System" + }, + "description": "过滤是否为系统预置" + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Order" + } + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Is Desc" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Page Size" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_PromptTemplateRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "studio/prompts" + ], + "summary": "创建提示词模板", + "operationId": "create_prompt_template_api_v1_studio_prompts_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PromptTemplateCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PromptTemplateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/prompts/categories": { + "get": { + "tags": [ + "studio/prompts" + ], + "summary": "获取提示词类别枚举(含中文映射)", + "operationId": "list_prompt_categories_api_v1_studio_prompts_categories_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_PromptCategoryOptionRead__" + } + } + } + } + } + } + }, + "/api/v1/studio/prompts/{template_id}": { + "get": { + "tags": [ + "studio/prompts" + ], + "summary": "获取提示词模板详情", + "operationId": "get_prompt_template_api_v1_studio_prompts__template_id__get", + "parameters": [ + { + "name": "template_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Template Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PromptTemplateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "studio/prompts" + ], + "summary": "局部更新提示词模板", + "operationId": "update_prompt_template_api_v1_studio_prompts__template_id__patch", + "parameters": [ + { + "name": "template_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Template Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PromptTemplateUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PromptTemplateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "studio/prompts" + ], + "summary": "删除提示词模板", + "operationId": "delete_prompt_template_api_v1_studio_prompts__template_id__delete", + "parameters": [ + { + "name": "template_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Template Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/files": { + "get": { + "tags": [ + "studio/files" + ], + "summary": "文件列表(分页)", + "operationId": "list_files_api_api_v1_studio_files_get", + "parameters": [ + { + "name": "q", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "关键字,过滤 name", + "title": "Q" + }, + "description": "关键字,过滤 name" + }, + { + "name": "order", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Order" + } + }, + { + "name": "is_desc", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Is Desc" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Page Size" + } + }, + { + "name": "project_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "按 file_usages 限定项目;提供后仅返回该项目下有关联记录的文件", + "title": "Project Id" + }, + "description": "按 file_usages 限定项目;提供后仅返回该项目下有关联记录的文件" + }, + { + "name": "chapter_title", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "章节标题(精确匹配,与 project_id 联用)", + "title": "Chapter Title" + }, + "description": "章节标题(精确匹配,与 project_id 联用)" + }, + { + "name": "shot_title", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "镜头标题(精确匹配,与 project_id 联用)", + "title": "Shot Title" + }, + "description": "镜头标题(精确匹配,与 project_id 联用)" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PaginatedData_FileRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/files/upload": { + "post": { + "tags": [ + "studio/files" + ], + "summary": "上传文件并创建 FileItem 记录", + "operationId": "upload_file_api_api_v1_studio_files_upload_post", + "parameters": [ + { + "name": "name", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_upload_file_api_api_v1_studio_files_upload_post" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_FileRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/files/{file_id}/download": { + "get": { + "tags": [ + "studio/files" + ], + "summary": "下载文件二进制内容", + "operationId": "download_file_api_api_v1_studio_files__file_id__download_get", + "parameters": [ + { + "name": "file_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "File Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/files/{file_id}/storage-info": { + "get": { + "tags": [ + "studio/files" + ], + "summary": "获取对象存储详情(head_object)", + "operationId": "get_file_storage_info_api_api_v1_studio_files__file_id__storage_info_get", + "parameters": [ + { + "name": "file_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "File Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/files/{file_id}": { + "get": { + "tags": [ + "studio/files" + ], + "summary": "获取文件详情(元信息 + file_usages)", + "operationId": "get_file_detail_api_v1_studio_files__file_id__get", + "parameters": [ + { + "name": "file_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "File Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_FileDetailRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "studio/files" + ], + "summary": "更新文件元信息", + "operationId": "update_file_meta_api_v1_studio_files__file_id__patch", + "parameters": [ + { + "name": "file_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "File Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_FileRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "studio/files" + ], + "summary": "删除文件(记录 + 存储对象)", + "operationId": "delete_file_api_api_v1_studio_files__file_id__delete", + "parameters": [ + { + "name": "file_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "File Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NoneType_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/image-tasks/actors/{actor_id}/image-tasks": { + "post": { + "tags": [ + "studio/image-tasks" + ], + "summary": "演员图片生成(任务版)", + "description": "为指定演员创建图片生成任务,并通过 `GenerationTaskLink` 关联。", + "operationId": "create_actor_image_generation_task_api_v1_studio_image_tasks_actors__actor_id__image_tasks_post", + "parameters": [ + { + "name": "actor_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Actor Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StudioImageTaskRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskCreated_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/image-tasks/actors/{actor_id}/render-prompt": { + "post": { + "tags": [ + "studio/image-tasks" + ], + "summary": "演员图片提示词渲染", + "operationId": "render_actor_image_prompt_api_v1_studio_image_tasks_actors__actor_id__render_prompt_post", + "parameters": [ + { + "name": "actor_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Actor Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StudioImageTaskRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_RenderedPromptResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/image-tasks/assets/{asset_type}/{asset_id}/image-tasks": { + "post": { + "tags": [ + "studio/image-tasks" + ], + "summary": "道具/场景/服装图片生成(任务版)", + "description": "为道具/场景/服装创建图片生成任务。\n\n- asset_type: prop / scene / costume\n- path 参数 asset_id 为对应资产 ID\n- body.image_id 必须为该资产下对应图片表记录的 ID(PropImage/SceneImage/CostumeImage)", + "operationId": "create_asset_image_generation_task_api_v1_studio_image_tasks_assets__asset_type___asset_id__image_tasks_post", + "parameters": [ + { + "name": "asset_type", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Asset Type" + } + }, + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Asset Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StudioImageTaskRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskCreated_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/image-tasks/assets/{asset_type}/{asset_id}/render-prompt": { + "post": { + "tags": [ + "studio/image-tasks" + ], + "summary": "道具/场景/服装图片提示词渲染", + "operationId": "render_asset_image_prompt_api_v1_studio_image_tasks_assets__asset_type___asset_id__render_prompt_post", + "parameters": [ + { + "name": "asset_type", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Asset Type" + } + }, + { + "name": "asset_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Asset Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StudioImageTaskRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_RenderedPromptResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/image-tasks/characters/{character_id}/image-tasks": { + "post": { + "tags": [ + "studio/image-tasks" + ], + "summary": "角色图片生成(任务版)", + "description": "为角色创建图片生成任务(对应 CharacterImage 业务)。\n\n- path 参数 character_id 为 Character.id\n- body.image_id 必须为该角色下的 CharacterImage.id", + "operationId": "create_character_image_generation_task_api_v1_studio_image_tasks_characters__character_id__image_tasks_post", + "parameters": [ + { + "name": "character_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Character Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StudioImageTaskRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskCreated_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/image-tasks/characters/{character_id}/render-prompt": { + "post": { + "tags": [ + "studio/image-tasks" + ], + "summary": "角色图片提示词渲染", + "operationId": "render_character_image_prompt_api_v1_studio_image_tasks_characters__character_id__render_prompt_post", + "parameters": [ + { + "name": "character_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Character Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StudioImageTaskRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_RenderedPromptResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/image-tasks/shot/{shot_id}/frame-image-tasks": { + "post": { + "tags": [ + "studio/image-tasks" + ], + "summary": "镜头分镜帧图片生成(任务版)", + "description": "为镜头分镜帧图片生成任务(基于 `shot_id + frame_type` 自动定位数据)。", + "operationId": "create_shot_frame_image_generation_task_api_v1_studio_image_tasks_shot__shot_id__frame_image_tasks_post", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotFrameImageTaskRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskCreated_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/image-tasks/shot/{shot_id}/frame-render-prompt": { + "post": { + "tags": [ + "studio/image-tasks" + ], + "summary": "镜头分镜帧提示词渲染", + "operationId": "render_shot_frame_prompt_api_v1_studio_image_tasks_shot__shot_id__frame_render_prompt_post", + "parameters": [ + { + "name": "shot_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Shot Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotFramePromptRenderRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_RenderedShotFramePromptRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/studio/shot-character-links": { + "get": { + "tags": [ + "studio/shot-character-links" + ], + "summary": "查询镜头角色关联列表(ShotCharacterLink)", + "operationId": "list_shot_character_links_api_v1_studio_shot_character_links_get", + "parameters": [ + { + "name": "shot_id", + "in": "query", + "required": true, + "schema": { + "type": "string", + "description": "镜头 ID", + "title": "Shot Id" + }, + "description": "镜头 ID" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_ShotCharacterLinkRead__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "studio/shot-character-links" + ], + "summary": "创建/更新镜头角色关联(ShotCharacterLink)", + "operationId": "upsert_shot_character_link_api_v1_studio_shot_character_links_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShotCharacterLinkCreate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ShotCharacterLinkRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/divide-async": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "异步将剧本分割为多个镜头", + "description": "创建章节分镜提取任务并立即返回 task_id;前端可通过任务状态接口轮询。", + "operationId": "divide_script_async_api_v1_script_processing_divide_async_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScriptDividerRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AsyncTaskCreateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/divide": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "将剧本分割为多个镜头", + "description": "输入完整剧本文本,输出分镜列表(index/start_line/end_line/script_excerpt/shot_name/time_of_day)。注意:此阶段不强制稳定ID,角色以“称呼/名字”弱信息输出,稳定ID在合并阶段统一分配。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 divide-async。", + "operationId": "divide_script_api_v1_script_processing_divide_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScriptDividerRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ScriptDivisionResult_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/merge-entities-async": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "异步合并多镜头的实体信息", + "description": "创建实体合并任务并立即返回 task_id;当前保留为预备能力,尚无真实前端入口。", + "operationId": "merge_entities_async_api_v1_script_processing_merge_entities_async_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityMergerRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AsyncTaskCreateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/merge-entities": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "合并多镜头的实体信息", + "description": "输入全部分镜提取结果(可选带上脚本分镜与历史实体库),输出合并后的实体库:角色库/地点库/场景库/道具库(静态画像 + 变体列表)。该步骤会统一分配稳定ID(如 char_001/loc_001/prop_001/scene_001)。当提供 previous_merge 与 conflict_resolutions 时,将进行冲突重试合并,优先消解 conflicts 并尽量保持 ID 稳定。当前接口保留为预备能力,尚无真实前端入口。", + "operationId": "merge_entities_api_v1_script_processing_merge_entities_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityMergerRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_EntityMergeResult_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/analyze-variants-async": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "异步分析服装/外形变体", + "description": "创建变体分析任务并立即返回 task_id;当前保留为预备能力,尚无真实前端入口。", + "operationId": "analyze_variants_async_api_v1_script_processing_analyze_variants_async_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VariantAnalysisRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AsyncTaskCreateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/analyze-variants": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "分析服装/外形变体", + "description": "检测角色服装/外形变化,构建演变时间线,生成章节变体建议列表与变体建议。当前接口保留为预备能力,尚无真实前端入口。", + "operationId": "analyze_variants_api_v1_script_processing_analyze_variants_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VariantAnalysisRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_VariantAnalysisResult_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/check-consistency-async": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "异步检查角色混淆一致性(基于原文)", + "description": "创建一致性检查任务并立即返回 task_id;前端可通过任务状态接口轮询。", + "operationId": "check_consistency_async_api_v1_script_processing_check_consistency_async_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScriptConsistencyCheckRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AsyncTaskCreateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/check-consistency": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "检查角色混淆一致性(基于原文)", + "description": "检测同一角色在不同段落/镜头被赋予不同身份/行为主体导致混淆,并给出修改建议。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 check-consistency-async。", + "operationId": "check_consistency_api_v1_script_processing_check_consistency_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScriptConsistencyCheckRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ScriptConsistencyCheckResult_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/analyze-character-portrait-async": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "异步分析人物画像缺失信息", + "description": "创建人物画像分析任务并立即返回 task_id;前端可通过任务状态接口轮询。", + "operationId": "analyze_character_portrait_async_api_v1_script_processing_analyze_character_portrait_async_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CharacterPortraitAnalysisRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AsyncTaskCreateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/analyze-character-portrait": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "分析人物画像缺失信息", + "description": "根据原文人物上下文与人物描述,判断缺少哪些关键信息,并给出优化后的人物画像描述。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 analyze-character-portrait-async。", + "operationId": "analyze_character_portrait_api_v1_script_processing_analyze_character_portrait_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CharacterPortraitAnalysisRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_CharacterPortraitAnalysisResult_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/analyze-prop-info-async": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "异步分析道具信息缺失项", + "description": "创建道具信息分析任务并立即返回 task_id;前端可通过任务状态接口轮询。", + "operationId": "analyze_prop_info_async_api_v1_script_processing_analyze_prop_info_async_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PropInfoAnalysisRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AsyncTaskCreateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/analyze-prop-info": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "分析道具信息缺失项", + "description": "根据原文道具上下文与道具描述,判断缺少哪些关键信息,并给出优化后的可生成道具描述。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 analyze-prop-info-async。", + "operationId": "analyze_prop_info_api_v1_script_processing_analyze_prop_info_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PropInfoAnalysisRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PropInfoAnalysisResult_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/analyze-scene-info-async": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "异步分析场景信息缺失项", + "description": "创建场景信息分析任务并立即返回 task_id;前端可通过任务状态接口轮询。", + "operationId": "analyze_scene_info_async_api_v1_script_processing_analyze_scene_info_async_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SceneInfoAnalysisRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AsyncTaskCreateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/analyze-scene-info": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "分析场景信息缺失项", + "description": "根据原文场景上下文与场景描述,判断缺少哪些关键信息,并给出优化后的可生成场景描述。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 analyze-scene-info-async。", + "operationId": "analyze_scene_info_api_v1_script_processing_analyze_scene_info_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SceneInfoAnalysisRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_SceneInfoAnalysisResult_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/analyze-costume-info-async": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "异步分析服装信息缺失项", + "description": "创建服装信息分析任务并立即返回 task_id;前端可通过任务状态接口轮询。", + "operationId": "analyze_costume_info_async_api_v1_script_processing_analyze_costume_info_async_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CostumeInfoAnalysisRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AsyncTaskCreateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/analyze-costume-info": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "分析服装信息缺失项", + "description": "根据原文服装上下文与服装描述,判断缺少哪些关键信息,并给出优化后的可生成服装描述。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 analyze-costume-info-async。", + "operationId": "analyze_costume_info_api_v1_script_processing_analyze_costume_info_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CostumeInfoAnalysisRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_CostumeInfoAnalysisResult_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/optimize-script-async": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "异步基于一致性检查优化剧本", + "description": "创建剧本优化任务并立即返回 task_id;前端可通过任务状态接口轮询。", + "operationId": "optimize_script_async_api_v1_script_processing_optimize_script_async_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScriptOptimizeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AsyncTaskCreateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/optimize-script": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "基于一致性检查优化剧本", + "description": "将一致性检查输出及原文作为输入,生成优化后的剧本(尽量少改,只改与角色混淆 issues 相关段落)。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 optimize-script-async。", + "operationId": "optimize_script_api_v1_script_processing_optimize_script_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScriptOptimizeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ScriptOptimizationResult_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/simplify-script": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "智能精简剧本", + "description": "在保留剧情主体并保证剧情连续的前提下精简剧本文本。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 simplify-script-async。", + "operationId": "simplify_script_api_v1_script_processing_simplify_script_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScriptSimplifyRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ScriptSimplificationResult_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/simplify-script-async": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "异步智能精简剧本", + "description": "创建剧本精简任务并立即返回 task_id;前端可通过任务状态接口轮询。", + "operationId": "simplify_script_async_api_v1_script_processing_simplify_script_async_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScriptSimplifyRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AsyncTaskCreateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/extract-async": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "异步项目级信息提取(最终输出)", + "description": "创建项目级信息提取任务并立即返回 task_id;前端可通过任务状态接口轮询。", + "operationId": "extract_script_async_api_v1_script_processing_extract_async_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScriptExtractRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AsyncTaskCreateRead_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script-processing/extract": { + "post": { + "tags": [ + "script-processing" + ], + "summary": "项目级信息提取(最终输出)", + "description": "输入分镜结果(可选带一致性检查结果),输出可导入 Studio 的草稿结构(name-based,ID 由导入接口生成)。当前同步接口主要用于兼容旧调用与调试场景;页面主流程优先使用 extract-async。", + "operationId": "extract_script_api_v1_script_processing_extract_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScriptExtractRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_StudioScriptExtractionDraft_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/health": { + "get": { + "summary": "Health", + "description": "健康检查。", + "operationId": "health_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ActionBeatPhaseRead": { + "properties": { + "text": { + "type": "string", + "title": "Text", + "description": "动作拍点原文" + }, + "phase": { + "type": "string", + "enum": [ + "trigger", + "peak", + "aftermath" + ], + "title": "Phase", + "description": "推断阶段:触发 / 峰值 / 收束" + } + }, + "type": "object", + "required": [ + "text", + "phase" + ], + "title": "ActionBeatPhaseRead", + "description": "动作拍点的轻量阶段推断结果。" + }, + "ApiResponse_AsyncTaskCreateRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/AsyncTaskCreateRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[AsyncTaskCreateRead]" + }, + "ApiResponse_ChapterRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ChapterRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ChapterRead]" + }, + "ApiResponse_ChapterTimelineRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ChapterTimelineRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ChapterTimelineRead]" + }, + "ApiResponse_CharacterPortraitAnalysisResult_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/CharacterPortraitAnalysisResult" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[CharacterPortraitAnalysisResult]" + }, + "ApiResponse_CostumeInfoAnalysisResult_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/CostumeInfoAnalysisResult" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[CostumeInfoAnalysisResult]" + }, + "ApiResponse_EntityMergeResult_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/EntityMergeResult" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[EntityMergeResult]" + }, + "ApiResponse_EntityNameExistenceCheckResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/EntityNameExistenceCheckResponse" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[EntityNameExistenceCheckResponse]" + }, + "ApiResponse_FileDetailRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/FileDetailRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[FileDetailRead]" + }, + "ApiResponse_FileRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/FileRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[FileRead]" + }, + "ApiResponse_GenerationTaskLinkRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/GenerationTaskLinkRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[GenerationTaskLinkRead]" + }, + "ApiResponse_ImageGenerationOptionsRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ImageGenerationOptionsRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ImageGenerationOptionsRead]" + }, + "ApiResponse_ModelChatTestRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelChatTestRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ModelChatTestRead]" + }, + "ApiResponse_ModelRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ModelRead]" + }, + "ApiResponse_ModelSettingsRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelSettingsRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ModelSettingsRead]" + }, + "ApiResponse_ModelVerifyRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelVerifyRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ModelVerifyRead]" + }, + "ApiResponse_NoneType_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "type": "null", + "title": "Data", + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[NoneType]" + }, + "ApiResponse_PaginatedData_Any__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_Any_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[Any]]" + }, + "ApiResponse_PaginatedData_ChapterRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_ChapterRead_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[ChapterRead]]" + }, + "ApiResponse_PaginatedData_FileRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_FileRead_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[FileRead]]" + }, + "ApiResponse_PaginatedData_GenerationTaskLinkRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_GenerationTaskLinkRead_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[GenerationTaskLinkRead]]" + }, + "ApiResponse_PaginatedData_ModelRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_ModelRead_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[ModelRead]]" + }, + "ApiResponse_PaginatedData_ProjectRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_ProjectRead_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[ProjectRead]]" + }, + "ApiResponse_PaginatedData_PromptTemplateRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_PromptTemplateRead_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[PromptTemplateRead]]" + }, + "ApiResponse_PaginatedData_ProviderRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_ProviderRead_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[ProviderRead]]" + }, + "ApiResponse_PaginatedData_ShotDetailRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_ShotDetailRead_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[ShotDetailRead]]" + }, + "ApiResponse_PaginatedData_ShotDialogLineRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_ShotDialogLineRead_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[ShotDialogLineRead]]" + }, + "ApiResponse_PaginatedData_ShotFrameImageRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_ShotFrameImageRead_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[ShotFrameImageRead]]" + }, + "ApiResponse_PaginatedData_ShotLinkedAssetItem__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_ShotLinkedAssetItem_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[ShotLinkedAssetItem]]" + }, + "ApiResponse_PaginatedData_ShotRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_ShotRead_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[ShotRead]]" + }, + "ApiResponse_PaginatedData_TaskListItemRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_TaskListItemRead_" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[TaskListItemRead]]" + }, + "ApiResponse_PaginatedData_dict_str__Any___": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PaginatedData_dict_str__Any__" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PaginatedData[dict[str, Any]]]" + }, + "ApiResponse_ProjectActorLinkRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProjectActorLinkRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ProjectActorLinkRead]" + }, + "ApiResponse_ProjectCostumeLinkRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProjectCostumeLinkRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ProjectCostumeLinkRead]" + }, + "ApiResponse_ProjectPropLinkRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProjectPropLinkRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ProjectPropLinkRead]" + }, + "ApiResponse_ProjectRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProjectRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ProjectRead]" + }, + "ApiResponse_ProjectSceneLinkRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProjectSceneLinkRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ProjectSceneLinkRead]" + }, + "ApiResponse_ProjectStyleOptionsRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProjectStyleOptionsRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ProjectStyleOptionsRead]" + }, + "ApiResponse_PromptTemplateRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PromptTemplateRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PromptTemplateRead]" + }, + "ApiResponse_PropInfoAnalysisResult_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PropInfoAnalysisResult" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[PropInfoAnalysisResult]" + }, + "ApiResponse_ProviderRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProviderRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ProviderRead]" + }, + "ApiResponse_RenderedPromptResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/RenderedPromptResponse" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[RenderedPromptResponse]" + }, + "ApiResponse_RenderedShotFramePromptRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/RenderedShotFramePromptRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[RenderedShotFramePromptRead]" + }, + "ApiResponse_SceneInfoAnalysisResult_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/SceneInfoAnalysisResult" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[SceneInfoAnalysisResult]" + }, + "ApiResponse_ScriptConsistencyCheckResult_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ScriptConsistencyCheckResult" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ScriptConsistencyCheckResult]" + }, + "ApiResponse_ScriptDivisionResult_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ScriptDivisionResult" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ScriptDivisionResult]" + }, + "ApiResponse_ScriptOptimizationResult_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ScriptOptimizationResult" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ScriptOptimizationResult]" + }, + "ApiResponse_ScriptSimplificationResult_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ScriptSimplificationResult" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ScriptSimplificationResult]" + }, + "ApiResponse_ShotAssetsOverviewRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotAssetsOverviewRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ShotAssetsOverviewRead]" + }, + "ApiResponse_ShotCharacterLinkRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotCharacterLinkRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ShotCharacterLinkRead]" + }, + "ApiResponse_ShotDetailRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotDetailRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ShotDetailRead]" + }, + "ApiResponse_ShotDialogLineRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotDialogLineRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ShotDialogLineRead]" + }, + "ApiResponse_ShotFrameImageRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotFrameImageRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ShotFrameImageRead]" + }, + "ApiResponse_ShotPreparationMutationResultRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotPreparationMutationResultRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ShotPreparationMutationResultRead]" + }, + "ApiResponse_ShotPreparationStateRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotPreparationStateRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ShotPreparationStateRead]" + }, + "ApiResponse_ShotRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ShotRead]" + }, + "ApiResponse_ShotVideoPromptPreviewRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotVideoPromptPreviewRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ShotVideoPromptPreviewRead]" + }, + "ApiResponse_ShotVideoReadinessRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotVideoReadinessRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[ShotVideoReadinessRead]" + }, + "ApiResponse_StudioScriptExtractionDraft_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/StudioScriptExtractionDraft" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[StudioScriptExtractionDraft]" + }, + "ApiResponse_TaskCancelRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/TaskCancelRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[TaskCancelRead]" + }, + "ApiResponse_TaskCreated_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/TaskCreated" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[TaskCreated]" + }, + "ApiResponse_TaskLinkAdoptRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/TaskLinkAdoptRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[TaskLinkAdoptRead]" + }, + "ApiResponse_TaskResultRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/TaskResultRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[TaskResultRead]" + }, + "ApiResponse_TaskStatusRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/TaskStatusRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[TaskStatusRead]" + }, + "ApiResponse_VariantAnalysisResult_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/VariantAnalysisResult" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[VariantAnalysisResult]" + }, + "ApiResponse_VideoGenerationOptionsRead_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/VideoGenerationOptionsRead" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[VideoGenerationOptionsRead]" + }, + "ApiResponse_VideoPromptPreviewResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/VideoPromptPreviewResponse" + }, + { + "type": "null" + } + ], + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[VideoPromptPreviewResponse]" + }, + "ApiResponse_dict_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[dict]" + }, + "ApiResponse_dict_str__Any__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[dict[str, Any]]" + }, + "ApiResponse_list_PromptCategoryOptionRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/PromptCategoryOptionRead" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[list[PromptCategoryOptionRead]]" + }, + "ApiResponse_list_ProviderSupportedRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/ProviderSupportedRead" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[list[ProviderSupportedRead]]" + }, + "ApiResponse_list_ShotCharacterLinkRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/ShotCharacterLinkRead" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[list[ShotCharacterLinkRead]]" + }, + "ApiResponse_list_ShotExtractedCandidateRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/ShotExtractedCandidateRead" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[list[ShotExtractedCandidateRead]]" + }, + "ApiResponse_list_ShotExtractedDialogueCandidateRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/ShotExtractedDialogueCandidateRead" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[list[ShotExtractedDialogueCandidateRead]]" + }, + "ApiResponse_list_ShotRuntimeSummaryRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/ShotRuntimeSummaryRead" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[list[ShotRuntimeSummaryRead]]" + }, + "ApiResponse_list_TimelineClipRead__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "与 HTTP 状态码一致", + "default": 200 + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/TimelineClipRead" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "实际数据" + }, + "meta": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Meta", + "description": "附加元信息" + } + }, + "type": "object", + "title": "ApiResponse[list[TimelineClipRead]]" + }, + "AsyncTaskCreateRead": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id", + "description": "任务 ID" + }, + "status": { + "$ref": "#/components/schemas/TaskStatus", + "description": "任务状态" + }, + "reused": { + "type": "boolean", + "title": "Reused", + "description": "是否复用了当前业务实体已有的活跃任务", + "default": false + }, + "relation_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Relation Type", + "description": "业务关联类型" + }, + "relation_entity_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Relation Entity Id", + "description": "业务关联实体 ID" + } + }, + "type": "object", + "required": [ + "task_id", + "status" + ], + "title": "AsyncTaskCreateRead" + }, + "Body_upload_file_api_api_v1_studio_files_upload_post": { + "properties": { + "file": { + "type": "string", + "contentMediaType": "application/octet-stream", + "title": "File", + "description": "要上传的二进制文件" + }, + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id", + "description": "可选:写入 file_usages 的项目 ID" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id" + }, + "shot_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Shot Id" + }, + "usage_kind": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Usage Kind", + "description": "与 project_id 同时提供时写入 file_usages" + }, + "source_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Source Ref" + } + }, + "type": "object", + "required": [ + "file" + ], + "title": "Body_upload_file_api_api_v1_studio_files_upload_post" + }, + "CameraAngle": { + "type": "string", + "enum": [ + "EYE_LEVEL", + "HIGH_ANGLE", + "LOW_ANGLE", + "BIRD_EYE", + "DUTCH", + "OVER_SHOULDER" + ], + "title": "CameraAngle", + "description": "机位角度(与 `app.schemas.skills.common.CameraAngle` 对齐,存英文 code)。" + }, + "CameraMovement": { + "type": "string", + "enum": [ + "STATIC", + "PAN", + "TILT", + "DOLLY_IN", + "DOLLY_OUT", + "TRACK", + "CRANE", + "HANDHELD", + "STEADICAM", + "ZOOM_IN", + "ZOOM_OUT" + ], + "title": "CameraMovement", + "description": "运镜方式(与 `app.schemas.skills.common.CameraMovement` 对齐,存英文 code)。" + }, + "CameraShotType": { + "type": "string", + "enum": [ + "ECU", + "CU", + "MCU", + "MS", + "MLS", + "LS", + "ELS" + ], + "title": "CameraShotType", + "description": "景别(与 `app.schemas.skills.common.ShotType` 对齐,存英文 code)。" + }, + "ChapterCreate": { + "properties": { + "project_id": { + "type": "string", + "title": "Project Id", + "description": "所属项目 ID" + }, + "index": { + "type": "integer", + "title": "Index", + "description": "章节序号(项目内唯一)" + }, + "title": { + "type": "string", + "title": "Title", + "description": "章节标题" + }, + "summary": { + "type": "string", + "title": "Summary", + "description": "章节摘要", + "default": "" + }, + "raw_text": { + "type": "string", + "title": "Raw Text", + "description": "章节原文", + "default": "" + }, + "condensed_text": { + "type": "string", + "title": "Condensed Text", + "description": "精简原文", + "default": "" + }, + "storyboard_count": { + "type": "integer", + "title": "Storyboard Count", + "description": "分镜数量", + "default": 0 + }, + "status": { + "$ref": "#/components/schemas/ChapterStatus", + "description": "章节状态", + "default": "draft" + }, + "id": { + "type": "string", + "title": "Id", + "description": "章节 ID" + } + }, + "type": "object", + "required": [ + "project_id", + "index", + "title", + "id" + ], + "title": "ChapterCreate" + }, + "ChapterRead": { + "properties": { + "project_id": { + "type": "string", + "title": "Project Id", + "description": "所属项目 ID" + }, + "index": { + "type": "integer", + "title": "Index", + "description": "章节序号(项目内唯一)" + }, + "title": { + "type": "string", + "title": "Title", + "description": "章节标题" + }, + "summary": { + "type": "string", + "title": "Summary", + "description": "章节摘要", + "default": "" + }, + "raw_text": { + "type": "string", + "title": "Raw Text", + "description": "章节原文", + "default": "" + }, + "condensed_text": { + "type": "string", + "title": "Condensed Text", + "description": "精简原文", + "default": "" + }, + "storyboard_count": { + "type": "integer", + "title": "Storyboard Count", + "description": "分镜数量", + "default": 0 + }, + "status": { + "$ref": "#/components/schemas/ChapterStatus", + "description": "章节状态", + "default": "draft" + }, + "id": { + "type": "string", + "title": "Id" + }, + "shot_count": { + "type": "integer", + "title": "Shot Count", + "description": "分镜数(shots 条数聚合)", + "default": 0 + } + }, + "type": "object", + "required": [ + "project_id", + "index", + "title", + "id" + ], + "title": "ChapterRead" + }, + "ChapterStatus": { + "type": "string", + "enum": [ + "draft", + "shooting", + "done" + ], + "title": "ChapterStatus", + "description": "章节生产状态。" + }, + "ChapterTimelineEncodeMode": { + "type": "string", + "enum": [ + "uniform_transcode", + "lossless_concat_only" + ], + "title": "ChapterTimelineEncodeMode", + "description": "导出编码策略。" + }, + "ChapterTimelineExportRequest": { + "properties": { + "idempotency_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Idempotency Key", + "description": "可选幂等键" + }, + "encode_mode": { + "$ref": "#/components/schemas/ChapterTimelineEncodeMode", + "description": "uniform_transcode:统一转码拼接;lossless_concat_only:仅当片段编码一致时无损拼接", + "default": "uniform_transcode" + } + }, + "type": "object", + "title": "ChapterTimelineExportRequest", + "description": "发起章节时间线导出任务。" + }, + "ChapterTimelineRead": { + "properties": { + "layout_version": { + "type": "integer", + "minimum": 1.0, + "title": "Layout Version", + "default": 1 + }, + "segments": { + "items": { + "$ref": "#/components/schemas/ChapterTimelineSegmentRead" + }, + "type": "array", + "title": "Segments" + }, + "preview_note": { + "type": "string", + "title": "Preview Note", + "description": "连续预览能力说明", + "default": "本期以镜头编排与导出成片为主;连续全片时间线预览将在后续迭代提供。" + } + }, + "type": "object", + "title": "ChapterTimelineRead", + "description": "章节时间线读取模型。" + }, + "ChapterTimelineSegmentRead": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "片段行 ID;尚未落库的合成行可为空字符串" + }, + "shot_id": { + "type": "string", + "title": "Shot Id" + }, + "position": { + "type": "integer", + "minimum": 0.0, + "title": "Position" + }, + "trim_start_ms": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Trim Start Ms" + }, + "trim_end_ms": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Trim End Ms" + }, + "clip_status": { + "$ref": "#/components/schemas/TimelineClipStatus" + }, + "file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File Id" + }, + "label": { + "type": "string", + "title": "Label", + "description": "镜头标题等展示字段", + "default": "" + } + }, + "type": "object", + "required": [ + "id", + "shot_id", + "position", + "clip_status" + ], + "title": "ChapterTimelineSegmentRead", + "description": "时间线片段读取模型(含成片文件解析状态)。" + }, + "ChapterTimelineSegmentWrite": { + "properties": { + "shot_id": { + "type": "string", + "title": "Shot Id", + "description": "镜头 ID" + }, + "trim_start_ms": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Trim Start Ms", + "description": "入点毫秒(可选)" + }, + "trim_end_ms": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Trim End Ms", + "description": "出点毫秒(可选)" + } + }, + "type": "object", + "required": [ + "shot_id" + ], + "title": "ChapterTimelineSegmentWrite", + "description": "保存时间线时的一段(顺序由数组顺序表达)。" + }, + "ChapterTimelineWrite": { + "properties": { + "layout_version": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Layout Version", + "description": "与 GET 返回一致时可校验乐观锁" + }, + "segments": { + "items": { + "$ref": "#/components/schemas/ChapterTimelineSegmentWrite" + }, + "type": "array", + "title": "Segments" + } + }, + "type": "object", + "title": "ChapterTimelineWrite", + "description": "全量替换章节时间线片段。" + }, + "ChapterUpdate": { + "properties": { + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id" + }, + "index": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Index" + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Title" + }, + "summary": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Summary" + }, + "raw_text": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Raw Text" + }, + "condensed_text": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Condensed Text" + }, + "storyboard_count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Storyboard Count" + }, + "status": { + "anyOf": [ + { + "$ref": "#/components/schemas/ChapterStatus" + }, + { + "type": "null" + } + ] + } + }, + "type": "object", + "title": "ChapterUpdate" + }, + "CharacterPortraitAnalysisRequest": { + "properties": { + "relation_entity_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Relation Entity Id", + "description": "任务关联实体 ID(资产页恢复任务可选)" + }, + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id", + "description": "项目 ID(异步任务关联可选)" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(异步任务关联可选)" + }, + "character_context": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Character Context", + "description": "原文人物上下文(可为空;用于提供额外背景,帮助判断缺失信息)" + }, + "character_description": { + "type": "string", + "minLength": 1, + "title": "Character Description", + "description": "原文人物描述" + } + }, + "type": "object", + "required": [ + "character_description" + ], + "title": "CharacterPortraitAnalysisRequest", + "description": "人物画像缺失信息分析请求。" + }, + "CharacterPortraitAnalysisResult": { + "properties": { + "issues": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Issues" + }, + "optimized_description": { + "type": "string", + "title": "Optimized Description" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "issues", + "optimized_description" + ], + "title": "CharacterPortraitAnalysisResult", + "description": "根据原文人物描述,分析缺少的信息,并给出优化后的可生成画像描述。" + }, + "CostumeInfoAnalysisRequest": { + "properties": { + "relation_entity_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Relation Entity Id", + "description": "任务关联实体 ID(资产页恢复任务可选)" + }, + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id", + "description": "项目 ID(异步任务关联可选)" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(异步任务关联可选)" + }, + "costume_context": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Costume Context", + "description": "原文服装上下文(可为空;用于提供额外背景,帮助判断缺失信息)" + }, + "costume_description": { + "type": "string", + "minLength": 1, + "title": "Costume Description", + "description": "原文服装描述" + } + }, + "type": "object", + "required": [ + "costume_description" + ], + "title": "CostumeInfoAnalysisRequest", + "description": "服装信息缺失分析请求。" + }, + "CostumeInfoAnalysisResult": { + "properties": { + "issues": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Issues" + }, + "optimized_description": { + "type": "string", + "title": "Optimized Description" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "issues", + "optimized_description" + ], + "title": "CostumeInfoAnalysisResult", + "description": "根据原文服装/造型描述,分析缺少的信息,并给出优化后的可生成服装描述。" + }, + "CostumeTimeline": { + "properties": { + "character_id": { + "type": "string", + "title": "Character Id", + "description": "角色稳定ID" + }, + "character_name": { + "type": "string", + "title": "Character Name", + "description": "角色名称" + }, + "timeline_entries": { + "items": { + "$ref": "#/components/schemas/CostumeTimelineEntry" + }, + "type": "array", + "title": "Timeline Entries", + "description": "时间线条目" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "character_id", + "character_name" + ], + "title": "CostumeTimeline", + "description": "单角色的服装演变时间线。" + }, + "CostumeTimelineEntry": { + "properties": { + "shot_index": { + "type": "integer", + "minimum": 1.0, + "title": "Shot Index", + "description": "镜头序号" + }, + "scene_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Scene Id", + "description": "可选:所属场景稳定ID(若已可推断)" + }, + "costume_note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Costume Note", + "description": "服装/外形要点(简短)" + }, + "changes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Changes", + "description": "与上一条相比的变化点" + }, + "evidence": { + "items": { + "$ref": "#/components/schemas/EvidenceSpan" + }, + "type": "array", + "title": "Evidence", + "description": "原文依据(可选)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "shot_index" + ], + "title": "CostumeTimelineEntry", + "description": "单角色的服装演变时间线条目。" + }, + "DialogueLineMode": { + "type": "string", + "enum": [ + "DIALOGUE", + "VOICE_OVER", + "OFF_SCREEN", + "PHONE" + ], + "title": "DialogueLineMode", + "description": "对白模式(与 `app.schemas.skills.common.DialogueLineMode` 对齐,存英文 code)。" + }, + "EntityEntry": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "实体稳定ID(合并阶段分配)" + }, + "name": { + "type": "string", + "title": "Name", + "description": "实体名称" + }, + "type": { + "type": "string", + "enum": [ + "character", + "scene", + "prop", + "location" + ], + "title": "Type", + "description": "实体类型" + }, + "normalized_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Normalized Name", + "description": "归一化名称(来自文本,可选)" + }, + "aliases": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Aliases", + "description": "别名/称呼(来自文本,可选)" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "基础画像/描述(忠实文本,简短)" + }, + "confidence": { + "anyOf": [ + { + "type": "number", + "maximum": 1.0, + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Confidence", + "description": "合并确定度 0-1(可选)" + }, + "first_appearance": { + "anyOf": [ + { + "$ref": "#/components/schemas/EvidenceSpan" + }, + { + "type": "null" + } + ], + "description": "首次出场证据(可选)" + }, + "costume_note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Costume Note", + "description": "服装/造型描述(可选,便于变体与资产关联)" + }, + "traits": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Traits", + "description": "性格/特征词(可选)" + }, + "location_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Location Type", + "description": "地点类型:房间/街道/森林/车厢等(可选)" + }, + "category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Category", + "description": "道具类别(可选:weapon/document/vehicle/clothing/device/magic_item/other)" + }, + "owner_character_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Owner Character Id", + "description": "拥有者角色ID(可选)" + }, + "evidence": { + "items": { + "$ref": "#/components/schemas/EvidenceSpan" + }, + "type": "array", + "title": "Evidence", + "description": "支撑该实体画像的证据片段(可选)" + }, + "first_shot": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "First Shot", + "description": "首次出现的镜头序号" + }, + "appearances": { + "items": { + "type": "integer" + }, + "type": "array", + "title": "Appearances", + "description": "出现镜头列表" + }, + "variants": { + "items": { + "$ref": "#/components/schemas/EntityVariant" + }, + "type": "array", + "title": "Variants", + "description": "变体列表" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "id", + "name", + "type" + ], + "title": "EntityEntry", + "description": "合并后的实体条目(脚本处理中间态)。" + }, + "EntityLibrary": { + "properties": { + "characters": { + "items": { + "$ref": "#/components/schemas/EntityEntry" + }, + "type": "array", + "title": "Characters", + "description": "角色库" + }, + "locations": { + "items": { + "$ref": "#/components/schemas/EntityEntry" + }, + "type": "array", + "title": "Locations", + "description": "地点库" + }, + "scenes": { + "items": { + "$ref": "#/components/schemas/EntityEntry" + }, + "type": "array", + "title": "Scenes", + "description": "场景库" + }, + "props": { + "items": { + "$ref": "#/components/schemas/EntityEntry" + }, + "type": "array", + "title": "Props", + "description": "道具库" + }, + "total_entries": { + "type": "integer", + "minimum": 0.0, + "title": "Total Entries", + "description": "总实体数" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "total_entries" + ], + "title": "EntityLibrary", + "description": "合并后的实体库(脚本处理中间态)。" + }, + "EntityMergeResult": { + "properties": { + "merged_library": { + "$ref": "#/components/schemas/EntityLibrary", + "description": "合并后的实体库" + }, + "merge_stats": { + "additionalProperties": true, + "type": "object", + "title": "Merge Stats", + "description": "合并统计信息" + }, + "conflicts": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Conflicts", + "description": "发现的冲突/待处理项" + }, + "notes": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Notes", + "description": "合并说明" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "merged_library" + ], + "title": "EntityMergeResult", + "description": "实体合并结果(脚本处理中间态)。" + }, + "EntityMergerRequest": { + "properties": { + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id", + "description": "项目 ID(异步任务关联可选)" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(异步任务关联可选)" + }, + "all_shot_extractions": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": "array", + "title": "All Shot Extractions", + "description": "所有镜头提取结果(ShotElementExtractionResult 的序列化形式)" + }, + "historical_library": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Historical Library", + "description": "历史实体库(可选,用于增量合并)" + }, + "script_division": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Script Division", + "description": "脚本分镜结果(可选;ScriptDivisionResult 序列化),用于定位与统计" + }, + "previous_merge": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Previous Merge", + "description": "上一次合并结果(可选;EntityMergeResult 序列化),用于冲突重试合并" + }, + "conflict_resolutions": { + "anyOf": [ + { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Conflict Resolutions", + "description": "冲突解决建议列表(可选;用于冲突重试合并)" + } + }, + "type": "object", + "required": [ + "all_shot_extractions" + ], + "title": "EntityMergerRequest", + "description": "实体合并请求。" + }, + "EntityNameExistenceCheckRequest": { + "properties": { + "project_id": { + "type": "string", + "minLength": 1, + "title": "Project Id", + "description": "项目 ID(必填)" + }, + "shot_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Shot Id", + "description": "镜头 ID(可选;不传则 linked_to_shot 恒为 false)" + }, + "character_names": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Character Names", + "description": "角色名称列表" + }, + "prop_names": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Prop Names", + "description": "道具名称列表" + }, + "scene_names": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Scene Names", + "description": "场景名称列表" + }, + "costume_names": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Costume Names", + "description": "服装名称列表" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "project_id" + ], + "title": "EntityNameExistenceCheckRequest", + "description": "批量检测项目内/全局资产名称是否存在(模糊匹配)。" + }, + "EntityNameExistenceCheckResponse": { + "properties": { + "characters": { + "items": { + "$ref": "#/components/schemas/EntityNameExistenceItem" + }, + "type": "array", + "title": "Characters" + }, + "props": { + "items": { + "$ref": "#/components/schemas/EntityNameExistenceItem" + }, + "type": "array", + "title": "Props" + }, + "scenes": { + "items": { + "$ref": "#/components/schemas/EntityNameExistenceItem" + }, + "type": "array", + "title": "Scenes" + }, + "costumes": { + "items": { + "$ref": "#/components/schemas/EntityNameExistenceItem" + }, + "type": "array", + "title": "Costumes" + } + }, + "additionalProperties": false, + "type": "object", + "title": "EntityNameExistenceCheckResponse", + "description": "批量存在性检测结果(按资产类型分组)。" + }, + "EntityNameExistenceItem": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "输入名称(原样回传)" + }, + "exists": { + "type": "boolean", + "title": "Exists", + "description": "数据库中是否存在(模糊命中)" + }, + "linked_to_project": { + "type": "boolean", + "title": "Linked To Project", + "description": "是否已关联到该项目(角色等同于 exists)" + }, + "linked_to_shot": { + "type": "boolean", + "title": "Linked To Shot", + "description": "是否已关联到请求中的 shot(未传 shot_id 时为 false)", + "default": false + }, + "asset_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Asset Id", + "description": "命中的资产 ID(如 prop_id/scene_id/costume_id/character_id)" + }, + "link_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Link Id", + "description": "若已关联到项目,对应 Project*Link 的 id;否则为空" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "exists", + "linked_to_project" + ], + "title": "EntityNameExistenceItem", + "description": "单个名称的存在性结果。" + }, + "EntityVariant": { + "properties": { + "variant_key": { + "type": "string", + "title": "Variant Key", + "description": "变体键(例如 outfit_v1、wounded_state 等)" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "变体描述(简短)" + }, + "affected_shots": { + "items": { + "type": "integer" + }, + "type": "array", + "title": "Affected Shots", + "description": "涉及镜头序号" + }, + "evidence": { + "items": { + "$ref": "#/components/schemas/EvidenceSpan" + }, + "type": "array", + "title": "Evidence", + "description": "原文依据(可选)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "variant_key" + ], + "title": "EntityVariant", + "description": "实体变体条目(最小可用结构,便于服装/外形演变)。" + }, + "EvidenceSpan": { + "properties": { + "chunk_id": { + "type": "string", + "title": "Chunk Id", + "description": "输入文本块的唯一ID(例如 chapter1_p03)" + }, + "start_char": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Start Char", + "description": "在该 chunk 中的起始字符位置(可选)" + }, + "end_char": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "End Char", + "description": "在该 chunk 中的结束字符位置(可选)" + }, + "quote": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Quote", + "description": "不超过200字的原文摘录(可选,便于人工审核)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "chunk_id" + ], + "title": "EvidenceSpan", + "description": "可追溯证据:原文定位(chunk + 起止位置/摘录),用于审核与回查。" + }, + "FileDetailRead": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "文件 ID" + }, + "type": { + "$ref": "#/components/schemas/FileTypeEnum", + "description": "文件类型" + }, + "name": { + "type": "string", + "title": "Name", + "description": "文件名/标题" + }, + "thumbnail": { + "type": "string", + "title": "Thumbnail", + "description": "缩略图 URL/路径", + "default": "" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Tags", + "description": "标签" + }, + "usages": { + "items": { + "$ref": "#/components/schemas/FileUsageRead" + }, + "type": "array", + "title": "Usages" + } + }, + "type": "object", + "required": [ + "id", + "type", + "name" + ], + "title": "FileDetailRead", + "description": "含 file_usages 列表(详情接口)。" + }, + "FileRead": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "文件 ID" + }, + "type": { + "$ref": "#/components/schemas/FileTypeEnum", + "description": "文件类型" + }, + "name": { + "type": "string", + "title": "Name", + "description": "文件名/标题" + }, + "thumbnail": { + "type": "string", + "title": "Thumbnail", + "description": "缩略图 URL/路径", + "default": "" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Tags", + "description": "标签" + } + }, + "type": "object", + "required": [ + "id", + "type", + "name" + ], + "title": "FileRead" + }, + "FileTypeEnum": { + "type": "string", + "enum": [ + "image", + "video" + ], + "title": "FileTypeEnum" + }, + "FileUpdate": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "thumbnail": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Thumbnail" + }, + "tags": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Tags" + }, + "usage": { + "anyOf": [ + { + "$ref": "#/components/schemas/FileUsageWrite" + }, + { + "type": "null" + } + ], + "description": "若提供则 upsert 一条 file_usages" + } + }, + "type": "object", + "title": "FileUpdate" + }, + "FileUsageRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id" + }, + "file_id": { + "type": "string", + "title": "File Id" + }, + "project_id": { + "type": "string", + "title": "Project Id" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id" + }, + "shot_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Shot Id" + }, + "usage_kind": { + "type": "string", + "title": "Usage Kind" + }, + "source_ref": { + "type": "string", + "title": "Source Ref" + } + }, + "type": "object", + "required": [ + "id", + "file_id", + "project_id", + "chapter_id", + "shot_id", + "usage_kind", + "source_ref" + ], + "title": "FileUsageRead" + }, + "FileUsageWrite": { + "properties": { + "project_id": { + "type": "string", + "title": "Project Id", + "description": "项目 ID" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID" + }, + "shot_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Shot Id", + "description": "镜头 ID" + }, + "usage_kind": { + "type": "string", + "title": "Usage Kind", + "description": "用途:shot_frame / generated_video / chapter_master_video / character_image / asset_image / upload / api 等" + }, + "source_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Source Ref", + "description": "幂等键(可选)" + } + }, + "type": "object", + "required": [ + "project_id", + "usage_kind" + ], + "title": "FileUsageWrite", + "description": "写入 file_usages 的关联信息(与 FileItem 一并提交)。" + }, + "FrameGuidanceDecisionRead": { + "properties": { + "text": { + "type": "string", + "title": "Text", + "description": "guidance 原文" + }, + "category": { + "type": "string", + "title": "Category", + "description": "guidance 分类,如 summary / continuity / composition / screen" + }, + "reason_tag": { + "type": "string", + "title": "Reason Tag", + "description": "简短原因标签,如 首帧保空间 / 关键帧保轴线", + "default": "" + }, + "reason": { + "type": "string", + "title": "Reason", + "description": "该 guidance 被保留或压缩的原因说明" + } + }, + "type": "object", + "required": [ + "text", + "category", + "reason" + ], + "title": "FrameGuidanceDecisionRead", + "description": "分镜帧 guidance 的保留/压缩决策结果。" + }, + "GenerationTaskLinkCreate": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id", + "description": "生成任务 ID" + }, + "resource_type": { + "type": "string", + "title": "Resource Type", + "description": "生成资源类型(如 image/video/text/task_link)" + }, + "relation_type": { + "type": "string", + "title": "Relation Type", + "description": "业务类型(如 prop/costume/scene 等)" + }, + "relation_entity_id": { + "type": "string", + "title": "Relation Entity Id", + "description": "关联业务实体 ID" + }, + "file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File Id", + "description": "关联产物文件 ID(files.id;适用于图片/音频/视频)" + }, + "status": { + "type": "string", + "title": "Status", + "description": "关联状态:accepted=已采用、todo=待操作、rejected=未采用;默认 todo", + "default": "todo" + } + }, + "type": "object", + "required": [ + "task_id", + "resource_type", + "relation_type", + "relation_entity_id" + ], + "title": "GenerationTaskLinkCreate", + "description": "创建生成任务关联请求体。" + }, + "GenerationTaskLinkRead": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id", + "description": "生成任务 ID" + }, + "resource_type": { + "type": "string", + "title": "Resource Type", + "description": "生成资源类型(如 image/video/text/task_link)" + }, + "relation_type": { + "type": "string", + "title": "Relation Type", + "description": "业务类型(如 prop/costume/scene 等)" + }, + "relation_entity_id": { + "type": "string", + "title": "Relation Entity Id", + "description": "关联业务实体 ID" + }, + "file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File Id", + "description": "关联产物文件 ID(files.id;适用于图片/音频/视频)" + }, + "status": { + "type": "string", + "title": "Status", + "description": "关联状态:accepted=已采用、todo=待操作、rejected=未采用" + }, + "id": { + "type": "integer", + "title": "Id", + "description": "关联行 ID" + } + }, + "type": "object", + "required": [ + "task_id", + "resource_type", + "relation_type", + "relation_entity_id", + "status", + "id" + ], + "title": "GenerationTaskLinkRead", + "description": "生成任务关联返回体。" + }, + "GenerationTaskLinkUpdate": { + "properties": { + "resource_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Resource Type", + "description": "生成资源类型(如 image/video/text/task_link)" + }, + "relation_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Relation Type", + "description": "业务类型(如 prop/costume/scene 等)" + }, + "relation_entity_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Relation Entity Id", + "description": "关联业务实体 ID" + }, + "file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File Id", + "description": "关联产物文件 ID(files.id;适用于图片/音频/视频)" + }, + "status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Status", + "description": "关联状态:accepted=已采用、todo=待操作、rejected=未采用" + } + }, + "type": "object", + "title": "GenerationTaskLinkUpdate", + "description": "更新生成任务关联请求体(不包含 is_adopted,采用状态由专用接口正向变更)。" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "ImageGenerationOptionsRead": { + "properties": { + "provider": { + "type": "string", + "title": "Provider", + "description": "供应商稳定键" + }, + "model_id": { + "type": "string", + "title": "Model Id", + "description": "默认图片模型 ID" + }, + "model_name": { + "type": "string", + "title": "Model Name", + "description": "默认图片模型名称" + }, + "supported_ratios": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Supported Ratios", + "description": "当前模型支持的目标比例" + }, + "default_resolution_profile": { + "type": "string", + "title": "Default Resolution Profile", + "description": "当前模型默认分辨率档位" + }, + "ratio_size_profiles": { + "additionalProperties": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "type": "object", + "title": "Ratio Size Profiles", + "description": "按比例和分辨率档位映射得到的像素尺寸" + } + }, + "type": "object", + "required": [ + "provider", + "model_id", + "model_name", + "default_resolution_profile" + ], + "title": "ImageGenerationOptionsRead", + "description": "当前默认图片模型对应的关键帧规格选项。" + }, + "LogLevel": { + "type": "string", + "enum": [ + "debug", + "info", + "warn", + "error" + ], + "title": "LogLevel", + "description": "全局日志级别。" + }, + "ModelCategoryKey": { + "type": "string", + "enum": [ + "text", + "image", + "video" + ], + "title": "ModelCategoryKey", + "description": "模型类别:文本/图片/视频。" + }, + "ModelChatTestRead": { + "properties": { + "reply": { + "type": "string", + "title": "Reply", + "description": "模型回复正文", + "default": "" + }, + "elapsed_ms": { + "type": "integer", + "title": "Elapsed Ms", + "description": "服务端调用耗时(毫秒)" + } + }, + "type": "object", + "required": [ + "elapsed_ms" + ], + "title": "ModelChatTestRead", + "description": "模型试聊响应(仅返回正文与耗时,不回显密钥)。" + }, + "ModelChatTestRequest": { + "properties": { + "message": { + "type": "string", + "maxLength": 8000, + "minLength": 1, + "title": "Message", + "description": "用户输入;服务端会做长度截断与超时保护" + } + }, + "type": "object", + "required": [ + "message" + ], + "title": "ModelChatTestRequest", + "description": "模型试聊请求(仅文本模型)。" + }, + "ModelCreate": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "模型名称" + }, + "category": { + "$ref": "#/components/schemas/ModelCategoryKey", + "description": "模型类别:text/image/video" + }, + "provider_id": { + "type": "string", + "title": "Provider Id", + "description": "所属供应商 ID" + }, + "params": { + "additionalProperties": true, + "type": "object", + "title": "Params", + "description": "模型参数(JSON)" + }, + "description": { + "type": "string", + "title": "Description", + "description": "说明", + "default": "" + }, + "created_by": { + "type": "string", + "title": "Created By", + "description": "创建人", + "default": "" + }, + "id": { + "type": "string", + "title": "Id", + "description": "模型 ID" + } + }, + "type": "object", + "required": [ + "name", + "category", + "provider_id", + "id" + ], + "title": "ModelCreate", + "description": "创建模型请求体。" + }, + "ModelRead": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "模型名称" + }, + "category": { + "$ref": "#/components/schemas/ModelCategoryKey", + "description": "模型类别:text/image/video" + }, + "provider_id": { + "type": "string", + "title": "Provider Id", + "description": "所属供应商 ID" + }, + "params": { + "additionalProperties": true, + "type": "object", + "title": "Params", + "description": "模型参数(JSON)" + }, + "description": { + "type": "string", + "title": "Description", + "description": "说明", + "default": "" + }, + "created_by": { + "type": "string", + "title": "Created By", + "description": "创建人", + "default": "" + }, + "id": { + "type": "string", + "title": "Id", + "description": "模型 ID" + } + }, + "type": "object", + "required": [ + "name", + "category", + "provider_id", + "id" + ], + "title": "ModelRead", + "description": "对外返回的模型信息。" + }, + "ModelSettingsRead": { + "properties": { + "default_text_model_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Default Text Model Id", + "description": "默认文本模型 ID" + }, + "default_image_model_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Default Image Model Id", + "description": "默认图片模型 ID" + }, + "default_video_model_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Default Video Model Id", + "description": "默认视频模型 ID" + }, + "api_timeout": { + "type": "integer", + "title": "Api Timeout", + "description": "API 超时(秒)", + "default": 30 + }, + "log_level": { + "$ref": "#/components/schemas/LogLevel", + "description": "日志级别", + "default": "info" + }, + "id": { + "type": "integer", + "title": "Id", + "description": "设置行 ID(通常为 1)" + } + }, + "type": "object", + "required": [ + "id" + ], + "title": "ModelSettingsRead", + "description": "对外返回的模型全局设置。" + }, + "ModelSettingsUpdate": { + "properties": { + "default_text_model_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Default Text Model Id", + "description": "默认文本模型 ID" + }, + "default_image_model_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Default Image Model Id", + "description": "默认图片模型 ID" + }, + "default_video_model_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Default Video Model Id", + "description": "默认视频模型 ID" + }, + "api_timeout": { + "type": "integer", + "title": "Api Timeout", + "description": "API 超时(秒)", + "default": 30 + }, + "log_level": { + "$ref": "#/components/schemas/LogLevel", + "description": "日志级别", + "default": "info" + } + }, + "type": "object", + "title": "ModelSettingsUpdate", + "description": "更新或保存模型全局设置请求体。" + }, + "ModelUpdate": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "模型名称" + }, + "category": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelCategoryKey" + }, + { + "type": "null" + } + ], + "description": "模型类别" + }, + "provider_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Provider Id", + "description": "所属供应商 ID" + }, + "params": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Params", + "description": "模型参数(JSON)" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "说明" + } + }, + "type": "object", + "title": "ModelUpdate", + "description": "更新模型请求体(全部可选)。" + }, + "ModelVerifyRead": { + "properties": { + "ok": { + "type": "boolean", + "title": "Ok", + "description": "是否探测通过" + }, + "category": { + "$ref": "#/components/schemas/ModelCategoryKey", + "description": "被验证模型的类别" + }, + "message": { + "type": "string", + "title": "Message", + "description": "面向用户的主提示" + }, + "elapsed_ms": { + "type": "integer", + "title": "Elapsed Ms", + "description": "服务端探测耗时(毫秒)" + }, + "detail": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Detail", + "description": "脱敏后的诊断信息(如 provider_key、上游 HTTP 状态、回复摘要);无 RBAC 时仍不得包含密钥" + } + }, + "type": "object", + "required": [ + "ok", + "category", + "message", + "elapsed_ms" + ], + "title": "ModelVerifyRead", + "description": "模型配置验证结果(同步探测;不含任何密钥明文)。" + }, + "PaginatedData_Any_": { + "properties": { + "items": { + "items": {}, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[Any]" + }, + "PaginatedData_ChapterRead_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ChapterRead" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[ChapterRead]" + }, + "PaginatedData_FileRead_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/FileRead" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[FileRead]" + }, + "PaginatedData_GenerationTaskLinkRead_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/GenerationTaskLinkRead" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[GenerationTaskLinkRead]" + }, + "PaginatedData_ModelRead_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ModelRead" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[ModelRead]" + }, + "PaginatedData_ProjectRead_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ProjectRead" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[ProjectRead]" + }, + "PaginatedData_PromptTemplateRead_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/PromptTemplateRead" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[PromptTemplateRead]" + }, + "PaginatedData_ProviderRead_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ProviderRead" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[ProviderRead]" + }, + "PaginatedData_ShotDetailRead_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ShotDetailRead" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[ShotDetailRead]" + }, + "PaginatedData_ShotDialogLineRead_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ShotDialogLineRead" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[ShotDialogLineRead]" + }, + "PaginatedData_ShotFrameImageRead_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ShotFrameImageRead" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[ShotFrameImageRead]" + }, + "PaginatedData_ShotLinkedAssetItem_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ShotLinkedAssetItem" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[ShotLinkedAssetItem]" + }, + "PaginatedData_ShotRead_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ShotRead" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[ShotRead]" + }, + "PaginatedData_TaskListItemRead_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/TaskListItemRead" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[TaskListItemRead]" + }, + "PaginatedData_dict_str__Any__": { + "properties": { + "items": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": "array", + "title": "Items", + "description": "当前页数据" + }, + "pagination": { + "$ref": "#/components/schemas/Pagination", + "description": "分页信息" + } + }, + "type": "object", + "required": [ + "items", + "pagination" + ], + "title": "PaginatedData[dict[str, Any]]" + }, + "Pagination": { + "properties": { + "page": { + "type": "integer", + "title": "Page", + "description": "当前页,从 1 开始" + }, + "page_size": { + "type": "integer", + "title": "Page Size", + "description": "每页条数" + }, + "total": { + "type": "integer", + "title": "Total", + "description": "总条数" + }, + "max_page": { + "type": "integer", + "title": "Max Page", + "description": "最大页码" + } + }, + "type": "object", + "required": [ + "page", + "page_size", + "total", + "max_page" + ], + "title": "Pagination", + "description": "分页信息。" + }, + "ProjectActorLinkRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id", + "description": "关联行 ID" + }, + "project_id": { + "type": "string", + "title": "Project Id", + "description": "项目 ID" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(可空)" + }, + "shot_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Shot Id", + "description": "镜头 ID(可空)" + }, + "actor_id": { + "type": "string", + "title": "Actor Id" + }, + "thumbnail": { + "type": "string", + "title": "Thumbnail", + "description": "演员缩略图下载地址", + "default": "" + } + }, + "type": "object", + "required": [ + "id", + "project_id", + "actor_id" + ], + "title": "ProjectActorLinkRead" + }, + "ProjectAssetLinkCreate": { + "properties": { + "project_id": { + "type": "string", + "title": "Project Id" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id" + }, + "shot_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Shot Id" + }, + "asset_id": { + "type": "string", + "title": "Asset Id" + } + }, + "type": "object", + "required": [ + "project_id", + "asset_id" + ], + "title": "ProjectAssetLinkCreate" + }, + "ProjectCostumeLinkRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id", + "description": "关联行 ID" + }, + "project_id": { + "type": "string", + "title": "Project Id", + "description": "项目 ID" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(可空)" + }, + "shot_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Shot Id", + "description": "镜头 ID(可空)" + }, + "costume_id": { + "type": "string", + "title": "Costume Id" + }, + "thumbnail": { + "type": "string", + "title": "Thumbnail", + "description": "服装缩略图下载地址", + "default": "" + } + }, + "type": "object", + "required": [ + "id", + "project_id", + "costume_id" + ], + "title": "ProjectCostumeLinkRead" + }, + "ProjectCreate": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "项目名称" + }, + "description": { + "type": "string", + "title": "Description", + "description": "项目简介", + "default": "" + }, + "style": { + "$ref": "#/components/schemas/ProjectStyle", + "description": "题材/风格", + "examples": [ + "真人都市", + "真人科幻", + "真人古装", + "动漫科幻", + "动漫3D", + "国漫", + "水墨画" + ] + }, + "visual_style": { + "$ref": "#/components/schemas/ProjectVisualStyle", + "description": "画面表现形式", + "default": "现实" + }, + "seed": { + "type": "integer", + "title": "Seed", + "description": "随机种子", + "default": 0 + }, + "unify_style": { + "type": "boolean", + "title": "Unify Style", + "description": "是否统一风格", + "default": true + }, + "progress": { + "type": "integer", + "title": "Progress", + "description": "进度百分比(0-100)", + "default": 0 + }, + "default_video_ratio": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Default Video Ratio", + "description": "项目级默认视频比例;分镜未覆盖时生效" + }, + "stats": { + "additionalProperties": true, + "type": "object", + "title": "Stats", + "description": "聚合统计(JSON)" + }, + "id": { + "type": "string", + "title": "Id", + "description": "项目 ID" + } + }, + "type": "object", + "required": [ + "name", + "style", + "id" + ], + "title": "ProjectCreate" + }, + "ProjectPropLinkRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id", + "description": "关联行 ID" + }, + "project_id": { + "type": "string", + "title": "Project Id", + "description": "项目 ID" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(可空)" + }, + "shot_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Shot Id", + "description": "镜头 ID(可空)" + }, + "prop_id": { + "type": "string", + "title": "Prop Id" + }, + "thumbnail": { + "type": "string", + "title": "Thumbnail", + "description": "道具缩略图下载地址", + "default": "" + } + }, + "type": "object", + "required": [ + "id", + "project_id", + "prop_id" + ], + "title": "ProjectPropLinkRead" + }, + "ProjectRead": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "项目名称" + }, + "description": { + "type": "string", + "title": "Description", + "description": "项目简介", + "default": "" + }, + "style": { + "$ref": "#/components/schemas/ProjectStyle", + "description": "题材/风格", + "examples": [ + "真人都市", + "真人科幻", + "真人古装", + "动漫科幻", + "动漫3D", + "国漫", + "水墨画" + ] + }, + "visual_style": { + "$ref": "#/components/schemas/ProjectVisualStyle", + "description": "画面表现形式", + "default": "现实" + }, + "seed": { + "type": "integer", + "title": "Seed", + "description": "随机种子", + "default": 0 + }, + "unify_style": { + "type": "boolean", + "title": "Unify Style", + "description": "是否统一风格", + "default": true + }, + "progress": { + "type": "integer", + "title": "Progress", + "description": "进度百分比(0-100)", + "default": 0 + }, + "default_video_ratio": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Default Video Ratio", + "description": "项目级默认视频比例;分镜未覆盖时生效" + }, + "stats": { + "additionalProperties": true, + "type": "object", + "title": "Stats", + "description": "聚合统计(JSON)" + }, + "id": { + "type": "string", + "title": "Id" + } + }, + "type": "object", + "required": [ + "name", + "style", + "id" + ], + "title": "ProjectRead" + }, + "ProjectSceneLinkRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id", + "description": "关联行 ID" + }, + "project_id": { + "type": "string", + "title": "Project Id", + "description": "项目 ID" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(可空)" + }, + "shot_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Shot Id", + "description": "镜头 ID(可空)" + }, + "scene_id": { + "type": "string", + "title": "Scene Id" + }, + "thumbnail": { + "type": "string", + "title": "Thumbnail", + "description": "场景缩略图下载地址", + "default": "" + } + }, + "type": "object", + "required": [ + "id", + "project_id", + "scene_id" + ], + "title": "ProjectSceneLinkRead" + }, + "ProjectStyle": { + "type": "string", + "enum": [ + "真人都市", + "真人科幻", + "真人古装", + "动漫科幻", + "动漫3D", + "国漫", + "水墨画" + ], + "title": "ProjectStyle", + "description": "项目题材/风格维度(不用于区分真人/动漫)。" + }, + "ProjectStyleOptionsRead": { + "properties": { + "visual_styles": { + "items": { + "$ref": "#/components/schemas/StyleOption" + }, + "type": "array", + "title": "Visual Styles", + "description": "视觉风格可选项" + }, + "styles_by_visual_style": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/StyleOption" + }, + "type": "array" + }, + "type": "object", + "title": "Styles By Visual Style", + "description": "按视觉风格分组的视频风格选项" + }, + "default_style_by_visual_style": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "title": "Default Style By Visual Style", + "description": "各视觉风格默认视频风格" + } + }, + "type": "object", + "title": "ProjectStyleOptionsRead", + "description": "项目风格候选项。" + }, + "ProjectUpdate": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "style": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProjectStyle" + }, + { + "type": "null" + } + ], + "description": "题材/风格", + "examples": [ + "真人都市", + "真人科幻", + "真人古装", + "动漫科幻", + "动漫3D", + "国漫", + "水墨画" + ] + }, + "visual_style": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProjectVisualStyle" + }, + { + "type": "null" + } + ] + }, + "seed": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Seed" + }, + "unify_style": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Unify Style" + }, + "progress": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Progress" + }, + "default_video_ratio": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Default Video Ratio" + }, + "stats": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Stats" + } + }, + "type": "object", + "title": "ProjectUpdate" + }, + "ProjectVisualStyle": { + "type": "string", + "enum": [ + "现实", + "动漫" + ], + "title": "ProjectVisualStyle", + "description": "画面表现形式维度:用于区分现实/动漫等。" + }, + "PromptCategory": { + "type": "string", + "enum": [ + "frame_head_image", + "frame_tail_image", + "frame_key_image", + "frame_head_prompt", + "frame_tail_prompt", + "frame_key_prompt", + "video_prompt", + "storyboard_prompt", + "bgm", + "sfx", + "character_image_front", + "character_image_other", + "actor_image_front", + "actor_image_other", + "prop_image_front", + "prop_image_other", + "scene_image_front", + "scene_image_other", + "costume_image_front", + "costume_image_other", + "combined" + ], + "title": "PromptCategory", + "description": "提示词模板类别。" + }, + "PromptCategoryOptionRead": { + "properties": { + "value": { + "$ref": "#/components/schemas/PromptCategory", + "description": "类别枚举值" + }, + "label": { + "type": "string", + "title": "Label", + "description": "中文名称" + }, + "description": { + "type": "string", + "title": "Description", + "description": "类别简介", + "default": "" + } + }, + "type": "object", + "required": [ + "value", + "label" + ], + "title": "PromptCategoryOptionRead", + "description": "提示词类别选项(枚举值 + 中文标签 + 简介)。" + }, + "PromptTemplateCreate": { + "properties": { + "category": { + "$ref": "#/components/schemas/PromptCategory", + "description": "模板类别" + }, + "name": { + "type": "string", + "title": "Name", + "description": "模板名称" + }, + "content": { + "type": "string", + "title": "Content", + "description": "模板内容" + }, + "preview": { + "type": "string", + "title": "Preview", + "description": "预览文案", + "default": "" + }, + "variables": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Variables", + "description": "变量名列表" + }, + "is_default": { + "type": "boolean", + "title": "Is Default", + "description": "是否为默认提示词", + "default": false + } + }, + "type": "object", + "required": [ + "category", + "name", + "content" + ], + "title": "PromptTemplateCreate", + "description": "创建提示词模板。id 由后端自动生成;is_system 不可由客户端设置。" + }, + "PromptTemplateRead": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "模板 ID" + }, + "category": { + "$ref": "#/components/schemas/PromptCategory", + "description": "模板类别" + }, + "name": { + "type": "string", + "title": "Name", + "description": "模板名称" + }, + "preview": { + "type": "string", + "title": "Preview", + "description": "预览文案" + }, + "content": { + "type": "string", + "title": "Content", + "description": "模板内容" + }, + "variables": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Variables", + "description": "变量名列表" + }, + "is_default": { + "type": "boolean", + "title": "Is Default", + "description": "是否为默认提示词" + }, + "is_system": { + "type": "boolean", + "title": "Is System", + "description": "是否为系统预置" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At", + "description": "创建时间" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At", + "description": "最后更新时间" + } + }, + "type": "object", + "required": [ + "id", + "category", + "name", + "preview", + "content", + "variables", + "is_default", + "is_system", + "created_at", + "updated_at" + ], + "title": "PromptTemplateRead", + "description": "读取提示词模板(含全部字段)。" + }, + "PromptTemplateUpdate": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "模板名称" + }, + "content": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Content", + "description": "模板内容" + }, + "preview": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Preview", + "description": "预览文案" + }, + "variables": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Variables", + "description": "变量名列表(整体替换)" + }, + "is_default": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Is Default", + "description": "是否为默认提示词" + } + }, + "type": "object", + "title": "PromptTemplateUpdate", + "description": "局部更新提示词模板。不含 id / is_system。" + }, + "PropInfoAnalysisRequest": { + "properties": { + "relation_entity_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Relation Entity Id", + "description": "任务关联实体 ID(资产页恢复任务可选)" + }, + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id", + "description": "项目 ID(异步任务关联可选)" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(异步任务关联可选)" + }, + "prop_context": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Prop Context", + "description": "原文道具上下文(可为空;用于提供额外背景,帮助判断缺失信息)" + }, + "prop_description": { + "type": "string", + "minLength": 1, + "title": "Prop Description", + "description": "原文道具描述" + } + }, + "type": "object", + "required": [ + "prop_description" + ], + "title": "PropInfoAnalysisRequest", + "description": "道具信息缺失分析请求。" + }, + "PropInfoAnalysisResult": { + "properties": { + "issues": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Issues" + }, + "optimized_description": { + "type": "string", + "title": "Optimized Description" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "issues", + "optimized_description" + ], + "title": "PropInfoAnalysisResult", + "description": "根据原文道具描述,分析缺少的信息,并给出优化后的可生成道具描述。" + }, + "ProviderCreate": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "供应商名称" + }, + "base_url": { + "type": "string", + "title": "Base Url", + "description": "文本/通用 API Base URL" + }, + "image_base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Image Base Url", + "description": "图片能力 API Base URL(可选覆盖)" + }, + "video_base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Base Url", + "description": "视频能力 API Base URL(可选覆盖)" + }, + "description": { + "type": "string", + "title": "Description", + "description": "说明", + "default": "" + }, + "status": { + "$ref": "#/components/schemas/ProviderStatus", + "description": "状态:active/testing/disabled", + "default": "testing" + }, + "created_by": { + "type": "string", + "title": "Created By", + "description": "创建人", + "default": "" + }, + "id": { + "type": "string", + "title": "Id", + "description": "供应商 ID" + }, + "api_key": { + "type": "string", + "title": "Api Key", + "description": "API Key(敏感,不在响应中回显)", + "default": "" + }, + "api_secret": { + "type": "string", + "title": "Api Secret", + "description": "API Secret(敏感,不在响应中回显)", + "default": "" + } + }, + "type": "object", + "required": [ + "name", + "base_url", + "id" + ], + "title": "ProviderCreate", + "description": "创建供应商时的请求体,允许填写敏感字段。" + }, + "ProviderRead": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "供应商名称" + }, + "base_url": { + "type": "string", + "title": "Base Url", + "description": "文本/通用 API Base URL" + }, + "image_base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Image Base Url", + "description": "图片能力 API Base URL(可选覆盖)" + }, + "video_base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Base Url", + "description": "视频能力 API Base URL(可选覆盖)" + }, + "description": { + "type": "string", + "title": "Description", + "description": "说明", + "default": "" + }, + "status": { + "$ref": "#/components/schemas/ProviderStatus", + "description": "状态:active/testing/disabled", + "default": "testing" + }, + "created_by": { + "type": "string", + "title": "Created By", + "description": "创建人", + "default": "" + }, + "id": { + "type": "string", + "title": "Id", + "description": "供应商 ID" + } + }, + "type": "object", + "required": [ + "name", + "base_url", + "id" + ], + "title": "ProviderRead", + "description": "对外返回的供应商信息(不包含 api_key/api_secret)。" + }, + "ProviderStatus": { + "type": "string", + "enum": [ + "active", + "testing", + "disabled" + ], + "title": "ProviderStatus", + "description": "供应商启用状态。" + }, + "ProviderSupportedRead": { + "properties": { + "key": { + "type": "string", + "title": "Key", + "description": "供应商稳定键" + }, + "display_name": { + "type": "string", + "title": "Display Name", + "description": "供应商展示名" + }, + "aliases": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Aliases", + "description": "可识别别名" + }, + "supported_categories": { + "items": { + "$ref": "#/components/schemas/ModelCategoryKey" + }, + "type": "array", + "title": "Supported Categories", + "description": "支持的模型类别" + }, + "default_base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Default Base Url", + "description": "默认 API Base URL" + }, + "requires_api_key": { + "type": "boolean", + "title": "Requires Api Key", + "description": "是否要求 api_key", + "default": true + }, + "requires_api_secret": { + "type": "boolean", + "title": "Requires Api Secret", + "description": "是否要求 api_secret", + "default": false + }, + "is_experimental": { + "type": "boolean", + "title": "Is Experimental", + "description": "是否实验性供应商", + "default": false + } + }, + "type": "object", + "required": [ + "key", + "display_name" + ], + "title": "ProviderSupportedRead", + "description": "系统支持的供应商能力清单。" + }, + "ProviderUpdate": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "供应商名称" + }, + "base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Base Url", + "description": "文本/通用 API Base URL" + }, + "image_base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Image Base Url", + "description": "图片能力 API Base URL(可选覆盖)" + }, + "video_base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Base Url", + "description": "视频能力 API Base URL(可选覆盖)" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "说明" + }, + "status": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProviderStatus" + }, + { + "type": "null" + } + ], + "description": "状态:active/testing/disabled" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Api Key", + "description": "API Key(敏感,不在响应中回显)" + }, + "api_secret": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Api Secret", + "description": "API Secret(敏感,不在响应中回显)" + } + }, + "type": "object", + "title": "ProviderUpdate", + "description": "更新供应商时的可选字段。" + }, + "RenderedPromptResponse": { + "properties": { + "prompt": { + "type": "string", + "title": "Prompt", + "description": "渲染后的提示词(已套用模板与变量替换)" + }, + "images": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Images", + "description": "参考图 file_id 列表(自动选择;顺序有效)" + } + }, + "type": "object", + "required": [ + "prompt" + ], + "title": "RenderedPromptResponse" + }, + "RenderedShotFramePromptRead": { + "properties": { + "base_prompt": { + "type": "string", + "title": "Base Prompt", + "description": "原始基础提示词(不含图片映射说明)" + }, + "rendered_prompt": { + "type": "string", + "title": "Rendered Prompt", + "description": "最终提交给模型的提示词(含图片映射说明)" + }, + "selected_guidance": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Selected Guidance", + "description": "最终 prompt 实际保留的 guidance 列表" + }, + "dropped_guidance": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Dropped Guidance", + "description": "本次渲染中被压缩掉的 guidance 列表" + }, + "selected_guidance_details": { + "items": { + "$ref": "#/components/schemas/FrameGuidanceDecisionRead" + }, + "type": "array", + "title": "Selected Guidance Details", + "description": "最终保留 guidance 的决策详情" + }, + "dropped_guidance_details": { + "items": { + "$ref": "#/components/schemas/FrameGuidanceDecisionRead" + }, + "type": "array", + "title": "Dropped Guidance Details", + "description": "被压缩 guidance 的决策详情" + }, + "images": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Images", + "description": "最终参考图 file_id 列表,顺序与 mappings 一致" + }, + "mappings": { + "items": { + "$ref": "#/components/schemas/ShotFramePromptMappingRead" + }, + "type": "array", + "title": "Mappings", + "description": "图片与实体名称的映射关系,顺序与 images 完全一致" + } + }, + "type": "object", + "required": [ + "base_prompt", + "rendered_prompt" + ], + "title": "RenderedShotFramePromptRead", + "description": "关键帧最终生成提示词渲染结果。" + }, + "SceneInfoAnalysisRequest": { + "properties": { + "relation_entity_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Relation Entity Id", + "description": "任务关联实体 ID(资产页恢复任务可选)" + }, + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id", + "description": "项目 ID(异步任务关联可选)" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(异步任务关联可选)" + }, + "scene_context": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Scene Context", + "description": "原文场景上下文(可为空;用于提供额外背景,帮助判断缺失信息)" + }, + "scene_description": { + "type": "string", + "minLength": 1, + "title": "Scene Description", + "description": "原文场景描述" + } + }, + "type": "object", + "required": [ + "scene_description" + ], + "title": "SceneInfoAnalysisRequest", + "description": "场景信息缺失分析请求。" + }, + "SceneInfoAnalysisResult": { + "properties": { + "issues": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Issues" + }, + "optimized_description": { + "type": "string", + "title": "Optimized Description" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "issues", + "optimized_description" + ], + "title": "SceneInfoAnalysisResult", + "description": "根据原文场景描述,分析缺少的信息,并给出优化后的可生成场景描述。" + }, + "ScriptConsistencyCheckRequest": { + "properties": { + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id", + "description": "项目 ID(异步任务关联可选)" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(异步任务关联可选)" + }, + "script_text": { + "type": "string", + "minLength": 1, + "title": "Script Text", + "description": "完整剧本文本" + } + }, + "type": "object", + "required": [ + "script_text" + ], + "title": "ScriptConsistencyCheckRequest", + "description": "一致性检查请求(角色混淆)。" + }, + "ScriptConsistencyCheckResult": { + "properties": { + "issues": { + "items": { + "$ref": "#/components/schemas/ScriptConsistencyIssue" + }, + "type": "array", + "title": "Issues", + "description": "问题列表" + }, + "has_issues": { + "type": "boolean", + "title": "Has Issues", + "description": "是否发现问题" + }, + "summary": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Summary", + "description": "总结(可选)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "has_issues" + ], + "title": "ScriptConsistencyCheckResult", + "description": "基于原文的一致性检查结果(聚焦角色混淆)。" + }, + "ScriptConsistencyIssue": { + "properties": { + "issue_type": { + "type": "string", + "const": "character_confusion", + "title": "Issue Type", + "description": "固定为角色混淆类问题", + "default": "character_confusion" + }, + "character_candidates": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Character Candidates", + "description": "涉及的角色候选(名字/称呼/ID 皆可,优先用原文称呼)" + }, + "description": { + "type": "string", + "title": "Description", + "description": "问题描述(为什么会混淆)" + }, + "suggestion": { + "type": "string", + "title": "Suggestion", + "description": "修改建议(如何改写以消除混淆)" + }, + "affected_lines": { + "anyOf": [ + { + "additionalProperties": { + "type": "integer" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Affected Lines", + "description": "受影响的行号范围,形如 {start_line: x, end_line: y}" + }, + "evidence": { + "items": { + "$ref": "#/components/schemas/EvidenceSpan" + }, + "type": "array", + "title": "Evidence", + "description": "原文依据(可选)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "description", + "suggestion" + ], + "title": "ScriptConsistencyIssue", + "description": "角色混淆类一致性问题:同一角色在不同镜头被赋予不同身份/行为主体导致混淆。" + }, + "ScriptDividerRequest": { + "properties": { + "script_text": { + "type": "string", + "minLength": 1, + "title": "Script Text", + "description": "完整剧本文本" + }, + "write_to_db": { + "type": "boolean", + "title": "Write To Db", + "description": "是否将分镜写入数据库(AI Studio shots 表)", + "default": false + }, + "chapter_id": { + "anyOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(write_to_db=true 时必填)" + } + }, + "type": "object", + "required": [ + "script_text" + ], + "title": "ScriptDividerRequest", + "description": "剧本分镜请求。" + }, + "ScriptDivisionResult": { + "properties": { + "shots": { + "items": { + "$ref": "#/components/schemas/ShotDivision" + }, + "type": "array", + "title": "Shots", + "description": "分镜列表" + }, + "total_shots": { + "type": "integer", + "minimum": 0.0, + "title": "Total Shots", + "description": "总镜头数" + }, + "notes": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Notes", + "description": "拆分说明或建议(可选)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "total_shots" + ], + "title": "ScriptDivisionResult", + "description": "剧本分镜结果:镜头列表(每镜起止行号+预览文本)。" + }, + "ScriptExtractRequest": { + "properties": { + "project_id": { + "type": "string", + "minLength": 1, + "title": "Project Id", + "description": "项目 ID" + }, + "chapter_id": { + "type": "string", + "minLength": 1, + "title": "Chapter Id", + "description": "章节 ID" + }, + "script_division": { + "additionalProperties": true, + "type": "object", + "title": "Script Division", + "description": "分镜结果(ScriptDivisionResult 序列化)" + }, + "consistency": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Consistency", + "description": "一致性检查结果(可选;ScriptConsistencyCheckResult 序列化)" + }, + "refresh_cache": { + "type": "boolean", + "title": "Refresh Cache", + "description": "是否跳过后端缓存并强制重新提取", + "default": false + } + }, + "type": "object", + "required": [ + "project_id", + "chapter_id", + "script_division" + ], + "title": "ScriptExtractRequest", + "description": "项目级信息提取请求(最终输出)。" + }, + "ScriptOptimizationResult": { + "properties": { + "optimized_script_text": { + "type": "string", + "title": "Optimized Script Text", + "description": "优化后的剧本文本" + }, + "change_summary": { + "type": "string", + "title": "Change Summary", + "description": "改动摘要(只围绕 issues)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "optimized_script_text", + "change_summary" + ], + "title": "ScriptOptimizationResult", + "description": "剧本优化输出:仅在发现角色混淆问题时使用。" + }, + "ScriptOptimizeRequest": { + "properties": { + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id", + "description": "项目 ID(异步任务关联可选)" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(异步任务关联可选)" + }, + "script_text": { + "type": "string", + "minLength": 1, + "title": "Script Text", + "description": "原文剧本文本" + }, + "consistency": { + "additionalProperties": true, + "type": "object", + "title": "Consistency", + "description": "一致性检查输出(ScriptConsistencyCheckResult 序列化)" + } + }, + "type": "object", + "required": [ + "script_text", + "consistency" + ], + "title": "ScriptOptimizeRequest", + "description": "剧本优化请求(基于一致性检查结果)。" + }, + "ScriptSimplificationResult": { + "properties": { + "simplified_script_text": { + "type": "string", + "title": "Simplified Script Text", + "description": "精简后的剧本文本" + }, + "simplification_summary": { + "type": "string", + "title": "Simplification Summary", + "description": "精简策略摘要(说明删改原则)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "simplified_script_text", + "simplification_summary" + ], + "title": "ScriptSimplificationResult", + "description": "剧本精简输出:在保留剧情主体与连续性的前提下压缩篇幅。" + }, + "ScriptSimplifyRequest": { + "properties": { + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id", + "description": "项目 ID(异步任务关联可选)" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(异步任务关联可选)" + }, + "script_text": { + "type": "string", + "minLength": 1, + "title": "Script Text", + "description": "原文剧本文本" + } + }, + "type": "object", + "required": [ + "script_text" + ], + "title": "ScriptSimplifyRequest", + "description": "智能精简剧本请求。" + }, + "ShotAssetOverviewItem": { + "properties": { + "key": { + "type": "string", + "title": "Key", + "description": "合并键:type:name" + }, + "type": { + "type": "string", + "enum": [ + "character", + "prop", + "scene", + "costume" + ], + "title": "Type", + "description": "实体类型:character/prop/scene/costume" + }, + "name": { + "type": "string", + "title": "Name", + "description": "资产名称" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "候选描述(来自 extraction payload)" + }, + "thumbnail": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Thumbnail", + "description": "缩略图" + }, + "file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File Id", + "description": "缩略图或参考图文件 ID" + }, + "source": { + "type": "string", + "enum": [ + "linked", + "candidate", + "both" + ], + "title": "Source", + "description": "来源:linked/candidate/both" + }, + "candidate_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Candidate Id", + "description": "候选项 ID" + }, + "candidate_status": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotCandidateStatus" + }, + { + "type": "null" + } + ], + "description": "候选确认状态" + }, + "linked_entity_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Linked Entity Id", + "description": "当前已关联实体 ID" + }, + "linked_image_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Linked Image Id", + "description": "当前已关联实体的 image 行 ID" + }, + "is_linked": { + "type": "boolean", + "title": "Is Linked", + "description": "当前是否已关联到镜头" + } + }, + "type": "object", + "required": [ + "key", + "type", + "name", + "source", + "is_linked" + ], + "title": "ShotAssetOverviewItem", + "description": "分镜资产总览项:统一返回已关联资产与提取候选的合并视图。" + }, + "ShotAssetsOverviewRead": { + "properties": { + "shot_id": { + "type": "string", + "title": "Shot Id", + "description": "镜头 ID" + }, + "skip_extraction": { + "type": "boolean", + "title": "Skip Extraction", + "description": "是否明确跳过提取" + }, + "status": { + "$ref": "#/components/schemas/ShotStatus", + "description": "镜头流程状态" + }, + "summary": { + "$ref": "#/components/schemas/ShotAssetsOverviewSummary", + "description": "总览统计" + }, + "items": { + "items": { + "$ref": "#/components/schemas/ShotAssetOverviewItem" + }, + "type": "array", + "title": "Items", + "description": "资产总览项" + } + }, + "type": "object", + "required": [ + "shot_id", + "skip_extraction", + "status", + "summary" + ], + "title": "ShotAssetsOverviewRead" + }, + "ShotAssetsOverviewSummary": { + "properties": { + "linked_count": { + "type": "integer", + "title": "Linked Count", + "description": "已关联项数量" + }, + "pending_count": { + "type": "integer", + "title": "Pending Count", + "description": "待确认候选数量" + }, + "ignored_count": { + "type": "integer", + "title": "Ignored Count", + "description": "已忽略候选数量" + }, + "total_count": { + "type": "integer", + "title": "Total Count", + "description": "总项数(含 ignored)" + } + }, + "type": "object", + "required": [ + "linked_count", + "pending_count", + "ignored_count", + "total_count" + ], + "title": "ShotAssetsOverviewSummary" + }, + "ShotCandidateStatus": { + "type": "string", + "enum": [ + "pending", + "linked", + "ignored" + ], + "title": "ShotCandidateStatus", + "description": "镜头提取候选确认状态。" + }, + "ShotCandidateType": { + "type": "string", + "enum": [ + "character", + "scene", + "prop", + "costume" + ], + "title": "ShotCandidateType", + "description": "镜头提取候选类型。" + }, + "ShotCharacterLinkCreate": { + "properties": { + "shot_id": { + "type": "string", + "title": "Shot Id" + }, + "character_id": { + "type": "string", + "title": "Character Id" + }, + "index": { + "type": "integer", + "title": "Index", + "default": 0 + }, + "note": { + "type": "string", + "title": "Note", + "default": "" + } + }, + "type": "object", + "required": [ + "shot_id", + "character_id" + ], + "title": "ShotCharacterLinkCreate" + }, + "ShotCharacterLinkRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id", + "description": "关联行 ID" + }, + "shot_id": { + "type": "string", + "title": "Shot Id", + "description": "镜头 ID" + }, + "character_id": { + "type": "string", + "title": "Character Id", + "description": "角色 ID" + }, + "index": { + "type": "integer", + "title": "Index", + "description": "镜头内角色排序", + "default": 0 + }, + "note": { + "type": "string", + "title": "Note", + "description": "备注", + "default": "" + } + }, + "type": "object", + "required": [ + "id", + "shot_id", + "character_id" + ], + "title": "ShotCharacterLinkRead" + }, + "ShotCreate": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "镜头 ID" + }, + "chapter_id": { + "type": "string", + "title": "Chapter Id", + "description": "所属章节 ID" + }, + "index": { + "type": "integer", + "title": "Index", + "description": "镜头序号(章节内唯一)" + }, + "title": { + "type": "string", + "title": "Title", + "description": "镜头标题" + }, + "thumbnail": { + "type": "string", + "title": "Thumbnail", + "description": "缩略图 URL/路径", + "default": "" + }, + "status": { + "$ref": "#/components/schemas/ShotStatus", + "description": "镜头状态", + "default": "pending" + }, + "skip_extraction": { + "type": "boolean", + "title": "Skip Extraction", + "description": "是否明确跳过信息提取", + "default": false + }, + "script_excerpt": { + "type": "string", + "title": "Script Excerpt", + "description": "剧本摘录", + "default": "" + }, + "generated_video_file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Generated Video File Id", + "description": "已生成视频关联的文件 ID(files.id,type=video)" + } + }, + "type": "object", + "required": [ + "id", + "chapter_id", + "index", + "title" + ], + "title": "ShotCreate" + }, + "ShotDetailCreate": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "镜头 ID(与 shots.id 共享主键)" + }, + "camera_shot": { + "$ref": "#/components/schemas/CameraShotType", + "description": "景别" + }, + "angle": { + "$ref": "#/components/schemas/CameraAngle", + "description": "机位角度" + }, + "movement": { + "$ref": "#/components/schemas/CameraMovement", + "description": "运镜方式" + }, + "scene_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Scene Id", + "description": "关联场景 ID(可空)" + }, + "duration": { + "type": "integer", + "title": "Duration", + "description": "时长(秒)", + "default": 0 + }, + "override_video_ratio": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Override Video Ratio", + "description": "分镜级视频比例覆盖;为空表示继承项目默认" + }, + "mood_tags": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Mood Tags", + "description": "情绪标签" + }, + "atmosphere": { + "type": "string", + "title": "Atmosphere", + "description": "氛围描述", + "default": "" + }, + "follow_atmosphere": { + "type": "boolean", + "title": "Follow Atmosphere", + "description": "是否沿用氛围", + "default": true + }, + "has_bgm": { + "type": "boolean", + "title": "Has Bgm", + "description": "是否包含 BGM", + "default": false + }, + "vfx_type": { + "$ref": "#/components/schemas/VFXType", + "description": "视效类型", + "default": "NONE" + }, + "vfx_note": { + "type": "string", + "title": "Vfx Note", + "description": "视效说明", + "default": "" + }, + "action_beats": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Action Beats", + "description": "动作拍点(按时间顺序排列)" + }, + "first_frame_prompt": { + "type": "string", + "title": "First Frame Prompt", + "description": "镜头分镜首帧提示词", + "default": "" + }, + "last_frame_prompt": { + "type": "string", + "title": "Last Frame Prompt", + "description": "镜头分镜尾帧提示词", + "default": "" + }, + "key_frame_prompt": { + "type": "string", + "title": "Key Frame Prompt", + "description": "镜头分镜关键帧提示词", + "default": "" + } + }, + "type": "object", + "required": [ + "id", + "camera_shot", + "angle", + "movement" + ], + "title": "ShotDetailCreate" + }, + "ShotDetailRead": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "镜头 ID(与 shots.id 共享主键)" + }, + "camera_shot": { + "$ref": "#/components/schemas/CameraShotType", + "description": "景别" + }, + "angle": { + "$ref": "#/components/schemas/CameraAngle", + "description": "机位角度" + }, + "movement": { + "$ref": "#/components/schemas/CameraMovement", + "description": "运镜方式" + }, + "scene_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Scene Id", + "description": "关联场景 ID(可空)" + }, + "duration": { + "type": "integer", + "title": "Duration", + "description": "时长(秒)", + "default": 0 + }, + "override_video_ratio": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Override Video Ratio", + "description": "分镜级视频比例覆盖;为空表示继承项目默认" + }, + "mood_tags": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Mood Tags", + "description": "情绪标签" + }, + "atmosphere": { + "type": "string", + "title": "Atmosphere", + "description": "氛围描述", + "default": "" + }, + "follow_atmosphere": { + "type": "boolean", + "title": "Follow Atmosphere", + "description": "是否沿用氛围", + "default": true + }, + "has_bgm": { + "type": "boolean", + "title": "Has Bgm", + "description": "是否包含 BGM", + "default": false + }, + "vfx_type": { + "$ref": "#/components/schemas/VFXType", + "description": "视效类型", + "default": "NONE" + }, + "vfx_note": { + "type": "string", + "title": "Vfx Note", + "description": "视效说明", + "default": "" + }, + "action_beats": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Action Beats", + "description": "动作拍点(按时间顺序排列)" + }, + "first_frame_prompt": { + "type": "string", + "title": "First Frame Prompt", + "description": "镜头分镜首帧提示词", + "default": "" + }, + "last_frame_prompt": { + "type": "string", + "title": "Last Frame Prompt", + "description": "镜头分镜尾帧提示词", + "default": "" + }, + "key_frame_prompt": { + "type": "string", + "title": "Key Frame Prompt", + "description": "镜头分镜关键帧提示词", + "default": "" + } + }, + "type": "object", + "required": [ + "id", + "camera_shot", + "angle", + "movement" + ], + "title": "ShotDetailRead" + }, + "ShotDetailUpdate": { + "properties": { + "camera_shot": { + "anyOf": [ + { + "$ref": "#/components/schemas/CameraShotType" + }, + { + "type": "null" + } + ] + }, + "angle": { + "anyOf": [ + { + "$ref": "#/components/schemas/CameraAngle" + }, + { + "type": "null" + } + ] + }, + "movement": { + "anyOf": [ + { + "$ref": "#/components/schemas/CameraMovement" + }, + { + "type": "null" + } + ] + }, + "scene_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Scene Id" + }, + "duration": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Duration" + }, + "override_video_ratio": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Override Video Ratio" + }, + "mood_tags": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Mood Tags" + }, + "atmosphere": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Atmosphere" + }, + "follow_atmosphere": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Follow Atmosphere" + }, + "has_bgm": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Has Bgm" + }, + "vfx_type": { + "anyOf": [ + { + "$ref": "#/components/schemas/VFXType" + }, + { + "type": "null" + } + ] + }, + "vfx_note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Vfx Note" + }, + "action_beats": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Action Beats" + }, + "first_frame_prompt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "First Frame Prompt" + }, + "last_frame_prompt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Last Frame Prompt" + }, + "key_frame_prompt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Key Frame Prompt" + } + }, + "type": "object", + "title": "ShotDetailUpdate" + }, + "ShotDialogLineCreate": { + "properties": { + "shot_detail_id": { + "type": "string", + "title": "Shot Detail Id" + }, + "index": { + "type": "integer", + "title": "Index", + "default": 0 + }, + "text": { + "type": "string", + "title": "Text" + }, + "line_mode": { + "$ref": "#/components/schemas/DialogueLineMode", + "default": "DIALOGUE" + }, + "speaker_character_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Speaker Character Id" + }, + "target_character_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Target Character Id" + }, + "speaker_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Speaker Name" + }, + "target_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Target Name" + } + }, + "type": "object", + "required": [ + "shot_detail_id", + "text" + ], + "title": "ShotDialogLineCreate" + }, + "ShotDialogLineRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id", + "description": "对话行 ID" + }, + "shot_detail_id": { + "type": "string", + "title": "Shot Detail Id", + "description": "所属镜头细节 ID" + }, + "index": { + "type": "integer", + "title": "Index", + "description": "行号(镜头内排序)", + "default": 0 + }, + "text": { + "type": "string", + "title": "Text", + "description": "台词内容" + }, + "line_mode": { + "$ref": "#/components/schemas/DialogueLineMode", + "description": "对白模式", + "default": "DIALOGUE" + }, + "speaker_character_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Speaker Character Id", + "description": "说话角色 ID" + }, + "target_character_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Target Character Id", + "description": "听者角色 ID" + }, + "speaker_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Speaker Name", + "description": "说话角色名称(用于回填关联;可空)" + }, + "target_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Target Name", + "description": "听者角色名称(用于回填关联;可空)" + } + }, + "type": "object", + "required": [ + "id", + "shot_detail_id", + "text" + ], + "title": "ShotDialogLineRead" + }, + "ShotDialogLineUpdate": { + "properties": { + "index": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Index" + }, + "text": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Text" + }, + "line_mode": { + "anyOf": [ + { + "$ref": "#/components/schemas/DialogueLineMode" + }, + { + "type": "null" + } + ] + }, + "speaker_character_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Speaker Character Id" + }, + "target_character_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Target Character Id" + }, + "speaker_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Speaker Name" + }, + "target_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Target Name" + } + }, + "type": "object", + "title": "ShotDialogLineUpdate" + }, + "ShotDialogueCandidateStatus": { + "type": "string", + "enum": [ + "pending", + "accepted", + "ignored" + ], + "title": "ShotDialogueCandidateStatus", + "description": "镜头对白提取候选确认状态。" + }, + "ShotDivision": { + "properties": { + "index": { + "type": "integer", + "minimum": 1.0, + "title": "Index", + "description": "镜头序号(章节内唯一)" + }, + "start_line": { + "type": "integer", + "minimum": 1.0, + "title": "Start Line", + "description": "起始行号(1-based)" + }, + "end_line": { + "type": "integer", + "minimum": 1.0, + "title": "End Line", + "description": "结束行号(1-based)" + }, + "script_excerpt": { + "type": "string", + "title": "Script Excerpt", + "description": "镜头对应的剧本摘录/文本" + }, + "shot_name": { + "type": "string", + "title": "Shot Name", + "description": "镜头名称(分镜名/镜头标题)", + "default": "" + }, + "time_of_day": { + "anyOf": [ + { + "type": "string", + "enum": [ + "DAY", + "NIGHT", + "DAWN", + "DUSK", + "UNKNOWN", + "日", + "夜", + "黎明", + "黄昏", + "不明", + "未知" + ] + }, + { + "type": "null" + } + ], + "title": "Time Of Day", + "description": "时间(日/夜/未知等,可选)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "index", + "start_line", + "end_line", + "script_excerpt" + ], + "title": "ShotDivision", + "description": "剧本分镜中的单镜信息:行号 + 预览文本(可选弱语义)。" + }, + "ShotExtractedCandidateLinkRequest": { + "properties": { + "linked_entity_id": { + "type": "string", + "title": "Linked Entity Id", + "description": "确认关联到的实体 ID" + } + }, + "type": "object", + "required": [ + "linked_entity_id" + ], + "title": "ShotExtractedCandidateLinkRequest" + }, + "ShotExtractedCandidateRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id", + "description": "候选项 ID" + }, + "shot_id": { + "type": "string", + "title": "Shot Id", + "description": "所属镜头 ID" + }, + "candidate_type": { + "$ref": "#/components/schemas/ShotCandidateType", + "description": "候选类型" + }, + "candidate_name": { + "type": "string", + "title": "Candidate Name", + "description": "提取出的候选名称" + }, + "candidate_status": { + "$ref": "#/components/schemas/ShotCandidateStatus", + "description": "候选确认状态" + }, + "linked_entity_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Linked Entity Id", + "description": "已关联实体 ID" + }, + "source": { + "type": "string", + "title": "Source", + "description": "候选来源" + }, + "payload": { + "additionalProperties": true, + "type": "object", + "title": "Payload", + "description": "候选附加信息" + }, + "confirmed_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Confirmed At", + "description": "确认时间" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At", + "description": "创建时间" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At", + "description": "更新时间" + } + }, + "type": "object", + "required": [ + "id", + "shot_id", + "candidate_type", + "candidate_name", + "candidate_status", + "source", + "created_at", + "updated_at" + ], + "title": "ShotExtractedCandidateRead" + }, + "ShotExtractedDialogueCandidateAcceptRequest": { + "properties": { + "index": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Index", + "description": "写入对白行时使用的排序;为空则使用候选排序" + }, + "text": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Text", + "description": "接受时可覆盖对白文本" + }, + "line_mode": { + "anyOf": [ + { + "$ref": "#/components/schemas/DialogueLineMode" + }, + { + "type": "null" + } + ], + "description": "接受时可覆盖对白模式" + }, + "speaker_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Speaker Name", + "description": "接受时可覆盖说话角色名称" + }, + "target_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Target Name", + "description": "接受时可覆盖听者角色名称" + } + }, + "type": "object", + "title": "ShotExtractedDialogueCandidateAcceptRequest" + }, + "ShotExtractedDialogueCandidateRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id", + "description": "对白候选项 ID" + }, + "shot_id": { + "type": "string", + "title": "Shot Id", + "description": "所属镜头 ID" + }, + "index": { + "type": "integer", + "title": "Index", + "description": "镜头内对白候选排序" + }, + "text": { + "type": "string", + "title": "Text", + "description": "提取出的对白文本" + }, + "line_mode": { + "$ref": "#/components/schemas/DialogueLineMode", + "description": "对白模式" + }, + "speaker_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Speaker Name", + "description": "说话角色名称" + }, + "target_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Target Name", + "description": "听者角色名称" + }, + "candidate_status": { + "$ref": "#/components/schemas/ShotDialogueCandidateStatus", + "description": "对白候选确认状态" + }, + "linked_dialog_line_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Linked Dialog Line Id", + "description": "已接受后关联的对白行 ID" + }, + "source": { + "type": "string", + "title": "Source", + "description": "候选来源" + }, + "payload": { + "additionalProperties": true, + "type": "object", + "title": "Payload", + "description": "候选附加信息" + }, + "confirmed_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Confirmed At", + "description": "确认时间" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At", + "description": "创建时间" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At", + "description": "更新时间" + } + }, + "type": "object", + "required": [ + "id", + "shot_id", + "index", + "text", + "line_mode", + "candidate_status", + "source", + "created_at", + "updated_at" + ], + "title": "ShotExtractedDialogueCandidateRead" + }, + "ShotExtractionSummaryRead": { + "properties": { + "state": { + "type": "string", + "enum": [ + "not_extracted", + "extracted_empty", + "extracted_pending", + "extracted_resolved", + "skipped" + ], + "title": "State", + "description": "镜头提取确认状态摘要" + }, + "has_extracted": { + "type": "boolean", + "title": "Has Extracted", + "description": "是否已执行过提取" + }, + "last_extracted_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Last Extracted At", + "description": "最近一次提取完成时间" + }, + "asset_candidate_total": { + "type": "integer", + "title": "Asset Candidate Total", + "description": "资产候选总数", + "default": 0 + }, + "dialogue_candidate_total": { + "type": "integer", + "title": "Dialogue Candidate Total", + "description": "对白候选总数", + "default": 0 + }, + "pending_asset_count": { + "type": "integer", + "title": "Pending Asset Count", + "description": "待确认资产候选数", + "default": 0 + }, + "pending_dialogue_count": { + "type": "integer", + "title": "Pending Dialogue Count", + "description": "待确认对白候选数", + "default": 0 + } + }, + "type": "object", + "required": [ + "state", + "has_extracted" + ], + "title": "ShotExtractionSummaryRead" + }, + "ShotFrameImageCreate": { + "properties": { + "shot_detail_id": { + "type": "string", + "title": "Shot Detail Id" + }, + "frame_type": { + "$ref": "#/components/schemas/ShotFrameType" + }, + "file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File Id" + }, + "width": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Width" + }, + "height": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Height" + }, + "format": { + "type": "string", + "title": "Format", + "default": "png" + } + }, + "type": "object", + "required": [ + "shot_detail_id", + "frame_type" + ], + "title": "ShotFrameImageCreate" + }, + "ShotFrameImageRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id", + "description": "图片行 ID" + }, + "shot_detail_id": { + "type": "string", + "title": "Shot Detail Id", + "description": "所属镜头细节 ID" + }, + "frame_type": { + "$ref": "#/components/schemas/ShotFrameType", + "description": "帧类型:first/last/key" + }, + "file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File Id", + "description": "关联的 FileItem ID(可为空,允许先创建占位)" + }, + "width": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Width", + "description": "宽(px)" + }, + "height": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Height", + "description": "高(px)" + }, + "format": { + "type": "string", + "title": "Format", + "description": "格式", + "default": "png" + } + }, + "type": "object", + "required": [ + "id", + "shot_detail_id", + "frame_type" + ], + "title": "ShotFrameImageRead" + }, + "ShotFrameImageTaskRequest": { + "properties": { + "model_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model Id", + "description": "可选模型 ID(models.id);不传则使用 ModelSettings.default_image_model_id;Provider 由模型关联反查" + }, + "frame_type": { + "$ref": "#/components/schemas/ShotFrameType", + "description": "first | last | key" + }, + "prompt": { + "type": "string", + "minLength": 1, + "title": "Prompt", + "description": "提示词(由前端传入,创建任务接口必填)。" + }, + "images": { + "items": { + "$ref": "#/components/schemas/ShotLinkedAssetItem" + }, + "type": "array", + "title": "Images", + "description": "参考资产条目列表(可多张,顺序有效)。后端会使用 item.file_id 作为参考图;无效条目会被跳过。" + }, + "target_ratio": { + "type": "string", + "enum": [ + "16:9", + "4:3", + "1:1", + "3:4", + "9:16", + "21:9", + "3:2", + "2:3" + ], + "title": "Target Ratio", + "description": "目标视频画幅比例;关键帧将按该画幅生成,以提升后续视频参考稳定性" + }, + "resolution_profile": { + "anyOf": [ + { + "type": "string", + "enum": [ + "standard", + "high" + ] + }, + { + "type": "null" + } + ], + "title": "Resolution Profile", + "description": "关键帧输出分辨率档位,默认 standard", + "default": "standard" + } + }, + "type": "object", + "required": [ + "frame_type", + "prompt", + "target_ratio" + ], + "title": "ShotFrameImageTaskRequest", + "description": "镜头分镜帧图片生成请求体:只根据 `shot_id + frame_type` 定位 ShotFrameImage。\n\n用于替代旧接口中通过 `image_id` 直接传入 ShotFrameImage.id 的方式。" + }, + "ShotFrameImageUpdate": { + "properties": { + "frame_type": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotFrameType" + }, + { + "type": "null" + } + ] + }, + "file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File Id" + }, + "width": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Width" + }, + "height": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Height" + }, + "format": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Format" + } + }, + "type": "object", + "title": "ShotFrameImageUpdate" + }, + "ShotFramePromptMappingRead": { + "properties": { + "token": { + "type": "string", + "title": "Token", + "description": "提示词中的图片占位 token,如 图1 / 图2" + }, + "type": { + "type": "string", + "enum": [ + "character", + "prop", + "scene", + "costume" + ], + "title": "Type", + "description": "实体类型:character/prop/scene/costume" + }, + "id": { + "type": "string", + "title": "Id", + "description": "实体 ID(如 character_id/prop_id/scene_id/costume_id)" + }, + "name": { + "type": "string", + "title": "Name", + "description": "实体名称" + }, + "file_id": { + "type": "string", + "title": "File Id", + "description": "本次渲染与生成使用的文件 ID" + } + }, + "type": "object", + "required": [ + "token", + "type", + "id", + "name", + "file_id" + ], + "title": "ShotFramePromptMappingRead", + "description": "关键帧提示词渲染后的图片映射关系。" + }, + "ShotFramePromptRenderRequest": { + "properties": { + "frame_type": { + "$ref": "#/components/schemas/ShotFrameType", + "description": "first | last | key" + }, + "prompt": { + "type": "string", + "minLength": 1, + "title": "Prompt", + "description": "原始基础提示词。渲染接口要求显式传入,用于生成最终提示词。" + }, + "images": { + "items": { + "$ref": "#/components/schemas/ShotLinkedAssetItem" + }, + "type": "array", + "title": "Images", + "description": "参考资产条目列表(可多张,顺序有效)。后端会使用 item.file_id 作为参考图;无效条目会被跳过。" + } + }, + "type": "object", + "required": [ + "frame_type", + "prompt" + ], + "title": "ShotFramePromptRenderRequest", + "description": "镜头分镜帧提示词渲染请求体。" + }, + "ShotFramePromptRequest": { + "properties": { + "shot_id": { + "type": "string", + "title": "Shot Id", + "description": "镜头 ID" + }, + "frame_type": { + "type": "string", + "title": "Frame Type", + "description": "first | last | key" + } + }, + "type": "object", + "required": [ + "shot_id", + "frame_type" + ], + "title": "ShotFramePromptRequest", + "description": "镜头分镜帧提示词生成任务请求。" + }, + "ShotFrameType": { + "type": "string", + "enum": [ + "first", + "last", + "key" + ], + "title": "ShotFrameType", + "description": "镜头分镜帧类型:首帧/尾帧/关键帧。" + }, + "ShotLinkedAssetItem": { + "properties": { + "type": { + "type": "string", + "enum": [ + "character", + "prop", + "scene", + "costume" + ], + "title": "Type", + "description": "实体类型:character/prop/scene/costume" + }, + "id": { + "type": "string", + "title": "Id", + "description": "实体 ID(如 character_id/prop_id/scene_id/costume_id)" + }, + "image_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Image Id", + "description": "最佳缩略图对应的 image 行 ID(如 PropImage.id);无图则为 null" + }, + "file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File Id", + "description": "最佳缩略图对应的文件 ID(files.id);用于参考图输入;无图则为 null" + }, + "name": { + "type": "string", + "title": "Name", + "description": "实体名称" + }, + "thumbnail": { + "type": "string", + "title": "Thumbnail", + "description": "缩略图下载地址(/api/v1/studio/files/{file_id}/download)", + "default": "" + } + }, + "type": "object", + "required": [ + "type", + "id", + "name" + ], + "title": "ShotLinkedAssetItem", + "description": "按分镜聚合返回的关联资产条目(角色/道具/场景/服装)。" + }, + "ShotPreparationLinkEntityType": { + "type": "string", + "enum": [ + "character", + "scene", + "prop", + "costume" + ], + "title": "ShotPreparationLinkEntityType" + }, + "ShotPreparationLinkRequest": { + "properties": { + "project_id": { + "type": "string", + "title": "Project Id", + "description": "项目 ID" + }, + "chapter_id": { + "type": "string", + "title": "Chapter Id", + "description": "章节 ID" + }, + "entity_type": { + "$ref": "#/components/schemas/ShotPreparationLinkEntityType", + "description": "准备页关联的实体类型" + }, + "linked_entity_id": { + "type": "string", + "title": "Linked Entity Id", + "description": "要关联的实体 ID" + } + }, + "type": "object", + "required": [ + "project_id", + "chapter_id", + "entity_type", + "linked_entity_id" + ], + "title": "ShotPreparationLinkRequest" + }, + "ShotPreparationMutationAction": { + "type": "string", + "enum": [ + "link_asset_candidate", + "ignore_asset_candidate", + "accept_dialogue_candidate", + "ignore_dialogue_candidate", + "skip_extraction", + "resume_extraction" + ], + "title": "ShotPreparationMutationAction" + }, + "ShotPreparationMutationResultRead": { + "properties": { + "action": { + "$ref": "#/components/schemas/ShotPreparationMutationAction", + "description": "本次执行的准备页动作" + }, + "state": { + "$ref": "#/components/schemas/ShotPreparationStateRead", + "description": "动作完成后的最新准备页聚合状态" + } + }, + "type": "object", + "required": [ + "action", + "state" + ], + "title": "ShotPreparationMutationResultRead", + "description": "准备页命令执行后的统一响应。" + }, + "ShotPreparationStateRead": { + "properties": { + "shot": { + "$ref": "#/components/schemas/ShotRead", + "description": "当前镜头最新状态" + }, + "assets_overview": { + "$ref": "#/components/schemas/ShotAssetsOverviewRead", + "description": "资产确认区聚合状态" + }, + "dialogue_candidates": { + "items": { + "$ref": "#/components/schemas/ShotExtractedDialogueCandidateRead" + }, + "type": "array", + "title": "Dialogue Candidates", + "description": "当前待处理/已存在的对白候选" + }, + "saved_dialogue_lines": { + "items": { + "$ref": "#/components/schemas/ShotDialogLineRead" + }, + "type": "array", + "title": "Saved Dialogue Lines", + "description": "当前已保存的对白行" + }, + "pending_confirm_count": { + "type": "integer", + "title": "Pending Confirm Count", + "description": "当前仍待确认的总数量(资产 + 对白)" + }, + "basic_info_ready": { + "type": "boolean", + "title": "Basic Info Ready", + "description": "标题与剧本摘录是否已补齐" + }, + "semantic_defaults_ready": { + "type": "boolean", + "title": "Semantic Defaults Ready", + "description": "镜头语言默认值是否已确认" + }, + "action_beats_ready": { + "type": "boolean", + "title": "Action Beats Ready", + "description": "动作拍点是否已确认" + }, + "action_beats_count": { + "type": "integer", + "title": "Action Beats Count", + "description": "当前已确认动作拍点数量", + "default": 0 + }, + "action_beat_phases": { + "items": { + "$ref": "#/components/schemas/ActionBeatPhaseRead" + }, + "type": "array", + "title": "Action Beat Phases", + "description": "当前动作拍点的阶段推断结果" + }, + "ready_for_generation": { + "type": "boolean", + "title": "Ready For Generation", + "description": "当前镜头是否已完成准备,可进入后续生成" + } + }, + "type": "object", + "required": [ + "shot", + "assets_overview", + "pending_confirm_count", + "basic_info_ready", + "semantic_defaults_ready", + "action_beats_ready", + "ready_for_generation" + ], + "title": "ShotPreparationStateRead", + "description": "分镜准备页聚合状态。" + }, + "ShotPromptAssetRef": { + "properties": { + "type": { + "type": "string", + "enum": [ + "character", + "prop", + "scene", + "costume" + ], + "title": "Type", + "description": "资产类型" + }, + "name": { + "type": "string", + "title": "Name", + "description": "资产名称" + }, + "description": { + "type": "string", + "title": "Description", + "description": "资产描述或提取候选描述", + "default": "" + }, + "file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File Id", + "description": "可作为参考图的文件 ID" + }, + "thumbnail": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Thumbnail", + "description": "缩略图" + } + }, + "type": "object", + "required": [ + "type", + "name" + ], + "title": "ShotPromptAssetRef", + "description": "用于提示词渲染的镜头资产引用。" + }, + "ShotPromptCameraInfo": { + "properties": { + "camera_shot": { + "type": "string", + "title": "Camera Shot", + "description": "景别", + "default": "" + }, + "angle": { + "type": "string", + "title": "Angle", + "description": "机位角度", + "default": "" + }, + "movement": { + "type": "string", + "title": "Movement", + "description": "运镜方式", + "default": "" + }, + "duration": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Duration", + "description": "镜头时长(秒)" + } + }, + "type": "object", + "title": "ShotPromptCameraInfo", + "description": "用于提示词渲染的镜头语言信息。" + }, + "ShotRead": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "镜头 ID" + }, + "chapter_id": { + "type": "string", + "title": "Chapter Id", + "description": "所属章节 ID" + }, + "index": { + "type": "integer", + "title": "Index", + "description": "镜头序号(章节内唯一)" + }, + "title": { + "type": "string", + "title": "Title", + "description": "镜头标题" + }, + "thumbnail": { + "type": "string", + "title": "Thumbnail", + "description": "缩略图 URL/路径", + "default": "" + }, + "status": { + "$ref": "#/components/schemas/ShotStatus", + "description": "镜头状态", + "default": "pending" + }, + "skip_extraction": { + "type": "boolean", + "title": "Skip Extraction", + "description": "是否明确跳过信息提取", + "default": false + }, + "script_excerpt": { + "type": "string", + "title": "Script Excerpt", + "description": "剧本摘录", + "default": "" + }, + "generated_video_file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Generated Video File Id", + "description": "已生成视频关联的文件 ID(files.id,type=video)" + }, + "last_extracted_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Last Extracted At", + "description": "最近一次完成信息提取的时间" + }, + "extraction": { + "$ref": "#/components/schemas/ShotExtractionSummaryRead", + "description": "镜头提取状态摘要" + } + }, + "type": "object", + "required": [ + "id", + "chapter_id", + "index", + "title", + "extraction" + ], + "title": "ShotRead" + }, + "ShotRuntimeSummaryRead": { + "properties": { + "shot_id": { + "type": "string", + "title": "Shot Id", + "description": "镜头 ID" + }, + "has_active_tasks": { + "type": "boolean", + "title": "Has Active Tasks", + "description": "是否存在进行中的关联任务" + }, + "has_active_video_tasks": { + "type": "boolean", + "title": "Has Active Video Tasks", + "description": "是否存在进行中的视频任务" + }, + "has_active_prompt_tasks": { + "type": "boolean", + "title": "Has Active Prompt Tasks", + "description": "是否存在进行中的提示词任务" + }, + "has_active_frame_tasks": { + "type": "boolean", + "title": "Has Active Frame Tasks", + "description": "是否存在进行中的分镜帧图片任务" + }, + "active_task_count": { + "type": "integer", + "title": "Active Task Count", + "description": "进行中的唯一任务数" + } + }, + "type": "object", + "required": [ + "shot_id", + "has_active_tasks", + "has_active_video_tasks", + "has_active_prompt_tasks", + "has_active_frame_tasks", + "active_task_count" + ], + "title": "ShotRuntimeSummaryRead" + }, + "ShotSemanticSuggestion": { + "properties": { + "camera_shot": { + "anyOf": [ + { + "$ref": "#/components/schemas/CameraShotType" + }, + { + "type": "null" + } + ], + "description": "建议景别" + }, + "angle": { + "anyOf": [ + { + "$ref": "#/components/schemas/CameraAngle" + }, + { + "type": "null" + } + ], + "description": "建议机位" + }, + "movement": { + "anyOf": [ + { + "$ref": "#/components/schemas/CameraMovement" + }, + { + "type": "null" + } + ], + "description": "建议运镜" + }, + "duration": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Duration", + "description": "建议时长(秒)" + }, + "action_beats": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Action Beats", + "description": "按时间顺序排列的动作拍点" + }, + "notes": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Notes", + "description": "不确定项说明" + } + }, + "additionalProperties": false, + "type": "object", + "title": "ShotSemanticSuggestion", + "description": "镜头语义默认建议:用于准备阶段初始化镜头语言与动作拍点。" + }, + "ShotSkipExtractionUpdate": { + "properties": { + "skip": { + "type": "boolean", + "title": "Skip", + "description": "是否明确跳过信息提取" + } + }, + "type": "object", + "required": [ + "skip" + ], + "title": "ShotSkipExtractionUpdate" + }, + "ShotStatus": { + "type": "string", + "enum": [ + "pending", + "generating", + "ready" + ], + "title": "ShotStatus", + "description": "镜头生成状态(更多是“生产流程”而非剧情状态)。" + }, + "ShotUpdate": { + "properties": { + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id" + }, + "index": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Index" + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Title" + }, + "thumbnail": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Thumbnail" + }, + "status": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotStatus" + }, + { + "type": "null" + } + ] + }, + "skip_extraction": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Skip Extraction" + }, + "script_excerpt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Script Excerpt" + }, + "generated_video_file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Generated Video File Id" + } + }, + "type": "object", + "title": "ShotUpdate" + }, + "ShotVideoPromptPackRead": { + "properties": { + "shot_id": { + "type": "string", + "title": "Shot Id", + "description": "镜头 ID" + }, + "title": { + "type": "string", + "title": "Title", + "description": "镜头标题", + "default": "" + }, + "script_excerpt": { + "type": "string", + "title": "Script Excerpt", + "description": "剧本摘录", + "default": "" + }, + "action_beats": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Action Beats", + "description": "动作/场景要点" + }, + "action_beat_phases": { + "items": { + "$ref": "#/components/schemas/ActionBeatPhaseRead" + }, + "type": "array", + "title": "Action Beat Phases", + "description": "动作拍点的阶段推断结果" + }, + "previous_shot_summary": { + "type": "string", + "title": "Previous Shot Summary", + "description": "上一镜头摘要,用于提示词连续性约束", + "default": "" + }, + "next_shot_goal": { + "type": "string", + "title": "Next Shot Goal", + "description": "下一镜头目标,用于提示词连续性约束", + "default": "" + }, + "continuity_guidance": { + "type": "string", + "title": "Continuity Guidance", + "description": "当前镜头与相邻镜头的承接建议", + "default": "" + }, + "composition_anchor": { + "type": "string", + "title": "Composition Anchor", + "description": "当前镜头的构图与空间锚点建议", + "default": "" + }, + "screen_direction_guidance": { + "type": "string", + "title": "Screen Direction Guidance", + "description": "当前镜头的人物朝向、视线与左右轴线建议", + "default": "" + }, + "dialogue_summary": { + "type": "string", + "title": "Dialogue Summary", + "description": "对白摘要", + "default": "" + }, + "characters": { + "items": { + "$ref": "#/components/schemas/ShotPromptAssetRef" + }, + "type": "array", + "title": "Characters", + "description": "角色引用" + }, + "scene": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotPromptAssetRef" + }, + { + "type": "null" + } + ], + "description": "场景引用" + }, + "props": { + "items": { + "$ref": "#/components/schemas/ShotPromptAssetRef" + }, + "type": "array", + "title": "Props", + "description": "道具引用" + }, + "costumes": { + "items": { + "$ref": "#/components/schemas/ShotPromptAssetRef" + }, + "type": "array", + "title": "Costumes", + "description": "服装引用" + }, + "camera": { + "$ref": "#/components/schemas/ShotPromptCameraInfo", + "description": "镜头语言" + }, + "atmosphere": { + "type": "string", + "title": "Atmosphere", + "description": "氛围描述", + "default": "" + }, + "visual_style": { + "type": "string", + "title": "Visual Style", + "description": "项目视觉风格", + "default": "" + }, + "style": { + "type": "string", + "title": "Style", + "description": "项目题材/风格", + "default": "" + }, + "negative_prompt": { + "type": "string", + "title": "Negative Prompt", + "description": "默认负面提示词", + "default": "" + } + }, + "type": "object", + "required": [ + "shot_id" + ], + "title": "ShotVideoPromptPackRead", + "description": "视频提示词渲染前的标准上下文包。" + }, + "ShotVideoPromptPreviewRead": { + "properties": { + "shot_id": { + "type": "string", + "title": "Shot Id", + "description": "镜头 ID" + }, + "template_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Template Id", + "description": "使用的提示词模板 ID" + }, + "template_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Template Name", + "description": "使用的提示词模板名称" + }, + "rendered_prompt": { + "type": "string", + "title": "Rendered Prompt", + "description": "渲染后的提示词" + }, + "pack": { + "$ref": "#/components/schemas/ShotVideoPromptPackRead", + "description": "渲染上下文包" + }, + "warnings": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Warnings", + "description": "渲染时发现的非阻塞提示" + } + }, + "type": "object", + "required": [ + "shot_id", + "rendered_prompt", + "pack" + ], + "title": "ShotVideoPromptPreviewRead", + "description": "视频提示词预览结果。" + }, + "ShotVideoReadinessCheck": { + "properties": { + "key": { + "type": "string", + "title": "Key", + "description": "检查项 key" + }, + "ok": { + "type": "boolean", + "title": "Ok", + "description": "是否通过" + }, + "message": { + "type": "string", + "title": "Message", + "description": "面向前端展示的说明" + } + }, + "type": "object", + "required": [ + "key", + "ok", + "message" + ], + "title": "ShotVideoReadinessCheck", + "description": "单项视频生成准备度检查结果。" + }, + "ShotVideoReadinessRead": { + "properties": { + "shot_id": { + "type": "string", + "title": "Shot Id", + "description": "镜头 ID" + }, + "reference_mode": { + "type": "string", + "title": "Reference Mode", + "description": "参考模式" + }, + "ready": { + "type": "boolean", + "title": "Ready", + "description": "是否满足当前 reference_mode 下的视频生成条件" + }, + "checks": { + "items": { + "$ref": "#/components/schemas/ShotVideoReadinessCheck" + }, + "type": "array", + "title": "Checks", + "description": "准备度检查项" + } + }, + "type": "object", + "required": [ + "shot_id", + "reference_mode", + "ready" + ], + "title": "ShotVideoReadinessRead", + "description": "镜头视频生成准备度。" + }, + "StudioAssetDraft": { + "properties": { + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Id", + "description": "资产 ID(已落库时回填,如 scene_id / prop_id / costume_id)" + }, + "file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File Id", + "description": "关联的文件 ID(可空)" + }, + "thumbnail": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Thumbnail", + "description": "缩略图下载地址(可空)" + }, + "name": { + "type": "string", + "title": "Name", + "description": "名称(同项目内建议唯一)" + }, + "description": { + "type": "string", + "title": "Description", + "description": "描述", + "default": "" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Tags", + "description": "标签" + }, + "prompt_template_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Prompt Template Id", + "description": "提示词模板 ID(可空)" + }, + "view_count": { + "type": "integer", + "minimum": 1.0, + "title": "View Count", + "description": "计划生成视角图数量", + "default": 1 + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name" + ], + "title": "StudioAssetDraft", + "description": "Studio 资产草稿(Scene/Prop/Costume)。\n\n导入 API 未传 id 时由服务端生成;分镜详情回填时可带 scene_id/prop_id/costume_id。" + }, + "StudioCharacterDraft": { + "properties": { + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Id", + "description": "角色 ID(已落库时回填 character_id)" + }, + "file_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File Id", + "description": "关联的文件 ID(可空)" + }, + "thumbnail": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Thumbnail", + "description": "缩略图下载地址(可空)" + }, + "index": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Index", + "description": "镜头内角色排序(shot_character_links.index)" + }, + "name": { + "type": "string", + "title": "Name", + "description": "角色名称(同项目内建议唯一)" + }, + "description": { + "type": "string", + "title": "Description", + "description": "角色描述", + "default": "" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Tags", + "description": "标签(可选)" + }, + "costume_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Costume Name", + "description": "服装名称(可选,导入时映射到 costume_id)" + }, + "prop_names": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Prop Names", + "description": "角色常用道具名称列表(可选)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name" + ], + "title": "StudioCharacterDraft", + "description": "Studio 角色草稿。\n\n导入 API 未传 id 时由服务端生成;分镜详情回填时可带 character_id。" + }, + "StudioImageTaskRequest": { + "properties": { + "model_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model Id", + "description": "可选模型 ID(models.id);不传则使用 ModelSettings.default_image_model_id;Provider 由模型关联反查" + }, + "image_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Image Id", + "description": "图片模型 ID,如 ActorImage.id / SceneImage.id / PropImage.id 等;必须与路径主体 ID 匹配" + }, + "prompt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Prompt", + "description": "提示词(由前端传入)。创建任务接口必填;render-prompt 接口可不传" + }, + "images": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Images", + "description": "参考图 file_id 列表(可多张,顺序有效)。创建任务接口会基于 file_id 从数据中解析为参考图" + } + }, + "type": "object", + "title": "StudioImageTaskRequest", + "description": "Studio 专用图片任务请求体:可选模型 ID,不传则用默认图片模型;供应商由模型反查。\n\nimage_id 表示具体的图片模型 ID,例如:\n- 演员图片:ActorImage.id\n- 场景图片:SceneImage.id\n- 道具图片:PropImage.id\n- 服装图片:CostumeImage.id\n- 角色图片:CharacterImage.id\n- 分镜帧图片:ShotFrameImage.id" + }, + "StudioScriptExtractionDraft": { + "properties": { + "project_id": { + "type": "string", + "title": "Project Id", + "description": "项目 ID(必填)" + }, + "chapter_id": { + "type": "string", + "title": "Chapter Id", + "description": "章节 ID(必填,用于创建 shots/links)" + }, + "script_text": { + "type": "string", + "title": "Script Text", + "description": "剧本文本(可为优化后版本)" + }, + "characters": { + "items": { + "$ref": "#/components/schemas/StudioCharacterDraft" + }, + "type": "array", + "title": "Characters" + }, + "scenes": { + "items": { + "$ref": "#/components/schemas/StudioAssetDraft" + }, + "type": "array", + "title": "Scenes" + }, + "props": { + "items": { + "$ref": "#/components/schemas/StudioAssetDraft" + }, + "type": "array", + "title": "Props" + }, + "costumes": { + "items": { + "$ref": "#/components/schemas/StudioAssetDraft" + }, + "type": "array", + "title": "Costumes" + }, + "shots": { + "items": { + "$ref": "#/components/schemas/StudioShotDraft" + }, + "type": "array", + "title": "Shots", + "description": "镜头草稿列表" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "project_id", + "chapter_id", + "script_text" + ], + "title": "StudioScriptExtractionDraft", + "description": "用于导入 Studio 的提取结果草稿(name-based)。" + }, + "StudioShotDraft": { + "properties": { + "index": { + "type": "integer", + "minimum": 1.0, + "title": "Index", + "description": "镜头序号(章节内唯一)" + }, + "title": { + "type": "string", + "title": "Title", + "description": "镜头标题" + }, + "script_excerpt": { + "type": "string", + "title": "Script Excerpt", + "description": "剧本摘录", + "default": "" + }, + "scene_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Scene Name", + "description": "场景名称(可选)" + }, + "character_names": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Character Names", + "description": "本镜出现角色名称列表" + }, + "prop_names": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Prop Names", + "description": "本镜关键道具名称列表" + }, + "costume_names": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Costume Names", + "description": "本镜服装名称列表" + }, + "dialogue_lines": { + "items": { + "$ref": "#/components/schemas/StudioShotDraftDialogueLine" + }, + "type": "array", + "title": "Dialogue Lines", + "description": "对白列表" + }, + "actions": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Actions", + "description": "动作/场景描述" + }, + "semantic_suggestion": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotSemanticSuggestion" + }, + { + "type": "null" + } + ], + "description": "镜头语言默认建议与动作拍点候选" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "index", + "title" + ], + "title": "StudioShotDraft", + "description": "镜头草稿:不含 shot_id,由导入 API 生成;引用实体用 name。" + }, + "StudioShotDraftDialogueLine": { + "properties": { + "index": { + "type": "integer", + "minimum": 0.0, + "title": "Index", + "description": "镜头内排序", + "default": 0 + }, + "text": { + "type": "string", + "title": "Text", + "description": "台词内容" + }, + "line_mode": { + "type": "string", + "enum": [ + "DIALOGUE", + "VOICE_OVER", + "OFF_SCREEN", + "PHONE" + ], + "title": "Line Mode", + "description": "对白模式", + "default": "DIALOGUE" + }, + "speaker_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Speaker Name", + "description": "说话角色名称(可空)" + }, + "target_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Target Name", + "description": "听者角色名称(可空)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "text" + ], + "title": "StudioShotDraftDialogueLine", + "description": "镜头对白草稿:speaker/target 使用角色 name,导入时映射为 character_id。" + }, + "StyleOption": { + "properties": { + "value": { + "type": "string", + "title": "Value", + "description": "选项值" + }, + "label": { + "type": "string", + "title": "Label", + "description": "选项展示文案" + } + }, + "type": "object", + "required": [ + "value", + "label" + ], + "title": "StyleOption", + "description": "通用下拉选项。" + }, + "TaskCancelRead": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id" + }, + "status": { + "$ref": "#/components/schemas/TaskStatus" + }, + "cancel_requested": { + "type": "boolean", + "title": "Cancel Requested", + "description": "是否已登记取消请求" + }, + "cancel_requested_at_ts": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Cancel Requested At Ts", + "description": "请求取消时间戳" + }, + "effective_immediately": { + "type": "boolean", + "title": "Effective Immediately", + "description": "是否已立即取消完成", + "default": false + } + }, + "type": "object", + "required": [ + "task_id", + "status", + "cancel_requested" + ], + "title": "TaskCancelRead" + }, + "TaskCancelRequest": { + "properties": { + "reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Reason", + "description": "取消原因(可选)" + } + }, + "type": "object", + "title": "TaskCancelRequest" + }, + "TaskCreated": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id", + "description": "任务 ID" + } + }, + "type": "object", + "required": [ + "task_id" + ], + "title": "TaskCreated" + }, + "TaskLinkAdoptRead": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id" + }, + "link_type": { + "type": "string", + "title": "Link Type", + "description": "project | chapter | shot" + }, + "entity_id": { + "type": "string", + "title": "Entity Id", + "description": "项目/章节/镜头 ID" + }, + "is_adopted": { + "type": "boolean", + "title": "Is Adopted", + "description": "是否采用(仅可正向变更为 true)" + } + }, + "type": "object", + "required": [ + "task_id", + "link_type", + "entity_id", + "is_adopted" + ], + "title": "TaskLinkAdoptRead", + "description": "采用状态更新结果。" + }, + "TaskLinkAdoptRequest": { + "properties": { + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id", + "description": "绑定项目 ID(可选)" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "绑定章节 ID(可选)" + }, + "shot_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Shot Id", + "description": "绑定镜头 ID(可选)" + }, + "task_id": { + "type": "string", + "title": "Task Id", + "description": "任务 ID" + } + }, + "type": "object", + "required": [ + "task_id" + ], + "title": "TaskLinkAdoptRequest", + "description": "更新采用状态请求:task_id + 三选一绑定对象(project_id/chapter_id/shot_id)。" + }, + "TaskListItemRead": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id" + }, + "task_kind": { + "type": "string", + "title": "Task Kind", + "description": "业务任务类型" + }, + "status": { + "$ref": "#/components/schemas/TaskStatus" + }, + "progress": { + "type": "integer", + "maximum": 100.0, + "minimum": 0.0, + "title": "Progress" + }, + "cancel_requested": { + "type": "boolean", + "title": "Cancel Requested", + "description": "是否已请求取消", + "default": false + }, + "cancel_requested_at_ts": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Cancel Requested At Ts", + "description": "请求取消时间戳" + }, + "started_at_ts": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Started At Ts", + "description": "任务开始执行时间戳" + }, + "finished_at_ts": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Finished At Ts", + "description": "任务结束时间戳" + }, + "elapsed_ms": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Elapsed Ms", + "description": "任务累计执行耗时(毫秒)" + }, + "created_at_ts": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Created At Ts", + "description": "任务创建时间戳" + }, + "updated_at_ts": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Updated At Ts", + "description": "任务更新时间戳" + }, + "executor_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Executor Type", + "description": "执行器类型,如 celery" + }, + "executor_task_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Executor Task Id", + "description": "执行器侧任务 ID" + }, + "relation_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Relation Type", + "description": "业务关联类型" + }, + "relation_entity_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Relation Entity Id", + "description": "业务关联实体 ID" + }, + "resource_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Resource Type", + "description": "资源类型" + }, + "navigate_relation_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Navigate Relation Type", + "description": "前端默认跳转关联类型" + }, + "navigate_relation_entity_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Navigate Relation Entity Id", + "description": "前端默认跳转关联实体 ID" + } + }, + "type": "object", + "required": [ + "task_id", + "task_kind", + "status", + "progress" + ], + "title": "TaskListItemRead" + }, + "TaskResultRead": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id" + }, + "status": { + "$ref": "#/components/schemas/TaskStatus" + }, + "progress": { + "type": "integer", + "maximum": 100.0, + "minimum": 0.0, + "title": "Progress" + }, + "result": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Result" + }, + "error": { + "type": "string", + "title": "Error", + "default": "" + }, + "cancel_requested": { + "type": "boolean", + "title": "Cancel Requested", + "description": "是否已请求取消", + "default": false + }, + "cancel_requested_at_ts": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Cancel Requested At Ts", + "description": "请求取消时间戳" + }, + "started_at_ts": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Started At Ts", + "description": "任务开始执行时间戳" + }, + "finished_at_ts": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Finished At Ts", + "description": "任务结束时间戳" + }, + "elapsed_ms": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Elapsed Ms", + "description": "任务累计执行耗时(毫秒)" + } + }, + "type": "object", + "required": [ + "task_id", + "status", + "progress" + ], + "title": "TaskResultRead" + }, + "TaskStatus": { + "type": "string", + "enum": [ + "pending", + "running", + "streaming", + "succeeded", + "failed", + "cancelled" + ], + "title": "TaskStatus", + "description": "任务状态枚举。" + }, + "TaskStatusRead": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id" + }, + "status": { + "$ref": "#/components/schemas/TaskStatus" + }, + "progress": { + "type": "integer", + "maximum": 100.0, + "minimum": 0.0, + "title": "Progress" + }, + "cancel_requested": { + "type": "boolean", + "title": "Cancel Requested", + "description": "是否已请求取消", + "default": false + }, + "cancel_requested_at_ts": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Cancel Requested At Ts", + "description": "请求取消时间戳" + }, + "started_at_ts": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Started At Ts", + "description": "任务开始执行时间戳" + }, + "finished_at_ts": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Finished At Ts", + "description": "任务结束时间戳" + }, + "elapsed_ms": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Elapsed Ms", + "description": "任务累计执行耗时(毫秒)" + }, + "error": { + "type": "string", + "title": "Error", + "description": "失败时的错误信息(成功/进行中通常为空)", + "default": "" + } + }, + "type": "object", + "required": [ + "task_id", + "status", + "progress" + ], + "title": "TaskStatusRead" + }, + "TimelineClipRead": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "片段 ID" + }, + "type": { + "$ref": "#/components/schemas/TimelineClipType", + "description": "片段类型:video / audio" + }, + "source_id": { + "type": "string", + "title": "Source Id", + "description": "来源素材 ID(逻辑引用)" + }, + "label": { + "type": "string", + "title": "Label", + "description": "轨道展示标签" + }, + "start": { + "type": "integer", + "title": "Start", + "description": "起始时间(秒)" + }, + "end": { + "type": "integer", + "title": "End", + "description": "结束时间(秒)" + }, + "track": { + "type": "integer", + "title": "Track", + "description": "轨道号" + } + }, + "type": "object", + "required": [ + "id", + "type", + "source_id", + "label", + "start", + "end", + "track" + ], + "title": "TimelineClipRead", + "description": "时间线片段只读模型。" + }, + "TimelineClipStatus": { + "type": "string", + "enum": [ + "ready", + "missing_video", + "file_missing" + ], + "title": "TimelineClipStatus", + "description": "片段成片解析状态(展示用)。" + }, + "TimelineClipType": { + "type": "string", + "enum": [ + "video", + "audio" + ], + "title": "TimelineClipType", + "description": "时间线片段类型(视频/音频)。" + }, + "VFXType": { + "type": "string", + "enum": [ + "NONE", + "PARTICLES", + "VOLUMETRIC_FOG", + "CG_DOUBLE", + "DIGITAL_ENVIRONMENT", + "MATTE_PAINTING", + "FIRE_SMOKE", + "WATER_SIM", + "DESTRUCTION", + "ENERGY_MAGIC", + "COMPOSITING_CLEANUP", + "SLOW_MOTION_TIME", + "OTHER" + ], + "title": "VFXType", + "description": "视效类型(与 `app.schemas.skills.common.VFXType` 对齐,存英文 code)。" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + }, + "input": { + "title": "Input" + }, + "ctx": { + "type": "object", + "title": "Context" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + }, + "VariantAnalysisRequest": { + "properties": { + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id", + "description": "项目 ID(异步任务关联可选)" + }, + "chapter_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Chapter Id", + "description": "章节 ID(异步任务关联可选)" + }, + "merged_library": { + "additionalProperties": true, + "type": "object", + "title": "Merged Library", + "description": "合并后的实体库(EntityLibrary 的序列化形式;来自 EntityMerger 输出的 merged_library)" + }, + "all_shot_extractions": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": "array", + "title": "All Shot Extractions", + "description": "所有镜头提取结果" + }, + "script_division": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Script Division", + "description": "脚本分镜结果(可选;ScriptDivisionResult 序列化),用于章节/段落分组" + } + }, + "type": "object", + "required": [ + "merged_library", + "all_shot_extractions" + ], + "title": "VariantAnalysisRequest", + "description": "变体分析请求。" + }, + "VariantAnalysisResult": { + "properties": { + "costume_timelines": { + "items": { + "$ref": "#/components/schemas/CostumeTimeline" + }, + "type": "array", + "title": "Costume Timelines", + "description": "各角色服装演变时间线" + }, + "variant_suggestions": { + "items": { + "$ref": "#/components/schemas/VariantSuggestion" + }, + "type": "array", + "title": "Variant Suggestions", + "description": "变体建议列表" + }, + "chapter_variants": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object", + "title": "Chapter Variants", + "description": "章节变体建议" + }, + "notes": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Notes", + "description": "分析说明" + } + }, + "additionalProperties": false, + "type": "object", + "title": "VariantAnalysisResult", + "description": "变体分析结果。" + }, + "VariantSuggestion": { + "properties": { + "entity_id": { + "type": "string", + "title": "Entity Id", + "description": "实体ID" + }, + "entity_name": { + "type": "string", + "title": "Entity Name", + "description": "实体名称" + }, + "entity_type": { + "type": "string", + "title": "Entity Type", + "description": "实体类型(character/scene/prop/location)" + }, + "suggestion": { + "type": "string", + "title": "Suggestion", + "description": "变体建议说明" + }, + "affected_shots": { + "items": { + "type": "integer" + }, + "type": "array", + "title": "Affected Shots", + "description": "涉及的镜头" + }, + "evidence": { + "items": { + "$ref": "#/components/schemas/EvidenceSpan" + }, + "type": "array", + "title": "Evidence", + "description": "原文依据(可选)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "entity_id", + "entity_name", + "entity_type", + "suggestion" + ], + "title": "VariantSuggestion", + "description": "变体建议。" + }, + "VideoGenerationOptionsRead": { + "properties": { + "provider": { + "type": "string", + "title": "Provider", + "description": "供应商稳定键" + }, + "model_id": { + "type": "string", + "title": "Model Id", + "description": "默认视频模型 ID" + }, + "model_name": { + "type": "string", + "title": "Model Name", + "description": "默认视频模型名称" + }, + "allowed_ratios": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Allowed Ratios", + "description": "当前模型允许的比例选项" + }, + "default_ratio": { + "type": "string", + "title": "Default Ratio", + "description": "当前模型默认比例" + } + }, + "type": "object", + "required": [ + "provider", + "model_id", + "model_name", + "default_ratio" + ], + "title": "VideoGenerationOptionsRead", + "description": "当前默认视频模型对应的生成参数选项。" + }, + "VideoGenerationTaskRequest": { + "properties": { + "shot_id": { + "type": "string", + "title": "Shot Id", + "description": "镜头 ID" + }, + "reference_mode": { + "type": "string", + "enum": [ + "first", + "last", + "key", + "first_last", + "first_last_key", + "text_only" + ], + "title": "Reference Mode", + "description": "参考模式:first | last | key | first_last | first_last_key | text_only" + }, + "prompt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Prompt", + "description": "视频提示词(text_only 必填)" + }, + "images": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Images", + "description": "参考图 file_id 列表,数量需与 reference_mode 严格匹配" + }, + "ratio": { + "type": "string", + "enum": [ + "16:9", + "4:3", + "1:1", + "3:4", + "9:16", + "21:9" + ], + "title": "Ratio", + "description": "视频画幅比例,如 16:9 / 9:16" + } + }, + "type": "object", + "required": [ + "shot_id", + "reference_mode", + "ratio" + ], + "title": "VideoGenerationTaskRequest", + "description": "视频生成任务请求。" + }, + "VideoPromptPreviewResponse": { + "properties": { + "prompt": { + "type": "string", + "title": "Prompt", + "description": "最终用于视频生成的提示词" + }, + "images": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Images", + "description": "关联参考图 file_id 列表" + }, + "pack": { + "anyOf": [ + { + "$ref": "#/components/schemas/ShotVideoPromptPackRead" + }, + { + "type": "null" + } + ], + "description": "视频提示词预览上下文包" + } + }, + "type": "object", + "required": [ + "prompt" + ], + "title": "VideoPromptPreviewResponse" + } + } + } +} \ No newline at end of file diff --git a/front/src/App.tsx b/front/src/App.tsx index 69bccf86..7bccdcac 100644 --- a/front/src/App.tsx +++ b/front/src/App.tsx @@ -1,5 +1,5 @@ import type React from 'react' -import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom' +import { BrowserRouter, Navigate, Route, Routes, useParams } from 'react-router-dom' import MainLayout from './layouts/MainLayout' import Settings from './pages/Settings' import NotFound from './pages/NotFound' @@ -22,6 +22,12 @@ import { ChapterShotsPage } from './pages/aiStudio/shots/ChapterShotsPage' import { ChapterShotEditPage } from './pages/aiStudio/shots/ChapterShotEditPage' import './App.css' +/** 兼容旧链接 `/projects/:projectId/chapters` → 工作台章节 Tab */ +function NavigateToWorkbenchChaptersTab() { + const { projectId } = useParams<{ projectId: string }>() + return +} + const App: React.FC = () => { return ( @@ -30,12 +36,14 @@ const App: React.FC = () => { } /> } /> } /> + } /> } /> } /> } /> } /> } /> } /> + } /> } /> } /> } /> diff --git a/front/src/layouts/MainLayout.tsx b/front/src/layouts/MainLayout.tsx index d17aa0e7..5fc0534e 100644 --- a/front/src/layouts/MainLayout.tsx +++ b/front/src/layouts/MainLayout.tsx @@ -57,6 +57,7 @@ const MainLayout: React.FC = () => { studio: '分镜工作室', prep: '章节编辑', shots: '分镜', + timeline: '章节剪辑', editor: '视频剪辑', edit: '编辑', } diff --git a/front/src/mocks/handlers.ts b/front/src/mocks/handlers.ts index ad8c0d7c..b579f2b8 100644 --- a/front/src/mocks/handlers.ts +++ b/front/src/mocks/handlers.ts @@ -18,6 +18,25 @@ import { // 可变的项目列表,支持创建/编辑/删除(会话内生效) let projectsList: Project[] = [...initialProjects] let chaptersList: Chapter[] = [...chapters] + +/** MSW:章节剪辑时间线内存状态(按 chapter_id) */ +let chapterTimelineMocks: Record< + string, + { + layout_version: number + segments: Array<{ + id: string + shot_id: string + position: number + clip_status: 'ready' | 'missing_video' | 'file_missing' + label?: string + file_id?: string | null + trim_start_ms?: number | null + trim_end_ms?: number | null + }> + preview_note?: string + } +> = {} let agentsList: Agent[] = [...initialAgents] let providersList: Provider[] = [...initialProviders] let modelsList: Model[] = [...llmModels] @@ -268,6 +287,57 @@ export const handlers = [ return ok(toChapterRead(newChapter), 200, 'Created') }), + http.get('/api/v1/studio/chapters/:chapter_id/timeline', ({ params }) => { + const { chapter_id } = params as { chapter_id: string } + const chapter = chaptersList.find((c) => c.id === chapter_id) + if (!chapter) return notFound('章节不存在') + const existing = chapterTimelineMocks[chapter_id] + const data = existing ?? { + layout_version: 1, + segments: [], + preview_note: 'Mock:连续预览未开启', + } + return ok(data) + }), + + http.put('/api/v1/studio/chapters/:chapter_id/timeline', async ({ params, request }) => { + const { chapter_id } = params as { chapter_id: string } + const chapter = chaptersList.find((c) => c.id === chapter_id) + if (!chapter) return notFound('章节不存在') + const body = (await request.json()) as { + layout_version?: number + segments: Array<{ shot_id: string; trim_start_ms?: number | null; trim_end_ms?: number | null }> + } + const prevVersion = chapterTimelineMocks[chapter_id]?.layout_version ?? 1 + if (body.layout_version != null && body.layout_version !== prevVersion) { + return HttpResponse.json({ code: 409, message: 'layout_version conflict', data: null }, { status: 409 }) + } + const nextVersion = prevVersion + 1 + const segments = body.segments.map((row, i) => ({ + id: row.shot_id ? `seg-${row.shot_id}` : '', + shot_id: row.shot_id, + position: i, + clip_status: 'ready' as const, + label: `镜头 ${row.shot_id}`, + file_id: null, + trim_start_ms: row.trim_start_ms ?? null, + trim_end_ms: row.trim_end_ms ?? null, + })) + chapterTimelineMocks[chapter_id] = { + layout_version: nextVersion, + segments, + preview_note: 'Mock:连续预览未开启', + } + return ok(chapterTimelineMocks[chapter_id]) + }), + + http.post('/api/v1/studio/chapters/:chapter_id/timeline/export', ({ params }) => { + const { chapter_id } = params as { chapter_id: string } + const chapter = chaptersList.find((c) => c.id === chapter_id) + if (!chapter) return notFound('章节不存在') + return ok({ task_id: `mock-timeline-export-${chapter_id}-${Date.now()}` }, 201, 'success') + }), + http.get('/api/v1/studio/chapters/:chapter_id', ({ params }) => { const { chapter_id } = params as { chapter_id: string } const chapter = chaptersList.find((c) => c.id === chapter_id) @@ -430,9 +500,18 @@ export const handlers = [ return HttpResponse.json(files, { status: 200 }) }), - // 某项目的时间线数据 - http.get('/api/projects/:projectId/timeline', () => { - return HttpResponse.json(timelineClips, { status: 200 }) + // 某项目的时间线数据(与 Studio OpenAPI 一致:统一响应包一层 data) + http.get('/api/v1/studio/projects/:project_id/timeline', () => { + const data = timelineClips.map((c) => ({ + id: c.id, + type: c.type, + source_id: c.sourceId, + label: c.label, + start: c.start, + end: c.end, + track: c.track, + })) + return HttpResponse.json({ code: 200, message: 'success', data, meta: null }, { status: 200 }) }), // Agent 列表 diff --git a/front/src/pages/aiStudio/assets/AssetManager.tsx b/front/src/pages/aiStudio/assets/AssetManager.tsx index 2198ff71..397b2573 100644 --- a/front/src/pages/aiStudio/assets/AssetManager.tsx +++ b/front/src/pages/aiStudio/assets/AssetManager.tsx @@ -5,6 +5,7 @@ import { ActorsTab } from './tabs/ActorsTab' import { ScenesTab } from './tabs/ScenesTab' import { PropsTab } from './tabs/PropsTab' import { CostumesTab } from './tabs/CostumesTab' +import { ScrollablePage } from '../components/ScrollablePage' const TAB_PARAM = 'tab' type AssetTabKey = 'actor' | 'scene' | 'prop' | 'costume' @@ -46,22 +47,24 @@ const AssetManager = () => { } return ( -
- - { - if (isValidTab(k)) setTabInUrl(k) - }} - items={[ - { key: 'actor', label: '演员', children: }, - { key: 'scene', label: '场景', children: }, - { key: 'prop', label: '道具', children: }, - { key: 'costume', label: '服装', children: }, - ]} - /> - -
+ +
+ + { + if (isValidTab(k)) setTabInUrl(k) + }} + items={[ + { key: 'actor', label: '演员', children: }, + { key: 'scene', label: '场景', children: }, + { key: 'prop', label: '道具', children: }, + { key: 'costume', label: '服装', children: }, + ]} + /> + +
+
) } diff --git a/front/src/pages/aiStudio/assets/assetAdapters.ts b/front/src/pages/aiStudio/assets/assetAdapters.ts index d40ef3e8..0b1ab2f8 100644 --- a/front/src/pages/aiStudio/assets/assetAdapters.ts +++ b/front/src/pages/aiStudio/assets/assetAdapters.ts @@ -1,5 +1,6 @@ import { StudioImageTasksService } from '../../../services/generated' import { StudioEntitiesApi } from '../../../services/studioEntities' +import { extractTaskIdFromApiEnvelope } from '../components/taskActionHelpers' import type { AssetEditPageBaseProps, BaseAsset, BaseAssetImage } from './components/AssetEditPageBase' type AdapterConfig = Omit< @@ -42,6 +43,9 @@ export const assetAdapters = { createImageSlot: async (id: string, angle) => { await StudioEntitiesApi.createImage('character', id, { view_angle: angle }) }, + deleteImageSlot: async (id: string, imageId: number) => { + await StudioEntitiesApi.deleteImage('character', id, imageId) + }, updateImage: async (id: string, imageId: number, payload) => { await StudioEntitiesApi.updateImage('character', id, imageId, normalizeUpdateImagePayload(payload)) }, @@ -61,7 +65,7 @@ export const assetAdapters = { characterId: id, requestBody: { image_id: imageId, model_id: null, prompt: payload.prompt, images: payload.images } as any, }) - return res.data?.task_id ?? null + return extractTaskIdFromApiEnvelope(res) }, } satisfies AdapterConfig, actor: { @@ -84,6 +88,9 @@ export const assetAdapters = { createImageSlot: async (id: string, angle) => { await StudioEntitiesApi.createImage('actor', id, { view_angle: angle }) }, + deleteImageSlot: async (id: string, imageId: number) => { + await StudioEntitiesApi.deleteImage('actor', id, imageId) + }, updateImage: async (id: string, imageId: number, payload) => { await StudioEntitiesApi.updateImage('actor', id, imageId, normalizeUpdateImagePayload(payload)) }, @@ -103,7 +110,7 @@ export const assetAdapters = { actorId: id, requestBody: { image_id: imageId, model_id: null, prompt: payload.prompt, images: payload.images } as any, }) - return res.data?.task_id ?? null + return extractTaskIdFromApiEnvelope(res) }, } satisfies AdapterConfig, scene: { @@ -126,6 +133,9 @@ export const assetAdapters = { createImageSlot: async (id: string, angle) => { await StudioEntitiesApi.createImage('scene', id, { view_angle: angle }) }, + deleteImageSlot: async (id: string, imageId: number) => { + await StudioEntitiesApi.deleteImage('scene', id, imageId) + }, updateImage: async (id: string, imageId: number, payload) => { await StudioEntitiesApi.updateImage('scene', id, imageId, normalizeUpdateImagePayload(payload)) }, @@ -147,7 +157,7 @@ export const assetAdapters = { assetId: id, requestBody: { image_id: imageId, prompt: payload.prompt, images: payload.images } as any, }) - return res.data?.task_id ?? null + return extractTaskIdFromApiEnvelope(res) }, } satisfies AdapterConfig, prop: { @@ -170,6 +180,9 @@ export const assetAdapters = { createImageSlot: async (id: string, angle) => { await StudioEntitiesApi.createImage('prop', id, { view_angle: angle }) }, + deleteImageSlot: async (id: string, imageId: number) => { + await StudioEntitiesApi.deleteImage('prop', id, imageId) + }, updateImage: async (id: string, imageId: number, payload) => { await StudioEntitiesApi.updateImage('prop', id, imageId, normalizeUpdateImagePayload(payload)) }, @@ -191,7 +204,7 @@ export const assetAdapters = { assetId: id, requestBody: { image_id: imageId, prompt: payload.prompt, images: payload.images } as any, }) - return res.data?.task_id ?? null + return extractTaskIdFromApiEnvelope(res) }, } satisfies AdapterConfig, costume: { @@ -214,6 +227,9 @@ export const assetAdapters = { createImageSlot: async (id: string, angle) => { await StudioEntitiesApi.createImage('costume', id, { view_angle: angle }) }, + deleteImageSlot: async (id: string, imageId: number) => { + await StudioEntitiesApi.deleteImage('costume', id, imageId) + }, updateImage: async (id: string, imageId: number, payload) => { await StudioEntitiesApi.updateImage('costume', id, imageId, normalizeUpdateImagePayload(payload)) }, @@ -235,7 +251,7 @@ export const assetAdapters = { assetId: id, requestBody: { image_id: imageId, prompt: payload.prompt, images: payload.images } as any, }) - return res.data?.task_id ?? null + return extractTaskIdFromApiEnvelope(res) }, } satisfies AdapterConfig, } diff --git a/front/src/pages/aiStudio/assets/components/AssetEditPageBase.tsx b/front/src/pages/aiStudio/assets/components/AssetEditPageBase.tsx index 14405e98..589842c8 100644 --- a/front/src/pages/aiStudio/assets/components/AssetEditPageBase.tsx +++ b/front/src/pages/aiStudio/assets/components/AssetEditPageBase.tsx @@ -24,7 +24,12 @@ import { buildFileDownloadUrl } from '../utils' import { DisplayImageCard } from './DisplayImageCard' import { ProjectVisualStyleAndStyleFields } from '../../project/ProjectVisualStyleAndStyleFields' import { useProjectStyleOptions } from '../../project/useProjectStyleOptions' -import { defaultTaskActionErrorMessage, executeAsyncTaskCreate, executeTaskCancel, notifyExistingTask } from '../../components/taskActionHelpers' +import { + defaultTaskActionErrorMessage, + executeAsyncTaskCreate, + executeTaskCancel, + notifyExistingTask, +} from '../../components/taskActionHelpers' import { handleTaskResultSafely } from '../../components/taskResultHelpers' import { useRelationTaskNotification } from '../../components/taskNotificationHelpers' import { useTaskPageContext } from '../../components/taskPageContext' @@ -102,6 +107,7 @@ export type AssetEditPageBaseProps Promise listImages: (assetId: string) => Promise createImageSlot: (assetId: string, angle: AssetViewAngle) => Promise + deleteImageSlot: (assetId: string, imageId: number) => Promise updateImage: (assetId: string, imageId: number, payload: { file_id: string; width?: number | null; height?: number | null; format?: string | null }) => Promise renderPrompt: (assetId: string, imageId: number) => Promise<{ prompt: string; images: string[] }> createGenerationTask: (assetId: string, imageId: number, payload: { prompt: string; images: string[] }) => Promise @@ -166,6 +172,7 @@ export function AssetEditPageBase { + submit: async ({ base, context, derived }) => { if (!assetId || !context.imageId) { throw new Error('asset image slot is required') } + const mergedPrompt = (base.prompt || '').trim() || (derived.prompt || '').trim() + const referenceImages = (derived.images || []).slice(0, 3) const taskId = await createGenerationTask(assetId, context.imageId, { - prompt: (derived.prompt || '').trim(), - images: derived.images, + prompt: mergedPrompt, + images: referenceImages, }) + if (!taskId) { + throw new Error('创建图片任务未返回任务 ID,请检查网关/代理是否剥离响应体或更新前端 OpenAPI 客户端') + } return { taskId } }, }) @@ -376,7 +388,7 @@ export function AssetEditPageBase clampViewCount(asset?.view_count), [asset?.view_count]) + const minViewCount = 1 const handleSaveBaseInfo = async () => { if (!assetId || !asset) return @@ -388,6 +400,12 @@ export function AssetEditPageBase img.view_angle && !nextAngles.has(img.view_angle)) + for (const stale of staleSlots) { + await deleteImageSlot(assetId, stale.id) + } const payload: AssetUpdate = { name: formName.trim(), description: formDesc.trim(), @@ -601,7 +619,7 @@ export function AssetEditPageBase ({ ...prev, [promptPreviewImage.id]: false })) } @@ -816,7 +839,7 @@ export function AssetEditPageBase setFormTags(e.target.value)} disabled={smartDetectBusy || savingBase} />
-
镜头数(仅可增加,最大 4)
+
镜头数(可增减,范围 1~4)
关联图片(参考图)
+
最多使用 3 张参考图,超出部分会自动截断。
{promptPreviewRefFileIds.length === 0 ? (
暂无关联图片
) : (
+ {promptPreviewRefFileIds.map((fid) => ( - +
+ + +
))}
diff --git a/front/src/pages/aiStudio/chapter/ChapterStudio.tsx b/front/src/pages/aiStudio/chapter/ChapterStudio.tsx index ef33ac6c..c70c323f 100644 --- a/front/src/pages/aiStudio/chapter/ChapterStudio.tsx +++ b/front/src/pages/aiStudio/chapter/ChapterStudio.tsx @@ -102,7 +102,11 @@ import type { import { listTaskLinksNormalized } from '../../../services/filmTaskLinks' import { buildFileDownloadUrl, resolveAssetUrl } from '../assets/utils' import type { Chapter } from '../../../mocks/data' -import { executeTaskCancel } from '../components/taskActionHelpers' +import { + defaultTaskActionErrorMessage, + executeTaskCancel, + extractTaskIdFromApiEnvelope, +} from '../components/taskActionHelpers' import { useRelationTaskNotification } from '../components/taskNotificationHelpers' import { TASK_COPY } from '../components/taskCopy' import { ChapterStudioBatchToolbar } from './components/ChapterStudioBatchToolbar' @@ -3026,6 +3030,7 @@ function Inspector(props: { const [keyframePromptPreviewLoading, setKeyframePromptPreviewLoading] = useState(false) const [keyframePromptActionLoading, setKeyframePromptActionLoading] = useState(false) const [keyframePromptPreviewFrameType, setKeyframePromptPreviewFrameType] = useState('key') + const [keyframePromptRefManualTouched, setKeyframePromptRefManualTouched] = useState(false) const [keyframePromptDebugContext, setKeyframePromptDebugContext] = useState(null) const [keyframePromptDebugCollapsed, setKeyframePromptDebugCollapsed] = useState(true) const [keyframeDirectiveCollapsed, setKeyframeDirectiveCollapsed] = useState(true) @@ -3966,9 +3971,51 @@ function Inspector(props: { const current = keyframePromptPreviewRefFileIds if (fromIndex < 0 || toIndex < 0 || fromIndex >= current.length || toIndex >= current.length) return const next = reorder(current, fromIndex, toIndex) + setKeyframePromptRefManualTouched(true) keyframePromptDraft.setContext({ refFileIds: next }) }, [keyframePromptDraft, keyframePromptPreviewRefFileIds]) + const allSelectableKeyframeRefFileIds = useMemo(() => { + const out: string[] = [] + const seen = new Set() + const push = (fid: string) => { + const normalized = String(fid || '').trim() + if (!normalized || seen.has(normalized)) return + seen.add(normalized) + out.push(normalized) + } + autoKeyframeRefFileIds.forEach(push) + keyframePromptPreviewRefFileIds.forEach(push) + return out + }, [autoKeyframeRefFileIds, keyframePromptPreviewRefFileIds]) + + const addKeyframePromptRefFile = useCallback((fid: string) => { + const normalized = String(fid || '').trim() + if (!normalized) return + if (keyframePromptPreviewRefFileIds.includes(normalized)) return + setKeyframePromptRefManualTouched(true) + keyframePromptDraft.replaceContext({ refFileIds: [...keyframePromptPreviewRefFileIds, normalized] }) + }, [keyframePromptDraft, keyframePromptPreviewRefFileIds]) + + const removeKeyframePromptRefFile = useCallback((fid: string) => { + const normalized = String(fid || '').trim() + if (!normalized) return + setKeyframePromptRefManualTouched(true) + keyframePromptDraft.replaceContext({ + refFileIds: keyframePromptPreviewRefFileIds.filter((item) => item !== normalized), + }) + }, [keyframePromptDraft, keyframePromptPreviewRefFileIds]) + + const resetKeyframePromptRefFiles = useCallback(() => { + setKeyframePromptRefManualTouched(false) + keyframePromptDraft.replaceContext({ refFileIds: autoKeyframeRefFileIds }) + }, [autoKeyframeRefFileIds, keyframePromptDraft]) + + const clearKeyframePromptRefFiles = useCallback(() => { + setKeyframePromptRefManualTouched(true) + keyframePromptDraft.replaceContext({ refFileIds: [] }) + }, [keyframePromptDraft]) + const loadProjectRoleOptions = async () => { if (!projectId) { setProjectRoleOptions([]) @@ -4300,7 +4347,7 @@ function Inspector(props: { const submitted = await videoPromptDraft.submitNow() const taskId = submitted?.taskId if (!taskId) { - message.error('视频生成任务创建失败:缺少任务 ID') + message.error('视频生成任务创建失败:服务端未返回任务 ID') return } setVideoTaskId(taskId) @@ -4314,8 +4361,8 @@ function Inspector(props: { }) setVideoSettledTask(null) setVideoPromptPreviewOpen(false) - } catch { - message.error('发起视频生成失败') + } catch (e) { + message.error(defaultTaskActionErrorMessage(e, '发起视频生成失败')) } finally { setVideoPromptPreviewSubmitting(false) } @@ -4427,6 +4474,7 @@ function Inspector(props: { setKeyframePromptPreviewLoading(true) setKeyframePromptPreviewOpen(true) setKeyframePromptPreviewFrameType(frameType) + setKeyframePromptRefManualTouched(false) setKeyframePromptDebugCollapsed(true) setKeyframeDirectiveCollapsed(true) setKeyframePromptDecisionCollapsed(true) @@ -4471,9 +4519,9 @@ function Inspector(props: { frame_type: frameType, }, }) - const taskId = created.data?.task_id + const taskId = extractTaskIdFromApiEnvelope(created) if (!taskId) { - message.error('生成任务创建失败:缺少任务 ID') + message.error('生成任务创建失败:服务端未返回任务 ID') return } setPromptTask({ @@ -4557,8 +4605,8 @@ function Inspector(props: { refFileIds: keyframePromptPreviewRefFileIds.length > 0 ? keyframePromptPreviewRefFileIds : autoKeyframeRefFileIds, }) message.success('提示词已生成') - } catch { - message.error('生成提示词失败') + } catch (e) { + message.error(defaultTaskActionErrorMessage(e, '生成提示词失败')) } finally { setKeyframePromptActionLoading(false) } @@ -4567,10 +4615,11 @@ function Inspector(props: { useEffect(() => { // 弹窗打开时,若当前没有参考图,则自动填充为分镜关联实体的参考图 if (!keyframePromptPreviewOpen) return + if (keyframePromptRefManualTouched) return if (keyframePromptPreviewRefFileIds.length > 0) return if (autoKeyframeRefFileIds.length === 0) return keyframePromptDraft.setContext({ refFileIds: autoKeyframeRefFileIds }) - }, [autoKeyframeRefFileIds, keyframePromptPreviewOpen, keyframePromptPreviewRefFileIds.length]) + }, [autoKeyframeRefFileIds, keyframePromptPreviewOpen, keyframePromptPreviewRefFileIds.length, keyframePromptRefManualTouched]) useEffect(() => { if (!keyframePromptPreviewOpen) return @@ -4619,7 +4668,7 @@ function Inspector(props: { const submitted = await keyframePromptDraft.submitNow() const taskId = submitted?.taskId if (!taskId) { - message.error('生成任务创建失败:缺少任务 ID') + message.error('生成任务创建失败:服务端未返回任务 ID') updateCardState(frameType, { loading: false, taskStatus: 'failed' }) return } @@ -4660,12 +4709,19 @@ function Inspector(props: { const latestSlotId = await getLatestFrameSlotId(frameType) await loadCardThumbs(frameType, latestSlotId, 5) setKeyframePromptPreviewOpen(false) - } else if (finalStatus !== 'failed' && finalStatus !== 'cancelled') { + } else if (finalStatus === 'failed') { + const err = finalTaskState?.error?.trim() + message.error( + err ? `${frameLabel[frameType]}生成失败:${err}` : `${frameLabel[frameType]}生成失败,请查看任务中心或稍后重试`, + ) + } else if (finalStatus === 'cancelled') { + message.warning(`${frameLabel[frameType]}生成已取消`) + } else { message.warning('生成任务仍在执行,请稍后刷新') } - } catch { + } catch (e) { updateCardState(frameType, { taskStatus: 'failed' }) - message.error(`${frameLabel[frameType]}生成失败`) + message.error(defaultTaskActionErrorMessage(e, `${frameLabel[frameType]}生成失败`)) } finally { updateCardState(frameType, { loading: false }) setKeyframePromptActionLoading(false) @@ -5528,23 +5584,32 @@ function Inspector(props: { {generatedVideos.length === 0 ? (
当前分镜暂无已生成视频
) : ( -
+
{generatedVideos.map((item, idx) => ( -
-
))}
)} + {allSelectableKeyframeRefFileIds.filter((fid) => !keyframePromptPreviewRefFileIds.includes(fid)).length > 0 ? ( +
+
可选参考图(点击添加)
+
+ {allSelectableKeyframeRefFileIds + .filter((fid) => !keyframePromptPreviewRefFileIds.includes(fid)) + .map((fid) => ( +
+ + + +
+ {shotLinkedAssetNameByFileId.get(fid) ?? fid} +
+ +
+ ))} +
+
+ ) : null}
diff --git a/front/src/pages/aiStudio/components/ScrollablePage.tsx b/front/src/pages/aiStudio/components/ScrollablePage.tsx new file mode 100644 index 00000000..ded6fb78 --- /dev/null +++ b/front/src/pages/aiStudio/components/ScrollablePage.tsx @@ -0,0 +1,18 @@ +import type { ReactNode } from 'react' + +/** + * 提供统一的页面级滚动容器,避免父级布局锁定 `overflow: hidden` 时内容被裁切。 + */ +export function ScrollablePage({ + children, + className = '', +}: { + children: ReactNode + className?: string +}) { + return ( +
+ {children} +
+ ) +} diff --git a/front/src/pages/aiStudio/components/taskActionHelpers.ts b/front/src/pages/aiStudio/components/taskActionHelpers.ts index b6db01ec..076ebdf3 100644 --- a/front/src/pages/aiStudio/components/taskActionHelpers.ts +++ b/front/src/pages/aiStudio/components/taskActionHelpers.ts @@ -1,5 +1,5 @@ import { message } from 'antd' -import { FilmService } from '../../../services/generated' +import { ApiError, FilmService } from '../../../services/generated' import type { TaskStatus } from '../../../services/generated' import type { RelationTaskState } from '../project/ProjectWorkbench/chapterDivisionTasks' @@ -29,6 +29,10 @@ type ExecuteAsyncTaskCreateOptions = { export function defaultTaskActionErrorMessage(error: unknown, fallbackErrorMessage: string): string { if (!error) return fallbackErrorMessage + if (error instanceof ApiError && error.body && typeof error.body === 'object') { + const msg = (error.body as { message?: unknown }).message + if (typeof msg === 'string' && msg.trim()) return msg.trim() + } if (typeof error === 'string' && error.trim()) return error if (typeof error === 'object') { const maybeAny = error as { @@ -127,3 +131,20 @@ export function notifyExistingTask( message.info(task.cancelRequested ? options.cancellingMessage : options.runningMessage) return true } + +/** + * 从统一 ApiResponse 信封或扁平结构中读取 task_id(兼容代理剥离 Content-Type、字段别名等)。 + */ +export function extractTaskIdFromApiEnvelope(body: unknown): string | null { + if (!body || typeof body !== 'object') return null + const o = body as Record + const nested = o.data + if (nested && typeof nested === 'object') { + const d = nested as Record + const id = d.task_id ?? d.taskId + if (typeof id === 'string' && id.trim().length > 0) return id.trim() + } + const flat = o.task_id ?? o.taskId + if (typeof flat === 'string' && flat.trim().length > 0) return flat.trim() + return null +} diff --git a/front/src/pages/aiStudio/components/taskNotificationHelpers.tsx b/front/src/pages/aiStudio/components/taskNotificationHelpers.tsx index 4490ceaf..2a9a1368 100644 --- a/front/src/pages/aiStudio/components/taskNotificationHelpers.tsx +++ b/front/src/pages/aiStudio/components/taskNotificationHelpers.tsx @@ -145,7 +145,7 @@ export function useRelationTaskNotification({ useEffect(() => { if (!settledTask) return - const settledKey = `${settledTask.taskId}:${settledTask.status}:${settledTask.finishedAtTs ?? ''}` + const settledKey = `${settledTask.taskId}:${settledTask.status}:${settledTask.finishedAtTs ?? ''}:${settledTask.error ?? ''}` if (previousSettledKeyRef.current === settledKey) return previousSettledKeyRef.current = settledKey @@ -157,12 +157,17 @@ export function useRelationTaskNotification({ startedAtLabel ? `开始于 ${startedAtLabel}` : null, ].filter(Boolean) + const failedMainDescription = + settledTask.status === 'failed' && settledTask.error?.trim() + ? settledTask.error.trim() + : failedDescription || '任务执行失败,请稍后重试。' + const statusMeta = settledTask.status === 'succeeded' ? { message: `${title}已完成`, description: successDescription || '任务已执行完成。', duration: 3.5 as number } : settledTask.status === 'cancelled' ? { message: `${title}已取消`, description: cancelledDescription || '任务已停止执行。', duration: 4.5 as number } - : { message: `${title}失败`, description: failedDescription || '任务执行失败,请稍后重试。', duration: 6 as number } + : { message: `${title}失败`, description: failedMainDescription, duration: 8 as number } upsertTask({ taskId: settledTask.taskId, diff --git a/front/src/pages/aiStudio/editor/VideoEditor.tsx b/front/src/pages/aiStudio/editor/VideoEditor.tsx index 068d2258..1ff81ab1 100644 --- a/front/src/pages/aiStudio/editor/VideoEditor.tsx +++ b/front/src/pages/aiStudio/editor/VideoEditor.tsx @@ -1,124 +1,740 @@ -import React, { useEffect, useState } from 'react' -import { Card, Layout, Button, message } from 'antd' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Alert, Button, Checkbox, Empty, Layout, Slider, Space, Spin, Switch, Tag, Typography, message } from 'antd' import { - ScissorOutlined, - PlusOutlined, - ExportOutlined, - PlayCircleOutlined, ArrowLeftOutlined, + CaretRightOutlined, + ExportOutlined, + PauseOutlined, + SaveOutlined, + StepBackwardOutlined, + StepForwardOutlined, } from '@ant-design/icons' -import { useParams, Link } from 'react-router-dom' -import api from '../../../services/aiStudioApi' -import type { TimelineClip } from '../../../mocks/data' +import { Link, Navigate, useParams } from 'react-router-dom' +import { ApiError } from '../../../services/generated' +import { StudioChaptersService } from '../../../services/generated' +import type { ChapterTimelineRead } from '../../../services/generated/models/ChapterTimelineRead' +import type { ChapterTimelineSegmentRead } from '../../../services/generated/models/ChapterTimelineSegmentRead' +import { buildFileDownloadUrl } from '../assets/utils' +import { getProjectChaptersPath } from '../project/ProjectWorkbench/routes' const { Content } = Layout +const { Text, Title } = Typography + +const DEFAULT_ZOOM_PX_PER_SEC = 100 +const MIN_CLIP_MS = 100 +const HANDLE_WIDTH = 18 +const EDITOR_MIN_HEIGHT = 1080 + +type TrimDragState = { + index: number + edge: 'start' | 'end' + startClientX: number + sourceDurationMs: number + initialStartMs: number + initialEndMs: number +} + +type TimelineClipModel = { + seg: ChapterTimelineSegmentRead + idx: number + sourceDurationMs: number + effectiveStartMs: number + effectiveEndMs: number + effectiveDurationMs: number + offsetMs: number +} + +function clipStatusTag(status: string | undefined) { + const v = status ?? '' + if (v === 'ready') return ready + if (v === 'missing_video') return missing_video + if (v === 'file_missing') return file_missing + return {v || 'unknown'} +} +/** 与后端一致:左闭右开 [start, end);缺省出点表示直到片尾。 */ +function playbackWindowMs(seg: ChapterTimelineSegmentRead, sourceDurationMs: number): { startMs: number; endMs: number } { + const safeDuration = Math.max(sourceDurationMs, MIN_CLIP_MS) + const startMs = Math.max(0, seg.trim_start_ms ?? 0) + const endRaw = seg.trim_end_ms ?? safeDuration + const endMs = Math.max(startMs + MIN_CLIP_MS, Math.min(endRaw, safeDuration)) + return { startMs, endMs } +} + +function formatMs(ms: number): string { + const totalSeconds = Math.max(0, Math.floor(ms / 1000)) + const minutes = Math.floor(totalSeconds / 60) + const seconds = totalSeconds % 60 + return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}` +} + +function buildTimelineModels( + segments: ChapterTimelineSegmentRead[], + durationMsByShot: Record, +): TimelineClipModel[] { + let offsetMs = 0 + return segments.map((seg, idx) => { + const sourceDurationMs = Math.max(durationMsByShot[seg.shot_id] ?? 5000, MIN_CLIP_MS) + const window = playbackWindowMs(seg, sourceDurationMs) + const model: TimelineClipModel = { + seg, + idx, + sourceDurationMs, + effectiveStartMs: window.startMs, + effectiveEndMs: window.endMs, + effectiveDurationMs: Math.max(window.endMs - window.startMs, MIN_CLIP_MS), + offsetMs, + } + offsetMs += model.effectiveDurationMs + return model + }) +} + +/** 单文件实现剪映式章节剪辑器:上方预览区 + 下方时间线轨道。 */ const VideoEditor: React.FC = () => { - const { projectId } = useParams<{ projectId: string }>() - const [clips, setClips] = useState([]) + const { projectId, chapterId } = useParams<{ projectId: string; chapterId?: string }>() + const [timeline, setTimeline] = useState(null) + const [segments, setSegments] = useState([]) const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) + const [exporting, setExporting] = useState(false) + const [losslessOnly, setLosslessOnly] = useState(false) + const [activeIdx, setActiveIdx] = useState(0) + const [autoChainPlay, setAutoChainPlay] = useState(true) + const [playing, setPlaying] = useState(false) + const [zoomPxPerSec, setZoomPxPerSec] = useState(DEFAULT_ZOOM_PX_PER_SEC) + const [durationMsByShot, setDurationMsByShot] = useState>({}) + const [previewTimelineMs, setPreviewTimelineMs] = useState(0) + const [trimDrag, setTrimDrag] = useState(null) + const videoRef = useRef(null) + const activeIdxRef = useRef(0) + const draggingIndexRef = useRef(null) + const probingShotsRef = useRef>(new Set()) + const shouldAutoResumeRef = useRef(false) + const timelineViewportRef = useRef(null) + + activeIdxRef.current = activeIdx - const load = async () => { - if (!projectId) return + const load = useCallback(async () => { + if (!chapterId) return setLoading(true) try { - const list = await api.timeline.get(projectId) - setClips(Array.isArray(list) ? list : []) + const res = await StudioChaptersService.getChapterTimelineApiV1StudioChaptersChapterIdTimelineGet({ chapterId }) + const data = res.data + setTimeline(data ?? null) + setSegments([...(data?.segments ?? [])]) + setActiveIdx(0) } catch { - message.error('加载时间线失败') + message.error('加载章节时间线失败') } finally { setLoading(false) } - } + }, [chapterId]) useEffect(() => { void load() - }, [projectId]) + }, [load]) - return ( -
-
- - {projectId ? '返回章节列表' : '项目列表'} - -
- }> - 导出成片 - + useEffect(() => { + if (activeIdx >= segments.length && segments.length > 0) setActiveIdx(segments.length - 1) + if (segments.length === 0) setActiveIdx(0) + }, [segments, activeIdx]) + + if (!projectId) return + if (!chapterId) return + + const patchSegment = useCallback( + (shotId: string, patch: Partial>) => { + setSegments((prev) => prev.map((s) => (s.shot_id === shotId ? { ...s, ...patch } : s))) + }, + [], + ) + + const timelineModels = useMemo(() => buildTimelineModels(segments, durationMsByShot), [durationMsByShot, segments]) + const activeClip = timelineModels[activeIdx] + const totalTimelineMs = timelineModels.length + ? timelineModels[timelineModels.length - 1]!.offsetMs + timelineModels[timelineModels.length - 1]!.effectiveDurationMs + : 0 + const pxPerMs = zoomPxPerSec / 1000 + const playheadPx = previewTimelineMs * pxPerMs + const activeVideoUrl = + activeClip?.seg.clip_status === 'ready' && activeClip.seg.file_id ? buildFileDownloadUrl(activeClip.seg.file_id) : undefined + + const findNextReadyIndex = useCallback( + (from: number): number => { + for (let i = from + 1; i < segments.length; i += 1) { + if (segments[i]?.clip_status === 'ready') return i + } + return -1 + }, + [segments], + ) + + const reorderSegments = useCallback( + (from: number, to: number) => { + if (from === to || from < 0 || to < 0 || from >= segments.length || to >= segments.length) return + const next = [...segments] + const [item] = next.splice(from, 1) + if (!item) return + next.splice(to, 0, item) + setSegments(next) + if (activeIdx === from) setActiveIdx(to) + else if (from < activeIdx && to >= activeIdx) setActiveIdx((v) => v - 1) + else if (from > activeIdx && to <= activeIdx) setActiveIdx((v) => v + 1) + }, + [activeIdx, segments], + ) + + useEffect(() => { + const toProbe = segments.filter( + (seg) => + seg.clip_status === 'ready' && + seg.file_id && + durationMsByShot[seg.shot_id] == null && + !probingShotsRef.current.has(seg.shot_id), + ) + if (!toProbe.length) return + toProbe.forEach((seg) => { + if (!seg.file_id) return + const url = buildFileDownloadUrl(seg.file_id) + if (!url) return + probingShotsRef.current.add(seg.shot_id) + const v = document.createElement('video') + v.preload = 'metadata' + v.onloadedmetadata = () => { + const durMs = Math.max(MIN_CLIP_MS, Math.round(v.duration * 1000)) + setDurationMsByShot((prev) => ({ ...prev, [seg.shot_id]: durMs })) + probingShotsRef.current.delete(seg.shot_id) + } + v.onerror = () => { + probingShotsRef.current.delete(seg.shot_id) + } + v.src = url + }) + }, [durationMsByShot, segments]) + + useEffect(() => { + if (!trimDrag) return + const handleMove = (event: MouseEvent) => { + const deltaMs = Math.round(((event.clientX - trimDrag.startClientX) / pxPerMs) / 100) * 100 + const current = timelineModels[trimDrag.index] + if (!current) return + if (trimDrag.edge === 'start') { + const nextStart = Math.max(0, Math.min(trimDrag.initialStartMs + deltaMs, trimDrag.initialEndMs - MIN_CLIP_MS)) + patchSegment(current.seg.shot_id, { + trim_start_ms: nextStart <= 0 ? undefined : nextStart, + }) + return + } + const nextEnd = Math.max( + trimDrag.initialStartMs + MIN_CLIP_MS, + Math.min(trimDrag.initialEndMs + deltaMs, trimDrag.sourceDurationMs), + ) + patchSegment(current.seg.shot_id, { + trim_end_ms: nextEnd >= trimDrag.sourceDurationMs - 2 ? undefined : nextEnd, + }) + } + const handleUp = () => setTrimDrag(null) + window.addEventListener('mousemove', handleMove) + window.addEventListener('mouseup', handleUp) + return () => { + window.removeEventListener('mousemove', handleMove) + window.removeEventListener('mouseup', handleUp) + } + }, [patchSegment, pxPerMs, timelineModels, trimDrag]) + + useEffect(() => { + if (!activeClip) { + setPreviewTimelineMs(0) + return + } + setPreviewTimelineMs(activeClip.offsetMs) + const viewport = timelineViewportRef.current + if (!viewport) return + const clipLeft = activeClip.offsetMs * pxPerMs + const clipRight = clipLeft + activeClip.effectiveDurationMs * pxPerMs + const viewLeft = viewport.scrollLeft + const viewRight = viewLeft + viewport.clientWidth + if (clipLeft < viewLeft || clipRight > viewRight) { + viewport.scrollTo({ + left: Math.max(0, clipLeft - viewport.clientWidth * 0.25), + behavior: 'smooth', + }) + } + }, [activeClip, pxPerMs]) + + const save = async () => { + setSaving(true) + try { + const res = await StudioChaptersService.putChapterTimelineApiV1StudioChaptersChapterIdTimelinePut({ + chapterId, + requestBody: { + layout_version: timeline?.layout_version ?? undefined, + segments: segments.map((s) => ({ + shot_id: s.shot_id, + trim_start_ms: s.trim_start_ms ?? undefined, + trim_end_ms: s.trim_end_ms ?? undefined, + })), + }, + }) + const data = res.data + setTimeline(data ?? null) + setSegments([...(data?.segments ?? [])]) + message.success('已保存时间线') + } catch (err) { + if (err instanceof ApiError && err.status === 409) { + message.warning('版本冲突,请刷新后重试') + await load() + } else { + message.error(err instanceof Error ? err.message : '保存失败') + } + } finally { + setSaving(false) + } + } + + const exportMaster = async () => { + setExporting(true) + try { + const res = await StudioChaptersService.postChapterTimelineExportApiV1StudioChaptersChapterIdTimelineExportPost({ + chapterId, + requestBody: { + encode_mode: losslessOnly ? 'lossless_concat_only' : 'uniform_transcode', + }, + }) + const tid = res.data?.task_id + message.success(tid ? `已创建导出任务:${tid}` : '已创建导出任务') + } catch (err) { + if (err instanceof ApiError) { + if (err.status === 400) { + const body = err.body as { message?: string; detail?: string } | undefined + message.error(body?.message ?? body?.detail ?? '导出条件不满足') + return } - > -
- - - -
+ if (err.status === 409) { + message.warning('已有导出任务进行中') + return + } + } + message.error('导出请求失败') + } finally { + setExporting(false) + } + } - -
-
素材库
-
- {clips.map((c) => ( -
- {c.label} -
- ))} - {clips.length === 0 && !loading && ( - 暂无素材,可从文件管理导入 - )} -
+ const jumpToClipStart = () => { + const v = videoRef.current + if (!v || !activeClip) return + v.currentTime = activeClip.effectiveStartMs / 1000 + setPreviewTimelineMs(activeClip.offsetMs) + } + + const handleLoadedMetadata = () => { + const v = videoRef.current + if (!v || !activeClip) return + const durMs = Math.max(MIN_CLIP_MS, Math.round(v.duration * 1000)) + setDurationMsByShot((prev) => ({ ...prev, [activeClip.seg.shot_id]: durMs })) + v.currentTime = activeClip.effectiveStartMs / 1000 + setPreviewTimelineMs(activeClip.offsetMs) + if (shouldAutoResumeRef.current || playing) { + shouldAutoResumeRef.current = false + void v.play().catch(() => { + setPlaying(false) + message.warning('自动播放失败,请手动点击播放') + }) + } + } + + const handleTimeUpdate = () => { + const v = videoRef.current + const current = timelineModels[activeIdxRef.current] + if (!v || !current || current.seg.clip_status !== 'ready') return + const elapsedWithinClipMs = Math.max(0, Math.round(v.currentTime * 1000) - current.effectiveStartMs) + setPreviewTimelineMs(current.offsetMs + Math.min(elapsedWithinClipMs, current.effectiveDurationMs)) + if (v.currentTime * 1000 < current.effectiveEndMs - 40) return + v.pause() + if (autoChainPlay) { + const nextReady = findNextReadyIndex(activeIdxRef.current) + if (nextReady >= 0) { + shouldAutoResumeRef.current = true + setActiveIdx(nextReady) + return + } + } + setPlaying(false) + } + + const handleEnded = () => { + if (autoChainPlay) { + const nextReady = findNextReadyIndex(activeIdxRef.current) + if (nextReady >= 0) { + shouldAutoResumeRef.current = true + setActiveIdx(nextReady) + return + } + } + setPlaying(false) + } + + const startPlay = () => { + if (!timelineModels.some((clip) => clip.seg.clip_status === 'ready')) { + message.warning('没有可预览的片段') + return + } + if (!activeClip || activeClip.seg.clip_status !== 'ready') { + const firstReady = timelineModels.find((clip) => clip.seg.clip_status === 'ready') + if (!firstReady) return + shouldAutoResumeRef.current = true + setActiveIdx(firstReady.idx) + return + } + setPlaying(true) + void videoRef.current?.play().catch(() => { + setPlaying(false) + message.warning('播放失败,请手动点击播放器') + }) + } + + const stopPlay = () => { + setPlaying(false) + videoRef.current?.pause() + } + + const rulerMarks = useMemo(() => { + const marks: number[] = [] + for (let ms = 0; ms <= totalTimelineMs; ms += 1000) marks.push(ms) + if (marks[marks.length - 1] !== totalTimelineMs) marks.push(totalTimelineMs) + return marks + }, [totalTimelineMs]) + + return ( +
+
+
+
+ + 返回章节列表 + + + 章节剪辑时间线 +
- -
时间线(拖拽素材到轨道)
-
-
-
轨道
-
内容
-
-
-
视频轨道
-
- {clips.filter((c) => c.type === 'video').map((c) => ( -
- {c.label}({c.start}s–{c.end}s) + + setLosslessOnly(e.target.checked)} className="text-slate-300"> + 无损拼接(不支持裁剪) + + + + +
+ + {timeline?.preview_note ? ( + + ) : null} + + + + +
+
+
片段列表
+
+ {timelineModels.length === 0 ? ( + 暂无片段} /> + ) : ( + timelineModels.map((clip) => ( + + )) + )} +
+
+ +
+
预览播放器
+
+
+ {activeVideoUrl ? ( + + ) : ( + 当前片段没有可预览视频 + )}
- ))} - {clips.filter((c) => c.type === 'video').length === 0 && ( - 拖入视频素材 - )} -
+
+ + + + + + + + 自动衔接下一段 + +
+
+ + +
+
草稿参数
+
+
+ 当前片段 +
{activeClip?.seg.label || activeClip?.seg.shot_id || '未选择'}
+
+
+ 片段状态 +
{clipStatusTag(activeClip?.seg.clip_status)}
+
+
+ 裁剪区间 +
+ {activeClip ? `${formatMs(activeClip.effectiveStartMs)} - ${formatMs(activeClip.effectiveEndMs)}` : '—'} +
+
+
+ 当前章节总时长 +
{formatMs(totalTimelineMs)}
+
+
+ 时间线缩放 +
+ setZoomPxPerSec(Number(v))} + /> +
+
+
+
-
-
音频轨道
-
- {clips.filter((c) => c.type === 'audio').map((c) => ( -
- {c.label}({c.start}s–{c.end}s) + +
+
+
+
时间线编辑区
+
拖动片段左上角“⋮⋮”重排;拖动左右亮色边缘裁剪单个片段。支持横向滚动与滚轮滚动。
+
+ layout_version={timeline?.layout_version ?? '—'} +
+ +
+
+
+
+ {rulerMarks.map((ms) => ( +
+ {formatMs(ms)} +
+ ))} +
+ +
+
+
+ {timelineModels.map((clip) => { + const width = Math.max(clip.effectiveDurationMs * pxPerMs, 90) + const ready = clip.seg.clip_status === 'ready' + return ( +
{ + event.preventDefault() + }} + onDrop={() => { + const from = draggingIndexRef.current + if (from == null) return + reorderSegments(from, clip.idx) + draggingIndexRef.current = null + }} + className={`group relative mr-3 h-20 shrink-0 overflow-hidden rounded-lg border transition ${ + clip.idx === activeIdx + ? 'border-cyan-400 shadow-[0_0_0_1px_rgba(34,211,238,0.45)]' + : 'border-white/10' + } ${ + ready ? 'bg-gradient-to-b from-cyan-600/75 to-cyan-900/80' : 'bg-gradient-to-b from-rose-700/75 to-rose-950/90' + }`} + style={{ width }} + onClick={() => setActiveIdx(clip.idx)} + > +
+
+ {Array.from({ length: Math.max(4, Math.floor(width / 28)) }).map((_, stripeIdx) => ( +
+ ))} +
+ {ready ? ( + <> +
{ + event.stopPropagation() + draggingIndexRef.current = clip.idx + }} + onDragEnd={() => { + draggingIndexRef.current = null + }} + className="absolute left-1 top-1 z-30 flex h-5 w-8 cursor-grab select-none items-center justify-center rounded bg-black/35 text-[12px] text-white/90 hover:bg-black/45 active:cursor-grabbing" + title="拖动重排片段" + > + ⋮⋮ +
+
+ ) + })} +
+
+ +
+ {activeClip ? ( +
+ 当前片段:{activeClip.seg.label || activeClip.seg.shot_id} + 开始:{activeClip.effectiveStartMs}ms + 结束:{activeClip.effectiveEndMs}ms + 有效时长:{activeClip.effectiveDurationMs}ms + 操作:拖左上角“⋮⋮”重排,拖左右边缘裁剪 + +
+ ) : ( + 请选择片段 + )} +
- ))} - {clips.filter((c) => c.type === 'audio').length === 0 && ( - 拖入音频/配乐 - )} +
-
-
- - - +
+ + + +
) } diff --git a/front/src/pages/aiStudio/files/FileManager.tsx b/front/src/pages/aiStudio/files/FileManager.tsx index 3fa5e739..d46b1443 100644 --- a/front/src/pages/aiStudio/files/FileManager.tsx +++ b/front/src/pages/aiStudio/files/FileManager.tsx @@ -1,137 +1,319 @@ -import React, { useEffect, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { + Button, Card, + Checkbox, + Col, + Empty, Input, - Select, + Modal, + Pagination, + Popconfirm, Row, - Col, + Select, + Space, + Spin, Tag, - Button, + Typography, message, - Space, } from 'antd' -import { DownloadOutlined, FileImageOutlined, VideoCameraOutlined } from '@ant-design/icons' -import api from '../../../services/aiStudioApi' -import type { FileItem } from '../../../mocks/data' +import { DeleteOutlined, DownloadOutlined, FileImageOutlined, VideoCameraOutlined } from '@ant-design/icons' +import { useSearchParams } from 'react-router-dom' +import { StudioFilesService } from '../../../services/generated' +import type { FileRead } from '../../../services/generated' +import { DisplayImageCard } from '../assets/components/DisplayImageCard' +import { buildFileDownloadUrl, resolveAssetUrl } from '../assets/utils' +import { ScrollablePage } from '../components/ScrollablePage' + +const { Text } = Typography +const PAGE_SIZE = 24 -const PRESET_TAGS = ['首帧', '尾帧', '关键帧', '成片', '版本1', '现实主义', '科幻', '待导出'] +function openDownload(url: string) { + window.open(url, '_blank', 'noopener,noreferrer') +} const FileManager: React.FC = () => { - const [files, setFiles] = useState([]) + const [searchParams] = useSearchParams() + const projectId = searchParams.get('projectId')?.trim() || undefined + + const [files, setFiles] = useState([]) const [loading, setLoading] = useState(true) + const [page, setPage] = useState(1) + const [total, setTotal] = useState(0) + const [q, setQ] = useState('') const [tagFilter, setTagFilter] = useState(null) - const [search, setSearch] = useState('') - const [selectedFileIds, setSelectedFileIds] = useState([]) - const [quickTag, setQuickTag] = useState('') + const [selectedFileIds, setSelectedFileIds] = useState>(() => new Set()) + const [previewVideo, setPreviewVideo] = useState(null) - const load = async () => { + const load = useCallback(async () => { setLoading(true) try { - const list = await api.files.list() - setFiles(Array.isArray(list) ? list : []) + const res = await StudioFilesService.listFilesApiApiV1StudioFilesGet({ + projectId: projectId ?? null, + q: q.trim() || null, + page, + pageSize: PAGE_SIZE, + order: 'updated_at', + isDesc: true, + }) + setFiles(res.data?.items ?? []) + setTotal(res.data?.pagination?.total ?? 0) } catch { + setFiles([]) + setTotal(0) message.error('加载文件失败') } finally { setLoading(false) } - } + }, [page, projectId, q]) useEffect(() => { void load() - }, []) + }, [load]) + + const allTags = useMemo( + () => Array.from(new Set(files.flatMap((f) => f.tags ?? []))).sort(), + [files], + ) - const list = Array.isArray(files) ? files : [] - const allTags = Array.from( - new Set(list.flatMap((f) => (f && Array.isArray(f.tags) ? f.tags : []))) - ).sort() + const filteredFiles = useMemo( + () => (!tagFilter ? files : files.filter((f) => (f.tags ?? []).includes(tagFilter))), + [files, tagFilter], + ) + + const toggleSelect = (id: string, checked: boolean) => { + setSelectedFileIds((prev) => { + const next = new Set(prev) + if (checked) next.add(id) + else next.delete(id) + return next + }) + } + + const handleDeleteOne = async (file: FileRead) => { + try { + await StudioFilesService.deleteFileApiApiV1StudioFilesFileIdDelete({ fileId: file.id }) + setSelectedFileIds((prev) => { + const next = new Set(prev) + next.delete(file.id) + return next + }) + if (previewVideo?.id === file.id) setPreviewVideo(null) + message.success('文件已删除') + await load() + } catch (err) { + message.error(err instanceof Error ? err.message : '删除文件失败') + } + } - const filtered = list.filter((f) => { - const matchSearch = - !search || - f.name.toLowerCase().includes(search.toLowerCase()) || - f.tags.some((t) => t.toLowerCase().includes(search.toLowerCase())) - const matchTag = !tagFilter || f.tags.includes(tagFilter) - return matchSearch && matchTag - }) + const handleBatchDelete = async () => { + const ids = Array.from(selectedFileIds) + if (!ids.length) return + try { + await Promise.all(ids.map((fileId) => StudioFilesService.deleteFileApiApiV1StudioFilesFileIdDelete({ fileId }))) + setSelectedFileIds(new Set()) + setPreviewVideo(null) + message.success(`已删除 ${ids.length} 个文件`) + await load() + } catch (err) { + message.error(err instanceof Error ? err.message : '批量删除文件失败') + } + } - const effectiveTags = [...new Set([...PRESET_TAGS, ...allTags])] + const handleBatchDownload = () => { + Array.from(selectedFileIds) + .map((id) => buildFileDownloadUrl(id)) + .filter((url): url is string => Boolean(url)) + .forEach((url, idx) => { + setTimeout(() => openDownload(url), idx * 250) + }) + } + + const filePreviewUrl = (file: FileRead) => + file.type === 'image' ? resolveAssetUrl(file.thumbnail) ?? buildFileDownloadUrl(file.id) : undefined return ( -
-
- setSearch(e.target.value)} - /> - setQuickTag(v ?? '')} - options={effectiveTags.map((t) => ({ label: t, value: t }))} - onSelect={(v) => { - if (selectedFileIds.length) message.success(`已为 ${selectedFileIds.length} 个文件添加标签「${v}」(Mock)`) - }} - /> - -
- - - - {filtered.map((f) => ( - - setSelectedFileIds((prev) => prev.includes(f.id) ? prev.filter((k) => k !== f.id) : [...prev, f.id])} - cover={ -
- {f.type === 'video' ? ( - - ) : ( - - )} -
- } - > -
- {f.name} -
-
{f.createdAt}
- - {f.tags.map((t) => ( - {t} - ))} - -
- -
-
- - ))} -
- {filtered.length === 0 && ( -
暂无文件
- )} -
-
+ <> + +
+ + {projectId ? 当前仅查看项目文件 : 全局文件} + {selectedFileIds.size > 0 ? ( + <> + + void handleBatchDelete()} + > + + + + ) : null} + + } + > +
+ { + setPage(1) + setQ(value) + }} + onChange={(e) => setQ(e.target.value)} + /> + + String(option?.label ?? option?.value ?? '') + .toLowerCase() + .includes(inputValue.toLowerCase()) + } onChange={(v) => applyDefaultBaseUrlForDisplayName(String(v))} + onSelect={(v) => applyDefaultBaseUrlForDisplayName(String(v))} /> , ): RelationTaskState { return { @@ -90,6 +93,7 @@ export function toRelationTaskStateFromStatusRead( startedAtTs: data.started_at_ts, finishedAtTs: data.finished_at_ts, elapsedMs: data.elapsed_ms, + error: data.error?.trim() ? data.error.trim() : null, } } @@ -105,6 +109,7 @@ export function applyCancelToRelationTaskState( startedAtTs: currentTask.startedAtTs, finishedAtTs: currentTask.finishedAtTs, elapsedMs: currentTask.elapsedMs, + error: currentTask.error, } } diff --git a/front/src/pages/aiStudio/project/ProjectWorkbench/index.tsx b/front/src/pages/aiStudio/project/ProjectWorkbench/index.tsx index 7b0b0303..4746d6f4 100644 --- a/front/src/pages/aiStudio/project/ProjectWorkbench/index.tsx +++ b/front/src/pages/aiStudio/project/ProjectWorkbench/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react' -import { Card, Button, Tabs, Space, Dropdown, Empty } from 'antd' +import { Card, Button, Tabs, Space, Dropdown, Empty, message } from 'antd' import type { MenuProps } from 'antd' import { PlusOutlined, @@ -18,7 +18,7 @@ import { CostumesTab, PropsTab } from './tabs/PropsTab' import { FilesTab } from './tabs/FilesTab' import { EditTab } from './tabs/EditTab' import { SettingsTab } from './tabs/SettingsTab' -import { getChapterShotsPath, getChapterStudioPath, getProjectEditorPath } from './routes' +import { getChapterShotsPath, getChapterStudioPath, getChapterTimelinePath } from './routes' import { useProject, useChapters } from './hooks/useProjectData' import { ensureHasShotsBeforeShooting } from './ensureHasShotsBeforeShooting' import { getChapterPreparationState } from './chapterPreparation' @@ -211,7 +211,19 @@ const ProjectWorkbench: React.FC = () => { > {primaryCta.label} - diff --git a/front/src/pages/aiStudio/project/ProjectWorkbench/routes.ts b/front/src/pages/aiStudio/project/ProjectWorkbench/routes.ts index b6b749e3..5da0f9b9 100644 --- a/front/src/pages/aiStudio/project/ProjectWorkbench/routes.ts +++ b/front/src/pages/aiStudio/project/ProjectWorkbench/routes.ts @@ -1,5 +1,6 @@ +/** 章节列表挂在项目工作台 Tab,无独立 /projects/:id/chapters 页面路由 */ export function getProjectChaptersPath(projectId: string) { - return `/projects/${projectId}/chapters` + return `/projects/${projectId}?tab=chapters` } export function getChapterStudioPath(projectId: string, chapterId: string) { @@ -14,7 +15,16 @@ export function getChapterShotEditPath(projectId: string, chapterId: string, sho return `/projects/${projectId}/chapters/${chapterId}/shots/${shotId}/edit` } +/** + * 旧入口 `/projects/:id/editor`(无 chapterId 时组件会重定向)。 + * 新流程请使用「剪辑」Tab 或 `getChapterTimelinePath(projectId, chapterId)`。 + */ export function getProjectEditorPath(projectId: string) { return `/projects/${projectId}/editor` } +/** 章节视频剪辑(时间线编排与导出入口) */ +export function getChapterTimelinePath(projectId: string, chapterId: string) { + return `/projects/${projectId}/chapters/${chapterId}/timeline` +} + diff --git a/front/src/pages/aiStudio/project/ProjectWorkbench/tabs/ChaptersTab.tsx b/front/src/pages/aiStudio/project/ProjectWorkbench/tabs/ChaptersTab.tsx index 4cf84c73..41d3b826 100644 --- a/front/src/pages/aiStudio/project/ProjectWorkbench/tabs/ChaptersTab.tsx +++ b/front/src/pages/aiStudio/project/ProjectWorkbench/tabs/ChaptersTab.tsx @@ -14,7 +14,7 @@ import { import { useNavigate, useParams, useSearchParams } from 'react-router-dom' import { ScriptProcessingService, StudioChaptersService } from '../../../../../services/generated' import { chapterStatusMap } from '../constants' -import { getChapterShotsPath, getChapterStudioPath } from '../routes' +import { getChapterShotsPath, getChapterStudioPath, getChapterTimelinePath } from '../routes' import { useChapters, newId, type Chapter } from '../hooks/useProjectData' import { ChapterRawTextEditorModal } from '../../../chapter/components/ChapterRawTextEditorModal' import { ensureHasShotsBeforeShooting } from '../ensureHasShotsBeforeShooting' @@ -29,6 +29,7 @@ import { upsertRelationTaskStateInMap, useChapterDivisionTaskMapPolling, } from '../chapterDivisionTasks' +import { ScrollablePage } from '../../../components/ScrollablePage' const { TextArea } = Input const CREATE_PARAM = 'create' @@ -354,10 +355,16 @@ export function ChaptersTab() { const state = getChapterPreparationState(record) const activeTask = chapterDivisionTaskMap[record.id] return [ + { + key: 'timeline', + label: '章节剪辑', + icon: , + onClick: () => navigate(getChapterTimelinePath(projectId, record.id)), + }, { key: 'shots', label: '查看分镜', - icon: , + icon: , onClick: () => navigate(getChapterShotsPath(projectId, record.id)), }, state.key !== 'prepare_shots' && (record.storyboardCount ?? 0) > 0 @@ -519,15 +526,15 @@ export function ChaptersTab() { if (chapters.length === 0 && !loading) { return ( - <> + - - - - + + + +
- + ) } return ( - - - - } - > - - rowKey="id" - loading={loading} - columns={columns} - dataSource={chapters} - pagination={{ pageSize: 10 }} - size="small" - /> + + + + + } + > + + rowKey="id" + loading={loading} + columns={columns} + dataSource={chapters} + pagination={{ pageSize: 10 }} + size="small" + /> - { - setEditOpen(false) - setEditingChapter(null) - }} - chapterId={editingChapter?.id} - onSaved={(next) => { - if (editingChapter?.id && typeof next.rawText === 'string') { - patchChapterLocal(editingChapter.id, { rawText: next.rawText }) - } - void refresh() - }} - /> + { + setEditOpen(false) + setEditingChapter(null) + }} + chapterId={editingChapter?.id} + onSaved={(next) => { + if (editingChapter?.id && typeof next.rawText === 'string') { + patchChapterLocal(editingChapter.id, { rawText: next.rawText }) + } + void refresh() + }} + /> - setCreateOpen(false)} - onOk={useMock ? handleCreateChapterMock : handleCreateChapter} - okText="创建" - width={560} - > -
-
- 章节标题 - setCreateTitle(e.target.value)} - className="mt-1" - /> -
-
- 章节内容(可粘贴剧本) -