refactor: Chat requests abort handling
This commit is contained in:
parent
42483f463d
commit
456828b365
|
|
@ -54,11 +54,9 @@ import type { SettingsChatServiceOptions } from '$lib/types/settings';
|
|||
* - Streaming response handling with real-time callbacks
|
||||
* - Reasoning content extraction and processing
|
||||
* - File attachment processing (images, PDFs, audio, text)
|
||||
* - Request lifecycle management (abort, cleanup)
|
||||
* - Request lifecycle management (abort via AbortSignal)
|
||||
*/
|
||||
export class ChatService {
|
||||
private abortControllers: Map<string, AbortController> = new Map();
|
||||
|
||||
/**
|
||||
* Sends a chat completion request to the llama.cpp server.
|
||||
* Supports both streaming and non-streaming responses with comprehensive parameter configuration.
|
||||
|
|
@ -72,7 +70,8 @@ export class ChatService {
|
|||
async sendMessage(
|
||||
messages: ApiChatMessageData[] | (DatabaseMessage & { extra?: DatabaseMessageExtra[] })[],
|
||||
options: SettingsChatServiceOptions = {},
|
||||
conversationId?: string
|
||||
conversationId?: string,
|
||||
signal?: AbortSignal
|
||||
): Promise<string | void> {
|
||||
const {
|
||||
stream,
|
||||
|
|
@ -112,15 +111,6 @@ export class ChatService {
|
|||
|
||||
const currentConfig = config();
|
||||
|
||||
const requestId = conversationId || 'default';
|
||||
|
||||
if (this.abortControllers.has(requestId)) {
|
||||
this.abortControllers.get(requestId)?.abort();
|
||||
}
|
||||
|
||||
const abortController = new AbortController();
|
||||
this.abortControllers.set(requestId, abortController);
|
||||
|
||||
const normalizedMessages: ApiChatMessageData[] = messages
|
||||
.map((msg) => {
|
||||
if ('id' in msg && 'convId' in msg && 'timestamp' in msg) {
|
||||
|
|
@ -206,7 +196,7 @@ export class ChatService {
|
|||
method: 'POST',
|
||||
headers: getJsonHeaders(),
|
||||
body: JSON.stringify(requestBody),
|
||||
signal: abortController.signal
|
||||
signal
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
|
@ -228,7 +218,7 @@ export class ChatService {
|
|||
onModel,
|
||||
onTimings,
|
||||
conversationId,
|
||||
abortController.signal
|
||||
signal
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
|
|
@ -272,8 +262,6 @@ export class ChatService {
|
|||
onError(userFriendlyError);
|
||||
}
|
||||
throw userFriendlyError;
|
||||
} finally {
|
||||
this.abortControllers.delete(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -739,27 +727,6 @@ export class ChatService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts any ongoing chat completion request.
|
||||
* Cancels the current request and cleans up the abort controller.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public abortChatCompletionRequest(conversationId?: string): void {
|
||||
if (conversationId) {
|
||||
const abortController = this.abortControllers.get(conversationId);
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
this.abortControllers.delete(conversationId);
|
||||
}
|
||||
} else {
|
||||
for (const controller of this.abortControllers.values()) {
|
||||
controller.abort();
|
||||
}
|
||||
this.abortControllers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects a system message at the beginning of the conversation if configured in settings.
|
||||
* Checks for existing system messages to avoid duplication and retrieves the system message
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ class ChatStore {
|
|||
chatLoadingStates = new SvelteMap<string, boolean>();
|
||||
chatStreamingStates = new SvelteMap<string, { response: string; messageId: string }>();
|
||||
|
||||
// Abort controllers for per-conversation request cancellation
|
||||
private abortControllers = new SvelteMap<string, AbortController>();
|
||||
|
||||
// Processing state tracking - per-conversation timing/context info
|
||||
private processingStates = new SvelteMap<string, ApiProcessingState | null>();
|
||||
private processingCallbacks = new SvelteSet<(state: ApiProcessingState | null) => void>();
|
||||
|
|
@ -517,6 +520,8 @@ class ChatStore {
|
|||
this.startStreaming();
|
||||
this.setActiveProcessingConversation(assistantMessage.convId);
|
||||
|
||||
const abortController = this.getOrCreateAbortController(assistantMessage.convId);
|
||||
|
||||
await chatService.sendMessage(
|
||||
allMessages,
|
||||
{
|
||||
|
|
@ -615,7 +620,8 @@ class ChatStore {
|
|||
if (onError) onError(error);
|
||||
}
|
||||
},
|
||||
assistantMessage.convId
|
||||
assistantMessage.convId,
|
||||
abortController.signal
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -673,12 +679,42 @@ class ChatStore {
|
|||
if (!activeConv) return;
|
||||
await this.savePartialResponseIfNeeded(activeConv.id);
|
||||
this.stopStreaming();
|
||||
chatService.abortChatCompletionRequest(activeConv.id);
|
||||
this.abortRequest(activeConv.id);
|
||||
this.setChatLoading(activeConv.id, false);
|
||||
this.clearChatStreaming(activeConv.id);
|
||||
this.clearProcessingState(activeConv.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates an AbortController for a conversation
|
||||
*/
|
||||
private getOrCreateAbortController(convId: string): AbortController {
|
||||
let controller = this.abortControllers.get(convId);
|
||||
if (!controller || controller.signal.aborted) {
|
||||
controller = new AbortController();
|
||||
this.abortControllers.set(convId, controller);
|
||||
}
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts any ongoing request for a conversation
|
||||
*/
|
||||
private abortRequest(convId?: string): void {
|
||||
if (convId) {
|
||||
const controller = this.abortControllers.get(convId);
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
this.abortControllers.delete(convId);
|
||||
}
|
||||
} else {
|
||||
for (const controller of this.abortControllers.values()) {
|
||||
controller.abort();
|
||||
}
|
||||
this.abortControllers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private async savePartialResponseIfNeeded(convId?: string): Promise<void> {
|
||||
const conversationId = convId || conversationsStore.activeConversation?.id;
|
||||
if (!conversationId) return;
|
||||
|
|
@ -1123,6 +1159,8 @@ class ChatStore {
|
|||
appendedThinking = '',
|
||||
hasReceivedContent = false;
|
||||
|
||||
const abortController = this.getOrCreateAbortController(msg.convId);
|
||||
|
||||
await chatService.sendMessage(
|
||||
contextWithContinue,
|
||||
{
|
||||
|
|
@ -1218,7 +1256,8 @@ class ChatStore {
|
|||
);
|
||||
}
|
||||
},
|
||||
msg.convId
|
||||
msg.convId,
|
||||
abortController.signal
|
||||
);
|
||||
} catch (error) {
|
||||
if (!this.isAbortError(error)) console.error('Failed to continue message:', error);
|
||||
|
|
|
|||
Loading…
Reference in New Issue