]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/blob
f8c1b23b06171add428e78fa7e9bb357892c7982
[pkg/ggml/sources/llama.cpp] /
1 <script lang="ts">
2 import { page } from '$app/state';
3 import { MessageSquare, Plus } from '@lucide/svelte';
4 import { Button } from '$lib/components/ui/button';
5 import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
6 import * as Tooltip from '$lib/components/ui/tooltip';
7 import { FILE_TYPE_ICONS } from '$lib/constants/icons';
8 import { TOOLTIP_DELAY_DURATION } from '$lib/constants/tooltip-config';
9
10 interface Props {
11 class?: string;
12 disabled?: boolean;
13 hasAudioModality?: boolean;
14 hasVisionModality?: boolean;
15 onFileUpload?: () => void;
16 onSystemPromptClick?: () => void;
17 }
18
19 type AttachmentActionId = 'images' | 'audio' | 'text' | 'pdf' | 'system';
20
21 interface AttachmentAction {
22 id: AttachmentActionId;
23 label: string;
24 disabled?: boolean;
25 disabledReason?: string;
26 tooltip?: string;
27 }
28
29 let {
30 class: className = '',
31 disabled = false,
32 hasAudioModality = false,
33 hasVisionModality = false,
34 onFileUpload,
35 onSystemPromptClick
36 }: Props = $props();
37
38 let isNewChat = $derived(!page.params.id);
39 let systemMessageTooltip = $derived(
40 isNewChat
41 ? 'Add custom system message for a new conversation'
42 : 'Inject custom system message at the beginning of the conversation'
43 );
44
45 let actions = $derived.by<AttachmentAction[]>(() => [
46 {
47 id: 'images',
48 label: 'Images',
49 disabled: !hasVisionModality,
50 disabledReason: !hasVisionModality
51 ? 'Images require vision models to be processed'
52 : undefined
53 },
54 {
55 id: 'audio',
56 label: 'Audio Files',
57 disabled: !hasAudioModality,
58 disabledReason: !hasAudioModality
59 ? 'Audio files require audio models to be processed'
60 : undefined
61 },
62 {
63 id: 'text',
64 label: 'Text Files'
65 },
66 {
67 id: 'pdf',
68 label: 'PDF Files',
69 tooltip: !hasVisionModality
70 ? 'PDFs will be converted to text. Image-based PDFs may not work properly.'
71 : undefined
72 },
73 {
74 id: 'system',
75 label: 'System Message',
76 tooltip: systemMessageTooltip
77 }
78 ]);
79
80 function handleActionClick(id: AttachmentActionId) {
81 if (id === 'system') {
82 onSystemPromptClick?.();
83 return;
84 }
85
86 onFileUpload?.();
87 }
88
89 const triggerTooltipText = 'Add files or system message';
90 const itemClass = 'flex cursor-pointer items-center gap-2';
91 </script>
92
93 <div class="flex items-center gap-1 {className}">
94 <DropdownMenu.Root>
95 <DropdownMenu.Trigger name="Attach files" {disabled}>
96 <Tooltip.Root>
97 <Tooltip.Trigger class="w-full">
98 <Button
99 class="file-upload-button h-8 w-8 rounded-full p-0"
100 {disabled}
101 variant="secondary"
102 type="button"
103 >
104 <span class="sr-only">{triggerTooltipText}</span>
105
106 <Plus class="h-4 w-4" />
107 </Button>
108 </Tooltip.Trigger>
109
110 <Tooltip.Content>
111 <p>{triggerTooltipText}</p>
112 </Tooltip.Content>
113 </Tooltip.Root>
114 </DropdownMenu.Trigger>
115
116 <DropdownMenu.Content align="start" class="w-56">
117 {#each actions as item (item.id)}
118 {@const hasDisabledTooltip = !!item.disabled && !!item.disabledReason}
119 {@const hasEnabledTooltip = !item.disabled && !!item.tooltip}
120
121 {#if hasDisabledTooltip}
122 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
123 <Tooltip.Trigger class="w-full">
124 <DropdownMenu.Item class={itemClass} disabled>
125 {#if item.id === 'images'}
126 <FILE_TYPE_ICONS.image class="h-4 w-4" />
127 {:else if item.id === 'audio'}
128 <FILE_TYPE_ICONS.audio class="h-4 w-4" />
129 {:else if item.id === 'text'}
130 <FILE_TYPE_ICONS.text class="h-4 w-4" />
131 {:else if item.id === 'pdf'}
132 <FILE_TYPE_ICONS.pdf class="h-4 w-4" />
133 {:else}
134 <MessageSquare class="h-4 w-4" />
135 {/if}
136
137 <span>{item.label}</span>
138 </DropdownMenu.Item>
139 </Tooltip.Trigger>
140
141 <Tooltip.Content side="right">
142 <p>{item.disabledReason}</p>
143 </Tooltip.Content>
144 </Tooltip.Root>
145 {:else if hasEnabledTooltip}
146 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
147 <Tooltip.Trigger class="w-full">
148 <DropdownMenu.Item class={itemClass} onclick={() => handleActionClick(item.id)}>
149 {#if item.id === 'images'}
150 <FILE_TYPE_ICONS.image class="h-4 w-4" />
151 {:else if item.id === 'audio'}
152 <FILE_TYPE_ICONS.audio class="h-4 w-4" />
153 {:else if item.id === 'text'}
154 <FILE_TYPE_ICONS.text class="h-4 w-4" />
155 {:else if item.id === 'pdf'}
156 <FILE_TYPE_ICONS.pdf class="h-4 w-4" />
157 {:else}
158 <MessageSquare class="h-4 w-4" />
159 {/if}
160
161 <span>{item.label}</span>
162 </DropdownMenu.Item>
163 </Tooltip.Trigger>
164
165 <Tooltip.Content side="right">
166 <p>{item.tooltip}</p>
167 </Tooltip.Content>
168 </Tooltip.Root>
169 {:else}
170 <DropdownMenu.Item class={itemClass} onclick={() => handleActionClick(item.id)}>
171 {#if item.id === 'images'}
172 <FILE_TYPE_ICONS.image class="h-4 w-4" />
173 {:else if item.id === 'audio'}
174 <FILE_TYPE_ICONS.audio class="h-4 w-4" />
175 {:else if item.id === 'text'}
176 <FILE_TYPE_ICONS.text class="h-4 w-4" />
177 {:else if item.id === 'pdf'}
178 <FILE_TYPE_ICONS.pdf class="h-4 w-4" />
179 {:else}
180 <MessageSquare class="h-4 w-4" />
181 {/if}
182
183 <span>{item.label}</span>
184 </DropdownMenu.Item>
185 {/if}
186 {/each}
187 </DropdownMenu.Content>
188 </DropdownMenu.Root>
189 </div>