AI CHAT对话降序获取升序输出,追加历史记录接口生成记录模块
This commit is contained in:
@@ -160,3 +160,5 @@ pnpm-debug.log*
|
|||||||
.vite/
|
.vite/
|
||||||
.next/
|
.next/
|
||||||
.nuxt/
|
.nuxt/
|
||||||
|
*.zip
|
||||||
|
*.bat
|
||||||
@@ -140,7 +140,15 @@ async def list_tasks(
|
|||||||
page,
|
page,
|
||||||
page_size,
|
page_size,
|
||||||
)
|
)
|
||||||
return GenerationAITaskListOut(total=total, items=[record_to_out(i) for i in items])
|
|
||||||
|
# ====================== 在这里加排序(最新在前)======================
|
||||||
|
# 按 created_at 降序(没有则用 id 降序)
|
||||||
|
items_sorted = sorted(
|
||||||
|
items,
|
||||||
|
key=lambda x: x.created_at if x.created_at is not None else x.id,
|
||||||
|
reverse=False # 降序
|
||||||
|
)
|
||||||
|
return GenerationAITaskListOut(total=total, items=[record_to_out(i) for i in items_sorted])
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
@@ -149,6 +157,8 @@ async def list_tasks(
|
|||||||
summary="获取AI生成历史日期分组",
|
summary="获取AI生成历史日期分组",
|
||||||
description=(
|
description=(
|
||||||
"按生成完成日期倒序返回当前用户的AI生成历史记录。"
|
"按生成完成日期倒序返回当前用户的AI生成历史记录。"
|
||||||
|
"默认查询 chat_generation_tasks / ChatGenerationTask 新任务历史。"
|
||||||
|
"当 history_source=generation_record 时,查询 generation_records / GenerationRecord 旧历史。"
|
||||||
"该接口只返回生成成功的任务,即 status=completed 且 generated_at 不为空的数据。"
|
"该接口只返回生成成功的任务,即 status=completed 且 generated_at 不为空的数据。"
|
||||||
"必须通过 gen_type 区分图片和视频。"
|
"必须通过 gen_type 区分图片和视频。"
|
||||||
"分页对象是生成日期,不是单条记录。"
|
"分页对象是生成日期,不是单条记录。"
|
||||||
@@ -160,7 +170,7 @@ async def list_tasks(
|
|||||||
"description": "查询成功,返回按生成日期分组的历史记录",
|
"description": "查询成功,返回按生成日期分组的历史记录",
|
||||||
},
|
},
|
||||||
400: {
|
400: {
|
||||||
"description": "参数错误,例如 gen_type 不是 image 或 video",
|
"description": "参数错误,例如 gen_type 不是 image 或 video,或 history_source 不支持",
|
||||||
},
|
},
|
||||||
401: {
|
401: {
|
||||||
"description": "未登录或 Token 无效",
|
"description": "未登录或 Token 无效",
|
||||||
@@ -186,6 +196,14 @@ async def list_history_grouped_days(
|
|||||||
description="每页返回的生成日期数量,范围 1~10,最大只能获取10天",
|
description="每页返回的生成日期数量,范围 1~10,最大只能获取10天",
|
||||||
examples=[10],
|
examples=[10],
|
||||||
),
|
),
|
||||||
|
history_source: str | None = Query(
|
||||||
|
None,
|
||||||
|
description=(
|
||||||
|
"历史数据来源。默认不传或传 chat_task 查询 chat_generation_tasks / ChatGenerationTask;"
|
||||||
|
"传 generation_record 查询 generation_records / GenerationRecord 旧历史数据"
|
||||||
|
),
|
||||||
|
examples=["generation_record"],
|
||||||
|
),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
@@ -195,6 +213,7 @@ async def list_history_grouped_days(
|
|||||||
gen_type=gen_type,
|
gen_type=gen_type,
|
||||||
page=page,
|
page=page,
|
||||||
page_size=page_size,
|
page_size=page_size,
|
||||||
|
history_source=history_source,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -204,6 +223,8 @@ async def list_history_grouped_days(
|
|||||||
summary="获取指定日期下的AI生成历史分页",
|
summary="获取指定日期下的AI生成历史分页",
|
||||||
description=(
|
description=(
|
||||||
"获取某一个生成日期下的生成成功记录分页。"
|
"获取某一个生成日期下的生成成功记录分页。"
|
||||||
|
"默认查询 chat_generation_tasks / ChatGenerationTask 新任务历史。"
|
||||||
|
"当 history_source=generation_record 时,查询 generation_records / GenerationRecord 旧历史。"
|
||||||
"该接口用于前端在历史分组列表中继续加载某一天的后续记录。"
|
"该接口用于前端在历史分组列表中继续加载某一天的后续记录。"
|
||||||
"例如 /history 接口中某一天 total=18,但 items 只返回前10条,"
|
"例如 /history 接口中某一天 total=18,但 items 只返回前10条,"
|
||||||
"则前端可以调用本接口 page=2&page_size=10 获取该日期下剩余记录。"
|
"则前端可以调用本接口 page=2&page_size=10 获取该日期下剩余记录。"
|
||||||
@@ -214,7 +235,7 @@ async def list_history_grouped_days(
|
|||||||
"description": "查询成功,返回指定日期下的历史记录分页",
|
"description": "查询成功,返回指定日期下的历史记录分页",
|
||||||
},
|
},
|
||||||
400: {
|
400: {
|
||||||
"description": "参数错误,例如 generated_date 格式不是 YYYY-MM-DD,或 gen_type 不合法",
|
"description": "参数错误,例如 generated_date 格式不是 YYYY-MM-DD,gen_type 不合法,或 history_source 不支持",
|
||||||
},
|
},
|
||||||
401: {
|
401: {
|
||||||
"description": "未登录或 Token 无效",
|
"description": "未登录或 Token 无效",
|
||||||
@@ -245,6 +266,14 @@ async def list_history_day_items(
|
|||||||
description="当前日期下每页返回的生成记录数量,范围 1~100",
|
description="当前日期下每页返回的生成记录数量,范围 1~100",
|
||||||
examples=[10],
|
examples=[10],
|
||||||
),
|
),
|
||||||
|
history_source: str | None = Query(
|
||||||
|
None,
|
||||||
|
description=(
|
||||||
|
"历史数据来源。默认不传或传 chat_task 查询 chat_generation_tasks / ChatGenerationTask;"
|
||||||
|
"传 generation_record 查询 generation_records / GenerationRecord 旧历史数据"
|
||||||
|
),
|
||||||
|
examples=["generation_record"],
|
||||||
|
),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
@@ -255,6 +284,7 @@ async def list_history_day_items(
|
|||||||
generated_date=generated_date,
|
generated_date=generated_date,
|
||||||
page=page,
|
page=page,
|
||||||
page_size=page_size,
|
page_size=page_size,
|
||||||
|
history_source=history_source,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from typing import Annotated, Literal
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
from app.schemas.common import NaiveDatetimeOptional
|
from app.schemas.common import NaiveDatetimeOptional
|
||||||
@@ -157,6 +159,7 @@ class GenerationAITaskOut(BaseModel):
|
|||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
"example": {
|
"example": {
|
||||||
"id": "0019e0a44895b6d837d",
|
"id": "0019e0a44895b6d837d",
|
||||||
|
"source_type": "chat_task",
|
||||||
"project_id": None,
|
"project_id": None,
|
||||||
"gen_type": "image",
|
"gen_type": "image",
|
||||||
"generation_mode": "chatapi_async",
|
"generation_mode": "chatapi_async",
|
||||||
@@ -204,6 +207,10 @@ class GenerationAITaskOut(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
id: str = Field(..., description="生成任务ID")
|
id: str = Field(..., description="生成任务ID")
|
||||||
|
source_type: Literal["chat_task"] = Field(
|
||||||
|
"chat_task",
|
||||||
|
description="历史记录来源。ChatGenerationTask 新任务历史固定为 chat_task",
|
||||||
|
)
|
||||||
project_id: str | None = Field(
|
project_id: str | None = Field(
|
||||||
None,
|
None,
|
||||||
description="项目ID。当前 /generation-ai 任务不绑定项目,通常为 null",
|
description="项目ID。当前 /generation-ai 任务不绑定项目,通常为 null",
|
||||||
@@ -274,6 +281,7 @@ class GenerationAITaskListOut(BaseModel):
|
|||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": "0019e0a44895b6d837d",
|
"id": "0019e0a44895b6d837d",
|
||||||
|
"source_type": "chat_task",
|
||||||
"project_id": None,
|
"project_id": None,
|
||||||
"gen_type": "image",
|
"gen_type": "image",
|
||||||
"generation_mode": "chatapi_async",
|
"generation_mode": "chatapi_async",
|
||||||
@@ -338,6 +346,124 @@ class GenerationAIRetryOut(BaseModel):
|
|||||||
message: str = Field(..., 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": "默认项目",
|
||||||
|
"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,
|
||||||
|
"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;项目不存在时为空")
|
||||||
|
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="最终视频地址。视频记录完成后通常有值")
|
||||||
|
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):
|
class GenerationAIHistoryDayGroupOut(BaseModel):
|
||||||
"""AI生成历史按天分组响应项。"""
|
"""AI生成历史按天分组响应项。"""
|
||||||
|
|
||||||
@@ -353,9 +479,13 @@ class GenerationAIHistoryDayGroupOut(BaseModel):
|
|||||||
|
|
||||||
generated_date: str = Field(..., description="生成日期,格式:YYYY-MM-DD")
|
generated_date: str = Field(..., description="生成日期,格式:YYYY-MM-DD")
|
||||||
total: int = Field(..., description="当前生成日期下的生成成功记录总数")
|
total: int = Field(..., description="当前生成日期下的生成成功记录总数")
|
||||||
items: list[GenerationAITaskOut] = Field(
|
items: list[GenerationAIHistoryItemOut] = Field(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
description="当前生成日期下倒序前10条生成记录详情",
|
description=(
|
||||||
|
"当前生成日期下倒序前10条生成记录详情。"
|
||||||
|
"默认 history_source=chat_task 时 item 为 GenerationAITaskOut;"
|
||||||
|
"history_source=generation_record 时 item 为 GenerationAIRecordHistoryItemOut"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -412,7 +542,11 @@ class GenerationAIHistoryDayItemsOut(BaseModel):
|
|||||||
total: int = Field(..., description="当前日期下的生成成功记录总数")
|
total: int = Field(..., description="当前日期下的生成成功记录总数")
|
||||||
page: int = Field(..., description="当前日期下的记录分页页码")
|
page: int = Field(..., description="当前日期下的记录分页页码")
|
||||||
page_size: int = Field(..., description="当前日期下每页返回的生成记录数量")
|
page_size: int = Field(..., description="当前日期下每页返回的生成记录数量")
|
||||||
items: list[GenerationAITaskOut] = Field(
|
items: list[GenerationAIHistoryItemOut] = Field(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
description="当前日期下的生成记录详情列表,按 generated_at 倒序排列",
|
description=(
|
||||||
|
"当前日期下的生成记录详情列表,按 generated_at 倒序排列。"
|
||||||
|
"默认 history_source=chat_task 时 item 为 GenerationAITaskOut;"
|
||||||
|
"history_source=generation_record 时 item 为 GenerationAIRecordHistoryItemOut"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
@@ -10,10 +10,16 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.models.chat_generation_task import ChatGenerationTask
|
from app.models.chat_generation_task import ChatGenerationTask
|
||||||
|
from app.models.generation_record import GenerationRecord
|
||||||
|
from app.models.project import Project
|
||||||
from app.models.image_engine import ImageEngine
|
from app.models.image_engine import ImageEngine
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
from app.models.video_engine import VideoEngine
|
from app.models.video_engine import VideoEngine
|
||||||
from app.schemas.generation_ai import GenerationAITaskCreate, GenerationAITaskOut
|
from app.schemas.generation_ai import (
|
||||||
|
GenerationAIRecordHistoryItemOut,
|
||||||
|
GenerationAITaskCreate,
|
||||||
|
GenerationAITaskOut,
|
||||||
|
)
|
||||||
from app.services.generation_billing_service import charge_generation_media_by_params
|
from app.services.generation_billing_service import charge_generation_media_by_params
|
||||||
from app.utils.id_gen import generate_id
|
from app.utils.id_gen import generate_id
|
||||||
|
|
||||||
@@ -344,6 +350,21 @@ def _normalize_history_gen_type(gen_type: str | None) -> str:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_history_source(history_source: str | None) -> str:
|
||||||
|
"""Normalize history source query param.
|
||||||
|
|
||||||
|
默认保持原来的 chat_generation_tasks 历史;只有显式传 generation_record
|
||||||
|
才切换旧 generation_records 历史,避免影响现有前端。
|
||||||
|
"""
|
||||||
|
value = (history_source or "chat_task").lower().strip()
|
||||||
|
if value in ("", "chat", "chat_task", "chat_generation_task", "chat_generation_tasks"):
|
||||||
|
return "chat_task"
|
||||||
|
if value in ("record", "records", "generation_record", "generation_records"):
|
||||||
|
return "generation_record"
|
||||||
|
raise HTTPException(status_code=400, detail="history_source 仅支持 chat_task 或 generation_record")
|
||||||
|
|
||||||
|
|
||||||
def _history_day_to_str(value) -> str:
|
def _history_day_to_str(value) -> str:
|
||||||
if isinstance(value, datetime):
|
if isinstance(value, datetime):
|
||||||
return value.date().strftime("%Y-%m-%d")
|
return value.date().strftime("%Y-%m-%d")
|
||||||
@@ -369,12 +390,198 @@ def _history_base_filters(user_id: str, gen_type: str):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _generation_record_history_base_filters(user_id: str, gen_type: str):
|
||||||
|
return [
|
||||||
|
GenerationRecord.user_id == user_id,
|
||||||
|
GenerationRecord.status == "completed",
|
||||||
|
GenerationRecord.gen_type == gen_type,
|
||||||
|
GenerationRecord.generated_at.is_not(None),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def generation_record_to_history_out(
|
||||||
|
record: GenerationRecord,
|
||||||
|
project_name: str | None = None,
|
||||||
|
) -> GenerationAIRecordHistoryItemOut:
|
||||||
|
refs = _parse_json(record.media_references)
|
||||||
|
return GenerationAIRecordHistoryItemOut(
|
||||||
|
id=record.id,
|
||||||
|
source_type="generation_record",
|
||||||
|
project_id=record.project_id,
|
||||||
|
project_name=project_name,
|
||||||
|
gen_type=record.gen_type,
|
||||||
|
generation_mode="generation_record",
|
||||||
|
pipeline_stage=None,
|
||||||
|
status=record.status,
|
||||||
|
original_prompt=record.original_prompt,
|
||||||
|
# optimized_prompt=None,
|
||||||
|
duration=record.duration,
|
||||||
|
aspect_ratio=record.aspect_ratio,
|
||||||
|
resolution=record.resolution,
|
||||||
|
image_size=record.image_size,
|
||||||
|
image_proportion=record.image_proportion,
|
||||||
|
image_px=record.image_px,
|
||||||
|
references=refs,
|
||||||
|
media_references=refs,
|
||||||
|
provider_task_id=record.seedance_task_id,
|
||||||
|
seedance_task_id=record.seedance_task_id,
|
||||||
|
remote_result_url=None,
|
||||||
|
image_url=record.image_url,
|
||||||
|
video_url=record.video_url,
|
||||||
|
engine_id=None,
|
||||||
|
engine_snapshot=None,
|
||||||
|
credits_cost=record.credits_cost or 0.0,
|
||||||
|
text_credits_cost=record.text_credits_cost or 0.0,
|
||||||
|
text_tokens_used=record.text_tokens_used or 0,
|
||||||
|
image_tokens_used=record.image_tokens_used or 0,
|
||||||
|
video_tokens_used=record.video_tokens_used or 0,
|
||||||
|
retry_count=0,
|
||||||
|
poll_count=0,
|
||||||
|
error_message=record.error_message,
|
||||||
|
created_at=record.created_at,
|
||||||
|
generated_at=record.generated_at,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def list_generation_record_history_grouped_days(
|
||||||
|
db: AsyncSession,
|
||||||
|
user_id: str,
|
||||||
|
gen_type: str,
|
||||||
|
page: int,
|
||||||
|
page_size: int,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
按生成日期倒序返回旧 generation_records 历史记录分组。
|
||||||
|
|
||||||
|
- 每页最多返回 10 个生成日期
|
||||||
|
- 每个日期分组内最多返回倒序前 10 条旧记录
|
||||||
|
- 只返回 completed 成功记录
|
||||||
|
"""
|
||||||
|
gen_type = _normalize_history_gen_type(gen_type)
|
||||||
|
page = max(page, 1)
|
||||||
|
page_size = min(max(page_size, 1), HISTORY_DAY_PAGE_SIZE_MAX)
|
||||||
|
|
||||||
|
filters = _generation_record_history_base_filters(user_id, gen_type)
|
||||||
|
day_expr = func.date(GenerationRecord.generated_at).label("generated_date")
|
||||||
|
|
||||||
|
days_subquery = (
|
||||||
|
select(day_expr)
|
||||||
|
.where(*filters)
|
||||||
|
.group_by(day_expr)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
|
||||||
|
total_days = (
|
||||||
|
await db.execute(select(func.count()).select_from(days_subquery))
|
||||||
|
).scalar_one()
|
||||||
|
|
||||||
|
day_rows_result = await db.execute(
|
||||||
|
select(
|
||||||
|
day_expr,
|
||||||
|
func.count(GenerationRecord.id).label("total"),
|
||||||
|
)
|
||||||
|
.where(*filters)
|
||||||
|
.group_by(day_expr)
|
||||||
|
.order_by(day_expr.desc())
|
||||||
|
.offset((page - 1) * page_size)
|
||||||
|
.limit(page_size)
|
||||||
|
)
|
||||||
|
day_rows = day_rows_result.all()
|
||||||
|
|
||||||
|
groups = []
|
||||||
|
for generated_day, day_total in day_rows:
|
||||||
|
item_result = await db.execute(
|
||||||
|
select(GenerationRecord, Project.name.label("project_name"))
|
||||||
|
.outerjoin(Project, GenerationRecord.project_id == Project.id)
|
||||||
|
.where(
|
||||||
|
*filters,
|
||||||
|
func.date(GenerationRecord.generated_at) == generated_day,
|
||||||
|
)
|
||||||
|
.order_by(GenerationRecord.generated_at.desc(), GenerationRecord.created_at.desc())
|
||||||
|
.limit(HISTORY_GROUP_ITEM_LIMIT)
|
||||||
|
)
|
||||||
|
rows = item_result.all()
|
||||||
|
|
||||||
|
groups.append(
|
||||||
|
{
|
||||||
|
"generated_date": _history_day_to_str(generated_day),
|
||||||
|
"total": int(day_total or 0),
|
||||||
|
"items": [
|
||||||
|
generation_record_to_history_out(record, project_name)
|
||||||
|
for record, project_name in rows
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total_days": int(total_days or 0),
|
||||||
|
"page": page,
|
||||||
|
"page_size": page_size,
|
||||||
|
"groups": groups,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def list_generation_record_history_day_items(
|
||||||
|
db: AsyncSession,
|
||||||
|
user_id: str,
|
||||||
|
gen_type: str,
|
||||||
|
generated_date: str,
|
||||||
|
page: int,
|
||||||
|
page_size: int,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
获取旧 generation_records 指定生成日期下的历史记录分页。
|
||||||
|
"""
|
||||||
|
gen_type = _normalize_history_gen_type(gen_type)
|
||||||
|
target_day = _parse_history_date(generated_date)
|
||||||
|
page = max(page, 1)
|
||||||
|
page_size = min(max(page_size, 1), 100)
|
||||||
|
|
||||||
|
filters = _generation_record_history_base_filters(user_id, gen_type)
|
||||||
|
day_expr = func.date(GenerationRecord.generated_at)
|
||||||
|
|
||||||
|
total = (
|
||||||
|
await db.execute(
|
||||||
|
select(func.count(GenerationRecord.id)).where(
|
||||||
|
*filters,
|
||||||
|
day_expr == target_day,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).scalar_one()
|
||||||
|
|
||||||
|
result = await db.execute(
|
||||||
|
select(GenerationRecord, Project.name.label("project_name"))
|
||||||
|
.outerjoin(Project, GenerationRecord.project_id == Project.id)
|
||||||
|
.where(
|
||||||
|
*filters,
|
||||||
|
day_expr == target_day,
|
||||||
|
)
|
||||||
|
.order_by(GenerationRecord.generated_at.desc(), GenerationRecord.created_at.desc())
|
||||||
|
.offset((page - 1) * page_size)
|
||||||
|
.limit(page_size)
|
||||||
|
)
|
||||||
|
|
||||||
|
rows = result.all()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"generated_date": target_day.strftime("%Y-%m-%d"),
|
||||||
|
"total": int(total or 0),
|
||||||
|
"page": page,
|
||||||
|
"page_size": page_size,
|
||||||
|
"items": [
|
||||||
|
generation_record_to_history_out(record, project_name)
|
||||||
|
for record, project_name in rows
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def list_generation_history_grouped_days(
|
async def list_generation_history_grouped_days(
|
||||||
db: AsyncSession,
|
db: AsyncSession,
|
||||||
user_id: str,
|
user_id: str,
|
||||||
gen_type: str,
|
gen_type: str,
|
||||||
page: int,
|
page: int,
|
||||||
page_size: int,
|
page_size: int,
|
||||||
|
history_source: str | None = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
按生成日期倒序返回历史记录分组。
|
按生成日期倒序返回历史记录分组。
|
||||||
@@ -383,6 +590,16 @@ async def list_generation_history_grouped_days(
|
|||||||
- 每个日期分组内最多返回倒序前 10 条任务
|
- 每个日期分组内最多返回倒序前 10 条任务
|
||||||
- 只返回 completed 成功任务
|
- 只返回 completed 成功任务
|
||||||
"""
|
"""
|
||||||
|
source = _normalize_history_source(history_source)
|
||||||
|
if source == "generation_record":
|
||||||
|
return await list_generation_record_history_grouped_days(
|
||||||
|
db=db,
|
||||||
|
user_id=user_id,
|
||||||
|
gen_type=gen_type,
|
||||||
|
page=page,
|
||||||
|
page_size=page_size,
|
||||||
|
)
|
||||||
|
|
||||||
gen_type = _normalize_history_gen_type(gen_type)
|
gen_type = _normalize_history_gen_type(gen_type)
|
||||||
page = max(page, 1)
|
page = max(page, 1)
|
||||||
page_size = min(max(page_size, 1), HISTORY_DAY_PAGE_SIZE_MAX)
|
page_size = min(max(page_size, 1), HISTORY_DAY_PAGE_SIZE_MAX)
|
||||||
@@ -450,12 +667,24 @@ async def list_generation_history_day_items(
|
|||||||
generated_date: str,
|
generated_date: str,
|
||||||
page: int,
|
page: int,
|
||||||
page_size: int,
|
page_size: int,
|
||||||
|
history_source: str | None = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
获取指定生成日期下的历史记录分页。
|
获取指定生成日期下的历史记录分页。
|
||||||
|
|
||||||
用于前端点击某一天后,继续加载该日期下的第 2 页、第 3 页数据。
|
用于前端点击某一天后,继续加载该日期下的第 2 页、第 3 页数据。
|
||||||
"""
|
"""
|
||||||
|
source = _normalize_history_source(history_source)
|
||||||
|
if source == "generation_record":
|
||||||
|
return await list_generation_record_history_day_items(
|
||||||
|
db=db,
|
||||||
|
user_id=user_id,
|
||||||
|
gen_type=gen_type,
|
||||||
|
generated_date=generated_date,
|
||||||
|
page=page,
|
||||||
|
page_size=page_size,
|
||||||
|
)
|
||||||
|
|
||||||
gen_type = _normalize_history_gen_type(gen_type)
|
gen_type = _normalize_history_gen_type(gen_type)
|
||||||
target_day = _parse_history_date(generated_date)
|
target_day = _parse_history_date(generated_date)
|
||||||
page = max(page, 1)
|
page = max(page, 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user