import { GlobalFunctions, TransferAreaProperties } from '@/GlobalFunctions'; import { apiService } from './api'; /** * Tamanhos de imagem disponíveis */ export type ImageSize = '1024x1024' | '1024x1792' | '1792x1024'; /** * Interface para a resposta da API de geração de imagens */ export interface ImageGenerationResponse { success: boolean; image_url: string; // URL da imagem gerada image_generation_id: string; message: string; // Descrição original } /** * Interface para uma imagem armazenada no banco de dados */ export interface ImageRecord { id: string; user_email: string; estabelecimento_id: number; description: string; model: string; image_url: string; size: ImageSize; cost_usd: string; total_tokens: number; input_tokens: number; output_tokens: number; created_at: string; } /** * Interface para resposta paginada de imagens */ export interface GetImagesResponse { images: ImageRecord[]; total: number; page: number; per_page: number; total_pages: number; } /** * Interface para os dados necessários para geração de imagem */ export interface ImageGenerationRequest { description: string; size: ImageSize; userEmail?: string; estabelecimentoId?: number; } /** * Informações sobre cada tamanho de imagem disponível */ export const IMAGE_SIZE_OPTIONS = { '1024x1024': { label: 'Quadrado', dimensions: '1024x1024', aspectRatio: '1:1', description: 'Ideal para avatares, ícones e posts em redes sociais', }, '1024x1792': { label: 'Retrato', dimensions: '1024x1792', aspectRatio: '9:16', description: 'Perfeito para stories, wallpapers verticais e reels', }, '1792x1024': { label: 'Paisagem', dimensions: '1792x1024', aspectRatio: '16:9', description: 'Ótimo para banners, capas e thumbnails de vídeos', }, } as const; /** * Serviço de geração de imagens com IA */ class ImageGenerationService { private readonly IMAGE_GENERATION_ENDPOINT = '/webhook/codex/image_generator'; private readonly GET_IMAGES_ENDPOINT = '/webhook/fd073ec0-81ab-4681-8acd-b0da71f6ad30/codex/get_images'; private readonly DELETE_IMAGE_ENDPOINT = '/webhook/fd073ec0-81ab-4681-8acd-b0da71f6ad30/codex/delete_images'; /** * Gera uma imagem a partir de uma descrição em texto * * @param request - Dados da requisição (descrição, tamanho, email, estabelecimento) * @returns Promise com a resposta da API */ async generateImage(request: ImageGenerationRequest): Promise { const { description, size, 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 a descrição if (!description || description.trim().length === 0) { throw { success: false, message: 'A descrição não pode estar vazia', }; } // Valida o tamanho if (!this.isValidSize(size)) { throw { success: false, message: `Tamanho inválido. Opções disponíveis: ${Object.keys(IMAGE_SIZE_OPTIONS).join(', ')}`, }; } // Log para debug (remover em produção se necessário) console.log('Gerando imagem:', { descriptionLength: description.length, size, userEmail: email, estabelecimentoId: estabId, }); try { // Faz a requisição usando o serviço de API // Nota: O campo no body é "estabelecito_id" (com typo na API) const response = await apiService.post( this.IMAGE_GENERATION_ENDPOINT, { estabelecimento_id: estabId, // Mantém o typo da API original user_email: email, description: description, size: size, } ); return response.data; } catch (error: any) { // Trata erros específicos console.error('Erro na geração de imagem:', error); throw { success: false, message: error.message || 'Erro ao gerar imagem', status: error.status, }; } } /** * Valida se o tamanho selecionado é suportado * * @param size - Tamanho a ser validado * @returns true se o tamanho é válido */ isValidSize(size: string): size is ImageSize { return Object.keys(IMAGE_SIZE_OPTIONS).includes(size); } /** * Obtém informações sobre um tamanho específico * * @param size - Tamanho da imagem * @returns Informações do tamanho */ getSizeInfo(size: ImageSize) { return IMAGE_SIZE_OPTIONS[size]; } /** * Lista todos os tamanhos disponíveis * * @returns Array com todas as opções de tamanho */ getAllSizes() { return Object.entries(IMAGE_SIZE_OPTIONS).map(([key, info]) => ({ value: key as ImageSize, ...info, })); } /** * Valida a descrição para geração de imagem * * @param description - Descrição a ser validada * @param minLength - Comprimento mínimo (padrão: 3 caracteres) * @param maxLength - Comprimento máximo (padrão: 1000 caracteres) * @returns Objeto com resultado da validação */ validateDescription( description: string, minLength: number = 3, maxLength: number = 1000 ): { valid: boolean; error?: string } { if (!description || description.trim().length === 0) { return { valid: false, error: 'A descrição não pode estar vazia', }; } if (description.trim().length < minLength) { return { valid: false, error: `A descrição deve ter pelo menos ${minLength} caracteres`, }; } if (description.length > maxLength) { return { valid: false, error: `A descrição é muito longa. Máximo: ${maxLength} caracteres`, }; } return { valid: true }; } /** * Faz download de uma imagem gerada * * @param imageUrl - URL da imagem * @param filename - Nome do arquivo (opcional) */ async downloadImage(imageUrl: string, filename?: string): Promise { try { const response = await fetch(imageUrl); const blob = await response.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename || `imagem_${Date.now()}.png`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } catch (error) { console.error('Erro ao baixar imagem:', error); throw new Error('Não foi possível baixar a imagem'); } } /** * Lista imagens 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 a resposta paginada */ async getImages( 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 imagens:', { userEmail: email, page, perPage, }); try { // Faz a requisição GET com parâmetros na URL e query const response = await apiService.get( `${this.GET_IMAGES_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 imagens // 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 'images' 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).images)) { return (response.data as any).images; } 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 imagens da resposta'); return []; } catch (error: any) { console.error('Erro ao buscar imagens:', error); throw { success: false, message: error.message || 'Erro ao buscar imagens', status: error.status, }; } } /** * Deleta uma imagem do banco de dados * * @param imageId - ID da imagem a ser deletada * @param userEmail - Email do usuário (opcional) * @returns Promise com sucesso ou erro */ async deleteImage(imageId: 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 (!imageId) { throw { success: false, message: 'ID da imagem não fornecido', }; } console.log('Deletando imagem:', { imageId, 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_IMAGE_ENDPOINT}/${email}/${imageId}` ); console.log('Resposta completa do DELETE:', response); console.log('response.data:', response.data); // A API pode retornar um array com um objeto: [{"success":true}] // ou diretamente 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 || 'Imagem deletada com sucesso', }; } catch (error: any) { console.error('Erro ao deletar imagem:', error); throw { success: false, message: error.message || 'Erro ao deletar imagem', status: error.status, }; } } } // Exporta instância única (Singleton) export const imageGenerationService = new ImageGenerationService();