{
"event": "PreToolUse",
"tool_name": "Edit",
"tool_input": {
"file_path": "\/var\/www\/dev.campus.systemische-tools.de\/src\/View\/content-pipeline\/run-status.php",
"old_string": " \/\/ ETA calculation state\n let lastProgress = 0;\n let progressHistory = [];",
"new_string": " \/\/ ETA calculation state\n let progressHistory = [];"
}
}
{
"tool_response": {
"filePath": "\/var\/www\/dev.campus.systemische-tools.de\/src\/View\/content-pipeline\/run-status.php",
"oldString": " \/\/ ETA calculation state\n let lastProgress = 0;\n let progressHistory = [];",
"newString": " \/\/ ETA calculation state\n let progressHistory = [];",
"originalFile": "<?php ob_start(); ?>\n\n<h1>Pipeline Status<\/h1>\n<p class=\"subtitle\"><?= htmlspecialchars($pipeline['name']) ?> → Run #<?= $run['id'] ?><\/p>\n\n<div class=\"status-page\"\n id=\"status-container\"\n data-poll-url=\"\/content-pipeline\/<?= $pipeline['id'] ?>\/run\/<?= $run['id'] ?>\/poll\">\n\n <!-- Status Badge + Cancel -->\n <div class=\"status-header\">\n <span class=\"badge badge--large\" id=\"status-badge\" data-status=\"<?= $run['status'] ?>\">\n <?= $run['status'] ?>\n <\/span>\n <form action=\"\/content-pipeline\/<?= $pipeline['id'] ?>\/run\/<?= $run['id'] ?>\/cancel\" method=\"POST\" id=\"cancel-form\" class=\"status-header__cancel\">\n <input type=\"hidden\" name=\"_csrf_token\" value=\"<?= htmlspecialchars($_SESSION['_csrf_token'] ?? '') ?>\">\n <button type=\"submit\" class=\"btn btn--danger btn--small\" id=\"cancel-btn\" <?= $run['status'] !== 'running' ? 'disabled' : '' ?>>\n Abbrechen\n <\/button>\n <\/form>\n <span class=\"stall-warning\" id=\"stall-warning\" style=\"display: none;\">\n Keine Aktivitaet seit 60s\n <\/span>\n <\/div>\n\n <!-- Status Info -->\n <ul class=\"status-info\">\n <li><span class=\"status-info__label\">Schritt:<\/span> <span class=\"status-info__value\" id=\"current-step\"><?= htmlspecialchars($run['current_step'] ?? 'Initialisierung') ?><\/span><\/li>\n <li><span class=\"status-info__label\">Dokument:<\/span> <span class=\"status-info__value\" id=\"current-document\"><?= htmlspecialchars($run['current_document'] ?? '-') ?><\/span><\/li>\n <li><span class=\"status-info__label\">Zeit:<\/span> <span class=\"status-info__value\" id=\"elapsed-time\">00:00<\/span><\/li>\n <li><span class=\"status-info__label\">ETA:<\/span> <span class=\"status-info__value\" id=\"eta-time\">-<\/span><\/li>\n <\/ul>\n\n <!-- Stats -->\n <div class=\"stats-row\">\n <div class=\"stat-box\">\n <div class=\"stat-box__value\" id=\"chunks-count\">0<\/div>\n <div class=\"stat-box__label\">Chunks<\/div>\n <\/div>\n <div class=\"stat-box\">\n <div class=\"stat-box__value\" id=\"embeddings-count\">0<\/div>\n <div class=\"stat-box__label\">Embeddings<\/div>\n <\/div>\n <div class=\"stat-box stat-box--danger\">\n <div class=\"stat-box__value\" id=\"failed-count\">0<\/div>\n <div class=\"stat-box__label\">Fehler<\/div>\n <\/div>\n <\/div>\n\n <!-- Live Log -->\n <div class=\"log-section\">\n <h3>Live Log<\/h3>\n <pre class=\"log-output\" id=\"log-output\"><?= htmlspecialchars($run['log_tail'] ?? 'Warte auf Ausgabe...') ?><\/pre>\n <\/div>\n\n <!-- Error Display -->\n <div class=\"error-section\" id=\"error-section\" style=\"display: <?= $run['error_log'] ? 'block' : 'none' ?>;\">\n <h3>Fehler<\/h3>\n <pre class=\"error-output\" id=\"error-output\"><?= htmlspecialchars($run['error_log'] ?? '') ?><\/pre>\n <\/div>\n\n<\/div>\n\n<style>\n.subtitle {\n color: var(--text-muted, #666);\n margin-top: -0.5rem;\n margin-bottom: 1.5rem;\n}\n\n.status-page {\n max-width: 800px;\n}\n\n.status-header {\n display: flex;\n align-items: center;\n gap: 1rem;\n margin-bottom: 1.5rem;\n}\n\n.badge--large {\n font-size: 1rem;\n padding: 0.5rem 1rem;\n text-transform: uppercase;\n}\n\n.badge[data-status=\"running\"] {\n background: var(--info-color, #0066cc);\n color: white;\n animation: pulse 2s infinite;\n}\n\n.badge[data-status=\"completed\"] {\n background: var(--success-color, #28a745);\n color: white;\n}\n\n.badge[data-status=\"failed\"] {\n background: var(--danger-color, #dc3545);\n color: white;\n}\n\n.badge[data-status=\"cancelled\"] {\n background: var(--warning-color, #ffc107);\n color: #333;\n}\n\n@keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.7; }\n}\n\n.stall-warning {\n background: var(--warning-bg, #fff3cd);\n color: var(--warning-color, #856404);\n padding: 0.25rem 0.5rem;\n border-radius: 4px;\n font-size: 0.85rem;\n animation: blink 1s infinite;\n}\n\n@keyframes blink {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n}\n\n.status-info {\n list-style: none;\n padding: 0;\n margin: 0 0 1.5rem 0;\n background: var(--card-bg, #fff);\n border: 1px solid var(--border-color, #ddd);\n border-radius: 8px;\n padding: 1rem;\n}\n\n.status-info li {\n display: flex;\n padding: 0.5rem 0;\n border-bottom: 1px solid var(--border-color, #eee);\n}\n\n.status-info li:last-child {\n border-bottom: none;\n}\n\n.status-info__label {\n font-weight: 500;\n color: var(--text-muted, #666);\n min-width: 100px;\n}\n\n.status-info__value {\n flex: 1;\n font-family: monospace;\n}\n\n.stats-row {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1rem;\n margin-bottom: 1.5rem;\n}\n\n.stat-box {\n background: var(--bg-muted, #f5f5f5);\n border-radius: 8px;\n padding: 1rem;\n text-align: center;\n}\n\n.stat-box--danger .stat-box__value {\n color: var(--danger-color, #dc3545);\n}\n\n.stat-box__value {\n font-size: 1.5rem;\n font-weight: bold;\n}\n\n.stat-box__label {\n font-size: 0.8rem;\n color: var(--text-muted, #666);\n}\n\n.log-section, .error-section {\n margin-bottom: 1.5rem;\n}\n\n.log-section h3, .error-section h3 {\n margin-bottom: 0.5rem;\n font-size: 1rem;\n}\n\n.log-output {\n background: var(--bg-dark, #1e1e1e);\n color: var(--text-light, #d4d4d4);\n padding: 1rem;\n border-radius: 6px;\n font-family: monospace;\n font-size: 0.8rem;\n max-height: 300px;\n overflow-y: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.error-output {\n background: var(--danger-bg, #f8d7da);\n color: var(--danger-color, #721c24);\n padding: 1rem;\n border-radius: 6px;\n font-family: monospace;\n font-size: 0.8rem;\n max-height: 200px;\n overflow-y: auto;\n white-space: pre-wrap;\n}\n\n.status-header__cancel {\n margin: 0;\n}\n\n@media (max-width: 600px) {\n .status-cards, .stats-row {\n grid-template-columns: 1fr;\n }\n}\n<\/style>\n\n<script>\n(function() {\n const container = document.getElementById('status-container');\n const pollUrl = container.dataset.pollUrl;\n let pollInterval = null;\n let isTerminal = false;\n\n \/\/ ETA calculation state\n let lastProgress = 0;\n let progressHistory = [];\n const startTime = Date.now();\n\n function calculateETA(data) {\n \/\/ Calculate progress based on documents processed\n const total = data.documents_total || 1;\n const processed = data.documents_processed || 0;\n const progress = processed \/ total;\n\n \/\/ Track progress changes for rate calculation\n const now = Date.now();\n progressHistory.push({ time: now, progress: progress });\n\n \/\/ Keep only last 10 data points\n if (progressHistory.length > 10) {\n progressHistory.shift();\n }\n\n \/\/ Need at least 2 points and some progress\n if (progressHistory.length < 2 || progress <= 0) {\n return '-';\n }\n\n \/\/ Calculate average rate over recent history\n const oldest = progressHistory[0];\n const newest = progressHistory[progressHistory.length - 1];\n const timeDiff = (newest.time - oldest.time) \/ 1000; \/\/ seconds\n const progressDiff = newest.progress - oldest.progress;\n\n if (timeDiff <= 0 || progressDiff <= 0) {\n return '-';\n }\n\n const rate = progressDiff \/ timeDiff; \/\/ progress per second\n const remaining = 1 - progress;\n const etaSeconds = remaining \/ rate;\n\n if (etaSeconds > 3600) {\n return Math.round(etaSeconds \/ 3600) + 'h ' + Math.round((etaSeconds % 3600) \/ 60) + 'min';\n } else if (etaSeconds > 60) {\n return Math.round(etaSeconds \/ 60) + 'min';\n } else {\n return Math.round(etaSeconds) + 's';\n }\n }\n\n function updateUI(data) {\n \/\/ Status Badge\n const badge = document.getElementById('status-badge');\n badge.textContent = data.status;\n badge.dataset.status = data.status;\n\n \/\/ Stall Warning\n document.getElementById('stall-warning').style.display = data.is_stalled ? 'inline' : 'none';\n\n \/\/ Current Step & Document\n document.getElementById('current-step').textContent = data.current_step || 'Warte...';\n document.getElementById('current-document').textContent = data.current_document || '-';\n\n \/\/ Time\n document.getElementById('elapsed-time').textContent = data.elapsed_formatted || '00:00';\n\n \/\/ ETA calculation\n const eta = calculateETA(data);\n document.getElementById('eta-time').textContent = eta;\n\n \/\/ Stats\n document.getElementById('chunks-count').textContent = data.chunks_created;\n document.getElementById('embeddings-count').textContent = data.embeddings_created;\n document.getElementById('failed-count').textContent = data.documents_failed;\n\n \/\/ Log\n if (data.log_tail) {\n const logEl = document.getElementById('log-output');\n logEl.textContent = data.log_tail;\n logEl.scrollTop = logEl.scrollHeight;\n }\n\n \/\/ Error\n if (data.error_log) {\n document.getElementById('error-section').style.display = 'block';\n document.getElementById('error-output').textContent = data.error_log;\n }\n\n \/\/ Cancel Button\n document.getElementById('cancel-btn').disabled = data.is_terminal;\n\n \/\/ Stop polling if terminal\n if (data.is_terminal && !isTerminal) {\n isTerminal = true;\n stopPolling();\n }\n }\n\n function poll() {\n fetch(pollUrl)\n .then(response => response.json())\n .then(data => {\n updateUI(data);\n })\n .catch(error => {\n console.error('Polling error:', error);\n });\n }\n\n function startPolling() {\n poll(); \/\/ Initial poll\n pollInterval = setInterval(poll, 2000); \/\/ Poll every 2s\n }\n\n function stop... [TRUNCATED-fd1c42d327597d4f]",
"structuredPatch": [
{
"oldStart": 237,
"oldLines": 7,
"newStart": 237,
"newLines": 6,
"lines": [
" let isTerminal = false;",
" ",
" \/\/ ETA calculation state",
"- let lastProgress = 0;",
" let progressHistory = [];",
" const startTime = Date.now();",
" "
]
}
],
"userModified": false,
"replaceAll": false
}
}