From 912851d8b62e88bf14bb2bf3ca2c2246c7400c26 Mon Sep 17 00:00:00 2001 From: Ida Dittrich Date: Wed, 3 Sep 2025 23:39:39 +0200 Subject: [PATCH] fixed files attachment in messages --- .../DashboardChatAreaMessageItem.tsx | 121 +++++++++++++++++- .../DashboardChatMessages.module.css | 1 + .../dashboardChatAreaProgressBar.ts | 77 +++++++++-- 3 files changed, 180 insertions(+), 19 deletions(-) diff --git a/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageItem.tsx b/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageItem.tsx index cc9923f..c7d86f6 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageItem.tsx +++ b/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageItem.tsx @@ -1,6 +1,7 @@ -import React from "react"; +import React, { useState } from "react"; import { Message, Document } from "./dashboardChatAreaTypes"; import messageStyles from './DashboardChatAreaStyles/DashboardChatMessages.module.css'; +import { useApiRequest } from '../../../hooks/useApi'; @@ -18,10 +19,102 @@ const formatFileSize = (bytes?: number) => { const MessageItem: React.FC = ({ message }) => { + const [downloadingFiles, setDownloadingFiles] = useState>(new Set()); + const { request } = useApiRequest(); - const handleDocumentClick = (doc: Document) => { - const link = doc.downloadUrl || doc.url; - if (link) window.open(link, '_blank'); + const handleDocumentClick = async (doc: Document) => { + console.log('📋 Document object:', 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 { + console.error('No downloadUrl available in document'); + alert('No download URL available'); + return; + } + + if (!actualFileId) { + console.error('Could not determine actual file ID'); + alert('Could not determine file ID for download'); + return; + } + + // Prevent multiple downloads of the same file + if (downloadingFiles.has(actualFileId)) { + console.log('⏭️ Download already in progress for this file'); + return; + } + + setDownloadingFiles(prev => new Set(prev).add(actualFileId)); + + 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; + }); + } }; @@ -66,12 +159,25 @@ const MessageItem: React.FC = ({ message }) => { {isUser ? 'Uploaded' : 'Attached'} Files ({message.documents?.length || 0})
- {message.documents!.map((doc, i) => ( + {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={`Click to open ${doc.name}`} + title={isDownloading ? 'Downloading...' : `Click to download ${doc.name}`} + style={{ + cursor: isDownloading ? 'wait' : 'pointer', + opacity: isDownloading ? 0.7 : 1 + }} >
@@ -107,7 +213,8 @@ const MessageItem: React.FC = ({ message }) => { */}
- ))} + ); + })}
)} diff --git a/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatMessages.module.css b/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatMessages.module.css index df54036..dc89ac1 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatMessages.module.css +++ b/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatMessages.module.css @@ -240,6 +240,7 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + color: var(--color-gray); } .message_document_size { diff --git a/src/components/Dashboard/DashboardChat/dashboardChatAreaProgressBar.ts b/src/components/Dashboard/DashboardChat/dashboardChatAreaProgressBar.ts index 6ab8391..62d56e2 100644 --- a/src/components/Dashboard/DashboardChat/dashboardChatAreaProgressBar.ts +++ b/src/components/Dashboard/DashboardChat/dashboardChatAreaProgressBar.ts @@ -115,30 +115,83 @@ export const transformWorkflowMessage = async (msg: any, request: any): Promise< let docs: any[] = []; if (msg.documents?.length > 0) { - docs = msg.documents.map((d: any) => ({ - id: d.id || d.fileId, - fileId: typeof d.fileId === 'string' ? parseInt(d.fileId) : d.fileId, - name: d.filename || `File_${d.id || d.fileId || 'unknown'}`, - ext: (d.filename && d.filename.includes('.')) ? d.filename.split('.').pop() : 'unknown', - type: d.mimeType, - size: d.fileSize, - downloadUrl: `/api/workflows/files/${d.fileId}/download` - })); + // Try to get filenames from the documents, but if missing, fetch from API + const needsApiCall = msg.documents.some((d: any) => !d.filename && !d.fileName && !d.name); + + if (needsApiCall) { + // If any document is missing filename, fetch details from API for all + const promises = msg.documents.map(async (d: any) => { + try { + const fileId = d.fileId || d.id; + const res = await request({ url: `/api/workflows/files/${fileId}/preview`, method: 'get' }); + const fullName = res.name || res.fileName || d.filename || d.fileName || d.name || `File_${fileId}`; + const lastDotIndex = fullName.lastIndexOf('.'); + const hasExtension = lastDotIndex > 0 && lastDotIndex < fullName.length - 1; + + return { + id: d.id || d.fileId, + fileId: typeof d.fileId === 'string' ? parseInt(d.fileId) : d.fileId, + name: hasExtension ? fullName.substring(0, lastDotIndex) : fullName, + ext: res.extension || res.ext || (hasExtension ? fullName.substring(lastDotIndex + 1) : ''), + type: d.mimeType || res.mimeType || res.type || 'application/octet-stream', + size: d.fileSize || res.size || 0, + downloadUrl: `/api/workflows/files/${d.fileId}/download` + }; + } catch { + // Fallback if API call fails + const fullFilename = d.filename || d.fileName || d.name || `File_${d.id || d.fileId || 'unknown'}`; + const lastDotIndex = fullFilename.lastIndexOf('.'); + const hasExtension = lastDotIndex > 0 && lastDotIndex < fullFilename.length - 1; + + return { + id: d.id || d.fileId, + fileId: typeof d.fileId === 'string' ? parseInt(d.fileId) : d.fileId, + name: hasExtension ? fullFilename.substring(0, lastDotIndex) : fullFilename, + ext: hasExtension ? fullFilename.substring(lastDotIndex + 1) : '', + type: d.mimeType || 'application/octet-stream', + size: d.fileSize || 0, + downloadUrl: `/api/workflows/files/${d.fileId}/download` + }; + } + }); + docs = await Promise.all(promises); + } else { + // All documents have filenames, process them directly + docs = msg.documents.map((d: any) => { + const fullFilename = d.filename || d.fileName || d.name || `File_${d.id || d.fileId || 'unknown'}`; + const lastDotIndex = fullFilename.lastIndexOf('.'); + const hasExtension = lastDotIndex > 0 && lastDotIndex < fullFilename.length - 1; + + return { + id: d.id || d.fileId, + fileId: typeof d.fileId === 'string' ? parseInt(d.fileId) : d.fileId, + name: hasExtension ? fullFilename.substring(0, lastDotIndex) : fullFilename, + ext: hasExtension ? fullFilename.substring(lastDotIndex + 1) : '', + type: d.mimeType, + size: d.fileSize, + downloadUrl: `/api/workflows/files/${d.fileId}/download` + }; + }); + } } else if (msg.fileIds?.length > 0) { const promises = msg.fileIds.map(async (id: number) => { try { const res = await request({ url: `/api/workflows/files/${id}/preview`, method: 'get' }); + const fullName = res.name || res.fileName || `File_${id}`; + const lastDotIndex = fullName.lastIndexOf('.'); + const hasExtension = lastDotIndex > 0 && lastDotIndex < fullName.length - 1; + return { id: id.toString(), fileId: id, - name: res.name || res.fileName || `File_${id}`, - ext: res.extension || res.ext || (res.name ? res.name.split('.').pop() : 'txt'), + name: hasExtension ? fullName.substring(0, lastDotIndex) : fullName, + ext: res.extension || res.ext || (hasExtension ? fullName.substring(lastDotIndex + 1) : ''), type: res.mimeType || res.type || 'application/octet-stream', size: res.size || 0, downloadUrl: res.downloadUrl || res.url }; } catch { - return { id: id.toString(), fileId: id, name: `File_${id}`, ext: 'unknown', type: 'application/octet-stream', size: 0 }; + return { id: id.toString(), fileId: id, name: `File_${id}`, ext: '', type: 'application/octet-stream', size: 0 }; } }); docs = await Promise.all(promises);