"use client"; import { useState, useCallback, useEffect } from "react"; import { useDropzone } from "react-dropzone"; import { Upload, FileText, FileSpreadsheet, Presentation, X, Download, Loader2, Cpu, AlertTriangle } from "lucide-react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { useTranslationStore } from "@/lib/store"; import { translateDocument, languages, providers, extractTextsFromDocument, reconstructDocument, TranslatedText } from "@/lib/api"; import { useWebLLM } from "@/lib/webllm"; import { cn } from "@/lib/utils"; const fileIcons: Record = { xlsx: FileSpreadsheet, xls: FileSpreadsheet, docx: FileText, doc: FileText, pptx: Presentation, ppt: Presentation, }; type ProviderType = "google" | "ollama" | "deepl" | "libre" | "webllm" | "openai" | "openrouter"; export function FileUploader() { const { settings, isTranslating, progress, setTranslating, setProgress } = useTranslationStore(); const webllm = useWebLLM(); const [file, setFile] = useState(null); const [targetLanguage, setTargetLanguage] = useState(settings.defaultTargetLanguage); const [provider, setProvider] = useState(settings.defaultProvider); const [translateImages, setTranslateImages] = useState(settings.translateImages); const [downloadUrl, setDownloadUrl] = useState(null); const [error, setError] = useState(null); const [translationStatus, setTranslationStatus] = useState(""); // Sync with store settings when they change useEffect(() => { setTargetLanguage(settings.defaultTargetLanguage); setProvider(settings.defaultProvider); setTranslateImages(settings.translateImages); }, [settings.defaultTargetLanguage, settings.defaultProvider, settings.translateImages]); const onDrop = useCallback((acceptedFiles: File[]) => { if (acceptedFiles.length > 0) { setFile(acceptedFiles[0]); setDownloadUrl(null); setError(null); } }, []); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"], "application/vnd.ms-excel": [".xls"], "application/vnd.openxmlformats-officedocument.wordprocessingml.document": [".docx"], "application/msword": [".doc"], "application/vnd.openxmlformats-officedocument.presentationml.presentation": [".pptx"], "application/vnd.ms-powerpoint": [".ppt"], }, multiple: false, }); const getFileExtension = (filename: string) => { return filename.split(".").pop()?.toLowerCase() || ""; }; const getFileIcon = (filename: string) => { const ext = getFileExtension(filename); return fileIcons[ext] || FileText; }; const formatFileSize = (bytes: number) => { if (bytes < 1024) return bytes + " B"; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"; return (bytes / (1024 * 1024)).toFixed(1) + " MB"; }; const handleTranslate = async () => { if (!file) return; // Validate provider-specific requirements if (provider === "openai" && !settings.openaiApiKey) { setError("OpenAI API key not configured. Go to Settings > Translation Services to add your API key."); return; } if (provider === "deepl" && !settings.deeplApiKey) { setError("DeepL API key not configured. Go to Settings > Translation Services to add your API key."); return; } // WebLLM specific validation if (provider === "webllm") { if (!webllm.isWebGPUSupported()) { setError("WebGPU is not supported in this browser. Please use Chrome 113+ or Edge 113+."); return; } if (!webllm.isLoaded) { setError("WebLLM model not loaded. Go to Settings > Translation Services to load a model first."); return; } } setTranslating(true); setProgress(0); setError(null); setDownloadUrl(null); setTranslationStatus(""); try { // For WebLLM, use client-side translation if (provider === "webllm") { await handleWebLLMTranslation(); } else { await handleServerTranslation(); } } catch (err) { setError(err instanceof Error ? err.message : "Translation failed"); } finally { setTranslating(false); setTranslationStatus(""); } }; // Get language name from code const getLanguageName = (code: string): string => { const lang = languages.find(l => l.code === code); return lang ? lang.name : code; }; // WebLLM client-side translation const handleWebLLMTranslation = async () => { if (!file) return; try { // Step 1: Extract texts from document setTranslationStatus("Extracting texts from document..."); setProgress(5); const extractResult = await extractTextsFromDocument(file); if (extractResult.texts.length === 0) { throw new Error("No translatable text found in document"); } setTranslationStatus(`Found ${extractResult.texts.length} texts to translate`); setProgress(10); // Step 2: Translate each text using WebLLM const translations: TranslatedText[] = []; const totalTexts = extractResult.texts.length; const langName = getLanguageName(targetLanguage); for (let i = 0; i < totalTexts; i++) { const item = extractResult.texts[i]; setTranslationStatus(`Translating ${i + 1}/${totalTexts}: "${item.text.substring(0, 30)}..."`); const translatedText = await webllm.translate( item.text, langName, settings.systemPrompt || undefined, settings.glossary || undefined ); translations.push({ id: item.id, translated_text: translatedText, }); // Update progress (10% for extraction, 80% for translation, 10% for reconstruction) const translationProgress = 10 + (80 * (i + 1) / totalTexts); setProgress(translationProgress); } // Step 3: Reconstruct document with translations setTranslationStatus("Reconstructing document..."); setProgress(92); const blob = await reconstructDocument( extractResult.session_id, translations, targetLanguage ); setProgress(100); setTranslationStatus("Translation complete!"); const url = URL.createObjectURL(blob); setDownloadUrl(url); } catch (err) { throw err; } }; // Server-side translation (existing logic) const handleServerTranslation = async () => { if (!file) return; // Simulate progress for UX let currentProgress = 0; const progressInterval = setInterval(() => { currentProgress = Math.min(currentProgress + Math.random() * 10, 90); setProgress(currentProgress); }, 500); try { const blob = await translateDocument({ file, targetLanguage, provider, ollamaModel: settings.ollamaModel, translateImages: translateImages || settings.translateImages, systemPrompt: settings.systemPrompt, glossary: settings.glossary, libreUrl: settings.libreTranslateUrl, openaiApiKey: settings.openaiApiKey, openaiModel: settings.openaiModel, openrouterApiKey: settings.openrouterApiKey, openrouterModel: settings.openrouterModel, }); clearInterval(progressInterval); setProgress(100); const url = URL.createObjectURL(blob); setDownloadUrl(url); } catch (err) { clearInterval(progressInterval); throw err; } }; const handleDownload = () => { if (!downloadUrl || !file) return; const a = document.createElement("a"); a.href = downloadUrl; const ext = getFileExtension(file.name); const baseName = file.name.replace(`.${ext}`, ""); a.download = `${baseName}_translated.${ext}`; document.body.appendChild(a); a.click(); document.body.removeChild(a); }; const removeFile = () => { setFile(null); setDownloadUrl(null); setError(null); setProgress(0); }; const FileIcon = file ? getFileIcon(file.name) : FileText; return (
{/* File Drop Zone */} Upload Document Drag and drop or click to select a file (Excel, Word, PowerPoint) {!file ? (

{isDragActive ? "Drop the file here..." : "Drag & drop a document here, or click to select"}

Supports: .xlsx, .docx, .pptx

) : (

{file.name}

{formatFileSize(file.size)}

{getFileExtension(file.name).toUpperCase()}
)}
{/* Translation Options */} Translation Options Select your target language and start translating {/* Target Language */}
{/* Translate Button */} {/* Progress Bar */} {isTranslating && (
{translationStatus || "Processing..."} {Math.round(progress)}%
{provider === "webllm" && (

Translating locally with WebLLM...

)}
)} {/* Error */} {error && (

{error}

)}
{/* Download Section */} {downloadUrl && ( Translation Complete Your document has been translated successfully )}
); }