]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/blob
46bab827461e5f4447593e8efcfa6901efa856ff
[pkg/ggml/sources/llama.cpp] /
1 <script lang="ts">
2 import {
3 Trash2,
4 Pencil,
5 MoreHorizontal,
6 Download,
7 Loader2,
8 Square,
9 GitBranch
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';
18
19 interface Props {
20 isActive?: boolean;
21 depth?: number;
22 conversation: DatabaseConversation;
23 onDelete?: (id: string) => void;
24 onEdit?: (id: string) => void;
25 onSelect?: (id: string) => void;
26 onStop?: (id: string) => void;
27 }
28
29 let {
30 conversation,
31 onDelete,
32 onEdit,
33 onSelect,
34 onStop,
35 isActive = false,
36 depth = 0
37 }: Props = $props();
38
39 let renderActionsDropdown = $state(false);
40 let dropdownOpen = $state(false);
41
42 let isLoading = $derived(getAllLoadingChats().includes(conversation.id));
43
44 function handleEdit(event: Event) {
45 event.stopPropagation();
46 onEdit?.(conversation.id);
47 }
48
49 function handleDelete(event: Event) {
50 event.stopPropagation();
51 onDelete?.(conversation.id);
52 }
53
54 function handleStop(event: Event) {
55 event.stopPropagation();
56 onStop?.(conversation.id);
57 }
58
59 function handleGlobalEditEvent(event: Event) {
60 const customEvent = event as CustomEvent<{ conversationId: string }>;
61
62 if (customEvent.detail.conversationId === conversation.id && isActive) {
63 handleEdit(event);
64 }
65 }
66
67 function handleMouseLeave() {
68 if (!dropdownOpen) {
69 renderActionsDropdown = false;
70 }
71 }
72
73 function handleMouseOver() {
74 renderActionsDropdown = true;
75 }
76
77 function handleSelect() {
78 onSelect?.(conversation.id);
79 }
80
81 $effect(() => {
82 if (!dropdownOpen) {
83 renderActionsDropdown = false;
84 }
85 });
86
87 onMount(() => {
88 document.addEventListener('edit-active-conversation', handleGlobalEditEvent as EventListener);
89
90 return () => {
91 document.removeEventListener(
92 'edit-active-conversation',
93 handleGlobalEditEvent as EventListener
94 );
95 };
96 });
97 </script>
98
99 <!-- svelte-ignore a11y_mouse_events_have_key_events -->
100 <button
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'
103 : ''} px-3"
104 onclick={handleSelect}
105 onmouseover={handleMouseOver}
106 onmouseleave={handleMouseLeave}
107 >
108 <div
109 class="flex min-w-0 flex-1 items-center gap-2"
110 style:padding-left="{depth * FORK_TREE_DEPTH_PADDING}px"
111 >
112 {#if depth > 0}
113 <Tooltip.Root>
114 <Tooltip.Trigger>
115 <a
116 href="#/chat/{conversation.forkedFromConversationId}"
117 class="flex shrink-0 items-center text-muted-foreground transition-colors hover:text-foreground"
118 >
119 <GitBranch class="h-3.5 w-3.5" />
120 </a>
121 </Tooltip.Trigger>
122
123 <Tooltip.Content>
124 <p>See parent conversation</p>
125 </Tooltip.Content>
126 </Tooltip.Root>
127 {/if}
128
129 {#if isLoading}
130 <Tooltip.Root>
131 <Tooltip.Trigger>
132 <div
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"
134 onclick={handleStop}
135 onkeydown={(e) => e.key === 'Enter' && handleStop(e)}
136 role="button"
137 tabindex="0"
138 aria-label="Stop generation"
139 >
140 <Loader2 class="loading-icon h-3.5 w-3.5 animate-spin" />
141
142 <Square class="stop-icon hidden h-3 w-3 fill-current text-destructive" />
143 </div>
144 </Tooltip.Trigger>
145
146 <Tooltip.Content>
147 <p>Stop generation</p>
148 </Tooltip.Content>
149 </Tooltip.Root>
150 {/if}
151
152 <TruncatedText text={conversation.name} class="text-sm font-medium" showTooltip={false} />
153 </div>
154
155 {#if renderActionsDropdown}
156 <div class="actions flex items-center">
157 <DropdownMenuActions
158 triggerIcon={MoreHorizontal}
159 triggerTooltip="More actions"
160 bind:open={dropdownOpen}
161 actions={[
162 {
163 icon: Pencil,
164 label: 'Edit',
165 onclick: handleEdit,
166 shortcut: ['shift', 'cmd', 'e']
167 },
168 {
169 icon: Download,
170 label: 'Export',
171 onclick: (e: Event) => {
172 e.stopPropagation();
173 conversationsStore.downloadConversation(conversation.id);
174 },
175 shortcut: ['shift', 'cmd', 's']
176 },
177 {
178 icon: Trash2,
179 label: 'Delete',
180 onclick: handleDelete,
181 variant: 'destructive',
182 shortcut: ['shift', 'cmd', 'd'],
183 separator: true
184 }
185 ]}
186 />
187 </div>
188 {/if}
189 </button>
190
191 <style>
192 button {
193 :global([data-slot='dropdown-menu-trigger']:not([data-state='open'])) {
194 opacity: 0;
195 }
196
197 &:is(:hover) :global([data-slot='dropdown-menu-trigger']) {
198 opacity: 1;
199 }
200 @media (max-width: 768px) {
201 :global([data-slot='dropdown-menu-trigger']) {
202 opacity: 1 !important;
203 }
204 }
205
206 .stop-button {
207 :global(.stop-icon) {
208 display: none;
209 }
210
211 :global(.loading-icon) {
212 display: block;
213 }
214 }
215
216 &:is(:hover) .stop-button {
217 :global(.stop-icon) {
218 display: block;
219 }
220
221 :global(.loading-icon) {
222 display: none;
223 }
224 }
225 }
226 </style>