diff --git a/src/static/js/app_network.js b/src/static/js/app_network.js index f97c3a0..607ecec 100644 --- a/src/static/js/app_network.js +++ b/src/static/js/app_network.js @@ -1,447 +1,454 @@ -/** - * Netzwerkanalyse-Erweiterungen für App-Objekt. - * Wird nach app.js geladen und erweitert App um Netzwerk-Funktionalität. - */ - -// State-Erweiterung -App.networkAnalyses = []; -App.currentNetworkId = null; -App._networkGenerating = new Set(); - -/** - * Netzwerkanalysen laden und Sidebar rendern. - */ -App.loadNetworkAnalyses = async function() { - try { - this.networkAnalyses = await API.listNetworkAnalyses(); - } catch (e) { - console.warn('Netzwerkanalysen laden fehlgeschlagen:', e); - this.networkAnalyses = []; - } - this.renderNetworkSidebar(); -}; - -/** - * Netzwerkanalysen-Sektion in der Sidebar rendern. - */ -App.renderNetworkSidebar = function() { - var container = document.getElementById('network-analyses-list'); - if (!container) return; - - var countEl = document.getElementById('count-network-analyses'); - if (countEl) countEl.textContent = '(' + this.networkAnalyses.length + ')'; - - if (this.networkAnalyses.length === 0) { - container.innerHTML = '
Keine Netzwerkanalysen
'; - return; - } - - var self = this; - container.innerHTML = this.networkAnalyses.map(function(na) { - var isActive = na.id === self.currentNetworkId; - var statusClass = na.status === 'generating' ? 'generating' : (na.status === 'error' ? 'error' : 'ready'); - var countText = na.status === 'ready' ? (na.entity_count + ' / ' + na.relation_count) : na.status === 'generating' ? '...' : ''; - return ''; - }).join(''); -}; - -/** - * Netzwerkanalyse auswählen und anzeigen. - */ -App.selectNetworkAnalysis = async function(id) { - this.currentNetworkId = id; - this.currentIncidentId = null; - localStorage.removeItem('selectedIncidentId'); - localStorage.setItem('selectedNetworkId', id); - - // Views umschalten - document.getElementById('empty-state').style.display = 'none'; - document.getElementById('incident-view').style.display = 'none'; - document.getElementById('network-view').style.display = 'flex'; - - // Sidebar aktualisieren - this.renderSidebar(); - this.renderNetworkSidebar(); - - // Analyse laden - try { - var analysis = await API.getNetworkAnalysis(id); - this._renderNetworkHeader(analysis); - - if (analysis.status === 'ready') { - this._hideNetworkProgress(); - var graphData = await API.getNetworkGraph(id); - NetworkGraph.init('network-graph-area', graphData); - this._setupNetworkFilters(graphData); - - // Update-Check - try { - var updateCheck = await API.checkNetworkUpdate(id); - var badge = document.getElementById('network-update-badge'); - if (badge) badge.style.display = updateCheck.has_update ? 'inline-flex' : 'none'; - } catch (e) { /* ignorieren */ } - } else if (analysis.status === 'generating') { - this._showNetworkProgress('entity_extraction', 0); - } else if (analysis.status === 'error') { - this._hideNetworkProgress(); - var graphArea = document.getElementById('network-graph-area'); - if (graphArea) graphArea.innerHTML = '
Fehler bei der Generierung. Versuche es erneut.
'; - } - } catch (err) { - UI.showToast('Fehler beim Laden der Netzwerkanalyse: ' + err.message, 'error'); - } -}; - -/** - * Netzwerkanalyse-Header rendern. - */ -App._renderNetworkHeader = function(analysis) { - var el; - el = document.getElementById('network-title'); - if (el) el.textContent = analysis.name; - - el = document.getElementById('network-entity-count'); - if (el) el.textContent = analysis.entity_count + ' Entitäten'; - - el = document.getElementById('network-relation-count'); - if (el) el.textContent = analysis.relation_count + ' Beziehungen'; - - el = document.getElementById('network-incident-list-text'); - if (el) el.textContent = (analysis.incident_titles || []).join(', ') || '-'; - - el = document.getElementById('network-last-generated'); - if (el) { - if (analysis.last_generated_at) { - var d = parseUTC(analysis.last_generated_at) || new Date(analysis.last_generated_at); - el.textContent = 'Generiert: ' + d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: TIMEZONE }) + ' ' + - d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE }); - } else { - el.textContent = ''; - } - } -}; - -/** - * Filter-Controls in der Netzwerk-Sidebar aufsetzen. - */ -App._setupNetworkFilters = function(graphData) { - // Typ-Filter-Buttons aktivieren - var types = new Set(); - (graphData.entities || []).forEach(function(e) { types.add(e.entity_type); }); - var filterContainer = document.getElementById('network-type-filter-container'); - if (filterContainer) { - var allTypes = ['person', 'organisation', 'location', 'event', 'military']; - var typeLabels = { person: 'Person', organisation: 'Organisation', location: 'Ort', event: 'Ereignis', military: 'Militär' }; - filterContainer.innerHTML = allTypes.map(function(t) { - var hasEntities = types.has(t); - return ''; - }).join(''); - } - - // Gewicht-Slider - var slider = document.getElementById('network-weight-slider'); - if (slider) { - slider.value = 1; - slider.oninput = function() { - var label = document.getElementById('network-weight-value'); - if (label) label.textContent = this.value; - NetworkGraph.filterByWeight(parseInt(this.value)); - }; - } - - // Suche - var searchInput = document.getElementById('network-search'); - if (searchInput) { - searchInput.value = ''; - var timer = null; - searchInput.oninput = function() { - clearTimeout(timer); - var val = this.value; - timer = setTimeout(function() { - NetworkGraph.search(val); - }, 250); - }; - } -}; - -/** - * Typ-Filter toggle. - */ -App.toggleNetworkTypeFilter = function(btn) { - btn.classList.toggle('active'); - var activeTypes = []; - document.querySelectorAll('.network-type-filter.active').forEach(function(b) { - activeTypes.push(b.dataset.type); - }); - NetworkGraph.filterByType(new Set(activeTypes)); -}; - -/** - * Progress-Bar anzeigen. - */ -App._showNetworkProgress = function(phase, progress) { - var bar = document.getElementById('network-progress-bar'); - if (bar) bar.style.display = 'block'; - - var steps = ['entity_extraction', 'relationship_extraction', 'correction']; - var stepEls = document.querySelectorAll('.network-progress-step'); - var connectorEls = document.querySelectorAll('.network-progress-connector'); - var phaseIndex = steps.indexOf(phase); - - stepEls.forEach(function(el, i) { - el.classList.remove('active', 'done'); - if (i < phaseIndex) el.classList.add('done'); - else if (i === phaseIndex) el.classList.add('active'); - }); - - connectorEls.forEach(function(el, i) { - el.classList.remove('done'); - if (i < phaseIndex) el.classList.add('done'); - }); - - var fill = document.getElementById('network-progress-fill'); - if (fill) { - var pct = ((phaseIndex / steps.length) * 100) + (progress || 0) * (100 / steps.length) / 100; - fill.style.width = Math.min(100, pct) + '%'; - } - - var label = document.getElementById('network-progress-label'); - if (label) { - var labels = { entity_extraction: 'Entitäten werden extrahiert...', relationship_extraction: 'Beziehungen werden analysiert...', correction: 'Korrekturen werden angewendet...' }; - label.textContent = labels[phase] || 'Wird verarbeitet...'; - } -}; - -App._hideNetworkProgress = function() { - var bar = document.getElementById('network-progress-bar'); - if (bar) bar.style.display = 'none'; -}; - -/** - * Modal: Neue Netzwerkanalyse öffnen. - */ -App.openNetworkModal = async function() { - var list = document.getElementById('network-incident-options'); - if (list) list.innerHTML = '
Lade Lagen...
'; - - openModal('modal-network-new'); - - // Lagen laden - try { - var incidents = await API.listIncidents(); - if (list) { - list.innerHTML = incidents.map(function(inc) { - var typeLabel = inc.type === 'research' ? 'Analyse' : 'Live'; - return ''; - }).join(''); - } - } catch (e) { - if (list) list.innerHTML = '
Fehler beim Laden der Lagen
'; - } - - // Name-Feld leeren - var nameField = document.getElementById('network-name'); - if (nameField) nameField.value = ''; - - // Suchfeld leeren - var searchField = document.getElementById('network-incident-search'); - if (searchField) { - searchField.value = ''; - searchField.oninput = function() { - var term = this.value.toLowerCase(); - document.querySelectorAll('.network-incident-option').forEach(function(opt) { - var text = opt.textContent.toLowerCase(); - opt.style.display = text.includes(term) ? '' : 'none'; - }); - }; - } -}; - -/** - * Netzwerkanalyse erstellen. - */ -App.submitNetworkAnalysis = async function(e) { - if (e) e.preventDefault(); - - var name = (document.getElementById('network-name').value || '').trim(); - if (!name) { - UI.showToast('Bitte einen Namen eingeben.', 'warning'); - return; - } - - var incidentIds = []; - document.querySelectorAll('.network-incident-cb:checked').forEach(function(cb) { - incidentIds.push(parseInt(cb.value)); - }); - - if (incidentIds.length === 0) { - UI.showToast('Bitte mindestens eine Lage auswählen.', 'warning'); - return; - } - - var btn = document.getElementById('network-submit-btn'); - if (btn) btn.disabled = true; - - try { - var result = await API.createNetworkAnalysis({ name: name, incident_ids: incidentIds }); - closeModal('modal-network-new'); - await this.loadNetworkAnalyses(); - await this.selectNetworkAnalysis(result.id); - UI.showToast('Netzwerkanalyse gestartet.', 'success'); - } catch (err) { - UI.showToast('Fehler: ' + err.message, 'error'); - } finally { - if (btn) btn.disabled = false; - } -}; - -/** - * Netzwerkanalyse neu generieren. - */ -App.regenerateNetwork = async function() { - if (!this.currentNetworkId) return; - if (!await confirmDialog('Netzwerkanalyse neu generieren? Bestehende Daten werden überschrieben.')) return; - - try { - await API.regenerateNetwork(this.currentNetworkId); - this._showNetworkProgress('entity_extraction', 0); - await this.loadNetworkAnalyses(); - UI.showToast('Neugenerierung gestartet.', 'success'); - } catch (err) { - UI.showToast('Fehler: ' + err.message, 'error'); - } -}; - -/** - * Netzwerkanalyse löschen. - */ -App.deleteNetworkAnalysis = async function() { - if (!this.currentNetworkId) return; - if (!await confirmDialog('Netzwerkanalyse wirklich löschen? Alle Daten gehen verloren.')) return; - - try { - await API.deleteNetworkAnalysis(this.currentNetworkId); - this.currentNetworkId = null; - localStorage.removeItem('selectedNetworkId'); - NetworkGraph.destroy(); - document.getElementById('network-view').style.display = 'none'; - document.getElementById('empty-state').style.display = 'flex'; - await this.loadNetworkAnalyses(); - UI.showToast('Netzwerkanalyse gelöscht.', 'success'); - } catch (err) { - UI.showToast('Fehler: ' + err.message, 'error'); - } -}; - -/** - * Netzwerkanalyse exportieren. - */ -App.exportNetwork = async function(format) { - if (!this.currentNetworkId) return; - - if (format === 'png') { - NetworkGraph.exportPNG(); - return; - } - - try { - var resp = await API.exportNetworkAnalysis(this.currentNetworkId, format); - if (!resp.ok) throw new Error('Export fehlgeschlagen'); - var blob = await resp.blob(); - var url = URL.createObjectURL(blob); - var a = document.createElement('a'); - a.href = url; - a.download = 'netzwerk-' + this.currentNetworkId + '.' + format; - a.click(); - URL.revokeObjectURL(url); - } catch (err) { - UI.showToast('Export fehlgeschlagen: ' + err.message, 'error'); - } -}; - -/** - * WebSocket-Handler für Netzwerk-Events. - */ -App._handleNetworkStatus = function(msg) { - if (msg.analysis_id === this.currentNetworkId) { - this._showNetworkProgress(msg.phase, msg.progress || 0); - } -}; - -App._handleNetworkComplete = async function(msg) { - this._networkGenerating.delete(msg.analysis_id); - - if (msg.analysis_id === this.currentNetworkId) { - this._hideNetworkProgress(); - // Graph neu laden - try { - var graphData = await API.getNetworkGraph(msg.analysis_id); - NetworkGraph.init('network-graph-area', graphData); - this._setupNetworkFilters(graphData); - - var analysis = await API.getNetworkAnalysis(msg.analysis_id); - this._renderNetworkHeader(analysis); - } catch (e) { - console.error('Graph nach Generierung laden fehlgeschlagen:', e); - } - UI.showToast('Netzwerkanalyse fertig: ' + (msg.entity_count || 0) + ' Entitäten, ' + (msg.relation_count || 0) + ' Beziehungen', 'success'); - } - - await this.loadNetworkAnalyses(); -}; - -App._handleNetworkError = function(msg) { - this._networkGenerating.delete(msg.analysis_id); - - if (msg.analysis_id === this.currentNetworkId) { - this._hideNetworkProgress(); - var graphArea = document.getElementById('network-graph-area'); - if (graphArea) graphArea.innerHTML = '
Fehler: ' + _escHtml(msg.error || 'Unbekannter Fehler') + '
'; - } - - UI.showToast('Netzwerkanalyse fehlgeschlagen: ' + (msg.error || 'Unbekannter Fehler'), 'error'); - this.loadNetworkAnalyses(); -}; - -/** - * Cluster isolieren (nur verbundene Knoten zeigen). - */ -App.isolateNetworkCluster = function() { - if (NetworkGraph._selectedNode) { - NetworkGraph.isolateCluster(NetworkGraph._selectedNode.id); - } -}; - -/** - * Graph-Ansicht zurücksetzen. - */ -App.resetNetworkView = function() { - NetworkGraph.resetView(); - // Typ-Filter zurücksetzen - document.querySelectorAll('.network-type-filter').forEach(function(btn) { - if (!btn.disabled) btn.classList.add('active'); - }); - var slider = document.getElementById('network-weight-slider'); - if (slider) { slider.value = 1; var lbl = document.getElementById('network-weight-value'); if (lbl) lbl.textContent = '1'; } - var search = document.getElementById('network-search'); - if (search) search.value = ''; -}; - -// HTML-Escape Hilfsfunktion (falls nicht global verfügbar) -function _escHtml(text) { - if (typeof UI !== 'undefined' && UI.escape) return UI.escape(text); - var d = document.createElement('div'); - d.textContent = text || ''; - return d.innerHTML; -} +/** + * Netzwerkanalyse-Erweiterungen für App-Objekt. + * Wird nach app.js geladen und erweitert App um Netzwerk-Funktionalität. + */ + +// State-Erweiterung +App.networkAnalyses = []; +App.currentNetworkId = null; +App._networkGenerating = new Set(); + +/** + * Netzwerkanalysen laden und Sidebar rendern. + */ +App.loadNetworkAnalyses = async function() { + try { + this.networkAnalyses = await API.listNetworkAnalyses(); + } catch (e) { + console.warn('Netzwerkanalysen laden fehlgeschlagen:', e); + this.networkAnalyses = []; + } + this.renderNetworkSidebar(); +}; + +/** + * Netzwerkanalysen-Sektion in der Sidebar rendern. + */ +App.renderNetworkSidebar = function() { + var container = document.getElementById('network-analyses-list'); + if (!container) return; + + var countEl = document.getElementById('count-network-analyses'); + if (countEl) countEl.textContent = '(' + this.networkAnalyses.length + ')'; + + if (this.networkAnalyses.length === 0) { + container.innerHTML = '
Keine Netzwerkanalysen
'; + return; + } + + var self = this; + container.innerHTML = this.networkAnalyses.map(function(na) { + var isActive = na.id === self.currentNetworkId; + var statusClass = na.status === 'generating' ? 'generating' : (na.status === 'error' ? 'error' : 'ready'); + var countText = na.status === 'ready' ? (na.entity_count + ' / ' + na.relation_count) : na.status === 'generating' ? '...' : ''; + return ''; + }).join(''); +}; + +/** + * Netzwerkanalyse auswählen und anzeigen. + */ +App.selectNetworkAnalysis = async function(id) { + this.currentNetworkId = id; + this.currentIncidentId = null; + localStorage.removeItem('selectedIncidentId'); + localStorage.setItem('selectedNetworkId', id); + + // Views umschalten + document.getElementById('empty-state').style.display = 'none'; + document.getElementById('incident-view').style.display = 'none'; + document.getElementById('network-view').style.display = 'flex'; + + // Sidebar aktualisieren + this.renderSidebar(); + this.renderNetworkSidebar(); + + // Analyse laden + try { + var analysis = await API.getNetworkAnalysis(id); + this._renderNetworkHeader(analysis); + + if (analysis.status === 'ready') { + this._hideNetworkProgress(); + var graphData = await API.getNetworkGraph(id); + NetworkGraph.init('network-graph-area', graphData); + this._setupNetworkFilters(graphData); + + // Update-Check + try { + var updateCheck = await API.checkNetworkUpdate(id); + var badge = document.getElementById('network-update-badge'); + if (badge) badge.style.display = updateCheck.has_update ? 'inline-flex' : 'none'; + } catch (e) { /* ignorieren */ } + } else if (analysis.status === 'generating') { + this._showNetworkProgress('entity_extraction', 0); + } else if (analysis.status === 'error') { + this._hideNetworkProgress(); + var graphArea = document.getElementById('network-graph-area'); + if (graphArea) graphArea.innerHTML = '
Fehler bei der Generierung. Versuche es erneut.
'; + } + } catch (err) { + UI.showToast('Fehler beim Laden der Netzwerkanalyse: ' + err.message, 'error'); + } +}; + +/** + * Netzwerkanalyse-Header rendern. + */ +App._renderNetworkHeader = function(analysis) { + var el; + el = document.getElementById('network-title'); + if (el) el.textContent = analysis.name; + + el = document.getElementById('network-entity-count'); + if (el) el.textContent = analysis.entity_count + ' Entitäten'; + + el = document.getElementById('network-relation-count'); + if (el) el.textContent = analysis.relation_count + ' Beziehungen'; + + el = document.getElementById('network-incident-list-text'); + if (el) el.textContent = (analysis.incident_titles || []).join(', ') || '-'; + + el = document.getElementById('network-last-generated'); + if (el) { + if (analysis.last_generated_at) { + var d = parseUTC(analysis.last_generated_at) || new Date(analysis.last_generated_at); + el.textContent = 'Generiert: ' + d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: TIMEZONE }) + ' ' + + d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE }); + } else { + el.textContent = ''; + } + } +}; + +/** + * Filter-Controls in der Netzwerk-Sidebar aufsetzen. + */ +App._setupNetworkFilters = function(graphData) { + // Typ-Filter-Buttons aktivieren + var types = new Set(); + (graphData.entities || []).forEach(function(e) { types.add(e.entity_type); }); + var filterContainer = document.getElementById('network-type-filter-container'); + if (filterContainer) { + var allTypes = ['person', 'organisation', 'location', 'event', 'military']; + var typeLabels = { person: 'Person', organisation: 'Organisation', location: 'Ort', event: 'Ereignis', military: 'Militär' }; + filterContainer.innerHTML = allTypes.map(function(t) { + var hasEntities = types.has(t); + return ''; + }).join(''); + } + + // Gewicht-Slider + var slider = document.getElementById('network-weight-slider'); + if (slider) { + slider.value = 1; + slider.oninput = function() { + var label = document.getElementById('network-weight-value'); + if (label) label.textContent = this.value; + NetworkGraph.filterByWeight(parseInt(this.value)); + }; + } + + // Suche + var searchInput = document.getElementById('network-search'); + if (searchInput) { + searchInput.value = ''; + var timer = null; + searchInput.oninput = function() { + clearTimeout(timer); + var val = this.value; + timer = setTimeout(function() { + NetworkGraph.search(val); + }, 250); + }; + } +}; + +/** + * Typ-Filter toggle. + */ +App.toggleNetworkTypeFilter = function(btn) { + btn.classList.toggle('active'); + var activeTypes = []; + document.querySelectorAll('.network-type-filter.active').forEach(function(b) { + activeTypes.push(b.dataset.type); + }); + NetworkGraph.filterByType(new Set(activeTypes)); +}; + +/** + * Progress-Bar anzeigen. + */ +App._showNetworkProgress = function(phase, progress) { + var bar = document.getElementById('network-progress-bar'); + if (bar) bar.style.display = 'block'; + + var steps = ['entity_extraction', 'relationship_extraction', 'correction']; + var stepEls = document.querySelectorAll('.network-progress-step'); + var connectorEls = document.querySelectorAll('.network-progress-connector'); + var phaseIndex = steps.indexOf(phase); + + stepEls.forEach(function(el, i) { + el.classList.remove('active', 'done'); + if (i < phaseIndex) el.classList.add('done'); + else if (i === phaseIndex) el.classList.add('active'); + }); + + connectorEls.forEach(function(el, i) { + el.classList.remove('done'); + if (i < phaseIndex) el.classList.add('done'); + }); + + var fill = document.getElementById('network-progress-fill'); + if (fill) { + var pct = ((phaseIndex / steps.length) * 100) + (progress || 0) * (100 / steps.length) / 100; + fill.style.width = Math.min(100, pct) + '%'; + } + + var label = document.getElementById('network-progress-label'); + if (label) { + var labels = { entity_extraction: 'Entitäten werden extrahiert...', relationship_extraction: 'Beziehungen werden analysiert...', correction: 'Korrekturen werden angewendet...' }; + label.textContent = labels[phase] || 'Wird verarbeitet...'; + } +}; + +App._hideNetworkProgress = function() { + var bar = document.getElementById('network-progress-bar'); + if (bar) bar.style.display = 'none'; +}; + +/** + * Modal: Neue Netzwerkanalyse öffnen. + */ +App.openNetworkModal = async function() { + var list = document.getElementById('network-incident-options'); + if (list) list.innerHTML = '
Lade Lagen...
'; + + openModal('modal-network-new'); + + // Lagen laden + try { + var incidents = await API.listIncidents(); + // Sortierung: zuerst Live (adhoc) alphabetisch, dann Analyse (research) alphabetisch + incidents.sort(function(a, b) { + var typeA = (a.type === 'research') ? 1 : 0; + var typeB = (b.type === 'research') ? 1 : 0; + if (typeA !== typeB) return typeA - typeB; + return (a.title || '').localeCompare(b.title || '', 'de'); + }); + if (list) { + list.innerHTML = incidents.map(function(inc) { + var typeLabel = inc.type === 'research' ? 'Analyse' : 'Live'; + return ''; + }).join(''); + } + } catch (e) { + if (list) list.innerHTML = '
Fehler beim Laden der Lagen
'; + } + + // Name-Feld leeren + var nameField = document.getElementById('network-name'); + if (nameField) nameField.value = ''; + + // Suchfeld leeren + var searchField = document.getElementById('network-incident-search'); + if (searchField) { + searchField.value = ''; + searchField.oninput = function() { + var term = this.value.toLowerCase(); + document.querySelectorAll('.network-incident-option').forEach(function(opt) { + var text = opt.textContent.toLowerCase(); + opt.style.display = text.includes(term) ? '' : 'none'; + }); + }; + } +}; + +/** + * Netzwerkanalyse erstellen. + */ +App.submitNetworkAnalysis = async function(e) { + if (e) e.preventDefault(); + + var name = (document.getElementById('network-name').value || '').trim(); + if (!name) { + UI.showToast('Bitte einen Namen eingeben.', 'warning'); + return; + } + + var incidentIds = []; + document.querySelectorAll('.network-incident-cb:checked').forEach(function(cb) { + incidentIds.push(parseInt(cb.value)); + }); + + if (incidentIds.length === 0) { + UI.showToast('Bitte mindestens eine Lage auswählen.', 'warning'); + return; + } + + var btn = document.getElementById('network-submit-btn'); + if (btn) btn.disabled = true; + + try { + var result = await API.createNetworkAnalysis({ name: name, incident_ids: incidentIds }); + closeModal('modal-network-new'); + await this.loadNetworkAnalyses(); + await this.selectNetworkAnalysis(result.id); + UI.showToast('Netzwerkanalyse gestartet.', 'success'); + } catch (err) { + UI.showToast('Fehler: ' + err.message, 'error'); + } finally { + if (btn) btn.disabled = false; + } +}; + +/** + * Netzwerkanalyse neu generieren. + */ +App.regenerateNetwork = async function() { + if (!this.currentNetworkId) return; + if (!await confirmDialog('Netzwerkanalyse neu generieren? Bestehende Daten werden überschrieben.')) return; + + try { + await API.regenerateNetwork(this.currentNetworkId); + this._showNetworkProgress('entity_extraction', 0); + await this.loadNetworkAnalyses(); + UI.showToast('Neugenerierung gestartet.', 'success'); + } catch (err) { + UI.showToast('Fehler: ' + err.message, 'error'); + } +}; + +/** + * Netzwerkanalyse löschen. + */ +App.deleteNetworkAnalysis = async function() { + if (!this.currentNetworkId) return; + if (!await confirmDialog('Netzwerkanalyse wirklich löschen? Alle Daten gehen verloren.')) return; + + try { + await API.deleteNetworkAnalysis(this.currentNetworkId); + this.currentNetworkId = null; + localStorage.removeItem('selectedNetworkId'); + NetworkGraph.destroy(); + document.getElementById('network-view').style.display = 'none'; + document.getElementById('empty-state').style.display = 'flex'; + await this.loadNetworkAnalyses(); + UI.showToast('Netzwerkanalyse gelöscht.', 'success'); + } catch (err) { + UI.showToast('Fehler: ' + err.message, 'error'); + } +}; + +/** + * Netzwerkanalyse exportieren. + */ +App.exportNetwork = async function(format) { + if (!this.currentNetworkId) return; + + if (format === 'png') { + NetworkGraph.exportPNG(); + return; + } + + try { + var resp = await API.exportNetworkAnalysis(this.currentNetworkId, format); + if (!resp.ok) throw new Error('Export fehlgeschlagen'); + var blob = await resp.blob(); + var url = URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = 'netzwerk-' + this.currentNetworkId + '.' + format; + a.click(); + URL.revokeObjectURL(url); + } catch (err) { + UI.showToast('Export fehlgeschlagen: ' + err.message, 'error'); + } +}; + +/** + * WebSocket-Handler für Netzwerk-Events. + */ +App._handleNetworkStatus = function(msg) { + if (msg.analysis_id === this.currentNetworkId) { + this._showNetworkProgress(msg.phase, msg.progress || 0); + } +}; + +App._handleNetworkComplete = async function(msg) { + this._networkGenerating.delete(msg.analysis_id); + + if (msg.analysis_id === this.currentNetworkId) { + this._hideNetworkProgress(); + // Graph neu laden + try { + var graphData = await API.getNetworkGraph(msg.analysis_id); + NetworkGraph.init('network-graph-area', graphData); + this._setupNetworkFilters(graphData); + + var analysis = await API.getNetworkAnalysis(msg.analysis_id); + this._renderNetworkHeader(analysis); + } catch (e) { + console.error('Graph nach Generierung laden fehlgeschlagen:', e); + } + UI.showToast('Netzwerkanalyse fertig: ' + (msg.entity_count || 0) + ' Entitäten, ' + (msg.relation_count || 0) + ' Beziehungen', 'success'); + } + + await this.loadNetworkAnalyses(); +}; + +App._handleNetworkError = function(msg) { + this._networkGenerating.delete(msg.analysis_id); + + if (msg.analysis_id === this.currentNetworkId) { + this._hideNetworkProgress(); + var graphArea = document.getElementById('network-graph-area'); + if (graphArea) graphArea.innerHTML = '
Fehler: ' + _escHtml(msg.error || 'Unbekannter Fehler') + '
'; + } + + UI.showToast('Netzwerkanalyse fehlgeschlagen: ' + (msg.error || 'Unbekannter Fehler'), 'error'); + this.loadNetworkAnalyses(); +}; + +/** + * Cluster isolieren (nur verbundene Knoten zeigen). + */ +App.isolateNetworkCluster = function() { + if (NetworkGraph._selectedNode) { + NetworkGraph.isolateCluster(NetworkGraph._selectedNode.id); + } +}; + +/** + * Graph-Ansicht zurücksetzen. + */ +App.resetNetworkView = function() { + NetworkGraph.resetView(); + // Typ-Filter zurücksetzen + document.querySelectorAll('.network-type-filter').forEach(function(btn) { + if (!btn.disabled) btn.classList.add('active'); + }); + var slider = document.getElementById('network-weight-slider'); + if (slider) { slider.value = 1; var lbl = document.getElementById('network-weight-value'); if (lbl) lbl.textContent = '1'; } + var search = document.getElementById('network-search'); + if (search) search.value = ''; +}; + +// HTML-Escape Hilfsfunktion (falls nicht global verfügbar) +function _escHtml(text) { + if (typeof UI !== 'undefined' && UI.escape) return UI.escape(text); + var d = document.createElement('div'); + d.textContent = text || ''; + return d.innerHTML; +}