PMFlow/backend/app/api/space_api/__init__.py

183 lines
6.8 KiB
Python

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"]