From b077c24bf1206e0fb9a2599496ead6d0740e6251 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Thu, 4 Jun 2026 08:31:32 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20corrections=20UI=20=E2=80=94=20indicateu?= =?UTF-8?q?r=20voute,=20couleurs,=20statut=20IA,=20spinner=20=C3=A9diteur?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Suppression du badge vault-context-pill doublon en mobile 2. Couleurs dynamiques indicateur header: - Bleu pour 'All' (.vault-ctx-all) - Mauve/violet pour les vaults (.vault-ctx-vault) - Jaune/ambre pour les répertoires (.vault-ctx-dir) 3. Statut IA: vérifie la présence du toolbar AI dans le DOM au lieu d'appeler /api/ai/status (qui ne fonctionnait pas) 4. Indicateur de traitement AI dans l'éditeur (spinner + badge 'AI...' dans le header de l'editor modal) au lieu du flash blanc sur le badge sync --- frontend/js/ai.js | 24 ++++++++++++ frontend/js/sidebar.js | 10 +++++ frontend/js/sync.js | 26 +++++++------ frontend/style.css | 87 +++++++++++++++++++++++++++++++++--------- 4 files changed, 117 insertions(+), 30 deletions(-) diff --git a/frontend/js/ai.js b/frontend/js/ai.js index 61a4d14..73ef02c 100644 --- a/frontend/js/ai.js +++ b/frontend/js/ai.js @@ -368,6 +368,10 @@ export async function createAIToolbar(container, getEditorView) { return; } showToast('⏳ AI: traitement en cours...', 'info'); + + // Show processing indicator in the editor header + _showProcessingIndicator(true); + // Dispatch AI processing start event document.dispatchEvent(new CustomEvent('ai-processing-start', { detail: { endpoint, mode } })); try { @@ -384,10 +388,30 @@ export async function createAIToolbar(container, getEditorView) { showToast('AI: ' + msg, 'error'); } } finally { + _showProcessingIndicator(false); // Dispatch AI processing end event document.dispatchEvent(new CustomEvent('ai-processing-end')); } } + + // ── In-editor processing indicator ── + function _showProcessingIndicator(show) { + let el = document.getElementById('ai-processing-indicator'); + if (show) { + if (!el) { + el = document.createElement('span'); + el.id = 'ai-processing-indicator'; + el.className = 'ai-processing-indicator'; + el.innerHTML = ' AI...'; + el.title = 'Traitement AI en cours'; + const header = document.querySelector('#editor-modal .editor-header'); + if (header) header.appendChild(el); + } + el.style.display = 'inline-flex'; + } else if (el) { + el.style.display = 'none'; + } + } // ── Keyboard shortcut Ctrl+J for inline completion ── document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.key === 'j') { diff --git a/frontend/js/sidebar.js b/frontend/js/sidebar.js index b265077..f8a3e8e 100644 --- a/frontend/js/sidebar.js +++ b/frontend/js/sidebar.js @@ -72,6 +72,16 @@ function syncVaultSelectors() { if (contextText) { contextText.textContent = state.selectedContextVault === "all" ? "All" : state.selectedContextVault; } + // Apply color class to the indicator + const indicator = document.getElementById("vault-context-indicator"); + if (indicator) { + indicator.classList.remove("vault-ctx-all", "vault-ctx-vault", "vault-ctx-dir"); + if (state.selectedContextVault === "all") { + indicator.classList.add("vault-ctx-all"); + } else { + indicator.classList.add("vault-ctx-vault"); + } + } } /** diff --git a/frontend/js/sync.js b/frontend/js/sync.js index 61c0dcd..6f8d742 100644 --- a/frontend/js/sync.js +++ b/frontend/js/sync.js @@ -339,15 +339,15 @@ function _renderSyncPanel(panel) { let _aiProcessing = false; let _aiConfigured = null; // null = unknown, true/false -/** Check if AI is configured by calling the status endpoint. */ -async function _checkAIStatus() { - try { - const resp = await api('/api/ai/status'); - console.log('[AI Status] API response:', resp); - _aiConfigured = !!(resp && resp.configured); - } catch (e) { - console.error('[AI Status] API call failed:', e); - _aiConfigured = false; +/** Check if AI is configured by looking for the AI toolbar in the DOM. + * The toolbar is only rendered when the AI API responds successfully. */ +function _checkAIStatus() { + const container = document.getElementById('ai-toolbar-container'); + if (container && container.children.length > 0) { + _aiConfigured = true; + } else if (_aiConfigured === null) { + // Keep unknown state until we have evidence + _aiConfigured = null; } } @@ -404,7 +404,10 @@ function _updateAIStatusInPanel() { } } -/** Flash the sync badge white during AI processing. */ +/** Flash the sync badge white during AI processing. + * NOTE: now handled via CSS class on #sync-badge. + * The visual flash in the editor itself is handled by ai.js + * which adds a processing indicator inline. */ function _setBadgeAIProcessing(active) { const badge = document.getElementById('sync-badge'); if (!badge) return; @@ -435,7 +438,8 @@ function _initAIStatus() { if (badge) { const origClick = badge.onclick; badge.addEventListener('click', () => { - _checkAIStatus().then(() => _updateAIStatusInPanel()); + _checkAIStatus(); + _updateAIStatusInPanel(); }, { once: false }); } } diff --git a/frontend/style.css b/frontend/style.css index e5d52a0..55aeb2b 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -171,24 +171,48 @@ a:hover { align-items: center; gap: 6px; padding: 6px 12px; - background: color-mix(in srgb, var(--accent) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent) 25%, var(--border)); + border: 1px solid var(--border); border-radius: 8px; font-family: "JetBrains Mono", monospace; font-size: 0.75rem; font-weight: 600; - color: var(--accent); cursor: default; transition: all 200ms ease; + /* Default: neutral (overridden by classes below) */ + background: var(--bg-tertiary); + color: var(--text-muted); } .vault-context-indicator:hover { - background: color-mix(in srgb, var(--accent) 18%, transparent); - border-color: var(--accent); + filter: brightness(1.15); } .vault-context-indicator i { - color: var(--accent); + opacity: 0.8; } +/* All vaults — blue */ +.vault-ctx-all { + background: color-mix(in srgb, #3b82f6 14%, transparent) !important; + border-color: color-mix(in srgb, #3b82f6 30%, var(--border)) !important; + color: #60a5fa !important; +} +.vault-ctx-all i { color: #60a5fa !important; } + +/* Specific vault — mauve/violet */ +.vault-ctx-vault { + background: color-mix(in srgb, #a855f7 14%, transparent) !important; + border-color: color-mix(in srgb, #a855f7 30%, var(--border)) !important; + color: #c084fc !important; +} +.vault-ctx-vault i { color: #c084fc !important; } + +/* Directory — amber/yellow */ +.vault-ctx-dir { + background: color-mix(in srgb, #f59e0b 14%, transparent) !important; + border-color: color-mix(in srgb, #f59e0b 30%, var(--border)) !important; + color: #fbbf24 !important; +} +.vault-ctx-dir i { color: #fbbf24 !important; } + .hamburger-btn { display: none; background: none; @@ -2150,6 +2174,41 @@ select { top: 0; z-index: 10; } + +/* AI processing indicator — shown inside editor header during AI calls */ +.ai-processing-indicator { + display: none; + align-items: center; + gap: 6px; + padding: 3px 10px; + border-radius: 12px; + font-size: 0.72rem; + font-weight: 500; + color: #ffffff; + background: rgba(59, 130, 246, 0.3); + border: 1px solid rgba(96, 165, 250, 0.4); + animation: ai-indicator-pulse 1s ease-in-out infinite; + margin-left: 12px; +} + +.ai-spinner { + width: 10px; + height: 10px; + border: 2px solid rgba(255,255,255,0.3); + border-top-color: #ffffff; + border-radius: 50%; + animation: ai-spinner-spin 0.6s linear infinite; +} + +@keyframes ai-spinner-spin { + to { transform: rotate(360deg); } +} + +@keyframes ai-indicator-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.6; } +} + .editor-title { font-family: "JetBrains Mono", monospace; font-size: 0.9rem; @@ -6825,22 +6884,12 @@ body.popup-mode .content-area { display: none !important; } - /* Vault context indicator */ + /* Vault context indicator — hidden (now in header) */ .vault-context-bar { - display: flex; - align-items: center; - padding: 6px 14px; - background: var(--bg-primary); - border-bottom: 1px solid var(--border); + display: none !important; } .vault-context-pill { - font-size: 0.72rem; - font-weight: 600; - color: var(--accent); - background: color-mix(in srgb, var(--accent) 10%, transparent); - padding: 3px 10px; - border-radius: 12px; - white-space: nowrap; + display: none; } .mt-btn {