refactor: Attachments data
This commit is contained in:
parent
1f0cb3ab26
commit
b7ba13b6a0
|
|
@ -1,12 +1,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { FileText, Image, Music, FileIcon, Eye } from '@lucide/svelte';
|
import { FileText, Image, Music, FileIcon, Eye } from '@lucide/svelte';
|
||||||
import { FileTypeCategory, MimeTypeApplication } from '$lib/enums/files';
|
|
||||||
import { ModelModality } from '$lib/enums/model';
|
|
||||||
import { AttachmentType } from '$lib/enums/attachment';
|
|
||||||
import type { DatabaseMessageExtra } from '$lib/types/database';
|
import type { DatabaseMessageExtra } from '$lib/types/database';
|
||||||
import { convertPDFToImage } from '$lib/utils/pdf-processing';
|
import { convertPDFToImage } from '$lib/utils/pdf-processing';
|
||||||
import { getFileTypeCategory } from '$lib/utils/file-type';
|
import { isTextFile, isImageFile, isPdfFile, isAudioFile } from '$lib/utils/attachment-type';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
// Either an uploaded file or a stored attachment
|
// Either an uploaded file or a stored attachment
|
||||||
|
|
@ -15,55 +12,27 @@
|
||||||
// For uploaded files
|
// For uploaded files
|
||||||
preview?: string;
|
preview?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
type?: string;
|
|
||||||
textContent?: string;
|
textContent?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { uploadedFile, attachment, preview, name, type, textContent }: Props = $props();
|
let { uploadedFile, attachment, preview, name, textContent }: Props = $props();
|
||||||
|
|
||||||
let displayName = $derived(uploadedFile?.name || attachment?.name || name || 'Unknown File');
|
let displayName = $derived(uploadedFile?.name || attachment?.name || name || 'Unknown File');
|
||||||
|
|
||||||
|
// Determine file type from uploaded file or attachment
|
||||||
|
let isAudio = $derived(isAudioFile(attachment, uploadedFile));
|
||||||
|
let isImage = $derived(isImageFile(attachment, uploadedFile));
|
||||||
|
let isPdf = $derived(isPdfFile(attachment, uploadedFile));
|
||||||
|
let isText = $derived(isTextFile(attachment, uploadedFile));
|
||||||
|
|
||||||
let displayPreview = $derived(
|
let displayPreview = $derived(
|
||||||
uploadedFile?.preview ||
|
uploadedFile?.preview ||
|
||||||
(attachment?.type === AttachmentType.IMAGE ? attachment.base64Url : preview)
|
(isImage && attachment && 'base64Url' in attachment ? attachment.base64Url : preview)
|
||||||
);
|
|
||||||
|
|
||||||
let displayType = $derived(
|
|
||||||
uploadedFile
|
|
||||||
? uploadedFile.type
|
|
||||||
: attachment?.type === AttachmentType.IMAGE
|
|
||||||
? 'image'
|
|
||||||
: attachment?.type === AttachmentType.TEXT
|
|
||||||
? 'text'
|
|
||||||
: attachment?.type === AttachmentType.AUDIO
|
|
||||||
? attachment.mimeType || ModelModality.AUDIO
|
|
||||||
: attachment?.type === AttachmentType.PDF
|
|
||||||
? MimeTypeApplication.PDF
|
|
||||||
: type || 'unknown'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let displayTextContent = $derived(
|
let displayTextContent = $derived(
|
||||||
uploadedFile?.textContent ||
|
uploadedFile?.textContent ||
|
||||||
(attachment?.type === AttachmentType.TEXT
|
(attachment && 'content' in attachment ? attachment.content : textContent)
|
||||||
? attachment.content
|
|
||||||
: attachment?.type === AttachmentType.PDF
|
|
||||||
? attachment.content
|
|
||||||
: textContent)
|
|
||||||
);
|
|
||||||
|
|
||||||
let isAudio = $derived(
|
|
||||||
getFileTypeCategory(displayType) === FileTypeCategory.AUDIO ||
|
|
||||||
displayType === ModelModality.AUDIO
|
|
||||||
);
|
|
||||||
|
|
||||||
let isImage = $derived(
|
|
||||||
getFileTypeCategory(displayType) === FileTypeCategory.IMAGE || displayType === 'image'
|
|
||||||
);
|
|
||||||
|
|
||||||
let isPdf = $derived(displayType === MimeTypeApplication.PDF);
|
|
||||||
|
|
||||||
let isText = $derived(
|
|
||||||
getFileTypeCategory(displayType) === FileTypeCategory.TEXT || displayType === 'text'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let IconComponent = $derived(() => {
|
let IconComponent = $derived(() => {
|
||||||
|
|
@ -93,15 +62,20 @@
|
||||||
|
|
||||||
if (uploadedFile?.file) {
|
if (uploadedFile?.file) {
|
||||||
file = uploadedFile.file;
|
file = uploadedFile.file;
|
||||||
} else if (attachment?.type === AttachmentType.PDF) {
|
} else if (isPdf && attachment) {
|
||||||
// Check if we have pre-processed images
|
// Check if we have pre-processed images
|
||||||
if (attachment.images && Array.isArray(attachment.images) && attachment.images.length > 0) {
|
if (
|
||||||
|
'images' in attachment &&
|
||||||
|
attachment.images &&
|
||||||
|
Array.isArray(attachment.images) &&
|
||||||
|
attachment.images.length > 0
|
||||||
|
) {
|
||||||
pdfImages = attachment.images;
|
pdfImages = attachment.images;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert base64 back to File for processing
|
// Convert base64 back to File for processing
|
||||||
if (attachment.base64Data) {
|
if ('base64Data' in attachment && attachment.base64Data) {
|
||||||
const base64Data = attachment.base64Data;
|
const base64Data = attachment.base64Data;
|
||||||
const byteCharacters = atob(base64Data);
|
const byteCharacters = atob(base64Data);
|
||||||
const byteNumbers = new Array(byteCharacters.length);
|
const byteNumbers = new Array(byteCharacters.length);
|
||||||
|
|
@ -109,7 +83,7 @@
|
||||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||||
}
|
}
|
||||||
const byteArray = new Uint8Array(byteNumbers);
|
const byteArray = new Uint8Array(byteNumbers);
|
||||||
file = new File([byteArray], displayName, { type: MimeTypeApplication.PDF });
|
file = new File([byteArray], displayName, { type: 'application/pdf' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -243,18 +217,18 @@
|
||||||
<div class="w-full max-w-md text-center">
|
<div class="w-full max-w-md text-center">
|
||||||
<Music class="mx-auto mb-4 h-16 w-16 text-muted-foreground" />
|
<Music class="mx-auto mb-4 h-16 w-16 text-muted-foreground" />
|
||||||
|
|
||||||
{#if attachment?.type === AttachmentType.AUDIO}
|
{#if uploadedFile?.preview}
|
||||||
|
<audio controls class="mb-4 w-full" src={uploadedFile.preview}>
|
||||||
|
Your browser does not support the audio element.
|
||||||
|
</audio>
|
||||||
|
{:else if isAudio && attachment && 'mimeType' in attachment && 'base64Data' in attachment}
|
||||||
<audio
|
<audio
|
||||||
controls
|
controls
|
||||||
class="mb-4 w-full"
|
class="mb-4 w-full"
|
||||||
src="data:{attachment.mimeType};base64,{attachment.base64Data}"
|
src={`data:${attachment.mimeType};base64,${attachment.base64Data}`}
|
||||||
>
|
>
|
||||||
Your browser does not support the audio element.
|
Your browser does not support the audio element.
|
||||||
</audio>
|
</audio>
|
||||||
{:else if uploadedFile?.preview}
|
|
||||||
<audio controls class="mb-4 w-full" src={uploadedFile.preview}>
|
|
||||||
Your browser does not support the audio element.
|
|
||||||
</audio>
|
|
||||||
{:else}
|
{:else}
|
||||||
<p class="mb-4 text-muted-foreground">Audio preview not available</p>
|
<p class="mb-4 text-muted-foreground">Audio preview not available</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { RemoveButton } from '$lib/components/app';
|
import { RemoveButton } from '$lib/components/app';
|
||||||
import { formatFileSize, getFileTypeLabel, getPreviewText } from '$lib/utils/file-preview';
|
import { formatFileSize, getFileTypeLabel, getPreviewText } from '$lib/utils/file-preview';
|
||||||
import { FileTypeCategory, MimeTypeText } from '$lib/enums/files';
|
import { isTextFile } from '$lib/utils/attachment-type';
|
||||||
|
import type { DatabaseMessageExtra } from '$lib/types/database';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
class?: string;
|
class?: string;
|
||||||
|
|
@ -12,7 +13,9 @@
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
size?: number;
|
size?: number;
|
||||||
textContent?: string;
|
textContent?: string;
|
||||||
type: string;
|
// Either uploaded file or stored attachment
|
||||||
|
uploadedFile?: ChatUploadedFile;
|
||||||
|
attachment?: DatabaseMessageExtra;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|
@ -24,11 +27,17 @@
|
||||||
readonly = false,
|
readonly = false,
|
||||||
size,
|
size,
|
||||||
textContent,
|
textContent,
|
||||||
type
|
uploadedFile,
|
||||||
|
attachment
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
|
let isText = $derived(isTextFile(attachment, uploadedFile));
|
||||||
|
|
||||||
|
// Get file type for display
|
||||||
|
let fileType = $derived(uploadedFile?.type || 'unknown');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if type === MimeTypeText.PLAIN || type === FileTypeCategory.TEXT}
|
{#if isText}
|
||||||
{#if readonly}
|
{#if readonly}
|
||||||
<!-- Readonly mode (ChatMessage) -->
|
<!-- Readonly mode (ChatMessage) -->
|
||||||
<button
|
<button
|
||||||
|
|
@ -45,7 +54,7 @@
|
||||||
<span class="text-xs text-muted-foreground">{formatFileSize(size)}</span>
|
<span class="text-xs text-muted-foreground">{formatFileSize(size)}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if textContent && type === 'text'}
|
{#if textContent}
|
||||||
<div class="relative mt-2 w-full">
|
<div class="relative mt-2 w-full">
|
||||||
<div
|
<div
|
||||||
class="overflow-hidden font-mono text-xs leading-relaxed break-words whitespace-pre-wrap text-muted-foreground"
|
class="overflow-hidden font-mono text-xs leading-relaxed break-words whitespace-pre-wrap text-muted-foreground"
|
||||||
|
|
@ -105,7 +114,7 @@
|
||||||
<div
|
<div
|
||||||
class="flex h-8 w-8 items-center justify-center rounded bg-primary/10 text-xs font-medium text-primary"
|
class="flex h-8 w-8 items-center justify-center rounded bg-primary/10 text-xs font-medium text-primary"
|
||||||
>
|
>
|
||||||
{getFileTypeLabel(type)}
|
{getFileTypeLabel(fileType)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@
|
||||||
import { ChevronLeft, ChevronRight } from '@lucide/svelte';
|
import { ChevronLeft, ChevronRight } from '@lucide/svelte';
|
||||||
import { getFileTypeCategory } from '$lib/utils/file-type';
|
import { getFileTypeCategory } from '$lib/utils/file-type';
|
||||||
import { FileTypeCategory } from '$lib/enums/files';
|
import { FileTypeCategory } from '$lib/enums/files';
|
||||||
import { ModelModality } from '$lib/enums/model';
|
import { isImageFile } from '$lib/utils/attachment-type';
|
||||||
import { AttachmentType } from '$lib/enums/attachment';
|
|
||||||
import { DialogChatAttachmentPreview, DialogChatAttachmentsViewAll } from '$lib/components/app';
|
import { DialogChatAttachmentPreview, DialogChatAttachmentsViewAll } from '$lib/components/app';
|
||||||
import type { ChatAttachmentDisplayItem, ChatAttachmentPreviewItem } from '$lib/types/chat';
|
import type { ChatAttachmentDisplayItem, ChatAttachmentPreviewItem } from '$lib/types/chat';
|
||||||
import type { DatabaseMessageExtra } from '$lib/types/database';
|
import type { DatabaseMessageExtra } from '$lib/types/database';
|
||||||
|
|
@ -62,7 +61,6 @@
|
||||||
name: file.name,
|
name: file.name,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
preview: file.preview,
|
preview: file.preview,
|
||||||
type: file.type,
|
|
||||||
isImage: getFileTypeCategory(file.type) === FileTypeCategory.IMAGE,
|
isImage: getFileTypeCategory(file.type) === FileTypeCategory.IMAGE,
|
||||||
uploadedFile: file,
|
uploadedFile: file,
|
||||||
textContent: file.textContent
|
textContent: file.textContent
|
||||||
|
|
@ -71,56 +69,17 @@
|
||||||
|
|
||||||
// Add stored attachments (ChatMessage)
|
// Add stored attachments (ChatMessage)
|
||||||
for (const [index, attachment] of attachments.entries()) {
|
for (const [index, attachment] of attachments.entries()) {
|
||||||
if (attachment.type === AttachmentType.IMAGE) {
|
const isImage = isImageFile(attachment);
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
items.push({
|
||||||
name: attachment.name,
|
id: `attachment-${index}`,
|
||||||
preview: attachment.base64Url,
|
name: attachment.name,
|
||||||
type: 'image',
|
preview: isImage && 'base64Url' in attachment ? attachment.base64Url : undefined,
|
||||||
isImage: true,
|
isImage,
|
||||||
attachment,
|
attachment,
|
||||||
attachmentIndex: index
|
attachmentIndex: index,
|
||||||
});
|
textContent: 'content' in attachment ? attachment.content : undefined
|
||||||
} else if (attachment.type === AttachmentType.TEXT) {
|
});
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
|
||||||
name: attachment.name,
|
|
||||||
type: 'text',
|
|
||||||
isImage: false,
|
|
||||||
attachment,
|
|
||||||
attachmentIndex: index,
|
|
||||||
textContent: attachment.content
|
|
||||||
});
|
|
||||||
} else if (attachment.type === AttachmentType.AUDIO) {
|
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
|
||||||
name: attachment.name,
|
|
||||||
type: attachment.mimeType || ModelModality.AUDIO,
|
|
||||||
isImage: false,
|
|
||||||
attachment,
|
|
||||||
attachmentIndex: index
|
|
||||||
});
|
|
||||||
} else if (attachment.type === AttachmentType.PDF) {
|
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
|
||||||
name: attachment.name,
|
|
||||||
type: 'application/pdf',
|
|
||||||
isImage: false,
|
|
||||||
attachment,
|
|
||||||
attachmentIndex: index
|
|
||||||
});
|
|
||||||
} else if (attachment.type === AttachmentType.LEGACY_CONTEXT) {
|
|
||||||
// Legacy format from old webui - treat as text file
|
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
|
||||||
name: attachment.name,
|
|
||||||
type: 'text',
|
|
||||||
isImage: false,
|
|
||||||
attachment,
|
|
||||||
attachmentIndex: index,
|
|
||||||
textContent: attachment.content
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.reverse();
|
return items.reverse();
|
||||||
|
|
@ -135,7 +94,6 @@
|
||||||
attachment: item.attachment,
|
attachment: item.attachment,
|
||||||
preview: item.preview,
|
preview: item.preview,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
type: item.type,
|
|
||||||
size: item.size,
|
size: item.size,
|
||||||
textContent: item.textContent
|
textContent: item.textContent
|
||||||
};
|
};
|
||||||
|
|
@ -223,11 +181,12 @@
|
||||||
: ''}"
|
: ''}"
|
||||||
id={item.id}
|
id={item.id}
|
||||||
name={item.name}
|
name={item.name}
|
||||||
type={item.type}
|
|
||||||
size={item.size}
|
size={item.size}
|
||||||
{readonly}
|
{readonly}
|
||||||
onRemove={onFileRemove}
|
onRemove={onFileRemove}
|
||||||
textContent={item.textContent}
|
textContent={item.textContent}
|
||||||
|
attachment={item.attachment}
|
||||||
|
uploadedFile={item.uploadedFile}
|
||||||
onClick={(event) => openPreview(item, event)}
|
onClick={(event) => openPreview(item, event)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -279,12 +238,13 @@
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
id={item.id}
|
id={item.id}
|
||||||
name={item.name}
|
name={item.name}
|
||||||
type={item.type}
|
|
||||||
size={item.size}
|
size={item.size}
|
||||||
{readonly}
|
{readonly}
|
||||||
onRemove={onFileRemove}
|
onRemove={onFileRemove}
|
||||||
textContent={item.textContent}
|
textContent={item.textContent}
|
||||||
onClick={(event) => openPreview(item, event)}
|
attachment={item.attachment}
|
||||||
|
uploadedFile={item.uploadedFile}
|
||||||
|
onClick={(event?: MouseEvent) => openPreview(item, event)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
@ -300,7 +260,6 @@
|
||||||
attachment={previewItem.attachment}
|
attachment={previewItem.attachment}
|
||||||
preview={previewItem.preview}
|
preview={previewItem.preview}
|
||||||
name={previewItem.name}
|
name={previewItem.name}
|
||||||
type={previewItem.type}
|
|
||||||
size={previewItem.size}
|
size={previewItem.size}
|
||||||
textContent={previewItem.textContent}
|
textContent={previewItem.textContent}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,8 @@
|
||||||
DialogChatAttachmentPreview
|
DialogChatAttachmentPreview
|
||||||
} from '$lib/components/app';
|
} from '$lib/components/app';
|
||||||
import { FileTypeCategory } from '$lib/enums/files';
|
import { FileTypeCategory } from '$lib/enums/files';
|
||||||
import { ModelModality } from '$lib/enums/model';
|
|
||||||
import { AttachmentType } from '$lib/enums/attachment';
|
|
||||||
import { getFileTypeCategory } from '$lib/utils/file-type';
|
import { getFileTypeCategory } from '$lib/utils/file-type';
|
||||||
|
import { isImageFile } from '$lib/utils/attachment-type';
|
||||||
import type { ChatAttachmentDisplayItem, ChatAttachmentPreviewItem } from '$lib/types/chat';
|
import type { ChatAttachmentDisplayItem, ChatAttachmentPreviewItem } from '$lib/types/chat';
|
||||||
import type { DatabaseMessageExtra } from '$lib/types/database';
|
import type { DatabaseMessageExtra } from '$lib/types/database';
|
||||||
|
|
||||||
|
|
@ -47,7 +46,6 @@
|
||||||
name: file.name,
|
name: file.name,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
preview: file.preview,
|
preview: file.preview,
|
||||||
type: file.type,
|
|
||||||
isImage: getFileTypeCategory(file.type) === FileTypeCategory.IMAGE,
|
isImage: getFileTypeCategory(file.type) === FileTypeCategory.IMAGE,
|
||||||
uploadedFile: file,
|
uploadedFile: file,
|
||||||
textContent: file.textContent
|
textContent: file.textContent
|
||||||
|
|
@ -55,56 +53,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [index, attachment] of attachments.entries()) {
|
for (const [index, attachment] of attachments.entries()) {
|
||||||
if (attachment.type === AttachmentType.IMAGE) {
|
const isImage = isImageFile(attachment);
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
items.push({
|
||||||
name: attachment.name,
|
id: `attachment-${index}`,
|
||||||
preview: attachment.base64Url,
|
name: attachment.name,
|
||||||
type: 'image',
|
preview: isImage && 'base64Url' in attachment ? attachment.base64Url : undefined,
|
||||||
isImage: true,
|
isImage,
|
||||||
attachment,
|
attachment,
|
||||||
attachmentIndex: index
|
attachmentIndex: index,
|
||||||
});
|
textContent: 'content' in attachment ? attachment.content : undefined
|
||||||
} else if (attachment.type === AttachmentType.TEXT) {
|
});
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
|
||||||
name: attachment.name,
|
|
||||||
type: 'text',
|
|
||||||
isImage: false,
|
|
||||||
attachment,
|
|
||||||
attachmentIndex: index,
|
|
||||||
textContent: attachment.content
|
|
||||||
});
|
|
||||||
} else if (attachment.type === AttachmentType.AUDIO) {
|
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
|
||||||
name: attachment.name,
|
|
||||||
type: attachment.mimeType || ModelModality.AUDIO,
|
|
||||||
isImage: false,
|
|
||||||
attachment,
|
|
||||||
attachmentIndex: index
|
|
||||||
});
|
|
||||||
} else if (attachment.type === AttachmentType.PDF) {
|
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
|
||||||
name: attachment.name,
|
|
||||||
type: 'application/pdf',
|
|
||||||
isImage: false,
|
|
||||||
attachment,
|
|
||||||
attachmentIndex: index
|
|
||||||
});
|
|
||||||
} else if (attachment.type === AttachmentType.LEGACY_CONTEXT) {
|
|
||||||
// Legacy format from old webui - treat as text file
|
|
||||||
items.push({
|
|
||||||
id: `attachment-${index}`,
|
|
||||||
name: attachment.name,
|
|
||||||
type: 'text',
|
|
||||||
isImage: false,
|
|
||||||
attachment,
|
|
||||||
attachmentIndex: index,
|
|
||||||
textContent: attachment.content
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.reverse();
|
return items.reverse();
|
||||||
|
|
@ -121,7 +80,6 @@
|
||||||
attachment: item.attachment,
|
attachment: item.attachment,
|
||||||
preview: item.preview,
|
preview: item.preview,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
type: item.type,
|
|
||||||
size: item.size,
|
size: item.size,
|
||||||
textContent: item.textContent
|
textContent: item.textContent
|
||||||
};
|
};
|
||||||
|
|
@ -140,12 +98,13 @@
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
id={item.id}
|
id={item.id}
|
||||||
name={item.name}
|
name={item.name}
|
||||||
type={item.type}
|
|
||||||
size={item.size}
|
size={item.size}
|
||||||
{readonly}
|
{readonly}
|
||||||
onRemove={onFileRemove}
|
onRemove={onFileRemove}
|
||||||
textContent={item.textContent}
|
textContent={item.textContent}
|
||||||
onClick={(event) => openPreview(item, event)}
|
attachment={item.attachment}
|
||||||
|
uploadedFile={item.uploadedFile}
|
||||||
|
onClick={(event?: MouseEvent) => openPreview(item, event)}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -185,7 +144,6 @@
|
||||||
attachment={previewItem.attachment}
|
attachment={previewItem.attachment}
|
||||||
preview={previewItem.preview}
|
preview={previewItem.preview}
|
||||||
name={previewItem.name}
|
name={previewItem.name}
|
||||||
type={previewItem.type}
|
|
||||||
size={previewItem.size}
|
size={previewItem.size}
|
||||||
textContent={previewItem.textContent}
|
textContent={previewItem.textContent}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Dialog from '$lib/components/ui/dialog';
|
import * as Dialog from '$lib/components/ui/dialog';
|
||||||
import { ModelModality } from '$lib/enums/model';
|
|
||||||
import { AttachmentType } from '$lib/enums/attachment';
|
|
||||||
import type { DatabaseMessageExtra } from '$lib/types/database';
|
import type { DatabaseMessageExtra } from '$lib/types/database';
|
||||||
import { ChatAttachmentPreview } from '$lib/components/app';
|
import { ChatAttachmentPreview } from '$lib/components/app';
|
||||||
import { formatFileSize } from '$lib/utils/file-preview';
|
import { formatFileSize } from '$lib/utils/file-preview';
|
||||||
|
import { getAttachmentTypeLabel } from '$lib/utils/attachment-type';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|
@ -15,7 +14,6 @@
|
||||||
// For uploaded files
|
// For uploaded files
|
||||||
preview?: string;
|
preview?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
type?: string;
|
|
||||||
size?: number;
|
size?: number;
|
||||||
textContent?: string;
|
textContent?: string;
|
||||||
}
|
}
|
||||||
|
|
@ -27,7 +25,6 @@
|
||||||
attachment,
|
attachment,
|
||||||
preview,
|
preview,
|
||||||
name,
|
name,
|
||||||
type,
|
|
||||||
size,
|
size,
|
||||||
textContent
|
textContent
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
@ -36,22 +33,10 @@
|
||||||
|
|
||||||
let displayName = $derived(uploadedFile?.name || attachment?.name || name || 'Unknown File');
|
let displayName = $derived(uploadedFile?.name || attachment?.name || name || 'Unknown File');
|
||||||
|
|
||||||
let displayType = $derived(
|
|
||||||
uploadedFile
|
|
||||||
? uploadedFile.type
|
|
||||||
: attachment?.type === AttachmentType.IMAGE
|
|
||||||
? 'image'
|
|
||||||
: attachment?.type === AttachmentType.TEXT
|
|
||||||
? 'text'
|
|
||||||
: attachment?.type === AttachmentType.AUDIO
|
|
||||||
? attachment.mimeType || ModelModality.AUDIO
|
|
||||||
: attachment?.type === AttachmentType.PDF
|
|
||||||
? 'application/pdf'
|
|
||||||
: type || 'unknown'
|
|
||||||
);
|
|
||||||
|
|
||||||
let displaySize = $derived(uploadedFile?.size || size);
|
let displaySize = $derived(uploadedFile?.size || size);
|
||||||
|
|
||||||
|
let typeLabel = $derived(getAttachmentTypeLabel(uploadedFile, attachment));
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (open && chatAttachmentPreviewRef) {
|
if (open && chatAttachmentPreviewRef) {
|
||||||
chatAttachmentPreviewRef.reset();
|
chatAttachmentPreviewRef.reset();
|
||||||
|
|
@ -62,9 +47,9 @@
|
||||||
<Dialog.Root bind:open {onOpenChange}>
|
<Dialog.Root bind:open {onOpenChange}>
|
||||||
<Dialog.Content class="grid max-h-[90vh] max-w-5xl overflow-hidden sm:w-auto sm:max-w-6xl">
|
<Dialog.Content class="grid max-h-[90vh] max-w-5xl overflow-hidden sm:w-auto sm:max-w-6xl">
|
||||||
<Dialog.Header>
|
<Dialog.Header>
|
||||||
<Dialog.Title>{displayName}</Dialog.Title>
|
<Dialog.Title class="pr-8">{displayName}</Dialog.Title>
|
||||||
<Dialog.Description>
|
<Dialog.Description>
|
||||||
{displayType}
|
{typeLabel}
|
||||||
{#if displaySize}
|
{#if displaySize}
|
||||||
• {formatFileSize(displaySize)}
|
• {formatFileSize(displaySize)}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -76,8 +61,7 @@
|
||||||
{uploadedFile}
|
{uploadedFile}
|
||||||
{attachment}
|
{attachment}
|
||||||
{preview}
|
{preview}
|
||||||
{name}
|
name={displayName}
|
||||||
{type}
|
|
||||||
{textContent}
|
{textContent}
|
||||||
/>
|
/>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ export interface ChatAttachmentDisplayItem {
|
||||||
name: string;
|
name: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
preview?: string;
|
preview?: string;
|
||||||
type: string;
|
|
||||||
isImage: boolean;
|
isImage: boolean;
|
||||||
uploadedFile?: ChatUploadedFile;
|
uploadedFile?: ChatUploadedFile;
|
||||||
attachment?: DatabaseMessageExtra;
|
attachment?: DatabaseMessageExtra;
|
||||||
|
|
@ -29,7 +28,6 @@ export interface ChatAttachmentPreviewItem {
|
||||||
attachment?: DatabaseMessageExtra;
|
attachment?: DatabaseMessageExtra;
|
||||||
preview?: string;
|
preview?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
type?: string;
|
|
||||||
size?: number;
|
size?: number;
|
||||||
textContent?: string;
|
textContent?: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
import { AttachmentType } from '$lib/enums/attachment';
|
||||||
|
import { FileTypeCategory } from '$lib/enums/files';
|
||||||
|
import { getFileTypeCategory } from '$lib/utils/file-type';
|
||||||
|
import { getFileTypeLabel } from '$lib/utils/file-preview';
|
||||||
|
import type { DatabaseMessageExtra } from '$lib/types/database';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an attachment or uploaded file is a text file
|
||||||
|
* @param uploadedFile - Optional uploaded file
|
||||||
|
* @param attachment - Optional database attachment
|
||||||
|
* @returns true if the file is a text file
|
||||||
|
*/
|
||||||
|
export function isTextFile(
|
||||||
|
attachment?: DatabaseMessageExtra,
|
||||||
|
uploadedFile?: ChatUploadedFile
|
||||||
|
): boolean {
|
||||||
|
if (uploadedFile) {
|
||||||
|
return getFileTypeCategory(uploadedFile.type) === FileTypeCategory.TEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment) {
|
||||||
|
return (
|
||||||
|
attachment.type === AttachmentType.TEXT || attachment.type === AttachmentType.LEGACY_CONTEXT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an attachment or uploaded file is an image
|
||||||
|
* @param uploadedFile - Optional uploaded file
|
||||||
|
* @param attachment - Optional database attachment
|
||||||
|
* @returns true if the file is an image
|
||||||
|
*/
|
||||||
|
export function isImageFile(
|
||||||
|
attachment?: DatabaseMessageExtra,
|
||||||
|
uploadedFile?: ChatUploadedFile
|
||||||
|
): boolean {
|
||||||
|
if (uploadedFile) {
|
||||||
|
return getFileTypeCategory(uploadedFile.type) === FileTypeCategory.IMAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment) {
|
||||||
|
return attachment.type === AttachmentType.IMAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an attachment or uploaded file is a PDF
|
||||||
|
* @param uploadedFile - Optional uploaded file
|
||||||
|
* @param attachment - Optional database attachment
|
||||||
|
* @returns true if the file is a PDF
|
||||||
|
*/
|
||||||
|
export function isPdfFile(
|
||||||
|
attachment?: DatabaseMessageExtra,
|
||||||
|
uploadedFile?: ChatUploadedFile
|
||||||
|
): boolean {
|
||||||
|
if (uploadedFile) {
|
||||||
|
return uploadedFile.type === 'application/pdf';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment) {
|
||||||
|
return attachment.type === AttachmentType.PDF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an attachment or uploaded file is an audio file
|
||||||
|
* @param uploadedFile - Optional uploaded file
|
||||||
|
* @param attachment - Optional database attachment
|
||||||
|
* @returns true if the file is an audio file
|
||||||
|
*/
|
||||||
|
export function isAudioFile(
|
||||||
|
attachment?: DatabaseMessageExtra,
|
||||||
|
uploadedFile?: ChatUploadedFile
|
||||||
|
): boolean {
|
||||||
|
if (uploadedFile) {
|
||||||
|
return getFileTypeCategory(uploadedFile.type) === FileTypeCategory.AUDIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment) {
|
||||||
|
return attachment.type === AttachmentType.AUDIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a human-readable type label for display
|
||||||
|
* @param uploadedFile - Optional uploaded file
|
||||||
|
* @param attachment - Optional database attachment
|
||||||
|
* @returns A formatted type label string
|
||||||
|
*/
|
||||||
|
export function getAttachmentTypeLabel(
|
||||||
|
attachment?: DatabaseMessageExtra,
|
||||||
|
uploadedFile?: ChatUploadedFile
|
||||||
|
): string {
|
||||||
|
if (uploadedFile) {
|
||||||
|
// For uploaded files, use the file type label utility
|
||||||
|
return getFileTypeLabel(uploadedFile.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment) {
|
||||||
|
// For attachments, convert enum to readable format
|
||||||
|
switch (attachment.type) {
|
||||||
|
case AttachmentType.IMAGE:
|
||||||
|
return 'image';
|
||||||
|
case AttachmentType.AUDIO:
|
||||||
|
return 'audio';
|
||||||
|
case AttachmentType.PDF:
|
||||||
|
return 'pdf';
|
||||||
|
case AttachmentType.TEXT:
|
||||||
|
case AttachmentType.LEGACY_CONTEXT:
|
||||||
|
return 'text';
|
||||||
|
default:
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue