Fortschrittsanzeige: Popup mit Checkboxen, Blur, Pro-Lage-Timer
Ladebalken ersetzt durch zentriertes Popup-Fenster mit Checkbox-Checkliste (Warteschlange, Recherche, Analyse, Faktencheck) und Echtzeit-Timer. Erster Durchlauf: Popup nicht wegklickbar, Blur-Effekt auf Kacheln. Aktualisierung: Popup minimierbar zu kompakter Status-Leiste. Timer laeuft pro Lage im Hintergrund weiter bei Lagenwechsel. Gesamtzeit wird am Ende im Abschluss-Popup angezeigt. Sidebar: Animierter Gold-Rand und Fortschrittstext (Recherchiert/ Analysiert/Faktencheck) unter dem Lage-Namen bei laufendem Refresh. Zusaetzlicher Cancel-Checkpoint im Orchestrator nach Uebersetzung. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -1167,6 +1167,9 @@ class AgentOrchestrator:
|
|||||||
# Cancel-Check nach paralleler Verarbeitung
|
# Cancel-Check nach paralleler Verarbeitung
|
||||||
self._check_cancelled(incident_id)
|
self._check_cancelled(incident_id)
|
||||||
|
|
||||||
|
# Cancel-Check nach Analyse+Faktencheck
|
||||||
|
self._check_cancelled(incident_id)
|
||||||
|
|
||||||
# --- Faktencheck-Ergebnisse verarbeiten ---
|
# --- Faktencheck-Ergebnisse verarbeiten ---
|
||||||
# Pre-Dedup: Duplikate aus LLM-Antwort entfernen
|
# Pre-Dedup: Duplikate aus LLM-Antwort entfernen
|
||||||
fact_checks = deduplicate_new_facts(fact_checks)
|
fact_checks = deduplicate_new_facts(fact_checks)
|
||||||
|
|||||||
@@ -1900,141 +1900,218 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* === Fortschrittsanzeige === */
|
/* === Fortschrittsanzeige === */
|
||||||
.progress-bar {
|
/* === Fortschritts-Popup === */
|
||||||
|
.progress-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 9000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.progress-overlay.blocking {
|
||||||
|
pointer-events: auto;
|
||||||
|
background: rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
.progress-popup {
|
||||||
|
pointer-events: auto;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
width: 420px;
|
||||||
|
max-width: 92vw;
|
||||||
|
box-shadow: 0 16px 48px rgba(0,0,0,0.5);
|
||||||
|
overflow: hidden;
|
||||||
|
animation: popupIn 0.25s ease-out;
|
||||||
|
}
|
||||||
|
@keyframes popupIn {
|
||||||
|
from { opacity: 0; transform: scale(0.95) translateY(10px); }
|
||||||
|
to { opacity: 1; transform: scale(1) translateY(0); }
|
||||||
|
}
|
||||||
|
.progress-popup-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 16px 20px 12px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.progress-popup-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.progress-popup-timer {
|
||||||
|
font-family: var(--font-mono, 'Courier New', monospace);
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 600;
|
||||||
|
min-width: 42px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.progress-popup-minimize {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.progress-popup-minimize:hover {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
.progress-popup-body { padding: 16px 20px; }
|
||||||
|
.progress-popup-pass {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.progress-checklist { display: flex; flex-direction: column; gap: 6px; }
|
||||||
|
.progress-check-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.progress-check-item.active { background: rgba(240,180,41,0.08); }
|
||||||
|
.progress-check-item.done { opacity: 0.55; }
|
||||||
|
.progress-check-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-disabled);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.progress-check-item.active .progress-check-icon { color: var(--accent); }
|
||||||
|
.progress-check-item.done .progress-check-icon { color: var(--success); }
|
||||||
|
.progress-check-item.error .progress-check-icon { color: var(--error); }
|
||||||
|
.progress-check-icon .spinner {
|
||||||
|
width: 16px; height: 16px;
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
border-top-color: var(--accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||||||
|
.progress-check-label { font-size: 13px; color: var(--text-secondary); flex: 1; }
|
||||||
|
.progress-check-item.active .progress-check-label { color: var(--text-primary); font-weight: 500; }
|
||||||
|
.progress-check-detail { font-size: 11px; color: var(--text-disabled); }
|
||||||
|
.progress-complete-summary {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
background: rgba(34,197,94,0.08);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--success);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.progress-complete-summary .total-time {
|
||||||
|
display: block; margin-top: 6px;
|
||||||
|
font-family: var(--font-mono, 'Courier New', monospace);
|
||||||
|
font-size: 12px; color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
.progress-popup-footer {
|
||||||
|
padding: 10px 20px 16px;
|
||||||
|
display: flex; justify-content: center;
|
||||||
|
}
|
||||||
|
.progress-cancel-btn {
|
||||||
|
background: none; border: none;
|
||||||
|
color: var(--text-disabled); font-size: 12px;
|
||||||
|
cursor: pointer; text-decoration: underline;
|
||||||
|
padding: 4px 8px; transition: color 0.2s;
|
||||||
|
}
|
||||||
|
.progress-cancel-btn:hover { color: var(--error); }
|
||||||
|
|
||||||
|
/* === Mini Progress Bar === */
|
||||||
|
.progress-mini {
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
padding: var(--sp-xl);
|
padding: 10px 16px;
|
||||||
margin-bottom: var(--sp-xl);
|
margin-bottom: var(--sp-xl);
|
||||||
position: relative;
|
display: flex; align-items: center; gap: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s, background 0.2s;
|
||||||
}
|
}
|
||||||
|
.progress-mini:hover { border-color: var(--accent); background: var(--bg-secondary); }
|
||||||
.progress-steps {
|
.progress-mini-dot {
|
||||||
display: flex;
|
width: 8px; height: 8px; border-radius: 50%;
|
||||||
justify-content: space-between;
|
background: var(--accent);
|
||||||
margin-bottom: var(--sp-lg);
|
animation: pulse 1.5s ease-in-out infinite;
|
||||||
}
|
|
||||||
|
|
||||||
.progress-step {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--sp-md);
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-disabled);
|
|
||||||
transition: color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-step.active {
|
|
||||||
color: var(--accent);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-step.done {
|
|
||||||
color: var(--success);
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-step-dot {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--text-disabled);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
.progress-mini-text { font-size: 12px; color: var(--text-secondary); flex: 1; }
|
||||||
.progress-step.active .progress-step-dot {
|
.progress-mini-timer {
|
||||||
background: var(--accent);
|
font-family: var(--font-mono, 'Courier New', monospace);
|
||||||
box-shadow: var(--glow-accent);
|
font-size: 12px; color: var(--accent); font-weight: 600;
|
||||||
animation: pulse 1.5s ease-in-out infinite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-step.done .progress-step-dot {
|
/* === Blur for First Refresh === */
|
||||||
background: var(--success);
|
.grid-stack.blurred .grid-stack-item-content {
|
||||||
|
filter: blur(8px);
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
transition: filter 0.4s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-track {
|
/* === Sidebar Refreshing Indicator === */
|
||||||
height: 4px;
|
.incident-item.refreshing-item {
|
||||||
background: var(--bg-secondary);
|
border: 1px solid transparent;
|
||||||
border-radius: 2px;
|
background-size: 300% 300%;
|
||||||
overflow: hidden;
|
animation: sidebarRefreshBorder 3s ease infinite;
|
||||||
margin-bottom: var(--sp-md);
|
border-image: linear-gradient(135deg, var(--accent), transparent, var(--accent)) 1;
|
||||||
}
|
border-radius: var(--radius);
|
||||||
|
|
||||||
.progress-fill {
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, var(--accent), var(--success));
|
|
||||||
border-radius: 2px;
|
|
||||||
transition: width 0.5s ease-out;
|
|
||||||
width: 0%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-label-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--sp-md);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.incident-item.refreshing-item::after {
|
||||||
.progress-label {
|
content: '';
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-timer {
|
|
||||||
font-family: var(--font-mono, 'Courier New', monospace);
|
|
||||||
color: var(--text-disabled);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-pass-info {
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--accent-primary);
|
|
||||||
margin-left: 8px;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 0.3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-cancel-btn {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: var(--sp-xl);
|
inset: -1px;
|
||||||
bottom: var(--sp-lg);
|
border-radius: var(--radius);
|
||||||
background: none;
|
border: 1px solid var(--accent);
|
||||||
border: none;
|
opacity: 0.3;
|
||||||
color: var(--text-disabled);
|
animation: sidebarGlow 2s ease-in-out infinite;
|
||||||
font-size: 11px;
|
pointer-events: none;
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline;
|
|
||||||
padding: 2px 4px;
|
|
||||||
transition: color 0.2s ease;
|
|
||||||
}
|
}
|
||||||
|
@keyframes sidebarGlow {
|
||||||
.progress-cancel-btn:hover {
|
0%, 100% { opacity: 0.15; box-shadow: 0 0 4px var(--accent); }
|
||||||
color: var(--error);
|
50% { opacity: 0.4; box-shadow: 0 0 12px var(--accent); }
|
||||||
}
|
}
|
||||||
|
.incident-refresh-status {
|
||||||
.progress-bar--complete .progress-cancel-btn,
|
font-size: 10px;
|
||||||
.progress-bar--error .progress-cancel-btn {
|
color: var(--accent);
|
||||||
display: none;
|
margin-top: 2px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
}
|
}
|
||||||
|
.incident-refresh-status .mini-spinner {
|
||||||
.progress-bar--complete .progress-fill {
|
width: 10px; height: 10px;
|
||||||
background: linear-gradient(90deg, var(--success), #34D399);
|
border: 1.5px solid var(--border);
|
||||||
width: 100% !important;
|
border-top-color: var(--accent);
|
||||||
}
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
.progress-bar--complete .progress-label {
|
flex-shrink: 0;
|
||||||
color: var(--success);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar--error .progress-fill {
|
|
||||||
background: linear-gradient(90deg, var(--error), #F87171);
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar--error .progress-label {
|
|
||||||
color: var(--error);
|
|
||||||
}
|
}
|
||||||
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||||
|
|
||||||
/* === Briefing === */
|
/* === Briefing === */
|
||||||
.briefing-content {
|
.briefing-content {
|
||||||
|
|||||||
@@ -183,31 +183,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Fortschrittsanzeige -->
|
<!-- Minimierte Fortschrittsanzeige -->
|
||||||
<div class="progress-bar" id="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-label="Verarbeitungsfortschritt" style="display:none;">
|
<div class="progress-mini" id="progress-mini" style="display:none;" onclick="App.openProgressPopup()">
|
||||||
<div class="progress-steps">
|
<span class="progress-mini-dot"></span>
|
||||||
<div class="progress-step" id="step-researching">
|
<span class="progress-mini-text" id="progress-mini-text">Läuft...</span>
|
||||||
<div class="progress-step-dot"></div>
|
<span class="progress-mini-timer" id="progress-mini-timer"></span>
|
||||||
<span>Recherche</span>
|
|
||||||
</div>
|
|
||||||
<div class="progress-step" id="step-analyzing">
|
|
||||||
<div class="progress-step-dot"></div>
|
|
||||||
<span>Analyse</span>
|
|
||||||
</div>
|
|
||||||
<div class="progress-step" id="step-factchecking">
|
|
||||||
<div class="progress-step-dot"></div>
|
|
||||||
<span>Faktencheck</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="progress-track">
|
|
||||||
<div class="progress-fill" id="progress-fill"></div>
|
|
||||||
</div>
|
|
||||||
<div class="progress-label-container">
|
|
||||||
<span id="progress-label" class="progress-label">Warte auf Start...</span>
|
|
||||||
<span id="progress-pass-info" class="progress-pass-info" style="display:none;"></span>
|
|
||||||
<span id="progress-timer" class="progress-timer"></span>
|
|
||||||
</div>
|
|
||||||
<button id="progress-cancel-btn" class="progress-cancel-btn" onclick="App.cancelRefresh()">Abbrechen</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Layout-Toolbar -->
|
<!-- Layout-Toolbar -->
|
||||||
@@ -706,5 +686,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Fortschritts-Popup -->
|
||||||
|
<div class="progress-overlay" id="progress-overlay" style="display:none;">
|
||||||
|
<div class="progress-popup" id="progress-popup">
|
||||||
|
<div class="progress-popup-header">
|
||||||
|
<span class="progress-popup-title" id="progress-popup-title">Aktualisierung läuft</span>
|
||||||
|
<span class="progress-popup-timer" id="progress-popup-timer"></span>
|
||||||
|
<button class="progress-popup-minimize" id="progress-popup-minimize" style="display:none;" onclick="App.minimizeProgress()" title="Minimieren">−</button>
|
||||||
|
</div>
|
||||||
|
<div class="progress-popup-body">
|
||||||
|
<div class="progress-popup-pass" id="progress-popup-pass" style="display:none;"></div>
|
||||||
|
<div class="progress-checklist" id="progress-checklist">
|
||||||
|
<div class="progress-check-item" data-step="queued">
|
||||||
|
<span class="progress-check-icon">○</span>
|
||||||
|
<span class="progress-check-label">In Warteschlange</span>
|
||||||
|
<span class="progress-check-detail"></span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-check-item" data-step="researching">
|
||||||
|
<span class="progress-check-icon">○</span>
|
||||||
|
<span class="progress-check-label">Quellen werden durchsucht</span>
|
||||||
|
<span class="progress-check-detail"></span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-check-item" data-step="analyzing">
|
||||||
|
<span class="progress-check-icon">○</span>
|
||||||
|
<span class="progress-check-label">Meldungen werden analysiert</span>
|
||||||
|
<span class="progress-check-detail"></span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-check-item" data-step="factchecking">
|
||||||
|
<span class="progress-check-icon">○</span>
|
||||||
|
<span class="progress-check-label">Faktencheck läuft</span>
|
||||||
|
<span class="progress-check-detail"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-complete-summary" id="progress-complete-summary" style="display:none;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-popup-footer">
|
||||||
|
<button class="progress-cancel-btn" id="progress-cancel-btn" onclick="App.cancelRefresh()">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -699,10 +699,18 @@ const App = {
|
|||||||
// Refresh-Status fuer diese Lage wiederherstellen
|
// Refresh-Status fuer diese Lage wiederherstellen
|
||||||
const isRefreshing = this._refreshingIncidents.has(id);
|
const isRefreshing = this._refreshingIncidents.has(id);
|
||||||
this._updateRefreshButton(isRefreshing);
|
this._updateRefreshButton(isRefreshing);
|
||||||
|
// Hide any popup from previous incident
|
||||||
|
const prevOverlay = document.getElementById('progress-overlay');
|
||||||
|
if (prevOverlay) prevOverlay.style.display = 'none';
|
||||||
|
const prevMini = document.getElementById('progress-mini');
|
||||||
|
if (prevMini) prevMini.style.display = 'none';
|
||||||
|
const grid = document.querySelector('.grid-stack');
|
||||||
|
if (grid) grid.classList.remove('blurred');
|
||||||
if (isRefreshing) {
|
if (isRefreshing) {
|
||||||
UI.showProgress('researching');
|
const state = UI._progressState[id];
|
||||||
} else {
|
const step = state ? state.step : 'researching';
|
||||||
UI.hideProgress();
|
const isFirst = state ? state.isFirst : false;
|
||||||
|
UI.showProgress(step, {}, id, isFirst);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alte Inhalte sofort leeren um Flackern beim Wechsel zu vermeiden
|
// Alte Inhalte sofort leeren um Flackern beim Wechsel zu vermeiden
|
||||||
@@ -1616,7 +1624,7 @@ const App = {
|
|||||||
// Sofort ersten Refresh starten
|
// Sofort ersten Refresh starten
|
||||||
this._refreshingIncidents.add(incident.id);
|
this._refreshingIncidents.add(incident.id);
|
||||||
this._updateRefreshButton(true);
|
this._updateRefreshButton(true);
|
||||||
UI.showProgress('queued');
|
// showProgress called via handleStatusUpdate
|
||||||
await API.refreshIncident(incident.id);
|
await API.refreshIncident(incident.id);
|
||||||
UI.showToast(`Lage "${incident.title}" angelegt. Recherche gestartet.`, 'success');
|
UI.showToast(`Lage "${incident.title}" angelegt. Recherche gestartet.`, 'success');
|
||||||
}
|
}
|
||||||
@@ -1676,12 +1684,14 @@ async handleRefresh() {
|
|||||||
try {
|
try {
|
||||||
this._refreshingIncidents.add(this.currentIncidentId);
|
this._refreshingIncidents.add(this.currentIncidentId);
|
||||||
this._updateRefreshButton(true);
|
this._updateRefreshButton(true);
|
||||||
UI.showProgress('queued');
|
// showProgress called via handleStatusUpdate
|
||||||
const result = await API.refreshIncident(this.currentIncidentId);
|
const result = await API.refreshIncident(this.currentIncidentId);
|
||||||
if (result && result.status === 'skipped') {
|
if (result && result.status === 'skipped') {
|
||||||
UI.showToast('Aktualisierung ist in der Warteschlange und wird ausgefuehrt, sobald die aktuelle Recherche abgeschlossen ist.', 'info');
|
UI.showToast('Aktualisierung ist in der Warteschlange und wird ausgefuehrt, sobald die aktuelle Recherche abgeschlossen ist.', 'info');
|
||||||
} else {
|
} else {
|
||||||
UI.showToast('Aktualisierung gestartet.', 'success');
|
UI.showToast('Aktualisierung gestartet.', 'success');
|
||||||
|
var _inc2 = this.incidents.find(function(i) { return i.id === this.currentIncidentId; }.bind(this));
|
||||||
|
UI.showProgress('queued', {}, this.currentIncidentId, _inc2 && !_inc2.summary);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._refreshingIncidents.delete(this.currentIncidentId);
|
this._refreshingIncidents.delete(this.currentIncidentId);
|
||||||
@@ -2013,9 +2023,8 @@ async handleRefresh() {
|
|||||||
handleStatusUpdate(msg) {
|
handleStatusUpdate(msg) {
|
||||||
const status = msg.data.status;
|
const status = msg.data.status;
|
||||||
if (status === 'retrying') {
|
if (status === 'retrying') {
|
||||||
// Retry-Status → Fehleranzeige mit Retry-Info
|
|
||||||
if (msg.incident_id === this.currentIncidentId) {
|
if (msg.incident_id === this.currentIncidentId) {
|
||||||
UI.showProgressError('', true, msg.data.delay || 120);
|
UI.showProgressError('', true, msg.data.delay || 120, msg.incident_id);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2023,8 +2032,11 @@ async handleRefresh() {
|
|||||||
this._refreshingIncidents.add(msg.incident_id);
|
this._refreshingIncidents.add(msg.incident_id);
|
||||||
}
|
}
|
||||||
this._updateSidebarDot(msg.incident_id);
|
this._updateSidebarDot(msg.incident_id);
|
||||||
|
// Detect first refresh: no summary means first run
|
||||||
|
const inc = this.incidents.find(i => i.id === msg.incident_id);
|
||||||
|
const isFirst = inc && !inc.summary;
|
||||||
|
UI.showProgress(status, msg.data, msg.incident_id, isFirst);
|
||||||
if (msg.incident_id === this.currentIncidentId) {
|
if (msg.incident_id === this.currentIncidentId) {
|
||||||
UI.showProgress(status, msg.data);
|
|
||||||
this._updateRefreshButton(status !== 'idle');
|
this._updateRefreshButton(status !== 'idle');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2037,14 +2049,13 @@ async handleRefresh() {
|
|||||||
this._updateRefreshButton(false);
|
this._updateRefreshButton(false);
|
||||||
await this.loadIncidentDetail(msg.incident_id);
|
await this.loadIncidentDetail(msg.incident_id);
|
||||||
|
|
||||||
// Progress-Bar nicht sofort ausblenden — auf refresh_summary warten
|
// Progress-Popup nicht sofort ausblenden — auf refresh_summary warten
|
||||||
this._pendingComplete = msg.incident_id;
|
this._pendingComplete = msg.incident_id;
|
||||||
// Fallback: Wenn nach 5s kein refresh_summary kommt → direkt ausblenden
|
|
||||||
if (this._pendingCompleteTimer) clearTimeout(this._pendingCompleteTimer);
|
if (this._pendingCompleteTimer) clearTimeout(this._pendingCompleteTimer);
|
||||||
this._pendingCompleteTimer = setTimeout(() => {
|
this._pendingCompleteTimer = setTimeout(() => {
|
||||||
if (this._pendingComplete === msg.incident_id) {
|
if (this._pendingComplete === msg.incident_id) {
|
||||||
this._pendingComplete = null;
|
this._pendingComplete = null;
|
||||||
UI.hideProgress();
|
UI.hideProgress(msg.incident_id);
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
@@ -2065,8 +2076,7 @@ async handleRefresh() {
|
|||||||
this._pendingCompleteTimer = null;
|
this._pendingCompleteTimer = null;
|
||||||
}
|
}
|
||||||
this._pendingComplete = null;
|
this._pendingComplete = null;
|
||||||
UI.showProgressComplete(d);
|
UI.showProgressComplete(d, msg.incident_id);
|
||||||
setTimeout(() => UI.hideProgress(), 4000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toast-Text zusammenbauen
|
// Toast-Text zusammenbauen
|
||||||
@@ -2145,7 +2155,7 @@ async handleRefresh() {
|
|||||||
this._pendingCompleteTimer = null;
|
this._pendingCompleteTimer = null;
|
||||||
}
|
}
|
||||||
this._pendingComplete = null;
|
this._pendingComplete = null;
|
||||||
UI.showProgressError(msg.data.error, false);
|
UI.showProgressError(msg.data.error, false, 0, msg.incident_id);
|
||||||
}
|
}
|
||||||
UI.showToast(`Recherche-Fehler: ${msg.data.error}`, 'error');
|
UI.showToast(`Recherche-Fehler: ${msg.data.error}`, 'error');
|
||||||
},
|
},
|
||||||
@@ -2160,11 +2170,19 @@ async handleRefresh() {
|
|||||||
this._pendingCompleteTimer = null;
|
this._pendingCompleteTimer = null;
|
||||||
}
|
}
|
||||||
this._pendingComplete = null;
|
this._pendingComplete = null;
|
||||||
UI.hideProgress();
|
UI.hideProgress(msg.incident_id);
|
||||||
}
|
}
|
||||||
UI.showToast('Recherche abgebrochen.', 'info');
|
UI.showToast('Recherche abgebrochen.', 'info');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
minimizeProgress() {
|
||||||
|
UI.minimizeProgress(this.currentIncidentId);
|
||||||
|
},
|
||||||
|
|
||||||
|
openProgressPopup() {
|
||||||
|
UI.openProgressPopup(this.currentIncidentId);
|
||||||
|
},
|
||||||
|
|
||||||
async cancelRefresh() {
|
async cancelRefresh() {
|
||||||
if (!this.currentIncidentId) return;
|
if (!this.currentIncidentId) return;
|
||||||
const ok = await confirmDialog('Laufende Recherche abbrechen?');
|
const ok = await confirmDialog('Laufende Recherche abbrechen?');
|
||||||
|
|||||||
@@ -232,205 +232,363 @@ const UI = {
|
|||||||
/**
|
/**
|
||||||
* Fortschrittsanzeige einblenden und Status setzen.
|
* Fortschrittsanzeige einblenden und Status setzen.
|
||||||
*/
|
*/
|
||||||
showProgress(status, extra = {}) {
|
// === Progress State (per-incident) ===
|
||||||
const bar = document.getElementById('progress-bar');
|
_progressState: {}, // { incidentId: { step, isFirst, startTime, minimized } }
|
||||||
if (!bar) return;
|
_progressTimerInterval: null,
|
||||||
bar.style.display = 'block';
|
|
||||||
bar.classList.remove('progress-bar--complete', 'progress-bar--error');
|
|
||||||
|
|
||||||
const steps = {
|
_getStepOrder() {
|
||||||
queued: { active: 0, label: 'In Warteschlange...' },
|
return ['queued', 'researching', 'deep_researching', 'analyzing', 'factchecking'];
|
||||||
researching: { active: 1, label: 'Recherchiert Quellen...' },
|
},
|
||||||
deep_researching: { active: 1, label: 'Tiefenrecherche läuft...' },
|
|
||||||
analyzing: { active: 2, label: 'Analysiert Meldungen...' },
|
_getStepLabel(step) {
|
||||||
factchecking: { active: 3, label: 'Faktencheck läuft...' },
|
const map = {
|
||||||
cancelling: { active: 0, label: 'Wird abgebrochen...' },
|
queued: 'In Warteschlange',
|
||||||
|
researching: 'Recherchiert...',
|
||||||
|
deep_researching: 'Tiefenrecherche...',
|
||||||
|
analyzing: 'Analysiert...',
|
||||||
|
factchecking: 'Faktencheck...',
|
||||||
|
cancelling: 'Wird abgebrochen...',
|
||||||
};
|
};
|
||||||
|
return map[step] || step;
|
||||||
|
},
|
||||||
|
|
||||||
const step = steps[status] || steps.queued;
|
showProgress(status, extra = {}, incidentId = null, isFirstRefresh = false) {
|
||||||
|
if (!incidentId) incidentId = App.currentIncidentId;
|
||||||
|
if (!incidentId) return;
|
||||||
|
|
||||||
// Queue-Position anzeigen
|
// Init state for this incident
|
||||||
let labelText = step.label;
|
if (!this._progressState[incidentId]) {
|
||||||
if (status === 'queued' && extra.queue_position > 1) {
|
this._progressState[incidentId] = { step: 'queued', isFirst: isFirstRefresh, startTime: null, minimized: false };
|
||||||
labelText = `In Warteschlange (Position ${extra.queue_position})...`;
|
}
|
||||||
} else if (extra.detail) {
|
const state = this._progressState[incidentId];
|
||||||
labelText = extra.detail;
|
state.step = status;
|
||||||
|
if (isFirstRefresh) state.isFirst = true;
|
||||||
|
|
||||||
|
// Start timer on first non-queued status
|
||||||
|
if (status !== 'queued' && !state.startTime) {
|
||||||
|
if (extra.started_at) {
|
||||||
|
const serverStart = typeof parseUTC === 'function' ? parseUTC(extra.started_at) : new Date(extra.started_at);
|
||||||
|
state.startTime = serverStart ? serverStart.getTime() : Date.now();
|
||||||
|
} else {
|
||||||
|
state.startTime = Date.now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi-Pass: Durchlauf-Info anzeigen
|
// Start global timer interval if not running
|
||||||
const passEl = document.getElementById('progress-pass-info');
|
if (!this._progressTimerInterval) {
|
||||||
|
this._progressTimerInterval = setInterval(() => this._tickProgressTimers(), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only show UI for current incident
|
||||||
|
if (incidentId !== App.currentIncidentId) return;
|
||||||
|
|
||||||
|
// Update sidebar status text
|
||||||
|
this._updateSidebarRefreshStatus(incidentId, status, extra);
|
||||||
|
|
||||||
|
if (state.minimized) {
|
||||||
|
this._showMiniProgress(status, state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._showPopupProgress(status, extra, state);
|
||||||
|
},
|
||||||
|
|
||||||
|
_showPopupProgress(status, extra, state) {
|
||||||
|
const overlay = document.getElementById('progress-overlay');
|
||||||
|
const popup = document.getElementById('progress-popup');
|
||||||
|
if (!overlay || !popup) return;
|
||||||
|
|
||||||
|
overlay.style.display = 'flex';
|
||||||
|
|
||||||
|
// Blocking (no close) for first refresh
|
||||||
|
if (state.isFirst) {
|
||||||
|
overlay.classList.add('blocking');
|
||||||
|
// Apply blur to grid
|
||||||
|
const grid = document.querySelector('.grid-stack');
|
||||||
|
if (grid) grid.classList.add('blurred');
|
||||||
|
} else {
|
||||||
|
overlay.classList.remove('blocking');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimize button: only for updates (not first)
|
||||||
|
const minBtn = document.getElementById('progress-popup-minimize');
|
||||||
|
if (minBtn) minBtn.style.display = state.isFirst ? 'none' : '';
|
||||||
|
|
||||||
|
// Title
|
||||||
|
const titleEl = document.getElementById('progress-popup-title');
|
||||||
|
if (titleEl) titleEl.textContent = state.isFirst ? 'Erste Recherche l\u00e4uft' : 'Aktualisierung l\u00e4uft';
|
||||||
|
|
||||||
|
// Multi-pass info
|
||||||
|
const passEl = document.getElementById('progress-popup-pass');
|
||||||
if (passEl) {
|
if (passEl) {
|
||||||
if (extra.research_pass && extra.research_total_passes) {
|
if (extra.research_pass && extra.research_total_passes) {
|
||||||
passEl.textContent = `Durchlauf ${extra.research_pass}/${extra.research_total_passes}`;
|
passEl.textContent = 'Durchlauf ' + extra.research_pass + '/' + extra.research_total_passes;
|
||||||
passEl.style.display = '';
|
passEl.style.display = '';
|
||||||
} else {
|
} else {
|
||||||
passEl.style.display = 'none';
|
passEl.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timer starten beim Übergang von queued zu aktivem Status
|
// Update checklist
|
||||||
if (step.active > 0 && !this._progressStartTime) {
|
const stepOrder = this._getStepOrder();
|
||||||
if (extra.started_at) {
|
const currentIdx = stepOrder.indexOf(status === 'deep_researching' ? 'researching' : status);
|
||||||
// Echte Startzeit vom Server verwenden
|
const items = document.querySelectorAll('.progress-check-item');
|
||||||
const serverStart = parseUTC(extra.started_at);
|
// Map checklist items to step indices: queued=0, researching=1, analyzing=3, factchecking=4
|
||||||
this._progressStartTime = serverStart ? serverStart.getTime() : Date.now();
|
const checkStepMap = { queued: 0, researching: 1, analyzing: 3, factchecking: 4 };
|
||||||
|
|
||||||
|
items.forEach(item => {
|
||||||
|
const step = item.dataset.step;
|
||||||
|
const stepIdx = checkStepMap[step] !== undefined ? checkStepMap[step] : -1;
|
||||||
|
const icon = item.querySelector('.progress-check-icon');
|
||||||
|
const detail = item.querySelector('.progress-check-detail');
|
||||||
|
|
||||||
|
item.classList.remove('active', 'done', 'error');
|
||||||
|
|
||||||
|
if (stepIdx < currentIdx || (step === 'queued' && currentIdx > 0)) {
|
||||||
|
item.classList.add('done');
|
||||||
|
if (icon) icon.innerHTML = '\u2713';
|
||||||
|
} else if (stepIdx === currentIdx || (step === 'researching' && (status === 'researching' || status === 'deep_researching'))) {
|
||||||
|
item.classList.add('active');
|
||||||
|
if (icon) icon.innerHTML = '<div class="spinner"></div>';
|
||||||
|
if (detail && extra.detail) detail.textContent = extra.detail;
|
||||||
|
else if (detail) detail.textContent = '';
|
||||||
} else {
|
} else {
|
||||||
this._progressStartTime = Date.now();
|
if (icon) icon.innerHTML = '\u25cb';
|
||||||
|
if (detail) detail.textContent = '';
|
||||||
}
|
}
|
||||||
this._startProgressTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
const stepIds = ['step-researching', 'step-analyzing', 'step-factchecking'];
|
|
||||||
|
|
||||||
stepIds.forEach((id, i) => {
|
|
||||||
const el = document.getElementById(id);
|
|
||||||
if (!el) return;
|
|
||||||
el.className = 'progress-step';
|
|
||||||
if (i + 1 < step.active) el.classList.add('done');
|
|
||||||
else if (i + 1 === step.active) el.classList.add('active');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const fill = document.getElementById('progress-fill');
|
// Cancel button
|
||||||
const percent = step.active === 0 ? 5 : Math.round((step.active / 3) * 100);
|
const cancelBtn = document.getElementById('progress-cancel-btn');
|
||||||
if (fill) {
|
if (cancelBtn) {
|
||||||
fill.style.width = percent + '%';
|
cancelBtn.style.display = '';
|
||||||
|
cancelBtn.textContent = 'Abbrechen';
|
||||||
|
cancelBtn.disabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ARIA-Werte auf der Progressbar aktualisieren
|
// Hide complete summary
|
||||||
bar.setAttribute('aria-valuenow', String(percent));
|
const summaryEl = document.getElementById('progress-complete-summary');
|
||||||
bar.setAttribute('aria-valuetext', labelText);
|
if (summaryEl) summaryEl.style.display = 'none';
|
||||||
|
|
||||||
const label = document.getElementById('progress-label');
|
// Hide mini bar
|
||||||
if (label) label.textContent = labelText;
|
const mini = document.getElementById('progress-mini');
|
||||||
|
if (mini) mini.style.display = 'none';
|
||||||
// Cancel-Button sichtbar machen
|
|
||||||
const cancelBtn = document.getElementById('progress-cancel-btn');
|
|
||||||
if (cancelBtn) cancelBtn.style.display = '';
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
_showMiniProgress(status, state) {
|
||||||
* Timer-Intervall starten (1x pro Sekunde).
|
const mini = document.getElementById('progress-mini');
|
||||||
*/
|
if (!mini) return;
|
||||||
_startProgressTimer() {
|
mini.style.display = 'flex';
|
||||||
if (this._progressTimer) return;
|
|
||||||
const timerEl = document.getElementById('progress-timer');
|
|
||||||
if (!timerEl) return;
|
|
||||||
|
|
||||||
this._progressTimer = setInterval(() => {
|
const textEl = document.getElementById('progress-mini-text');
|
||||||
if (!this._progressStartTime) return;
|
if (textEl) textEl.textContent = this._getStepLabel(status);
|
||||||
const elapsed = Math.max(0, Math.floor((Date.now() - this._progressStartTime) / 1000));
|
|
||||||
|
// Hide popup
|
||||||
|
const overlay = document.getElementById('progress-overlay');
|
||||||
|
if (overlay) overlay.style.display = 'none';
|
||||||
|
},
|
||||||
|
|
||||||
|
minimizeProgress(incidentId) {
|
||||||
|
if (!incidentId) incidentId = App.currentIncidentId;
|
||||||
|
const state = this._progressState[incidentId];
|
||||||
|
if (!state) return;
|
||||||
|
state.minimized = true;
|
||||||
|
this._showMiniProgress(state.step, state);
|
||||||
|
},
|
||||||
|
|
||||||
|
openProgressPopup(incidentId) {
|
||||||
|
if (!incidentId) incidentId = App.currentIncidentId;
|
||||||
|
const state = this._progressState[incidentId];
|
||||||
|
if (!state) return;
|
||||||
|
state.minimized = false;
|
||||||
|
this._showPopupProgress(state.step, {}, state);
|
||||||
|
},
|
||||||
|
|
||||||
|
showProgressComplete(data, incidentId) {
|
||||||
|
if (!incidentId) incidentId = App.currentIncidentId;
|
||||||
|
const state = this._progressState[incidentId];
|
||||||
|
|
||||||
|
// Calculate total time
|
||||||
|
let totalTimeStr = '';
|
||||||
|
if (state && state.startTime) {
|
||||||
|
const elapsed = Math.floor((Date.now() - state.startTime) / 1000);
|
||||||
const mins = Math.floor(elapsed / 60);
|
const mins = Math.floor(elapsed / 60);
|
||||||
const secs = elapsed % 60;
|
const secs = elapsed % 60;
|
||||||
timerEl.textContent = `${mins}:${String(secs).padStart(2, '0')}`;
|
totalTimeStr = mins + ':' + String(secs).padStart(2, '0');
|
||||||
}, 1000);
|
}
|
||||||
|
|
||||||
|
if (incidentId === App.currentIncidentId) {
|
||||||
|
// Remove blur
|
||||||
|
const grid = document.querySelector('.grid-stack');
|
||||||
|
if (grid) grid.classList.remove('blurred');
|
||||||
|
|
||||||
|
const overlay = document.getElementById('progress-overlay');
|
||||||
|
if (overlay) {
|
||||||
|
overlay.style.display = 'flex';
|
||||||
|
overlay.classList.remove('blocking');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark all steps done
|
||||||
|
document.querySelectorAll('.progress-check-item').forEach(item => {
|
||||||
|
item.classList.remove('active', 'error');
|
||||||
|
item.classList.add('done');
|
||||||
|
const icon = item.querySelector('.progress-check-icon');
|
||||||
|
if (icon) icon.innerHTML = '\u2713';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show summary
|
||||||
|
const parts = [];
|
||||||
|
if (data.new_articles > 0) parts.push(data.new_articles + ' neue Artikel');
|
||||||
|
if (data.confirmed_count > 0) parts.push(data.confirmed_count + ' Fakten best\u00e4tigt');
|
||||||
|
if (data.contradicted_count > 0) parts.push(data.contradicted_count + ' widerlegt');
|
||||||
|
const summaryText = parts.length > 0 ? parts.join(', ') : 'Keine neuen Entwicklungen';
|
||||||
|
|
||||||
|
const summaryEl = document.getElementById('progress-complete-summary');
|
||||||
|
if (summaryEl) {
|
||||||
|
summaryEl.innerHTML = '\u2713 Abgeschlossen: ' + summaryText
|
||||||
|
+ (totalTimeStr ? '<span class="total-time">Gesamtzeit: ' + totalTimeStr + '</span>' : '');
|
||||||
|
summaryEl.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update title
|
||||||
|
const titleEl = document.getElementById('progress-popup-title');
|
||||||
|
if (titleEl) titleEl.textContent = 'Abgeschlossen';
|
||||||
|
|
||||||
|
// Hide cancel, show minimize
|
||||||
|
const cancelBtn = document.getElementById('progress-cancel-btn');
|
||||||
|
if (cancelBtn) cancelBtn.style.display = 'none';
|
||||||
|
const minBtn = document.getElementById('progress-popup-minimize');
|
||||||
|
if (minBtn) minBtn.style.display = '';
|
||||||
|
|
||||||
|
// Hide mini bar
|
||||||
|
const mini = document.getElementById('progress-mini');
|
||||||
|
if (mini) mini.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove sidebar refresh status
|
||||||
|
this._removeSidebarRefreshStatus(incidentId);
|
||||||
|
|
||||||
|
// Clean up state after delay
|
||||||
|
setTimeout(() => {
|
||||||
|
this.hideProgress(incidentId);
|
||||||
|
}, 5000);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
showProgressError(errorMsg, willRetry = false, delay = 0, incidentId = null) {
|
||||||
* Abschluss-Animation: Grüner Balken mit Summary-Text.
|
if (!incidentId) incidentId = App.currentIncidentId;
|
||||||
*/
|
if (incidentId !== App.currentIncidentId) return;
|
||||||
showProgressComplete(data) {
|
|
||||||
const bar = document.getElementById('progress-bar');
|
|
||||||
if (!bar) return;
|
|
||||||
|
|
||||||
// Timer stoppen
|
const overlay = document.getElementById('progress-overlay');
|
||||||
this._stopProgressTimer();
|
if (overlay) overlay.style.display = 'flex';
|
||||||
|
|
||||||
// Alle Steps auf done
|
// Mark current step as error
|
||||||
['step-researching', 'step-analyzing', 'step-factchecking'].forEach(id => {
|
const state = this._progressState[incidentId];
|
||||||
const el = document.getElementById(id);
|
if (state) {
|
||||||
if (el) { el.className = 'progress-step done'; }
|
const items = document.querySelectorAll('.progress-check-item.active');
|
||||||
});
|
items.forEach(item => {
|
||||||
|
item.classList.remove('active');
|
||||||
// Fill auf 100%
|
item.classList.add('error');
|
||||||
const fill = document.getElementById('progress-fill');
|
const icon = item.querySelector('.progress-check-icon');
|
||||||
if (fill) fill.style.width = '100%';
|
if (icon) icon.innerHTML = '\u2717';
|
||||||
|
});
|
||||||
// Complete-Klasse
|
}
|
||||||
bar.classList.remove('progress-bar--error');
|
|
||||||
bar.classList.add('progress-bar--complete');
|
const titleEl = document.getElementById('progress-popup-title');
|
||||||
|
if (titleEl) {
|
||||||
// Label mit Summary
|
titleEl.textContent = willRetry
|
||||||
const parts = [];
|
? 'Fehlgeschlagen \u2014 erneuter Versuch in ' + delay + 's...'
|
||||||
if (data.new_articles > 0) {
|
: 'Fehlgeschlagen: ' + errorMsg;
|
||||||
parts.push(`${data.new_articles} neue Artikel`);
|
|
||||||
}
|
|
||||||
if (data.confirmed_count > 0) {
|
|
||||||
parts.push(`${data.confirmed_count} Fakten bestätigt`);
|
|
||||||
}
|
|
||||||
if (data.contradicted_count > 0) {
|
|
||||||
parts.push(`${data.contradicted_count} widerlegt`);
|
|
||||||
}
|
|
||||||
const summaryText = parts.length > 0 ? parts.join(', ') : 'Keine neuen Entwicklungen';
|
|
||||||
const label = document.getElementById('progress-label');
|
|
||||||
if (label) label.textContent = `Abgeschlossen: ${summaryText}`;
|
|
||||||
|
|
||||||
// Cancel-Button und Pass-Info ausblenden
|
|
||||||
const cancelBtn = document.getElementById('progress-cancel-btn');
|
|
||||||
if (cancelBtn) cancelBtn.style.display = 'none';
|
|
||||||
const passElDone = document.getElementById('progress-pass-info');
|
|
||||||
if (passElDone) passElDone.style.display = 'none';
|
|
||||||
|
|
||||||
bar.setAttribute('aria-valuenow', '100');
|
|
||||||
bar.setAttribute('aria-valuetext', 'Abgeschlossen');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fehler-Zustand: Roter Balken mit Fehlermeldung.
|
|
||||||
*/
|
|
||||||
showProgressError(errorMsg, willRetry = false, delay = 0) {
|
|
||||||
const bar = document.getElementById('progress-bar');
|
|
||||||
if (!bar) return;
|
|
||||||
bar.style.display = 'block';
|
|
||||||
|
|
||||||
// Timer stoppen
|
|
||||||
this._stopProgressTimer();
|
|
||||||
|
|
||||||
// Error-Klasse
|
|
||||||
bar.classList.remove('progress-bar--complete');
|
|
||||||
bar.classList.add('progress-bar--error');
|
|
||||||
|
|
||||||
const label = document.getElementById('progress-label');
|
|
||||||
if (label) {
|
|
||||||
label.textContent = willRetry
|
|
||||||
? `Fehlgeschlagen \u2014 erneuter Versuch in ${delay}s...`
|
|
||||||
: `Fehlgeschlagen: ${errorMsg}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel-Button ausblenden
|
|
||||||
const cancelBtn = document.getElementById('progress-cancel-btn');
|
const cancelBtn = document.getElementById('progress-cancel-btn');
|
||||||
if (cancelBtn) cancelBtn.style.display = 'none';
|
if (cancelBtn) cancelBtn.style.display = 'none';
|
||||||
|
|
||||||
// Bei finalem Fehler nach 6s ausblenden
|
|
||||||
if (!willRetry) {
|
if (!willRetry) {
|
||||||
setTimeout(() => this.hideProgress(), 6000);
|
this._removeSidebarRefreshStatus(incidentId);
|
||||||
|
setTimeout(() => this.hideProgress(incidentId), 6000);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
hideProgress(incidentId) {
|
||||||
* Timer-Intervall stoppen und zurücksetzen.
|
if (!incidentId) incidentId = App.currentIncidentId;
|
||||||
*/
|
|
||||||
_stopProgressTimer() {
|
// Remove blur
|
||||||
if (this._progressTimer) {
|
const grid = document.querySelector('.grid-stack');
|
||||||
clearInterval(this._progressTimer);
|
if (grid) grid.classList.remove('blurred');
|
||||||
this._progressTimer = null;
|
|
||||||
|
if (incidentId === App.currentIncidentId) {
|
||||||
|
const overlay = document.getElementById('progress-overlay');
|
||||||
|
if (overlay) { overlay.style.display = 'none'; overlay.classList.remove('blocking'); }
|
||||||
|
const mini = document.getElementById('progress-mini');
|
||||||
|
if (mini) mini.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove sidebar status
|
||||||
|
this._removeSidebarRefreshStatus(incidentId);
|
||||||
|
|
||||||
|
// Clean up state
|
||||||
|
delete this._progressState[incidentId];
|
||||||
|
|
||||||
|
// Stop timer if no more active refreshes
|
||||||
|
if (Object.keys(this._progressState).length === 0 && this._progressTimerInterval) {
|
||||||
|
clearInterval(this._progressTimerInterval);
|
||||||
|
this._progressTimerInterval = null;
|
||||||
}
|
}
|
||||||
this._progressStartTime = null;
|
|
||||||
const timerEl = document.getElementById('progress-timer');
|
|
||||||
if (timerEl) timerEl.textContent = '';
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
_tickProgressTimers() {
|
||||||
* Fortschrittsanzeige ausblenden.
|
for (const [id, state] of Object.entries(this._progressState)) {
|
||||||
*/
|
if (!state.startTime) continue;
|
||||||
hideProgress() {
|
const elapsed = Math.max(0, Math.floor((Date.now() - state.startTime) / 1000));
|
||||||
const bar = document.getElementById('progress-bar');
|
const mins = Math.floor(elapsed / 60);
|
||||||
if (bar) {
|
const secs = elapsed % 60;
|
||||||
bar.style.display = 'none';
|
const timeStr = mins + ':' + String(secs).padStart(2, '0');
|
||||||
bar.classList.remove('progress-bar--complete', 'progress-bar--error');
|
|
||||||
|
if (parseInt(id) === App.currentIncidentId) {
|
||||||
|
// Update popup timer
|
||||||
|
const timerEl = document.getElementById('progress-popup-timer');
|
||||||
|
if (timerEl) timerEl.textContent = timeStr;
|
||||||
|
// Update mini timer
|
||||||
|
const miniTimer = document.getElementById('progress-mini-timer');
|
||||||
|
if (miniTimer) miniTimer.textContent = timeStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update sidebar timer for this incident
|
||||||
|
const sidebarTimer = document.getElementById('sidebar-refresh-timer-' + id);
|
||||||
|
if (sidebarTimer) sidebarTimer.textContent = timeStr;
|
||||||
}
|
}
|
||||||
this._stopProgressTimer();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// === Sidebar Refresh Status ===
|
||||||
|
_updateSidebarRefreshStatus(incidentId, status, extra) {
|
||||||
|
const item = document.querySelector('.incident-item[data-id="' + incidentId + '"]');
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
// Add refreshing class for animated border
|
||||||
|
item.classList.add('refreshing-item');
|
||||||
|
|
||||||
|
// Add or update status text below meta
|
||||||
|
let statusEl = document.getElementById('sidebar-refresh-' + incidentId);
|
||||||
|
if (!statusEl) {
|
||||||
|
const textCol = item.querySelector('div[style*="flex:1"]');
|
||||||
|
if (!textCol) return;
|
||||||
|
statusEl = document.createElement('div');
|
||||||
|
statusEl.id = 'sidebar-refresh-' + incidentId;
|
||||||
|
statusEl.className = 'incident-refresh-status';
|
||||||
|
textCol.appendChild(statusEl);
|
||||||
|
}
|
||||||
|
const label = this._getStepLabel(status);
|
||||||
|
statusEl.innerHTML = '<span class="mini-spinner"></span><span>' + label + '</span><span id="sidebar-refresh-timer-' + incidentId + '" style="margin-left:auto;font-family:var(--font-mono,monospace);font-size:10px;color:var(--text-disabled);"></span>';
|
||||||
|
},
|
||||||
|
|
||||||
|
_removeSidebarRefreshStatus(incidentId) {
|
||||||
|
const statusEl = document.getElementById('sidebar-refresh-' + incidentId);
|
||||||
|
if (statusEl) statusEl.remove();
|
||||||
|
const item = document.querySelector('.incident-item[data-id="' + incidentId + '"]');
|
||||||
|
if (item) item.classList.remove('refreshing-item');
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zusammenfassung mit Inline-Zitaten und Quellenverzeichnis rendern.
|
* Zusammenfassung mit Inline-Zitaten und Quellenverzeichnis rendern.
|
||||||
*/
|
*/
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren