I have created a chatbot that has a backend created with FastAPI and the following function:
@router.get("/download/")
async def download_file(filename: str, collection_name: str) -> FileResponse:
file_path = f"data/files/{collection_name}/{filename}"
headers = {'Access-Control-Expose-Headers': 'Content-Disposition'}
if not os.path.exists(file_path):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="File not found.")
return FileResponse(file_path, filename=filename, headers=headers)
Then, I created a ReactJS component that generates the message and embeds download links to it so that I can download the referenced files:
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
function ChatMessage({ message, collection }) {
const generateMarkdownLinks = (sources) => {
return sources.map((source, index) => `[Download Source ${index + 1}](/download/${encodeURIComponent(source)})`).join(" \n");
};
const markdownContent = message.isBot && message.sources && message.sources.length > 0
? `${message.text}\n\n${generateMarkdownLinks(message.sources)}`
: message.text;
const handleDownloadClick = async (e, filepath, collectionName) => {
e.preventDefault();
const filename = decodeURIComponent(filepath.split('%2F').pop());
const url = `/download/?filename=${encodeURIComponent(filename)}&collection_name=${encodeURIComponent(collectionName)}`;
console.log(`Downloading file: ${filename}`);
console.log(`Filepath: ${filepath}`);
console.log(`Collection: ${collectionName}`);
console.log(`URL: ${url}`);
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.statusText}`);
}
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(downloadUrl);
} catch (error) {
console.error('File download failed:', error);
alert('File download failed.');
}
};
const renderLink = ({ href, children }) => {
if (href.startsWith('/download/')) {
const filepath = href.replace('/download/', '');
return (
<a
href={href}
onClick={(e) => handleDownloadClick(e, filepath, collection)}
>
{children}
</a>
);
}
return <a href={href}>{children}</a>;
};
return (
<div className={`chat-message ${message.isBot ? 'bot-message' : 'user-message'}`}>
{message.isBot ? (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
a: renderLink
}}
>
{markdownContent}
</ReactMarkdown>
) : (
<>
{message.text}
{message.sources && message.sources.length > 0 && message.sources.map((source, index) => (
<a
key={index}
href={`/download/${encodeURIComponent(source)}`}
onClick={(e) => handleDownloadClick(e, source, message.collectionName)}
>
Download Source {index + 1}
</a>
))}
</>
)}
</div>
);
}
export default ChatMessage;
The problem occurs when I click any of the links, I am able to download a file, but it is corrupt and doesn't contain the information from the original one. I assume, maybe wrongly, that the problem is the frontend code, because I am able to properly download the file with the backend using the Swagger documentation from FastAPI.
Could anyone help with that? Thank you very much.
I have figured out what the problem was. The URL in the handleDownloadClick
function was missing an important part at the beginning. So, I reformatted it like this:
const baseUrl = `${window.location.origin}/api`;
const url = `${baseUrl}/download/?filename=${encodeURIComponent(filename)}&collection_name=${encodeURIComponent(collectionName)}`;
This dynamically generates the URL based on the server address and handles the download properly.