home页生成记录API 补追文件
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.dependencies import get_current_user, get_db
|
||||
from app.enums.recent_generation import RecentGenerationModuleEnum
|
||||
from app.models.user import User
|
||||
from app.schemas.recent_generation import RecentGenerationGroupOut
|
||||
from app.services.recent_generation_service import (
|
||||
DEFAULT_RECENT_GENERATION_LIMIT,
|
||||
MAX_RECENT_GENERATION_LIMIT,
|
||||
list_recent_generations,
|
||||
)
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/recent-generations",
|
||||
tags=["recent-generations"],
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=RecentGenerationGroupOut,
|
||||
summary="获取当前用户各模块最近生成记录",
|
||||
description=(
|
||||
"获取当前登录用户在多个生成模块下最近生成成功的图片/视频记录。"
|
||||
"返回结构固定为 project、chat_ai、hot_opening_replicate、shot_replicate 四个数组。"
|
||||
"modules 不传时查询全部模块;modules 可重复传参指定一个或多个模块,例如 "
|
||||
"?modules=project&modules=chat_ai。"
|
||||
"limit 表示每个模块最多返回多少条,默认 5 条,最大 100 条。"
|
||||
"接口只查询和返回展示所需轻量字段,不返回 prompt、engine_snapshot、provider_response_json 等大字段。"
|
||||
),
|
||||
responses={
|
||||
200: {
|
||||
"description": "查询成功,固定返回四个模块数组;没有数据的模块返回空数组。",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"project": [],
|
||||
"chat_ai": [],
|
||||
"hot_opening_replicate": [],
|
||||
"shot_replicate": [
|
||||
{
|
||||
"generated_time": "2026-06-26T14:30:00",
|
||||
"result_url": "https://example.com/generate/video/demo.mp4?exp=1780000000&sign=xxxx",
|
||||
"cover_url": "https://example.com/generate/cover/demo.jpg?exp=1780000000&sign=xxxx",
|
||||
"module": "shot_replicate",
|
||||
"shot_task_set_id": "0019ef0000000000001",
|
||||
"shot_segment_id": "0019ef0000000000002",
|
||||
"module_project_id": "0019ef0000000000003",
|
||||
"module_step_id": "0019ef0000000000004",
|
||||
"generation_id": "0019ef0000000000005",
|
||||
"resource_type": "video",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
401: {"description": "未登录或 Token 无效"},
|
||||
403: {"description": "账号需要先设置登录密码或无权限"},
|
||||
422: {"description": "参数校验失败,例如 limit 超出范围或 modules 枚举值非法"},
|
||||
},
|
||||
)
|
||||
async def get_recent_generations(
|
||||
limit: Annotated[
|
||||
int,
|
||||
Query(
|
||||
ge=1,
|
||||
le=MAX_RECENT_GENERATION_LIMIT,
|
||||
description=(
|
||||
"每个模块返回的最近生成记录数量,默认 5,最大 100。"
|
||||
"例如 limit=10 表示 project/chat_ai/hot_opening_replicate/shot_replicate 每个模块最多返回 10 条。"
|
||||
),
|
||||
examples=[DEFAULT_RECENT_GENERATION_LIMIT],
|
||||
),
|
||||
] = DEFAULT_RECENT_GENERATION_LIMIT,
|
||||
modules: Annotated[
|
||||
list[RecentGenerationModuleEnum] | None,
|
||||
Query(
|
||||
description=(
|
||||
"模块枚举,可不传或重复传参。"
|
||||
"不传表示查询全部模块。"
|
||||
"可选值:"
|
||||
"project=项目生成 GenerationRecord;"
|
||||
"chat_ai=AI创作 ChatGenerationTask.generation_mode=chatapi_async;"
|
||||
"hot_opening_replicate=爆款开头复刻 ChatGenerationTask.generation_mode=hot_opening_replicate;"
|
||||
"shot_replicate=拆镜复刻 ChatGenerationTask.generation_mode=shot_replicate。"
|
||||
),
|
||||
examples=[["project", "chat_ai"]],
|
||||
),
|
||||
] = None,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> RecentGenerationGroupOut:
|
||||
return await list_recent_generations(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
modules=modules,
|
||||
limit=limit,
|
||||
)
|
||||
@@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from app.enums.generation_task import ChatGenerationTaskStatus, GenerationMode
|
||||
|
||||
|
||||
class RecentGenerationModuleEnum(StrEnum):
|
||||
"""最近生成记录接口支持的模块分组枚举。"""
|
||||
|
||||
PROJECT = "project"
|
||||
CHAT_AI = "chat_ai"
|
||||
HOT_OPENING_REPLICATE = "hot_opening_replicate"
|
||||
SHOT_REPLICATE = "shot_replicate"
|
||||
|
||||
|
||||
class RecentGenerationResourceTypeEnum(StrEnum):
|
||||
"""最近生成记录接口返回的资源类型枚举。"""
|
||||
|
||||
IMAGE = "image"
|
||||
VIDEO = "video"
|
||||
|
||||
|
||||
RECENT_GENERATION_ALL_MODULES: tuple[RecentGenerationModuleEnum, ...] = (
|
||||
RecentGenerationModuleEnum.PROJECT,
|
||||
RecentGenerationModuleEnum.CHAT_AI,
|
||||
RecentGenerationModuleEnum.HOT_OPENING_REPLICATE,
|
||||
RecentGenerationModuleEnum.SHOT_REPLICATE,
|
||||
)
|
||||
"""最近生成记录接口默认查询的全部模块。"""
|
||||
|
||||
|
||||
RECENT_GENERATION_CHAT_TASK_MODULES: tuple[RecentGenerationModuleEnum, ...] = (
|
||||
RecentGenerationModuleEnum.CHAT_AI,
|
||||
RecentGenerationModuleEnum.HOT_OPENING_REPLICATE,
|
||||
RecentGenerationModuleEnum.SHOT_REPLICATE,
|
||||
)
|
||||
"""来自 chat_generation_tasks 表的模块集合。"""
|
||||
|
||||
|
||||
RECENT_GENERATION_MODULE_TO_TASK_MODE: dict[RecentGenerationModuleEnum, GenerationMode] = {
|
||||
RecentGenerationModuleEnum.CHAT_AI: GenerationMode.CHATAPI_ASYNC,
|
||||
RecentGenerationModuleEnum.HOT_OPENING_REPLICATE: GenerationMode.HOT_OPENING_REPLICATE,
|
||||
RecentGenerationModuleEnum.SHOT_REPLICATE: GenerationMode.SHOT_REPLICATE,
|
||||
}
|
||||
"""最近生成记录模块枚举到 ChatGenerationTask.generation_mode 的映射。"""
|
||||
|
||||
|
||||
RECENT_GENERATION_TASK_MODE_VALUE_TO_MODULE: dict[str, RecentGenerationModuleEnum] = {
|
||||
task_mode.value: module
|
||||
for module, task_mode in RECENT_GENERATION_MODULE_TO_TASK_MODE.items()
|
||||
}
|
||||
"""ChatGenerationTask.generation_mode 字符串值到最近生成记录模块枚举的映射。"""
|
||||
|
||||
|
||||
RECENT_GENERATION_COMPLETED_STATUS = ChatGenerationTaskStatus.COMPLETED.value
|
||||
"""最近生成记录只展示生成成功的数据,状态值与 ChatGenerationTaskStatus.COMPLETED 保持一致。"""
|
||||
@@ -0,0 +1,119 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from app.enums.recent_generation import RecentGenerationModuleEnum, RecentGenerationResourceTypeEnum
|
||||
from app.schemas.common import NaiveDatetimeOptional
|
||||
|
||||
|
||||
class RecentGenerationItemOut(BaseModel):
|
||||
"""最近生成记录响应项。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"generated_time": "2026-06-26T14:30:00",
|
||||
"result_url": "https://example.com/generate/video/demo.mp4?exp=1780000000&sign=xxxx",
|
||||
"cover_url": "https://example.com/generate/cover/demo.jpg?exp=1780000000&sign=xxxx",
|
||||
"module": "shot_replicate",
|
||||
"shot_task_set_id": "0019ef0000000000001",
|
||||
"shot_segment_id": "0019ef0000000000002",
|
||||
"module_project_id": "0019ef0000000000003",
|
||||
"module_step_id": "0019ef0000000000004",
|
||||
"generation_id": "0019ef0000000000005",
|
||||
"resource_type": "video",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
generated_time: NaiveDatetimeOptional = Field(
|
||||
None,
|
||||
description="生成完成时间。优先使用 generated_at;历史数据 generated_at 为空时回退 updated_at,再回退 created_at。",
|
||||
)
|
||||
result_url: str | None = Field(
|
||||
None,
|
||||
description="生成结果链接。resource_type=image 时为图片链接;resource_type=video 时为视频链接。返回前会按项目资源签名规则追加 exp/sign。",
|
||||
)
|
||||
cover_url: str | None = Field(
|
||||
None,
|
||||
description="视频封面链接。仅视频资源通常有值;图片资源或历史无封面数据时返回 null。返回前会按项目资源签名规则追加 exp/sign。",
|
||||
)
|
||||
module: RecentGenerationModuleEnum = Field(
|
||||
...,
|
||||
description=(
|
||||
"数据模块枚举:"
|
||||
"project=项目生成 GenerationRecord;"
|
||||
"chat_ai=AI创作 ChatGenerationTask.generation_mode=chatapi_async;"
|
||||
"hot_opening_replicate=爆款开头复刻 ChatGenerationTask.generation_mode=hot_opening_replicate;"
|
||||
"shot_replicate=拆镜复刻 ChatGenerationTask.generation_mode=shot_replicate。"
|
||||
),
|
||||
)
|
||||
shot_task_set_id: str | None = Field(
|
||||
None,
|
||||
description="关联拆镜总任务ID。仅 shot_replicate 模块可能有值,来源 shot_replicate_segments.task_set_id;其他模块返回 null。",
|
||||
)
|
||||
shot_segment_id: str | None = Field(
|
||||
None,
|
||||
description="关联拆镜片段ID。仅 shot_replicate 模块可能有值,来源 shot_replicate_segments.id;其他模块返回 null。",
|
||||
)
|
||||
module_project_id: str | None = Field(
|
||||
None,
|
||||
description="通用模块项目ID。hot_opening_replicate/shot_replicate 模块可能有值,来源 module_generation_steps.project_id;project/chat_ai 返回 null。",
|
||||
)
|
||||
module_step_id: str | None = Field(
|
||||
None,
|
||||
description="通用模块步骤ID。hot_opening_replicate/shot_replicate 模块可能有值,来源 module_generation_steps.id;project/chat_ai 返回 null。",
|
||||
)
|
||||
generation_id: str = Field(
|
||||
...,
|
||||
description="生成ID。project 模块为 generation_records.id;chat_ai/hot_opening_replicate/shot_replicate 模块为 chat_generation_tasks.id。",
|
||||
)
|
||||
resource_type: RecentGenerationResourceTypeEnum = Field(
|
||||
...,
|
||||
description="资源类型枚举:image=图片资源;video=视频资源。前端可据此决定预览组件。",
|
||||
)
|
||||
|
||||
|
||||
class RecentGenerationGroupOut(BaseModel):
|
||||
"""最近生成记录固定分组响应。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"project": [],
|
||||
"chat_ai": [
|
||||
{
|
||||
"generated_time": "2026-06-26T14:30:00",
|
||||
"result_url": "https://example.com/generate/image/demo.png?exp=1780000000&sign=xxxx",
|
||||
"cover_url": None,
|
||||
"module": "chat_ai",
|
||||
"shot_task_set_id": None,
|
||||
"shot_segment_id": None,
|
||||
"module_project_id": None,
|
||||
"module_step_id": None,
|
||||
"generation_id": "0019ef0000000000010",
|
||||
"resource_type": "image",
|
||||
}
|
||||
],
|
||||
"hot_opening_replicate": [],
|
||||
"shot_replicate": [],
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
project: list[RecentGenerationItemOut] = Field(
|
||||
default_factory=list,
|
||||
description="项目生成最近记录数组,来源 generation_records。未查询该模块或无数据时返回空数组。",
|
||||
)
|
||||
chat_ai: list[RecentGenerationItemOut] = Field(
|
||||
default_factory=list,
|
||||
description="AI创作最近记录数组,来源 chat_generation_tasks,条件 generation_mode=chatapi_async。未查询该模块或无数据时返回空数组。",
|
||||
)
|
||||
hot_opening_replicate: list[RecentGenerationItemOut] = Field(
|
||||
default_factory=list,
|
||||
description="爆款开头复刻最近记录数组,来源 chat_generation_tasks,条件 generation_mode=hot_opening_replicate。未查询该模块或无数据时返回空数组。",
|
||||
)
|
||||
shot_replicate: list[RecentGenerationItemOut] = Field(
|
||||
default_factory=list,
|
||||
description="拆镜复刻最近记录数组,来源 chat_generation_tasks,条件 generation_mode=shot_replicate。未查询该模块或无数据时返回空数组。",
|
||||
)
|
||||
@@ -0,0 +1,407 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from datetime import datetime
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from sqlalchemy import and_, func, or_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.enums.generation_task import GenerationType
|
||||
from app.enums.recent_generation import (
|
||||
RECENT_GENERATION_ALL_MODULES,
|
||||
RECENT_GENERATION_CHAT_TASK_MODULES,
|
||||
RECENT_GENERATION_COMPLETED_STATUS,
|
||||
RECENT_GENERATION_MODULE_TO_TASK_MODE,
|
||||
RECENT_GENERATION_TASK_MODE_VALUE_TO_MODULE,
|
||||
RecentGenerationModuleEnum,
|
||||
RecentGenerationResourceTypeEnum,
|
||||
)
|
||||
from app.models.chat_generation_task import ChatGenerationTask
|
||||
from app.models.generation_record import GenerationRecord
|
||||
from app.models.module_generation_step import ModuleGenerationStep
|
||||
from app.models.shot_replicate_segment import ShotReplicateSegment
|
||||
from app.schemas.recent_generation import RecentGenerationGroupOut, RecentGenerationItemOut
|
||||
from app.services.resource_signed_url_service import build_resource_signed_url
|
||||
|
||||
DEFAULT_RECENT_GENERATION_LIMIT = 5
|
||||
MAX_RECENT_GENERATION_LIMIT = 100
|
||||
|
||||
|
||||
class _StepLinkInfo(TypedDict):
|
||||
module_project_id: str | None
|
||||
module_step_id: str | None
|
||||
module: str | None
|
||||
|
||||
|
||||
class _ShotLinkInfo(TypedDict):
|
||||
shot_task_set_id: str | None
|
||||
shot_segment_id: str | None
|
||||
|
||||
|
||||
def _normalize_limit(limit: int | None) -> int:
|
||||
if limit is None:
|
||||
return DEFAULT_RECENT_GENERATION_LIMIT
|
||||
return min(max(int(limit), 1), MAX_RECENT_GENERATION_LIMIT)
|
||||
|
||||
|
||||
def _normalize_modules(
|
||||
modules: Iterable[RecentGenerationModuleEnum] | None,
|
||||
) -> list[RecentGenerationModuleEnum]:
|
||||
if not modules:
|
||||
return list(RECENT_GENERATION_ALL_MODULES)
|
||||
|
||||
normalized: list[RecentGenerationModuleEnum] = []
|
||||
seen: set[RecentGenerationModuleEnum] = set()
|
||||
for module in modules:
|
||||
module_enum = RecentGenerationModuleEnum(module)
|
||||
if module_enum not in seen:
|
||||
normalized.append(module_enum)
|
||||
seen.add(module_enum)
|
||||
return normalized
|
||||
|
||||
|
||||
def _has_url(column) -> Any:
|
||||
return and_(column.is_not(None), column != "")
|
||||
|
||||
|
||||
def _detect_resource_type(
|
||||
gen_type: str | None,
|
||||
image_url: str | None,
|
||||
video_url: str | None,
|
||||
) -> RecentGenerationResourceTypeEnum:
|
||||
gen_type_value = (gen_type or "").strip().lower()
|
||||
|
||||
if gen_type_value == GenerationType.IMAGE.value and image_url:
|
||||
return RecentGenerationResourceTypeEnum.IMAGE
|
||||
if gen_type_value == GenerationType.VIDEO.value and video_url:
|
||||
return RecentGenerationResourceTypeEnum.VIDEO
|
||||
if video_url:
|
||||
return RecentGenerationResourceTypeEnum.VIDEO
|
||||
return RecentGenerationResourceTypeEnum.IMAGE
|
||||
|
||||
|
||||
def _sign_url(url: str | None) -> str | None:
|
||||
if not url:
|
||||
return None
|
||||
return build_resource_signed_url(url)
|
||||
|
||||
|
||||
def _build_item(
|
||||
*,
|
||||
generation_id: str,
|
||||
module: RecentGenerationModuleEnum,
|
||||
gen_type: str | None,
|
||||
image_url: str | None,
|
||||
video_url: str | None,
|
||||
video_cover_url: str | None,
|
||||
generated_time: datetime | None,
|
||||
step_info: _StepLinkInfo | None = None,
|
||||
shot_info: _ShotLinkInfo | None = None,
|
||||
) -> RecentGenerationItemOut:
|
||||
resource_type = _detect_resource_type(
|
||||
gen_type=gen_type,
|
||||
image_url=image_url,
|
||||
video_url=video_url,
|
||||
)
|
||||
|
||||
raw_result_url = video_url if resource_type == RecentGenerationResourceTypeEnum.VIDEO else image_url
|
||||
raw_cover_url = video_cover_url if resource_type == RecentGenerationResourceTypeEnum.VIDEO else None
|
||||
|
||||
return RecentGenerationItemOut(
|
||||
generated_time=generated_time,
|
||||
result_url=_sign_url(raw_result_url),
|
||||
cover_url=_sign_url(raw_cover_url),
|
||||
module=module,
|
||||
shot_task_set_id=shot_info["shot_task_set_id"] if shot_info else None,
|
||||
shot_segment_id=shot_info["shot_segment_id"] if shot_info else None,
|
||||
module_project_id=step_info["module_project_id"] if step_info else None,
|
||||
module_step_id=step_info["module_step_id"] if step_info else None,
|
||||
generation_id=generation_id,
|
||||
resource_type=resource_type,
|
||||
)
|
||||
|
||||
|
||||
async def _list_project_recent_items(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
user_id: str,
|
||||
limit: int,
|
||||
) -> list[RecentGenerationItemOut]:
|
||||
generated_time_expr = func.coalesce(
|
||||
GenerationRecord.generated_at,
|
||||
GenerationRecord.updated_at,
|
||||
GenerationRecord.created_at,
|
||||
).label("generated_time")
|
||||
|
||||
stmt = (
|
||||
select(
|
||||
GenerationRecord.id.label("generation_id"),
|
||||
GenerationRecord.gen_type.label("gen_type"),
|
||||
GenerationRecord.image_url.label("image_url"),
|
||||
GenerationRecord.video_url.label("video_url"),
|
||||
GenerationRecord.video_cover_url.label("video_cover_url"),
|
||||
generated_time_expr,
|
||||
)
|
||||
.where(
|
||||
GenerationRecord.user_id == user_id,
|
||||
GenerationRecord.deleted_at.is_(None),
|
||||
GenerationRecord.status == RECENT_GENERATION_COMPLETED_STATUS,
|
||||
or_(_has_url(GenerationRecord.image_url), _has_url(GenerationRecord.video_url)),
|
||||
)
|
||||
.order_by(generated_time_expr.desc(), GenerationRecord.created_at.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
|
||||
rows = (await db.execute(stmt)).mappings().all()
|
||||
return [
|
||||
_build_item(
|
||||
generation_id=row["generation_id"],
|
||||
module=RecentGenerationModuleEnum.PROJECT,
|
||||
gen_type=row["gen_type"],
|
||||
image_url=row["image_url"],
|
||||
video_url=row["video_url"],
|
||||
video_cover_url=row["video_cover_url"],
|
||||
generated_time=row["generated_time"],
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
|
||||
async def _list_chat_task_recent_rows(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
user_id: str,
|
||||
modules: list[RecentGenerationModuleEnum],
|
||||
limit: int,
|
||||
) -> list[dict[str, Any]]:
|
||||
task_mode_values = [
|
||||
RECENT_GENERATION_MODULE_TO_TASK_MODE[module].value
|
||||
for module in modules
|
||||
if module in RECENT_GENERATION_CHAT_TASK_MODULES
|
||||
]
|
||||
if not task_mode_values:
|
||||
return []
|
||||
|
||||
generated_time_expr = func.coalesce(
|
||||
ChatGenerationTask.generated_at,
|
||||
ChatGenerationTask.updated_at,
|
||||
ChatGenerationTask.created_at,
|
||||
)
|
||||
|
||||
ranked_subquery = (
|
||||
select(
|
||||
ChatGenerationTask.id.label("generation_id"),
|
||||
ChatGenerationTask.generation_mode.label("generation_mode"),
|
||||
ChatGenerationTask.gen_type.label("gen_type"),
|
||||
ChatGenerationTask.image_url.label("image_url"),
|
||||
ChatGenerationTask.video_url.label("video_url"),
|
||||
ChatGenerationTask.video_cover_url.label("video_cover_url"),
|
||||
generated_time_expr.label("generated_time"),
|
||||
func.row_number()
|
||||
.over(
|
||||
partition_by=ChatGenerationTask.generation_mode,
|
||||
order_by=(generated_time_expr.desc(), ChatGenerationTask.created_at.desc()),
|
||||
)
|
||||
.label("row_num"),
|
||||
)
|
||||
.where(
|
||||
ChatGenerationTask.user_id == user_id,
|
||||
ChatGenerationTask.deleted_at.is_(None),
|
||||
ChatGenerationTask.status == RECENT_GENERATION_COMPLETED_STATUS,
|
||||
ChatGenerationTask.generation_mode.in_(task_mode_values),
|
||||
or_(_has_url(ChatGenerationTask.image_url), _has_url(ChatGenerationTask.video_url)),
|
||||
)
|
||||
.subquery()
|
||||
)
|
||||
|
||||
stmt = (
|
||||
select(ranked_subquery)
|
||||
.where(ranked_subquery.c.row_num <= limit)
|
||||
.order_by(ranked_subquery.c.generation_mode.asc(), ranked_subquery.c.generated_time.desc())
|
||||
)
|
||||
|
||||
return [dict(row) for row in (await db.execute(stmt)).mappings().all()]
|
||||
|
||||
|
||||
async def _load_step_link_map(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
chat_task_ids: list[str],
|
||||
) -> dict[str, _StepLinkInfo]:
|
||||
if not chat_task_ids:
|
||||
return {}
|
||||
|
||||
stmt = (
|
||||
select(
|
||||
ModuleGenerationStep.chat_task_id.label("chat_task_id"),
|
||||
ModuleGenerationStep.id.label("module_step_id"),
|
||||
ModuleGenerationStep.project_id.label("module_project_id"),
|
||||
ModuleGenerationStep.module.label("module"),
|
||||
ModuleGenerationStep.is_current.label("is_current"),
|
||||
ModuleGenerationStep.updated_at.label("updated_at"),
|
||||
)
|
||||
.where(
|
||||
ModuleGenerationStep.deleted_at.is_(None),
|
||||
ModuleGenerationStep.chat_task_id.in_(chat_task_ids),
|
||||
ModuleGenerationStep.module.in_(
|
||||
[
|
||||
RecentGenerationModuleEnum.HOT_OPENING_REPLICATE.value,
|
||||
RecentGenerationModuleEnum.SHOT_REPLICATE.value,
|
||||
]
|
||||
),
|
||||
)
|
||||
.order_by(
|
||||
ModuleGenerationStep.chat_task_id.asc(),
|
||||
ModuleGenerationStep.is_current.desc(),
|
||||
ModuleGenerationStep.updated_at.desc(),
|
||||
)
|
||||
)
|
||||
|
||||
link_map: dict[str, _StepLinkInfo] = {}
|
||||
rows = (await db.execute(stmt)).mappings().all()
|
||||
for row in rows:
|
||||
chat_task_id = row["chat_task_id"]
|
||||
if not chat_task_id or chat_task_id in link_map:
|
||||
continue
|
||||
link_map[chat_task_id] = {
|
||||
"module_project_id": row["module_project_id"],
|
||||
"module_step_id": row["module_step_id"],
|
||||
"module": row["module"],
|
||||
}
|
||||
return link_map
|
||||
|
||||
|
||||
async def _load_shot_link_map(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
module_project_ids: list[str],
|
||||
) -> dict[str, _ShotLinkInfo]:
|
||||
if not module_project_ids:
|
||||
return {}
|
||||
|
||||
stmt = (
|
||||
select(
|
||||
ShotReplicateSegment.module_project_id.label("module_project_id"),
|
||||
ShotReplicateSegment.id.label("shot_segment_id"),
|
||||
ShotReplicateSegment.task_set_id.label("shot_task_set_id"),
|
||||
)
|
||||
.where(
|
||||
ShotReplicateSegment.deleted_at.is_(None),
|
||||
ShotReplicateSegment.module_project_id.in_(module_project_ids),
|
||||
)
|
||||
.order_by(ShotReplicateSegment.updated_at.desc())
|
||||
)
|
||||
|
||||
link_map: dict[str, _ShotLinkInfo] = {}
|
||||
rows = (await db.execute(stmt)).mappings().all()
|
||||
for row in rows:
|
||||
module_project_id = row["module_project_id"]
|
||||
if not module_project_id or module_project_id in link_map:
|
||||
continue
|
||||
link_map[module_project_id] = {
|
||||
"shot_task_set_id": row["shot_task_set_id"],
|
||||
"shot_segment_id": row["shot_segment_id"],
|
||||
}
|
||||
return link_map
|
||||
|
||||
|
||||
async def _build_chat_task_group_items(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
rows: list[dict[str, Any]],
|
||||
) -> dict[RecentGenerationModuleEnum, list[RecentGenerationItemOut]]:
|
||||
grouped: dict[RecentGenerationModuleEnum, list[RecentGenerationItemOut]] = {
|
||||
RecentGenerationModuleEnum.CHAT_AI: [],
|
||||
RecentGenerationModuleEnum.HOT_OPENING_REPLICATE: [],
|
||||
RecentGenerationModuleEnum.SHOT_REPLICATE: [],
|
||||
}
|
||||
if not rows:
|
||||
return grouped
|
||||
|
||||
module_task_rows: list[dict[str, Any]] = []
|
||||
module_chat_task_ids: list[str] = []
|
||||
for row in rows:
|
||||
module = RECENT_GENERATION_TASK_MODE_VALUE_TO_MODULE.get(row["generation_mode"])
|
||||
if module in (
|
||||
RecentGenerationModuleEnum.HOT_OPENING_REPLICATE,
|
||||
RecentGenerationModuleEnum.SHOT_REPLICATE,
|
||||
):
|
||||
module_task_rows.append(row)
|
||||
module_chat_task_ids.append(row["generation_id"])
|
||||
|
||||
step_link_map = await _load_step_link_map(db, chat_task_ids=module_chat_task_ids)
|
||||
|
||||
shot_project_ids = [
|
||||
step_info["module_project_id"]
|
||||
for row in module_task_rows
|
||||
if (step_info := step_link_map.get(row["generation_id"]))
|
||||
and step_info.get("module") == RecentGenerationModuleEnum.SHOT_REPLICATE.value
|
||||
and step_info.get("module_project_id")
|
||||
]
|
||||
shot_link_map = await _load_shot_link_map(db, module_project_ids=shot_project_ids)
|
||||
|
||||
for row in rows:
|
||||
module = RECENT_GENERATION_TASK_MODE_VALUE_TO_MODULE.get(row["generation_mode"])
|
||||
if not module:
|
||||
continue
|
||||
|
||||
step_info = step_link_map.get(row["generation_id"])
|
||||
shot_info = None
|
||||
if module == RecentGenerationModuleEnum.SHOT_REPLICATE and step_info:
|
||||
module_project_id = step_info.get("module_project_id")
|
||||
if module_project_id:
|
||||
shot_info = shot_link_map.get(module_project_id)
|
||||
|
||||
grouped[module].append(
|
||||
_build_item(
|
||||
generation_id=row["generation_id"],
|
||||
module=module,
|
||||
gen_type=row["gen_type"],
|
||||
image_url=row["image_url"],
|
||||
video_url=row["video_url"],
|
||||
video_cover_url=row["video_cover_url"],
|
||||
generated_time=row["generated_time"],
|
||||
step_info=step_info,
|
||||
shot_info=shot_info,
|
||||
)
|
||||
)
|
||||
|
||||
return grouped
|
||||
|
||||
|
||||
async def list_recent_generations(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
user_id: str,
|
||||
modules: Iterable[RecentGenerationModuleEnum] | None = None,
|
||||
limit: int | None = None,
|
||||
) -> RecentGenerationGroupOut:
|
||||
"""获取当前用户各模块最近生成成功的图片/视频记录。"""
|
||||
|
||||
normalized_limit = _normalize_limit(limit)
|
||||
normalized_modules = _normalize_modules(modules)
|
||||
|
||||
response = RecentGenerationGroupOut()
|
||||
|
||||
if RecentGenerationModuleEnum.PROJECT in normalized_modules:
|
||||
response.project = await _list_project_recent_items(
|
||||
db,
|
||||
user_id=user_id,
|
||||
limit=normalized_limit,
|
||||
)
|
||||
|
||||
chat_modules = [module for module in normalized_modules if module in RECENT_GENERATION_CHAT_TASK_MODULES]
|
||||
if chat_modules:
|
||||
chat_rows = await _list_chat_task_recent_rows(
|
||||
db,
|
||||
user_id=user_id,
|
||||
modules=chat_modules,
|
||||
limit=normalized_limit,
|
||||
)
|
||||
chat_grouped = await _build_chat_task_group_items(db, rows=chat_rows)
|
||||
response.chat_ai = chat_grouped[RecentGenerationModuleEnum.CHAT_AI]
|
||||
response.hot_opening_replicate = chat_grouped[RecentGenerationModuleEnum.HOT_OPENING_REPLICATE]
|
||||
response.shot_replicate = chat_grouped[RecentGenerationModuleEnum.SHOT_REPLICATE]
|
||||
|
||||
return response
|
||||
Reference in New Issue
Block a user