/** * AegisSight Chat-Assistent Widget. */ const Chat = { _conversationId: null, _isOpen: false, _isLoading: false, _hasGreeted: false, _tutorialHintDismissed: false, _isFullscreen: false, init() { const btn = document.getElementById('chat-toggle-btn'); const closeBtn = document.getElementById('chat-close-btn'); const form = document.getElementById('chat-form'); const input = document.getElementById('chat-input'); if (!btn || !form) return; btn.addEventListener('click', () => this.toggle()); closeBtn.addEventListener('click', () => this.close()); const resetBtn = document.getElementById('chat-reset-btn'); if (resetBtn) resetBtn.addEventListener('click', () => this.reset()); const fsBtn = document.getElementById('chat-fullscreen-btn'); if (fsBtn) fsBtn.addEventListener('click', () => this.toggleFullscreen()); form.addEventListener('submit', (e) => { e.preventDefault(); this.send(); }); // Enter sendet, Shift+Enter für Zeilenumbruch input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.send(); } }); // Auto-resize textarea input.addEventListener('input', () => { input.style.height = 'auto'; input.style.height = Math.min(input.scrollHeight, 120) + 'px'; }); }, toggle() { if (this._isOpen) { this.close(); } else { this.open(); } }, open() { const win = document.getElementById('chat-window'); const btn = document.getElementById('chat-toggle-btn'); if (!win) return; win.classList.add('open'); btn.classList.add('active'); this._isOpen = true; if (!this._hasGreeted) { this._hasGreeted = true; this.addMessage('assistant', 'Hallo! Ich bin der AegisSight Assistent. Stell mir gerne jede Frage rund um die Bedienung des Monitors, ich helfe dir weiter.'); } // Tutorial-Hinweis bei jedem Oeffnen aktualisieren (wenn nicht dismissed) if (typeof Tutorial !== 'undefined' && !this._tutorialHintDismissed) { var oldHint = document.getElementById('chat-tutorial-hint'); if (oldHint) oldHint.remove(); this._showTutorialHint(); } // Focus auf Input setTimeout(() => { const input = document.getElementById('chat-input'); if (input) input.focus(); }, 200); }, close() { const win = document.getElementById('chat-window'); const btn = document.getElementById('chat-toggle-btn'); if (!win) return; win.classList.remove('open'); win.classList.remove('fullscreen'); btn.classList.remove('active'); this._isOpen = false; this._isFullscreen = false; const fsBtn = document.getElementById('chat-fullscreen-btn'); if (fsBtn) { fsBtn.title = 'Vollbild'; fsBtn.innerHTML = ''; } }, reset() { this._conversationId = null; this._hasGreeted = false; this._isLoading = false; const container = document.getElementById('chat-messages'); if (container) container.innerHTML = ''; this._updateResetBtn(); this.open(); }, toggleFullscreen() { const win = document.getElementById('chat-window'); const btn = document.getElementById('chat-fullscreen-btn'); if (!win) return; this._isFullscreen = !this._isFullscreen; win.classList.toggle('fullscreen', this._isFullscreen); if (btn) { btn.title = this._isFullscreen ? 'Vollbild beenden' : 'Vollbild'; btn.innerHTML = this._isFullscreen ? '' : ''; } }, _updateResetBtn() { const btn = document.getElementById('chat-reset-btn'); if (btn) btn.style.display = this._conversationId ? '' : 'none'; }, async send() { const input = document.getElementById('chat-input'); const text = (input.value || '').trim(); if (!text || this._isLoading) return; input.value = ''; input.style.height = 'auto'; this.addMessage('user', text); this._showTyping(); this._isLoading = true; // Tutorial-Keywords abfangen var lowerText = text.toLowerCase(); if (lowerText === 'rundgang' || lowerText === 'tutorial' || lowerText === 'tour' || lowerText === 'f\u00fchrung') { this._hideTyping(); this._isLoading = false; this.close(); if (typeof Tutorial !== 'undefined') Tutorial.start(); return; } try { const body = { message: text, conversation_id: this._conversationId, }; // Aktuelle Lage mitschicken falls geoeffnet const incidentId = this._getIncidentContext(); if (incidentId) { body.incident_id = incidentId; } const data = await this._request(body); this._conversationId = data.conversation_id; this._updateResetBtn(); this._hideTyping(); this.addMessage('assistant', data.reply); this._highlightUI(data.reply); } catch (err) { this._hideTyping(); const msg = err.detail || err.message || 'Etwas ist schiefgelaufen. Bitte versuche es erneut.'; this.addMessage('assistant', msg); } finally { this._isLoading = false; } }, addMessage(role, text) { const container = document.getElementById('chat-messages'); if (!container) return; const bubble = document.createElement('div'); bubble.className = 'chat-message ' + role; // Einfache Formatierung: Zeilenumbrueche und Fettschrift const formatted = text .replace(/&/g, '&') .replace(//g, '>') .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/\n/g, '
'); bubble.innerHTML = '
' + formatted + '
'; container.appendChild(bubble); // User-Nachrichten: nach unten scrollen. Antworten: zum Anfang der Antwort scrollen. if (role === 'user') { container.scrollTop = container.scrollHeight; } else { bubble.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }, _showTyping() { const container = document.getElementById('chat-messages'); if (!container) return; const el = document.createElement('div'); el.className = 'chat-message assistant chat-typing-msg'; el.innerHTML = '
'; container.appendChild(el); container.scrollTop = container.scrollHeight; }, _hideTyping() { const el = document.querySelector('.chat-typing-msg'); if (el) el.remove(); }, _getIncidentContext() { if (typeof App !== 'undefined' && App.currentIncidentId) { return App.currentIncidentId; } return null; }, async _request(body) { const token = localStorage.getItem('osint_token'); const resp = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': token ? 'Bearer ' + token : '', }, body: JSON.stringify(body), }); if (!resp.ok) { const data = await resp.json().catch(() => ({})); throw data; } return await resp.json(); }, // ----------------------------------------------------------------------- // UI-Highlight: Bedienelemente im Dashboard hervorheben wenn im Chat erwaehnt // ----------------------------------------------------------------------- _UI_HIGHLIGHTS: [ { keywords: ['neue lage', 'lage erstellen', 'lage anlegen', 'recherche erstellen', 'neuen fall'], selector: '#new-incident-btn' }, { keywords: ['theme wechseln', 'theme-umschalter', 'farbschema', 'helles design', 'dunkles design', 'hell- und dunkel', 'hellem und dunklem', 'dark mode', 'light mode'], selector: '#theme-toggle' }, { keywords: ['barrierefreiheit', 'accessibility', 'hoher kontrast', 'focus-anzeige', 'groessere schrift', 'animationen aus'], selector: '#a11y-btn' }, { keywords: ['abmelden', 'logout', 'ausloggen', 'abmeldung'], selector: '#logout-btn' }, { keywords: ['benachrichtigung', 'glocken-symbol', 'abonnieren', 'abonniert'], selector: '#notification-btn' }, { keywords: ['aktualisieren', 'refresh starten'], selector: '#refresh-btn' }, { keywords: ['exportieren', 'export-button', 'lagebericht exportieren'], selector: 'button[onclick*="toggleExportDropdown"]' }, { keywords: ['faktencheck', 'factcheck'], selector: '[gs-id="factcheck"]' }, { keywords: ['kartenansicht', 'karte angezeigt', 'interaktive karte', 'geoparsing'], selector: '[gs-id="map"]' }, { keywords: ['quellen verwalten', 'quellenverwaltung', 'quelleneinstellung', 'quellenausschluss', 'quellen-einstellung'], selector: 'button[onclick*="openSourceManagement"]' }, { keywords: ['sichtbarkeit', 'privat oder oeffentlich', 'lage privat'], selector: '#incident-settings-btn' }, { keywords: ['eigene lagen', 'nur eigene'], selector: '.sidebar-filter-btn[data-filter="mine"]' }, { keywords: ['alle lagen anzeigen'], selector: '.sidebar-filter-btn[data-filter="all"]' }, { keywords: ['feedback senden', 'feedback geben', 'rueckmeldung'], selector: 'button[onclick*="openFeedback"]' }, { keywords: ['lage loeschen', 'lage entfernen', 'fall loeschen'], selector: '#delete-incident-btn' }, ], _highlightUI(text) { if (!text) return; var lower = text.toLowerCase(); var highlighted = new Set(); for (var i = 0; i < this._UI_HIGHLIGHTS.length; i++) { var entry = this._UI_HIGHLIGHTS[i]; for (var k = 0; k < entry.keywords.length; k++) { var kw = entry.keywords[k]; if (lower.indexOf(kw) !== -1) { var selectors = entry.selector.split(','); for (var s = 0; s < selectors.length; s++) { var sel = selectors[s].trim(); if (highlighted.has(sel)) continue; var el = document.querySelector(sel); if (el) { highlighted.add(sel); el.scrollIntoView({ behavior: 'smooth', block: 'center' }); (function(element) { setTimeout(function() { element.classList.add('chat-ui-highlight'); }, 400); setTimeout(function() { element.classList.remove('chat-ui-highlight'); }, 4400); })(el); } } break; } } } }, async _showTutorialHint() { var container = document.getElementById('chat-messages'); if (!container) return; // API-State laden (Fallback: Standard-Hint) var state = null; try { state = await API.getTutorialState(); } catch(e) {} var hint = document.createElement('div'); hint.className = 'chat-tutorial-hint'; hint.id = 'chat-tutorial-hint'; var textDiv = document.createElement('div'); textDiv.className = 'chat-tutorial-hint-text'; textDiv.style.cursor = 'pointer'; if (state && !state.completed && state.current_step !== null && state.current_step > 0) { // Mittendrin abgebrochen var totalSteps = (typeof Tutorial !== 'undefined') ? Tutorial._steps.length : 32; textDiv.innerHTML = 'Tipp: Sie haben den Rundgang bei Schritt ' + (state.current_step + 1) + '/' + totalSteps + ' unterbrochen. Klicken Sie hier, um fortzusetzen.'; textDiv.addEventListener('click', function() { Chat.close(); Chat._tutorialHintDismissed = true; if (typeof Tutorial !== 'undefined') Tutorial.start(); }); } else if (state && state.completed) { // Bereits abgeschlossen textDiv.innerHTML = 'Tipp: Sie haben den Rundgang bereits abgeschlossen. Erneut starten?'; textDiv.addEventListener('click', async function() { Chat.close(); Chat._tutorialHintDismissed = true; try { await API.resetTutorialState(); } catch(e) {} if (typeof Tutorial !== 'undefined') Tutorial.start(true); }); } else { // Nie gestartet textDiv.innerHTML = 'Tipp: Kennen Sie schon den interaktiven Rundgang? Er zeigt Ihnen Schritt für Schritt alle Funktionen des Monitors. Klicken Sie hier, um ihn zu starten.'; textDiv.addEventListener('click', function() { Chat.close(); Chat._tutorialHintDismissed = true; if (typeof Tutorial !== 'undefined') Tutorial.start(); }); } var closeBtn = document.createElement('button'); closeBtn.className = 'chat-tutorial-hint-close'; closeBtn.title = 'Schließen'; closeBtn.innerHTML = '×'; closeBtn.addEventListener('click', function(e) { e.stopPropagation(); hint.remove(); Chat._tutorialHintDismissed = true; }); hint.appendChild(textDiv); hint.appendChild(closeBtn); container.appendChild(hint); }, };