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;
+}