fix: improve chat UI streaming feedback and fix test image path
All checks were successful
continuous-integration/drone/push Build is passing

- Add loading spinner with 'Thinking...' text during streaming when
  content is not yet available
- Fix :key binding to use message.id instead of message.content
- Remove unnecessary TypeScript cast in file reader handler
- Move test image into testdata/ directory for proper test organization
- Remove t.Skip and simplify TestSummarizeChat test message
This commit is contained in:
2026-05-01 21:04:26 -04:00
parent 2154a9f203
commit 0dd9521419
3 changed files with 16 additions and 7 deletions

View File

@@ -66,8 +66,6 @@ func TestSendMessage(t *testing.T) {
} }
func TestSummarizeChat(t *testing.T) { func TestSummarizeChat(t *testing.T) {
t.Skip("requires live LLM API - run manually with: go test -run TestSummarizeChat ./internal/client/")
// Initialize Client // Initialize Client
baseURL, err := url.Parse("https://llm-api.va.reichard.io/v1") baseURL, err := url.Parse("https://llm-api.va.reichard.io/v1")
if err != nil { if err != nil {
@@ -80,8 +78,7 @@ func TestSummarizeChat(t *testing.T) {
defer cancel() defer cancel()
// Generate Text Stream // Generate Text Stream
userMessage := "Write me a go program that reads in a zip file and prints the contents along with their sizes and mimetype." output, err := client.CreateTitle(ctx, "Hi!", model)
output, err := client.CreateTitle(ctx, userMessage, model)
if err != nil { if err != nil {
t.Fatalf("Failed to generate text stream: %v", err) t.Fatalf("Failed to generate text stream: %v", err)
} }
@@ -108,7 +105,7 @@ func TestSendMessageWithImage(t *testing.T) {
defer cancel() defer cancel()
// Load Test Image and Convert to Base64 Data URL // Load Test Image and Convert to Base64 Data URL
imgData, err := os.ReadFile("test_image.jpg") imgData, err := os.ReadFile("./testdata/test_image.jpg")
if err != nil { if err != nil {
t.Fatalf("Failed to read test image: %v", err) t.Fatalf("Failed to read test image: %v", err)
} }

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -3,7 +3,7 @@
<div <div
class="h-dvh pt-16 flex flex-col-reverse pb-36 overflow-scroll mx-auto px-4 md:px-6 max-w-6xl" class="h-dvh pt-16 flex flex-col-reverse pb-36 overflow-scroll mx-auto px-4 md:px-6 max-w-6xl"
> >
<template x-for="message in currentChatMessages" :key="message.content"> <template x-for="message in currentChatMessages" :key="message.id">
<div <div
:class="['mb-4', message.role === 'user' ? 'text-right' : 'text-left']" :class="['mb-4', message.role === 'user' ? 'text-right' : 'text-left']"
> >
@@ -50,8 +50,20 @@
<hr x-show="message.thinking" class="my-2 border-primary-400/50" /> <hr x-show="message.thinking" class="my-2 border-primary-400/50" />
<!-- Loading Spinner (Streaming with no content yet) -->
<div
x-show="message.role === 'assistant' && message.status === 'streaming' && !message.thinking && !message.content"
class="flex items-center gap-2 py-1"
>
<div
class="h-4 w-4 animate-spin rounded-full border-2 border-primary-500 border-t-transparent"
></div>
<span class="text-xs text-primary-600">Thinking...</span>
</div>
<!-- Main Content --> <!-- Main Content -->
<div <div
x-show="message.content || message.status !== 'streaming'"
class="prose max-w-none text-sm prose-p:my-1 prose-headings:my-2 prose-ul:my-1 prose-ol:my-1 prose-pre:p-0" class="prose max-w-none text-sm prose-p:my-1 prose-headings:my-2 prose-ul:my-1 prose-ol:my-1 prose-pre:p-0"
x-html="renderMarkdown(message.content)" x-html="renderMarkdown(message.content)"
></div> ></div>
@@ -224,7 +236,7 @@
@change=" @change="
Array.from($event.target.files).forEach(file => { Array.from($event.target.files).forEach(file => {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (e) => { selectedImages.push(e.target.result as string); }; reader.onload = (e) => { selectedImages.push(e.target.result); };
reader.readAsDataURL(file); reader.readAsDataURL(file);
}); });
$event.target.value = ''; $event.target.value = '';