Spaces:
Running
Running
| const API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:8000' | |
| const AUTH_TOKEN_STORAGE_KEY = 'mesa_auth_token' | |
| let AUTH_TOKEN = '' | |
| if (typeof window !== 'undefined') { | |
| AUTH_TOKEN = window.localStorage.getItem(AUTH_TOKEN_STORAGE_KEY) || '' | |
| } | |
| export function setAuthToken(token) { | |
| const value = String(token || '').trim() | |
| AUTH_TOKEN = value | |
| if (typeof window === 'undefined') return | |
| if (value) { | |
| window.localStorage.setItem(AUTH_TOKEN_STORAGE_KEY, value) | |
| } else { | |
| window.localStorage.removeItem(AUTH_TOKEN_STORAGE_KEY) | |
| } | |
| } | |
| export function getAuthToken() { | |
| return AUTH_TOKEN | |
| } | |
| function authHeaders(extraHeaders = {}) { | |
| const headers = { ...extraHeaders } | |
| if (AUTH_TOKEN) headers['X-Auth-Token'] = AUTH_TOKEN | |
| return headers | |
| } | |
| function emitAuthRequired(detail = 'Login obrigatorio') { | |
| if (typeof window === 'undefined') return | |
| const event = new CustomEvent('mesa:auth-required', { detail: { message: detail } }) | |
| window.dispatchEvent(event) | |
| } | |
| function describeFetchFailure(cause) { | |
| const name = String(cause?.name || '').trim() | |
| const message = String(cause?.message || '').trim() | |
| if (name === 'AbortError') { | |
| return 'a requisicao foi interrompida antes de concluir' | |
| } | |
| if (typeof navigator !== 'undefined' && navigator.onLine === false) { | |
| return 'sem conexao com a internet' | |
| } | |
| if (/failed to fetch|networkerror|load failed/i.test(message)) { | |
| return 'conexao interrompida durante a transferencia ou bloqueio de rede/CORS/proxy' | |
| } | |
| return message || 'falha de rede nao identificada' | |
| } | |
| function buildFetchError(path, stage, cause, status = null) { | |
| const endpoint = String(path || '').trim() || '(endpoint desconhecido)' | |
| const reason = describeFetchFailure(cause) | |
| const statusNumber = Number(status) | |
| const statusLabel = Number.isFinite(statusNumber) ? ` (HTTP ${statusNumber})` : '' | |
| const action = stage === 'body' | |
| ? `Falha ao receber dados de ${endpoint}${statusLabel}` | |
| : `Falha ao conectar com ${endpoint}` | |
| const error = new Error(`${action}. Motivo: ${reason}.`) | |
| error.path = endpoint | |
| error.stage = stage | |
| error.reason = reason | |
| if (Number.isFinite(statusNumber)) { | |
| error.status = statusNumber | |
| } | |
| error.cause = cause | |
| return error | |
| } | |
| async function fetchWithDiagnostics(path, options = {}) { | |
| try { | |
| return await fetch(`${API_BASE}${path}`, options) | |
| } catch (cause) { | |
| throw buildFetchError(path, 'request', cause) | |
| } | |
| } | |
| async function responseBlobWithDiagnostics(response, path = '') { | |
| try { | |
| return await response.blob() | |
| } catch (cause) { | |
| throw buildFetchError(path, 'body', cause, response?.status) | |
| } | |
| } | |
| async function handleResponse(response, path = '') { | |
| if (!response.ok) { | |
| let detail = 'Erro inesperado' | |
| try { | |
| const data = await response.json() | |
| detail = data.detail || detail | |
| } catch { | |
| detail = response.statusText || detail | |
| } | |
| const detailMessage = typeof detail === 'string' | |
| ? detail | |
| : String(detail?.message || response.statusText || 'Erro inesperado') | |
| if (response.status === 401 && path !== '/api/auth/login') { | |
| setAuthToken('') | |
| emitAuthRequired(detailMessage) | |
| } | |
| const error = new Error(detailMessage) | |
| error.status = response.status | |
| error.path = path | |
| error.detail = detail | |
| throw error | |
| } | |
| return response | |
| } | |
| async function postJson(path, body) { | |
| const response = await fetchWithDiagnostics(path, { | |
| method: 'POST', | |
| headers: authHeaders({ 'Content-Type': 'application/json' }), | |
| body: JSON.stringify(body), | |
| }) | |
| await handleResponse(response, path) | |
| return response.json() | |
| } | |
| async function postForm(path, formData) { | |
| const response = await fetchWithDiagnostics(path, { | |
| method: 'POST', | |
| headers: authHeaders(), | |
| body: formData, | |
| }) | |
| await handleResponse(response, path) | |
| return response.json() | |
| } | |
| async function getJson(path) { | |
| const response = await fetchWithDiagnostics(path, { headers: authHeaders() }) | |
| await handleResponse(response, path) | |
| return response.json() | |
| } | |
| async function getBlob(path) { | |
| const response = await fetchWithDiagnostics(path, { headers: authHeaders() }) | |
| await handleResponse(response, path) | |
| return responseBlobWithDiagnostics(response, path) | |
| } | |
| export function downloadBlob(blob, fileName) { | |
| const url = URL.createObjectURL(blob) | |
| const a = document.createElement('a') | |
| a.href = url | |
| a.download = fileName | |
| document.body.appendChild(a) | |
| a.click() | |
| a.remove() | |
| URL.revokeObjectURL(url) | |
| } | |
| export const api = { | |
| authLogin: (usuario, matricula) => postJson('/api/auth/login', { usuario, matricula }), | |
| authMe: () => getJson('/api/auth/me'), | |
| authLogout: () => postJson('/api/auth/logout', {}), | |
| createSession: () => postJson('/api/sessions', {}), | |
| pesquisaAdminConfig: () => getJson('/api/pesquisa/admin-config'), | |
| pesquisaAdminConfigSalvar: (campos = {}) => postJson('/api/pesquisa/admin-config', { campos }), | |
| pesquisarModelos(filtros = {}) { | |
| const params = new URLSearchParams() | |
| Object.entries(filtros).forEach(([key, value]) => { | |
| if (value === null || value === undefined) return | |
| const text = String(value).trim() | |
| if (!text) return | |
| params.append(key, text) | |
| }) | |
| const query = params.toString() | |
| return getJson(query ? `/api/pesquisa/modelos?${query}` : '/api/pesquisa/modelos') | |
| }, | |
| pesquisarMapaModelos(modelosIds = []) { | |
| return postJson('/api/pesquisa/mapa-modelos', { modelos_ids: modelosIds }) | |
| }, | |
| uploadElaboracaoFile(sessionId, file) { | |
| const form = new FormData() | |
| form.append('session_id', sessionId) | |
| form.append('file', file) | |
| return postForm('/api/elaboracao/upload', form) | |
| }, | |
| elaboracaoRepositorioModelos: () => getJson('/api/elaboracao/repositorio-modelos'), | |
| elaboracaoRepositorioCarregar: (sessionId, modeloId) => postJson('/api/elaboracao/repositorio-carregar', { session_id: sessionId, modelo_id: modeloId }), | |
| confirmSheet: (sessionId, sheetName) => postJson('/api/elaboracao/confirm-sheet', { session_id: sessionId, sheet_name: sheetName }), | |
| mapCoords: (sessionId, colLat, colLon) => postJson('/api/elaboracao/map-coords', { session_id: sessionId, col_lat: colLat, col_lon: colLon }), | |
| geocodificar: (sessionId, colCdlog, colNum, auto200) => postJson('/api/elaboracao/geocodificar', { session_id: sessionId, col_cdlog: colCdlog, col_num: colNum, auto_200: auto200 }), | |
| geocodificarCorrecoes: (sessionId, correcoes, auto200) => postJson('/api/elaboracao/geocodificar-correcoes', { session_id: sessionId, correcoes, auto_200: auto200 }), | |
| geocodificarReiniciar: (sessionId) => postJson('/api/elaboracao/geocodificar-reiniciar', { session_id: sessionId }), | |
| geocodificarExcluirCoords: (sessionId) => postJson('/api/elaboracao/geocodificar-excluir-coords', { session_id: sessionId }), | |
| applySelection(payload) { | |
| return postJson('/api/elaboracao/apply-selection', payload) | |
| }, | |
| classifyElaboracaoX: (sessionId, colunasX) => postJson('/api/elaboracao/classify-x', { session_id: sessionId, colunas_x: colunasX }), | |
| searchTransformations: (sessionId, grauCoef, grauF, transformacoesFixas = {}, transformacaoYFixa = 'Livre') => postJson('/api/elaboracao/search-transformations', { | |
| session_id: sessionId, | |
| grau_min_coef: grauCoef, | |
| grau_min_f: grauF, | |
| transformacoes_fixas: transformacoesFixas, | |
| transformacao_y_fixa: transformacaoYFixa, | |
| }), | |
| adoptSuggestion: (sessionId, indice) => postJson('/api/elaboracao/adopt-suggestion', { session_id: sessionId, indice }), | |
| fitModel(payload) { | |
| return postJson('/api/elaboracao/fit-model', payload) | |
| }, | |
| updateModelDispersao: (sessionId, payload) => postJson('/api/elaboracao/model-dispersao', { | |
| session_id: sessionId, | |
| ...(payload || {}), | |
| }), | |
| getDispersaoInterativo: (sessionId, alvo) => postJson('/api/elaboracao/dispersao-interativo', { | |
| session_id: sessionId, | |
| alvo, | |
| }), | |
| getDiagnosticoInterativo: (sessionId, grafico) => postJson('/api/elaboracao/diagnostico-interativo', { | |
| session_id: sessionId, | |
| grafico, | |
| }), | |
| previewTransformElab: (sessionId, transformacaoY, transformacoesX) => postJson('/api/elaboracao/transform-preview', { | |
| session_id: sessionId, | |
| transformacao_y: transformacaoY, | |
| transformacoes_x: transformacoesX, | |
| }), | |
| applyOutlierFilters: (sessionId, filtros) => postJson('/api/elaboracao/outliers/apply-filters', { session_id: sessionId, filtros }), | |
| applyOutlierFiltersRecursive: (sessionId, filtros) => postJson('/api/elaboracao/outliers/apply-filters-recursive', { session_id: sessionId, filtros }), | |
| restartOutlierIteration: (sessionId, outliersTexto, reincluirTexto, grauCoef, grauF) => postJson('/api/elaboracao/outliers/restart', { | |
| session_id: sessionId, | |
| outliers_texto: outliersTexto, | |
| reincluir_texto: reincluirTexto, | |
| grau_min_coef: grauCoef, | |
| grau_min_f: grauF, | |
| }), | |
| outlierSummary: (sessionId, outliersTexto, reincluirTexto) => postJson('/api/elaboracao/outliers/summary', { | |
| session_id: sessionId, | |
| outliers_texto: outliersTexto, | |
| reincluir_texto: reincluirTexto, | |
| }), | |
| clearOutlierHistory: (sessionId) => postJson('/api/elaboracao/outliers/clear-history', { session_id: sessionId }), | |
| evaluationFieldsElab: (sessionId) => postJson('/api/elaboracao/evaluation/fields', { session_id: sessionId }), | |
| evaluationCalculateElab: (sessionId, valoresX, indiceBase) => postJson('/api/elaboracao/evaluation/calculate', { | |
| session_id: sessionId, | |
| valores_x: valoresX, | |
| indice_base: indiceBase, | |
| }), | |
| evaluationKnnDetailsElab: (sessionId, valoresX) => postJson('/api/elaboracao/evaluation/knn-details', { | |
| session_id: sessionId, | |
| valores_x: valoresX, | |
| }), | |
| evaluationClearElab: (sessionId) => postJson('/api/elaboracao/evaluation/clear', { session_id: sessionId }), | |
| evaluationDeleteElab: (sessionId, indice, indiceBase) => postJson('/api/elaboracao/evaluation/delete', { | |
| session_id: sessionId, | |
| indice, | |
| indice_base: indiceBase, | |
| }), | |
| getAvaliadores: () => getJson('/api/elaboracao/avaliadores'), | |
| evaluationBaseElab: (sessionId, indiceBase) => postJson('/api/elaboracao/evaluation/base', { | |
| session_id: sessionId, | |
| indice_base: indiceBase, | |
| }), | |
| exportEvaluationElab: async (sessionId) => getBlob(`/api/elaboracao/evaluation/export?session_id=${encodeURIComponent(sessionId)}`), | |
| exportEquationElab: async (sessionId, mode = 'excel') => getBlob(`/api/elaboracao/equation/export?session_id=${encodeURIComponent(sessionId)}&mode=${encodeURIComponent(String(mode || 'excel'))}`), | |
| exportModel: async (sessionId, nomeArquivo, elaborador) => { | |
| const path = '/api/elaboracao/export-model' | |
| const response = await fetchWithDiagnostics(path, { | |
| method: 'POST', | |
| headers: authHeaders({ 'Content-Type': 'application/json' }), | |
| body: JSON.stringify({ session_id: sessionId, nome_arquivo: nomeArquivo, elaborador }), | |
| }) | |
| await handleResponse(response, path) | |
| return responseBlobWithDiagnostics(response, path) | |
| }, | |
| exportBase: (sessionId, filtered = true) => getBlob(`/api/elaboracao/export-base?session_id=${encodeURIComponent(sessionId)}&filtered=${String(filtered)}`), | |
| updateElaboracaoMap: (sessionId, variavelMapa, modoMapa = 'pontos') => postJson('/api/elaboracao/map/update', { | |
| session_id: sessionId, | |
| variavel_mapa: variavelMapa, | |
| modo_mapa: modoMapa, | |
| }), | |
| updateElaboracaoResiduosMap: (sessionId, variavelMapa, modoMapa = 'pontos', escalaExtremoAbs = null) => postJson('/api/elaboracao/residuos/map/update', { | |
| session_id: sessionId, | |
| variavel_mapa: variavelMapa, | |
| modo_mapa: modoMapa, | |
| escala_extremo_abs: escalaExtremoAbs, | |
| }), | |
| previewMarketDateColumn: (sessionId, colunaData) => postJson('/api/elaboracao/market-date/preview', { session_id: sessionId, coluna_data: colunaData }), | |
| applyMarketDateColumn: (sessionId, colunaData) => postJson('/api/elaboracao/market-date/apply', { session_id: sessionId, coluna_data: colunaData }), | |
| getContext: (sessionId) => getJson(`/api/elaboracao/context?session_id=${encodeURIComponent(sessionId)}`), | |
| uploadVisualizacaoFile(sessionId, file) { | |
| const form = new FormData() | |
| form.append('session_id', sessionId) | |
| form.append('file', file) | |
| return postForm('/api/visualizacao/upload', form) | |
| }, | |
| visualizacaoRepositorioModelos: () => getJson('/api/visualizacao/repositorio-modelos'), | |
| visualizacaoRepositorioCarregar: (sessionId, modeloId) => postJson('/api/visualizacao/repositorio-carregar', { session_id: sessionId, modelo_id: modeloId }), | |
| exibirVisualizacao: (sessionId) => postJson('/api/visualizacao/exibir', { session_id: sessionId }), | |
| evaluationContextViz: (sessionId) => postJson('/api/visualizacao/evaluation/context', { session_id: sessionId }), | |
| updateVisualizacaoMap: (sessionId, variavelMapa) => postJson('/api/visualizacao/map/update', { session_id: sessionId, variavel_mapa: variavelMapa }), | |
| evaluationFieldsViz: (sessionId) => postJson('/api/visualizacao/evaluation/fields', { session_id: sessionId }), | |
| evaluationCalculateViz: (sessionId, valoresX, indiceBase) => postJson('/api/visualizacao/evaluation/calculate', { | |
| session_id: sessionId, | |
| valores_x: valoresX, | |
| indice_base: indiceBase, | |
| }), | |
| evaluationKnnDetailsViz: (sessionId, valoresX) => postJson('/api/visualizacao/evaluation/knn-details', { | |
| session_id: sessionId, | |
| valores_x: valoresX, | |
| }), | |
| evaluationClearViz: (sessionId) => postJson('/api/visualizacao/evaluation/clear', { session_id: sessionId }), | |
| evaluationDeleteViz: (sessionId, indice, indiceBase) => postJson('/api/visualizacao/evaluation/delete', { | |
| session_id: sessionId, | |
| indice, | |
| indice_base: indiceBase, | |
| }), | |
| evaluationBaseViz: (sessionId, indiceBase) => postJson('/api/visualizacao/evaluation/base', { | |
| session_id: sessionId, | |
| indice_base: indiceBase, | |
| }), | |
| exportEvaluationViz: (sessionId) => getBlob(`/api/visualizacao/evaluation/export?session_id=${encodeURIComponent(sessionId)}`), | |
| exportEquationViz: (sessionId, mode = 'excel') => getBlob(`/api/visualizacao/equation/export?session_id=${encodeURIComponent(sessionId)}&mode=${encodeURIComponent(String(mode || 'excel'))}`), | |
| clearVisualizacao: (sessionId) => postJson('/api/visualizacao/clear', { session_id: sessionId }), | |
| repositorioListar: () => getJson('/api/repositorio/modelos'), | |
| repositorioUpload(files = [], { confirmarSubstituicao = false } = {}) { | |
| const form = new FormData() | |
| files.forEach((file) => { | |
| form.append('files', file) | |
| }) | |
| form.append('confirmar_substituicao', confirmarSubstituicao ? 'true' : 'false') | |
| return postForm('/api/repositorio/upload', form) | |
| }, | |
| repositorioDelete: (modelosIds = []) => postJson('/api/repositorio/delete', { modelos_ids: modelosIds }), | |
| logsStatus: () => getJson('/api/logs/status'), | |
| logsEvents({ scope = '', usuario = '', limit = 200 } = {}) { | |
| const params = new URLSearchParams() | |
| const scopeText = String(scope || '').trim() | |
| const usuarioText = String(usuario || '').trim() | |
| const limitNumber = Number(limit) | |
| if (scopeText) params.set('scope', scopeText) | |
| if (usuarioText) params.set('usuario', usuarioText) | |
| if (Number.isFinite(limitNumber) && limitNumber > 0) params.set('limit', String(Math.floor(limitNumber))) | |
| const query = params.toString() | |
| return getJson(query ? `/api/logs/events?${query}` : '/api/logs/events') | |
| }, | |
| } | |