refactor: Attachments data

This commit is contained in:
Aleksander Grygier 2025-11-23 21:46:43 +01:00
parent 1f0cb3ab26
commit b7ba13b6a0
7 changed files with 204 additions and 196 deletions

View File

@ -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}

View File

@ -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">

View File

@ -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}
/> />

View File

@ -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}
/> />

View File

@ -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>

View File

@ -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;
} }

View File

@ -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';
}