生成记录接口兼容图片类型|CHAT对话生成视频/图片任务API+celery异步模块完成
This commit is contained in:
@@ -0,0 +1,418 @@
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from app.schemas.common import NaiveDatetimeOptional
|
||||
|
||||
|
||||
class GenerationAIReference(BaseModel):
|
||||
"""AI生成任务参考素材。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"url": "https://example.com/reference.png",
|
||||
"type": "image",
|
||||
"name": "参考图.png",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
url: str = Field(
|
||||
...,
|
||||
description="参考素材地址,可以是图片地址或视频地址",
|
||||
examples=["https://example.com/reference.png"],
|
||||
)
|
||||
type: str = Field(
|
||||
...,
|
||||
description="参考素材类型:image=图片,video=视频",
|
||||
examples=["image"],
|
||||
)
|
||||
name: str | None = Field(
|
||||
None,
|
||||
description="参考素材名称,前端展示用,可为空",
|
||||
examples=["参考图.png"],
|
||||
)
|
||||
|
||||
|
||||
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 GenerationAITaskOut(BaseModel):
|
||||
"""AI生成任务详情响应体。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"id": "0019e0a44895b6d837d",
|
||||
"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,
|
||||
"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")
|
||||
project_id: str | None = Field(
|
||||
None,
|
||||
description="项目ID。当前 /generation-ai 任务不绑定项目,通常为 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="最终视频地址。视频任务完成后通常有值")
|
||||
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",
|
||||
"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,
|
||||
"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 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 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[GenerationAITaskOut] = Field(
|
||||
default_factory=list,
|
||||
description="当前生成日期下倒序前10条生成记录详情",
|
||||
)
|
||||
|
||||
|
||||
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[GenerationAITaskOut] = Field(
|
||||
default_factory=list,
|
||||
description="当前日期下的生成记录详情列表,按 generated_at 倒序排列",
|
||||
)
|
||||
Reference in New Issue
Block a user