3 ChatAttachmentsPreviewCurrentItem,
4 ChatAttachmentsPreviewFileInfo,
5 ChatAttachmentsPreviewNavButtons,
6 ChatAttachmentsPreviewThumbnailStrip
7 } from '$lib/components/app';
8 import { modelsStore } from '$lib/stores';
12 getAttachmentDisplayItems,
13 getLanguageFromFilename,
23 interface PreviewItem {
28 uploadedFile?: ChatUploadedFile;
29 attachment?: DatabaseMessageExtra;
37 uploadedFiles?: ChatUploadedFile[];
38 attachments?: DatabaseMessageExtra[];
39 activeModelId?: string;
41 previewFocusIndex?: number;
47 class: className = '',
48 previewFocusIndex = 0,
52 let allItems = $derived(
53 getAttachmentDisplayItems({ attachments, uploadedFiles })
54 .filter((item) => !isMcpPrompt(item) && !isMcpResource(item))
56 (item): PreviewItem => ({
58 isAudio: isAudioFile(item.attachment, item.uploadedFile),
59 isImage: isImageFile(item.attachment, item.uploadedFile),
60 isVideo: isVideoFile(item.attachment, item.uploadedFile)
65 let currentIndex = $state(0);
68 if (previewFocusIndex >= 0 && previewFocusIndex < allItems.length) {
69 currentIndex = previewFocusIndex;
74 const handler = (e: Event) => {
75 const delta = (e as CustomEvent).detail;
78 currentIndex = currentIndex > 0 ? currentIndex - 1 : allItems.length - 1;
80 currentIndex = currentIndex < allItems.length - 1 ? currentIndex + 1 : 0;
84 document.addEventListener('chat-attachments-nav', handler);
86 return () => document.removeEventListener('chat-attachments-nav', handler);
90 const index = currentIndex;
93 const thumbnail = document.querySelector(`[data-thumbnail-index="${index}"]`);
95 thumbnail?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
99 let currentItem = $derived(allItems[currentIndex] ?? null);
100 let displayName = $derived(
102 currentItem?.uploadedFile?.name ||
103 currentItem?.attachment?.name ||
106 let isAudio = $derived(
107 currentItem ? isAudioFile(currentItem.attachment, currentItem.uploadedFile) : false
109 let isVideo = $derived(
110 currentItem ? isVideoFile(currentItem.attachment, currentItem.uploadedFile) : false
112 let isImage = $derived(
113 currentItem ? isImageFile(currentItem.attachment, currentItem.uploadedFile) : false
115 let isPdf = $derived(
116 currentItem ? isPdfFile(currentItem.attachment, currentItem.uploadedFile) : false
118 let isText = $derived(
119 currentItem ? isTextFile(currentItem.attachment, currentItem.uploadedFile) : false
122 let displayPreview = $derived(
123 currentItem?.uploadedFile?.preview ||
124 (isImage && currentItem?.attachment && 'base64Url' in currentItem.attachment
125 ? currentItem.attachment.base64Url
126 : currentItem?.preview)
129 let displayTextContent = $derived(
130 currentItem?.uploadedFile?.textContent ||
131 (currentItem?.attachment && 'content' in currentItem.attachment
132 ? currentItem.attachment.content
133 : currentItem?.textContent)
136 let language = $derived(getLanguageFromFilename(displayName));
138 let fileSize = $derived(currentItem?.size ? formatFileSize(currentItem.size) : '');
140 let hasVisionModality = $derived(
141 currentItem && activeModelId ? modelsStore.modelSupportsVision(activeModelId) : false
144 let audioSrc = $derived(
145 isAudio && currentItem
146 ? (currentItem.uploadedFile?.preview ??
147 (currentItem.attachment &&
148 'mimeType' in currentItem.attachment &&
149 'base64Data' in currentItem.attachment
150 ? createBase64DataUrl(
151 currentItem.attachment.mimeType,
152 currentItem.attachment.base64Data
158 let videoSrc = $derived(
159 isVideo && currentItem
160 ? (currentItem.uploadedFile?.preview ??
161 (currentItem.attachment &&
162 'mimeType' in currentItem.attachment &&
163 'base64Data' in currentItem.attachment
164 ? createBase64DataUrl(
165 currentItem.attachment.mimeType,
166 currentItem.attachment.base64Data
172 export function prev() {
173 currentIndex = currentIndex > 0 ? currentIndex - 1 : allItems.length - 1;
176 export function next() {
177 currentIndex = currentIndex < allItems.length - 1 ? currentIndex + 1 : 0;
180 function onNavigate(index: number) {
181 currentIndex = index;
185 <div class="{className} flex flex-col text-white">
186 <div class="relative flex min-h-0 flex-1 items-center justify-center overflow-hidden">
187 <ChatAttachmentsPreviewNavButtons onPrev={prev} onNext={next} show={allItems.length > 1} />
189 <div class="flex h-full w-full flex-col items-center justify-start overflow-auto py-4">
191 <ChatAttachmentsPreviewFileInfo {displayName} {fileSize} />
193 <ChatAttachmentsPreviewCurrentItem
210 <ChatAttachmentsPreviewThumbnailStrip items={allItems} {currentIndex} {onNavigate} />