Diarization / App.tsx
stephane09's picture
Upload 17 files
1e2f309 verified
import React, { useState, useCallback, useMemo } from 'react';
import { DiarizationEntry } from './types';
import { processAudioFile } from './services/geminiService';
import FileUpload from './components/FileUpload';
import Loader from './components/Loader';
import DiarizationResult from './components/DiarizationResult';
import { ResetIcon } from './components/icons';
import ApiKeyInput from './components/ApiKeyInput';
const App: React.FC = () => {
const [apiKey, setApiKey] = useState<string>('');
const [useStarFormat, setUseStarFormat] = useState<boolean>(true);
const [file, setFile] = useState<File | null>(null);
const [diarizationResult, setDiarizationResult] = useState<DiarizationEntry[] | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [loadingMessage, setLoadingMessage] = useState<string>('');
const [error, setError] = useState<string | null>(null);
const isApiKeySet = apiKey.trim() !== '';
const handleFileSelect = useCallback(async (selectedFile: File) => {
if (!isApiKeySet) {
setError("Veuillez d'abord fournir une clé API Gemini.");
return;
}
setFile(selectedFile);
setIsLoading(true);
setError(null);
setDiarizationResult(null);
setLoadingMessage('Analyse audio en cours... (cela peut prendre un moment)');
try {
const result = await processAudioFile(selectedFile, apiKey);
const formattedResult = result.map(entry => ({
...entry,
speaker: useStarFormat
? `*${entry.speaker.replace(/ /g, '_')}`
: entry.speaker,
}));
setDiarizationResult(formattedResult);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'An unexpected error occurred.';
setError(errorMessage);
} finally {
setIsLoading(false);
setLoadingMessage('');
}
}, [apiKey, isApiKeySet, useStarFormat]);
const speakerCount = useMemo(() => {
if (!diarizationResult) return 0;
const speakers = new Set(diarizationResult.map(entry => entry.speaker));
return speakers.size;
}, [diarizationResult]);
const handleReset = () => {
setFile(null);
setDiarizationResult(null);
setIsLoading(false);
setError(null);
setLoadingMessage('');
};
const renderContent = () => {
if (isLoading) {
return <Loader message={loadingMessage} />;
}
if (error) {
return (
<div className="text-center text-red-400 bg-red-900/50 p-4 rounded-lg">
<p className="font-bold">Erreur</p>
<p>{error}</p>
</div>
);
}
if (diarizationResult && file) {
return <DiarizationResult result={diarizationResult} fileName={file.name} speakerCount={speakerCount} useStarFormat={useStarFormat} />;
}
// L'upload est désactivé si la clé n'est pas fournie
return <FileUpload onFileSelect={handleFileSelect} isLoading={isLoading || !isApiKeySet} />;
};
return (
<div className="min-h-screen bg-gray-900 text-gray-100 flex flex-col items-center justify-center p-4 sm:p-6 lg:p-8">
<div className="w-full max-w-4xl text-center mb-8">
<h1 className="text-4xl sm:text-5xl font-extrabold tracking-tight text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
Diarisation de MP3 par IA
</h1>
<p className="mt-4 text-lg text-gray-400">
Fournissez votre clé API Gemini, puis téléchargez un MP3 pour obtenir une analyse des locuteurs.
</p>
</div>
{!diarizationResult && (
<div className="w-full max-w-2xl mb-8 p-6 bg-gray-800/50 border border-gray-700 rounded-xl">
<h2 className="text-xl font-bold mb-4 text-center">Configuration</h2>
<ApiKeyInput apiKey={apiKey} setApiKey={setApiKey} />
<div className="mt-4 flex items-center justify-center">
<input
type="checkbox"
id="star-format"
checked={useStarFormat}
onChange={(e) => setUseStarFormat(e.target.checked)}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 bg-gray-700"
/>
<label htmlFor="star-format" className="ml-2 block text-sm text-gray-300">
Formater le nom du locuteur (ex: *Locuteur_A)
</label>
</div>
</div>
)}
<main className="w-full max-w-4xl flex-grow flex items-center justify-center">
{renderContent()}
</main>
{(diarizationResult || error) && !isLoading && (
<footer className="mt-8">
<button
onClick={handleReset}
className="flex items-center gap-2 px-6 py-2 bg-gray-700 text-white font-semibold rounded-lg hover:bg-gray-600 transition-colors duration-300"
>
<ResetIcon className="w-5 h-5" />
Analyser un autre fichier
</button>
</footer>
)}
</div>
);
};
export default App;