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
  • Chat message components that trigger exports
  • Download management systems
  • Backend API integration
  • File storage services