# === ANCHOR: IMPORTS_START === import os import json import sqlite3 from typing import List, Optional, Dict, Any from fastapi import FastAPI, HTTPException, Depends, Security from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse, HTMLResponse from pydantic import BaseModel from llm_agent import process_chat_message # === ANCHOR: IMPORTS_END === # === ANCHOR: APP_INIT_START === app = FastAPI( title="SCUD Orion AI Context & Task Tracker API", version="2.0.0", description="REST API локального контекста, памяти и трекинга задач для ИИ-аудитора" ) DB_NAME = "context_memory.db" SECURITY_TOKEN = os.getenv("API_BEARER_TOKEN", "scud_secret_token_2026") security = HTTPBearer() # === ANCHOR: APP_INIT_END === # === ANCHOR: DATABASE_HELPERS_START === def init_chat_table(): """Создает таблицу истории чата, если она не существует.""" conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS chat_sessions ( session_id TEXT PRIMARY KEY, history_json TEXT NOT NULL, updated_at TEXT DEFAULT CURRENT_TIMESTAMP ) """) conn.commit() conn.close() init_chat_table() def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)): if credentials.credentials != SECURITY_TOKEN: raise HTTPException(status_code=403, detail="Недействительный токен доступа") return credentials.credentials def get_db_connection(): conn = sqlite3.connect(DB_NAME) conn.row_factory = sqlite3.Row return conn # === ANCHOR: DATABASE_HELPERS_END === # === ANCHOR: SCHEMAS_START === class TaskSchema(BaseModel): task_id: str module: str title: str status: str priority: str completed_at: Optional[str] = None class RuleSchema(BaseModel): id: int category: str rule_text: str is_active: int class ChatRequest(BaseModel): session_id: str = "default" message: str class ChatResponse(BaseModel): session_id: str reply: str # === ANCHOR: SCHEMAS_END === # === ANCHOR: STATIC_ROUTES_START === # Подключаем папку static для отдачи HTML-интерфейса app.mount("/static", StaticFiles(directory="static"), name="static") @app.get("/", response_class=HTMLResponse) def read_root(): return FileResponse("static/index.html") # === ANCHOR: STATIC_ROUTES_END === # === ANCHOR: TASKS_ENDPOINTS_START === @app.get("/api/v1/tasks", response_model=List[TaskSchema], dependencies=[Depends(verify_token)]) def get_all_tasks(status: Optional[str] = None): conn = get_db_connection() cursor = conn.cursor() if status: cursor.execute("SELECT * FROM tasks WHERE status = ? ORDER BY task_id", (status.upper(),)) else: cursor.execute("SELECT * FROM tasks ORDER BY task_id") rows = cursor.fetchall() conn.close() return [dict(r) for r in rows] @app.post("/api/v1/tasks/{task_id}/complete", dependencies=[Depends(verify_token)]) def mark_task_completed(task_id: str): conn = get_db_connection() cursor = conn.cursor() cursor.execute( "UPDATE tasks SET status = 'COMPLETED', completed_at = CURRENT_TIMESTAMP WHERE task_id = ?", (task_id.upper(),) ) if cursor.rowcount == 0: conn.close() raise HTTPException(status_code=404, detail=f"Задача {task_id} не найдена") conn.commit() conn.close() return {"status": "success", "message": f"Задача {task_id} успешно выполнена"} # === ANCHOR: TASKS_ENDPOINTS_END === # === ANCHOR: RULES_AND_CHAT_ENDPOINTS_START === @app.get("/api/v1/rules", response_model=List[RuleSchema], dependencies=[Depends(verify_token)]) def get_active_rules(): conn = get_db_connection() cursor = conn.cursor() cursor.execute("SELECT * FROM architecture_memory WHERE is_active = 1 ORDER BY id") rows = cursor.fetchall() conn.close() return [dict(r) for r in rows] @app.post("/api/v1/chat", response_model=ChatResponse, dependencies=[Depends(verify_token)]) def chat_endpoint(req: ChatRequest): """Принимает сообщение, подтягивает историю из БД, выполняет вызовы функций к БД и сохраняет историю.""" conn = get_db_connection() cursor = conn.cursor() cursor.execute("SELECT history_json FROM chat_sessions WHERE session_id = ?", (req.session_id,)) row = cursor.fetchone() chat_history = json.loads(row["history_json"]) if row else [] reply, updated_history = process_chat_message(req.message, chat_history) cursor.execute( "INSERT OR REPLACE INTO chat_sessions (session_id, history_json, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)", (req.session_id, json.dumps(updated_history, ensure_ascii=False)) ) conn.commit() conn.close() return ChatResponse(session_id=req.session_id, reply=reply) @app.get("/health") def health_check(): return {"status": "healthy", "service": "scud_context_api", "version": "2.0.0"} # === ANCHOR: RULES_AND_CHAT_ENDPOINTS_END ===