QUARTO_EXPORT_BUTTON.md
Component documentation for the QuartoExportButton document export functionality.
Overview
The QuartoExportButton component provides a comprehensive solution for exporting chat messages and content to various document formats including DOCX, PDF, HTML, and PPTX. It handles the complete export workflow from user interaction to file download.
Component Location
- File:
src/components/QuartoExportButton.tsx - Type: Action Component
- Framework: React with TypeScript
Features
- Multiple Formats: Supports DOCX, PDF, HTML, and PPTX exports
- Progress Tracking: Visual progress indicators during export process
- Error Handling: Comprehensive error management and user feedback
- Content Processing: Automatic content cleaning and preparation
- Flexible Display: Icon-only or full button variants
- Cloud Integration: Works with backend storage services
- Status Messages: Real-time status updates during export
Export Formats
export type ExportFormat = 'docx' | 'pdf' | 'html' | 'pptx';
Props Interface
interface QuartoExportButtonProps {
apiEndpoint?: string; // Backend API endpoint
reportData?: Record<string, any>; // Additional report data
buttonText?: string; // Button label text
method?: string; // HTTP method (default: POST)
exportFormat?: ExportFormat; // Export format (default: docx)
message: ChatMessage; // Message to export
className?: string; // Additional CSS classes
extractTextOnly?: boolean; // Remove HTML/thinking content
onSuccess?: (result?: any) => void; // Success callback
onError?: (error?: Error) => void; // Error callback
variant?: 'icon' | 'button'; // Display variant
showLabel?: boolean; // Show text label with icon
bucketName?: string; // Storage bucket name
onExportComplete?: (messageId: string, downloadInfo: DownloadInfo) => void; // Completion callback
}
Usage Examples
Basic Icon Button
import QuartoExportButton from '@/components/QuartoExportButton';
function MessageActions({ message }) {
return (
<div className="message-actions">
<QuartoExportButton
message={message}
exportFormat="pdf"
variant="icon"
onSuccess={() => console.log('Export successful')}
onError={(error) => console.error('Export failed:', error)}
/>
</div>
);
}
Full Button with Custom Endpoint
<QuartoExportButton
message={chatMessage}
exportFormat="docx"
variant="button"
buttonText="Export to Word"
apiEndpoint="/api/custom-export"
reportData={{includeMetadata: true }}
onExportComplete={(messageId, downloadInfo) => {
console.log(`Export complete: ${downloadInfo.filename}`);
}}
/>
Multiple Format Options
function ExportOptions({ message }) {
const formats: ExportFormat[] = ['docx', 'pdf', 'html', 'pptx'];
return (
<div className="export-options">
{formats.map(format => (
<QuartoExportButton
key={format}
message={message}
exportFormat={format}
variant="icon"
showLabel={true}
buttonText={format.toUpperCase()}
/>
))}
</div>
);
}
Export Process Flow
1. Content Preparation
const prepareMessageForExport = (): Record<string, any> => {
let content = message.content;
// Remove thinking blocks if extractTextOnly is enabled
if (extractTextOnly && content) {
content = content.replace(/<thinking>[\s\S]*?<\/thinking>/g, '').trim();
}
return {
content,
timestamp: message.timestamp,
sender: message.sender,
userName: message.userName
};
};
2. API Request
const exportData = {
...reportData,
message: prepareMessageForExport(),
format: exportFormat,
bucketName: bucketName
};
const response = await fetch(apiEndpoint, {
method,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(exportData)
});
3. Download Handling
const result = await response.json();
const downloadUrl = result.download_url;
const filename = result.filename || `export.${exportFormat}`;
// Open in new tab for download
window.open(downloadUrl, '_blank');
Progress Tracking
The component provides visual feedback throughout the export process:
// Progress states with user-friendly messages
setProgress(10); setStatusMessage('Preparing document...');
setProgress(30); setStatusMessage('Generating document...');
setProgress(70); setStatusMessage('Finalizing document...');
setProgress(90); setStatusMessage('Opening document...');
setProgress(100); setStatusMessage('Document ready!');
Format-Specific Icons
Each export format has a dedicated icon:
const getIcon = () => {
if (isLoading) {
return <SpinnerIcon className="animate-spin h-4 w-4" />;
}
switch (exportFormat.toLowerCase()) {
case 'pdf':
return <PDFIcon className="h-4 w-4" />;
case 'html':
return <HTMLIcon className="h-4 w-4" />;
case 'pptx':
return <PowerPointIcon className="h-4 w-4" />;
case 'docx':
default:
return <DocumentIcon className="h-4 w-4" />;
}
};
Error Handling
Comprehensive error management with user-friendly messages:
try {
// Export process
} catch (err) {
const error = err as Error;
console.error('Quarto export failed:', error);
setError(error.message || 'An unexpected error occurred.');
setStatusMessage('');
setProgress(0);
onError(error);
} finally {
setIsLoading(false);
}
Component Variants
Icon Variant
if (variant === 'icon') {
return (
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
<button
onClick={handleExport}
disabled={isLoading}
title={`Export to ${exportFormat.toUpperCase()}`}
className="p-1 rounded-full text-gray-500 hover:text-gray-700"
>
{getIcon()}
{showLabel && (
<span className="ml-1 text-xs">{buttonText}</span>
)}
</button>
{renderStatusContent()}
</div>
);
}
Button Variant
return (
<div className="flex flex-col sm:flex-row sm:items-center gap-2">
<button
onClick={handleExport}
disabled={isLoading}
className="inline-flex items-center justify-center px-3 py-1.5 rounded border"
>
<span className="mr-1.5">{getIcon()}</span>
{isLoading ? 'Exporting...' : buttonText}
</button>
{renderStatusContent()}
</div>
);
Status Display
Real-time status updates with progress bars:
const renderStatusContent = () => {
if (error) {
return (
<span className="text-red-600 font-medium text-xs">
Error: {error}
</span>
);
}
if (statusMessage) {
return (
<div className="flex items-center space-x-2">
<span className="text-blue-700 font-medium text-xs">{statusMessage}</span>
{isLoading && progress > 0 && progress < 100 && (
<div className="w-16 bg-blue-200 rounded-full h-1">
<div
className="bg-blue-600 h-1 rounded-full transition-all"
style={{ width: `${progress}%` }}
/>
</div>
)}
</div>
);
}
return null;
};
Download Information
The component tracks download details:
interface DownloadInfo {
url: string; // Download URL
filename: string; // Generated filename
format: string; // Export format
timestamp: number; // Creation timestamp
}
const downloadInfo: DownloadInfo = {
url: downloadUrl,
filename: filename,
format: exportFormat,
timestamp: Date.now()
};
onExportComplete(messageId, downloadInfo);
Message ID Generation
Consistent message identification for tracking:
// Generate consistent ID for tracking exports
const messageId = message.id ||
`msg-${btoa(message.content.substring(0, 50)).replace(/=/g, '')}-${message.timestamp}`;
Dependencies
Core Libraries
- React hooks for state management
- Fetch API for backend communication
- File download handling with Blob API
UI Components
- Custom button styling
- Loading spinners and progress indicators
- Error message display
Utilities
- Base64 encoding for ID generation
- String manipulation for content cleaning
- File extension mapping
Accessibility
- ARIA labels for screen readers
- Keyboard navigation support
- Clear loading states
- Descriptive error messages
Performance Considerations
- Efficient content processing
- Proper cleanup of blob URLs
- Non-blocking user interface during export
- Error recovery mechanisms
Security
- Input validation for message content
- Safe file extension handling
- Secure API endpoint communication
- Proper error message sanitization
Related Components
- Chat message components that trigger exports
- Download management systems
- Backend API integration
- File storage services