fix: Lagen im Netzwerk-Modal sortiert — Live alphabetisch, dann Analyse alphabetisch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Claude Dev
2026-03-16 00:42:56 +01:00
Ursprung 432147de4b
Commit 5018dddad5

Datei anzeigen

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