from __future__ import annotations from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Query from fastapi.responses import JSONResponse from sqlalchemy.ext.asyncio import AsyncSession from ...core.database import get_db from ...core.response import error_response, success_response from ...dependencies import get_current_user from ...models import User from ...schemas.rag import QASessionListQuery, RAGAskRequest from ...schemas.space import SavedIdeaCreateRequest, SavedIdeaListResponse, SavedIdeaPromoteResponse, SavedIdeaUpdateRequest from ...services.ai_pmo.ai_pmo_kernel import AIPMOKernelService from ...services.adaptive_rag_service import AdaptiveRAGService from ...services.pmflow_launch_service import PMFlowLaunchError from ...services.space_saved_idea_service import SavedIdeaError, SpaceSavedIdeaService from .. import rag_ask_contract router = APIRouter(prefix="/space", tags=["space"]) @router.get("/ai-concierge/status") async def get_ai_concierge_status( current_user: User = Depends(get_current_user), ): return success_response( { "chat": "available", "history": "available", "actions": "available", "tenant_id": int(current_user.tenant_id), } ) @router.post("/ai-concierge/ask") async def ask_ai_concierge( body: RAGAskRequest, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): service = AIPMOKernelService(db, tenant_id=int(current_user.tenant_id)) ingress_result = await service.process_ingress( **rag_ask_contract.build_kernel_ingress_kwargs( request=body, current_user=current_user, ) ) if not bool(ingress_result.get("handled")): raise HTTPException( status_code=400, detail=str(ingress_result.get("reason") or "space_ai_concierge_not_handled"), ) payload = rag_ask_contract.build_rag_response_payload(ingress_result=ingress_result) if not str(payload.get("answer") or "").strip() and not str(payload.get("reply_text") or "").strip(): return JSONResponse(status_code=500, content=error_response(500, "AI 管家没有返回可用回答。")) return success_response(payload) @router.get("/ai-concierge/sessions") async def list_ai_concierge_sessions( date_from: datetime | None = Query(default=None), date_to: datetime | None = Query(default=None), page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): query = QASessionListQuery( date_from=date_from, date_to=date_to, page=page, page_size=page_size, ) service = AdaptiveRAGService(db, tenant_id=int(current_user.tenant_id)) result = await service.list_qa_sessions( user_id=int(current_user.id), date_from=query.date_from, date_to=query.date_to, page=query.page, page_size=query.page_size, ) return success_response(result) @router.get("/ai-concierge/sessions/{session_id}") async def get_ai_concierge_session( session_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): service = AdaptiveRAGService(db, tenant_id=int(current_user.tenant_id)) try: result = await service.get_qa_session_detail(session_id=session_id, user_id=int(current_user.id)) except ValueError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return success_response(result) @router.get("/saved-ideas") async def list_saved_ideas( limit: int = Query(default=20, ge=1, le=100), include_archived: bool = False, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): service = SpaceSavedIdeaService(db, tenant_id=int(current_user.tenant_id)) items = await service.list_ideas( user_id=int(current_user.id), limit=limit, include_archived=include_archived, ) return success_response(SavedIdeaListResponse(items=items).model_dump(mode="json")) @router.post("/saved-ideas") async def create_saved_idea( body: SavedIdeaCreateRequest, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): service = SpaceSavedIdeaService(db, tenant_id=int(current_user.tenant_id)) try: item = await service.create_idea(body=body, user_id=int(current_user.id)) except SavedIdeaError as exc: return JSONResponse(status_code=exc.status_code, content=error_response(exc.status_code, f"{exc.code}: {exc.message}")) return success_response(item.model_dump(mode="json")) @router.patch("/saved-ideas/{idea_id}") async def update_saved_idea( idea_id: str, body: SavedIdeaUpdateRequest, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): service = SpaceSavedIdeaService(db, tenant_id=int(current_user.tenant_id)) try: item = await service.update_idea(idea_id=idea_id, body=body, user_id=int(current_user.id)) except SavedIdeaError as exc: return JSONResponse(status_code=exc.status_code, content=error_response(exc.status_code, f"{exc.code}: {exc.message}")) return success_response(item.model_dump(mode="json")) @router.post("/saved-ideas/{idea_id}/archive") async def archive_saved_idea( idea_id: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): service = SpaceSavedIdeaService(db, tenant_id=int(current_user.tenant_id)) try: item = await service.archive_idea(idea_id=idea_id, user_id=int(current_user.id)) except SavedIdeaError as exc: return JSONResponse(status_code=exc.status_code, content=error_response(exc.status_code, f"{exc.code}: {exc.message}")) return success_response(item.model_dump(mode="json")) @router.post("/saved-ideas/{idea_id}/promote") async def promote_saved_idea( idea_id: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): service = SpaceSavedIdeaService(db, tenant_id=int(current_user.tenant_id)) try: idea, draft = await service.promote_to_launch_draft(idea_id=idea_id, user_id=int(current_user.id)) except SavedIdeaError as exc: return JSONResponse(status_code=exc.status_code, content=error_response(exc.status_code, f"{exc.code}: {exc.message}")) except PMFlowLaunchError as exc: return JSONResponse(status_code=exc.status_code, content=error_response(exc.status_code, f"{exc.code}: {exc.message}")) payload = SavedIdeaPromoteResponse( idea=idea, draft=draft, redirect_url="/project-launch?state=human_review", ) return success_response(payload.model_dump(mode="json")) __all__ = ["router"]