feat: revue de code, doc CODE_REVIEW, forfaits 2026, traduction LLM, providers avec modèle
Made-with: Cursor
This commit is contained in:
200
frontend/src/app/dashboard/translate/page.tsx
Normal file
200
frontend/src/app/dashboard/translate/page.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { Languages, ShieldCheck, Clock, ArrowRight, RotateCcw, Loader2 } from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { FileDropZone } from './FileDropZone';
|
||||
import { FilePreview } from './FilePreview';
|
||||
import { useFileUpload } from './useFileUpload';
|
||||
import { useTranslationConfig } from './useTranslationConfig';
|
||||
import { useTranslationSubmit } from './useTranslationSubmit';
|
||||
import { LanguageSelector } from './LanguageSelector';
|
||||
import { ProviderSelector } from './ProviderSelector';
|
||||
import { TranslationProgress } from './TranslationProgress';
|
||||
import { TranslationComplete } from './TranslationComplete';
|
||||
import { useNotification } from '@/components/ui/notification';
|
||||
|
||||
export default function TranslatePage() {
|
||||
const upload = useFileUpload();
|
||||
const config = useTranslationConfig(!!upload.file);
|
||||
const submit = useTranslationSubmit();
|
||||
const { error: showError } = useNotification();
|
||||
const lastErrorRef = useRef<string | null>(null);
|
||||
|
||||
const handleTranslate = async () => {
|
||||
if (!upload.file || !config.isConfigValid) return;
|
||||
await submit.submitTranslation(upload.file, config.getConfig());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (submit.error && submit.error !== lastErrorRef.current) {
|
||||
lastErrorRef.current = submit.error;
|
||||
showError({
|
||||
title: 'Translation Error',
|
||||
description: submit.error,
|
||||
});
|
||||
}
|
||||
}, [submit.error, showError]);
|
||||
|
||||
const handleNewTranslation = () => {
|
||||
submit.reset();
|
||||
upload.removeFile();
|
||||
};
|
||||
|
||||
const isConfiguring = upload.file && submit.status === 'idle' && !submit.isSubmitting;
|
||||
const isProcessing = (submit.status === 'processing' || submit.isSubmitting) && submit.status !== 'completed';
|
||||
const isCompleted = submit.status === 'completed';
|
||||
const isFailed = submit.status === 'failed';
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-xl px-4 py-6 lg:px-8">
|
||||
<Card className="border-border/70 shadow-lg">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Languages className="size-5" />
|
||||
Office Translator
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Upload an Excel, Word, or PowerPoint file to translate
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
{upload.file && !isProcessing && !isCompleted && (
|
||||
<div className="flex flex-col items-center justify-center gap-3 rounded-lg border-2 border-success/40 bg-success/5 px-6 py-4">
|
||||
<FilePreview file={upload.file} onRemove={upload.removeFile} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!upload.file && !isProcessing && !isCompleted && (
|
||||
<FileDropZone upload={upload} />
|
||||
)}
|
||||
|
||||
{upload.error && !isProcessing && !isCompleted && (
|
||||
<p className="text-sm text-destructive">{upload.error}</p>
|
||||
)}
|
||||
|
||||
{isConfiguring && (
|
||||
<>
|
||||
<div className="my-2 flex items-center gap-2">
|
||||
<div className="h-px flex-1 bg-border" />
|
||||
<span className="text-xs text-muted-foreground">Configuration</span>
|
||||
<div className="h-px flex-1 bg-border" />
|
||||
</div>
|
||||
|
||||
<LanguageSelector
|
||||
sourceLang={config.sourceLang}
|
||||
targetLang={config.targetLang}
|
||||
languages={config.languages}
|
||||
isLoading={config.isLoadingLanguages}
|
||||
error={config.languagesError}
|
||||
onSourceChange={config.setSourceLang}
|
||||
onTargetChange={config.setTargetLang}
|
||||
/>
|
||||
|
||||
<ProviderSelector
|
||||
provider={config.provider}
|
||||
onProviderChange={config.setProvider}
|
||||
availableProviders={config.availableProviders}
|
||||
isLoadingProviders={config.isLoadingProviders}
|
||||
isPro={config.isPro}
|
||||
/>
|
||||
|
||||
<Button
|
||||
size="lg"
|
||||
className="w-full text-sm font-semibold"
|
||||
disabled={!config.isConfigValid || submit.isSubmitting}
|
||||
onClick={handleTranslate}
|
||||
>
|
||||
{submit.isSubmitting ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 size-4 animate-spin" />
|
||||
Uploading...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Translate Document
|
||||
<ArrowRight className="ml-2 size-4" />
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isProcessing && !isCompleted && (
|
||||
<>
|
||||
<div className="flex items-center justify-between text-sm text-muted-foreground mb-2">
|
||||
<span>File: {submit.fileName || upload.file?.name}</span>
|
||||
</div>
|
||||
<TranslationProgress
|
||||
progress={submit.progress}
|
||||
currentStep={submit.currentStep || (submit.isSubmitting ? 'Uploading file...' : 'Starting translation...')}
|
||||
estimatedRemaining={submit.estimatedRemaining}
|
||||
error={null}
|
||||
isPolling={submit.isPolling}
|
||||
isUploading={submit.isSubmitting}
|
||||
isCompleted={false}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleNewTranslation}
|
||||
className="w-full mt-2"
|
||||
>
|
||||
<RotateCcw className="mr-2 size-4" />
|
||||
Cancel
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isCompleted && submit.jobId && (
|
||||
<TranslationComplete
|
||||
jobId={submit.jobId}
|
||||
fileName={submit.fileName}
|
||||
onNewTranslation={handleNewTranslation}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isFailed && (
|
||||
<>
|
||||
<TranslationProgress
|
||||
progress={submit.progress}
|
||||
currentStep={submit.currentStep}
|
||||
estimatedRemaining={submit.estimatedRemaining}
|
||||
error={submit.error}
|
||||
isPolling={false}
|
||||
isCompleted={false}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleNewTranslation}
|
||||
className="w-full mt-2"
|
||||
>
|
||||
<RotateCcw className="mr-2 size-4" />
|
||||
Try Again
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!upload.file && !isProcessing && !isCompleted && !isFailed && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Supported formats: Excel (.xlsx), Word (.docx), PowerPoint (.pptx)
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex items-center justify-center gap-4 mt-4 text-xs text-muted-foreground">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<ShieldCheck className="size-3.5" />
|
||||
<span>Zero Data Retention</span>
|
||||
</div>
|
||||
<div className="h-3 w-px bg-border" />
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Clock className="size-3.5" />
|
||||
<span>Files deleted after 60 min</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user