2 import { Plus } from '@lucide/svelte';
3 import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
4 import * as Tooltip from '$lib/components/ui/tooltip';
5 import { buttonVariants } from '$lib/components/ui/button';
6 import { cn } from '$lib/components/ui/utils';
9 ATTACHMENT_EXTRA_ITEMS,
11 ATTACHMENT_TOOLTIP_TEXT,
12 TOOLTIP_DELAY_DURATION
13 } from '$lib/constants';
14 import { AttachmentMenuItemId } from '$lib/enums';
16 ChatFormActionAddToolsSubmenu,
17 ChatFormActionAddMcpServersSubmenu
18 } from '$lib/components/app';
20 import { useAttachmentMenu } from '$lib/hooks/use-attachment-menu.svelte';
25 hasAudioModality?: boolean;
26 hasVisionModality?: boolean;
27 hasMcpPromptsSupport?: boolean;
28 hasMcpResourcesSupport?: boolean;
29 onFileUpload?: () => void;
30 onSystemPromptClick?: () => void;
31 onMcpPromptClick?: () => void;
32 onMcpSettingsClick?: () => void;
33 onMcpResourcesClick?: () => void;
37 class: className = '',
39 hasAudioModality = false,
40 hasVisionModality = false,
41 hasMcpPromptsSupport = false,
42 hasMcpResourcesSupport = false,
50 let dropdownOpen = $state(false);
52 function handleMcpSettingsClick() {
54 onMcpSettingsClick?.();
57 const attachmentMenu = useAttachmentMenu(
62 hasMcpResourcesSupport
64 () => ({ onFileUpload, onSystemPromptClick, onMcpPromptClick, onMcpResourcesClick }),
71 <div class="flex items-center gap-1 {className}">
72 <DropdownMenu.Root bind:open={dropdownOpen}>
75 {#snippet child({ props })}
79 buttonVariants({ variant: 'secondary' }),
80 'file-upload-button h-8 w-8 cursor-pointer rounded-full p-0'
84 <span class="sr-only">{ATTACHMENT_TOOLTIP_TEXT}</span>
86 <Plus class="h-4 w-4" />
87 </DropdownMenu.Trigger>
92 <p>{ATTACHMENT_TOOLTIP_TEXT}</p>
96 <DropdownMenu.Content align="start" class="w-48">
97 {#each ATTACHMENT_FILE_ITEMS as item (item.id)}
98 {@const enabled = attachmentMenu.isItemEnabled(item.enabledWhen)}
101 class="{item.class ?? ''} flex cursor-pointer items-center gap-2"
102 onclick={() => attachmentMenu.callbacks[item.action]()}
104 <item.icon class="h-4 w-4" />
106 <span>{item.label}</span>
108 {:else if item.disabledTooltip}
109 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
110 <Tooltip.Trigger tabindex={-1}>
111 {#snippet child({ props })}
112 <div {...props} class="cursor-default">
113 <DropdownMenu.Item class="{item.class ?? ''} flex items-center gap-2" disabled>
114 <item.icon class="h-4 w-4" />
116 <span>{item.label}</span>
122 <Tooltip.Content side="right">
123 <p>{item.disabledTooltip}</p>
129 {#if !attachmentMenu.isItemEnabled('hasVisionModality')}
130 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
132 {#snippet child({ props })}
135 class="flex cursor-pointer items-center gap-2"
136 onclick={attachmentMenu.callbacks.onFileUpload}
138 {@const pdfItem = ATTACHMENT_FILE_ITEMS.find(
139 (i) => i.id === AttachmentMenuItemId.PDF
142 <pdfItem.icon class="h-4 w-4" />
144 <span>{pdfItem.label}</span>
150 <Tooltip.Content side="right">
151 <p>PDFs will be converted to text. Image-based PDFs may not work properly.</p>
156 <DropdownMenu.Separator />
158 {#each ATTACHMENT_EXTRA_ITEMS as item (item.id)}
159 {#if item.id === AttachmentMenuItemId.SYSTEM_MESSAGE}
160 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
162 {#snippet child({ props })}
165 class="flex cursor-pointer items-center gap-2"
166 onclick={() => attachmentMenu.callbacks[item.action]()}
168 <item.icon class="h-4 w-4" />
170 <span>{item.label}</span>
175 <Tooltip.Content side="right">
176 <p>{attachmentMenu.getSystemMessageTooltip()}</p>
182 <ChatFormActionAddToolsSubmenu />
184 <ChatFormActionAddMcpServersSubmenu onMcpSettingsClick={handleMcpSettingsClick} />
186 {#each ATTACHMENT_MCP_ITEMS as item (item.id)}
187 {#if attachmentMenu.isItemVisible(item.visibleWhen)}
189 class="flex cursor-pointer items-center gap-2"
190 onclick={() => attachmentMenu.callbacks[item.action]()}
192 <item.icon class="h-4 w-4" />
194 <span>{item.label}</span>
198 </DropdownMenu.Content>