Add autocomplete status check and restructure test environments
CI / lint (push) Failing after 26s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Failing after 10s

Add Ollama autocomplete status endpoint to AI routes, update sync panel
UI to display autocomplete state, replace monolithic test compose files
with separate Linux/Windows variants, and add PowerShell build script
for Windows deployment
This commit is contained in:
2026-06-18 09:40:09 -04:00
parent 8acfd1f19a
commit daab917ed1
10 changed files with 460 additions and 137 deletions
+35
View File
@@ -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,
}
+157
View File
@@ -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
+78 -17
View File
@@ -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
-44
View File
@@ -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:
+53
View File
@@ -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:
+55
View File
@@ -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:
-44
View File
@@ -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:
+11 -3
View File
@@ -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:
+58 -23
View File
@@ -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 = `
<div class="sync-panel__ai-header">
<span class="sync-panel__ai-title">Intelligence Artificielle</span>
<span class="sync-panel__ai-state ${statusClass}" data-lucide-icon="${statusIcon}">
<i data-lucide="${statusIcon}" style="width:14px;height:14px"></i>
${statusText}
</span>
</div>
`;
// ---- 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 = '' +
'<div class="sync-panel__ai-title">Intelligence Artificielle</div>' +
'<div class="sync-panel__ai-row">' +
'<span class="sync-panel__ai-label">Fournisseur</span>' +
'<span class="sync-panel__ai-state ' + extClass + '" data-lucide-icon="' + extIcon + '">' +
'<i data-lucide="' + extIcon + '" style="width:14px;height:14px"></i>' +
extText +
'</span>' +
'</div>' +
'<div class="sync-panel__ai-row">' +
'<span class="sync-panel__ai-label">Auto-complétion</span>' +
'<span class="sync-panel__ai-state ' + acClass + '" data-lucide-icon="' + acIcon + '">' +
'<i data-lucide="' + acIcon + '" style="width:14px;height:14px"></i>' +
acText +
'</span>' +
'</div>';
panel.appendChild(section);
// Trigger icon refresh via global
+13 -6
View File
@@ -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 {