import * as AlertDialog from '$lib/components/ui/alert-dialog';
import Input from '$lib/components/ui/input/input.svelte';
import { conversationsStore, conversations } from '$lib/stores/conversations.svelte';
+ import { chatStore } from '$lib/stores/chat.svelte';
import ChatSidebarActions from './ChatSidebarActions.svelte';
const sidebar = Sidebar.useSidebar();
await goto(`#/chat/${id}`);
}
+
+ function handleStopGeneration(id: string) {
+ chatStore.stopGenerationForChat(id);
+ }
</script>
<ScrollArea class="h-[100vh]">
onSelect={selectConversation}
onEdit={handleEditConversation}
onDelete={handleDeleteConversation}
+ onStop={handleStopGeneration}
/>
</Sidebar.MenuItem>
{/each}
<script lang="ts">
- import { Trash2, Pencil, MoreHorizontal, Download, Loader2 } from '@lucide/svelte';
+ import { Trash2, Pencil, MoreHorizontal, Download, Loader2, Square } from '@lucide/svelte';
import { ActionDropdown } from '$lib/components/app';
+ import * as Tooltip from '$lib/components/ui/tooltip';
import { getAllLoadingChats } from '$lib/stores/chat.svelte';
import { conversationsStore } from '$lib/stores/conversations.svelte';
import { onMount } from 'svelte';
onDelete?: (id: string) => void;
onEdit?: (id: string) => void;
onSelect?: (id: string) => void;
+ onStop?: (id: string) => void;
}
let {
onDelete,
onEdit,
onSelect,
+ onStop,
isActive = false
}: Props = $props();
onDelete?.(conversation.id);
}
+ function handleStop(event: Event) {
+ event.stopPropagation();
+ onStop?.(conversation.id);
+ }
+
function handleGlobalEditEvent(event: Event) {
const customEvent = event as CustomEvent<{ conversationId: string }>;
+
if (customEvent.detail.conversationId === conversation.id && isActive) {
handleEdit(event);
}
>
<div class="flex min-w-0 flex-1 items-center gap-2">
{#if isLoading}
- <Loader2 class="h-3.5 w-3.5 shrink-0 animate-spin text-muted-foreground" />
+ <Tooltip.Root>
+ <Tooltip.Trigger>
+ <div
+ class="stop-button flex h-4 w-4 shrink-0 cursor-pointer items-center justify-center rounded text-muted-foreground transition-colors hover:text-foreground"
+ onclick={handleStop}
+ onkeydown={(e) => e.key === 'Enter' && handleStop(e)}
+ role="button"
+ tabindex="0"
+ aria-label="Stop generation"
+ >
+ <Loader2 class="loading-icon h-3.5 w-3.5 animate-spin" />
+
+ <Square class="stop-icon hidden h-3 w-3 fill-current text-destructive" />
+ </div>
+ </Tooltip.Trigger>
+
+ <Tooltip.Content>
+ <p>Stop generation</p>
+ </Tooltip.Content>
+ </Tooltip.Root>
{/if}
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<span class="truncate text-sm font-medium" onclick={handleMobileSidebarItemClick}>
opacity: 1 !important;
}
}
+
+ .stop-button {
+ :global(.stop-icon) {
+ display: none;
+ }
+
+ :global(.loading-icon) {
+ display: block;
+ }
+ }
+
+ &:is(:hover) .stop-button {
+ :global(.stop-icon) {
+ display: block;
+ }
+
+ :global(.loading-icon) {
+ display: none;
+ }
+ }
}
</style>
if (!activeConv) return;
- await this.savePartialResponseIfNeeded(activeConv.id);
+ await this.stopGenerationForChat(activeConv.id);
+ }
+
+ async stopGenerationForChat(convId: string): Promise<void> {
+ await this.savePartialResponseIfNeeded(convId);
this.stopStreaming();
- this.abortRequest(activeConv.id);
- this.setChatLoading(activeConv.id, false);
- this.clearChatStreaming(activeConv.id);
- this.clearProcessingState(activeConv.id);
+ this.abortRequest(convId);
+ this.setChatLoading(convId, false);
+ this.clearChatStreaming(convId);
+ this.clearProcessingState(convId);
}
/**