""" AI 应用 API """ import logging from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.base_schema import PaginatedResponse, ResponseModel from ai_platform.models import AIApp, LLMModel from ai_platform.schemas.app_schema import ( AppCreate, AppUpdate, AppResponse, AppListResponse, ) logger = logging.getLogger(__name__) router = APIRouter(prefix="/apps", tags=["AI-应用"]) @router.get("", response_model=PaginatedResponse[AppListResponse], summary="应用列表") async def list_apps( name: Optional[str] = Query(None, description="名称"), app_type: Optional[str] = Query(None, description="类型"), status: Optional[str] = Query(None, description="状态"), page: int = Query(1, ge=1, description="页码"), page_size: int = Query(20, ge=1, le=100, alias="pageSize", description="每页数量"), db: AsyncSession = Depends(get_db), ): """获取应用列表""" query = select(AIApp).where(AIApp.is_deleted == False) if name: query = query.where(AIApp.name.ilike(f"%{name}%")) if app_type: query = query.where(AIApp.app_type == app_type) if status: query = query.where(AIApp.status == status) # 获取总数 count_result = await db.execute(select(func.count()).select_from(query.subquery())) total = count_result.scalar() or 0 # 分页 offset = (page - 1) * page_size query = query.order_by(AIApp.sort.desc(), AIApp.sys_create_datetime.desc()) query = query.offset(offset).limit(page_size) result = await db.execute(query) apps = result.scalars().all() items = [] for app in apps: model_name = "" if app.model_id: model_result = await db.execute( select(LLMModel).where(LLMModel.id == app.model_id) ) model = model_result.scalar_one_or_none() model_name = model.display_name if model else "" items.append({ "id": app.id, "name": app.name, "code": app.code, "description": app.description or "", "icon": app.icon or "", "app_type": app.app_type or "chat", "status": app.status or "draft", "model_name": model_name, "is_public": app.is_public or False, "conversation_count": app.conversation_count or 0, "message_count": app.message_count or 0, "sys_create_datetime": app.sys_create_datetime, }) return PaginatedResponse(items=items, total=total) @router.get("/published", response_model=List[AppListResponse], summary="获取已发布应用") async def list_published_apps(db: AsyncSession = Depends(get_db)): """获取已发布的应用列表(用于用户选择)""" query = select(AIApp).where( AIApp.is_deleted == False, AIApp.status == "published" ).order_by(AIApp.sort.desc()) result = await db.execute(query) apps = result.scalars().all() items = [] for app in apps: model_name = "" if app.model_id: model_result = await db.execute( select(LLMModel).where(LLMModel.id == app.model_id) ) model = model_result.scalar_one_or_none() model_name = model.display_name if model else "" items.append({ "id": app.id, "name": app.name, "code": app.code, "description": app.description or "", "icon": app.icon or "", "app_type": app.app_type or "chat", "status": app.status or "draft", "model_name": model_name, "is_public": app.is_public or False, "conversation_count": app.conversation_count or 0, "message_count": app.message_count or 0, "sys_create_datetime": app.sys_create_datetime, }) return items @router.get("/code/{code}", response_model=AppResponse, summary="根据编码获取应用") async def get_app_by_code(code: str, db: AsyncSession = Depends(get_db)): """根据编码获取应用""" result = await db.execute( select(AIApp).where(AIApp.code == code, AIApp.is_deleted == False) ) app = result.scalar_one_or_none() if not app: raise HTTPException(status_code=404, detail="应用不存在") return await _build_app_response(app, db) @router.get("/{app_id}", response_model=AppResponse, summary="应用详情") async def get_app(app_id: str, db: AsyncSession = Depends(get_db)): """获取应用详情""" result = await db.execute( select(AIApp).where(AIApp.id == app_id, AIApp.is_deleted == False) ) app = result.scalar_one_or_none() if not app: raise HTTPException(status_code=404, detail="应用不存在") return await _build_app_response(app, db) @router.post("", response_model=AppResponse, summary="创建应用") async def create_app(data: AppCreate, db: AsyncSession = Depends(get_db)): """创建应用""" # 检查编码是否重复 exists_result = await db.execute( select(AIApp).where(AIApp.code == data.code, AIApp.is_deleted == False) ) if exists_result.scalar_one_or_none(): raise HTTPException(status_code=400, detail=f"应用编码 {data.code} 已存在") # 验证模型 if data.model_id: model_result = await db.execute( select(LLMModel).where(LLMModel.id == data.model_id, LLMModel.is_deleted == False) ) if not model_result.scalar_one_or_none(): raise HTTPException(status_code=400, detail="模型不存在") app = AIApp(**data.model_dump()) db.add(app) await db.commit() await db.refresh(app) return await _build_app_response(app, db) @router.put("/{app_id}", response_model=AppResponse, summary="更新应用") async def update_app( app_id: str, data: AppUpdate, db: AsyncSession = Depends(get_db), ): """更新应用""" result = await db.execute( select(AIApp).where(AIApp.id == app_id, AIApp.is_deleted == False) ) app = result.scalar_one_or_none() if not app: raise HTTPException(status_code=404, detail="应用不存在") update_data = data.model_dump(exclude_unset=True) # 验证模型 if "model_id" in update_data and update_data["model_id"]: model_result = await db.execute( select(LLMModel).where( LLMModel.id == update_data["model_id"], LLMModel.is_deleted == False ) ) if not model_result.scalar_one_or_none(): raise HTTPException(status_code=400, detail="模型不存在") for key, value in update_data.items(): setattr(app, key, value) await db.commit() await db.refresh(app) return await _build_app_response(app, db) @router.delete("/{app_id}", response_model=ResponseModel, summary="删除应用") async def delete_app(app_id: str, db: AsyncSession = Depends(get_db)): """删除应用""" result = await db.execute( select(AIApp).where(AIApp.id == app_id, AIApp.is_deleted == False) ) app = result.scalar_one_or_none() if not app: raise HTTPException(status_code=404, detail="应用不存在") app.is_deleted = True await db.commit() return ResponseModel(message="删除成功") @router.post("/{app_id}/publish", response_model=AppResponse, summary="发布应用") async def publish_app(app_id: str, db: AsyncSession = Depends(get_db)): """发布应用""" result = await db.execute( select(AIApp).where(AIApp.id == app_id, AIApp.is_deleted == False) ) app = result.scalar_one_or_none() if not app: raise HTTPException(status_code=404, detail="应用不存在") app.status = "published" await db.commit() await db.refresh(app) return await _build_app_response(app, db) @router.post("/{app_id}/disable", response_model=AppResponse, summary="停用应用") async def disable_app(app_id: str, db: AsyncSession = Depends(get_db)): """停用应用""" result = await db.execute( select(AIApp).where(AIApp.id == app_id, AIApp.is_deleted == False) ) app = result.scalar_one_or_none() if not app: raise HTTPException(status_code=404, detail="应用不存在") app.status = "disabled" await db.commit() await db.refresh(app) return await _build_app_response(app, db) async def _build_app_response(app: AIApp, db: AsyncSession) -> dict: """构建应用输出""" model_name = "" if app.model_id: model_result = await db.execute( select(LLMModel).where(LLMModel.id == app.model_id) ) model = model_result.scalar_one_or_none() model_name = model.display_name if model else "" return { "id": app.id, "name": app.name, "code": app.code, "description": app.description or "", "icon": app.icon or "", "app_type": app.app_type or "chat", "status": app.status or "draft", "model_id": app.model_id, "model_name": model_name, "system_prompt": app.system_prompt or "", "temperature": app.temperature or 0.7, "top_p": app.top_p or 1.0, "max_tokens": app.max_tokens or 2048, "opening_statement": app.opening_statement or "", "suggested_questions": app.suggested_questions or [], "workflow_definition": app.workflow_definition or {}, "is_public": app.is_public or False, "conversation_count": app.conversation_count or 0, "message_count": app.message_count or 0, "sort": app.sort or 0, "sys_create_datetime": app.sys_create_datetime, "sys_update_datetime": app.sys_update_datetime, }