feat(chat): stop active llm responses
This commit is contained in:
@@ -68,12 +68,18 @@
|
||||
x-html="renderMarkdown(message.content)"
|
||||
></div>
|
||||
|
||||
<!-- Timestamp -->
|
||||
<!-- Message Metadata -->
|
||||
<div class="flex items-center justify-between gap-2 mt-2">
|
||||
<div
|
||||
class="text-[10px] opacity-60"
|
||||
x-text="new Date(message.created_at).toLocaleTimeString()"
|
||||
></div>
|
||||
<div
|
||||
x-show="message.role === 'assistant' && ['stopped', 'error', 'failed'].includes(message.status)"
|
||||
:class="message.status === 'stopped' ? 'bg-primary-300/50 text-primary-700' : 'bg-tertiary-100 text-tertiary-700'"
|
||||
class="px-2 py-0.5 rounded-full text-[10px] font-medium"
|
||||
x-text="message.status === 'stopped' ? 'Stopped' : 'Error'"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -279,6 +285,26 @@
|
||||
</svg>
|
||||
</template>
|
||||
</button>
|
||||
|
||||
<button
|
||||
x-show="loading"
|
||||
type="button"
|
||||
@click="stopResponse()"
|
||||
:disabled="!activeStreamChatID"
|
||||
:class="!activeStreamChatID ? 'opacity-50 cursor-not-allowed' : 'hover:shadow-md hover:scale-105'"
|
||||
class="self-stretch w-[44px] bg-tertiary-600 text-white rounded-xl transition-all flex items-center justify-center flex-shrink-0"
|
||||
title="Stop response"
|
||||
aria-label="Stop response"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M6 6h12v12H6z" />
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Error Message -->
|
||||
|
||||
@@ -124,6 +124,17 @@ export async function streamChatUpdates(
|
||||
return streamMessage(response, onChunk);
|
||||
}
|
||||
|
||||
export async function stopChatGeneration(chatId: string): Promise<void> {
|
||||
const response = await fetch(`/api/chats/${chatId}/stop`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(readError(errorData) || `HTTP ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getChatMessages(chatId: string): Promise<Chat> {
|
||||
const response = await fetch(`/api/chats/${chatId}`);
|
||||
const data = await response.json().catch(() => ({}));
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
getModels,
|
||||
sendMessage,
|
||||
streamChatUpdates,
|
||||
stopChatGeneration,
|
||||
getChatMessages,
|
||||
listChats,
|
||||
deleteChat,
|
||||
@@ -87,6 +88,18 @@ Alpine.data('chatManager', () => ({
|
||||
}
|
||||
},
|
||||
|
||||
async stopResponse() {
|
||||
if (!this.activeStreamChatID) return;
|
||||
|
||||
// Stop Active Generation
|
||||
try {
|
||||
await stopChatGeneration(this.activeStreamChatID);
|
||||
} catch (err) {
|
||||
console.error('Error stopping response:', err);
|
||||
this.error = parseError(err);
|
||||
}
|
||||
},
|
||||
|
||||
async sendMessage() {
|
||||
const message = this.inputMessage.trim();
|
||||
if ((!message && this.selectedImages.length === 0) || this.loading) return;
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface Chat {
|
||||
messages: Message[];
|
||||
}
|
||||
|
||||
export type MessageStatus = 'streaming' | 'complete' | 'failed';
|
||||
export type MessageStatus = 'streaming' | 'complete' | 'stopped' | 'error' | 'failed';
|
||||
|
||||
export interface Message {
|
||||
id: string;
|
||||
|
||||
Reference in New Issue
Block a user