{
"tool_response": {
"filePath": "\/var\/www\/dev.campus.systemische-tools.de\/src\/View\/chat\/index.php",
"oldString": "\/\/ Form handling\ndocument.body.addEventListener('htmx:beforeRequest', (e) => {\n if (e.detail.elt === form) {\n sendBtn.disabled = true;\n sendBtn.classList.add('chat-send--loading');\n }\n});\n\ndocument.body.addEventListener('htmx:afterRequest', (e) => {\n if (e.detail.elt === form) {\n form.querySelector('input[name=\"message\"]').value = '';\n form.querySelector('input[name=\"message\"]').focus();\n sendBtn.disabled = false;\n sendBtn.classList.remove('chat-send--loading');\n messages.scrollTop = messages.scrollHeight;\n htmx.ajax('GET', '\/chat\/sessions?current=<?= $session['uuid'] ?? '' ?>', '#session-list');\n }\n});\n\ndocument.body.addEventListener('htmx:afterSwap', (e) => {\n if (e.detail.target.classList.contains('chat-messages__inner')) {\n messages.scrollTop = messages.scrollHeight;\n }\n});",
"newString": "\/\/ Streaming Form Handler with SSE\nform.addEventListener('submit', async (e) => {\n e.preventDefault();\n\n const messageInput = form.querySelector('input[name=\"message\"]');\n const question = messageInput.value.trim();\n if (!question) return;\n\n sendBtn.disabled = true;\n sendBtn.classList.add('chat-send--loading');\n\n const messagesInner = document.querySelector('#messages .chat-messages__inner');\n\n \/\/ Remove welcome if present\n const welcome = messagesInner.querySelector('.chat-welcome');\n if (welcome) welcome.remove();\n\n \/\/ Add user message immediately\n const userMsg = document.createElement('div');\n userMsg.className = 'chat-msg chat-msg--user';\n userMsg.innerHTML = '<div class=\"chat-msg__content\">' + escapeHtml(question) + '<\/div>';\n messagesInner.appendChild(userMsg);\n\n \/\/ Add progress container\n const progressContainer = document.createElement('div');\n progressContainer.className = 'chat-progress';\n progressContainer.innerHTML = '<div class=\"chat-progress__header\">Verarbeitung...<\/div><div class=\"chat-progress__log\"><\/div>';\n messagesInner.appendChild(progressContainer);\n const progressLog = progressContainer.querySelector('.chat-progress__log');\n\n messages.scrollTop = messages.scrollHeight;\n\n \/\/ Collect form data\n const formData = new FormData(form);\n\n \/\/ Use fetch with streaming\n try {\n const response = await fetch('\/chat\/<?= $session['uuid'] ?? '' ?>\/message\/stream', {\n method: 'POST',\n body: formData\n });\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const {value, done} = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, {stream: true});\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (line.startsWith('event: ')) {\n const eventType = line.substring(7);\n continue;\n }\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.substring(6));\n\n if (data.ts && data.msg) {\n \/\/ Progress event\n const logEntry = document.createElement('div');\n logEntry.className = 'chat-progress__entry';\n let duration = '';\n if (data.ms !== null) {\n duration = '<span class=\"chat-progress__duration\">' + data.ms + 'ms<\/span>';\n }\n logEntry.innerHTML = '<span class=\"chat-progress__time\">' + data.ts + '<\/span>' +\n '<span class=\"chat-progress__msg\">' + escapeHtml(data.msg) + '<\/span>' + duration;\n progressLog.appendChild(logEntry);\n messages.scrollTop = messages.scrollHeight;\n }\n\n if (data.html) {\n \/\/ Done event - replace progress with response\n \/\/ Remove user message (it's in the HTML)\n userMsg.remove();\n progressContainer.remove();\n messagesInner.insertAdjacentHTML('beforeend', data.html);\n messages.scrollTop = messages.scrollHeight;\n }\n\n if (data.error) {\n \/\/ Error event\n progressContainer.innerHTML = '<div class=\"chat-error\">' + escapeHtml(data.error) + '<\/div>';\n }\n } catch (e) {\n console.error('Parse error:', e, line);\n }\n }\n }\n }\n } catch (err) {\n progressContainer.innerHTML = '<div class=\"chat-error\">Verbindungsfehler: ' + escapeHtml(err.message) + '<\/div>';\n }\n\n messageInput.value = '';\n messageInput.focus();\n sendBtn.disabled = false;\n sendBtn.classList.remove('chat-send--loading');\n htmx.ajax('GET', '\/chat\/sessions?current=<?= $session['uuid'] ?? '' ?>', '#session-list');\n});\n\nfunction escapeHtml(text) {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}",
"originalFile": "<?php\n\/**\n * Chat - Redesign\n * Clean, minimalist, professional\n *\/\n$currentModel = $session['model'] ?? $defaultModel ?? 'claude-opus-4-5-20251101';\n$selected = json_decode($session['collections'] ?? '[\"documents\"]', true) ?: ['documents'];\n$currentLimit = (int) ($session['context_limit'] ?? 5);\n$currentProfileId = (int) ($session['author_profile_id'] ?? 0);\n$currentPromptId = (int) ($session['system_prompt_id'] ?? 1);\n$currentTemperature = (float) ($session['temperature'] ?? 0.5);\n$currentMaxTokens = (int) ($session['max_tokens'] ?? 4096);\n?>\n<!DOCTYPE html>\n<html lang=\"de\" data-theme=\"light\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title><?= htmlspecialchars($session['title'] ?? 'KI-Chat') ?> - Campus<\/title>\n <link rel=\"icon\" type=\"image\/png\" href=\"https:\/\/campus-am-see.de\/wp-content\/uploads\/menu-logo.png\">\n <link rel=\"stylesheet\" href=\"\/css\/chat-redesign.css\">\n <script src=\"\/js\/htmx.min.js\"><\/script>\n<\/head>\n<body>\n<div class=\"chat-layout\">\n <!-- Sidebar -->\n <aside class=\"chat-sidebar\" id=\"sidebar\">\n <div class=\"chat-sidebar__header\">\n <a href=\"\/chat\" class=\"chat-sidebar__new\">+ Neuer Chat<\/a>\n <button class=\"chat-sidebar__delete-all\" hx-delete=\"\/chat\" hx-confirm=\"Alle <?= count($sessions ?? []) ?> Chats löschen?\" title=\"Alle löschen\">× Alle<\/button>\n <\/div>\n <div class=\"chat-sidebar__list\" id=\"session-list\">\n <?php foreach ($sessions ?? [] as $s):\n $totalTokens = (int) ($s['total_input_tokens'] ?? 0) + (int) ($s['total_output_tokens'] ?? 0);\n $totalCost = ((int) ($s['total_input_tokens'] ?? 0) * 0.000015) + ((int) ($s['total_output_tokens'] ?? 0) * 0.000075);\n $isOllama = str_starts_with($s['model'] ?? '', 'ollama:');\n ?>\n <a href=\"\/chat\/<?= $s['uuid'] ?>\"\n class=\"chat-session <?= ($session['uuid'] ?? '') === $s['uuid'] ? 'chat-session--active' : '' ?>\"\n data-uuid=\"<?= $s['uuid'] ?>\">\n <div class=\"chat-session__title\" id=\"title-<?= $s['uuid'] ?>\"><?= htmlspecialchars($s['title'] ?? 'Neuer Chat') ?><\/div>\n <div class=\"chat-session__meta\">\n <span><?= $isOllama ? 'Lokal' : 'Claude' ?><\/span>\n <span><?= $s['message_count'] ?? 0 ?> Nachr.<\/span>\n <?php if (!$isOllama && $totalTokens > 0): ?>\n <span title=\"<?= number_format((int) $s['total_input_tokens']) ?> in \/ <?= number_format((int) $s['total_output_tokens']) ?> out\"><?= number_format($totalTokens) ?> Tok.<\/span>\n <span class=\"chat-session__cost\">~$<?= number_format($totalCost, 2) ?><\/span>\n <?php elseif ($isOllama): ?>\n <span class=\"chat-session__local\">lokal<\/span>\n <?php endif; ?>\n <\/div>\n <div class=\"chat-session__actions\">\n <button class=\"chat-session__edit\" onclick=\"event.preventDefault(); event.stopPropagation(); editTitle('<?= $s['uuid'] ?>');\" title=\"Bearbeiten\">✎<\/button>\n <button class=\"chat-session__delete\" hx-delete=\"\/chat\/<?= $s['uuid'] ?>\" hx-confirm=\"Session löschen?\" onclick=\"event.preventDefault(); event.stopPropagation();\">×<\/button>\n <\/div>\n <\/a>\n <?php endforeach; ?>\n <?php if (empty($sessions)): ?>\n <div class=\"chat-session chat-session--empty\">Keine Sessions<\/div>\n <?php endif; ?>\n <\/div>\n <\/aside>\n\n <div class=\"chat-overlay\" id=\"overlay\"><\/div>\n\n <!-- Main -->\n <main class=\"chat-main\">\n <!-- Header -->\n <header class=\"chat-header\">\n <button class=\"chat-toggle\" id=\"toggle\" title=\"Sidebar\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M3 12h18M3 6h18M3 18h18\"\/><\/svg>\n <\/button>\n <a href=\"\/\" class=\"chat-header__logo\" title=\"Zur Startseite\">\n <img src=\"https:\/\/campus-am-see.de\/wp-content\/uploads\/menu-logo.png\" alt=\"Campus\">\n <\/a>\n <div class=\"chat-header__title\">\n <h1 id=\"page-title\"><?= htmlspecialchars($session['title'] ?? 'KI-Chat') ?><\/h1>\n <\/div>\n <\/header>\n\n <!-- Messages -->\n <div class=\"chat-messages\" id=\"messages\">\n <div class=\"chat-messages__inner\">\n <?php if (empty($messages)): ?>\n <div class=\"chat-welcome\">\n <img src=\"https:\/\/campus-am-see.de\/wp-content\/uploads\/menu-logo.png\" alt=\"Campus am See\" class=\"chat-welcome__logo\">\n <h2>Campus am See KI Assistent<\/h2>\n <\/div>\n <?php endif; ?>\n\n <?php foreach ($messages ?? [] as $msg): ?>\n <div class=\"chat-msg chat-msg--<?= $msg['role'] ?>\">\n <div class=\"chat-msg__content\">\n <?php if ($msg['role'] === 'user'): ?>\n <?= htmlspecialchars($msg['content']) ?>\n <?php else: ?>\n <?= nl2br(htmlspecialchars($msg['content'])) ?>\n\n <?php if (!empty($msg['sources'])): ?>\n <?php $sources = json_decode($msg['sources'], true) ?: []; ?>\n <?php if (!empty($sources)): ?>\n <div class=\"chat-sources\" id=\"sources-<?= $msg['id'] ?>\">\n <button type=\"button\" class=\"chat-sources__toggle\" onclick=\"this.parentElement.classList.toggle('chat-sources--open')\">\n <?= count($sources) ?> Quelle<?= count($sources) > 1 ? 'n' : '' ?> ▾\n <\/button>\n <div class=\"chat-sources__list\">\n <?php foreach ($sources as $source): ?>\n <div class=\"chat-source\">\n <div class=\"chat-source__header\">\n <?php if (!empty($source['collection'])): ?>\n <span class=\"chat-source__collection\">[<?= htmlspecialchars($source['collection']) ?>]<\/span>\n <?php endif; ?>\n <span class=\"chat-source__title\"><?= htmlspecialchars($source['title'] ?? 'Unbekannt') ?><\/span>\n <span class=\"chat-source__score\"><?= round(($source['score'] ?? 0) * 100) ?>%<\/span>\n <\/div>\n <?php if (!empty($source['content'])): ?>\n <div class=\"chat-source__content\">\"<?= htmlspecialchars(mb_substr($source['content'], 0, 200)) ?><?= mb_strlen($source['content']) > 200 ? '...' : '' ?>\"<\/div>\n <?php endif; ?>\n <\/div>\n <?php endforeach; ?>\n <\/div>\n <\/div>\n <?php endif; ?>\n <?php endif; ?>\n\n <?php\n $inputTokens = (int) ($msg['tokens_input'] ?? 0);\n $outputTokens = (int) ($msg['tokens_output'] ?? 0);\n $msgCost = ($inputTokens * 0.000015) + ($outputTokens * 0.000075);\n $msgModel = $msg['model'] ?? 'claude-opus-4-5-20251101';\n $msgIsOllama = str_starts_with($msgModel, 'ollama:');\n ?>\n <div class=\"chat-msg__meta\">\n <span><?= $msgIsOllama ? substr($msgModel, 7) : $msgModel ?><\/span>\n <?php if (!$msgIsOllama && ($inputTokens > 0 || $outputTokens > 0)): ?>\n <span class=\"chat-msg__tokens\">↓<?= number_format($inputTokens) ?> ↑<?= number_format($outputTokens) ?><\/span>\n <span class=\"chat-msg__cost\">~$<?= number_format($msgCost, 4) ?><\/span>\n <?php elseif ($msgIsOllama): ?>\n <span class=\"chat-msg__local\">lokal<\/span>\n <?php endif; ?>\n <\/div>\n <?php endif; ?>\n <\/div>\n <\/div>\n <?php endforeach; ?>\n <\/div>\n <\/div>\n\n <!-- Input Area -->\n <div class=\"chat-input-area\">\n <div class=\"chat-input-wrapper\">\n <form class=\"chat-form\" id=\"chatForm\"\n hx-post=\"\/chat\/<?= $session['uuid'] ?? '' ?>\/message\"\n hx-target=\"#messages .chat-messages__inner\"\n hx-swap=\"beforeend\">\n\n <div class=\"chat-input-row\">\n <input type=\"text\" name=\"message\" class=\"chat-input\" placeholder=\"Nachricht eingeben...\" autocomplete=\"off\" required>\n <button type=\"submit\" class=\"chat-send\" id=\"sendBtn\">\n <span class=\"chat-send__text\">Senden<\/span>\n <span class=\"chat-send__loading\"><span><\/span><span><\/span><span><\/span><\/span>\n <\/button>\n <\/div>\n\n <!-- Settings Row 1 -->\n <div class=\"chat-settings\" id=\"settings\">\n <select name=\"model\" class=\"chat-select\">\n <optgroup label=\"Anthropic\">\n <?php foreach ($models ?? [] as $modelId => $modelLabel): ?>\n <?php if (!str_starts_with($modelId, 'ollama:')): ?>\n ... [TRUNCATED-f780364339695fa7]",
"structuredPatch": [
{
"oldStart": 360,
"oldLines": 31,
"newStart": 360,
"newLines": 119,
"lines": [
" });",
" });",
" ",
"-\/\/ Form handling",
"-document.body.addEventListener('htmx:beforeRequest', (e) => {",
"- if (e.detail.elt === form) {",
"- sendBtn.disabled = true;",
"- sendBtn.classList.add('chat-send--loading');",
"- }",
"-});",
"+\/\/ Streaming Form Handler with SSE",
"+form.addEventListener('submit', async (e) => {",
"+ e.preventDefault();",
" ",
"-document.body.addEventListener('htmx:afterRequest', (e) => {",
"- if (e.detail.elt === form) {",
"- form.querySelector('input[name=\"message\"]').value = '';",
"- form.querySelector('input[name=\"message\"]').focus();",
"- sendBtn.disabled = false;",
"- sendBtn.classList.remove('chat-send--loading');",
"- messages.scrollTop = messages.scrollHeight;",
"- htmx.ajax('GET', '\/chat\/sessions?current=<?= $session['uuid'] ?? '' ?>', '#session-list');",
"- }",
"-});",
"+ const messageInput = form.querySelector('input[name=\"message\"]');",
"+ const question = messageInput.value.trim();",
"+ if (!question) return;",
" ",
"-document.body.addEventListener('htmx:afterSwap', (e) => {",
"- if (e.detail.target.classList.contains('chat-messages__inner')) {",
"- messages.scrollTop = messages.scrollHeight;",
"+ sendBtn.disabled = true;",
"+ sendBtn.classList.add('chat-send--loading');",
"+",
"+ const messagesInner = document.querySelector('#messages .chat-messages__inner');",
"+",
"+ \/\/ Remove welcome if present",
"+ const welcome = messagesInner.querySelector('.chat-welcome');",
"+ if (welcome) welcome.remove();",
"+",
"+ \/\/ Add user message immediately",
"+ const userMsg = document.createElement('div');",
"+ userMsg.className = 'chat-msg chat-msg--user';",
"+ userMsg.innerHTML = '<div class=\"chat-msg__content\">' + escapeHtml(question) + '<\/div>';",
"+ messagesInner.appendChild(userMsg);",
"+",
"+ \/\/ Add progress container",
"+ const progressContainer = document.createElement('div');",
"+ progressContainer.className = 'chat-progress';",
"+ progressContainer.innerHTML = '<div class=\"chat-progress__header\">Verarbeitung...<\/div><div class=\"chat-progress__log\"><\/div>';",
"+ messagesInner.appendChild(progressContainer);",
"+ const progressLog = progressContainer.querySelector('.chat-progress__log');",
"+",
"+ messages.scrollTop = messages.scrollHeight;",
"+",
"+ \/\/ Collect form data",
"+ const formData = new FormData(form);",
"+",
"+ \/\/ Use fetch with streaming",
"+ try {",
"+ const response = await fetch('\/chat\/<?= $session['uuid'] ?? '' ?>\/message\/stream', {",
"+ method: 'POST',",
"+ body: formData",
"+ });",
"+",
"+ const reader = response.body.getReader();",
"+ const decoder = new TextDecoder();",
"+ let buffer = '';",
"+",
"+ while (true) {",
"+ const {value, done} = await reader.read();",
"+ if (done) break;",
"+",
"+ buffer += decoder.decode(value, {stream: true});",
"+ const lines = buffer.split('\\n');",
"+ buffer = lines.pop() || '';",
"+",
"+ for (const line of lines) {",
"+ if (line.startsWith('event: ')) {",
"+ const eventType = line.substring(7);",
"+ continue;",
"+ }",
"+ if (line.startsWith('data: ')) {",
"+ try {",
"+ const data = JSON.parse(line.substring(6));",
"+",
"+ if (data.ts && data.msg) {",
"+ \/\/ Progress event",
"+ const logEntry = document.createElement('div');",
"+ logEntry.className = 'chat-progress__entry';",
"+ let duration = '';",
"+ if (data.ms !== null) {",
"+ duration = '<span class=\"chat-progress__duration\">' + data.ms + 'ms<\/span>';",
"+ }",
"+ logEntry.innerHTML = '<span class=\"chat-progress__time\">' + data.ts + '<\/span>' +",
"+ '<span class=\"chat-progress__msg\">' + escapeHtml(data.msg) + '<\/span>' + duration;",
"+ progressLog.appendChild(logEntry);",
"+ messages.scrollTop = messages.scrollHeight;",
"+ }",
"+",
"+ if (data.html) {",
"+ \/\/ Done event - replace progress with response",
"+ \/\/ Remove user message (it's in the HTML)",
"+ userMsg.remove();",
"+ progressContainer.remove();",
"+ messagesInner.insertAdjacentHTML('beforeend', data.html);",
"+ messages.scrollTop = messages.scrollHeight;",
"+ }",
"+",
"+ if (data.error) {",
"+ \/\/ Error event",
"+ progressContainer.innerHTML = '<div class=\"chat-error\">' + escapeHtml(data.error) + '<\/div>';",
"+ }",
"+ } catch (e) {",
"+ console.error('Parse error:', e, line);",
"+ }",
"+ }",
"+ }",
"+ }",
"+ } catch (err) {",
"+ progressContainer.innerHTML = '<div class=\"chat-error\">Verbindungsfehler: ' + escapeHtml(err.message) + '<\/div>';",
" }",
"+",
"+ messageInput.value = '';",
"+ messageInput.focus();",
"+ sendBtn.disabled = false;",
"+ sendBtn.classList.remove('chat-send--loading');",
"+ htmx.ajax('GET', '\/chat\/sessions?current=<?= $session['uuid'] ?? '' ?>', '#session-list');",
" });",
" ",
"+function escapeHtml(text) {",
"+ const div = document.createElement('div');",
"+ div.textContent = text;",
"+ return div.innerHTML;",
"+}",
"+",
" \/\/ Scroll to bottom on load",
" messages.scrollTop = messages.scrollHeight;",
" "
]
}
],
"userModified": false,
"replaceAll": false
}
}