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 hasVideoModality?: boolean;
27 hasVisionModality?: boolean;
28 hasMcpPromptsSupport?: boolean;
29 hasMcpResourcesSupport?: boolean;
30 onFileUpload?: () => void;
31 onSystemPromptClick?: () => void;
32 onMcpPromptClick?: () => void;
33 onMcpSettingsClick?: () => void;
34 onMcpResourcesClick?: () => void;
38 class: className = '',
40 hasAudioModality = false,
41 hasVideoModality = false,
42 hasVisionModality = false,
43 hasMcpPromptsSupport = false,
44 hasMcpResourcesSupport = false,
52 let dropdownOpen = $state(false);
54 function handleMcpSettingsClick() {
56 onMcpSettingsClick?.();
59 const attachmentMenu = useAttachmentMenu(
65 hasMcpResourcesSupport
67 () => ({ onFileUpload, onSystemPromptClick, onMcpPromptClick, onMcpResourcesClick }),
74 <div class="flex items-center gap-1 {className}">
75 <DropdownMenu.Root bind:open={dropdownOpen}>
78 {#snippet child({ props })}
82 buttonVariants({ variant: 'secondary' }),
83 'file-upload-button h-8 w-8 cursor-pointer rounded-full p-0'
87 <span class="sr-only">{ATTACHMENT_TOOLTIP_TEXT}</span>
89 <Plus class="h-4 w-4" />
90 </DropdownMenu.Trigger>
95 <p>{ATTACHMENT_TOOLTIP_TEXT}</p>
99 <DropdownMenu.Content align="start" class="w-48">
100 {#each ATTACHMENT_FILE_ITEMS as item (item.id)}
101 {@const enabled = attachmentMenu.isItemEnabled(item.enabledWhen)}
104 class="{item.class ?? ''} flex cursor-pointer items-center gap-2"
105 onclick={() => attachmentMenu.callbacks[item.action]()}
107 <item.icon class="h-4 w-4" />
109 <span>{item.label}</span>
111 {:else if item.disabledTooltip}
112 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
113 <Tooltip.Trigger tabindex={-1}>
114 {#snippet child({ props })}
115 <div {...props} class="cursor-default">
116 <DropdownMenu.Item class="{item.class ?? ''} flex items-center gap-2" disabled>
117 <item.icon class="h-4 w-4" />
119 <span>{item.label}</span>
125 <Tooltip.Content side="right">
126 <p>{item.disabledTooltip}</p>
132 {#if !attachmentMenu.isItemEnabled('hasVisionModality')}
133 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
135 {#snippet child({ props })}
138 class="flex cursor-pointer items-center gap-2"
139 onclick={attachmentMenu.callbacks.onFileUpload}
141 {@const pdfItem = ATTACHMENT_FILE_ITEMS.find(
142 (i) => i.id === AttachmentMenuItemId.PDF
145 <pdfItem.icon class="h-4 w-4" />
147 <span>{pdfItem.label}</span>
153 <Tooltip.Content side="right">
154 <p>PDFs will be converted to text. Image-based PDFs may not work properly.</p>
159 <DropdownMenu.Separator />
161 {#each ATTACHMENT_EXTRA_ITEMS as item (item.id)}
162 {#if item.id === AttachmentMenuItemId.SYSTEM_MESSAGE}
163 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
165 {#snippet child({ props })}
168 class="flex cursor-pointer items-center gap-2"
169 onclick={() => attachmentMenu.callbacks[item.action]()}
171 <item.icon class="h-4 w-4" />
173 <span>{item.label}</span>
178 <Tooltip.Content side="right">
179 <p>{attachmentMenu.getSystemMessageTooltip()}</p>
185 <ChatFormActionAddToolsSubmenu />
187 <ChatFormActionAddMcpServersSubmenu onMcpSettingsClick={handleMcpSettingsClick} />
189 {#each ATTACHMENT_MCP_ITEMS as item (item.id)}
190 {#if attachmentMenu.isItemVisible(item.visibleWhen)}
192 class="flex cursor-pointer items-center gap-2"
193 onclick={() => attachmentMenu.callbacks[item.action]()}
195 <item.icon class="h-4 w-4" />
197 <span>{item.label}</span>
201 </DropdownMenu.Content>