Paso 1: Configurar un proyecto Next.js
Para empezar, crea un nuevo proyecto Next.js con TypeScript y Tailwind CSS. El primer paso para dar vida a su aplicación de transcripción de audio es configurar un nuevo proyecto Next.js.
Para Ello, necesitará usar el comando create-next-app
, que inicia el proceso de configuración del proyecto y le hace una serie de preguntas para configurar sus preferencias.
npx create-next-app@latest
Durante el proceso de configuración del proyecto, se le harán varias preguntas para adaptar el proyecto a sus necesidades. Estas son las respuestas típicas, pero siéntase libre de ajustarlas según sus preferencias:
- ¿Cuál es el nombre de su proyecto? transcribetext
- ¿Le gustaría usar TypeScript? Sí
- ¿Le gustaría usar ESLint? Sí
- ¿Le gustaría usar Tailwind CSS? Sí
- ¿Le gustaría usar el directorio
src/
para su código? No
- ¿Le gustaría usar App Router? (recomendado) Sí
- ¿Le gustaría personalizar el alias de importación? No
Paso 2: Instalar las dependencias necesarias
Una vez que el proyecto está configurado, debemos instalar las dependencias requeridas. Abre tu terminal, navega al directorio del proyecto y ejecuta los siguientes comandos para instalar el Groq SDK y Lucid React:
npm install --save groq-sdk
npm i lucid-react
Paso 3: Asegurar tu clave API de Groq
Para autenticar tu aplicación con el API Groq, necesitarás una clave API.
Para obtener una clave API de Groq, deberás visitar su sitio web y seguir su proceso de registro. Una vez que te hayas registrado, navega hasta la sección de las claves API en el panel de control de tu cuenta. Aquí, puedes crear una nueva clave que usará tu aplicación para comunicarse con los servicios de Groq.
Después, crea un archivo .env.local
en la raíz de tu proyecto y añade tu clave API:
GROQ_API_KEY=YOUR_API_KEY_HERE
Reemplaza YOUR_API_KEY_HERE
con tu clave API real. Asegúrate de no exponer este archivo en tu repositorio público.
Paso 4: Componente AudioUploader
Este es el contenido del AudioUploader.tsx
:
"use client";
import React, { useState, useRef, useEffect } from "react";
import { Mic, Upload, Loader2, StopCircle, Volume2 } from "lucide-react";
import TranscriptionResult from "./TranscriptionResult";
import { useHasBrowser } from "@/lib/useHasBrowser";
interface AudioVisualizerProps {
audioUrl: string | null; mediaStream: MediaStream | null; isLive: boolean;
}
const ALLOWED_TYPES = ["audio/mpeg", "audio/wav", "audio/x-m4a", "audio/mp4"];
const AudioUploader = () => {
const hasBrowser = useHasBrowser();
const [file, setFile] = useState<File | null>(null);
const [audioUrl, setAudioUrl] = useState<string | null>(null);
const [transcription, setTranscription] = useState<string>("");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string>("");
const [isRecording, setIsRecording] = useState(false);
const [mediaStream, setMediaStream] = useState<MediaStream | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const recognitionRef = useRef<any>(null);
useEffect(() => {
if (!hasBrowser) return;
const speechSupported = 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window;
setIsSpeechSupported(speechSupported);
const recognition = new ((window as any).SpeechRecognition || (window as any).webkitSpeechRecognition)();
recognition.continuous = true;
recognition.interimResults = true;
recognition.lang = "en-US";
recognition.onresult = (event: any) => {
let interimTranscript = "";
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
finalTranscript += transcript + " ";
} else {
interimTranscript += transcript;
}
}
setTranscription(finalTranscript + interimTranscript);
};
recognition.onerror = (event: any) => {
console.error("Speech recognition error:", event);
setError(`Speech recognition error: ${event.error}`);
setIsRecording(false)
};
recognitionRef.current = recognition;
}, [hasBrowser]);
const handleStopRecording = async () => {
if(recognitionRef.current && isRecording){
recognitionRef.current.stop();
if (mediaStream) {
mediaStream.getTracks().forEach(track => track.stop());
setMediaStream(null)
}
setIsRecording(false);
}
}
const handleStartRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
setMediaStream(stream);
recognitionRef.current.start();
setIsRecording(true);
setError("");
} catch (err: any) {
console.error("Error accessing microphone:", err);
setError(`Microphone access denied or not available.`);
}
};
const handleFileChange = (selectedFile: File | undefined) => {
if (!selectedFile || !ALLOWED_TYPES.includes(selectedFile.type)) {
setError("Please upload an MP3, WAV, or M4A file");
return;
}
if (selectedFile.size > maxSize) {
setError(`File size must be less than ${maxSizeReadable}`);
return;
}
if (audioUrl) {
URL.revokeObjectURL(audioUrl);
}
setFile(selectedFile);
setAudioUrl(URL.createObjectURL(selectedFile));
setIsRecording(false);
setError("");
}
const handleSubmit = async () => {
setIsLoading(true);
setError("");
if (!file) return;
const formData = new FormData();
formData.append("file", file);
try {
const response = await fetch("/api/transcribe", {
method: "POST",
body: formData,
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error?.message || "Failed to transcribe audio");
}
setTranscription(data.transcription);
} catch (error: any) {
console.error(error);
setError(error.message);
} finally {
setIsLoading(false);
}
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
const droppedFile = e.dataTransfer.files[0];
if (droppedFile) {
handleFileChange(droppedFile)
}
};
return (
<main className="min-h-screen p-8 bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-gray-900 dark:to-indigo-950">
<UploadCard onFileChange={handleFileChange} handleDrop={handleDrop} isRecording={isRecording} handleStartRecording={handleStartRecording} handleStopRecording={handleStopRecording} isLoading={isLoading} isSpeechSupported={isSpeechSupported} />
<TranscriptionResult text={transcription} />
</main>
);
};
export default AudioUploader;
Este componente incluye las siguientes funciones y elementos clave:
- Subida de archivos de audio: Permite a los usuarios seleccionar y subir archivos de audio desde sus dispositivos.
- Grabación en vivo: Ofrece la opción de grabar audio en vivo usando el micrófono del dispositivo.
- Manejo de estado: Gestiona el estado de la interfaz de usuario, incluyendo la carga, los errores y la transcripción de texto.
- Diseño adaptativo: Diseñado con clases Tailwind CSS para asegurar un buen aspecto en diferentes tamaños de pantalla y dispositivos.
Nota: He omitido el código correspondiente al AudioVisualizer y TranscriptionResult. Ambos son componentes cruciales para la aplicación, pero no puedo sobrecargar la respuesta. Te recomiendo que veas el vídeo o uses el código fuente para completar estos pasos.