2 import type { Snippet } from 'svelte';
3 import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
4 import * as Tooltip from '$lib/components/ui/tooltip';
7 ATTACHMENT_EXTRA_ITEMS,
10 } from '$lib/constants';
11 import { AttachmentMenuItemId } from '$lib/enums';
13 ChatFormActionAddToolsSubmenu,
14 ChatFormActionAddMcpServersSubmenu
15 } from '$lib/components/app';
17 import { useAttachmentMenu } from '$lib/hooks/use-attachment-menu.svelte';
22 hasAudioModality?: boolean;
23 hasVisionModality?: boolean;
24 hasMcpPromptsSupport?: boolean;
25 hasMcpResourcesSupport?: boolean;
26 onFileUpload?: () => void;
27 onSystemPromptClick?: () => void;
28 onMcpPromptClick?: () => void;
29 onMcpSettingsClick?: () => void;
30 onMcpResourcesClick?: () => void;
31 trigger: Snippet<[{ disabled: boolean }]>;
35 class: className = '',
37 hasAudioModality = false,
38 hasVisionModality = false,
39 hasMcpPromptsSupport = false,
40 hasMcpResourcesSupport = false,
49 let dropdownOpen = $state(false);
51 function handleMcpSettingsClick() {
53 onMcpSettingsClick?.();
56 const attachmentMenu = useAttachmentMenu(
57 () => ({ hasVisionModality, hasAudioModality, hasMcpPromptsSupport, hasMcpResourcesSupport }),
58 () => ({ onFileUpload, onSystemPromptClick, onMcpPromptClick, onMcpResourcesClick }),
65 <div class="flex items-center gap-1 {className}">
66 <DropdownMenu.Root bind:open={dropdownOpen}>
67 <DropdownMenu.Trigger name="Attach files" {disabled}>
68 {@render trigger({ disabled })}
69 </DropdownMenu.Trigger>
71 <DropdownMenu.Content align="start" class="w-48">
72 {#each ATTACHMENT_FILE_ITEMS as item (item.id)}
73 {@const enabled = attachmentMenu.isItemEnabled(item.enabledWhen)}
76 class="{item.class ?? ''} flex cursor-pointer items-center gap-2"
77 onclick={() => attachmentMenu.callbacks[item.action]()}
79 <item.icon class="h-4 w-4" />
81 <span>{item.label}</span>
83 {:else if item.disabledTooltip}
84 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
85 <Tooltip.Trigger class="w-full">
87 class="{item.class ?? ''} flex cursor-pointer items-center gap-2"
90 <item.icon class="h-4 w-4" />
92 <span>{item.label}</span>
96 <Tooltip.Content side="right">
97 <p>{item.disabledTooltip}</p>
103 {#if !attachmentMenu.isItemEnabled('hasVisionModality')}
104 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
105 <Tooltip.Trigger class="w-full">
107 class="flex cursor-pointer items-center gap-2"
108 onclick={attachmentMenu.callbacks.onFileUpload}
110 {@const pdfItem = ATTACHMENT_FILE_ITEMS.find(
111 (i) => i.id === AttachmentMenuItemId.PDF
114 <pdfItem.icon class="h-4 w-4" />
116 <span>{pdfItem.label}</span>
121 <Tooltip.Content side="right">
122 <p>PDFs will be converted to text. Image-based PDFs may not work properly.</p>
127 <DropdownMenu.Separator />
129 {#each ATTACHMENT_EXTRA_ITEMS as item (item.id)}
130 {#if item.id === AttachmentMenuItemId.SYSTEM_MESSAGE}
131 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
132 <Tooltip.Trigger class="w-full">
134 class="flex cursor-pointer items-center gap-2"
135 onclick={() => attachmentMenu.callbacks[item.action]()}
137 <item.icon class="h-4 w-4" />
139 <span>{item.label}</span>
143 <Tooltip.Content side="right">
144 <p>{attachmentMenu.getSystemMessageTooltip()}</p>
150 <ChatFormActionAddToolsSubmenu />
152 <ChatFormActionAddMcpServersSubmenu onMcpSettingsClick={handleMcpSettingsClick} />
154 {#each ATTACHMENT_MCP_ITEMS as item (item.id)}
155 {#if attachmentMenu.isItemVisible(item.visibleWhen)}
157 class="flex cursor-pointer items-center gap-2"
158 onclick={() => attachmentMenu.callbacks[item.action]()}
160 <item.icon class="h-4 w-4" />
162 <span>{item.label}</span>
166 </DropdownMenu.Content>