Initial commit: VKUS vitrina MVP (frontend + backend).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user