diff --git a/wwwroot/css/style.css b/wwwroot/css/style.css
new file mode 100644
index 0000000..01a2538
--- /dev/null
+++ b/wwwroot/css/style.css
@@ -0,0 +1,206 @@
+* { margin: 0; padding: 0; box-sizing: border-box; }
+
+body {
+ font-family: monospace;
+ background: #1a1a2e;
+ color: #e0e0e0;
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+}
+
+/* ── Header ── */
+
+header {
+ padding: 12px 20px;
+ border-bottom: 1px solid #333;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-shrink: 0;
+}
+
+header h1 {
+ color: #7c83ff;
+ font-size: 1.2em;
+}
+
+select {
+ font-family: monospace;
+ padding: 6px 10px;
+ border: 1px solid #333;
+ border-radius: 4px;
+ background: #16213e;
+ color: #e0e0e0;
+}
+
+.clear-btn {
+ padding: 6px 10px;
+ background: #333;
+ color: #aaa;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-family: monospace;
+ font-size: 0.85em;
+}
+
+.clear-btn:hover { background: #444; }
+
+.system-toggle {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-left: auto;
+}
+
+.system-toggle label {
+ font-size: 0.85em;
+ color: #888;
+ cursor: pointer;
+}
+
+#systemPrompt {
+ display: none;
+ width: calc(100% - 40px);
+ font-family: monospace;
+ padding: 8px;
+ border: 1px solid #333;
+ border-radius: 4px;
+ background: #0a0a1a;
+ color: #e0e0e0;
+ resize: vertical;
+ font-size: 12px;
+ min-height: 40px;
+ margin: 8px 20px 0;
+}
+
+#systemPrompt.visible { display: block; }
+
+/* ── Meta Bar ── */
+
+.meta-bar {
+ padding: 4px 20px;
+ background: #0a0a1a;
+ border-bottom: 1px solid #222;
+ font-size: 0.8em;
+ color: #666;
+ flex-shrink: 0;
+ min-height: 24px;
+}
+
+/* ── Chat ── */
+
+#chat {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.msg {
+ max-width: 85%;
+ padding: 10px 14px;
+ border-radius: 8px;
+ white-space: pre-wrap;
+ word-break: break-word;
+ line-height: 1.5;
+}
+
+.msg.user {
+ align-self: flex-end;
+ background: #16213e;
+ border: 1px solid #333;
+}
+
+.msg.assistant {
+ align-self: flex-start;
+ background: #0f3460;
+ border: 1px solid #1a4a7a;
+}
+
+.msg.system-info {
+ align-self: center;
+ background: #1a1a2e;
+ border: 1px solid #333;
+ color: #888;
+ font-size: 0.85em;
+ text-align: center;
+ max-width: 95%;
+}
+
+/* ── Cursor ── */
+
+.msg .cursor {
+ display: inline-block;
+ width: 2px;
+ height: 1em;
+ background: #7c83ff;
+ animation: blink 0.8s infinite;
+ vertical-align: text-bottom;
+ margin-left: 1px;
+}
+
+@keyframes blink {
+ 0%, 50% { opacity: 1; }
+ 51%, 100% { opacity: 0; }
+}
+
+/* ── Tool Boxes ── */
+
+.tool-box {
+ align-self: center;
+ max-width: 95%;
+ padding: 8px 12px;
+ border-radius: 6px;
+ background: #0a1e3d;
+ border: 1px solid #1a3a5c;
+ font-size: 0.85em;
+}
+
+.tool-name { color: #7c83ff; font-weight: bold; }
+.tool-input { color: #888; margin-left: 6px; font-size: 0.85em; }
+.tool-result { color: #6bff8a; font-size: 0.8em; margin-top: 4px; max-height: 100px; overflow-y: auto; }
+.tool-error { color: #ff6b6b; }
+.tool-spinner { color: #7c83ff; }
+
+/* ── Input Area ── */
+
+.input-area {
+ padding: 12px 20px;
+ border-top: 1px solid #333;
+ display: flex;
+ gap: 8px;
+ flex-shrink: 0;
+}
+
+.input-area textarea {
+ flex: 1;
+ font-family: monospace;
+ padding: 10px;
+ border: 1px solid #333;
+ border-radius: 4px;
+ background: #0f3460;
+ color: #e0e0e0;
+ resize: none;
+ font-size: 14px;
+ min-height: 60px;
+ max-height: 150px;
+}
+
+.input-area button {
+ padding: 10px 20px;
+ background: #7c83ff;
+ color: #fff;
+ border: none;
+ border-radius: 4px;
+ font-family: monospace;
+ font-weight: bold;
+ cursor: pointer;
+ align-self: flex-end;
+}
+
+.input-area button:hover { background: #5a63e0; }
+.input-area button:disabled { opacity: 0.5; cursor: not-allowed; }
diff --git a/wwwroot/index.html b/wwwroot/index.html
index 260c7a3..12150c6 100644
--- a/wwwroot/index.html
+++ b/wwwroot/index.html
@@ -1,41 +1,10 @@
-
-
-Gott - Debug Console
-
+
+
+ Gott - Debug Console
+
@@ -45,276 +14,26 @@ select { font-family: monospace; padding: 6px 10px; border: 1px solid #333; bord
-
+
-
+
+
-
-
+
-
+
+
+
+
+
diff --git a/wwwroot/js/api.js b/wwwroot/js/api.js
new file mode 100644
index 0000000..d36a71c
--- /dev/null
+++ b/wwwroot/js/api.js
@@ -0,0 +1,14 @@
+// ── API Calls ──
+
+async function apiChat(model, messages, systemPrompt) {
+ return fetch('/api/chat', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ model, messages, systemPrompt })
+ });
+}
+
+async function apiHistory(sessionId = 'null', limit = 50) {
+ const resp = await fetch(`/api/history?sessionId=${sessionId}&limit=${limit}`);
+ return resp.json();
+}
diff --git a/wwwroot/js/app.js b/wwwroot/js/app.js
new file mode 100644
index 0000000..2694ffc
--- /dev/null
+++ b/wwwroot/js/app.js
@@ -0,0 +1,28 @@
+// ── App Init ──
+
+document.addEventListener('DOMContentLoaded', () => {
+ // System Prompt Toggle
+ $('#showSystem').addEventListener('change', () => {
+ $('#systemPrompt').classList.toggle('visible', $('#showSystem').checked);
+ });
+
+ // Send Button
+ $('#send').addEventListener('click', sendMessage);
+
+ // Ctrl+Enter
+ $('#input').addEventListener('keydown', (e) => {
+ if (e.ctrlKey && e.key === 'Enter') sendMessage();
+ });
+
+ // Auto-Resize Textarea
+ $('#input').addEventListener('input', function () {
+ this.style.height = 'auto';
+ this.style.height = Math.min(this.scrollHeight, 150) + 'px';
+ });
+
+ // Clear Button
+ $('#clearBtn').addEventListener('click', clearChat);
+
+ // History laden
+ loadHistory();
+});
diff --git a/wwwroot/js/chat.js b/wwwroot/js/chat.js
new file mode 100644
index 0000000..f3fb416
--- /dev/null
+++ b/wwwroot/js/chat.js
@@ -0,0 +1,108 @@
+// ── Chat State ──
+
+const messages = [];
+
+function clearChat() {
+ messages.length = 0;
+ chatEl().innerHTML = '';
+ setMeta('');
+}
+
+// ── Send Message ──
+
+async function sendMessage() {
+ const input = $('#input');
+ const btn = $('#send');
+ const model = $('#model').value;
+ const systemPrompt = $('#systemPrompt').value.trim();
+ const text = input.value.trim();
+ if (!text) return;
+
+ messages.push({ role: 'user', content: text });
+ addUserBubble(text);
+
+ input.value = '';
+ input.style.height = 'auto';
+ btn.disabled = true;
+ setMetaHtml('⏳ Streaming...');
+
+ let fullText = '';
+ let currentToolBox = null;
+
+ try {
+ const resp = await apiChat(model, messages, systemPrompt);
+ const reader = resp.body.getReader();
+ const decoder = new TextDecoder();
+ let buffer = '';
+ let eventType = null;
+
+ while (true) {
+ const { done, value } = 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: ')) {
+ eventType = line.substring(7).trim();
+ } else if (line.startsWith('data: ') && eventType) {
+ let data;
+ try { data = JSON.parse(line.substring(6)); } catch { continue; }
+
+ switch (eventType) {
+ case 'text_start':
+ endStreamBubble();
+ startStreamBubble();
+ break;
+
+ case 'text_delta':
+ appendToStream(data.text);
+ fullText += data.text;
+ break;
+
+ case 'text_end':
+ endStreamBubble();
+ break;
+
+ case 'tool_start':
+ endStreamBubble();
+ currentToolBox = addToolBoxLoading(data.name);
+ break;
+
+ case 'tool_end':
+ finishToolBox(currentToolBox, data);
+ currentToolBox = null;
+ break;
+
+ case 'error':
+ endStreamBubble();
+ addErrorBubble(data.error);
+ break;
+
+ case 'done':
+ endStreamBubble();
+ let meta = `Model: ${data.model || model} | In: ${data.inputTokens || 0} | Out: ${data.outputTokens || 0}`;
+ if (data.iterations > 1) meta += ` | Iterations: ${data.iterations}`;
+ if (data.durationMs) meta += ` | ${(data.durationMs / 1000).toFixed(1)}s`;
+ setMeta(meta);
+ break;
+ }
+ eventType = null;
+ }
+ }
+ }
+
+ if (fullText) {
+ messages.push({ role: 'assistant', content: fullText });
+ }
+
+ } catch (e) {
+ endStreamBubble();
+ addErrorBubble('Netzwerk-Fehler: ' + e.message);
+ } finally {
+ btn.disabled = false;
+ endStreamBubble();
+ }
+}
diff --git a/wwwroot/js/history.js b/wwwroot/js/history.js
new file mode 100644
index 0000000..3f02b3b
--- /dev/null
+++ b/wwwroot/js/history.js
@@ -0,0 +1,40 @@
+// ── History Loading ──
+
+async function loadHistory() {
+ try {
+ const data = await apiHistory('null');
+ data.reverse(); // DESC → chronologisch
+
+ for (const entry of data) {
+ for (const msg of entry.messages) {
+ switch (msg.type) {
+ case 'text':
+ if (msg.role === 'user') {
+ addUserBubble(msg.content);
+ messages.push({ role: 'user', content: msg.content });
+ } else if (msg.role === 'assistant') {
+ addAssistantBubble(msg.content);
+ messages.push({ role: 'assistant', content: msg.content });
+ }
+ break;
+
+ case 'tool_use':
+ addToolBoxStatic(msg.toolName, msg.content, null, false);
+ break;
+
+ case 'tool_result':
+ addToolBoxStatic(null, null, msg.content, msg.isError);
+ break;
+
+ case 'error':
+ addErrorBubble(msg.content);
+ break;
+ }
+ }
+ }
+
+ scrollDown();
+ } catch (e) {
+ console.error('History laden fehlgeschlagen:', e);
+ }
+}
diff --git a/wwwroot/js/ui.js b/wwwroot/js/ui.js
new file mode 100644
index 0000000..2136379
--- /dev/null
+++ b/wwwroot/js/ui.js
@@ -0,0 +1,125 @@
+// ── DOM Helpers ──
+
+const $ = (sel) => document.querySelector(sel);
+const chatEl = () => $('#chat');
+const metaEl = () => $('#meta');
+
+function scrollDown() {
+ const chat = chatEl();
+ chat.scrollTop = chat.scrollHeight;
+}
+
+function esc(s) {
+ const d = document.createElement('div');
+ d.textContent = s;
+ return d.innerHTML;
+}
+
+function truncate(s, max) {
+ return s && s.length > max ? s.substring(0, max) + '...' : (s || '');
+}
+
+// ── Message Bubbles ──
+
+function addUserBubble(text) {
+ const div = document.createElement('div');
+ div.className = 'msg user';
+ div.textContent = text;
+ chatEl().appendChild(div);
+ scrollDown();
+}
+
+function addAssistantBubble(text) {
+ const div = document.createElement('div');
+ div.className = 'msg assistant';
+ div.textContent = text;
+ chatEl().appendChild(div);
+ scrollDown();
+ return div;
+}
+
+function addErrorBubble(text) {
+ const div = document.createElement('div');
+ div.className = 'msg system-info';
+ div.style.color = '#ff6b6b';
+ div.textContent = text;
+ chatEl().appendChild(div);
+ scrollDown();
+}
+
+// ── Streaming Bubble ──
+
+let _streamDiv = null;
+let _streamText = '';
+
+function startStreamBubble() {
+ _streamDiv = document.createElement('div');
+ _streamDiv.className = 'msg assistant';
+ _streamDiv.innerHTML = '';
+ _streamText = '';
+ chatEl().appendChild(_streamDiv);
+ scrollDown();
+}
+
+function appendToStream(text) {
+ if (!_streamDiv) startStreamBubble();
+ _streamText += text;
+ _streamDiv.textContent = _streamText;
+ const cursor = document.createElement('span');
+ cursor.className = 'cursor';
+ _streamDiv.appendChild(cursor);
+ scrollDown();
+}
+
+function endStreamBubble() {
+ if (_streamDiv) {
+ const cursor = _streamDiv.querySelector('.cursor');
+ if (cursor) cursor.remove();
+ if (!_streamText.trim()) _streamDiv.remove();
+ }
+ _streamDiv = null;
+ _streamText = '';
+}
+
+// ── Tool Boxes ──
+
+function addToolBoxLoading(name) {
+ const div = document.createElement('div');
+ div.className = 'tool-box';
+ div.innerHTML = '' + esc(name) + ' ⏳';
+ chatEl().appendChild(div);
+ scrollDown();
+ return div;
+}
+
+function finishToolBox(div, data) {
+ if (!div) return;
+ div.innerHTML = '' + esc(data.name) + ''
+ + '' + esc(truncate(data.input, 120)) + ''
+ + ''
+ + esc(truncate(data.result, 300)) + '
';
+ scrollDown();
+}
+
+function addToolBoxStatic(name, input, result, isError) {
+ const div = document.createElement('div');
+ div.className = 'tool-box';
+ div.innerHTML = '' + esc(name) + ''
+ + '' + esc(truncate(input, 120)) + '';
+ if (result) {
+ div.innerHTML += ''
+ + esc(truncate(result, 300)) + '
';
+ }
+ chatEl().appendChild(div);
+ scrollDown();
+}
+
+// ── Meta Bar ──
+
+function setMeta(text) {
+ metaEl().textContent = text;
+}
+
+function setMetaHtml(html) {
+ metaEl().innerHTML = html;
+}