862e6b7b25
Co-authored-by: Cursor <cursoragent@cursor.com>
313 lines
5.6 KiB
Python
313 lines
5.6 KiB
Python
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import RedirectResponse
|
|
|
|
|
|
|
|
from app.analytics_store import analytics_store
|
|
from app.assistant_engine import get_default_profile, process_message
|
|
|
|
from app.config import settings
|
|
|
|
from app.data.knowledge_base import QUICK_PROMPTS
|
|
|
|
from app.data.mock_data import get_services
|
|
|
|
from app.llm.client import get_llm_client
|
|
|
|
from app.models import (
|
|
AnalyticsSummary,
|
|
Appeal,
|
|
AssistantResult,
|
|
ChatRequest,
|
|
CreateAppealRequest,
|
|
FeedbackRequest,
|
|
NotificationItem,
|
|
ServiceItem,
|
|
UserProfile,
|
|
)
|
|
from app.notification_store import notification_store
|
|
|
|
from app.rag.service import init_local_rag
|
|
|
|
from app.store import appeal_store
|
|
|
|
|
|
|
|
_rag_status: dict[str, object] = {}
|
|
|
|
|
|
|
|
|
|
|
|
@asynccontextmanager
|
|
|
|
async def lifespan(app: FastAPI):
|
|
|
|
global _rag_status
|
|
|
|
_rag_status = init_local_rag()
|
|
|
|
yield
|
|
|
|
|
|
|
|
|
|
|
|
app = FastAPI(
|
|
|
|
title="ВКУС IT Assistant API",
|
|
|
|
description="Backend витрины корпоративных услуг — ядро обработки запросов",
|
|
|
|
version="1.0.0",
|
|
|
|
lifespan=lifespan,
|
|
|
|
)
|
|
|
|
|
|
|
|
app.add_middleware(
|
|
|
|
CORSMiddleware,
|
|
|
|
allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"],
|
|
|
|
allow_credentials=True,
|
|
|
|
allow_methods=["*"],
|
|
|
|
allow_headers=["*"],
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/")
|
|
def root():
|
|
"""Корень backend — перенаправление на витрину (frontend)."""
|
|
return RedirectResponse(url="http://127.0.0.1:5173")
|
|
|
|
|
|
@app.get("/api")
|
|
def api_root() -> dict[str, str]:
|
|
return {
|
|
"message": "ВКУС IT Assistant API",
|
|
"docs": "/docs",
|
|
"health": "/api/health",
|
|
"frontend": "http://localhost:5173",
|
|
}
|
|
|
|
|
|
@app.get("/api/llm/test")
|
|
def test_llm() -> dict[str, object]:
|
|
result = get_llm_client().test_connection()
|
|
return {
|
|
"ok": result.ok,
|
|
"configured": result.configured,
|
|
"enabled": settings.llm_enabled,
|
|
"model": result.model,
|
|
"endpoint": result.endpoint,
|
|
"message": result.message,
|
|
"sample": result.sample,
|
|
"statusCode": result.status_code,
|
|
"error": result.error,
|
|
}
|
|
|
|
|
|
@app.get("/api/health")
|
|
|
|
def health() -> dict[str, object]:
|
|
|
|
llm = get_llm_client()
|
|
|
|
return {
|
|
|
|
"status": "ok",
|
|
|
|
"rag": {
|
|
|
|
**_rag_status,
|
|
|
|
"note": "Временный локальный RAG в backend. Целевой контур — платформа ИИ.",
|
|
|
|
},
|
|
|
|
"llm": {
|
|
"enabled": settings.llm_enabled,
|
|
"configured": llm.is_configured(),
|
|
"model": settings.llm_model,
|
|
"endpoint": settings.llm_chat_url,
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/api/profile", response_model=UserProfile)
|
|
|
|
def get_profile() -> UserProfile:
|
|
|
|
return get_default_profile()
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/api/prompts", response_model=list[str])
|
|
|
|
def get_prompts() -> list[str]:
|
|
|
|
return QUICK_PROMPTS
|
|
|
|
|
|
|
|
|
|
|
|
@app.post("/api/chat", response_model=AssistantResult)
|
|
|
|
def chat(request: ChatRequest) -> AssistantResult:
|
|
|
|
profile = get_default_profile()
|
|
|
|
conversation = [turn.model_dump() for turn in request.conversation]
|
|
|
|
return process_message(request.message, profile, request.is_first_message, conversation)
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/api/appeals", response_model=list[Appeal])
|
|
|
|
def list_appeals() -> list[Appeal]:
|
|
|
|
return appeal_store.list_all()
|
|
|
|
|
|
|
|
|
|
|
|
@app.post("/api/appeals", response_model=Appeal)
|
|
|
|
def create_appeal(request: CreateAppealRequest) -> Appeal:
|
|
|
|
appeal = appeal_store.create(request)
|
|
|
|
notification_store.add(
|
|
|
|
title="Обращение создано",
|
|
|
|
text=f"{appeal.number}: {appeal.title}. Отслеживайте статус во вкладке «История моих обращений».",
|
|
|
|
link_tab="appeals",
|
|
|
|
)
|
|
|
|
return appeal
|
|
|
|
|
|
|
|
|
|
|
|
@app.post("/api/feedback")
|
|
|
|
def submit_feedback(request: FeedbackRequest) -> dict[str, object]:
|
|
|
|
analytics_store.record(request)
|
|
|
|
if request.helped:
|
|
|
|
notification_store.add(
|
|
|
|
title="Спасибо за отзыв",
|
|
|
|
text="Ваш ответ «Помогло» учтён и поможет улучшить сервис.",
|
|
|
|
link_tab="vitrina",
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
notification_store.add(
|
|
|
|
title="Переход к оформлению обращения",
|
|
|
|
text="Ответ не помог — подготовьте заявку с контекстом диалога.",
|
|
|
|
link_tab="vitrina",
|
|
|
|
)
|
|
|
|
return {"ok": True, "analytics": analytics_store.summary().model_dump(by_alias=True)}
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/api/analytics/summary", response_model=AnalyticsSummary)
|
|
|
|
def analytics_summary() -> AnalyticsSummary:
|
|
|
|
return analytics_store.summary()
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/api/notifications", response_model=list[NotificationItem])
|
|
|
|
def list_notifications() -> list[NotificationItem]:
|
|
|
|
return notification_store.list_all()
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/api/notifications/unread-count")
|
|
|
|
def notifications_unread_count() -> dict[str, int]:
|
|
|
|
return {"count": notification_store.unread_count()}
|
|
|
|
|
|
|
|
|
|
|
|
@app.post("/api/notifications/{notification_id}/read")
|
|
|
|
def mark_notification_read(notification_id: str) -> dict[str, bool]:
|
|
|
|
return {"ok": notification_store.mark_read(notification_id)}
|
|
|
|
|
|
|
|
|
|
|
|
@app.post("/api/notifications/read-all")
|
|
|
|
def mark_all_notifications_read() -> dict[str, int]:
|
|
|
|
return {"marked": notification_store.mark_all_read()}
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/api/services", response_model=list[ServiceItem])
|
|
|
|
def list_services() -> list[ServiceItem]:
|
|
|
|
return get_services()
|
|
|
|
|