395 lines
11 KiB
TypeScript
395 lines
11 KiB
TypeScript
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<ImageGenerationResponse> {
|
|
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<ImageGenerationResponse>(
|
|
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<void> {
|
|
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<ImageRecord[]> {
|
|
// 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<ImageRecord[]>(
|
|
`${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();
|