Nova versao codex Parecer, atualizacao completa

This commit is contained in:
Vitex Tecnologia
2026-03-18 01:07:27 -03:00
parent 39032ea621
commit 6bf7e7d42e
49 changed files with 8003 additions and 2486 deletions
+84 -262
View File
@@ -1,9 +1,6 @@
import { GlobalFunctions, TransferAreaProperties } from '@/GlobalFunctions';
import { apiService } from './api';
/**
* Interface para resposta do POST de parecer técnico
*/
export interface CreateOpinionResponse {
success: boolean;
id?: string;
@@ -11,9 +8,6 @@ export interface CreateOpinionResponse {
file_url_melhoria?: string;
}
/**
* Interface para requisição de criação de parecer
*/
export interface CreateOpinionRequest {
titulo: string;
categoria: string;
@@ -22,14 +16,8 @@ export interface CreateOpinionRequest {
estabelecimentoId?: number;
}
/**
* Status do parecer
*/
export type OpinionStatus = 'processando' | 'concluido' | 'erro';
/**
* Interface para um parecer retornado pela API
*/
export interface OpinionRecord {
id: string;
estabelecimento_id: number;
@@ -40,13 +28,10 @@ export interface OpinionRecord {
file_url: string;
created_at: string;
file_url_melhoria: string;
status?: OpinionStatus; // Status do processamento do parecer (pode vir da API ou ser local)
isLocalPending?: boolean; // Flag para indicar se é um registro local temporário
status?: OpinionStatus;
isLocalPending?: boolean;
}
/**
* Interface para parâmetros de paginação e busca
*/
export interface GetOpinionsParams {
page?: number;
per_page?: number;
@@ -55,9 +40,6 @@ export interface GetOpinionsParams {
estabelecimentoId?: number;
}
/**
* Interface para resposta do GET de pareceres
*/
export interface GetOpinionsResponse {
data: OpinionRecord[];
total: number;
@@ -65,74 +47,53 @@ export interface GetOpinionsResponse {
per_page: number;
}
/**
* Serviço para gerenciamento de pareceres jurídicos
*/
type ApiErrorShape = { message?: string; status?: number };
class AgentService {
private readonly CREATE_OPINION_ENDPOINT = '/webhook/codex/gepam/parecer-tecnico';
private readonly GET_OPINIONS_ENDPOINT = '/webhook/fd073ec0-81ab-4681-8acd-b0da71f6ad30/codex/get_parecer';
private readonly CREATE_ENDPOINT = '/webhook/codex/gepam/parecer-tecnico';
private readonly OPINIONS_BASE = '/webhook/fd073ec0-81ab-4681-8acd-b0da71f6ad30/codex';
/**
* Cria um novo parecer técnico
*
* @param request - Dados do parecer
* @returns Promise com a resposta da API
*/
async createOpinion(request: CreateOpinionRequest): Promise<CreateOpinionResponse> {
const {
titulo,
categoria,
instrucoes,
userEmail,
estabelecimentoId
} = request;
// Usa valores do GlobalFunctions se não forem fornecidos
private resolveUserContext(userEmail?: string, estabelecimentoId?: number) {
const email = userEmail || GlobalFunctions.getTransferProperty(TransferAreaProperties.UsuarioEmail);
const estabId = estabelecimentoId || GlobalFunctions.getTransferProperty(TransferAreaProperties.EstabelecimentoCodigo);
return { email, estabId };
}
// Validações
if (!titulo || titulo.trim().length === 0) {
throw {
success: false,
message: 'Título do parecer é obrigatório',
};
}
if (!instrucoes || instrucoes.trim().length === 0) {
throw {
success: false,
message: 'Instruções são obrigatórias',
};
}
private assertUserContext(email: unknown, estabId: unknown) {
if (!email) {
throw {
success: false,
message: 'Email do usuário não fornecido',
};
throw { success: false, message: 'Email do usuário não fornecido' };
}
if (!estabId) {
throw {
success: false,
message: 'ID do estabelecimento não fornecido',
};
throw { success: false, message: 'ID do estabelecimento não fornecido' };
}
}
private toApiError(error: unknown): never {
const e = error as ApiErrorShape;
throw {
success: false,
message: e?.message || 'Erro desconhecido',
status: e?.status,
};
}
async createOpinion(request: CreateOpinionRequest): Promise<CreateOpinionResponse> {
const { titulo, categoria, instrucoes, userEmail, estabelecimentoId } = request;
if (!titulo?.trim()) {
throw { success: false, message: 'Título do parecer é obrigatório' };
}
// Log para debug
console.log('Criando parecer:', {
titulo,
categoria,
instrucoesLength: instrucoes.length,
userEmail: email,
estabelecimentoId: estabId,
});
if (!instrucoes?.trim()) {
throw { success: false, message: 'Instruções são obrigatórias' };
}
const { email, estabId } = this.resolveUserContext(userEmail, estabelecimentoId);
this.assertUserContext(email, estabId);
try {
// Faz a requisição usando o serviço de API com timeout de 5 minutos
const response = await apiService.post<CreateOpinionResponse>(
this.CREATE_OPINION_ENDPOINT,
this.CREATE_ENDPOINT,
{
user_email: email,
estabelecimento_id: estabId,
@@ -140,245 +101,106 @@ class AgentService {
categoria: categoria.trim(),
instrucoes: instrucoes.trim(),
},
{
timeout: 600000, // 5 minutos para geração de parecer (processo demorado)
}
{ timeout: 600_000 }
);
console.log('Resposta da API (criar parecer):', response.data);
return response.data;
} catch (error: any) {
// Trata erros específicos
} catch (error: unknown) {
console.error('Erro ao criar parecer:', error);
throw {
success: false,
message: error.message || 'Erro ao criar parecer',
status: error.status,
};
this.toApiError(error);
}
}
/**
* Busca todos os pareceres do usuário com paginação e busca
*
* @param params - Parâmetros de paginação e busca
* @returns Promise com array de pareceres
*/
async getOpinions(params?: GetOpinionsParams): Promise<OpinionRecord[]> {
const {
page = 1,
per_page = 10,
search = '',
userEmail,
estabelecimentoId
} = params || {};
const { page = 1, per_page = 10, search = '', userEmail, estabelecimentoId } = params || {};
// Usa valores do GlobalFunctions se não forem fornecidos
const email = userEmail || GlobalFunctions.getTransferProperty(TransferAreaProperties.UsuarioEmail);
const estabId = estabelecimentoId || GlobalFunctions.getTransferProperty(TransferAreaProperties.EstabelecimentoCodigo);
if (!email) {
throw {
success: false,
message: 'Email do usuário não fornecido',
};
}
if (!estabId) {
throw {
success: false,
message: 'ID do estabelecimento não fornecido',
};
}
console.log('Buscando pareceres:', {
userEmail: email,
estabelecimentoId: estabId,
page,
per_page,
search,
});
const { email, estabId } = this.resolveUserContext(userEmail, estabelecimentoId);
this.assertUserContext(email, estabId);
try {
// Constrói a URL com parâmetros de query
const url = `${this.GET_OPINIONS_ENDPOINT}/${email}/${estabId}`;
const url = `${this.OPINIONS_BASE}/get_parecer/${email}/${estabId}`;
const response = await apiService.get<OpinionRecord[]>(url, {
params: {
page,
per_page,
search,
},
const response = await apiService.get<OpinionRecord[] | GetOpinionsResponse>(url, {
params: { page, per_page, search },
});
console.log('Resposta da API (buscar pareceres):', response.data);
const raw = response.data;
// A API retorna diretamente o array de pareceres
// 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, tenta encontrar o array dentro dele
console.warn('API retornou objeto em vez de array:', response.data);
if (Array.isArray(raw)) return raw;
if (Array.isArray((response.data as any).data)) {
return (response.data as any).data;
} else if (Array.isArray((response.data as any).opinions)) {
return (response.data as any).opinions;
if (raw && typeof raw === 'object') {
if (Array.isArray((raw as GetOpinionsResponse).data)) return (raw as GetOpinionsResponse).data;
if (Array.isArray((raw as { opinions?: OpinionRecord[] }).opinions)) {
return (raw as unknown as { opinions: OpinionRecord[] }).opinions;
}
}
// Se não conseguir extrair array, retorna vazio
console.warn('Não foi possível extrair array de pareceres da resposta');
return [];
} catch (error: any) {
} catch (error: unknown) {
console.error('Erro ao buscar pareceres:', error);
throw {
success: false,
message: error.message || 'Erro ao buscar pareceres',
status: error.status,
};
this.toApiError(error);
}
}
/**
* Faz download de um arquivo de parecer
*
* @param fileUrl - URL do arquivo a ser baixado
* @param fileName - Nome do arquivo para download
*/
async downloadOpinion(fileUrl: string, fileName: string): Promise<void> {
if (!fileUrl) {
throw {
success: false,
message: 'URL do arquivo não fornecida',
};
throw { success: false, message: 'URL do arquivo não fornecida' };
}
const triggerDownload = (href: string) => {
const link = document.createElement('a');
link.href = href;
link.download = fileName;
link.target = '_blank';
link.rel = 'noopener noreferrer';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
try {
console.log('Iniciando download:', { fileUrl, fileName });
const response = await fetch(fileUrl, { method: 'GET', mode: 'cors', cache: 'no-cache' });
// Tenta primeiro fazer o download via fetch (funciona se CORS estiver configurado)
try {
const response = await fetch(fileUrl, {
method: 'GET',
mode: 'cors',
cache: 'no-cache',
});
if (!response.ok) {
throw new Error(`Erro HTTP: ${response.status}`);
}
// Converte a resposta em blob
const blob = await response.blob();
// Cria uma URL temporária para o blob
const blobUrl = URL.createObjectURL(blob);
// Cria um elemento <a> temporário para forçar o download
const link = document.createElement('a');
link.href = blobUrl;
link.download = fileName;
document.body.appendChild(link);
link.click();
// Remove o elemento e libera a URL temporária
document.body.removeChild(link);
URL.revokeObjectURL(blobUrl);
console.log('Download via fetch concluído:', { fileUrl, fileName });
return;
} catch (fetchError: any) {
console.warn('Erro no download via fetch, tentando método alternativo:', fetchError.message);
// Se falhar (erro de CORS), usa o método alternativo de abrir em nova aba
// Isso permite que o navegador force o download mesmo com restrições de CORS
const link = document.createElement('a');
link.href = fileUrl;
link.download = fileName;
link.target = '_blank';
link.rel = 'noopener noreferrer';
// Para S3, podemos tentar adicionar parâmetros que forçam o download
const url = new URL(fileUrl);
url.searchParams.set('response-content-disposition', `attachment; filename="${encodeURIComponent(fileName)}"`);
link.href = url.toString();
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
console.log('Download via link direto iniciado:', { fileUrl, fileName });
if (!response.ok) {
throw new Error(`Erro HTTP: ${response.status}`);
}
} catch (error: any) {
console.error('Erro ao fazer download:', error);
throw {
success: false,
message: error.message || 'Erro ao fazer download do arquivo. Verifique se a URL está acessível.',
};
const blob = await response.blob();
const blobUrl = URL.createObjectURL(blob);
triggerDownload(blobUrl);
URL.revokeObjectURL(blobUrl);
} catch {
const url = new URL(fileUrl);
url.searchParams.set('response-content-disposition', `attachment; filename="${encodeURIComponent(fileName)}"`);
triggerDownload(url.toString());
}
}
/**
* Exclui um parecer técnico
*
* @param opinionId - ID do parecer a ser excluído
* @param userEmail - Email do usuário (opcional, usa GlobalFunctions se não fornecido)
* @returns Promise com a resposta da API
*/
async deleteOpinion(opinionId: string, userEmail?: string): Promise<{ success: boolean }> {
// Usa valores do GlobalFunctions se não forem fornecidos
if (!opinionId) {
throw { success: false, message: 'ID do parecer não fornecido' };
}
const email = userEmail || GlobalFunctions.getTransferProperty(TransferAreaProperties.UsuarioEmail);
if (!opinionId) {
throw {
success: false,
message: 'ID do parecer não fornecido',
};
}
if (!email) {
throw {
success: false,
message: 'Email do usuário não fornecido',
};
throw { success: false, message: 'Email do usuário não fornecido' };
}
console.log('Excluindo parecer:', {
opinionId,
userEmail: email,
});
try {
// Constrói a URL com o user_email e id
const url = `/webhook/fd073ec0-81ab-4681-8acd-b0da71f6ad30/codex/delete_parecer/${email}/${opinionId}`;
const url = `${this.OPINIONS_BASE}/delete_parecer/${email}/${opinionId}`;
const response = await apiService.delete<{ success: boolean }[]>(url);
console.log('Resposta da API (excluir parecer):', response.data);
// A API retorna um array com { success: true }
if (Array.isArray(response.data) && response.data.length > 0) {
return response.data[0];
}
return { success: true };
} catch (error: any) {
} catch (error: unknown) {
console.error('Erro ao excluir parecer:', error);
throw {
success: false,
message: error.message || 'Erro ao excluir parecer',
status: error.status,
};
this.toApiError(error);
}
}
}
// Exporta instância única (Singleton)
export const agentService = new AgentService();