From 9d837efae69093c93781e38a354118dc58493682 Mon Sep 17 00:00:00 2001 From: Eddy Date: Tue, 14 Apr 2026 12:44:50 +0200 Subject: [PATCH] =?UTF-8?q?Phase=207:=20UI-Verbesserungen=20=E2=80=94=20Co?= =?UTF-8?q?de-Copy,=20Edit,=20Regenerate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Code-Blöcke mit Copy-Button (📋) und Sprach-Label - Nachrichten bearbeiten (✏️) mit Speichern & Senden - Antwort regenerieren (🔄) für letzte Assistant-Nachricht - Custom marked-Renderer für Code-Block-Wrapper - MutationObserver für Streaming-kompatible Copy-Buttons Co-Authored-By: Claude Opus 4.5 --- src/lib/components/ChatPanel.svelte | 365 +++++++++++++++++++++++++++- src/lib/components/CodeBlock.svelte | 96 ++++++++ 2 files changed, 450 insertions(+), 11 deletions(-) create mode 100644 src/lib/components/CodeBlock.svelte diff --git a/src/lib/components/ChatPanel.svelte b/src/lib/components/ChatPanel.svelte index 67b8e58..efa55ef 100644 --- a/src/lib/components/ChatPanel.svelte +++ b/src/lib/components/ChatPanel.svelte @@ -1,11 +1,22 @@
@@ -102,7 +273,7 @@ {$messages.length} Nachrichten
-
+
{#if $messages.length === 0}
🤖
@@ -110,8 +281,8 @@

Enter = Senden, Shift+Enter = Neue Zeile, Escape = Stopp

{:else} - {#each $messages as message} -
+ {#each $messages as message, index} +
{#if message.role === 'user'} @@ -122,12 +293,33 @@ ⚙️ System {/if} - - {message.timestamp.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })} - +
+ {#if message.role === 'user' && !$isProcessing && editingMessageId !== message.id} + + {/if} + {#if message.role === 'assistant' && !$isProcessing && isLastAssistantMessage(index) && message.content} + + {/if} + + {message.timestamp.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })} + +
- {#if message.role === 'assistant'} + {#if editingMessageId === message.id} + +
+ + +
+ {:else if message.role === 'assistant'} {@html renderMarkdown(message.content)} {:else} {message.content} @@ -268,11 +460,101 @@ font-weight: 600; } + .message-actions { + display: flex; + align-items: center; + gap: var(--spacing-xs); + } + + .action-btn { + padding: 0.15rem 0.3rem; + background: transparent; + border: none; + border-radius: var(--radius-sm); + cursor: pointer; + opacity: 0; + transition: all 0.15s ease; + font-size: 0.7rem; + } + + .message:hover .action-btn { + opacity: 0.6; + } + + .action-btn:hover { + opacity: 1 !important; + background: var(--bg-tertiary); + } + .message-time { font-size: 0.6rem; color: var(--text-secondary); } + /* Edit-Modus */ + .message.editing { + border: 1px solid var(--accent); + } + + .edit-textarea { + width: 100%; + min-height: 60px; + padding: var(--spacing-sm); + font-size: 0.85rem; + line-height: 1.5; + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + resize: vertical; + font-family: inherit; + } + + .edit-textarea:focus { + outline: none; + border-color: var(--accent); + } + + .edit-actions { + display: flex; + justify-content: flex-end; + gap: var(--spacing-sm); + margin-top: var(--spacing-xs); + } + + .edit-btn { + padding: 0.3rem 0.75rem; + font-size: 0.75rem; + border-radius: var(--radius-sm); + cursor: pointer; + transition: all 0.15s ease; + } + + .edit-btn.cancel { + background: transparent; + border: 1px solid var(--border); + color: var(--text-secondary); + } + + .edit-btn.cancel:hover { + background: var(--bg-tertiary); + color: var(--text-primary); + } + + .edit-btn.confirm { + background: var(--accent); + border: 1px solid var(--accent); + color: white; + } + + .edit-btn.confirm:hover:not(:disabled) { + background: var(--accent-hover); + } + + .edit-btn.confirm:disabled { + opacity: 0.5; + cursor: not-allowed; + } + .message-content { font-size: 0.85rem; line-height: 1.6; @@ -299,7 +581,68 @@ border-radius: var(--radius-sm); } - .message-content :global(pre) { + /* Code-Block mit Header und Copy-Button */ + .message-content :global(.code-block-wrapper) { + margin: 0.5em 0; + border-radius: var(--radius-md); + background: var(--bg-tertiary); + overflow: hidden; + } + + .message-content :global(.code-header) { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.3rem 0.75rem; + background: rgba(0, 0, 0, 0.15); + font-size: 0.65rem; + gap: 0.5rem; + } + + .message-content :global(.code-lang) { + color: var(--text-secondary); + font-family: var(--font-mono); + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .message-content :global(.copy-btn) { + margin-left: auto; + padding: 0.2rem 0.5rem; + background: transparent; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--text-secondary); + font-size: 0.7rem; + cursor: pointer; + transition: all 0.15s ease; + } + + .message-content :global(.copy-btn:hover) { + background: var(--bg-secondary); + color: var(--text-primary); + border-color: var(--text-secondary); + } + + .message-content :global(.copy-btn .copied) { + color: var(--success); + } + + .message-content :global(.code-block-wrapper pre) { + margin: 0; + padding: var(--spacing-sm) var(--spacing-md); + overflow-x: auto; + font-size: 0.75rem; + line-height: 1.5; + } + + .message-content :global(.code-block-wrapper pre code) { + padding: 0; + background: none; + } + + /* Fallback für inline pre (ohne wrapper) */ + .message-content :global(pre:not(.code-block-wrapper pre)) { margin: 0.5em 0; padding: var(--spacing-sm) var(--spacing-md); background: var(--bg-tertiary); @@ -309,7 +652,7 @@ line-height: 1.4; } - .message-content :global(pre code) { + .message-content :global(pre:not(.code-block-wrapper pre) code) { padding: 0; background: none; } diff --git a/src/lib/components/CodeBlock.svelte b/src/lib/components/CodeBlock.svelte new file mode 100644 index 0000000..fcabe4f --- /dev/null +++ b/src/lib/components/CodeBlock.svelte @@ -0,0 +1,96 @@ + + +
+
+ {#if language} + {language} + {/if} + +
+
{code}
+
+ +