- 新增审核卡片和确认卡片模型,支持Agent推送审核任务和用户确认 - 实现审核卡片API服务,支持创建、更新、批准、驳回等操作 - 扩展审核维度配置,新增角色一致性、剧情连贯性等维度 - 优化前端审核配置页面,修复API路径错误和状态枚举问题 - 改进剧集创作平台布局,新增左侧边栏用于剧集管理和上下文查看 - 增强Skill管理,支持从审核系统跳转创建/编辑Skill - 修复episodes.json数据问题,清理聊天历史记录 - 更新Agent提示词,明确Skill引用加载流程 - 统一前端主题配置,优化整体UI体验
320 lines
9.7 KiB
Python
320 lines
9.7 KiB
Python
"""
|
||
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)}")
|