Nova versao codex Parecer, atualizacao completa
This commit is contained in:
+84
-262
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user