Spaces:
Sleeping
Sleeping
| 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; | |