]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/blob
0efe052b1f19bf0b40f616e36d6ebeee4607f5a6
[pkg/ggml/sources/llama.cpp] /
1 <script lang="ts">
2 import {
3 ChatAttachmentsPreviewCurrentItem,
4 ChatAttachmentsPreviewFileInfo,
5 ChatAttachmentsPreviewNavButtons,
6 ChatAttachmentsPreviewThumbnailStrip
7 } from '$lib/components/app';
8 import { modelsStore } from '$lib/stores';
9 import {
10 createBase64DataUrl,
11 formatFileSize,
12 getAttachmentDisplayItems,
13 getLanguageFromFilename,
14 isAudioFile,
15 isImageFile,
16 isMcpPrompt,
17 isMcpResource,
18 isPdfFile,
19 isTextFile,
20 isVideoFile
21 } from '$lib/utils';
22
23 interface PreviewItem {
24 id: string;
25 name: string;
26 size?: number;
27 preview?: string;
28 uploadedFile?: ChatUploadedFile;
29 attachment?: DatabaseMessageExtra;
30 textContent?: string;
31 isImage: boolean;
32 isAudio: boolean;
33 isVideo: boolean;
34 }
35
36 interface Props {
37 uploadedFiles?: ChatUploadedFile[];
38 attachments?: DatabaseMessageExtra[];
39 activeModelId?: string;
40 class?: string;
41 previewFocusIndex?: number;
42 }
43
44 let {
45 activeModelId,
46 attachments = [],
47 class: className = '',
48 previewFocusIndex = 0,
49 uploadedFiles = []
50 }: Props = $props();
51
52 let allItems = $derived(
53 getAttachmentDisplayItems({ attachments, uploadedFiles })
54 .filter((item) => !isMcpPrompt(item) && !isMcpResource(item))
55 .map(
56 (item): PreviewItem => ({
57 ...item,
58 isAudio: isAudioFile(item.attachment, item.uploadedFile),
59 isImage: isImageFile(item.attachment, item.uploadedFile),
60 isVideo: isVideoFile(item.attachment, item.uploadedFile)
61 })
62 )
63 );
64
65 let currentIndex = $state(0);
66
67 $effect(() => {
68 if (previewFocusIndex >= 0 && previewFocusIndex < allItems.length) {
69 currentIndex = previewFocusIndex;
70 }
71 });
72
73 $effect(() => {
74 const handler = (e: Event) => {
75 const delta = (e as CustomEvent).detail;
76
77 if (delta < 0) {
78 currentIndex = currentIndex > 0 ? currentIndex - 1 : allItems.length - 1;
79 } else {
80 currentIndex = currentIndex < allItems.length - 1 ? currentIndex + 1 : 0;
81 }
82 };
83
84 document.addEventListener('chat-attachments-nav', handler);
85
86 return () => document.removeEventListener('chat-attachments-nav', handler);
87 });
88
89 $effect(() => {
90 const index = currentIndex;
91
92 setTimeout(() => {
93 const thumbnail = document.querySelector(`[data-thumbnail-index="${index}"]`);
94
95 thumbnail?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
96 }, 0);
97 });
98
99 let currentItem = $derived(allItems[currentIndex] ?? null);
100 let displayName = $derived(
101 currentItem?.name ||
102 currentItem?.uploadedFile?.name ||
103 currentItem?.attachment?.name ||
104 'Unknown File'
105 );
106 let isAudio = $derived(
107 currentItem ? isAudioFile(currentItem.attachment, currentItem.uploadedFile) : false
108 );
109 let isVideo = $derived(
110 currentItem ? isVideoFile(currentItem.attachment, currentItem.uploadedFile) : false
111 );
112 let isImage = $derived(
113 currentItem ? isImageFile(currentItem.attachment, currentItem.uploadedFile) : false
114 );
115 let isPdf = $derived(
116 currentItem ? isPdfFile(currentItem.attachment, currentItem.uploadedFile) : false
117 );
118 let isText = $derived(
119 currentItem ? isTextFile(currentItem.attachment, currentItem.uploadedFile) : false
120 );
121
122 let displayPreview = $derived(
123 currentItem?.uploadedFile?.preview ||
124 (isImage && currentItem?.attachment && 'base64Url' in currentItem.attachment
125 ? currentItem.attachment.base64Url
126 : currentItem?.preview)
127 );
128
129 let displayTextContent = $derived(
130 currentItem?.uploadedFile?.textContent ||
131 (currentItem?.attachment && 'content' in currentItem.attachment
132 ? currentItem.attachment.content
133 : currentItem?.textContent)
134 );
135
136 let language = $derived(getLanguageFromFilename(displayName));
137
138 let fileSize = $derived(currentItem?.size ? formatFileSize(currentItem.size) : '');
139
140 let hasVisionModality = $derived(
141 currentItem && activeModelId ? modelsStore.modelSupportsVision(activeModelId) : false
142 );
143
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
153 )
154 : null))
155 : null
156 );
157
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
167 )
168 : null))
169 : null
170 );
171
172 export function prev() {
173 currentIndex = currentIndex > 0 ? currentIndex - 1 : allItems.length - 1;
174 }
175
176 export function next() {
177 currentIndex = currentIndex < allItems.length - 1 ? currentIndex + 1 : 0;
178 }
179
180 function onNavigate(index: number) {
181 currentIndex = index;
182 }
183 </script>
184
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} />
188
189 <div class="flex h-full w-full flex-col items-center justify-start overflow-auto py-4">
190 {#if currentItem}
191 <ChatAttachmentsPreviewFileInfo {displayName} {fileSize} />
192
193 <ChatAttachmentsPreviewCurrentItem
194 {currentItem}
195 {isImage}
196 {isAudio}
197 {isVideo}
198 {isPdf}
199 {isText}
200 {displayPreview}
201 {displayTextContent}
202 {audioSrc}
203 {videoSrc}
204 {language}
205 {hasVisionModality}
206 {activeModelId}
207 />
208 {/if}
209
210 <ChatAttachmentsPreviewThumbnailStrip items={allItems} {currentIndex} {onNavigate} />
211 </div>
212 </div>
213 </div>