Skip to content

Commit f3a858c

Browse files
committed
feat: Enable per-conversation loading states
1 parent 3a2bdcd commit f3a858c

File tree

5 files changed

+260
-158
lines changed

5 files changed

+260
-158
lines changed

tools/server/webui/src/lib/components/app/chat/ChatProcessingInfo.svelte

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,31 @@
22
import { PROCESSING_INFO_TIMEOUT } from '$lib/constants/processing-info';
33
import { useProcessingState } from '$lib/hooks/use-processing-state.svelte';
44
import { slotsService } from '$lib/services/slots';
5-
import { isLoading, activeMessages, activeConversation } from '$lib/stores/chat.svelte';
5+
import {
6+
isConversationLoading,
7+
activeMessages,
8+
activeConversation
9+
} from '$lib/stores/chat.svelte';
610
import { config } from '$lib/stores/settings.svelte';
711
812
const processingState = useProcessingState();
913
1014
let processingDetails = $derived(processingState.getProcessingDetails());
1115
12-
let showSlotsInfo = $derived(isLoading() || config().keepStatsVisible);
16+
let isCurrentConversationLoading = $derived(
17+
activeConversation() ? isConversationLoading(activeConversation()!.id) : false
18+
);
19+
20+
let showSlotsInfo = $derived(isCurrentConversationLoading || config().keepStatsVisible);
1321
1422
$effect(() => {
1523
const keepStatsVisible = config().keepStatsVisible;
1624
17-
if (keepStatsVisible || isLoading()) {
25+
if (keepStatsVisible || isCurrentConversationLoading) {
1826
processingState.startMonitoring();
1927
}
2028
21-
if (!isLoading() && !keepStatsVisible) {
29+
if (!isCurrentConversationLoading && !keepStatsVisible) {
2230
setTimeout(() => {
2331
if (!config().keepStatsVisible) {
2432
processingState.stopMonitoring();

tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
activeConversation,
2424
deleteConversation,
2525
isLoading,
26+
isConversationLoading,
2627
sendMessage,
2728
stopGeneration,
2829
setMaxContextError
@@ -81,6 +82,10 @@
8182
8283
let isServerLoading = $derived(serverLoading());
8384
85+
let isCurrentConversationLoading = $derived(
86+
activeConversation() ? isConversationLoading(activeConversation()!.id) : false
87+
);
88+
8489
async function handleDeleteConfirm() {
8590
const conversation = activeConversation();
8691
if (conversation) {
@@ -261,7 +266,7 @@
261266
});
262267
263268
$effect(() => {
264-
if (isLoading() && autoScrollEnabled) {
269+
if (isCurrentConversationLoading && autoScrollEnabled) {
265270
scrollInterval = setInterval(scrollChatToBottom, AUTO_SCROLL_INTERVAL);
266271
} else if (scrollInterval) {
267272
clearInterval(scrollInterval);
@@ -312,7 +317,7 @@
312317

313318
<div class="conversation-chat-form pointer-events-auto rounded-t-3xl pb-4">
314319
<ChatForm
315-
isLoading={isLoading()}
320+
isLoading={isCurrentConversationLoading}
316321
onFileRemove={handleFileRemove}
317322
onFileUpload={handleFileUpload}
318323
onSend={handleSendMessage}
@@ -355,7 +360,7 @@
355360

356361
<div in:fly={{ y: 10, duration: 250, delay: 300 }}>
357362
<ChatForm
358-
isLoading={isLoading()}
363+
isLoading={isCurrentConversationLoading}
359364
onFileRemove={handleFileRemove}
360365
onFileUpload={handleFileUpload}
361366
onSend={handleSendMessage}

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

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { slotsService } from './slots';
3030
* - Request lifecycle management (abort, cleanup)
3131
*/
3232
export class ChatService {
33-
private abortController: AbortController | null = null;
33+
private abortControllers: Map<string, AbortController> = new Map();
3434

3535
/**
3636
* Sends a chat completion request to the llama.cpp server.
@@ -44,7 +44,8 @@ export class ChatService {
4444
*/
4545
async sendMessage(
4646
messages: ApiChatMessageData[] | (DatabaseMessage & { extra?: DatabaseMessageExtra[] })[],
47-
options: SettingsChatServiceOptions = {}
47+
options: SettingsChatServiceOptions = {},
48+
conversationId?: string
4849
): Promise<string | void> {
4950
const {
5051
stream,
@@ -78,9 +79,17 @@ export class ChatService {
7879
timings_per_token
7980
} = options;
8081

81-
// Cancel any ongoing request and create a new abort controller
82-
this.abort();
83-
this.abortController = new AbortController();
82+
// Create or get abort controller for this conversation
83+
const requestId = conversationId || 'default';
84+
85+
// Cancel any existing request for this conversation
86+
if (this.abortControllers.has(requestId)) {
87+
this.abortControllers.get(requestId)?.abort();
88+
}
89+
90+
// Create new abort controller for this conversation
91+
const abortController = new AbortController();
92+
this.abortControllers.set(requestId, abortController);
8493

8594
// Convert database messages with attachments to API format if needed
8695
const normalizedMessages: ApiChatMessageData[] = messages
@@ -171,7 +180,7 @@ export class ChatService {
171180
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {})
172181
},
173182
body: JSON.stringify(requestBody),
174-
signal: this.abortController.signal
183+
signal: abortController.signal
175184
});
176185

177186
if (!response.ok) {
@@ -223,6 +232,8 @@ export class ChatService {
223232
onError(userFriendlyError);
224233
}
225234
throw userFriendlyError;
235+
} finally {
236+
this.abortControllers.delete(requestId);
226237
}
227238
}
228239

@@ -603,10 +614,20 @@ export class ChatService {
603614
*
604615
* @public
605616
*/
606-
public abort(): void {
607-
if (this.abortController) {
608-
this.abortController.abort();
609-
this.abortController = null;
617+
public abort(conversationId?: string): void {
618+
if (conversationId) {
619+
const abortController = this.abortControllers.get(conversationId);
620+
621+
if (abortController) {
622+
abortController.abort();
623+
this.abortControllers.delete(conversationId);
624+
}
625+
} else {
626+
for (const controller of this.abortControllers.values()) {
627+
controller.abort();
628+
}
629+
630+
this.abortControllers.clear();
610631
}
611632
}
612633

0 commit comments

Comments
 (0)