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';
13 hasAudioModality?: boolean;
14 hasVisionModality?: boolean;
15 onFileUpload?: () => void;
16 onSystemPromptClick?: () => void;
19 type AttachmentActionId = 'images' | 'audio' | 'text' | 'pdf' | 'system';
21 interface AttachmentAction {
22 id: AttachmentActionId;
25 disabledReason?: string;
30 class: className = '',
32 hasAudioModality = false,
33 hasVisionModality = false,
38 let isNewChat = $derived(!page.params.id);
39 let systemMessageTooltip = $derived(
41 ? 'Add custom system message for a new conversation'
42 : 'Inject custom system message at the beginning of the conversation'
45 let actions = $derived.by<AttachmentAction[]>(() => [
49 disabled: !hasVisionModality,
50 disabledReason: !hasVisionModality
51 ? 'Images require vision models to be processed'
57 disabled: !hasAudioModality,
58 disabledReason: !hasAudioModality
59 ? 'Audio files require audio models to be processed'
69 tooltip: !hasVisionModality
70 ? 'PDFs will be converted to text. Image-based PDFs may not work properly.'
75 label: 'System Message',
76 tooltip: systemMessageTooltip
80 function handleActionClick(id: AttachmentActionId) {
81 if (id === 'system') {
82 onSystemPromptClick?.();
89 const triggerTooltipText = 'Add files or system message';
90 const itemClass = 'flex cursor-pointer items-center gap-2';
93 <div class="flex items-center gap-1 {className}">
95 <DropdownMenu.Trigger name="Attach files" {disabled}>
97 <Tooltip.Trigger class="w-full">
99 class="file-upload-button h-8 w-8 rounded-full p-0"
104 <span class="sr-only">{triggerTooltipText}</span>
106 <Plus class="h-4 w-4" />
111 <p>{triggerTooltipText}</p>
114 </DropdownMenu.Trigger>
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}
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" />
134 <MessageSquare class="h-4 w-4" />
137 <span>{item.label}</span>
141 <Tooltip.Content side="right">
142 <p>{item.disabledReason}</p>
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" />
158 <MessageSquare class="h-4 w-4" />
161 <span>{item.label}</span>
165 <Tooltip.Content side="right">
166 <p>{item.tooltip}</p>
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" />
180 <MessageSquare class="h-4 w-4" />
183 <span>{item.label}</span>
187 </DropdownMenu.Content>