857 lines
39 KiB
Python
857 lines
39 KiB
Python
from typing import Annotated, Literal
|
||
|
||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||
|
||
from app.schemas.common import NaiveDatetimeOptional
|
||
from app.enums.generation_history import normalize_generation_history_source
|
||
|
||
|
||
class GenerationAIReference(BaseModel):
|
||
"""AI生成任务参考素材。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"url": "https://example.com/reference.png",
|
||
"type": "image",
|
||
"name": "参考图.png",
|
||
"duration": 5.0,
|
||
}
|
||
}
|
||
)
|
||
|
||
url: str = Field(
|
||
...,
|
||
description="参考素材地址,可以是图片地址、视频地址或音频地址",
|
||
examples=["https://example.com/reference.png"],
|
||
)
|
||
type: str = Field(
|
||
...,
|
||
description="参考素材类型:image=图片,video=视频,audio=音频",
|
||
examples=["image"],
|
||
)
|
||
name: str | None = Field(
|
||
None,
|
||
description="参考素材名称,前端展示用,可为空",
|
||
examples=["参考图.png"],
|
||
)
|
||
duration: float | None = Field(
|
||
None,
|
||
ge=0,
|
||
description="参考素材时长(秒)。type=video/audio 时使用,用于视频/音频素材时长校验",
|
||
examples=[5.0],
|
||
)
|
||
source: str | None = Field(
|
||
None,
|
||
description="参考素材来源。private_portrait_asset=真人素材库;为空表示普通上传文件",
|
||
examples=["private_portrait_asset"],
|
||
)
|
||
private_asset_id: str | None = Field(
|
||
None,
|
||
description="真人素材库本地素材ID。source=private_portrait_asset 时必填,后端据此解析 remote_asset_id",
|
||
examples=["0019fxxx"],
|
||
)
|
||
remote_asset_id: str | None = Field(
|
||
None,
|
||
description="后端回填的火山 Asset ID。前端传入时不可信,创建任务时以后端查库为准",
|
||
examples=["asset-20260318071009-xxxxx"],
|
||
)
|
||
|
||
|
||
class GenerationAITaskCreate(BaseModel):
|
||
"""创建AI图片/视频生成任务请求体。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"examples": [
|
||
{
|
||
"project_id": None,
|
||
"gen_type": "image",
|
||
"original_prompt": "生成一张赛博朋克风格的城市夜景",
|
||
"engine_id": None,
|
||
"media_references": [
|
||
{
|
||
"url": "https://example.com/reference.png",
|
||
"type": "image",
|
||
"name": "参考图.png",
|
||
}
|
||
],
|
||
"idempotency_key": "frontend-submit-uuid-001",
|
||
"image_size": "2K",
|
||
"image_proportion": "1:1",
|
||
"image_px": "2048x2048",
|
||
"duration": None,
|
||
"aspect_ratio": None,
|
||
"resolution": None,
|
||
},
|
||
{
|
||
"project_id": None,
|
||
"gen_type": "video",
|
||
"original_prompt": "生成一段海边日落的电影感视频",
|
||
"engine_id": None,
|
||
"media_references": None,
|
||
"idempotency_key": "frontend-submit-uuid-002",
|
||
"image_size": None,
|
||
"image_proportion": None,
|
||
"image_px": None,
|
||
"duration": 4,
|
||
"aspect_ratio": "16:9",
|
||
"resolution": "480p",
|
||
},
|
||
]
|
||
}
|
||
)
|
||
|
||
# 新 chat 生成任务不绑定 project_id。为了兼容旧前端误传,保留可选字段但后端不使用。
|
||
project_id: str | None = Field(
|
||
None,
|
||
description="兼容旧前端字段。当前 /generation-ai 任务不绑定项目,后端不使用该字段,可传 null 或不传",
|
||
examples=[None],
|
||
)
|
||
gen_type: str = Field(
|
||
...,
|
||
description="生成类型:image=图片生成,video=视频生成",
|
||
examples=["image"],
|
||
)
|
||
original_prompt: str = Field(
|
||
...,
|
||
min_length=1,
|
||
max_length=5000,
|
||
description="用户原始提示词,不能为空,最长5000字符",
|
||
examples=["生成一张赛博朋克风格的城市夜景"],
|
||
)
|
||
engine_id: str | None = Field(
|
||
None,
|
||
description="图片/视频引擎ID;为空则使用当前激活且优先级最高的引擎",
|
||
examples=[None],
|
||
)
|
||
media_references: list[GenerationAIReference] | None = Field(
|
||
None,
|
||
description="参考素材列表。可以传图片/视频/音频参考素材;为空表示不使用参考素材",
|
||
)
|
||
idempotency_key: str | None = Field(
|
||
None,
|
||
max_length=64,
|
||
description=(
|
||
"幂等键,用于防止前端重复提交、网络重试导致重复创建任务和重复扣费。"
|
||
"同一用户、同一 idempotency_key、同一 generation_mode 下重复请求会返回已有任务。"
|
||
"建议前端每次点击生成时生成 UUID;同一次请求失败重试时复用同一个 UUID。"
|
||
),
|
||
examples=["frontend-submit-uuid-001"],
|
||
)
|
||
|
||
# image params
|
||
image_size: str | None = Field(
|
||
None,
|
||
description="图片分辨率档位,例如:1K、2K。仅图片生成或视频首帧参数需要使用;为空则使用引擎默认值",
|
||
examples=["2K"],
|
||
)
|
||
image_proportion: str | None = Field(
|
||
None,
|
||
description="图片比例,例如:1:1、16:9、9:16。仅图片生成或视频首帧参数需要使用;为空则使用默认值",
|
||
examples=["1:1"],
|
||
)
|
||
image_px: str | None = Field(
|
||
None,
|
||
description="图片像素尺寸,例如:2048x2048。为空时后端根据 image_size 和 image_proportion 自动匹配",
|
||
examples=["2048x2048"],
|
||
)
|
||
|
||
# video params
|
||
duration: int | None = Field(
|
||
None,
|
||
description="视频时长,单位秒。仅视频生成使用;为空则使用默认时长",
|
||
examples=[4],
|
||
)
|
||
aspect_ratio: str | None = Field(
|
||
None,
|
||
description="视频比例,例如:16:9、9:16、1:1。仅视频生成使用;为空则使用默认比例",
|
||
examples=["16:9"],
|
||
)
|
||
resolution: str | None = Field(
|
||
None,
|
||
description="视频分辨率,例如:480p、720p、1080p。仅视频生成使用;为空则使用默认分辨率",
|
||
examples=["480p"],
|
||
)
|
||
|
||
|
||
|
||
|
||
class GenerationAIImageEngineOptionOut(BaseModel):
|
||
"""AI图片生成可用引擎响应项。"""
|
||
|
||
id: str = Field(..., description="图片引擎ID,创建图片任务时传入 engine_id")
|
||
name: str = Field(..., description="图片引擎名称,前端展示用")
|
||
provider: str = Field(..., description="服务商标识,例如 ark")
|
||
model_name: str | None = Field(None, description="服务商模型名称")
|
||
supported_models: list[str] = Field(default_factory=list, description="该图片引擎支持的模型名称列表")
|
||
supported_sizes: dict = Field(
|
||
default_factory=dict,
|
||
description="支持的图片尺寸映射。第一层为分辨率档位,例如 2K/4K;第二层为画布比例,例如 1:1/16:9;值为像素尺寸",
|
||
)
|
||
default_size: str | None = Field(None, description="默认图片分辨率档位,例如 2K")
|
||
priority: int = Field(0, description="引擎优先级,数值越大越优先")
|
||
max_image_count: int = Field(0, description="最大图片数量")
|
||
|
||
|
||
class GenerationAIVideoEngineOptionOut(BaseModel):
|
||
"""AI视频生成可用引擎响应项。"""
|
||
|
||
id: str = Field(..., description="视频引擎ID,创建视频任务时传入 engine_id")
|
||
name: str = Field(..., description="视频引擎名称,前端展示用")
|
||
provider: str = Field(..., description="服务商标识,例如 ark")
|
||
model_name: str | None = Field(None, description="服务商模型名称")
|
||
supported_ratios: list[str] = Field(default_factory=list, description="支持的视频画面比例,例如 16:9、9:16、1:1")
|
||
supported_resolutions: list[str] = Field(default_factory=list, description="支持的视频分辨率,例如 480p、720p、1080p")
|
||
supported_durations: list[int] = Field(default_factory=list, description="支持的视频时长列表,单位秒")
|
||
max_duration: int | None = Field(None, description="最大视频时长,单位秒")
|
||
priority: int = Field(0, description="引擎优先级,数值越大越优先")
|
||
max_image_count: int | None = Field(None, description="最大图片数量")
|
||
max_video_count: int | None = Field(None, description="最大视频数量")
|
||
max_audio_count: int | None = Field(None, description="最大参考音频数量,0 表示不支持音频参考")
|
||
supports_first_last_frame: bool = Field(False, description="是否支持首帧和最后一帧")
|
||
supports_universal_reference: bool = Field(False, description="是否支持通用参考")
|
||
|
||
|
||
class GenerationAIEngineGroupOut(BaseModel):
|
||
"""AI图片/视频引擎分组。"""
|
||
|
||
image: list[GenerationAIImageEngineOptionOut] = Field(
|
||
default_factory=list,
|
||
description="当前启用的图片生成引擎列表",
|
||
)
|
||
video: list[GenerationAIVideoEngineOptionOut] = Field(
|
||
default_factory=list,
|
||
description="当前启用的视频生成引擎列表",
|
||
)
|
||
|
||
|
||
class GenerationAIEngineOptionsOut(BaseModel):
|
||
"""AI图片/视频生成引擎列表响应体。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"engine": {
|
||
"image": [
|
||
{
|
||
"id": "image_engine_xxx",
|
||
"name": "豆包文生图",
|
||
"provider": "ark",
|
||
"model_name": "doubao-seedream-5-0-260128",
|
||
"supported_models": ["doubao-seedream-5-0-260128"],
|
||
"supported_sizes": {
|
||
"2K": {
|
||
"1:1": "2048x2048",
|
||
"16:9": "2560x1440",
|
||
}
|
||
},
|
||
"default_size": "2K",
|
||
"priority": 10,
|
||
"max_image_count": 0,
|
||
}
|
||
],
|
||
"video": [
|
||
{
|
||
"id": "video_engine_xxx",
|
||
"name": "Seedance 2.0",
|
||
"provider": "ark",
|
||
"model_name": "doubao-seedance-2-0-260128",
|
||
"supported_ratios": ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"],
|
||
"supported_resolutions": ["480p", "720p", "1080p"],
|
||
"supported_durations": [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||
"max_duration": 15,
|
||
"priority": 10,
|
||
"max_image_count": 2,
|
||
"max_video_count": 0,
|
||
"max_audio_count": 0,
|
||
"supports_first_last_frame": False,
|
||
"supports_universal_reference": False,
|
||
}
|
||
],
|
||
}
|
||
}
|
||
}
|
||
)
|
||
|
||
engine: GenerationAIEngineGroupOut = Field(..., description="图片/视频可用引擎分组")
|
||
|
||
|
||
class GenerationAITaskOut(BaseModel):
|
||
"""AI生成任务详情响应体。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"id": "0019e0a44895b6d837d",
|
||
"source_type": "chat_task",
|
||
"user_id": None,
|
||
"user_name": None,
|
||
"project_id": None,
|
||
"generated_resource_id": "generated_resource_xxx",
|
||
"gen_type": "image",
|
||
"generation_mode": "chatapi_async",
|
||
"pipeline_stage": "done",
|
||
"status": "completed",
|
||
"original_prompt": "生成一张赛博朋克风格的城市夜景",
|
||
"optimized_prompt": None,
|
||
"duration": None,
|
||
"aspect_ratio": None,
|
||
"resolution": None,
|
||
"image_size": "2K",
|
||
"image_proportion": "1:1",
|
||
"image_px": "2048x2048",
|
||
"media_references": None,
|
||
"provider_task_id": "provider_task_xxx",
|
||
"seedance_task_id": "provider_task_xxx",
|
||
"remote_result_url": None,
|
||
"image_url": "https://example.com/result.png",
|
||
"video_url": None,
|
||
"video_cover_url": None,
|
||
"engine_id": "engine_xxx",
|
||
"engine_snapshot": {
|
||
"engine_type": "image",
|
||
"id": "engine_xxx",
|
||
"name": "图片生成引擎",
|
||
"provider": "provider_name",
|
||
"model_name": "model_name",
|
||
"supported_models": [],
|
||
"default_size": "2K",
|
||
"selected_size": "2K",
|
||
"selected_proportion": "1:1",
|
||
"selected_px": "2048x2048",
|
||
},
|
||
"credits_cost": 10.0,
|
||
"text_credits_cost": 0.0,
|
||
"text_tokens_used": 0,
|
||
"image_tokens_used": 0,
|
||
"video_tokens_used": 0,
|
||
"retry_count": 0,
|
||
"poll_count": 3,
|
||
"error_message": None,
|
||
"created_at": "2026-05-27T10:12:00",
|
||
"generated_at": "2026-05-27T10:15:30",
|
||
}
|
||
}
|
||
)
|
||
|
||
id: str = Field(..., description="生成任务ID")
|
||
source_type: Literal["chat_task"] = Field(
|
||
"chat_task",
|
||
description="历史记录来源。ChatGenerationTask 新任务历史固定为 chat_task",
|
||
)
|
||
user_id: str | None = Field(
|
||
None,
|
||
description="用户ID,管理后台调用存在对应值,通常为 null",
|
||
)
|
||
user_name: str | None = Field(
|
||
None,
|
||
description="用户名称,管理后台调用存在对应值,通常为 null",
|
||
)
|
||
project_id: str | None = Field(
|
||
None,
|
||
description="项目ID。当前 /generation-ai 任务不绑定项目,通常为 null",
|
||
)
|
||
generated_resource_id: str | None = Field(
|
||
None,
|
||
description="关联的生成资源账本ID,来源于 generated_resources.id;历史脏数据可能为空",
|
||
)
|
||
file_name: str | None = Field(None, description="文件名,来源于 generated_resources.file_name")
|
||
history_source: str | None = Field(
|
||
None,
|
||
description=(
|
||
"素材云历史来源:chat_task=AI创作,generation_record=项目生成,"
|
||
"hot_opening_replicate=爆款开头复刻,shot_replicate=拆镜复刻"
|
||
),
|
||
)
|
||
history_source_label: str | None = Field(None, description="素材云历史来源中文名称")
|
||
module_project_id: str | None = Field(None, description="模块生成项目ID;非模块生成历史返回 null")
|
||
module_project_title: str | None = Field(None, description="模块生成项目标题;非模块生成历史返回 null")
|
||
module_step_id: str | None = Field(None, description="模块生成步骤ID;非模块生成历史返回 null")
|
||
module_step_code: str | None = Field(None, description="模块生成步骤编码;非模块生成历史返回 null")
|
||
hot_opening_project_id: str | None = Field(None, description="爆款开头复刻项目ID;非爆款开头复刻返回 null")
|
||
hot_opening_project_title: str | None = Field(None, description="爆款开头复刻项目标题;非爆款开头复刻返回 null")
|
||
shot_replicate_project_id: str | None = Field(None, description="拆镜复刻项目ID;非拆镜复刻返回 null")
|
||
shot_replicate_project_title: str | None = Field(None, description="拆镜复刻项目标题;非拆镜复刻返回 null")
|
||
shot_task_set_id: str | None = Field(None, description="拆镜复刻总任务ID;非拆镜复刻返回 null")
|
||
shot_segment_id: str | None = Field(None, description="拆镜复刻片段ID;非拆镜复刻返回 null")
|
||
shot_segment_index: int | None = Field(None, description="拆镜复刻片段序号;非拆镜复刻返回 null")
|
||
shot_segment_label: str | None = Field(None, description="拆镜复刻片段展示名称,例如:拆镜复刻片段1;非拆镜复刻返回 null")
|
||
gen_type: str = Field(..., description="生成类型:image=图片,video=视频")
|
||
generation_mode: str | None = Field(
|
||
None,
|
||
description="生成模式。当前异步Chat生成任务一般为 chatapi_async",
|
||
)
|
||
pipeline_stage: str | None = Field(
|
||
None,
|
||
description=(
|
||
"任务流水线阶段,例如:queued=已入队,creating_provider_task=创建第三方任务中,"
|
||
"waiting_remote=等待第三方生成,result_ready=远程结果已就绪,downloading=下载中,done=完成"
|
||
),
|
||
)
|
||
status: str = Field(
|
||
...,
|
||
description="任务状态,例如:generating=生成中,completed=已完成,failed=失败",
|
||
)
|
||
original_prompt: str = Field(..., description="用户原始提示词")
|
||
optimized_prompt: str | None = Field(None, description="优化后的提示词,可能为空")
|
||
duration: int | None = Field(None, description="视频时长,单位秒。图片任务通常为空")
|
||
aspect_ratio: str | None = Field(None, description="视频比例,例如 16:9。图片任务通常为空")
|
||
resolution: str | None = Field(None, description="视频分辨率,例如 480p。图片任务通常为空")
|
||
image_size: str | None = Field(None, description="图片分辨率档位,例如 2K")
|
||
image_proportion: str | None = Field(None, description="图片比例,例如 1:1")
|
||
image_px: str | None = Field(None, description="图片像素尺寸,例如 2048x2048")
|
||
media_references: list[dict] | None = Field(
|
||
None,
|
||
description="参考素材列表。由创建任务时传入的 media_references 转换而来",
|
||
)
|
||
provider_task_id: str | None = Field(None, description="第三方服务商任务ID")
|
||
seedance_task_id: str | None = Field(
|
||
None,
|
||
description="兼容旧前端/旧服务命名的第三方任务ID字段",
|
||
)
|
||
remote_result_url: str | None = Field(
|
||
None,
|
||
description="第三方远程结果地址。当前接口可能隐藏或为空,最终展示优先使用 image_url/video_url",
|
||
)
|
||
image_url: str | None = Field(None, description="最终图片地址。图片任务完成后通常有值")
|
||
video_url: str | None = Field(None, description="最终视频地址。视频任务完成后通常有值")
|
||
video_cover_url: str | None = Field(None, description="视频封面图片地址。视频任务完成且封面截帧成功后通常有值")
|
||
engine_id: str | None = Field(None, description="本次任务使用的生成引擎ID")
|
||
engine_snapshot: dict | None = Field(
|
||
None,
|
||
description="生成任务创建时的引擎快照,用于前端展示当时使用的模型、比例、尺寸等信息",
|
||
)
|
||
credits_cost: float = Field(0.0, description="本次任务总消耗积分")
|
||
text_credits_cost: float = Field(0.0, description="文本优化或文本处理消耗积分")
|
||
text_tokens_used: int = Field(0, description="文本 token 使用量")
|
||
image_tokens_used: int = Field(0, description="图片 token 使用量")
|
||
video_tokens_used: int = Field(0, description="视频 token 使用量")
|
||
retry_count: int = Field(0, description="任务重试次数")
|
||
poll_count: int = Field(0, description="轮询第三方任务状态次数")
|
||
error_message: str | None = Field(None, description="错误信息。成功任务一般为 null")
|
||
created_at: NaiveDatetimeOptional = Field(None, description="任务创建时间")
|
||
generated_at: NaiveDatetimeOptional = Field(None, description="任务生成完成时间")
|
||
|
||
|
||
class GenerationAITaskListOut(BaseModel):
|
||
"""AI生成任务列表响应体。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"total": 1,
|
||
"items": [
|
||
{
|
||
"id": "0019e0a44895b6d837d",
|
||
"source_type": "chat_task",
|
||
"user_id": None,
|
||
"user_name": None,
|
||
"project_id": None,
|
||
"gen_type": "image",
|
||
"generation_mode": "chatapi_async",
|
||
"pipeline_stage": "done",
|
||
"status": "completed",
|
||
"original_prompt": "生成一张赛博朋克风格的城市夜景",
|
||
"optimized_prompt": None,
|
||
"duration": None,
|
||
"aspect_ratio": None,
|
||
"resolution": None,
|
||
"image_size": "2K",
|
||
"image_proportion": "1:1",
|
||
"image_px": "2048x2048",
|
||
"media_references": None,
|
||
"provider_task_id": "provider_task_xxx",
|
||
"seedance_task_id": "provider_task_xxx",
|
||
"remote_result_url": None,
|
||
"image_url": "https://example.com/result.png",
|
||
"video_url": None,
|
||
"video_cover_url": None,
|
||
"engine_id": "engine_xxx",
|
||
"engine_snapshot": {},
|
||
"credits_cost": 10.0,
|
||
"text_credits_cost": 0.0,
|
||
"text_tokens_used": 0,
|
||
"image_tokens_used": 0,
|
||
"video_tokens_used": 0,
|
||
"retry_count": 0,
|
||
"poll_count": 3,
|
||
"error_message": None,
|
||
"created_at": "2026-05-27T10:12:00",
|
||
"generated_at": "2026-05-27T10:15:30",
|
||
}
|
||
],
|
||
}
|
||
}
|
||
)
|
||
|
||
total: int = Field(..., description="符合筛选条件的任务总数")
|
||
items: list[GenerationAITaskOut] = Field(
|
||
default_factory=list,
|
||
description="当前分页的任务列表",
|
||
)
|
||
|
||
|
||
|
||
|
||
class GenerationAITaskDeleteOut(BaseModel):
|
||
"""AI生成任务删除响应体。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"message": "任务已删除",
|
||
"task_id": "0019e0a44895b6d837d",
|
||
"deleted": True,
|
||
"freed_size_bytes": 123456,
|
||
}
|
||
}
|
||
)
|
||
|
||
message: str = Field(..., description="操作结果提示信息")
|
||
task_id: str = Field(..., description="被软删除的AI生成任务ID")
|
||
deleted: bool = Field(..., description="是否已完成软删除")
|
||
freed_size_bytes: int = Field(0, description="本次软删联动释放的有效资源空间字节数")
|
||
|
||
|
||
class GenerationAIHistoryBatchDeleteRequest(BaseModel):
|
||
"""素材云历史批量删除请求体。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"history_source": "shot_replicate",
|
||
"ids": ["shot_segment_id_1", "shot_segment_id_2"],
|
||
}
|
||
}
|
||
)
|
||
|
||
history_source: str = Field(
|
||
...,
|
||
description=(
|
||
"素材云历史来源。"
|
||
"generation_record=项目生成,chat_task=AI创作,"
|
||
"hot_opening_replicate=爆款开头复刻,shot_replicate=拆镜复刻。"
|
||
"注意:项目生成/AI创作传记录ID;爆款开头复刻传 module_project_id;拆镜复刻传 shot_segment_id。"
|
||
),
|
||
examples=["shot_replicate"],
|
||
)
|
||
ids: list[str] = Field(
|
||
...,
|
||
min_length=1,
|
||
max_length=30,
|
||
description="需要删除的ID数组,最多30个;不允许重复或空字符串",
|
||
examples=[["shot_segment_id_1", "shot_segment_id_2"]],
|
||
)
|
||
|
||
@field_validator("history_source")
|
||
@classmethod
|
||
def validate_history_source(cls, value: str) -> str:
|
||
try:
|
||
return normalize_generation_history_source(value).value
|
||
except ValueError as exc:
|
||
raise ValueError("history_source 不支持") from exc
|
||
|
||
@field_validator("ids")
|
||
@classmethod
|
||
def validate_ids(cls, values: list[str]) -> list[str]:
|
||
normalized = [str(item).strip() for item in values if str(item or "").strip()]
|
||
if not normalized:
|
||
raise ValueError("ids 不能为空")
|
||
if len(normalized) > 30:
|
||
raise ValueError("单次最多删除30条记录")
|
||
if len(normalized) != len(set(normalized)):
|
||
raise ValueError("ids 不允许重复")
|
||
return normalized
|
||
|
||
|
||
class GenerationAIHistoryBatchDeleteOut(BaseModel):
|
||
"""素材云历史批量删除响应体。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"message": "删除成功",
|
||
"history_source": "shot_replicate",
|
||
"history_source_label": "拆镜复刻",
|
||
"requested_count": 2,
|
||
"deleted_count": 2,
|
||
"requested_ids": ["shot_segment_id_1", "shot_segment_id_2"],
|
||
"deleted_ids": ["shot_segment_id_1", "shot_segment_id_2"],
|
||
"generation_record_ids": [],
|
||
"chat_task_ids": ["chat_task_id_1", "chat_task_id_2"],
|
||
"module_project_ids": ["module_project_id_1", "module_project_id_2"],
|
||
"shot_segment_ids": ["shot_segment_id_1", "shot_segment_id_2"],
|
||
"deleted": True,
|
||
"freed_size_bytes": 123456,
|
||
}
|
||
}
|
||
)
|
||
|
||
message: str = Field(..., description="操作结果提示信息")
|
||
history_source: str = Field(..., description="素材云历史来源")
|
||
history_source_label: str | None = Field(None, description="素材云历史来源中文名称")
|
||
requested_count: int = Field(..., description="请求删除数量")
|
||
deleted_count: int = Field(..., description="实际删除数量")
|
||
requested_ids: list[str] = Field(default_factory=list, description="请求删除的原始ID列表")
|
||
deleted_ids: list[str] = Field(default_factory=list, description="已删除的原始ID列表")
|
||
generation_record_ids: list[str] = Field(default_factory=list, description="联动软删除的 GenerationRecord ID")
|
||
chat_task_ids: list[str] = Field(default_factory=list, description="联动软删除的 ChatGenerationTask ID")
|
||
module_project_ids: list[str] = Field(default_factory=list, description="联动软删除的 ModuleGenerationProject ID")
|
||
shot_segment_ids: list[str] = Field(default_factory=list, description="联动软删除的 ShotReplicateSegment ID")
|
||
deleted: bool = Field(..., description="是否已完成软删除")
|
||
freed_size_bytes: int = Field(0, description="本次软删联动释放的有效资源空间字节数")
|
||
|
||
|
||
class GenerationAIRetryOut(BaseModel):
|
||
"""AI生成任务重试响应体。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"id": "0019e0a44895b6d837d",
|
||
"status": "generating",
|
||
"pipeline_stage": "queued",
|
||
"message": "任务已重新投递",
|
||
}
|
||
}
|
||
)
|
||
|
||
id: str = Field(..., description="被重试的任务ID")
|
||
status: str = Field(..., description="重试后的任务状态")
|
||
pipeline_stage: str | None = Field(None, description="重试后的任务流水线阶段")
|
||
message: str = Field(..., description="操作结果提示信息")
|
||
|
||
|
||
class GenerationAIRecordHistoryItemOut(BaseModel):
|
||
"""旧 generation_records 历史记录详情响应体。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"id": "0019e0a44895b6d837d",
|
||
"source_type": "generation_record",
|
||
"project_id": "project_xxx",
|
||
"project_name": "默认项目",
|
||
"generated_resource_id": "generated_resource_xxx",
|
||
"gen_type": "image",
|
||
"generation_mode": "generation_record",
|
||
"pipeline_stage": None,
|
||
"status": "completed",
|
||
"original_prompt": "生成一张赛博朋克风格的城市夜景",
|
||
"optimized_prompt": "赛博朋克城市夜景,霓虹灯,电影感,高细节",
|
||
"duration": None,
|
||
"aspect_ratio": None,
|
||
"resolution": None,
|
||
"image_size": "2K",
|
||
"image_proportion": "1:1",
|
||
"image_px": "2048x2048",
|
||
"references": None,
|
||
"media_references": None,
|
||
"provider_task_id": "provider_task_xxx",
|
||
"seedance_task_id": "provider_task_xxx",
|
||
"remote_result_url": None,
|
||
"image_url": "https://example.com/result.png",
|
||
"video_url": None,
|
||
"video_cover_url": None,
|
||
"engine_id": None,
|
||
"engine_snapshot": None,
|
||
"credits_cost": 10.0,
|
||
"text_credits_cost": 1.0,
|
||
"text_tokens_used": 100,
|
||
"image_tokens_used": 0,
|
||
"video_tokens_used": 0,
|
||
"retry_count": 0,
|
||
"poll_count": 0,
|
||
"error_message": None,
|
||
"created_at": "2026-05-27T10:12:00",
|
||
"generated_at": "2026-05-27T10:15:30",
|
||
}
|
||
}
|
||
)
|
||
|
||
id: str = Field(..., description="旧生成记录ID")
|
||
source_type: Literal["generation_record"] = Field(
|
||
"generation_record",
|
||
description="历史记录来源固定为 generation_record,用于和 chat_task 历史区分",
|
||
)
|
||
project_id: str | None = Field(None, description="旧项目ID,来源于 generation_records.project_id")
|
||
project_name: str | None = Field(None, description="旧项目名称,来源于 projects.name;项目不存在时为空")
|
||
generated_resource_id: str | None = Field(
|
||
None,
|
||
description="关联的生成资源账本ID,来源于 generated_resources.id;历史脏数据可能为空",
|
||
)
|
||
file_name: str | None = Field(None, description="文件名,来源于 generated_resources.file_name")
|
||
history_source: str | None = Field(
|
||
None,
|
||
description=(
|
||
"素材云历史来源:chat_task=AI创作,generation_record=项目生成,"
|
||
"hot_opening_replicate=爆款开头复刻,shot_replicate=拆镜复刻"
|
||
),
|
||
)
|
||
history_source_label: str | None = Field(None, description="素材云历史来源中文名称")
|
||
module_project_id: str | None = Field(None, description="模块生成项目ID;非模块生成历史返回 null")
|
||
module_project_title: str | None = Field(None, description="模块生成项目标题;非模块生成历史返回 null")
|
||
module_step_id: str | None = Field(None, description="模块生成步骤ID;非模块生成历史返回 null")
|
||
module_step_code: str | None = Field(None, description="模块生成步骤编码;非模块生成历史返回 null")
|
||
hot_opening_project_id: str | None = Field(None, description="爆款开头复刻项目ID;非爆款开头复刻返回 null")
|
||
hot_opening_project_title: str | None = Field(None, description="爆款开头复刻项目标题;非爆款开头复刻返回 null")
|
||
shot_replicate_project_id: str | None = Field(None, description="拆镜复刻项目ID;非拆镜复刻返回 null")
|
||
shot_replicate_project_title: str | None = Field(None, description="拆镜复刻项目标题;非拆镜复刻返回 null")
|
||
shot_task_set_id: str | None = Field(None, description="拆镜复刻总任务ID;非拆镜复刻返回 null")
|
||
shot_segment_id: str | None = Field(None, description="拆镜复刻片段ID;非拆镜复刻返回 null")
|
||
shot_segment_index: int | None = Field(None, description="拆镜复刻片段序号;非拆镜复刻返回 null")
|
||
shot_segment_label: str | None = Field(None, description="拆镜复刻片段展示名称,例如:拆镜复刻片段1;非拆镜复刻返回 null")
|
||
gen_type: str = Field(..., description="生成类型:image=图片,video=视频")
|
||
generation_mode: str | None = Field(
|
||
"generation_record",
|
||
description="兼容新历史结构的生成模式字段。旧表历史固定返回 generation_record",
|
||
)
|
||
pipeline_stage: str | None = Field(
|
||
None,
|
||
description="兼容新历史结构的流水线阶段字段。旧 generation_records 无该字段,固定为 null",
|
||
)
|
||
status: str = Field(..., description="记录状态,例如 completed=已完成,failed=失败")
|
||
original_prompt: str = Field(..., description="用户原始提示词")
|
||
optimized_prompt: str | None = Field(None, description="优化后的提示词,可能为空")
|
||
duration: int | None = Field(None, description="视频时长,单位秒。图片记录通常为空")
|
||
aspect_ratio: str | None = Field(None, description="视频比例,例如 16:9。图片记录通常为空")
|
||
resolution: str | None = Field(None, description="视频分辨率,例如 480p。图片记录通常为空")
|
||
image_size: str | None = Field(None, description="图片分辨率档位,例如 2K")
|
||
image_proportion: str | None = Field(None, description="图片比例,例如 1:1")
|
||
image_px: str | None = Field(None, description="图片像素尺寸,例如 2048x2048")
|
||
references: list[dict] | None = Field(
|
||
None,
|
||
description="旧接口字段名,来源于 generation_records.media_references 解析后的参考素材列表",
|
||
)
|
||
media_references: list[dict] | None = Field(
|
||
None,
|
||
description="兼容新历史结构字段名,和 references 内容一致",
|
||
)
|
||
provider_task_id: str | None = Field(None, description="第三方服务商任务ID,旧表使用 seedance_task_id 兼容填充")
|
||
seedance_task_id: str | None = Field(None, description="旧服务命名的第三方任务ID字段")
|
||
remote_result_url: str | None = Field(
|
||
None,
|
||
description="第三方远程结果地址。旧 generation_records 未保存该字段,固定为 null",
|
||
)
|
||
image_url: str | None = Field(None, description="最终图片地址。图片记录完成后通常有值")
|
||
video_url: str | None = Field(None, description="最终视频地址。视频记录完成后通常有值")
|
||
video_cover_url: str | None = Field(None, description="视频封面图片地址。视频记录完成且封面截帧成功后通常有值")
|
||
engine_id: str | None = Field(
|
||
None,
|
||
description="兼容新历史结构的引擎ID字段。旧 generation_records 未保存该字段,固定为 null",
|
||
)
|
||
engine_snapshot: dict | None = Field(
|
||
None,
|
||
description="兼容新历史结构的引擎快照字段。旧 generation_records 未保存该字段,固定为 null",
|
||
)
|
||
credits_cost: float = Field(0.0, description="本次记录总消耗积分")
|
||
text_credits_cost: float = Field(0.0, description="文本优化或文本处理消耗积分")
|
||
text_tokens_used: int = Field(0, description="文本 token 使用量")
|
||
image_tokens_used: int = Field(0, description="图片 token 使用量")
|
||
video_tokens_used: int = Field(0, description="视频 token 使用量")
|
||
retry_count: int = Field(
|
||
0,
|
||
description="兼容新历史结构的重试次数字段。旧 generation_records 未保存该字段,固定为 0",
|
||
)
|
||
poll_count: int = Field(
|
||
0,
|
||
description="兼容新历史结构的轮询次数字段。旧 generation_records 未保存该字段,固定为 0",
|
||
)
|
||
error_message: str | None = Field(None, description="错误信息。成功记录一般为 null")
|
||
created_at: NaiveDatetimeOptional = Field(None, description="记录创建时间")
|
||
generated_at: NaiveDatetimeOptional = Field(None, description="生成完成时间")
|
||
|
||
|
||
GenerationAIHistoryItemOut = Annotated[
|
||
GenerationAITaskOut | GenerationAIRecordHistoryItemOut,
|
||
Field(discriminator="source_type"),
|
||
]
|
||
|
||
|
||
class GenerationAIHistoryDayGroupOut(BaseModel):
|
||
"""AI生成历史按天分组响应项。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"generated_date": "2026-05-27",
|
||
"total": 18,
|
||
"items": [],
|
||
}
|
||
}
|
||
)
|
||
|
||
generated_date: str = Field(..., description="生成日期,格式:YYYY-MM-DD")
|
||
total: int = Field(..., description="当前生成日期下的生成成功记录总数")
|
||
items: list[GenerationAIHistoryItemOut] = Field(
|
||
default_factory=list,
|
||
description=(
|
||
"当前生成日期下倒序前10条生成记录详情。"
|
||
"history_source=chat_task/hot_opening_replicate/shot_replicate 时 item 为 GenerationAITaskOut;"
|
||
"history_source=generation_record 时 item 为 GenerationAIRecordHistoryItemOut"
|
||
),
|
||
)
|
||
|
||
|
||
class GenerationAIHistoryGroupedOut(BaseModel):
|
||
"""AI生成历史日期分组列表响应体。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"total_days": 2,
|
||
"page": 1,
|
||
"page_size": 10,
|
||
"groups": [
|
||
{
|
||
"generated_date": "2026-05-27",
|
||
"total": 18,
|
||
"items": [],
|
||
},
|
||
{
|
||
"generated_date": "2026-05-26",
|
||
"total": 6,
|
||
"items": [],
|
||
},
|
||
],
|
||
}
|
||
}
|
||
)
|
||
|
||
total_days: int = Field(..., description="当前生成类型下,用户一共有多少个生成日期分组")
|
||
page: int = Field(..., description="当前日期分组分页页码")
|
||
page_size: int = Field(..., description="当前每页返回的日期分组数量,最大10")
|
||
groups: list[GenerationAIHistoryDayGroupOut] = Field(
|
||
default_factory=list,
|
||
description="按生成日期倒序排列的历史记录分组列表",
|
||
)
|
||
|
||
|
||
class GenerationAIHistoryDayItemsOut(BaseModel):
|
||
"""指定日期下AI生成历史分页响应体。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"generated_date": "2026-05-27",
|
||
"total": 18,
|
||
"page": 2,
|
||
"page_size": 10,
|
||
"items": [],
|
||
}
|
||
}
|
||
)
|
||
|
||
generated_date: str = Field(..., description="当前查询的生成日期,格式:YYYY-MM-DD")
|
||
total: int = Field(..., description="当前日期下的生成成功记录总数")
|
||
page: int = Field(..., description="当前日期下的记录分页页码")
|
||
page_size: int = Field(..., description="当前日期下每页返回的生成记录数量")
|
||
items: list[GenerationAIHistoryItemOut] = Field(
|
||
default_factory=list,
|
||
description=(
|
||
"当前日期下的生成记录详情列表,按 generated_at 倒序排列。"
|
||
"history_source=chat_task/hot_opening_replicate/shot_replicate 时 item 为 GenerationAITaskOut;"
|
||
"history_source=generation_record 时 item 为 GenerationAIRecordHistoryItemOut"
|
||
),
|
||
) |