From f6eef3a7f6b2cc0138a7b800e6340815ccb7638c Mon Sep 17 00:00:00 2001 From: shahondin1624 Date: Thu, 12 Mar 2026 11:29:10 +0100 Subject: [PATCH] feat: connect chat UI to gRPC-Web streaming with loading indicator - Wire processRequest() async generator to chat page - Progressive message rendering as stream chunks arrive - Animated loading dots while waiting for first chunk - Error display with OrchestratorError code mapping - Session ID management with crypto.randomUUID() Closes #7 Co-Authored-By: Claude Opus 4.6 --- implementation-plans/_index.md | 1 + implementation-plans/issue-007.md | 16 ++++++++ src/routes/chat/+page.svelte | 61 ++++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 implementation-plans/issue-007.md diff --git a/implementation-plans/_index.md b/implementation-plans/_index.md index 0279062..97bd704 100644 --- a/implementation-plans/_index.md +++ b/implementation-plans/_index.md @@ -8,3 +8,4 @@ | #4 | gRPC-Web client service layer | COMPLETED | [issue-004.md](issue-004.md) | | #5 | Chat page layout and message list component | COMPLETED | [issue-005.md](issue-005.md) | | #6 | Message input with send and keyboard shortcuts | COMPLETED | [issue-006.md](issue-006.md) | +| #7 | Streaming response rendering | COMPLETED | [issue-007.md](issue-007.md) | diff --git a/implementation-plans/issue-007.md b/implementation-plans/issue-007.md new file mode 100644 index 0000000..01054f0 --- /dev/null +++ b/implementation-plans/issue-007.md @@ -0,0 +1,16 @@ +--- +--- + +# Issue #7: Streaming response rendering + +**Status:** COMPLETED +**Issue:** https://git.shahondin1624.de/llm-multiverse/llm-multiverse-ui/issues/7 +**Branch:** `feature/issue-7-streaming-response` + +## Acceptance Criteria + +- [x] Chat UI connected to gRPC-Web client service from #4 +- [x] Streaming responses rendered in real-time as chunks arrive +- [x] `message` field displayed progressively +- [x] Handles stream completion and errors gracefully +- [x] Loading indicator shown while waiting for first chunk diff --git a/src/routes/chat/+page.svelte b/src/routes/chat/+page.svelte index e5c1496..92bb49f 100644 --- a/src/routes/chat/+page.svelte +++ b/src/routes/chat/+page.svelte @@ -2,11 +2,16 @@ import type { ChatMessage } from '$lib/types'; import MessageList from '$lib/components/MessageList.svelte'; import MessageInput from '$lib/components/MessageInput.svelte'; + import { processRequest, OrchestratorError } from '$lib/services/orchestrator'; let messages: ChatMessage[] = $state([]); let isStreaming = $state(false); + let error: string | null = $state(null); + let sessionId = $state(crypto.randomUUID()); + + async function handleSend(content: string) { + error = null; - function handleSend(content: string) { const userMessage: ChatMessage = { id: crypto.randomUUID(), role: 'user', @@ -14,6 +19,40 @@ timestamp: new Date() }; messages.push(userMessage); + + const assistantMessage: ChatMessage = { + id: crypto.randomUUID(), + role: 'assistant', + content: '', + timestamp: new Date() + }; + messages.push(assistantMessage); + + isStreaming = true; + + try { + for await (const response of processRequest(sessionId, content)) { + const idx = messages.length - 1; + messages[idx] = { + ...messages[idx], + content: response.message + }; + } + } catch (err) { + const msg = + err instanceof OrchestratorError + ? `Error (${err.code}): ${err.message}` + : 'An unexpected error occurred'; + error = msg; + // Update the assistant message with the error + const idx = messages.length - 1; + messages[idx] = { + ...messages[idx], + content: `⚠ ${msg}` + }; + } finally { + isStreaming = false; + } } @@ -23,5 +62,25 @@ + + {#if isStreaming && messages.length > 0 && messages[messages.length - 1].content === ''} +
+
+ + + +
+
+ {/if} + + {#if error} +
+ {error} +
+ {/if} +