diff --git a/src/agents/orchestrator.py b/src/agents/orchestrator.py index 769c7c6..6c297a8 100644 --- a/src/agents/orchestrator.py +++ b/src/agents/orchestrator.py @@ -548,6 +548,7 @@ class AgentOrchestrator: # Refresh-Log starten now = datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S') + now_utc = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') cursor = await db.execute( "INSERT INTO refresh_log (incident_id, started_at, status, trigger_type, retry_count, tenant_id) VALUES (?, ?, 'running', ?, ?, ?)", (incident_id, now, trigger_type, retry_count, tenant_id), @@ -562,7 +563,7 @@ class AgentOrchestrator: await self._ws_manager.broadcast_for_incident({ "type": "status_update", "incident_id": incident_id, - "data": {"status": research_status, "detail": research_detail, "started_at": now}, + "data": {"status": research_status, "detail": research_detail, "started_at": now_utc}, }, visibility, created_by, tenant_id) # Schritt 1+2: RSS-Feeds und Claude-Recherche parallel ausführen @@ -840,7 +841,7 @@ class AgentOrchestrator: await self._ws_manager.broadcast_for_incident({ "type": "status_update", "incident_id": incident_id, - "data": {"status": "factchecking", "detail": "Prüft Fakten gegen unabhängige Quellen...", "started_at": now}, + "data": {"status": "factchecking", "detail": "Prüft Fakten gegen unabhängige Quellen...", "started_at": now_utc}, }, visibility, created_by, tenant_id) # Schritt 4: Faktencheck diff --git a/src/static/js/components.js b/src/static/js/components.js index b94e09d..1cc1bfc 100644 --- a/src/static/js/components.js +++ b/src/static/js/components.js @@ -291,7 +291,7 @@ const UI = { this._progressTimer = setInterval(() => { if (!this._progressStartTime) return; - const elapsed = Math.floor((Date.now() - this._progressStartTime) / 1000); + const elapsed = Math.max(0, Math.floor((Date.now() - this._progressStartTime) / 1000)); const mins = Math.floor(elapsed / 60); const secs = elapsed % 60; timerEl.textContent = `${mins}:${String(secs).padStart(2, '0')}`; @@ -605,6 +605,45 @@ const UI = { _pendingLocations: null, + // Farbige Marker-Icons nach Kategorie (inline SVG, keine externen Ressourcen) + _markerIcons: null, + _createSvgIcon(fillColor, strokeColor) { + const svg = ``; + return L.divIcon({ + html: svg, + className: 'map-marker-svg', + iconSize: [28, 42], + iconAnchor: [14, 42], + popupAnchor: [0, -36], + }); + }, + _initMarkerIcons() { + if (this._markerIcons || typeof L === 'undefined') return; + this._markerIcons = { + target: this._createSvgIcon('#dc3545', '#a71d2a'), + retaliation: this._createSvgIcon('#f39c12', '#c47d0a'), + actor: this._createSvgIcon('#2a81cb', '#1a5c8f'), + mentioned: this._createSvgIcon('#7b7b7b', '#555555'), + }; + }, + + _categoryLabels: { + target: 'Angegriffene Ziele', + retaliation: 'Vergeltung / Eskalation', + actor: 'Strategische Akteure', + mentioned: 'Erwaehnt', + }, + _categoryColors: { + target: '#cb2b3e', + retaliation: '#f39c12', + actor: '#2a81cb', + mentioned: '#7b7b7b', + }, + renderMap(locations) { const container = document.getElementById('map-container'); const emptyEl = document.getElementById('map-empty'); @@ -683,14 +722,24 @@ const UI = { // Marker hinzufuegen const bounds = []; + this._initMarkerIcons(); + const usedCategories = new Set(); + locations.forEach(loc => { - const marker = L.marker([loc.lat, loc.lon]); + const cat = loc.category || 'mentioned'; + usedCategories.add(cat); + const icon = (this._markerIcons && this._markerIcons[cat]) ? this._markerIcons[cat] : undefined; + const markerOpts = icon ? { icon } : {}; + const marker = L.marker([loc.lat, loc.lon], markerOpts); // Popup-Inhalt + const catLabel = this._categoryLabels[cat] || cat; + const catColor = this._categoryColors[cat] || '#7b7b7b'; let popupHtml = `