From 3cb468f4e8f349ba145d0f950c4a2f672f6a1c1e Mon Sep 17 00:00:00 2001 From: Meleki Date: Sat, 28 Feb 2026 11:53:50 +0100 Subject: [PATCH] Chat: User-Nachrichten [Datei:]-Tags als klickbare Chips rendern, System-Prompt mit Workspace-Pfad und Leerzeilen-Hinweis erweitern --- Endpoints/ChatEndpoint.cs | 19 ++++++++++++++++++- TODO.md | 3 ++- wwwroot/css/chat.css | 18 ++++++++++++++++++ wwwroot/js/history.js | 2 +- wwwroot/js/ui.js | 37 ++++++++++++++++++++++++++++++++++++- 5 files changed, 75 insertions(+), 4 deletions(-) diff --git a/Endpoints/ChatEndpoint.cs b/Endpoints/ChatEndpoint.cs index f4c96da..d08afef 100644 --- a/Endpoints/ChatEndpoint.cs +++ b/Endpoints/ChatEndpoint.cs @@ -78,15 +78,32 @@ public static class ChatEndpoint var promptParts = new List(); promptParts.Add($"Du bist {agent.Name}."); + if (!string.IsNullOrWhiteSpace(agent.Persona)) + { promptParts.Add($"## Persona\n{agent.Persona}"); + } + if (!string.IsNullOrWhiteSpace(agent.Instructions)) + { promptParts.Add($"## Instructions\n{agent.Instructions}"); + } if (!string.IsNullOrWhiteSpace(session.Workspace)) - promptParts.Add("## Tools\nDu hast Tools zur Verfügung (read_file, write_file, edit_file, list_files, delete_file, execute). Benutze sie um Dateien zu lesen, schreiben und bearbeiten. Beschreibe NICHT was du tun würdest - tu es einfach über die Tools."); + { + promptParts.Add(@$"## Tools +Du hast Tools zur Verfügung (read_file, write_file, edit_file, list_files, delete_file, execute). Benutze sie um Dateien zu lesen, schreiben und bearbeiten. + +Der Workspace-Pfad ist: `{session.Workspace}` +Alle Dateipfade in Tool-Aufrufen müssen relativ zu diesem Workspace sein oder absolute Pfade verwenden, die mit diesem Pfad beginnen. + +Wenn der User auf eine bestimmte Zeile in einer Datei verweist (z.B. `[Datei: foo.txt:11]`), zähle Leerzeilen immer mit. Verwende `cat -n` oder ein ähnliches Tool um Zeilennummern exakt zu bestimmen - NIEMALS Leerzeilen beim Zählen überspringen."); + } + if (!string.IsNullOrWhiteSpace(session.Context)) + { promptParts.Add($"## Kontext\n{session.Context}"); + } var systemPromptText = string.Join("\n\n", promptParts); diff --git a/TODO.md b/TODO.md index d827ebb..c22d7da 100644 --- a/TODO.md +++ b/TODO.md @@ -12,4 +12,5 @@ ## Frontend - [ ] Chat: Es scrollt beim Laden nicht ganz nach unten, ich denke weil im DOM noch etwas nachgeladen wird (ich denke es ist die Schriftart), und die Höhe sich beim Laden nochmal verändert -- [ ] Chat: User-Nachrichten mit dem Tag [Datei: pfat/zur/datei.xyz:1-10] wird nicht als HTML geparsed +- [x] Chat: User-Nachrichten mit dem Tag [Datei: pfat/zur/datei.xyz:1-10] wird nicht als HTML geparsed +- [ ] Chat: Wird zwar erkannt und ist klickbar, aber es öffnet sich einfach nur der File Explorer, ohne dass sich die Datei öffnet und die Darstellung in Monospace ist auch nicht schön diff --git a/wwwroot/css/chat.css b/wwwroot/css/chat.css index a9892f1..685d916 100644 --- a/wwwroot/css/chat.css +++ b/wwwroot/css/chat.css @@ -328,6 +328,24 @@ opacity: 1; } +/* File Ref Chips in User-Bubbles */ + +.file-ref-chip { + display: inline-block; + padding: var(--badge-padding); + background: var(--f2-bg); + border: var(--border-width) solid var(--f2-border); + border-radius: var(--border-radius); + font-family: var(--mono); + font-size: .85em; + white-space: nowrap; +} + +.file-ref-chip:hover { + border-color: var(--f3-border); + background: var(--f3-bg); +} + /* Attachments in History */ .attachment-indicator { diff --git a/wwwroot/js/history.js b/wwwroot/js/history.js index 564e233..7e40085 100644 --- a/wwwroot/js/history.js +++ b/wwwroot/js/history.js @@ -112,7 +112,7 @@ async function loadLatestArchive() { case 'text': if (msg.role === 'user') { div.className = 'msg user archived'; - div.textContent = (msg.content || '').trim(); + renderUserContent(div, msg.content); } else { div.className = 'msg assistant markdown archived'; div.innerHTML = renderMarkdown(msg.content); diff --git a/wwwroot/js/ui.js b/wwwroot/js/ui.js index 7da3989..7ace95d 100644 --- a/wwwroot/js/ui.js +++ b/wwwroot/js/ui.js @@ -30,11 +30,46 @@ function truncate(s, max) { // ── Message Bubbles ── +function renderUserContent(container, text) { + const raw = (text || '').trim(); + // [Datei: path] oder [Datei: path:1-10] erkennen + const pattern = /\[Datei: ([^\]]+)\]/g; + let last = 0; + let match; + const frag = document.createDocumentFragment(); + + while ((match = pattern.exec(raw)) !== null) { + // Text vor dem Tag + if (match.index > last) { + frag.appendChild(document.createTextNode(raw.slice(last, match.index))); + } + + const ref = match[1]; // z.B. "src/foo.cs:10-25" + const chip = document.createElement('span'); + chip.className = 'file-ref-chip'; + chip.textContent = match[0]; + chip.title = ref; + chip.style.cursor = 'pointer'; + chip.addEventListener('click', () => { + openFileExplorer({ sessionId: getCurrentSessionId() }); + }); + frag.appendChild(chip); + last = match.index + match[0].length; + } + + // Resttext + if (last < raw.length) { + frag.appendChild(document.createTextNode(raw.slice(last))); + } + + container.appendChild(frag); +} + function addUserBubble(text, streamed = false) { const div = document.createElement('div'); div.className = 'msg user'; if (streamed) div.classList.add('streamed'); - div.textContent = (text || '').trim(); + renderUserContent(div, text); chatEl().appendChild(div); scrollDown(); }