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