CoolFace
Apppublic

honey126/VoxAI

sourceHugging Facemitupdated 9mo agoView on Hugging Face
0likes
react-client-example.tsx308 linesDownload Raw Back to root
1/**2 * React Component Example for F5-TTS API3 *4 * This shows how to integrate the F5-TTS API into your React application5 * Copy this into your React project and customize as needed6 */7 8import React, { useState, useRef } from 'react';9 10// TypeScript types11interface TTSRequest {12  refAudio: File;13  refText: string;14  genText: string;15  removesilence?: boolean;16  targetRms?: number;17  speed?: number;18  nfeStep?: number;19}20 21interface TTSResponse {22  audioUrl: string;23  seed: number;24  sampleRate: number;25}26 27// API configuration28const API_BASE_URL = process.env.REACT_APP_TTS_API_URL || 'http://localhost:8000';29 30/**31 * Hook for F5-TTS API calls32 */33const useF5TTS = () => {34  const [loading, setLoading] = useState(false);35  const [error, setError] = useState<string | null>(null);36 37  const generateSpeech = async (request: TTSRequest): Promise<Blob | null> => {38    setLoading(true);39    setError(null);40 41    try {42      const formData = new FormData();43      formData.append('ref_audio', request.refAudio);44      formData.append('ref_text', request.refText);45      formData.append('gen_text', request.genText);46 47      if (request.removesilence !== undefined) {48        formData.append('remove_silence', String(request.removesilence));49      }50      if (request.targetRms !== undefined) {51        formData.append('target_rms', String(request.targetRms));52      }53      if (request.speed !== undefined) {54        formData.append('speed', String(request.speed));55      }56      if (request.nfeStep !== undefined) {57        formData.append('nfe_step', String(request.nfeStep));58      }59 60      const response = await fetch(`${API_BASE_URL}/api/tts`, {61        method: 'POST',62        body: formData,63      });64 65      if (!response.ok) {66        const errorData = await response.json();67        throw new Error(errorData.detail || 'Speech generation failed');68      }69 70      const audioBlob = await response.blob();71      return audioBlob;72 73    } catch (err) {74      const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';75      setError(errorMessage);76      console.error('TTS Error:', err);77      return null;78    } finally {79      setLoading(false);80    }81  };82 83  return { generateSpeech, loading, error };84};85 86/**87 * Example React Component88 */89export const F5TTSGenerator: React.FC = () => {90  const { generateSpeech, loading, error } = useF5TTS();91 92  const [refAudio, setRefAudio] = useState<File | null>(null);93  const [refText, setRefText] = useState('');94  const [genText, setGenText] = useState('');95  const [audioUrl, setAudioUrl] = useState<string | null>(null);96  const [removeSilence, setRemoveSilence] = useState(false);97  const [speed, setSpeed] = useState(1.0);98 99  const audioRef = useRef<HTMLAudioElement>(null);100 101  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {102    if (e.target.files && e.target.files[0]) {103      setRefAudio(e.target.files[0]);104    }105  };106 107  const handleGenerate = async () => {108    if (!refAudio || !refText || !genText) {109      alert('Please provide reference audio, reference text, and generation text');110      return;111    }112 113    // Revoke previous audio URL to free memory114    if (audioUrl) {115      URL.revokeObjectURL(audioUrl);116    }117 118    const audioBlob = await generateSpeech({119      refAudio,120      refText,121      genText,122      removesilence: removeSilence,123      speed,124    });125 126    if (audioBlob) {127      const url = URL.createObjectURL(audioBlob);128      setAudioUrl(url);129    }130  };131 132  const handleDownload = () => {133    if (audioUrl) {134      const a = document.createElement('a');135      a.href = audioUrl;136      a.download = 'generated_speech.wav';137      document.body.appendChild(a);138      a.click();139      document.body.removeChild(a);140    }141  };142 143  return (144    <div className="f5-tts-generator" style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>145      <h1>๐Ÿ—ฃ๏ธ F5-TTS Voice Cloning</h1>146 147      {/* Reference Audio Upload */}148      <div className="form-group" style={{ marginBottom: '20px' }}>149        <label htmlFor="ref-audio" style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold' }}>150          Reference Audio (WAV/MP3)151        </label>152        <input153          id="ref-audio"154          type="file"155          accept="audio/*"156          onChange={handleFileChange}157          style={{ width: '100%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px' }}158        />159        {refAudio && <p style={{ marginTop: '8px', color: '#666' }}>Selected: {refAudio.name}</p>}160      </div>161 162      {/* Reference Text */}163      <div className="form-group" style={{ marginBottom: '20px' }}>164        <label htmlFor="ref-text" style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold' }}>165          Reference Text (What the reference audio says)166        </label>167        <textarea168          id="ref-text"169          value={refText}170          onChange={(e) => setRefText(e.target.value)}171          placeholder="some call me nature, others call me mother nature."172          rows={3}173          style={{ width: '100%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px', fontFamily: 'inherit' }}174        />175      </div>176 177      {/* Generation Text */}178      <div className="form-group" style={{ marginBottom: '20px' }}>179        <label htmlFor="gen-text" style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold' }}>180          Text to Generate (In the same voice)181        </label>182        <textarea183          id="gen-text"184          value={genText}185          onChange={(e) => setGenText(e.target.value)}186          placeholder="Enter the text you want to generate in the reference voice..."187          rows={5}188          style={{ width: '100%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px', fontFamily: 'inherit' }}189        />190      </div>191 192      {/* Advanced Options */}193      <div className="advanced-options" style={{ marginBottom: '20px', padding: '15px', background: '#f5f5f5', borderRadius: '4px' }}>194        <h3 style={{ marginTop: 0 }}>Advanced Options</h3>195 196        <div style={{ marginBottom: '12px' }}>197          <label>198            <input199              type="checkbox"200              checked={removeSilence}201              onChange={(e) => setRemoveSilence(e.target.checked)}202              style={{ marginRight: '8px' }}203            />204            Remove silence from output205          </label>206        </div>207 208        <div>209          <label htmlFor="speed" style={{ display: 'block', marginBottom: '4px' }}>210            Speed: {speed.toFixed(1)}x211          </label>212          <input213            id="speed"214            type="range"215            min="0.5"216            max="2.0"217            step="0.1"218            value={speed}219            onChange={(e) => setSpeed(parseFloat(e.target.value))}220            style={{ width: '100%' }}221          />222        </div>223      </div>224 225      {/* Generate Button */}226      <button227        onClick={handleGenerate}228        disabled={loading || !refAudio || !refText || !genText}229        style={{230          width: '100%',231          padding: '15px',232          fontSize: '16px',233          fontWeight: 'bold',234          color: 'white',235          background: loading ? '#ccc' : '#007bff',236          border: 'none',237          borderRadius: '4px',238          cursor: loading ? 'not-allowed' : 'pointer',239          marginBottom: '20px'240        }}241      >242        {loading ? '๐Ÿ”„ Generating Speech...' : '๐ŸŽ™๏ธ Generate Speech'}243      </button>244 245      {/* Error Display */}246      {error && (247        <div style={{ padding: '15px', background: '#fee', border: '1px solid #fcc', borderRadius: '4px', marginBottom: '20px' }}>248          <strong>Error:</strong> {error}249        </div>250      )}251 252      {/* Audio Player */}253      {audioUrl && (254        <div className="audio-output" style={{ padding: '20px', background: '#f0f8ff', borderRadius: '4px' }}>255          <h3>โœ… Generated Audio</h3>256          <audio257            ref={audioRef}258            controls259            src={audioUrl}260            style={{ width: '100%', marginBottom: '15px' }}261          />262          <button263            onClick={handleDownload}264            style={{265              padding: '10px 20px',266              fontSize: '14px',267              color: 'white',268              background: '#28a745',269              border: 'none',270              borderRadius: '4px',271              cursor: 'pointer'272            }}273          >274            โฌ‡๏ธ Download Audio275          </button>276        </div>277      )}278 279      {/* Loading Indicator */}280      {loading && (281        <div style={{ textAlign: 'center', padding: '20px' }}>282          <div style={{283            border: '4px solid #f3f3f3',284            borderTop: '4px solid #007bff',285            borderRadius: '50%',286            width: '40px',287            height: '40px',288            animation: 'spin 1s linear infinite',289            margin: '0 auto'290          }} />291          <p style={{ marginTop: '15px', color: '#666' }}>292            Generating speech... This may take 10-30 seconds depending on text length.293          </p>294        </div>295      )}296 297      <style>{`298        @keyframes spin {299          0% { transform: rotate(0deg); }300          100% { transform: rotate(360deg); }301        }302      `}</style>303    </div>304  );305};306 307export default F5TTSGenerator;308