]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/blob
750532a6266019719ba8e74c0fd166d0be95233c
[pkg/ggml/sources/llama.cpp] /
1 <script lang="ts">
2 import type { ChatAttachmentDisplayItem } from '$lib/types';
3 import { FileText, Eye, Info } from '@lucide/svelte';
4 import { Button } from '$lib/components/ui/button';
5 import * as Alert from '$lib/components/ui/alert';
6 import { SyntaxHighlightedCode } from '$lib/components/app';
7 import { getLanguageFromFilename } from '$lib/utils';
8 import { convertPDFToImage } from '$lib/utils/browser-only';
9 import { PdfViewMode } from '$lib/enums';
10
11 interface Props {
12 currentItem: ChatAttachmentDisplayItem | null;
13 displayName: string;
14 displayTextContent: string | undefined;
15 hasVisionModality: boolean;
16 activeModelId?: string;
17 }
18
19 let { currentItem, displayName, displayTextContent, hasVisionModality, activeModelId }: Props =
20 $props();
21
22 let pdfViewMode = $state<PdfViewMode>(PdfViewMode.PAGES);
23 let pdfImages = $state<string[]>([]);
24 let pdfImagesLoading = $state(false);
25 let pdfImagesError = $state<string | null>(null);
26
27 let language = $derived(getLanguageFromFilename(displayName));
28
29 async function loadPdfImages() {
30 if (pdfImages.length > 0 || pdfImagesLoading || !currentItem) return;
31
32 pdfImagesLoading = true;
33 pdfImagesError = null;
34
35 try {
36 let file: File | null = null;
37
38 if (currentItem.uploadedFile?.file) {
39 file = currentItem.uploadedFile.file;
40 } else if (currentItem.attachment) {
41 // Check if we have pre-processed images
42 if (
43 'images' in currentItem.attachment &&
44 currentItem.attachment.images &&
45 Array.isArray(currentItem.attachment.images) &&
46 currentItem.attachment.images.length > 0
47 ) {
48 pdfImages = currentItem.attachment.images;
49 return;
50 }
51
52 // Convert base64 back to File for processing
53 if ('base64Data' in currentItem.attachment && currentItem.attachment.base64Data) {
54 const base64Data = currentItem.attachment.base64Data;
55 const byteCharacters = atob(base64Data);
56 const byteNumbers = new Array(byteCharacters.length);
57 for (let i = 0; i < byteCharacters.length; i++) {
58 byteNumbers[i] = byteCharacters.charCodeAt(i);
59 }
60 const byteArray = new Uint8Array(byteNumbers);
61 file = new File([byteArray], displayName, { type: 'application/pdf' });
62 }
63 }
64
65 if (file) {
66 pdfImages = await convertPDFToImage(file);
67 } else {
68 throw new Error('No PDF file available for conversion');
69 }
70 } catch (error) {
71 pdfImagesError = error instanceof Error ? error.message : 'Failed to load PDF images';
72 } finally {
73 pdfImagesLoading = false;
74 }
75 }
76
77 $effect(() => {
78 if (pdfViewMode === PdfViewMode.PAGES) {
79 loadPdfImages();
80 }
81 });
82 </script>
83
84 <div class="mb-4 flex items-center justify-end gap-2">
85 <Button
86 variant={pdfViewMode === PdfViewMode.TEXT ? 'default' : 'outline'}
87 size="sm"
88 onclick={() => (pdfViewMode = PdfViewMode.TEXT)}
89 disabled={pdfImagesLoading}
90 >
91 <FileText class="mr-1 h-4 w-4" />
92 Text
93 </Button>
94
95 <Button
96 variant={pdfViewMode === PdfViewMode.PAGES ? 'default' : 'outline'}
97 size="sm"
98 onclick={() => (pdfViewMode = PdfViewMode.PAGES)}
99 disabled={pdfImagesLoading}
100 >
101 {#if pdfImagesLoading}
102 <div
103 class="mr-1 h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"
104 ></div>
105 {:else}
106 <Eye class="mr-1 h-4 w-4" />
107 {/if}
108 Pages
109 </Button>
110 </div>
111
112 {#if !hasVisionModality && activeModelId && currentItem}
113 <Alert.Root class="mb-4 max-w-4xl">
114 <Info class="h-4 w-4" />
115 <Alert.Title>Preview only</Alert.Title>
116 <Alert.Description>
117 <span class="inline-flex">
118 The selected model does not support vision. Only the extracted
119 <!-- svelte-ignore a11y_click_events_have_key_events -->
120 <!-- svelte-ignore a11y_no_static_element_interactions -->
121 <span
122 class="mx-1 cursor-pointer underline"
123 onclick={() => (pdfViewMode = PdfViewMode.TEXT)}
124 >
125 text
126 </span>
127 will be sent to the model.
128 </span>
129 </Alert.Description>
130 </Alert.Root>
131 {/if}
132
133 {#if pdfImagesLoading}
134 <div class="flex flex-1 items-center justify-center p-8">
135 <div class="text-center">
136 <div
137 class="mx-auto mb-4 h-8 w-8 animate-spin rounded-full border-4 border-white border-t-transparent"
138 ></div>
139 <p class="text-white/70">Converting PDF to images...</p>
140 </div>
141 </div>
142 {:else if pdfImagesError}
143 <div class="flex flex-1 items-center justify-center p-8">
144 <div class="text-center">
145 <FileText class="mx-auto mb-4 h-16 w-16 text-white/50" />
146 <p class="mb-4 text-white/70">Failed to load PDF images</p>
147 <p class="text-sm text-white/50">{pdfImagesError}</p>
148 </div>
149 </div>
150 {:else if pdfImages.length > 0}
151 {#each pdfImages as image, index (image)}
152 <p class="mb-2 text-sm text-white/50">Page {index + 1}</p>
153 <img src={image} alt="PDF Page {index + 1}" class="mx-auto max-w-[85vw] rounded-lg shadow-lg" />
154 <div class="h-4"></div>
155 {/each}
156 {:else}
157 <div class="flex flex-1 items-center justify-center p-8">
158 <div class="text-center">
159 <FileText class="mx-auto mb-4 h-16 w-16 text-white/50" />
160 <p class="text-white/70">No PDF pages available</p>
161 </div>
162 </div>
163 {/if}
164
165 {#if pdfViewMode === PdfViewMode.TEXT && displayTextContent}
166 <div class="px-4 pb-4">
167 <SyntaxHighlightedCode
168 class="max-w-4xl"
169 code={displayTextContent}
170 {language}
171 maxHeight="none"
172 />
173 </div>
174 {/if}