From 7503f63b0df47f17d59b21cb30910a7153b30e9b Mon Sep 17 00:00:00 2001 From: Claude Dev Date: Sun, 15 Mar 2026 12:49:06 +0100 Subject: [PATCH] feat: Neuer-Chat-Button im Chat-Header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reload-Button (kreisförmiger Pfeil) neben dem Schließen-Button. Wird erst sichtbar nach der ersten Nachricht. Setzt Konversation zurück, leert den Verlauf und zeigt die Begrüßung erneut. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/static/css/style.css | 17 +++- src/static/dashboard.html | 9 +- src/static/js/chat.js | 197 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 src/static/js/chat.js diff --git a/src/static/css/style.css b/src/static/css/style.css index f717069..2fbe9ec 100644 --- a/src/static/css/style.css +++ b/src/static/css/style.css @@ -4682,20 +4682,31 @@ a.map-popup-article:hover { font-weight: 600; color: var(--text-primary); } -.chat-header-close { +.chat-header-actions { + display: flex; + align-items: center; + gap: 2px; + margin-left: auto; +} +.chat-header-btn { background: none; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px; line-height: 1; - font-size: 18px; border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; } -.chat-header-close:hover { +.chat-header-btn:hover { color: var(--text-primary); background: var(--bg-tertiary); } +.chat-header-close { + font-size: 18px; +} .chat-messages { flex: 1; diff --git a/src/static/dashboard.html b/src/static/dashboard.html index 61b90ee..849d203 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -591,7 +591,12 @@
AegisSight Assistent - +
+ + +
@@ -613,7 +618,7 @@ - + diff --git a/src/static/js/chat.js b/src/static/js/chat.js new file mode 100644 index 0000000..ccc822d --- /dev/null +++ b/src/static/js/chat.js @@ -0,0 +1,197 @@ +/** + * AegisSight Chat-Assistent Widget. + */ +const Chat = { + _conversationId: null, + _isOpen: false, + _isLoading: false, + _hasGreeted: 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()); + + form.addEventListener('submit', (e) => { + e.preventDefault(); + this.send(); + }); + + // Enter sendet, Shift+Enter fuer 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. Ich kann dir sowohl Fragen zur Bedienung des Monitors beantworten als auch Auskunft zu deinen angelegten Lagen und Recherchen geben.\n\nBeispiele:\n\n"Welche Lagen gibt es gerade?"\n"Fass mir die aktuelle Lage zusammen"\n"Wie viele Artikel hat die Lage zum Irankonflikt?"\n"Wie erstelle ich eine neue Recherche?"\n"Welche Quellen werden genutzt?"\n\nWenn du eine Lage ge\u00f6ffnet hast, beziehe ich mich automatisch darauf.'); + } + + // 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'); + btn.classList.remove('active'); + this._isOpen = false; + }, + + reset() { + this._conversationId = null; + this._hasGreeted = false; + this._isLoading = false; + const container = document.getElementById('chat-messages'); + if (container) container.innerHTML = ''; + this._updateResetBtn(); + this.open(); + }, + + _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; + + 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); + } 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(); + }, +};