166 lines
5.2 KiB
Python
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 === |