| 
 | 1 | +<script lang="ts">  | 
 | 2 | +	import * as Dialog from '$lib/components/ui/dialog';  | 
 | 3 | +	import { FileText, Image, Music, FileIcon } from '@lucide/svelte';  | 
 | 4 | +	import type { DatabaseMessageExtra } from '$lib/types/database.d.ts';  | 
 | 5 | +	import type { ChatUploadedFile } from '$lib/types/chat.d.ts';  | 
 | 6 | +
  | 
 | 7 | +	interface Props {  | 
 | 8 | +		open: boolean;  | 
 | 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 | +		size?: number;  | 
 | 17 | +		textContent?: string;  | 
 | 18 | +	}  | 
 | 19 | +
  | 
 | 20 | +	let {  | 
 | 21 | +		open = $bindable(),  | 
 | 22 | +		uploadedFile,  | 
 | 23 | +		attachment,  | 
 | 24 | +		preview,  | 
 | 25 | +		name,  | 
 | 26 | +		type,  | 
 | 27 | +		size,  | 
 | 28 | +		textContent  | 
 | 29 | +	}: Props = $props();  | 
 | 30 | +
  | 
 | 31 | +	let displayName = $derived(uploadedFile?.name || attachment?.name || name || 'Unknown File');  | 
 | 32 | +
  | 
 | 33 | +	let displayType = $derived(  | 
 | 34 | +		uploadedFile?.type ||  | 
 | 35 | +			(attachment?.type === 'imageFile'  | 
 | 36 | +				? 'image'  | 
 | 37 | +				: attachment?.type === 'textFile'  | 
 | 38 | +					? 'text'  | 
 | 39 | +					: attachment?.type === 'audioFile'  | 
 | 40 | +						? (attachment as any).mimeType || 'audio'  | 
 | 41 | +						: attachment?.type === 'pdfFile'  | 
 | 42 | +							? 'application/pdf'  | 
 | 43 | +							: type || 'unknown')  | 
 | 44 | +	);  | 
 | 45 | +
  | 
 | 46 | +	let displaySize = $derived(uploadedFile?.size || size);  | 
 | 47 | +
  | 
 | 48 | +	let displayPreview = $derived(  | 
 | 49 | +		uploadedFile?.preview ||  | 
 | 50 | +			(attachment?.type === 'imageFile' ? (attachment as any).base64Url : preview)  | 
 | 51 | +	);  | 
 | 52 | +
  | 
 | 53 | +	let displayTextContent = $derived(  | 
 | 54 | +		uploadedFile?.textContent ||  | 
 | 55 | +			(attachment?.type === 'textFile'  | 
 | 56 | +				? (attachment as any).content  | 
 | 57 | +				: attachment?.type === 'pdfFile'  | 
 | 58 | +					? (attachment as any).content  | 
 | 59 | +					: textContent)  | 
 | 60 | +	);  | 
 | 61 | +
  | 
 | 62 | +	let isImage = $derived(displayType.startsWith('image/') || displayType === 'image');  | 
 | 63 | +	let isText = $derived(displayType.startsWith('text/') || displayType === 'text');  | 
 | 64 | +	let isPdf = $derived(displayType === 'application/pdf');  | 
 | 65 | +	let isAudio = $derived(displayType.startsWith('audio/') || displayType === 'audio');  | 
 | 66 | +
  | 
 | 67 | +	let IconComponent = $derived(() => {  | 
 | 68 | +		if (isImage) return Image;  | 
 | 69 | +		if (isText || isPdf) return FileText;  | 
 | 70 | +		if (isAudio) return Music;  | 
 | 71 | +		return FileIcon;  | 
 | 72 | +	});  | 
 | 73 | +
  | 
 | 74 | +	function formatFileSize(bytes: number): string {  | 
 | 75 | +		if (bytes === 0) return '0 Bytes';  | 
 | 76 | +
  | 
 | 77 | +		const k = 1024;  | 
 | 78 | +		const sizes = ['Bytes', 'KB', 'MB', 'GB'];  | 
 | 79 | +		const i = Math.floor(Math.log(bytes) / Math.log(k));  | 
 | 80 | +
  | 
 | 81 | +		return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];  | 
 | 82 | +	}  | 
 | 83 | +</script>  | 
 | 84 | + | 
 | 85 | +<Dialog.Root bind:open>  | 
 | 86 | +	<Dialog.Content class="grid max-h-[90vh] max-w-4xl overflow-hidden sm:w-auto sm:max-w-6xl">  | 
 | 87 | +		<Dialog.Header class="flex-shrink-0">  | 
 | 88 | +			<div class="flex items-center space-x-4">  | 
 | 89 | +				<div class="flex items-center gap-3">  | 
 | 90 | +					{#if IconComponent}  | 
 | 91 | +						<IconComponent class="text-muted-foreground h-5 w-5" />  | 
 | 92 | +					{/if}  | 
 | 93 | + | 
 | 94 | +					<div>  | 
 | 95 | +						<Dialog.Title class="text-left">{displayName}</Dialog.Title>  | 
 | 96 | + | 
 | 97 | +						<div class="text-muted-foreground flex items-center gap-2 text-sm">  | 
 | 98 | +							<span>{displayType}</span>  | 
 | 99 | +							{#if displaySize}  | 
 | 100 | +								<span>•</span>  | 
 | 101 | +								<span>{formatFileSize(displaySize)}</span>  | 
 | 102 | +							{/if}  | 
 | 103 | +						</div>  | 
 | 104 | +					</div>  | 
 | 105 | +				</div>  | 
 | 106 | +			</div>  | 
 | 107 | +		</Dialog.Header>  | 
 | 108 | + | 
 | 109 | +		<div class="flex-1 overflow-auto">  | 
 | 110 | +			{#if isImage && displayPreview}  | 
 | 111 | +				<div class="flex items-center justify-center p-4">  | 
 | 112 | +					<img  | 
 | 113 | +						src={displayPreview}  | 
 | 114 | +						alt={displayName}  | 
 | 115 | +						class="max-h-full rounded-lg object-contain shadow-lg"  | 
 | 116 | +					/>  | 
 | 117 | +				</div>  | 
 | 118 | +			{:else if (isText || isPdf) && displayTextContent}  | 
 | 119 | +				<div class="p-4">  | 
 | 120 | +					<div  | 
 | 121 | +						class="bg-muted max-h-[60vh] overflow-auto whitespace-pre-wrap break-words rounded-lg p-4 font-mono text-sm"  | 
 | 122 | +					>  | 
 | 123 | +						{displayTextContent}  | 
 | 124 | +					</div>  | 
 | 125 | +				</div>  | 
 | 126 | +			{:else if isAudio && attachment?.type === 'audioFile'}  | 
 | 127 | +				<div class="flex items-center justify-center p-8">  | 
 | 128 | +					<div class="text-center">  | 
 | 129 | +						<Music class="text-muted-foreground mx-auto mb-4 h-16 w-16" />  | 
 | 130 | + | 
 | 131 | +						<p class="text-muted-foreground mb-4">Audio file preview not available</p>  | 
 | 132 | +					</div>  | 
 | 133 | +				</div>  | 
 | 134 | +			{:else}  | 
 | 135 | +				<div class="flex items-center justify-center p-8">  | 
 | 136 | +					<div class="text-center">  | 
 | 137 | +						{#if IconComponent}  | 
 | 138 | +							<IconComponent class="text-muted-foreground mx-auto mb-4 h-16 w-16" />  | 
 | 139 | +						{/if}  | 
 | 140 | + | 
 | 141 | +						<p class="text-muted-foreground mb-4">  | 
 | 142 | +							Preview not available for this file type  | 
 | 143 | +						</p>  | 
 | 144 | +					</div>  | 
 | 145 | +				</div>  | 
 | 146 | +			{/if}  | 
 | 147 | +		</div>  | 
 | 148 | +	</Dialog.Content>  | 
 | 149 | +</Dialog.Root>  | 
0 commit comments