Files
2026-08-06 18:55:21 +03:00

166 lines
5.2 KiB
Python

# === 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 ===