拆镜复刻、爆款开头复刻抽象公共逻辑,拆镜复刻关联项目步骤状态BUG修复
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.enums.common import ModuleEventTypeEnum, ModuleStepStatusEnum
|
||||
from app.models.chat_generation_task import ChatGenerationTask
|
||||
from app.models.module_generation_project import ModuleGenerationProject
|
||||
from app.models.module_generation_step import ModuleGenerationStep
|
||||
from app.models.user import User
|
||||
from app.services.generation_refund_service import mark_chat_generation_task_failed_and_refund_once
|
||||
from app.enums.module_generation_flow import ModuleGenerationFlowConfig
|
||||
from app.services.module_generation_step_common_service import build_step_input, build_step_output, utc_now
|
||||
from app.services.resource_accounting_service import soft_delete_chat_task_resources
|
||||
from app.utils.id_gen import generate_id
|
||||
|
||||
LogModuleEventCallable = Callable[..., Awaitable[None]]
|
||||
|
||||
|
||||
async def get_project_for_user(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
project_id: str,
|
||||
user: User,
|
||||
config: ModuleGenerationFlowConfig,
|
||||
for_update: bool = False,
|
||||
populate_existing: bool = False,
|
||||
) -> ModuleGenerationProject:
|
||||
query = select(ModuleGenerationProject).where(
|
||||
ModuleGenerationProject.id == project_id,
|
||||
ModuleGenerationProject.module == config.module,
|
||||
ModuleGenerationProject.deleted_at.is_(None),
|
||||
)
|
||||
if not user.is_admin:
|
||||
query = query.where(ModuleGenerationProject.user_id == user.id)
|
||||
if populate_existing:
|
||||
query = query.execution_options(populate_existing=True)
|
||||
if for_update:
|
||||
query = query.with_for_update()
|
||||
result = await db.execute(query.limit(1))
|
||||
project = result.scalar_one_or_none()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail=config.project_not_found_message)
|
||||
return project
|
||||
|
||||
|
||||
async def get_step_for_user(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
project_id: str,
|
||||
step_id: str,
|
||||
user: User,
|
||||
config: ModuleGenerationFlowConfig,
|
||||
for_update: bool = False,
|
||||
) -> ModuleGenerationStep:
|
||||
await get_project_for_user(db, project_id=project_id, user=user, config=config, for_update=for_update)
|
||||
query = select(ModuleGenerationStep).where(
|
||||
ModuleGenerationStep.id == step_id,
|
||||
ModuleGenerationStep.project_id == project_id,
|
||||
ModuleGenerationStep.module == config.module,
|
||||
ModuleGenerationStep.deleted_at.is_(None),
|
||||
ModuleGenerationStep.is_current == True,
|
||||
)
|
||||
if not user.is_admin:
|
||||
query = query.where(ModuleGenerationStep.user_id == user.id)
|
||||
if for_update:
|
||||
query = query.with_for_update()
|
||||
result = await db.execute(query.limit(1))
|
||||
step = result.scalar_one_or_none()
|
||||
if not step:
|
||||
raise HTTPException(status_code=404, detail=config.step_not_found_message)
|
||||
return step
|
||||
|
||||
|
||||
async def get_current_steps(db: AsyncSession, *, project_id: str, config: ModuleGenerationFlowConfig) -> list[ModuleGenerationStep]:
|
||||
result = await db.execute(
|
||||
select(ModuleGenerationStep)
|
||||
.where(
|
||||
ModuleGenerationStep.project_id == project_id,
|
||||
ModuleGenerationStep.module == config.module,
|
||||
ModuleGenerationStep.deleted_at.is_(None),
|
||||
ModuleGenerationStep.is_current == True,
|
||||
)
|
||||
.order_by(ModuleGenerationStep.step_index.asc(), ModuleGenerationStep.created_at.asc())
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
async def get_current_step_by_code(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
project_id: str,
|
||||
step_code: str,
|
||||
config: ModuleGenerationFlowConfig,
|
||||
) -> ModuleGenerationStep | None:
|
||||
result = await db.execute(
|
||||
select(ModuleGenerationStep)
|
||||
.where(
|
||||
ModuleGenerationStep.project_id == project_id,
|
||||
ModuleGenerationStep.module == config.module,
|
||||
ModuleGenerationStep.step_code == step_code,
|
||||
ModuleGenerationStep.is_current == True,
|
||||
ModuleGenerationStep.deleted_at.is_(None),
|
||||
)
|
||||
.order_by(ModuleGenerationStep.version.desc(), ModuleGenerationStep.created_at.desc())
|
||||
.limit(1)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def next_version(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
project_id: str,
|
||||
step_code: str,
|
||||
config: ModuleGenerationFlowConfig,
|
||||
) -> int:
|
||||
result = await db.execute(
|
||||
select(func.max(ModuleGenerationStep.version)).where(
|
||||
ModuleGenerationStep.project_id == project_id,
|
||||
ModuleGenerationStep.module == config.module,
|
||||
ModuleGenerationStep.step_code == step_code,
|
||||
)
|
||||
)
|
||||
return int(result.scalar_one_or_none() or 0) + 1
|
||||
|
||||
|
||||
async def create_module_step(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
project: ModuleGenerationProject,
|
||||
step_code: str,
|
||||
config: ModuleGenerationFlowConfig,
|
||||
log_module_event: LogModuleEventCallable,
|
||||
status: str = ModuleStepStatusEnum.PENDING.value,
|
||||
parent_step_id: str | None = None,
|
||||
source_step_id: str | None = None,
|
||||
chat_task_id: str | None = None,
|
||||
input_data: dict[str, Any] | None = None,
|
||||
output_data: dict[str, Any] | None = None,
|
||||
) -> ModuleGenerationStep:
|
||||
version = await next_version(db, project_id=project.id, step_code=step_code, config=config)
|
||||
step = ModuleGenerationStep(
|
||||
id=generate_id(),
|
||||
project_id=project.id,
|
||||
user_id=project.user_id,
|
||||
module=project.module,
|
||||
step_index=config.step_index_map[step_code],
|
||||
step_code=step_code,
|
||||
status=status,
|
||||
version=version,
|
||||
is_current=True,
|
||||
parent_step_id=parent_step_id,
|
||||
source_step_id=source_step_id,
|
||||
chat_task_id=chat_task_id,
|
||||
input_json=(
|
||||
build_step_input(
|
||||
step_code=step_code,
|
||||
payload=input_data,
|
||||
source_step_id=source_step_id,
|
||||
parent_step_id=parent_step_id,
|
||||
schema_version=config.step_io_schema_version,
|
||||
)
|
||||
if input_data is not None
|
||||
else None
|
||||
),
|
||||
output_json=(
|
||||
build_step_output(
|
||||
step_code=step_code,
|
||||
status=status,
|
||||
result=output_data,
|
||||
schema_version=config.step_io_schema_version,
|
||||
)
|
||||
if output_data is not None
|
||||
else None
|
||||
),
|
||||
started_at=utc_now() if status == ModuleStepStatusEnum.PROCESSING.value else None,
|
||||
completed_at=utc_now() if status == ModuleStepStatusEnum.COMPLETED.value else None,
|
||||
)
|
||||
db.add(step)
|
||||
project.current_step_code = step_code
|
||||
await db.flush()
|
||||
await log_module_event(
|
||||
db,
|
||||
project=project,
|
||||
step=step,
|
||||
event_type=ModuleEventTypeEnum.STEP_CREATED.value,
|
||||
detail={"step_code": step_code, "version": version},
|
||||
)
|
||||
return step
|
||||
|
||||
|
||||
async def soft_delete_steps_from_index(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
project: ModuleGenerationProject,
|
||||
start_index: int,
|
||||
config: ModuleGenerationFlowConfig,
|
||||
log_module_event: LogModuleEventCallable,
|
||||
deleted_at: datetime | None = None,
|
||||
) -> list[ModuleGenerationStep]:
|
||||
deleted_at = deleted_at or utc_now()
|
||||
result = await db.execute(
|
||||
select(ModuleGenerationStep)
|
||||
.where(
|
||||
ModuleGenerationStep.project_id == project.id,
|
||||
ModuleGenerationStep.module == config.module,
|
||||
ModuleGenerationStep.is_current == True,
|
||||
ModuleGenerationStep.deleted_at.is_(None),
|
||||
ModuleGenerationStep.step_index >= start_index,
|
||||
)
|
||||
.with_for_update()
|
||||
)
|
||||
steps = list(result.scalars().all())
|
||||
for step in steps:
|
||||
step.is_current = False
|
||||
step.deleted_at = deleted_at
|
||||
if step.chat_task_id:
|
||||
chat_result = await db.execute(
|
||||
select(ChatGenerationTask)
|
||||
.where(ChatGenerationTask.id == step.chat_task_id, ChatGenerationTask.deleted_at.is_(None))
|
||||
.with_for_update()
|
||||
.limit(1)
|
||||
)
|
||||
chat_task = chat_result.scalar_one_or_none()
|
||||
if chat_task:
|
||||
if chat_task.status == "completed":
|
||||
await soft_delete_chat_task_resources(db, chat_task.id, deleted_at=deleted_at)
|
||||
elif chat_task.status != "failed":
|
||||
await mark_chat_generation_task_failed_and_refund_once(
|
||||
db,
|
||||
task=chat_task,
|
||||
error_message=config.cancel_chat_task_error_message,
|
||||
pipeline_stage="failed",
|
||||
)
|
||||
chat_task.deleted_at = deleted_at
|
||||
if steps:
|
||||
await log_module_event(
|
||||
db,
|
||||
project=project,
|
||||
event_type=ModuleEventTypeEnum.SOFT_DELETE_STEPS.value,
|
||||
message=f"软删除第 {start_index} 步及之后的旧子任务",
|
||||
detail={"step_ids": [step.id for step in steps]},
|
||||
)
|
||||
return steps
|
||||
|
||||
|
||||
async def chat_tasks_by_id(db: AsyncSession, steps: list[ModuleGenerationStep]) -> dict[str, ChatGenerationTask]:
|
||||
ids = [step.chat_task_id for step in steps if step.chat_task_id]
|
||||
if not ids:
|
||||
return {}
|
||||
result = await db.execute(select(ChatGenerationTask).where(ChatGenerationTask.id.in_(ids)))
|
||||
return {task.id: task for task in result.scalars().all()}
|
||||
Reference in New Issue
Block a user