2 import { Plus } from '@lucide/svelte';
3 import { Button } from '$lib/components/ui/button';
4 import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
5 import * as Tooltip from '$lib/components/ui/tooltip';
8 ATTACHMENT_EXTRA_ITEMS,
10 ATTACHMENT_TOOLTIP_TEXT,
11 TOOLTIP_DELAY_DURATION
12 } from '$lib/constants';
13 import { AttachmentMenuItemId } from '$lib/enums';
14 import { ChatFormActionToolsSubmenu, ChatFormActionMcpServersSubmenu } from '$lib/components/app';
16 import { useAttachmentMenu } from '$lib/hooks/use-attachment-menu.svelte';
21 hasAudioModality?: boolean;
22 hasVisionModality?: boolean;
23 hasMcpPromptsSupport?: boolean;
24 hasMcpResourcesSupport?: boolean;
25 onFileUpload?: () => void;
26 onSystemPromptClick?: () => void;
27 onMcpPromptClick?: () => void;
28 onMcpSettingsClick?: () => void;
29 onMcpResourcesClick?: () => void;
33 class: className = '',
35 hasAudioModality = false,
36 hasVisionModality = false,
37 hasMcpPromptsSupport = false,
38 hasMcpResourcesSupport = false,
46 let dropdownOpen = $state(false);
48 function handleMcpSettingsClick() {
50 onMcpSettingsClick?.();
53 const attachmentMenu = useAttachmentMenu(
54 () => ({ hasVisionModality, hasAudioModality, hasMcpPromptsSupport, hasMcpResourcesSupport }),
55 () => ({ onFileUpload, onSystemPromptClick, onMcpPromptClick, onMcpResourcesClick }),
62 <div class="flex items-center gap-1 {className}">
63 <DropdownMenu.Root bind:open={dropdownOpen}>
64 <DropdownMenu.Trigger name="Attach files" {disabled}>
66 <Tooltip.Trigger class="w-full">
68 class="file-upload-button h-8 w-8 rounded-full p-0"
73 <span class="sr-only">{ATTACHMENT_TOOLTIP_TEXT}</span>
75 <Plus class="h-4 w-4" />
80 <p>{ATTACHMENT_TOOLTIP_TEXT}</p>
83 </DropdownMenu.Trigger>
85 <DropdownMenu.Content align="start" class="w-48">
86 {#each ATTACHMENT_FILE_ITEMS as item (item.id)}
87 {@const enabled = attachmentMenu.isItemEnabled(item.enabledWhen)}
90 class="{item.class ?? ''} flex cursor-pointer items-center gap-2"
91 onclick={() => attachmentMenu.callbacks[item.action]()}
93 <item.icon class="h-4 w-4" />
95 <span>{item.label}</span>
97 {:else if item.disabledTooltip}
98 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
99 <Tooltip.Trigger class="w-full">
101 class="{item.class ?? ''} flex cursor-pointer items-center gap-2"
104 <item.icon class="h-4 w-4" />
106 <span>{item.label}</span>
110 <Tooltip.Content side="right">
111 <p>{item.disabledTooltip}</p>
117 {#if !attachmentMenu.isItemEnabled('hasVisionModality')}
118 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
119 <Tooltip.Trigger class="w-full">
121 class="flex cursor-pointer items-center gap-2"
122 onclick={attachmentMenu.callbacks.onFileUpload}
124 {@const pdfItem = ATTACHMENT_FILE_ITEMS.find(
125 (i) => i.id === AttachmentMenuItemId.PDF
128 <pdfItem.icon class="h-4 w-4" />
130 <span>{pdfItem.label}</span>
135 <Tooltip.Content side="right">
136 <p>PDFs will be converted to text. Image-based PDFs may not work properly.</p>
141 <DropdownMenu.Separator />
143 {#each ATTACHMENT_EXTRA_ITEMS as item (item.id)}
144 {#if item.id === AttachmentMenuItemId.SYSTEM_MESSAGE}
145 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
146 <Tooltip.Trigger class="w-full">
148 class="flex cursor-pointer items-center gap-2"
149 onclick={() => attachmentMenu.callbacks[item.action]()}
151 <item.icon class="h-4 w-4" />
153 <span>{item.label}</span>
157 <Tooltip.Content side="right">
158 <p>{attachmentMenu.getSystemMessageTooltip()}</p>
164 <ChatFormActionToolsSubmenu />
166 <ChatFormActionMcpServersSubmenu onMcpSettingsClick={handleMcpSettingsClick} />
168 {#each ATTACHMENT_MCP_ITEMS as item (item.id)}
169 {#if attachmentMenu.isItemVisible(item.visibleWhen)}
171 class="flex cursor-pointer items-center gap-2"
172 onclick={() => attachmentMenu.callbacks[item.action]()}
174 <item.icon class="h-4 w-4" />
176 <span>{item.label}</span>
180 </DropdownMenu.Content>