feat: декомпозиция JS на модули (auth, tasks, chat, app) и добавление гостевого режима с локальной нейросетью

This commit is contained in:
2026-08-09 10:40:18 +03:00
parent cc6b80d714
commit a47514648c
6 changed files with 398 additions and 349 deletions
+52 -145
View File
@@ -1,166 +1,73 @@
# === ANCHOR: IMPORTS_START ===
import os
import json
import sqlite3
from typing import List, Optional, Dict, Any
from fastapi import FastAPI, HTTPException, Depends, Security
import urllib.request
from typing import List, Dict, Any, Optional
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.responses import FileResponse
from pydantic import BaseModel
from llm.agent import process_chat_message
# === ANCHOR: IMPORTS_END ===
from llm.db_tools import db_get_tasks
API_TOKEN = "scud_secret_token_2026"
OLLAMA_URL = "http://192.168.11.3:11434/api/chat"
MODEL_NAME = "qwen2.5:14b"
# === ANCHOR: APP_INIT_START ===
app = FastAPI(
title="SCUD Orion AI Context & Task Tracker API",
version="2.0.0",
description="REST API локального контекста, памяти и трекинга задач для ИИ-аудитора"
)
DB_NAME = "/home/puh/scud_orion_ai_v2/data/scud_orion_ai.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="Недействительный токен доступа")
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
if credentials.credentials != API_TOKEN:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Неверный токен доступа",
headers={"WWW-Authenticate": "Bearer"},
)
return credentials.credentials
app = FastAPI(title="SCUD Orion AI Context API")
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)
class ChatRequest(BaseModel):
session_id: str
message: str
@app.get("/")
def read_root():
return FileResponse("static/index.html")
# === ANCHOR: STATIC_ROUTES_END ===
@app.get("/api/v1/tasks")
def get_tasks(token: str = Depends(verify_token)):
return db_get_tasks()
# === 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/chat")
def chat_endpoint(req: ChatRequest, token: str = Depends(verify_token)):
reply, _ = process_chat_message(req.message)
return {"reply": reply}
@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()
# Эндпоинт для гостевого режима (без авторизации и без привязки к проекту)
@app.post("/api/v1/chat/guest")
def guest_chat_endpoint(req: ChatRequest):
payload = {
"model": MODEL_NAME,
"messages": [
{"role": "system", "content": "Ты — полезный ИИ-ассистент. Отвечай на вопросы пользователя четко и по существу."},
{"role": "user", "content": req.message}
],
"stream": False,
"options": {"num_predict": 2048, "temperature": 0.3}
}
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 ===
try:
req_ollama = urllib.request.Request(
OLLAMA_URL,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req_ollama) as response:
res_data = json.loads(response.read().decode("utf-8"))
reply = res_data.get("message", {}).get("content", "").strip()
return {"reply": reply}
except Exception as e:
return {"reply": f"Ошибка связи с локальной нейросетью: {e}"}