From 5816a1b7524e9a31306aed13cbbd4b34a58bd8fc Mon Sep 17 00:00:00 2001 From: Ida Dittrich Date: Mon, 8 Sep 2025 18:18:18 +0200 Subject: [PATCH] finished file preview --- .../DashboardChatAreaMessageItem.tsx | 237 ++++++++---------- .../DashboardChatMessages.module.css | 2 +- .../FilePreview/FilePreview.module.css | 18 +- src/components/FilePreview/FilePreview.tsx | 26 +- .../FilePreview/renderers/HtmlRenderer.tsx | 41 +++ .../FilePreview/renderers/ImageRenderer.tsx | 18 +- .../FilePreview/renderers/JsonRenderer.tsx | 2 +- src/components/FilePreview/renderers/index.ts | 1 + src/hooks/useFiles.ts | 167 ++++++++++++ 9 files changed, 362 insertions(+), 150 deletions(-) create mode 100644 src/components/FilePreview/renderers/HtmlRenderer.tsx diff --git a/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageItem.tsx b/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageItem.tsx index c7d86f6..d1c1118 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageItem.tsx +++ b/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageItem.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { Message, Document } from "./dashboardChatAreaTypes"; import messageStyles from './DashboardChatAreaStyles/DashboardChatMessages.module.css'; -import { useApiRequest } from '../../../hooks/useApi'; +import { FilePreview } from '../../FilePreview'; @@ -19,102 +19,61 @@ const formatFileSize = (bytes?: number) => { const MessageItem: React.FC = ({ message }) => { - const [downloadingFiles, setDownloadingFiles] = useState>(new Set()); - const { request } = useApiRequest(); + const [previewModalOpen, setPreviewModalOpen] = useState(false); + const [previewingFile, setPreviewingFile] = useState(null); - const handleDocumentClick = async (doc: Document) => { - console.log('📋 Document object:', doc); + const handlePreview = (doc: Document) => { + console.log('👁️ Opening preview for document:', doc); // Extract the UUID file ID from the downloadUrl (same as files page) let actualFileId: string | null = null; - let downloadUrl: string; if (doc.downloadUrl) { // Extract UUID from the downloadUrl: /api/workflows/files/{UUID}/download const match = doc.downloadUrl.match(/\/api\/workflows\/files\/([^\/]+)\/download/); if (match) { actualFileId = match[1]; - // Use the regular files endpoint with the UUID (same as files page) - downloadUrl = `/api/files/${actualFileId}/download`; console.log(`🔗 Extracted UUID from downloadUrl: ${actualFileId}`); - console.log(`🌐 Will use files endpoint: ${downloadUrl}`); } else { console.error('Could not extract file ID from downloadUrl:', doc.downloadUrl); alert('Could not extract file ID from download URL'); return; } + } else if (doc.id) { + // Fallback to using the document id directly + actualFileId = doc.id; + console.log(`🔗 Using document id as fileId: ${actualFileId}`); } else { - console.error('No downloadUrl available in document'); - alert('No download URL available'); + console.error('No downloadUrl or id available in document'); + alert('No file ID available for preview'); return; } if (!actualFileId) { console.error('Could not determine actual file ID'); - alert('Could not determine file ID for download'); + alert('Could not determine file ID for preview'); return; } - // Prevent multiple downloads of the same file - if (downloadingFiles.has(actualFileId)) { - console.log('⏭️ Download already in progress for this file'); - return; - } + // Create a modified document with the correct file ID + const documentWithFileId = { + ...doc, + id: actualFileId + }; + + setPreviewingFile(documentWithFileId); + setPreviewModalOpen(true); + }; - setDownloadingFiles(prev => new Set(prev).add(actualFileId)); + const handleClosePreview = () => { + console.log('❌ Closing preview'); + setPreviewModalOpen(false); + setPreviewingFile(null); + }; - try { - console.log(`📥 Starting download for file: ${doc.name} (UUID: ${actualFileId})`); - console.log(`🌐 Making request to: ${downloadUrl}`); - - const blob = await request({ - url: downloadUrl, - method: 'get', - additionalConfig: { - responseType: 'blob' - } - }); - - console.log(`✅ Download successful for: ${doc.name}`, { - size: blob.size, - type: blob.type - }); - - // Create filename with extension - const fileName = doc.ext ? `${doc.name}.${doc.ext}` : doc.name; - - // Create a download link and trigger the download - const url = window.URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.setAttribute('download', fileName); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - window.URL.revokeObjectURL(url); - - } catch (error: any) { - console.error(`❌ Download failed for ${doc.name}:`, error); - console.error('Full error object:', error); - console.error('Error response:', error.response); - - let errorMessage = 'Download failed'; - if (error.response?.status === 404) { - errorMessage = `File "${doc.name}" not found or has been deleted. (404)`; - } else if (error.response?.status === 403) { - errorMessage = `No permission to download "${doc.name}". (403)`; - } else { - errorMessage = `Download failed: ${error.message || 'Unknown error'}`; - } - - alert(errorMessage); - } finally { - setDownloadingFiles(prev => { - const newSet = new Set(prev); - newSet.delete(actualFileId!); - return newSet; - }); - } + const handleDocumentClick = (doc: Document) => { + console.log('📋 Document clicked, opening preview:', doc); + handlePreview(doc); }; @@ -138,88 +97,92 @@ const MessageItem: React.FC = ({ message }) => { const isUser = message.role === 'user'; return ( -
-
- {isUser ? 'You' : message.agentName} - {message.timestamp && ( - - • {formatTimestamp(message.timestamp)} - - )} -
- -
-
- {message.content || [No message content]} + <> +
+
+ {isUser ? 'You' : message.agentName} + {message.timestamp && ( + + • {formatTimestamp(message.timestamp)} + + )}
- {hasDocuments && ( -
-
- {isUser ? 'Uploaded' : 'Attached'} Files ({message.documents?.length || 0}) -
-
+
+
+ {message.content || [No message content]} +
+ + {hasDocuments && ( +
+
+ {isUser ? 'Uploaded' : 'Attached'} Files ({message.documents?.length || 0}) +
+
{message.documents!.map((doc, i) => { - // Extract UUID from downloadUrl for download state tracking - let fileKey: string | null = null; - if (doc.downloadUrl) { - const match = doc.downloadUrl.match(/\/api\/workflows\/files\/([^\/]+)\/download/); - fileKey = match ? match[1] : null; - } - const isDownloading = fileKey && downloadingFiles.has(fileKey); - return (
handleDocumentClick(doc)} - title={isDownloading ? 'Downloading...' : `Click to download ${doc.name}`} + title={`Click to preview ${doc.name}`} style={{ - cursor: isDownloading ? 'wait' : 'pointer', - opacity: isDownloading ? 0.7 : 1 + cursor: 'pointer' }} > -
-
- {doc.ext ? `${doc.name}.${doc.ext}` : doc.name} +
+
+ {doc.ext ? `${doc.name}.${doc.ext}` : doc.name} +
+ {doc.size && ( +
+ {formatFileSize(doc.size)} +
+ )} +
+
+ {/* Preview and Download buttons disabled for now + + + */}
- {doc.size && ( -
- {formatFileSize(doc.size)} -
- )}
-
- {/* Preview and Download buttons disabled for now - - - */} -
-
- ); - })} + ); + })} +
-
- )} + )} +
-
+ + {/* File Preview Modal */} + {previewingFile && ( + + )} + ); }; diff --git a/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatMessages.module.css b/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatMessages.module.css index dc89ac1..36c2eb3 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatMessages.module.css +++ b/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatMessages.module.css @@ -297,7 +297,7 @@ font-size: 20px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.2); - z-index: 1000; + z-index: 100; display: flex; align-items: center; justify-content: center; diff --git a/src/components/FilePreview/FilePreview.module.css b/src/components/FilePreview/FilePreview.module.css index ce25139..e6bb776 100644 --- a/src/components/FilePreview/FilePreview.module.css +++ b/src/components/FilePreview/FilePreview.module.css @@ -232,9 +232,9 @@ .valueContainer { display: flex; flex-direction: column; - width: 100%; + width: auto; overflow: visible !important; - min-width: 0; + min-width: 20rem; } .jsonValuePreview { @@ -506,8 +506,8 @@ border-radius: 4px; background: #f8f9fa; overflow: visible !important; - width: 100%; - min-width: 100%; + width: auto; + min-width: 20rem; } @@ -563,9 +563,11 @@ box-sizing: border-box; position: relative; z-index: 1; + white-space: nowrap !important; + word-break: keep-all !important; overflow: visible !important; - min-width: 0; - width: 100%; + min-width: 20rem; + width: auto; } .nestedValueSummary { @@ -578,8 +580,8 @@ .arrayItems { display: flex; flex-wrap: wrap; - gap: 4px; - margin-top: 4px; + gap: 0px; + margin-left: -0.4rem; } /* Array items when no key is shown (should span full width) */ diff --git a/src/components/FilePreview/FilePreview.tsx b/src/components/FilePreview/FilePreview.tsx index 32946a8..15a4c2d 100644 --- a/src/components/FilePreview/FilePreview.tsx +++ b/src/components/FilePreview/FilePreview.tsx @@ -10,6 +10,7 @@ import { ImageRenderer, TextRenderer, PdfRenderer, + HtmlRenderer, ApplicationRenderer, UnsupportedRenderer, LoadingRenderer, @@ -122,8 +123,8 @@ export function FilePreview({ // Create action buttons for the popup header const actions: PopupAction[] = [ - // Copy Content Button - only show for text-based files (exclude PDFs) or corrupted PDFs - ...(mimeType !== 'application/pdf' && (mimeType?.startsWith('text/') || mimeType === 'application/json' || previewContent) ? [{ + // Copy Content Button - only show for text-based files (exclude PDFs and images) or corrupted PDFs + ...(mimeType !== 'application/pdf' && !mimeType?.startsWith('image/') && (mimeType?.startsWith('text/') || mimeType === 'application/json' || previewContent) ? [{ label: copySuccess ? t('files.preview.copied', 'Copied!') : t(''), icon: copySuccess ? '✓' : , onClick: handleCopyContent, @@ -207,6 +208,17 @@ export function FilePreview({ ); case 'text': + // Special handling for HTML files + if (mimeType === 'text/html') { + return ( + setError('Failed to load HTML preview')} + /> + ); + } + return ( setError('Failed to load HTML preview')} + /> + ); + } + return ( diff --git a/src/components/FilePreview/renderers/HtmlRenderer.tsx b/src/components/FilePreview/renderers/HtmlRenderer.tsx new file mode 100644 index 0000000..f22a52b --- /dev/null +++ b/src/components/FilePreview/renderers/HtmlRenderer.tsx @@ -0,0 +1,41 @@ +import styles from '../FilePreview.module.css'; + +interface HtmlRendererProps { + previewUrl: string; + fileName: string; + onError: () => void; +} + +export function HtmlRenderer({ previewUrl, fileName, onError }: HtmlRendererProps) { + const handleLoad = () => { + console.log('🌐 HTML loaded successfully:', { previewUrl, fileName }); + }; + + const handleError = (event: React.SyntheticEvent) => { + console.error('❌ HTML failed to load:', { previewUrl, fileName, event }); + onError(); + }; + + console.log('🔍 HtmlRenderer props:', { + previewUrl, + fileName, + hasPreviewUrl: !!previewUrl + }); + + return ( +