fixed files attachment in messages
This commit is contained in:
parent
8d64ee7f9c
commit
912851d8b6
3 changed files with 180 additions and 19 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { Message, Document } from "./dashboardChatAreaTypes";
|
import { Message, Document } from "./dashboardChatAreaTypes";
|
||||||
import messageStyles from './DashboardChatAreaStyles/DashboardChatMessages.module.css';
|
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 MessageItem: React.FC<MessageItemProps> = ({ message }) => {
|
||||||
|
const [downloadingFiles, setDownloadingFiles] = useState<Set<string>>(new Set());
|
||||||
|
const { request } = useApiRequest();
|
||||||
|
|
||||||
const handleDocumentClick = (doc: Document) => {
|
const handleDocumentClick = async (doc: Document) => {
|
||||||
const link = doc.downloadUrl || doc.url;
|
console.log('📋 Document object:', doc);
|
||||||
if (link) window.open(link, '_blank');
|
|
||||||
|
// 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})
|
{isUser ? 'Uploaded' : 'Attached'} Files ({message.documents?.length || 0})
|
||||||
</div>
|
</div>
|
||||||
<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
|
<div
|
||||||
key={doc.id || i}
|
key={doc.id || i}
|
||||||
className={messageStyles.message_document_item}
|
className={messageStyles.message_document_item}
|
||||||
onClick={() => handleDocumentClick(doc)}
|
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_info}>
|
||||||
<div className={messageStyles.message_document_name}>
|
<div className={messageStyles.message_document_name}>
|
||||||
|
|
@ -107,7 +213,8 @@ const MessageItem: React.FC<MessageItemProps> = ({ message }) => {
|
||||||
*/}
|
*/}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
color: var(--color-gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
.message_document_size {
|
.message_document_size {
|
||||||
|
|
|
||||||
|
|
@ -115,30 +115,83 @@ export const transformWorkflowMessage = async (msg: any, request: any): Promise<
|
||||||
let docs: any[] = [];
|
let docs: any[] = [];
|
||||||
|
|
||||||
if (msg.documents?.length > 0) {
|
if (msg.documents?.length > 0) {
|
||||||
docs = msg.documents.map((d: any) => ({
|
// Try to get filenames from the documents, but if missing, fetch from API
|
||||||
id: d.id || d.fileId,
|
const needsApiCall = msg.documents.some((d: any) => !d.filename && !d.fileName && !d.name);
|
||||||
fileId: typeof d.fileId === 'string' ? parseInt(d.fileId) : d.fileId,
|
|
||||||
name: d.filename || `File_${d.id || d.fileId || 'unknown'}`,
|
if (needsApiCall) {
|
||||||
ext: (d.filename && d.filename.includes('.')) ? d.filename.split('.').pop() : 'unknown',
|
// If any document is missing filename, fetch details from API for all
|
||||||
type: d.mimeType,
|
const promises = msg.documents.map(async (d: any) => {
|
||||||
size: d.fileSize,
|
try {
|
||||||
downloadUrl: `/api/workflows/files/${d.fileId}/download`
|
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) {
|
} else if (msg.fileIds?.length > 0) {
|
||||||
const promises = msg.fileIds.map(async (id: number) => {
|
const promises = msg.fileIds.map(async (id: number) => {
|
||||||
try {
|
try {
|
||||||
const res = await request({ url: `/api/workflows/files/${id}/preview`, method: 'get' });
|
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 {
|
return {
|
||||||
id: id.toString(),
|
id: id.toString(),
|
||||||
fileId: id,
|
fileId: id,
|
||||||
name: res.name || res.fileName || `File_${id}`,
|
name: hasExtension ? fullName.substring(0, lastDotIndex) : fullName,
|
||||||
ext: res.extension || res.ext || (res.name ? res.name.split('.').pop() : 'txt'),
|
ext: res.extension || res.ext || (hasExtension ? fullName.substring(lastDotIndex + 1) : ''),
|
||||||
type: res.mimeType || res.type || 'application/octet-stream',
|
type: res.mimeType || res.type || 'application/octet-stream',
|
||||||
size: res.size || 0,
|
size: res.size || 0,
|
||||||
downloadUrl: res.downloadUrl || res.url
|
downloadUrl: res.downloadUrl || res.url
|
||||||
};
|
};
|
||||||
} catch {
|
} 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);
|
docs = await Promise.all(promises);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue