fixed files attachment in messages

This commit is contained in:
Ida Dittrich 2025-09-03 23:39:39 +02:00
parent 8d64ee7f9c
commit 912851d8b6
3 changed files with 180 additions and 19 deletions

View file

@ -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<MessageItemProps> = ({ message }) => {
const [downloadingFiles, setDownloadingFiles] = useState<Set<string>>(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<MessageItemProps> = ({ message }) => {
{isUser ? 'Uploaded' : 'Attached'} Files ({message.documents?.length || 0})
</div>
<div>
{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 (
<div
key={doc.id || i}
className={messageStyles.message_document_item}
onClick={() => 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
}}
>
<div className={messageStyles.message_document_info}>
<div className={messageStyles.message_document_name}>
@ -107,7 +213,8 @@ const MessageItem: React.FC<MessageItemProps> = ({ message }) => {
*/}
</div>
</div>
))}
);
})}
</div>
</div>
)}

View file

@ -240,6 +240,7 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--color-gray);
}
.message_document_size {

View file

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