Files
Loki/backend/app/main.py
T
MichaelandClaude Opus 4.8 de2befe31e Phase 7 : outils web_search et run_shell avec validation
Backend :
- tools.py : web_search (DuckDuckGo HTML sans clé, ou SearxNG via SEARX_URL) et
  run_shell (confiné au workspace, sortie bornée, timeout)
- agent.py : run_shell sensible -> émet tool_confirm et ne s'exécute pas tant que
  l'utilisateur n'a pas validé (confirm_shell)
- agent_config.py : web_search/run_shell désactivés par défaut, flag confirm_shell
- routes/shell.py : POST /api/shell/run exécute une commande validée
- routes/chat.py : relaie l'événement tool_confirm, passe confirm_shell

Frontend :
- streamChat : événement tool_confirm
- store : pendingShell + approveShell/rejectShell (réinjecte le résultat à l'agent)
- ChatPanel : carte de validation de commande (Approuver / Refuser)
- SettingsView : toggles web_search/run_shell, marqueur sensible, switch confirm_shell
- ToolCard : glyphes web_search/run_shell, statut 'à valider', aperçu d'argument

Tests : run_shell confiné, gate de confirmation (commande dangereuse non exécutée),
route shell, parser DuckDuckGo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
2026-06-29 21:09:27 +00:00

66 lines
1.7 KiB
Python

"""Point d'entrée FastAPI de Loki.
Sert l'API (/api/*) et, en production, le frontend React compilé (static/).
"""
from __future__ import annotations
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from . import db
from .config import settings
from .routes import chat, config, files, models, sessions, shell
@asynccontextmanager
async def lifespan(_: FastAPI):
db.init_db()
yield
app = FastAPI(
title="Loki", description="Agent IA local sur Ollama", lifespan=lifespan
)
# En dev, le front tourne sur Vite (5173). On autorise le CORS large ;
# en prod le front est servi par le même origin, donc sans impact.
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(models.router)
app.include_router(sessions.router)
app.include_router(chat.router)
app.include_router(files.router)
app.include_router(config.router)
app.include_router(shell.router)
@app.get("/api/health")
async def health() -> dict:
return {"status": "ok", "service": "loki"}
# ── Service du frontend compilé (présent uniquement en image Docker) ─────
_STATIC_DIR = os.path.join(os.path.dirname(__file__), "..", "static")
if os.path.isdir(_STATIC_DIR):
app.mount(
"/assets",
StaticFiles(directory=os.path.join(_STATIC_DIR, "assets")),
name="assets",
)
@app.get("/{full_path:path}")
async def spa_fallback(full_path: str):
"""Renvoie index.html pour toutes les routes (SPA React)."""
index = os.path.join(_STATIC_DIR, "index.html")
return FileResponse(index)