10 } from '@lucide/svelte';
11 import { DropdownMenuActions } from '$lib/components/app';
12 import * as Tooltip from '$lib/components/ui/tooltip';
13 import { FORK_TREE_DEPTH_PADDING } from '$lib/constants';
14 import { getAllLoadingChats } from '$lib/stores/chat.svelte';
15 import { conversationsStore } from '$lib/stores/conversations.svelte';
16 import { TruncatedText } from '$lib/components/app';
17 import { onMount } from 'svelte';
22 conversation: DatabaseConversation;
23 onDelete?: (id: string) => void;
24 onEdit?: (id: string) => void;
25 onSelect?: (id: string) => void;
26 onStop?: (id: string) => void;
39 let renderActionsDropdown = $state(false);
40 let dropdownOpen = $state(false);
42 let isLoading = $derived(getAllLoadingChats().includes(conversation.id));
44 function handleEdit(event: Event) {
45 event.stopPropagation();
46 onEdit?.(conversation.id);
49 function handleDelete(event: Event) {
50 event.stopPropagation();
51 onDelete?.(conversation.id);
54 function handleStop(event: Event) {
55 event.stopPropagation();
56 onStop?.(conversation.id);
59 function handleGlobalEditEvent(event: Event) {
60 const customEvent = event as CustomEvent<{ conversationId: string }>;
62 if (customEvent.detail.conversationId === conversation.id && isActive) {
67 function handleMouseLeave() {
69 renderActionsDropdown = false;
73 function handleMouseOver() {
74 renderActionsDropdown = true;
77 function handleSelect() {
78 onSelect?.(conversation.id);
83 renderActionsDropdown = false;
88 document.addEventListener('edit-active-conversation', handleGlobalEditEvent as EventListener);
91 document.removeEventListener(
92 'edit-active-conversation',
93 handleGlobalEditEvent as EventListener
99 <!-- svelte-ignore a11y_mouse_events_have_key_events -->
101 class="group flex min-h-9 w-full cursor-pointer items-center justify-between space-x-3 rounded-lg py-1.5 text-left transition-colors hover:bg-foreground/10 {isActive
102 ? 'bg-foreground/5 text-accent-foreground'
104 onclick={handleSelect}
105 onmouseover={handleMouseOver}
106 onmouseleave={handleMouseLeave}
109 class="flex min-w-0 flex-1 items-center gap-2"
110 style:padding-left="{depth * FORK_TREE_DEPTH_PADDING}px"
116 href="#/chat/{conversation.forkedFromConversationId}"
117 class="flex shrink-0 items-center text-muted-foreground transition-colors hover:text-foreground"
119 <GitBranch class="h-3.5 w-3.5" />
124 <p>See parent conversation</p>
133 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"
135 onkeydown={(e) => e.key === 'Enter' && handleStop(e)}
138 aria-label="Stop generation"
140 <Loader2 class="loading-icon h-3.5 w-3.5 animate-spin" />
142 <Square class="stop-icon hidden h-3 w-3 fill-current text-destructive" />
147 <p>Stop generation</p>
152 <TruncatedText text={conversation.name} class="text-sm font-medium" showTooltip={false} />
155 {#if renderActionsDropdown}
156 <div class="actions flex items-center">
158 triggerIcon={MoreHorizontal}
159 triggerTooltip="More actions"
160 bind:open={dropdownOpen}
166 shortcut: ['shift', 'cmd', 'e']
171 onclick: (e: Event) => {
173 conversationsStore.downloadConversation(conversation.id);
175 shortcut: ['shift', 'cmd', 's']
180 onclick: handleDelete,
181 variant: 'destructive',
182 shortcut: ['shift', 'cmd', 'd'],
193 :global([data-slot='dropdown-menu-trigger']:not([data-state='open'])) {
197 &:is(:hover) :global([data-slot='dropdown-menu-trigger']) {
200 @media (max-width: 768px) {
201 :global([data-slot='dropdown-menu-trigger']) {
202 opacity: 1 !important;
207 :global(.stop-icon) {
211 :global(.loading-icon) {
216 &:is(:hover) .stop-button {
217 :global(.stop-icon) {
221 :global(.loading-icon) {