From 18285851ac885f53966ca8da1019e2b28b33e7e1 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:49:40 +0000 Subject: [PATCH] Refactor audio section in sidebar --- src/components/Layout.tsx | 13 +- src/components/audio/GenerationView.tsx | 342 +++++++++++++++++++++ src/components/audio/TranscriptionView.tsx | 318 +++++++++++++++++++ src/pages/Index.tsx | 8 +- 4 files changed, 672 insertions(+), 9 deletions(-) create mode 100644 src/components/audio/GenerationView.tsx create mode 100644 src/components/audio/TranscriptionView.tsx diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 4c9f62f..a5ba627 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,16 +1,16 @@ import { useState } from "react"; -import { MessageSquare, Image, Mic, ArrowLeft, Plus, ChevronLeft, ChevronRight, Bot, FileText } from "lucide-react"; +import { MessageSquare, Image, FileAudio, Mic, ArrowLeft, Plus, ChevronLeft, ChevronRight, Bot, Brain } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { ThemeToggle } from "@/components/ThemeToggle"; interface LayoutProps { children: React.ReactNode; - activeTab: "chat" | "images" | "audio" | "bots" | "agent"; - onTabChange: (tab: "chat" | "images" | "audio" | "bots" | "agent") => void; + activeTab: "chat" | "images" | "transcription" | "generation" | "bots" | "agent"; + onTabChange: (tab: "chat" | "images" | "transcription" | "generation" | "bots" | "agent") => void; } -type TabType = "chat" | "images" | "audio" | "bots" | "agent"; +type TabType = "chat" | "images" | "transcription" | "generation" | "bots" | "agent"; export const Layout = ({ children, activeTab, onTabChange }: LayoutProps) => { const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); @@ -18,9 +18,10 @@ export const Layout = ({ children, activeTab, onTabChange }: LayoutProps) => { const tabs = [ { id: "chat" as TabType, label: "Bate-papo", icon: MessageSquare }, { id: "images" as TabType, label: "Imagens", icon: Image }, - { id: "audio" as TabType, label: "Áudio", icon: Mic }, + { id: "transcription" as TabType, label: "Transcrição de Áudio", icon: FileAudio }, + { id: "generation" as TabType, label: "Geração de Áudio", icon: Mic }, { id: "bots" as TabType, label: "Bots", icon: Bot }, - { id: "agent" as TabType, label: "Agente de Parecer", icon: FileText }, + { id: "agent" as TabType, label: "Agente de Parecer", icon: Brain }, ]; return ( diff --git a/src/components/audio/GenerationView.tsx b/src/components/audio/GenerationView.tsx new file mode 100644 index 0000000..c9a057e --- /dev/null +++ b/src/components/audio/GenerationView.tsx @@ -0,0 +1,342 @@ +import { useState, useEffect } from "react"; +import { ChatHeader } from "@/components/ChatHeader"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { Input } from "@/components/ui/input"; +import { Mic, Play, Download, Trash2, Search } from "lucide-react"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { useToast } from "@/hooks/use-toast"; + +interface GeneratedAudio { + id: string; + text: string; + voice: string; + voiceLabel: string; + audioUrl: string; + timestamp: Date; +} + +const VOICE_OPTIONS = { + alloy: { + label: "Alloy", + gender: "Masculina", + style: "Neutra, equilibrada, tom corporativo", + description: "Boa para tutoriais e comunicações institucionais." + }, + echo: { + label: "Echo", + gender: "Masculina", + style: "Forte e profissional, mais grave", + description: "Ideal para voz de autoridade ou locução firme." + }, + fable: { + label: "Fable", + gender: "Feminina", + style: "Narrativa, calorosa e envolvente", + description: "Ótima para storytelling e áudios empáticos." + }, + onyx: { + label: "Onyx", + gender: "Masculina", + style: "Grave, autoritária, impactante", + description: "Excelente para trailers, mensagens sérias ou institucionais." + }, + nova: { + label: "Nova", + gender: "Feminina", + style: "Brilhante, animada, energética", + description: "Boa para vídeos curtos, marketing ou conteúdos leves." + }, + shimmer: { + label: "Shimmer", + gender: "Feminina", + style: "Suave, otimista, clara", + description: "Boa para mensagens acolhedoras, explicações e IA conversacional." + } +}; + +export const GenerationView = () => { + const [textToSpeech, setTextToSpeech] = useState(""); + const [isProcessing, setIsProcessing] = useState(false); + const [selectedVoice, setSelectedVoice] = useState("alloy"); + const [generatedAudio, setGeneratedAudio] = useState(null); + const [audioHistory, setAudioHistory] = useState([]); + const [audioSearchQuery, setAudioSearchQuery] = useState(""); + const { toast } = useToast(); + + useEffect(() => { + const savedAudios = localStorage.getItem('audioHistory'); + if (savedAudios) { + setAudioHistory(JSON.parse(savedAudios)); + } + }, []); + + const handleGenerateAudio = () => { + setIsProcessing(true); + + setTimeout(() => { + const audio: GeneratedAudio = { + id: Date.now().toString(), + text: textToSpeech, + voice: selectedVoice, + voiceLabel: VOICE_OPTIONS[selectedVoice].label, + audioUrl: "data:audio/mp3;base64,//sample", + timestamp: new Date(), + }; + + setGeneratedAudio(audio); + + const newHistory = [audio, ...audioHistory].slice(0, 10); + setAudioHistory(newHistory); + localStorage.setItem('audioHistory', JSON.stringify(newHistory)); + + setIsProcessing(false); + + toast({ + title: "Áudio gerado com sucesso", + description: `Voz: ${VOICE_OPTIONS[selectedVoice].label}`, + }); + }, 2000); + }; + + const handleDownloadAudio = (audio: GeneratedAudio) => { + const a = document.createElement('a'); + a.href = audio.audioUrl; + a.download = `audio_${audio.voiceLabel}_${new Date(audio.timestamp).getTime()}.mp3`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + toast({ + title: "Download iniciado", + description: "O arquivo de áudio está sendo baixado.", + }); + }; + + const handleDeleteAudio = (audioId: string) => { + if (generatedAudio?.id === audioId) { + setGeneratedAudio(null); + } + const newHistory = audioHistory.filter(a => a.id !== audioId); + setAudioHistory(newHistory); + localStorage.setItem('audioHistory', JSON.stringify(newHistory)); + + toast({ + title: "Áudio removido", + description: "O áudio foi removido do histórico.", + }); + }; + + const filteredAudios = audioHistory.filter(item => + item.voiceLabel.toLowerCase().includes(audioSearchQuery.toLowerCase()) || + item.text.toLowerCase().includes(audioSearchQuery.toLowerCase()) + ); + + return ( +
+ + + +
+ + + + Geração + + + Histórico + + + + +
+

+ Converter Texto em Voz +

+ +
+ +