import { GlobalFunctions, TransferAreaProperties } from '@/GlobalFunctions'; import { apiService } from './api'; /** * Tipos de vozes disponíveis para geração de áudio */ export type VoiceType = 'alloy' | 'echo' | 'fable' | 'nova' | 'onyx' | 'shimmer'; /** * Interface para a resposta da API de geração de áudio */ export interface AudioGenerationResponse { success: boolean; audio_url: string; // URL do áudio gerado audio_generation_id: string; message: string; // Texto que foi convertido em áudio } /** * Interface para um áudio armazenado no banco de dados */ export interface AudioRecord { id: string; user_email: string; estabelecimento_id: number; input_text: string; model: string; voice: VoiceType; audio_url: string; duration_seconds: number | null; file_size: number; cost_usd: string; created_at: string; } /** * Interface para os dados necessários para geração de áudio */ export interface AudioGenerationRequest { message: string; voice: VoiceType; userEmail?: string; estabelecimentoId?: number; } /** * Informações sobre cada tipo de voz disponível */ export 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." } } as const; /** * Serviço de geração de áudio (Text-to-Speech) */ class AudioGenerationService { private readonly AUDIO_GENERATION_ENDPOINT = '/webhook/codex/gerar_audio'; private readonly GET_AUDIOS_ENDPOINT = '/webhook/fd073ec0-81ab-4681-8acd-b0da71f6ad30/codex/get_gerar_audios'; private readonly DELETE_AUDIO_ENDPOINT = '/webhook/fd073ec0-81ab-4681-8acd-b0da71f6ad30/codex/delete_gerar_audio'; /** * Gera um arquivo de áudio a partir de texto * * @param request - Dados da requisição (texto, voz, email, estabelecimento) * @returns Promise com a resposta da API */ async generateAudio(request: AudioGenerationRequest): Promise { const { message, voice, userEmail, estabelecimentoId } = request; // Usa valores do .env se não forem fornecidos const email = GlobalFunctions.getTransferProperty(TransferAreaProperties.UsuarioEmail);//userEmail || import.meta.env.VITE_USER_EMAIL || ''; const estabId = GlobalFunctions.getTransferProperty(TransferAreaProperties.EstabelecimentoCodigo);//estabelecimentoId || parseInt(import.meta.env.VITE_ESTABELECIMENTO_ID) || 1; // Valida o texto if (!message || message.trim().length === 0) { throw { success: false, message: 'O texto não pode estar vazio', }; } // Valida a voz if (!this.isValidVoice(voice)) { throw { success: false, message: `Voz inválida. Opções disponíveis: ${Object.keys(VOICE_OPTIONS).join(', ')}`, }; } // Log para debug (remover em produção se necessário) console.log('Gerando áudio:', { messageLength: message.length, voice, userEmail: email, estabelecimentoId: estabId, }); try { // Faz a requisição usando o serviço de API const response = await apiService.post( this.AUDIO_GENERATION_ENDPOINT, { estabelecimento_id: estabId, user_email: email, message: message, voice: voice, } ); return response.data; } catch (error: any) { // Trata erros específicos console.error('Erro na geração de áudio:', error); throw { success: false, message: error.message || 'Erro ao gerar áudio', status: error.status, }; } } /** * Valida se a voz selecionada é suportada * * @param voice - Voz a ser validada * @returns true se a voz é válida */ isValidVoice(voice: string): voice is VoiceType { return Object.keys(VOICE_OPTIONS).includes(voice); } /** * Obtém informações sobre uma voz específica * * @param voice - Tipo de voz * @returns Informações da voz */ getVoiceInfo(voice: VoiceType) { return VOICE_OPTIONS[voice]; } /** * Lista todas as vozes disponíveis * * @returns Array com todas as opções de voz */ getAllVoices() { return Object.entries(VOICE_OPTIONS).map(([key, info]) => ({ value: key as VoiceType, ...info, })); } /** * Valida o texto para geração de áudio * * @param text - Texto a ser validado * @param maxLength - Comprimento máximo (padrão: 4096 caracteres) * @returns Objeto com resultado da validação */ validateText(text: string, maxLength: number = 4096): { valid: boolean; error?: string } { if (!text || text.trim().length === 0) { return { valid: false, error: 'O texto não pode estar vazio', }; } if (text.length > maxLength) { return { valid: false, error: `O texto é muito longo. Máximo: ${maxLength} caracteres`, }; } return { valid: true }; } /** * Lista áudios do banco de dados com paginação * * @param userEmail - Email do usuário * @param page - Número da página (padrão: 1) * @param perPage - Quantidade de itens por página (padrão: 10) * @returns Promise com o array de áudios */ async getAudios( userEmail?: string, page: number = 1, perPage: number = 10 ): Promise { // Usa valores do GlobalFunctions se não forem fornecidos const email = userEmail || GlobalFunctions.getTransferProperty(TransferAreaProperties.UsuarioEmail); if (!email) { throw { success: false, message: 'Email do usuário não fornecido', }; } console.log('Buscando áudios:', { userEmail: email, page, perPage, }); try { // Faz a requisição GET com parâmetros na URL e query const response = await apiService.get( `${this.GET_AUDIOS_ENDPOINT}/${email}`, { params: { page: page.toString(), per_page: perPage.toString(), }, } ); console.log('Resposta completa da API:', response); console.log('response.data:', response.data); console.log('É array?:', Array.isArray(response.data)); // A API retorna diretamente o array de áudios // Garante que sempre retorna um array if (Array.isArray(response.data)) { return response.data; } else if (response.data && typeof response.data === 'object') { // Se a resposta for um objeto com uma propriedade 'audios' ou similar console.warn('API retornou objeto em vez de array:', response.data); // Tenta encontrar o array dentro do objeto if (Array.isArray((response.data as any).audios)) { return (response.data as any).audios; } else if (Array.isArray((response.data as any).data)) { return (response.data as any).data; } } // Se não conseguir extrair array, retorna vazio console.warn('Não foi possível extrair array de áudios da resposta'); return []; } catch (error: any) { console.error('Erro ao buscar áudios:', error); throw { success: false, message: error.message || 'Erro ao buscar áudios', status: error.status, }; } } /** * Deleta um áudio do banco de dados * * @param audioId - ID do áudio a ser deletado * @param userEmail - Email do usuário (opcional) * @returns Promise com sucesso ou erro */ async deleteAudio(audioId: string, userEmail?: string): Promise<{ success: boolean; message?: string }> { // Usa valores do GlobalFunctions se não forem fornecidos const email = userEmail || GlobalFunctions.getTransferProperty(TransferAreaProperties.UsuarioEmail); if (!email) { throw { success: false, message: 'Email do usuário não fornecido', }; } if (!audioId) { throw { success: false, message: 'ID do áudio não fornecido', }; } console.log('Deletando áudio:', { audioId, userEmail: email, }); try { // Faz a requisição DELETE com parâmetros na URL const response = await apiService.delete<{ success: boolean; message?: string } | Array<{ success: boolean; message?: string }>>( `${this.DELETE_AUDIO_ENDPOINT}/${email}/${audioId}` ); console.log('Resposta completa do DELETE:', response); console.log('response.data:', response.data); // A API retorna um array com um objeto: [{"success":true}] let result: { success: boolean; message?: string }; if (Array.isArray(response.data)) { // Se for array, pega o primeiro elemento result = response.data[0]; console.log('API retornou array, usando primeiro elemento:', result); } else { // Se for objeto direto result = response.data; console.log('API retornou objeto direto:', result); } // Garante que tem a estrutura mínima return { success: result.success ?? true, message: result.message || 'Áudio deletado com sucesso', }; } catch (error: any) { console.error('Erro ao deletar áudio:', error); throw { success: false, message: error.message || 'Erro ao deletar áudio', status: error.status, }; } } } // Exporta instância única (Singleton) export const audioGenerationService = new AudioGenerationService();