Skip to content

Commit 4c91f26

Browse files
authored
Improved file naming & structure for UI components (ggml-org#17405)
* refactor: Component iles naming & structure * chore: update webui build output * refactor: Dialog titles + components namig * chore: update webui build output * refactor: Imports * chore: update webui build output
1 parent 92c0b38 commit 4c91f26

33 files changed

+962
-847
lines changed

tools/server/public/index.html.gz

-1.48 KB
Binary file not shown.
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
<script lang="ts">
2+
import { FileText, Image, Music, FileIcon, Eye } from '@lucide/svelte';
3+
import { FileTypeCategory, MimeTypeApplication } from '$lib/enums/files';
4+
import { convertPDFToImage } from '$lib/utils/pdf-processing';
5+
import { Button } from '$lib/components/ui/button';
6+
import { getFileTypeCategory } from '$lib/utils/file-type';
7+
8+
interface Props {
9+
// Either an uploaded file or a stored attachment
10+
uploadedFile?: ChatUploadedFile;
11+
attachment?: DatabaseMessageExtra;
12+
// For uploaded files
13+
preview?: string;
14+
name?: string;
15+
type?: string;
16+
textContent?: string;
17+
}
18+
19+
let { uploadedFile, attachment, preview, name, type, textContent }: Props = $props();
20+
21+
let displayName = $derived(uploadedFile?.name || attachment?.name || name || 'Unknown File');
22+
23+
let displayPreview = $derived(
24+
uploadedFile?.preview || (attachment?.type === 'imageFile' ? attachment.base64Url : preview)
25+
);
26+
27+
let displayType = $derived(
28+
uploadedFile?.type ||
29+
(attachment?.type === 'imageFile'
30+
? 'image'
31+
: attachment?.type === 'textFile'
32+
? 'text'
33+
: attachment?.type === 'audioFile'
34+
? attachment.mimeType || 'audio'
35+
: attachment?.type === 'pdfFile'
36+
? MimeTypeApplication.PDF
37+
: type || 'unknown')
38+
);
39+
40+
let displayTextContent = $derived(
41+
uploadedFile?.textContent ||
42+
(attachment?.type === 'textFile'
43+
? attachment.content
44+
: attachment?.type === 'pdfFile'
45+
? attachment.content
46+
: textContent)
47+
);
48+
49+
let isAudio = $derived(
50+
getFileTypeCategory(displayType) === FileTypeCategory.AUDIO || displayType === 'audio'
51+
);
52+
53+
let isImage = $derived(
54+
getFileTypeCategory(displayType) === FileTypeCategory.IMAGE || displayType === 'image'
55+
);
56+
57+
let isPdf = $derived(displayType === MimeTypeApplication.PDF);
58+
59+
let isText = $derived(
60+
getFileTypeCategory(displayType) === FileTypeCategory.TEXT || displayType === 'text'
61+
);
62+
63+
let IconComponent = $derived(() => {
64+
if (isImage) return Image;
65+
if (isText || isPdf) return FileText;
66+
if (isAudio) return Music;
67+
68+
return FileIcon;
69+
});
70+
71+
let pdfViewMode = $state<'text' | 'pages'>('pages');
72+
73+
let pdfImages = $state<string[]>([]);
74+
75+
let pdfImagesLoading = $state(false);
76+
77+
let pdfImagesError = $state<string | null>(null);
78+
79+
async function loadPdfImages() {
80+
if (!isPdf || pdfImages.length > 0 || pdfImagesLoading) return;
81+
82+
pdfImagesLoading = true;
83+
pdfImagesError = null;
84+
85+
try {
86+
let file: File | null = null;
87+
88+
if (uploadedFile?.file) {
89+
file = uploadedFile.file;
90+
} else if (attachment?.type === 'pdfFile') {
91+
// Check if we have pre-processed images
92+
if (attachment.images && Array.isArray(attachment.images)) {
93+
pdfImages = attachment.images;
94+
return;
95+
}
96+
97+
// Convert base64 back to File for processing
98+
if (attachment.base64Data) {
99+
const base64Data = attachment.base64Data;
100+
const byteCharacters = atob(base64Data);
101+
const byteNumbers = new Array(byteCharacters.length);
102+
for (let i = 0; i < byteCharacters.length; i++) {
103+
byteNumbers[i] = byteCharacters.charCodeAt(i);
104+
}
105+
const byteArray = new Uint8Array(byteNumbers);
106+
file = new File([byteArray], displayName, { type: MimeTypeApplication.PDF });
107+
}
108+
}
109+
110+
if (file) {
111+
pdfImages = await convertPDFToImage(file);
112+
} else {
113+
throw new Error('No PDF file available for conversion');
114+
}
115+
} catch (error) {
116+
pdfImagesError = error instanceof Error ? error.message : 'Failed to load PDF images';
117+
} finally {
118+
pdfImagesLoading = false;
119+
}
120+
}
121+
122+
export function reset() {
123+
pdfImages = [];
124+
pdfImagesLoading = false;
125+
pdfImagesError = null;
126+
pdfViewMode = 'pages';
127+
}
128+
129+
$effect(() => {
130+
if (isPdf && pdfViewMode === 'pages') {
131+
loadPdfImages();
132+
}
133+
});
134+
</script>
135+
136+
<div class="space-y-4">
137+
<div class="flex items-center justify-end gap-6">
138+
{#if isPdf}
139+
<div class="flex items-center gap-2">
140+
<Button
141+
variant={pdfViewMode === 'text' ? 'default' : 'outline'}
142+
size="sm"
143+
onclick={() => (pdfViewMode = 'text')}
144+
disabled={pdfImagesLoading}
145+
>
146+
<FileText class="mr-1 h-4 w-4" />
147+
148+
Text
149+
</Button>
150+
151+
<Button
152+
variant={pdfViewMode === 'pages' ? 'default' : 'outline'}
153+
size="sm"
154+
onclick={() => {
155+
pdfViewMode = 'pages';
156+
loadPdfImages();
157+
}}
158+
disabled={pdfImagesLoading}
159+
>
160+
{#if pdfImagesLoading}
161+
<div
162+
class="mr-1 h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"
163+
></div>
164+
{:else}
165+
<Eye class="mr-1 h-4 w-4" />
166+
{/if}
167+
168+
Pages
169+
</Button>
170+
</div>
171+
{/if}
172+
</div>
173+
174+
<div class="flex-1 overflow-auto">
175+
{#if isImage && displayPreview}
176+
<div class="flex items-center justify-center">
177+
<img
178+
src={displayPreview}
179+
alt={displayName}
180+
class="max-h-full rounded-lg object-contain shadow-lg"
181+
/>
182+
</div>
183+
{:else if isPdf && pdfViewMode === 'pages'}
184+
{#if pdfImagesLoading}
185+
<div class="flex items-center justify-center p-8">
186+
<div class="text-center">
187+
<div
188+
class="mx-auto mb-4 h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"
189+
></div>
190+
191+
<p class="text-muted-foreground">Converting PDF to images...</p>
192+
</div>
193+
</div>
194+
{:else if pdfImagesError}
195+
<div class="flex items-center justify-center p-8">
196+
<div class="text-center">
197+
<FileText class="mx-auto mb-4 h-16 w-16 text-muted-foreground" />
198+
199+
<p class="mb-4 text-muted-foreground">Failed to load PDF images</p>
200+
201+
<p class="text-sm text-muted-foreground">{pdfImagesError}</p>
202+
203+
<Button class="mt-4" onclick={() => (pdfViewMode = 'text')}>View as Text</Button>
204+
</div>
205+
</div>
206+
{:else if pdfImages.length > 0}
207+
<div class="max-h-[70vh] space-y-4 overflow-auto">
208+
{#each pdfImages as image, index (image)}
209+
<div class="text-center">
210+
<p class="mb-2 text-sm text-muted-foreground">Page {index + 1}</p>
211+
212+
<img
213+
src={image}
214+
alt="PDF Page {index + 1}"
215+
class="mx-auto max-w-full rounded-lg shadow-lg"
216+
/>
217+
</div>
218+
{/each}
219+
</div>
220+
{:else}
221+
<div class="flex items-center justify-center p-8">
222+
<div class="text-center">
223+
<FileText class="mx-auto mb-4 h-16 w-16 text-muted-foreground" />
224+
225+
<p class="mb-4 text-muted-foreground">No PDF pages available</p>
226+
</div>
227+
</div>
228+
{/if}
229+
{:else if (isText || (isPdf && pdfViewMode === 'text')) && displayTextContent}
230+
<div
231+
class="max-h-[60vh] overflow-auto rounded-lg bg-muted p-4 font-mono text-sm break-words whitespace-pre-wrap"
232+
>
233+
{displayTextContent}
234+
</div>
235+
{:else if isAudio}
236+
<div class="flex items-center justify-center p-8">
237+
<div class="w-full max-w-md text-center">
238+
<Music class="mx-auto mb-4 h-16 w-16 text-muted-foreground" />
239+
240+
{#if attachment?.type === 'audioFile'}
241+
<audio
242+
controls
243+
class="mb-4 w-full"
244+
src="data:{attachment.mimeType};base64,{attachment.base64Data}"
245+
>
246+
Your browser does not support the audio element.
247+
</audio>
248+
{:else if uploadedFile?.preview}
249+
<audio controls class="mb-4 w-full" src={uploadedFile.preview}>
250+
Your browser does not support the audio element.
251+
</audio>
252+
{:else}
253+
<p class="mb-4 text-muted-foreground">Audio preview not available</p>
254+
{/if}
255+
256+
<p class="text-sm text-muted-foreground">
257+
{displayName}
258+
</p>
259+
</div>
260+
</div>
261+
{:else}
262+
<div class="flex items-center justify-center p-8">
263+
<div class="text-center">
264+
{#if IconComponent}
265+
<IconComponent class="mx-auto mb-4 h-16 w-16 text-muted-foreground" />
266+
{/if}
267+
268+
<p class="mb-4 text-muted-foreground">Preview not available for this file type</p>
269+
</div>
270+
</div>
271+
{/if}
272+
</div>
273+
</div>

0 commit comments

Comments
 (0)