diff --git a/backend/ai_routes.py b/backend/ai_routes.py index 83390b9..4c941dc 100644 --- a/backend/ai_routes.py +++ b/backend/ai_routes.py @@ -37,10 +37,45 @@ async def api_status(): "available": has_key, "model": PROVIDERS[name]["model"] if has_key else None, } + + # ---- Autocomplete (Ollama) status ---- + ollama_cfg = PROVIDERS.get("ollama", {}) + ollama_url = (ollama_cfg.get("base_url") or "").rstrip("/v1").rstrip("/") + ollama_model = ollama_cfg.get("model", "") + autocomplete = { + "available": False, + "server_ok": False, + "model_loaded": False, + "model": ollama_model, + "error": None, + } + if ollama_url: + try: + import httpx + async with httpx.AsyncClient(timeout=5.0) as client: + # Check server health + list loaded models + r = await client.get(f"{ollama_url}/api/tags") + if r.status_code == 200: + autocomplete["server_ok"] = True + data = r.json() + models = [m.get("name", "") for m in data.get("models", [])] + # Check if our model (with or without tag) is loaded + model_base = ollama_model.split(":")[0] + autocomplete["model_loaded"] = any( + m == ollama_model or m.startswith(model_base + ":") + for m in models + ) + autocomplete["available"] = autocomplete["model_loaded"] + else: + autocomplete["error"] = f"HTTP {r.status_code}" + except Exception as e: + autocomplete["error"] = str(e) + return { "configured": any(p["available"] for p in providers.values()), "default_provider": DEFAULT_PROVIDER, "providers": providers, + "autocomplete": autocomplete, } diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..2361185 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,157 @@ +# ============================================================================= +# ObsiGate — Script de build et déploiement Docker (Windows PowerShell) +# ============================================================================= +# +# Usage : +# .\build.ps1 [-Prod | -Test] [OPTIONS] +# +# Environnements : +# -Prod Production (défaut) — docker-compose.yml +# -Test Test Windows (Docker Desktop) — docker-compose.test-win.yml +# +# Options : +# -BuildOnly Construit l'image sans démarrer le conteneur +# -NoCache Rebuild complet sans cache Docker (défaut) +# -UseCache Utilise le cache Docker (plus rapide si peu de changements) +# -Help Affiche cette aide +# +# Exemples : +# .\build.ps1 # Production +# .\build.ps1 -Test # Test Windows +# .\build.ps1 -Test -BuildOnly # Build test sans démarrer +# .\build.ps1 -Prod -UseCache # Production avec cache +# ============================================================================= + +param( + [switch]$Prod, + [switch]$Test, + [switch]$BuildOnly, + [switch]$NoCache, + [switch]$UseCache, + [switch]$Help +) + +# ----- Helpers ----- +function Write-Info { Write-Host "[INFO] $args" -ForegroundColor Cyan } +function Write-Ok { Write-Host "[OK] $args" -ForegroundColor Green } +function Write-Warn { Write-Host "[WARN] $args" -ForegroundColor Yellow } +function Write-Error { Write-Host "[ERR] $args" -ForegroundColor Red } + +# ----- Help ----- +if ($Help) { + Get-Help $PSCommandPath -Detailed + exit 0 +} + +# ----- Select environment ----- +$EnvMode = "prod" +$ComposeEnvName = "production" +if ($Test) { + $EnvMode = "test-win" + $ComposeEnvName = "test Windows" +} + +switch ($EnvMode) { + "prod" { $ComposeFile = "docker-compose.yml" } + "test-win" { $ComposeFile = "docker-compose.test-win.yml" } +} + +# ----- Pre-flight checks ----- +$dockerVersion = docker --version 2>$null +if (-not $dockerVersion) { + Write-Error "Docker est introuvable. Installez Docker Desktop avant de continuer." + exit 1 +} +Write-Ok "Docker détecté" + +$composeVersion = docker compose version 2>$null +if (-not $composeVersion) { + Write-Error "Docker Compose est introuvable (plugin docker compose requis)." + exit 1 +} +Write-Ok $composeVersion.Trim() + +# ----- Validate compose file ----- +if (-not (Test-Path $ComposeFile)) { + Write-Error "$ComposeFile introuvable dans $(Get-Location)" + exit 1 +} + +Write-Host "" +Write-Info "=== ObsiGate — Build & Deploy ($ComposeEnvName) ===" +Write-Info "Fichier compose : $ComposeFile" + +# ----- Test mode: create test directories ----- +if ($EnvMode -eq "test-win") { + Write-Info "Création des dossiers de test..." + + $testVault = "C:\dev\git\python\ObsiGate\test_vault" + $testDir = "C:\dev\git\python\ObsiGate\test_dir" + + if (-not (Test-Path $testVault)) { + New-Item -ItemType Directory -Force -Path $testVault | Out-Null + } + if (-not (Test-Path $testDir)) { + New-Item -ItemType Directory -Force -Path $testDir | Out-Null + } + + $welcomeFile = Join-Path $testVault "Accueil.md" + if (-not (Test-Path $welcomeFile)) { + @" +# Bienvenue dans le vault de test + +Ce vault est utilisé pour les tests de développement d'ObsiGate. +"@ | Out-File -Encoding UTF8 -FilePath $welcomeFile + Write-Ok "Note de test créée : test_vault\Accueil.md" + } +} + +# ----- Build the image ----- +$BuildArgs = @("-f", $ComposeFile) +if (-not $UseCache) { + $BuildArgs += "--no-cache" + Write-Info "Construction de l'image Docker (sans cache)..." +} else { + Write-Info "Construction de l'image Docker (avec cache)..." +} + +docker compose @BuildArgs build +if ($LASTEXITCODE -ne 0) { + Write-Error "Échec de la construction de l'image Docker." + exit $LASTEXITCODE +} +Write-Ok "Image construite avec succès" + +# ----- Stop if build-only ----- +if ($BuildOnly) { + Write-Host "" + Write-Info "Mode build-only : conteneur non démarré." + Write-Info "Pour démarrer manuellement : docker compose -f $ComposeFile up -d" + exit 0 +} + +# ----- Start the container ----- +Write-Info "Démarrage du conteneur..." +docker compose -f $ComposeFile up -d --remove-orphans +if ($LASTEXITCODE -ne 0) { + Write-Error "Échec du démarrage du conteneur." + exit $LASTEXITCODE +} +Write-Ok "Conteneur démarré" + +# ----- Show status & tip ----- +Write-Host "" +Write-Info "Statut du conteneur :" +docker compose -f $ComposeFile ps + +Write-Host "" +Write-Info "Application accessible sur http://localhost:2020" +if ($EnvMode -eq "prod") { + Write-Info "Login : admin / (mot de passe dans .env)" +} else { + Write-Info "Login : admin / test123" +} +Write-Host "" + +Write-Info "Logs (Ctrl+C pour quitter) :" +docker compose -f $ComposeFile logs --tail=20 -f diff --git a/build.sh b/build.sh index 3724e27..03cfee4 100644 --- a/build.sh +++ b/build.sh @@ -4,7 +4,12 @@ # ============================================================================= # # Usage : -# ./build.sh [OPTIONS] +# ./build.sh [--prod | --test-win | --test-linux] [OPTIONS] +# +# Environnements : +# --prod Production (défaut) — docker-compose.yml +# --test-win Test Windows (Docker Desktop) — docker-compose.test-win.yml +# --test-linux Test Linux — docker-compose.test-linux.yml # # Options : # --help, -h Affiche cette aide @@ -16,17 +21,17 @@ # # Étapes exécutées : # 1. Vérifie que Docker et Docker Compose sont installés -# 2. Valide le fichier docker-compose.yml -# 3. Vérifie que les volumes montés existent sur le host +# 2. Valide le fichier docker-compose choisi +# 3. Crée les dossiers de test si nécessaire (modes test uniquement) # 4. Construit l'image Docker (multi-stage, ~180MB) # 5. Démarre le conteneur en arrière-plan # 6. Affiche le statut et les logs # # Exemples : -# ./build.sh # Build from scratch + démarrage -# ./build.sh --cache # Build avec cache + démarrage -# ./build.sh --build-only # Build uniquement, sans démarrer -# ./build.sh --progress=plain # Sortie verbeuse +# ./build.sh # Production +# ./build.sh --test-win # Test Windows +# ./build.sh --test-linux --cache # Test Linux avec cache +# ./build.sh --test-win --build-only # Build test Windows sans démarrer # ============================================================================= set -euo pipefail @@ -43,6 +48,8 @@ show_help() { } # ----- Parse args ----- +ENV_MODE="prod" +COMPOSE_FILE="" BUILD_ONLY=false USE_CACHE=false BUILD_ARGS=() @@ -52,6 +59,15 @@ for arg in "$@"; do --help|-h) show_help ;; + --prod) + ENV_MODE="prod" + ;; + --test-win) + ENV_MODE="test-win" + ;; + --test-linux) + ENV_MODE="test-linux" + ;; --build-only) BUILD_ONLY=true ;; @@ -67,6 +83,26 @@ for arg in "$@"; do esac done +# ----- Select compose file ----- +case "$ENV_MODE" in + prod) + COMPOSE_FILE="docker-compose.yml" + COMPOSE_ENV_NAME="production" + ;; + test-win) + COMPOSE_FILE="docker-compose.test-win.yml" + COMPOSE_ENV_NAME="test Windows" + ;; + test-linux) + COMPOSE_FILE="docker-compose.test-linux.yml" + COMPOSE_ENV_NAME="test Linux" + ;; + *) + error "Environnement inconnu : $ENV_MODE" + exit 1 + ;; +esac + # ----- Pre-flight checks ----- if ! command -v docker &>/dev/null; then error "Docker est introuvable. Installez Docker avant de continuer." @@ -87,21 +123,46 @@ fi ok "$($COMPOSE_CMD version --short 2>/dev/null || $COMPOSE_CMD version | head -1)" # ----- Validate compose file ----- -COMPOSE_FILE="docker-compose.yml" if [[ ! -f "$COMPOSE_FILE" ]]; then error "$COMPOSE_FILE introuvable dans $(pwd)" exit 1 fi -info "=== ObsiGate — Build & Deploy ===" +echo "" +info "=== ObsiGate — Build & Deploy ($COMPOSE_ENV_NAME) ===" info "Fichier compose : $COMPOSE_FILE" -# ----- Extract volume mount paths from compose file and warn about missing ones ----- +# ----- Test mode: create test directories ----- +if [[ "$ENV_MODE" == "test-win" ]]; then + info "Création des dossiers de test Windows..." + mkdir -p /c/dev/git/python/ObsiGate/test_vault + mkdir -p /c/dev/git/python/ObsiGate/test_dir + # Create a sample note for the test vault + if [[ ! -f /c/dev/git/python/ObsiGate/test_vault/Accueil.md ]]; then + echo "# Bienvenue dans le vault de test" > /c/dev/git/python/ObsiGate/test_vault/Accueil.md + echo "" >> /c/dev/git/python/ObsiGate/test_vault/Accueil.md + echo "Ce vault est utilisé pour les tests de développement d'ObsiGate." >> /c/dev/git/python/ObsiGate/test_vault/Accueil.md + ok "Note de test créée : test_vault/Accueil.md" + fi +elif [[ "$ENV_MODE" == "test-linux" ]]; then + info "Création des dossiers de test Linux..." + mkdir -p /tmp/obsigate-test-vault + mkdir -p /tmp/obsigate-test-dir + mkdir -p /tmp/obsigate-test-data + chmod -R 777 /tmp/obsigate-test-data 2>/dev/null || true + if [[ ! -f /tmp/obsigate-test-vault/Accueil.md ]]; then + echo "# Bienvenue dans le vault de test" > /tmp/obsigate-test-vault/Accueil.md + echo "" >> /tmp/obsigate-test-vault/Accueil.md + echo "Ce vault est utilisé pour les tests de développement d'ObsiGate." >> /tmp/obsigate-test-vault/Accueil.md + ok "Note de test créée : test-vault/Accueil.md" + fi +fi + +# ----- Extract volume mount paths and warn about missing ones ----- info "Vérification des volumes montés sur le host..." VOLUME_COUNT=0 MISSING_COUNT=0 while IFS= read -r line; do - # Lines like: - /host/path:/container/path if [[ "$line" =~ ^[[:space:]]*-[[:space:]]+(/[^:]+): ]]; then host_path="${BASH_REMATCH[1]}" VOLUME_COUNT=$((VOLUME_COUNT + 1)) @@ -115,7 +176,7 @@ while IFS= read -r line; do done < <(grep -E '^\s+-\s+/' "$COMPOSE_FILE") if [[ "$VOLUME_COUNT" -eq 0 ]]; then - warn "Aucun volume host détecté dans $COMPOSE_FILE. Pensez à configurer vos vaults." + warn "Aucun volume host détecté dans $COMPOSE_FILE." fi if [[ "$MISSING_COUNT" -gt 0 ]]; then warn "$MISSING_COUNT volume(s) absent(s). Vérifiez vos chemins dans $COMPOSE_FILE." @@ -132,27 +193,27 @@ else info "Construction de l'image Docker (avec cache)..." fi -$COMPOSE_CMD build "${BUILD_FLAGS[@]}" "${BUILD_ARGS[@]}" +$COMPOSE_CMD -f "$COMPOSE_FILE" build "${BUILD_FLAGS[@]}" "${BUILD_ARGS[@]}" ok "Image construite avec succès" # ----- Stop if build-only ----- if [[ "$BUILD_ONLY" == true ]]; then echo "" info "Mode build-only : conteneur non démarré." - info "Pour démarrer manuellement : $COMPOSE_CMD up -d" + info "Pour démarrer manuellement : $COMPOSE_CMD -f $COMPOSE_FILE up -d" exit 0 fi # ----- Start the container ----- info "Démarrage du conteneur..." -$COMPOSE_CMD up -d --remove-orphans +$COMPOSE_CMD -f "$COMPOSE_FILE" up -d --remove-orphans ok "Conteneur démarré" # ----- Show status ----- echo "" info "Statut du conteneur :" -$COMPOSE_CMD ps +$COMPOSE_CMD -f "$COMPOSE_FILE" ps echo "" info "Logs récents (Ctrl+C pour quitter) :" -$COMPOSE_CMD logs --tail=20 -f +$COMPOSE_CMD -f "$COMPOSE_FILE" logs --tail=20 -f diff --git a/docker-compose.podman.yml b/docker-compose.podman.yml deleted file mode 100644 index 4c23524..0000000 --- a/docker-compose.podman.yml +++ /dev/null @@ -1,44 +0,0 @@ -services: - obsigate-podman: - build: - context: . - image: obsigate:latest - container_name: obsigate-podman - user: "1000:1000" - restart: unless-stopped - ports: - - "2022:8080" - volumes: - - /mnt/c/dev/git/python/obsigate/test_vault:/vaults/TestVault - - /mnt/c/dev/git/python/obsigate/test_dir:/vaults/TestDir - - /mnt/c/dev/git/python/obsigate/data:/app/data - environment: - - VAULT_1_NAME=TestVault - - VAULT_1_PATH=/vaults/TestVault - - DIR_1_NAME=TestDir - - DIR_1_PATH=/vaults/TestDir - - OBSIGATE_AUTH_ENABLED=false - - OBSIGATE_ADMIN_USER=admin - - OBSIGATE_ADMIN_PASSWORD=test123 - - AI_DEFAULT_PROVIDER=deepseek - - OLLAMA_BASE_URL=http://ollama:11434/v1 - - OLLAMA_MODEL=qwen2.5-coder:1.5b - - ollama: - image: ollama/ollama:latest - container_name: obsigate-ollama - restart: unless-stopped - ports: - - "11434:11434" - volumes: - - ollama_data:/root/.ollama - entrypoint: ["/bin/sh", "-c"] - command: - - | - ollama serve & - sleep 5 - ollama pull qwen2.5-coder:1.5b - wait - -volumes: - ollama_data: diff --git a/docker-compose.test-linux.yml b/docker-compose.test-linux.yml new file mode 100644 index 0000000..1b6cb2d --- /dev/null +++ b/docker-compose.test-linux.yml @@ -0,0 +1,53 @@ +# ============================================================================= +# ObsiGate — Environnement de TEST Linux +# ============================================================================= +# Utilisé par : build.sh --test-linux +# Ports : 2021 (app), 11435 (Ollama) — ports distincts pour éviter conflits +# Vaults : dossiers temporaires (/tmp/obsigate-test-*) +# Auth : activé, admin / test123 +# ============================================================================= + +services: + obsigate: + build: + context: . + image: obsigate:latest + container_name: obsigate-test + user: "1000:1000" + restart: unless-stopped + ports: + - "2021:8080" + volumes: + - /tmp/obsigate-test-vault:/vaults/TestVault + - /tmp/obsigate-test-dir:/vaults/TestDir + - /tmp/obsigate-test-data:/app/data + environment: + - VAULT_1_NAME=TestVault + - VAULT_1_PATH=/vaults/TestVault + - DIR_1_NAME=TestDir + - DIR_1_PATH=/vaults/TestDir + - OBSIGATE_AUTH_ENABLED=true + - OBSIGATE_ADMIN_USER=admin + - OBSIGATE_ADMIN_PASSWORD=test123 + - AI_DEFAULT_PROVIDER=deepseek + - OLLAMA_BASE_URL=http://ollama:11435/v1 + - OLLAMA_MODEL=qwen2.5-coder:1.5b + + ollama: + image: ollama/ollama:latest + container_name: obsigate-test-ollama + restart: unless-stopped + ports: + - "11435:11434" + volumes: + - ollama_data:/root/.ollama + entrypoint: ["/bin/sh", "-c"] + command: + - | + ollama serve & + sleep 5 + ollama pull qwen2.5-coder:1.5b + wait + +volumes: + ollama_data: diff --git a/docker-compose.test-win.yml b/docker-compose.test-win.yml new file mode 100644 index 0000000..01f2480 --- /dev/null +++ b/docker-compose.test-win.yml @@ -0,0 +1,55 @@ +# ============================================================================= +# ObsiGate — Environnement de TEST Windows (Docker Desktop) +# ============================================================================= +# Utilisé par : build.sh --test-win | build.ps1 -Test +# Ports : 2020 (app), 11434 (Ollama) +# Vaults : chemins Windows via Docker Desktop (/c/dev/git/...) +# Auth : activé, admin / test123 +# NOTE : Pas de user: "1000:1000" → tourne en root (évite les problèmes +# de permissions NTFS sur les volumes bind-mount Windows) +# ============================================================================= + +services: + obsigate: + build: + context: . + image: obsigate:latest + container_name: obsigate-test + restart: unless-stopped + ports: + - "2020:8080" + volumes: + - /c/dev/git/python/ObsiGate/test_vault:/vaults/TestVault + - /c/dev/git/python/ObsiGate/test_dir:/vaults/TestDir + - obsigate_data:/app/data + environment: + - VAULT_1_NAME=TestVault + - VAULT_1_PATH=/vaults/TestVault + - DIR_1_NAME=TestDir + - DIR_1_PATH=/vaults/TestDir + - OBSIGATE_AUTH_ENABLED=true + - OBSIGATE_ADMIN_USER=admin + - OBSIGATE_ADMIN_PASSWORD=test123 + - AI_DEFAULT_PROVIDER=deepseek + - OLLAMA_BASE_URL=http://ollama:11434/v1 + - OLLAMA_MODEL=qwen2.5-coder:1.5b + + ollama: + image: ollama/ollama:latest + container_name: obsigate-test-ollama + restart: unless-stopped + ports: + - "11434:11434" + volumes: + - ollama_data:/root/.ollama + entrypoint: ["/bin/sh", "-c"] + command: + - | + ollama serve & + sleep 5 + ollama pull qwen2.5-coder:1.5b + wait + +volumes: + ollama_data: + obsigate_data: diff --git a/docker-compose.test.yml b/docker-compose.test.yml deleted file mode 100644 index 5676884..0000000 --- a/docker-compose.test.yml +++ /dev/null @@ -1,44 +0,0 @@ -services: - obsigate: - build: - context: . - image: obsigate:latest - container_name: obsigate - user: "1000:1000" - restart: unless-stopped - ports: - - "2020:8080" - volumes: - - /c/dev/git/python/obsigate/test_vault:/vaults/TestVault - - /c/dev/git/python/obsigate/test_dir:/vaults/TestDir - - /c/dev/git/python/obsigate/data:/app/data - environment: - - VAULT_1_NAME=TestVault - - VAULT_1_PATH=/vaults/TestVault - - DIR_1_NAME=TestDir - - DIR_1_PATH=/vaults/TestDir - - OBSIGATE_AUTH_ENABLED=false - - OBSIGATE_ADMIN_USER=admin - - OBSIGATE_ADMIN_PASSWORD=test123 - - AI_DEFAULT_PROVIDER=deepseek - - OLLAMA_BASE_URL=http://ollama:11434/v1 - - OLLAMA_MODEL=qwen2.5-coder:1.5b - - ollama: - image: ollama/ollama:latest - container_name: obsigate-ollama - restart: unless-stopped - ports: - - "11434:11434" - volumes: - - ollama_data:/root/.ollama - entrypoint: ["/bin/sh", "-c"] - command: - - | - ollama serve & - sleep 5 - ollama pull qwen2.5-coder:1.5b - wait - -volumes: - ollama_data: diff --git a/docker-compose.yml b/docker-compose.yml index 3e4946e..b8513e4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,12 @@ +# ============================================================================= +# ObsiGate — Déploiement PRODUCTION (serveur Linux) +# ============================================================================= +# Utilisé par : build.sh --prod (défaut) +# Ports : 2020 (app), 11434 (Ollama) +# Vaults : chemins absolus Linux (/NFS/..., /DOCKER_CONFIG/...) +# Auth : activé, mot de passe admin dans .env +# ============================================================================= + services: obsigate: build: @@ -41,11 +50,10 @@ services: - VAULT_5_PATH=/vaults/SessionsManager - DIR_1_NAME=Bruno - DIR_1_PATH=/vaults/bruno - # Auth configuration (edit values in .env file) - OBSIGATE_AUTH_ENABLED=true - OBSIGATE_ADMIN_USER=admin - # OBSIGATE_ADMIN_PASSWORD → définir dans .env (pas ici !) - # - OBSIGATE_SECURE_COOKIES=false # Set true if behind HTTPS reverse proxy + # OBSIGATE_ADMIN_PASSWORD → .env + # OBSIGATE_SECURE_COOKIES=true # si derrière reverse proxy HTTPS - OLLAMA_BASE_URL=http://ollama:11434/v1 - OLLAMA_MODEL=qwen2.5-coder:1.5b env_file: diff --git a/frontend/js/sync.js b/frontend/js/sync.js index e386155..745707e 100644 --- a/frontend/js/sync.js +++ b/frontend/js/sync.js @@ -341,15 +341,18 @@ function _renderSyncPanel(panel) { let _aiProcessing = false; let _aiConfigured = null; // null = unknown, true/false +let _aiAutocomplete = null; // null = unknown, {available, server_ok, model_loaded, model, error} /** Check if AI is configured by calling the /api/ai/status endpoint. - * Updates _aiConfigured and refreshes the panel if open. */ + * Updates _aiConfigured, _aiAutocomplete and refreshes the panel if open. */ async function _checkAIStatus() { try { const status = await api('/api/ai/status'); _aiConfigured = status.configured === true; + _aiAutocomplete = status.autocomplete || null; } catch { _aiConfigured = false; + _aiAutocomplete = null; } _updateAIStatusInPanel(); } @@ -359,34 +362,66 @@ function _renderAIStatusSection(panel) { section.className = 'sync-panel__ai-section'; section.id = 'sync-panel-ai'; - let statusIcon, statusText, statusClass; + // ---- External provider status ---- + let extIcon, extText, extClass; if (_aiProcessing) { - statusIcon = 'loader'; - statusText = 'Traitement en cours…'; - statusClass = 'sync-panel__ai--processing'; + extIcon = 'loader'; + extText = 'Traitement en cours…'; + extClass = 'sync-panel__ai--processing'; } else if (_aiConfigured === true) { - statusIcon = 'check-circle'; - statusText = 'IA configurée et active'; - statusClass = 'sync-panel__ai--active'; + extIcon = 'check-circle'; + extText = 'Fournisseur externe actif'; + extClass = 'sync-panel__ai--active'; } else if (_aiConfigured === false) { - statusIcon = 'x-circle'; - statusText = 'IA non configurée'; - statusClass = 'sync-panel__ai--inactive'; + extIcon = 'x-circle'; + extText = 'Aucun fournisseur'; + extClass = 'sync-panel__ai--inactive'; } else { - statusIcon = 'help-circle'; - statusText = 'Statut IA inconnu'; - statusClass = 'sync-panel__ai--unknown'; + extIcon = 'help-circle'; + extText = 'Vérification…'; + extClass = 'sync-panel__ai--unknown'; } - section.innerHTML = ` -
- Intelligence Artificielle - - - ${statusText} - -
- `; + // ---- Autocomplete (Ollama) status ---- + let acIcon, acText, acClass; + if (_aiAutocomplete && _aiAutocomplete.available) { + acIcon = 'check-circle'; + acText = 'Auto-complétion active'; + acClass = 'sync-panel__ai--active'; + } else if (_aiAutocomplete && _aiAutocomplete.server_ok && !_aiAutocomplete.model_loaded) { + acIcon = 'alert-triangle'; + acText = 'Modèle non chargé'; + acClass = 'sync-panel__ai--inactive'; + } else if (_aiAutocomplete && _aiAutocomplete.error) { + acIcon = 'x-circle'; + acText = 'Serveur Ollama injoignable'; + acClass = 'sync-panel__ai--inactive'; + } else if (_aiAutocomplete === null) { + acIcon = 'help-circle'; + acText = 'Vérification…'; + acClass = 'sync-panel__ai--unknown'; + } else { + acIcon = 'x-circle'; + acText = 'Non configuré'; + acClass = 'sync-panel__ai--inactive'; + } + + section.innerHTML = '' + + '
Intelligence Artificielle
' + + '
' + + 'Fournisseur' + + '' + + '' + + extText + + '' + + '
' + + '
' + + 'Auto-complétion' + + '' + + '' + + acText + + '' + + '
'; panel.appendChild(section); // Trigger icon refresh via global diff --git a/frontend/style.css b/frontend/style.css index ddd5d45..deac04a 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -4239,16 +4239,23 @@ body.resizing-v { padding: 10px 14px; } -.sync-panel__ai-header { - display: flex; - align-items: center; - justify-content: space-between; -} - .sync-panel__ai-title { font-size: 0.78rem; font-weight: 600; color: var(--text-primary); + margin-bottom: 8px; +} + +.sync-panel__ai-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 5px; +} + +.sync-panel__ai-label { + font-size: 0.72rem; + color: var(--text-muted); } .sync-panel__ai-state {