]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/blob
2e824ebd4164703e1b8d6c45da08b88954c13708
[pkg/ggml/sources/llama.cpp] /
1 <script lang="ts">
2 import { X, Music, Video } from '@lucide/svelte';
3 import {
4 formatFileSize,
5 getFileTypeLabel,
6 getPreviewText,
7 isPdfFile,
8 isAudioFile,
9 isVideoFile,
10 isTextFile
11 } from '$lib/utils';
12 import { ActionIcon } from '$lib/components/app';
13 import { AttachmentType } from '$lib/enums';
14
15 interface Props {
16 attachment?: DatabaseMessageExtra;
17 class?: string;
18 id: string;
19 onclick?: (event: MouseEvent) => void;
20 onRemove?: (id: string) => void;
21 name: string;
22 readonly?: boolean;
23 size?: number;
24 textContent?: string;
25 // Either uploaded file or stored attachment
26 uploadedFile?: ChatUploadedFile;
27 }
28
29 let {
30 attachment,
31 class: className = '',
32 id,
33 onclick,
34 onRemove,
35 name,
36 readonly = false,
37 size,
38 textContent,
39 uploadedFile
40 }: Props = $props();
41
42 let isPdf = $derived(isPdfFile(attachment, uploadedFile));
43 let isAudio = $derived(isAudioFile(attachment, uploadedFile));
44 let isVideo = $derived(isVideoFile(attachment, uploadedFile));
45 let isPdfWithContent = $derived(isPdf && !!textContent);
46
47 let isText = $derived(isTextFile(attachment, uploadedFile));
48 let isTextWithContent = $derived(isText && !!textContent);
49
50 let fileTypeLabel = $derived.by(() => {
51 if (uploadedFile?.type) {
52 return getFileTypeLabel(uploadedFile.type);
53 }
54
55 if (attachment) {
56 if ('mimeType' in attachment && attachment.mimeType) {
57 return getFileTypeLabel(attachment.mimeType);
58 }
59
60 if (attachment.type) {
61 return getFileTypeLabel(attachment.type);
62 }
63 }
64
65 return getFileTypeLabel(name);
66 });
67
68 let pdfProcessingMode = $derived.by(() => {
69 if (attachment?.type === AttachmentType.PDF) {
70 const pdfAttachment = attachment as DatabaseMessageExtraPdfFile;
71
72 return pdfAttachment.processedAsImages ? 'Sent as Image' : 'Sent as Text';
73 }
74
75 return null;
76 });
77 </script>
78
79 {#snippet textPreview(content: string)}
80 <div class="relative">
81 <div
82 class="font-mono text-xs leading-relaxed break-words whitespace-pre-wrap text-muted-foreground {!readonly
83 ? 'max-h-3rem line-height-1.2'
84 : ''}"
85 >
86 {getPreviewText(content)}
87 </div>
88
89 {#if content.length > 150}
90 <div
91 class="pointer-events-none absolute right-0 bottom-0 left-0 h-4 bg-gradient-to-t from-muted to-transparent {readonly
92 ? 'h-6'
93 : ''}"
94 ></div>
95 {/if}
96 </div>
97 {/snippet}
98
99 {#snippet removeButton()}
100 <div
101 class="absolute top-2 right-2 opacity-0 transition-opacity group-focus-within:opacity-100 group-hover:opacity-100"
102 >
103 <ActionIcon icon={X} tooltip="Remove" stopPropagationOnClick onclick={() => onRemove?.(id)} />
104 </div>
105 {/snippet}
106
107 {#snippet fileIcon()}
108 <div
109 class="flex h-8 w-8 items-center justify-center rounded bg-primary/10 text-xs font-medium text-primary"
110 >
111 {#if isAudio}
112 <Music class="h-4 w-4 text-white/70" />
113 {:else if isVideo}
114 <Video class="h-4 w-4 text-white/70" />
115 {:else}
116 {fileTypeLabel}
117 {/if}
118 </div>
119 {/snippet}
120
121 {#snippet info(text: string | undefined)}
122 {#if text}
123 <span class="text-xs text-muted-foreground">{text}</span>
124 {/if}
125 {/snippet}
126
127 {#if isTextWithContent || isPdfWithContent}
128 <button
129 aria-label={readonly ? `Preview ${name}` : undefined}
130 class="rounded-lg border border-border bg-muted p-3 {className} cursor-pointer {readonly
131 ? 'w-full max-w-2xl transition-shadow hover:shadow-md'
132 : `group relative text-left ${textContent ? 'max-h-24 max-w-72' : 'max-w-36'}`} overflow-hidden"
133 {onclick}
134 type="button"
135 >
136 {#if !readonly}
137 {@render removeButton()}
138 {/if}
139
140 <div class={[!readonly && 'pr-8', 'overflow-hidden']}>
141 {#if readonly}
142 <div class="flex items-start gap-3">
143 <div class="flex min-w-0 flex-1 flex-col items-start text-left">
144 <span class="w-full truncate text-sm font-medium text-foreground">{name}</span>
145
146 {@render info(pdfProcessingMode || (size ? formatFileSize(size) : undefined))}
147
148 {#if textContent}
149 {@render textPreview(textContent)}
150 {/if}
151 </div>
152 </div>
153 {:else}
154 <span class="mb-3 block truncate text-sm font-medium text-foreground">{name}</span>
155
156 {#if textContent}
157 {@render textPreview(textContent)}
158 {/if}
159 {/if}
160 </div>
161 </button>
162 {:else}
163 <button
164 class="group flex items-center gap-3 rounded-lg border border-border bg-muted p-3 {className} relative"
165 {onclick}
166 type="button"
167 >
168 {@render fileIcon()}
169
170 <div class="flex flex-col items-start gap-0.5">
171 <span
172 class="max-w-24 truncate text-sm font-medium text-foreground {readonly
173 ? ''
174 : 'group-hover:pr-6'} md:max-w-32"
175 >
176 {name}
177 </span>
178
179 {@render info(pdfProcessingMode || (size ? formatFileSize(size) : undefined))}
180 </div>
181
182 {#if !readonly}
183 {@render removeButton()}
184 {/if}
185 </button>
186 {/if}