Skip to content

Commit 761e432

Browse files
committed
feat: Adds loading indicator to conversation items
1 parent d754b2c commit 761e432

File tree

3 files changed

+23
-16
lines changed

3 files changed

+23
-16
lines changed

tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebarConversationItem.svelte

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts">
2-
import { Trash2, Pencil, MoreHorizontal, Download } from '@lucide/svelte';
2+
import { Trash2, Pencil, MoreHorizontal, Download, Loader2 } from '@lucide/svelte';
33
import { ActionDropdown } from '$lib/components/app';
4-
import { downloadConversation } from '$lib/stores/chat.svelte';
4+
import { downloadConversation, getAllLoadingConversations } from '$lib/stores/chat.svelte';
55
import { onMount } from 'svelte';
66
77
interface Props {
@@ -25,6 +25,9 @@
2525
let renderActionsDropdown = $state(false);
2626
let dropdownOpen = $state(false);
2727
28+
// Track loading state reactively by checking if conversation ID is in loading conversations array
29+
let isLoading = $derived(getAllLoadingConversations().includes(conversation.id));
30+
2831
function handleEdit(event: Event) {
2932
event.stopPropagation();
3033
onEdit?.(conversation.id);
@@ -83,11 +86,16 @@
8386
onmouseover={handleMouseOver}
8487
onmouseleave={handleMouseLeave}
8588
>
86-
<!-- svelte-ignore a11y_click_events_have_key_events -->
87-
<!-- svelte-ignore a11y_no_static_element_interactions -->
88-
<span class="truncate text-sm font-medium" onclick={handleMobileSidebarItemClick}>
89-
{conversation.name}
90-
</span>
89+
<div class="flex min-w-0 flex-1 items-center gap-2">
90+
{#if isLoading}
91+
<Loader2 class="h-3.5 w-3.5 shrink-0 animate-spin text-muted-foreground" />
92+
{/if}
93+
<!-- svelte-ignore a11y_click_events_have_key_events -->
94+
<!-- svelte-ignore a11y_no_static_element_interactions -->
95+
<span class="truncate text-sm font-medium" onclick={handleMobileSidebarItemClick}>
96+
{conversation.name}
97+
</span>
98+
</div>
9199

92100
{#if renderActionsDropdown}
93101
<div class="actions flex items-center">

tools/server/webui/src/lib/services/slots.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ export class SlotsService {
288288
return conversationState;
289289
}
290290
}
291-
291+
292292
// Fallback to global state
293293
if (this.lastKnownState) {
294294
return this.lastKnownState;

tools/server/webui/src/lib/stores/chat.svelte.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { filterByLeafNodeId, findLeafNode, findDescendantMessages } from '$lib/u
66
import { browser } from '$app/environment';
77
import { goto } from '$app/navigation';
88
import { toast } from 'svelte-sonner';
9+
import { SvelteMap } from 'svelte/reactivity';
910
import type { ExportedConversations } from '$lib/types/database';
1011

1112
/**
@@ -51,10 +52,8 @@ class ChatStore {
5152
isInitialized = $state(false);
5253
isLoading = $state(false);
5354
// Track loading and streaming state per conversation
54-
conversationLoadingStates = $state<Map<string, boolean>>(new Map());
55-
conversationStreamingStates = $state<Map<string, { response: string; messageId: string }>>(
56-
new Map()
57-
);
55+
conversationLoadingStates = new SvelteMap<string, boolean>();
56+
conversationStreamingStates = new SvelteMap<string, { response: string; messageId: string }>();
5857
titleUpdateConfirmationCallback?: (currentTitle: string, newTitle: string) => Promise<boolean>;
5958

6059
constructor() {
@@ -471,7 +470,7 @@ class ChatStore {
471470

472471
// Update database with the message's conversation ID (not activeConversation which may have changed)
473472
await DatabaseStore.updateCurrentNode(assistantMessage.convId, assistantMessage.id);
474-
473+
475474
// Only update activeConversation.currNode if this is still the active conversation
476475
if (this.activeConversation?.id === assistantMessage.convId) {
477476
this.activeConversation.currNode = assistantMessage.id;
@@ -631,7 +630,7 @@ class ChatStore {
631630
}
632631

633632
this.errorDialogState = null;
634-
633+
635634
// Set loading state for this specific conversation
636635
this.setConversationLoading(this.activeConversation.id, true);
637636
this.clearConversationStreaming(this.activeConversation.id);
@@ -1284,12 +1283,12 @@ class ChatStore {
12841283
clearActiveConversation(): void {
12851284
this.activeConversation = null;
12861285
this.activeMessages = [];
1287-
1286+
12881287
// Clear global UI state since there's no active conversation
12891288
// Background streaming will continue but won't affect the UI
12901289
this.isLoading = false;
12911290
this.currentResponse = '';
1292-
1291+
12931292
// Clear active conversation in slots service
12941293
slotsService.setActiveConversation(null);
12951294
}

0 commit comments

Comments
 (0)