creative_studio/backend/app/api/v1/confirm_cards.py
hjjjj 5487450f34 feat: 实现审核系统核心功能与UI优化
- 新增审核卡片和确认卡片模型,支持Agent推送审核任务和用户确认
- 实现审核卡片API服务,支持创建、更新、批准、驳回等操作
- 扩展审核维度配置,新增角色一致性、剧情连贯性等维度
- 优化前端审核配置页面,修复API路径错误和状态枚举问题
- 改进剧集创作平台布局,新增左侧边栏用于剧集管理和上下文查看
- 增强Skill管理,支持从审核系统跳转创建/编辑Skill
- 修复episodes.json数据问题,清理聊天历史记录
- 更新Agent提示词,明确Skill引用加载流程
- 统一前端主题配置,优化整体UI体验
2026-01-30 18:32:48 +08:00

320 lines
9.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Confirm Cards API Routes
确认卡片API路由 - 提供确认卡片的CRUD操作
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional
from datetime import datetime, timedelta
from app.models.confirm_card import (
ConfirmCard, ConfirmCardStatus, ConfirmCardOption
)
from app.db.confirm_card_repository import get_confirm_card_repo
from app.api.v1.websocket import manager
from app.utils.logger import get_logger
logger = get_logger(__name__)
router = APIRouter(prefix="/confirm-cards", tags=["确认卡片"])
# ============================================
# 依赖注入
# ============================================
async def get_repo():
"""获取确认卡片仓储"""
return get_confirm_card_repo()
# ============================================
# CRUD 端点
# ============================================
@router.post("")
async def create_confirm_card(
card: ConfirmCard,
repo=Depends(get_repo)
):
"""
创建确认卡片
由Agent推送用户可中途退出后随时确认
"""
try:
created_card = await repo.create(card)
# WebSocket推送
await manager.send_to_project(card.project_id, {
"type": "confirm_card_created",
"data": {
"card_id": card.id,
"card_type": card.card_type.value,
"title": card.title,
"description": card.description[:200] + "..." if len(card.description) > 200 else card.description,
"options_count": len(card.options),
"created_at": card.created_at.isoformat()
}
})
logger.info(f"创建确认卡片成功: {card.id}")
return {"success": True, "card": created_card}
except Exception as e:
logger.error(f"创建确认卡片失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"创建确认卡片失败: {str(e)}")
@router.post("/simple")
async def create_confirm_card_simple(
project_id: str,
card_type: str,
title: str,
description: str,
options: Optional[List[dict]] = None,
episode_number: Optional[int] = None,
allow_custom_response: bool = False,
expires_in_hours: Optional[int] = None,
repo=Depends(get_repo)
):
"""
简化创建确认卡片
用于Agent快速推送确认任务
"""
try:
# 构建选项列表
card_options = []
if options:
for i, opt in enumerate(options):
if isinstance(opt, dict):
card_options.append(ConfirmCardOption(
id=opt.get("id", f"opt_{i}"),
label=opt.get("label", f"选项 {i+1}"),
description=opt.get("description", ""),
implications=opt.get("implications")
))
elif isinstance(opt, str):
card_options.append(ConfirmCardOption(
id=f"opt_{i}",
label=opt,
description=""
))
# 计算过期时间
expires_at = None
if expires_in_hours:
expires_at = datetime.now() + timedelta(hours=expires_in_hours)
from app.models.confirm_card import ConfirmCardType
# 创建卡片
card = ConfirmCard(
id=None, # 由repository生成
project_id=project_id,
episode_number=episode_number,
card_type=ConfirmCardType(card_type),
title=title,
description=description,
options=card_options,
allow_custom_response=allow_custom_response,
expires_at=expires_at
)
created_card = await repo.create(card)
# WebSocket推送
await manager.send_to_project(project_id, {
"type": "confirm_card_created",
"data": {
"card_id": created_card.id,
"card_type": card.card_type.value,
"title": card.title,
"description": card.description[:200] + "..." if len(card.description) > 200 else card.description,
"options": [{"id": o.id, "label": o.label} for o in card_options],
"created_at": created_card.created_at.isoformat()
}
})
logger.info(f"简化创建确认卡片成功: {created_card.id}")
return {"success": True, "card_id": created_card.id, "card": created_card}
except Exception as e:
logger.error(f"简化创建确认卡片失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"创建确认卡片失败: {str(e)}")
@router.get("")
async def list_confirm_cards(
project_id: str,
status: Optional[ConfirmCardStatus] = None,
include_expired: bool = False,
repo=Depends(get_repo)
) -> List[ConfirmCard]:
"""
获取确认卡片列表
默认不包含过期的卡片
"""
try:
return await repo.list_by_project(project_id, status, include_expired)
except Exception as e:
logger.error(f"获取确认卡片列表失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"获取列表失败: {str(e)}")
@router.get("/{card_id}")
async def get_confirm_card(
card_id: str,
repo=Depends(get_repo)
) -> ConfirmCard:
"""获取确认卡片详情"""
card = await repo.get(card_id)
if not card:
raise HTTPException(status_code=404, detail="确认卡片不存在")
# 检查是否过期
if card.expires_at and card.expires_at < datetime.now():
if card.status == ConfirmCardStatus.PENDING:
card.status = ConfirmCardStatus.EXPIRED
return card
@router.delete("/{card_id}")
async def delete_confirm_card(
card_id: str,
repo=Depends(get_repo)
):
"""删除确认卡片"""
try:
success = await repo.delete(card_id)
if not success:
raise HTTPException(status_code=404, detail="确认卡片不存在")
logger.info(f"删除确认卡片成功: {card_id}")
return {"success": True}
except HTTPException:
raise
except Exception as e:
logger.error(f"删除确认卡片失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"删除失败: {str(e)}")
# ============================================
# 操作端点
# ============================================
@router.post("/{card_id}/confirm")
async def confirm_card(
card_id: str,
selected_option_id: Optional[str] = None,
custom_response: Optional[str] = None,
user_notes: Optional[str] = None,
repo=Depends(get_repo)
):
"""
确认卡片
用户选择一个选项或提供自定义回复
"""
try:
card = await repo.get(card_id)
if not card:
raise HTTPException(status_code=404, detail="确认卡片不存在")
# 检查是否过期
if card.expires_at and card.expires_at < datetime.now():
await repo.update_status(card_id, ConfirmCardStatus.EXPIRED)
raise HTTPException(status_code=400, detail="确认卡片已过期")
# 更新状态
updated_card = await repo.update_status(
card_id,
ConfirmCardStatus.APPROVED,
selected_option_id,
custom_response,
user_notes
)
# WebSocket通知
await manager.send_to_project(card.project_id, {
"type": "confirm_card_confirmed",
"data": {
"card_id": card_id,
"status": "approved",
"selected_option": selected_option_id,
"custom_response": custom_response,
"confirmed_at": updated_card.confirmed_at.isoformat() if updated_card.confirmed_at else None
}
})
logger.info(f"确认卡片成功: {card_id}, 选择: {selected_option_id}")
return {"success": True, "card": updated_card}
except HTTPException:
raise
except Exception as e:
logger.error(f"确认卡片失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"操作失败: {str(e)}")
@router.post("/{card_id}/reject")
async def reject_card(
card_id: str,
user_notes: Optional[str] = None,
repo=Depends(get_repo)
):
"""
拒绝确认卡片
用户拒绝该确认任务
"""
try:
updated_card = await repo.update_status(
card_id,
ConfirmCardStatus.REJECTED,
user_notes=user_notes
)
if not updated_card:
raise HTTPException(status_code=404, detail="确认卡片不存在")
# WebSocket通知
await manager.send_to_project(updated_card.project_id, {
"type": "confirm_card_rejected",
"data": {
"card_id": card_id,
"status": "rejected",
"user_notes": user_notes
}
})
logger.info(f"拒绝确认卡片成功: {card_id}")
return {"success": True, "card": updated_card}
except HTTPException:
raise
except Exception as e:
logger.error(f"拒绝确认卡片失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"操作失败: {str(e)}")
@router.post("/cleanup-expired")
async def cleanup_expired_cards(repo=Depends(get_repo)):
"""
清理过期的确认卡片
将过期且仍为pending状态的卡片标记为expired
"""
try:
count = await repo.cleanup_expired()
logger.info(f"清理过期确认卡片: {count}")
return {"success": True, "cleaned_count": count}
except Exception as e:
logger.error(f"清理过期卡片失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"清理失败: {str(e)}")