Spaces:
Running
Running
Guilherme Silberfarb Costa commited on
Commit ·
2a8204e
1
Parent(s): 8cc746d
introducao do repositorio
Browse files- README.md +20 -0
- backend/app/api/elaboracao.py +15 -0
- backend/app/api/visualizacao.py +15 -0
- backend/app/core/pesquisa/modelos_dai/README.md +3 -0
- backend/app/services/elaboracao_service.py +12 -0
- backend/app/services/model_repository.py +271 -0
- backend/app/services/pesquisa_service.py +23 -6
- backend/app/services/visualizacao_service.py +12 -0
- backend/requirements.txt +1 -0
- frontend/src/api.js +4 -0
- frontend/src/components/ElaboracaoTab.jsx +175 -52
- frontend/src/components/VisualizacaoTab.jsx +112 -1
- frontend/src/styles.css +8 -0
README.md
CHANGED
|
@@ -53,3 +53,23 @@ Para apontar para outro backend:
|
|
| 53 |
```bash
|
| 54 |
VITE_API_BASE=http://localhost:8000 npm run dev
|
| 55 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
```bash
|
| 54 |
VITE_API_BASE=http://localhost:8000 npm run dev
|
| 55 |
```
|
| 56 |
+
|
| 57 |
+
## Repositório de modelos `.dai`
|
| 58 |
+
|
| 59 |
+
Os modelos usados em **Pesquisa**, **Elaboração** (carregar modelo existente) e
|
| 60 |
+
**Visualização** (carregar modelo existente) podem vir de duas fontes:
|
| 61 |
+
|
| 62 |
+
- `local` (pasta do projeto)
|
| 63 |
+
- `hf_dataset` (dataset no Hugging Face)
|
| 64 |
+
|
| 65 |
+
Variáveis de ambiente do backend:
|
| 66 |
+
|
| 67 |
+
- `MODELOS_REPOSITORIO_PROVIDER` (`local` ou `hf_dataset`)
|
| 68 |
+
- `MODELOS_REPOSITORIO_LOCAL_DIR` (opcional, quando `local`)
|
| 69 |
+
- `MODELOS_REPOSITORIO_HF_REPO_ID` (ex.: `gui-sparim/repositorio_mesa`)
|
| 70 |
+
- `MODELOS_REPOSITORIO_HF_REVISION` (ex.: `main`)
|
| 71 |
+
- `MODELOS_REPOSITORIO_HF_SUBDIR` (ex.: `modelos_dai`)
|
| 72 |
+
- `HF_TOKEN` (opcional para dataset privado)
|
| 73 |
+
|
| 74 |
+
No modo `hf_dataset`, o backend consulta a revisão atual do dataset e só
|
| 75 |
+
sincroniza novamente quando detectar mudança de revisão.
|
backend/app/api/elaboracao.py
CHANGED
|
@@ -128,6 +128,10 @@ class ColunaDataMercadoPayload(SessionPayload):
|
|
| 128 |
coluna_data: str
|
| 129 |
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
@router.post("/upload")
|
| 132 |
async def upload_file(
|
| 133 |
session_id: str = Form(...),
|
|
@@ -139,6 +143,17 @@ async def upload_file(
|
|
| 139 |
return elaboracao_service.load_uploaded_file(session)
|
| 140 |
|
| 141 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
@router.post("/confirm-sheet")
|
| 143 |
def confirm_sheet(payload: ConfirmSheetPayload) -> dict[str, Any]:
|
| 144 |
session = session_store.get(payload.session_id)
|
|
|
|
| 128 |
coluna_data: str
|
| 129 |
|
| 130 |
|
| 131 |
+
class RepositorioModeloPayload(SessionPayload):
|
| 132 |
+
modelo_id: str
|
| 133 |
+
|
| 134 |
+
|
| 135 |
@router.post("/upload")
|
| 136 |
async def upload_file(
|
| 137 |
session_id: str = Form(...),
|
|
|
|
| 143 |
return elaboracao_service.load_uploaded_file(session)
|
| 144 |
|
| 145 |
|
| 146 |
+
@router.get("/repositorio-modelos")
|
| 147 |
+
def repositorio_modelos() -> dict[str, Any]:
|
| 148 |
+
return elaboracao_service.listar_modelos_repositorio()
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
@router.post("/repositorio-carregar")
|
| 152 |
+
def repositorio_carregar(payload: RepositorioModeloPayload) -> dict[str, Any]:
|
| 153 |
+
session = session_store.get(payload.session_id)
|
| 154 |
+
return elaboracao_service.carregar_modelo_repositorio(session, payload.modelo_id)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
@router.post("/confirm-sheet")
|
| 158 |
def confirm_sheet(payload: ConfirmSheetPayload) -> dict[str, Any]:
|
| 159 |
session = session_store.get(payload.session_id)
|
backend/app/api/visualizacao.py
CHANGED
|
@@ -36,6 +36,10 @@ class AvaliacaoBasePayload(SessionPayload):
|
|
| 36 |
indice_base: str | None = None
|
| 37 |
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
@router.post("/upload")
|
| 40 |
async def upload_file(
|
| 41 |
session_id: str = Form(...),
|
|
@@ -47,6 +51,17 @@ async def upload_file(
|
|
| 47 |
return visualizacao_service.carregar_modelo(session, caminho)
|
| 48 |
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
@router.post("/exibir")
|
| 51 |
def exibir(payload: SessionPayload) -> dict[str, Any]:
|
| 52 |
session = session_store.get(payload.session_id)
|
|
|
|
| 36 |
indice_base: str | None = None
|
| 37 |
|
| 38 |
|
| 39 |
+
class RepositorioModeloPayload(SessionPayload):
|
| 40 |
+
modelo_id: str
|
| 41 |
+
|
| 42 |
+
|
| 43 |
@router.post("/upload")
|
| 44 |
async def upload_file(
|
| 45 |
session_id: str = Form(...),
|
|
|
|
| 51 |
return visualizacao_service.carregar_modelo(session, caminho)
|
| 52 |
|
| 53 |
|
| 54 |
+
@router.get("/repositorio-modelos")
|
| 55 |
+
def repositorio_modelos() -> dict[str, Any]:
|
| 56 |
+
return visualizacao_service.listar_modelos_repositorio()
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
@router.post("/repositorio-carregar")
|
| 60 |
+
def repositorio_carregar(payload: RepositorioModeloPayload) -> dict[str, Any]:
|
| 61 |
+
session = session_store.get(payload.session_id)
|
| 62 |
+
return visualizacao_service.carregar_modelo_repositorio(session, payload.modelo_id)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
@router.post("/exibir")
|
| 66 |
def exibir(payload: SessionPayload) -> dict[str, Any]:
|
| 67 |
session = session_store.get(payload.session_id)
|
backend/app/core/pesquisa/modelos_dai/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
| 1 |
# Pasta de Modelos da Aba Pesquisa
|
| 2 |
|
|
|
|
|
|
|
|
|
|
| 3 |
Coloque nesta pasta os arquivos `.dai` que devem aparecer na aba **Pesquisa**.
|
| 4 |
|
| 5 |
## Estrutura
|
|
|
|
| 1 |
# Pasta de Modelos da Aba Pesquisa
|
| 2 |
|
| 3 |
+
Esta pasta é usada quando o provider de repositório de modelos está configurado
|
| 4 |
+
como `local` (`MODELOS_REPOSITORIO_PROVIDER=local`).
|
| 5 |
+
|
| 6 |
Coloque nesta pasta os arquivos `.dai` que devem aparecer na aba **Pesquisa**.
|
| 7 |
|
| 8 |
## Estrutura
|
backend/app/services/elaboracao_service.py
CHANGED
|
@@ -40,6 +40,7 @@ from app.core.elaboracao.formatadores import (
|
|
| 40 |
formatar_outliers_anteriores_html,
|
| 41 |
)
|
| 42 |
from app.models.session import SessionState
|
|
|
|
| 43 |
from app.services.serializers import dataframe_to_payload, figure_to_payload, sanitize_value
|
| 44 |
|
| 45 |
_AVALIADORES_PATH = Path(__file__).resolve().parent.parent / "core" / "elaboracao" / "avaliadores.json"
|
|
@@ -472,6 +473,17 @@ def load_uploaded_file(session: SessionState, selected_sheet: str | None = None)
|
|
| 472 |
}
|
| 473 |
|
| 474 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
def load_dai_for_elaboracao(session: SessionState, caminho_arquivo: str) -> dict[str, Any]:
|
| 476 |
(
|
| 477 |
df,
|
|
|
|
| 40 |
formatar_outliers_anteriores_html,
|
| 41 |
)
|
| 42 |
from app.models.session import SessionState
|
| 43 |
+
from app.services import model_repository
|
| 44 |
from app.services.serializers import dataframe_to_payload, figure_to_payload, sanitize_value
|
| 45 |
|
| 46 |
_AVALIADORES_PATH = Path(__file__).resolve().parent.parent / "core" / "elaboracao" / "avaliadores.json"
|
|
|
|
| 473 |
}
|
| 474 |
|
| 475 |
|
| 476 |
+
def listar_modelos_repositorio() -> dict[str, Any]:
|
| 477 |
+
return sanitize_value(model_repository.list_repository_models())
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
def carregar_modelo_repositorio(session: SessionState, modelo_id: str) -> dict[str, Any]:
|
| 481 |
+
caminho = model_repository.resolve_model_file(modelo_id)
|
| 482 |
+
session.uploaded_file_path = str(caminho)
|
| 483 |
+
session.uploaded_filename = caminho.name
|
| 484 |
+
return load_dai_for_elaboracao(session, str(caminho))
|
| 485 |
+
|
| 486 |
+
|
| 487 |
def load_dai_for_elaboracao(session: SessionState, caminho_arquivo: str) -> dict[str, Any]:
|
| 488 |
(
|
| 489 |
df,
|
backend/app/services/model_repository.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from threading import Lock
|
| 7 |
+
from typing import Any
|
| 8 |
+
|
| 9 |
+
from fastapi import HTTPException
|
| 10 |
+
|
| 11 |
+
try:
|
| 12 |
+
from huggingface_hub import HfApi, snapshot_download
|
| 13 |
+
except Exception: # pragma: no cover - dependência opcional em tempo de import
|
| 14 |
+
HfApi = None # type: ignore[assignment]
|
| 15 |
+
snapshot_download = None # type: ignore[assignment]
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
DEFAULT_LOCAL_MODELOS_DIR = Path(__file__).resolve().parent.parent / "core" / "pesquisa" / "modelos_dai"
|
| 19 |
+
DEFAULT_HF_REPO_ID = "gui-sparim/repositorio_mesa"
|
| 20 |
+
DEFAULT_HF_REVISION = "main"
|
| 21 |
+
DEFAULT_HF_SUBDIR = "modelos_dai"
|
| 22 |
+
|
| 23 |
+
_STATE_LOCK = Lock()
|
| 24 |
+
_STATE: dict[str, Any] = {
|
| 25 |
+
"provider": None,
|
| 26 |
+
"signature": None,
|
| 27 |
+
"revision": None,
|
| 28 |
+
"repo_id": None,
|
| 29 |
+
"subdir": None,
|
| 30 |
+
"modelos_dir": None,
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@dataclass(frozen=True)
|
| 35 |
+
class ModelRepositoryResolution:
|
| 36 |
+
provider: str
|
| 37 |
+
modelos_dir: Path
|
| 38 |
+
signature: str
|
| 39 |
+
revision: str | None = None
|
| 40 |
+
repo_id: str | None = None
|
| 41 |
+
subdir: str | None = None
|
| 42 |
+
degraded: bool = False
|
| 43 |
+
|
| 44 |
+
def as_payload(self) -> dict[str, Any]:
|
| 45 |
+
return {
|
| 46 |
+
"provider": self.provider,
|
| 47 |
+
"revision": self.revision,
|
| 48 |
+
"repo_id": self.repo_id,
|
| 49 |
+
"subdir": self.subdir,
|
| 50 |
+
"degraded": self.degraded,
|
| 51 |
+
"modelos_dir": str(self.modelos_dir),
|
| 52 |
+
"signature": self.signature,
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _provider() -> str:
|
| 57 |
+
raw = (
|
| 58 |
+
os.getenv("MODELOS_REPOSITORIO_PROVIDER")
|
| 59 |
+
or os.getenv("PESQUISA_MODELOS_PROVIDER")
|
| 60 |
+
or "local"
|
| 61 |
+
)
|
| 62 |
+
value = str(raw).strip().lower()
|
| 63 |
+
if value in {"hf", "hf_dataset", "dataset", "huggingface"}:
|
| 64 |
+
return "hf_dataset"
|
| 65 |
+
return "local"
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def _hf_repo_id() -> str:
|
| 69 |
+
return str(
|
| 70 |
+
os.getenv("MODELOS_REPOSITORIO_HF_REPO_ID")
|
| 71 |
+
or os.getenv("PESQUISA_HF_REPO_ID")
|
| 72 |
+
or DEFAULT_HF_REPO_ID
|
| 73 |
+
).strip()
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _hf_revision() -> str:
|
| 77 |
+
return str(
|
| 78 |
+
os.getenv("MODELOS_REPOSITORIO_HF_REVISION")
|
| 79 |
+
or os.getenv("PESQUISA_HF_REVISION")
|
| 80 |
+
or DEFAULT_HF_REVISION
|
| 81 |
+
).strip()
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def _hf_subdir() -> str:
|
| 85 |
+
return str(
|
| 86 |
+
os.getenv("MODELOS_REPOSITORIO_HF_SUBDIR")
|
| 87 |
+
or os.getenv("PESQUISA_HF_SUBDIR")
|
| 88 |
+
or DEFAULT_HF_SUBDIR
|
| 89 |
+
).strip().strip("/")
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def _hf_token() -> str | None:
|
| 93 |
+
for key in ("HF_TOKEN", "HUGGINGFACE_HUB_TOKEN", "HUGGINGFACE_TOKEN"):
|
| 94 |
+
value = os.getenv(key)
|
| 95 |
+
if value:
|
| 96 |
+
return value
|
| 97 |
+
return None
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def _local_mode_models_dir() -> Path:
|
| 101 |
+
raw = os.getenv("MODELOS_REPOSITORIO_LOCAL_DIR")
|
| 102 |
+
if raw and str(raw).strip():
|
| 103 |
+
return Path(str(raw).strip()).expanduser().resolve()
|
| 104 |
+
return DEFAULT_LOCAL_MODELOS_DIR
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def _ensure_local_resolution() -> ModelRepositoryResolution:
|
| 108 |
+
modelos_dir = _local_mode_models_dir()
|
| 109 |
+
modelos_dir.mkdir(parents=True, exist_ok=True)
|
| 110 |
+
signature = f"local:{modelos_dir}"
|
| 111 |
+
return ModelRepositoryResolution(
|
| 112 |
+
provider="local",
|
| 113 |
+
modelos_dir=modelos_dir,
|
| 114 |
+
signature=signature,
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def _resolve_hf() -> ModelRepositoryResolution:
|
| 119 |
+
if HfApi is None or snapshot_download is None:
|
| 120 |
+
raise HTTPException(
|
| 121 |
+
status_code=500,
|
| 122 |
+
detail="Provider hf_dataset indisponivel: instale huggingface_hub no backend",
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
repo_id = _hf_repo_id()
|
| 126 |
+
revision_ref = _hf_revision()
|
| 127 |
+
subdir = _hf_subdir()
|
| 128 |
+
token = _hf_token()
|
| 129 |
+
pattern = f"{subdir}/*.dai"
|
| 130 |
+
|
| 131 |
+
api = HfApi(token=token)
|
| 132 |
+
|
| 133 |
+
try:
|
| 134 |
+
info = api.dataset_info(repo_id=repo_id, revision=revision_ref, token=token)
|
| 135 |
+
except Exception as exc:
|
| 136 |
+
with _STATE_LOCK:
|
| 137 |
+
snapshot_dir = _STATE.get("modelos_dir")
|
| 138 |
+
signature = _STATE.get("signature")
|
| 139 |
+
rev = _STATE.get("revision")
|
| 140 |
+
same_repo = _STATE.get("provider") == "hf_dataset" and _STATE.get("repo_id") == repo_id
|
| 141 |
+
if snapshot_dir and signature and same_repo and Path(snapshot_dir).exists():
|
| 142 |
+
return ModelRepositoryResolution(
|
| 143 |
+
provider="hf_dataset",
|
| 144 |
+
modelos_dir=Path(snapshot_dir),
|
| 145 |
+
signature=str(signature),
|
| 146 |
+
revision=str(rev) if rev else None,
|
| 147 |
+
repo_id=repo_id,
|
| 148 |
+
subdir=subdir,
|
| 149 |
+
degraded=True,
|
| 150 |
+
)
|
| 151 |
+
raise HTTPException(
|
| 152 |
+
status_code=503,
|
| 153 |
+
detail=f"Nao foi possivel consultar o repositório de modelos no Hugging Face: {exc}",
|
| 154 |
+
) from exc
|
| 155 |
+
|
| 156 |
+
commit_sha = str(getattr(info, "sha", "") or "").strip() or revision_ref
|
| 157 |
+
signature = f"hf_dataset:{repo_id}@{commit_sha}:{subdir}"
|
| 158 |
+
|
| 159 |
+
with _STATE_LOCK:
|
| 160 |
+
current_signature = _STATE.get("signature")
|
| 161 |
+
current_dir = _STATE.get("modelos_dir")
|
| 162 |
+
if (
|
| 163 |
+
current_signature == signature
|
| 164 |
+
and current_dir
|
| 165 |
+
and Path(str(current_dir)).exists()
|
| 166 |
+
):
|
| 167 |
+
return ModelRepositoryResolution(
|
| 168 |
+
provider="hf_dataset",
|
| 169 |
+
modelos_dir=Path(str(current_dir)),
|
| 170 |
+
signature=signature,
|
| 171 |
+
revision=commit_sha,
|
| 172 |
+
repo_id=repo_id,
|
| 173 |
+
subdir=subdir,
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
try:
|
| 177 |
+
snapshot_root = Path(
|
| 178 |
+
snapshot_download(
|
| 179 |
+
repo_id=repo_id,
|
| 180 |
+
repo_type="dataset",
|
| 181 |
+
revision=commit_sha,
|
| 182 |
+
allow_patterns=[pattern],
|
| 183 |
+
token=token,
|
| 184 |
+
)
|
| 185 |
+
)
|
| 186 |
+
modelos_dir = snapshot_root / subdir
|
| 187 |
+
if not modelos_dir.exists():
|
| 188 |
+
raise RuntimeError(f"Pasta '{subdir}' nao encontrada no snapshot '{commit_sha}'")
|
| 189 |
+
except Exception as exc:
|
| 190 |
+
with _STATE_LOCK:
|
| 191 |
+
fallback_dir = _STATE.get("modelos_dir")
|
| 192 |
+
fallback_signature = _STATE.get("signature")
|
| 193 |
+
fallback_rev = _STATE.get("revision")
|
| 194 |
+
same_repo = _STATE.get("provider") == "hf_dataset" and _STATE.get("repo_id") == repo_id
|
| 195 |
+
if fallback_dir and fallback_signature and same_repo and Path(str(fallback_dir)).exists():
|
| 196 |
+
return ModelRepositoryResolution(
|
| 197 |
+
provider="hf_dataset",
|
| 198 |
+
modelos_dir=Path(str(fallback_dir)),
|
| 199 |
+
signature=str(fallback_signature),
|
| 200 |
+
revision=str(fallback_rev) if fallback_rev else None,
|
| 201 |
+
repo_id=repo_id,
|
| 202 |
+
subdir=subdir,
|
| 203 |
+
degraded=True,
|
| 204 |
+
)
|
| 205 |
+
raise HTTPException(
|
| 206 |
+
status_code=503,
|
| 207 |
+
detail=f"Nao foi possivel sincronizar modelos do dataset no Hugging Face: {exc}",
|
| 208 |
+
) from exc
|
| 209 |
+
|
| 210 |
+
with _STATE_LOCK:
|
| 211 |
+
_STATE.update(
|
| 212 |
+
{
|
| 213 |
+
"provider": "hf_dataset",
|
| 214 |
+
"signature": signature,
|
| 215 |
+
"revision": commit_sha,
|
| 216 |
+
"repo_id": repo_id,
|
| 217 |
+
"subdir": subdir,
|
| 218 |
+
"modelos_dir": str(modelos_dir),
|
| 219 |
+
}
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
return ModelRepositoryResolution(
|
| 223 |
+
provider="hf_dataset",
|
| 224 |
+
modelos_dir=modelos_dir,
|
| 225 |
+
signature=signature,
|
| 226 |
+
revision=commit_sha,
|
| 227 |
+
repo_id=repo_id,
|
| 228 |
+
subdir=subdir,
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def resolve_model_repository() -> ModelRepositoryResolution:
|
| 233 |
+
provider = _provider()
|
| 234 |
+
if provider == "hf_dataset":
|
| 235 |
+
return _resolve_hf()
|
| 236 |
+
return _ensure_local_resolution()
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
def list_repository_models() -> dict[str, Any]:
|
| 240 |
+
resolved = resolve_model_repository()
|
| 241 |
+
modelos = sorted(resolved.modelos_dir.glob("*.dai"), key=lambda item: item.name.lower())
|
| 242 |
+
return {
|
| 243 |
+
"modelos": [
|
| 244 |
+
{
|
| 245 |
+
"id": caminho.stem,
|
| 246 |
+
"arquivo": caminho.name,
|
| 247 |
+
"nome_modelo": caminho.stem,
|
| 248 |
+
}
|
| 249 |
+
for caminho in modelos
|
| 250 |
+
],
|
| 251 |
+
"total_modelos": len(modelos),
|
| 252 |
+
"fonte": resolved.as_payload(),
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def resolve_model_file(modelo_id: str) -> Path:
|
| 257 |
+
resolved = resolve_model_repository()
|
| 258 |
+
chave = str(modelo_id or "").strip()
|
| 259 |
+
if not chave:
|
| 260 |
+
raise HTTPException(status_code=400, detail="Informe o identificador do modelo")
|
| 261 |
+
|
| 262 |
+
modelos = sorted(resolved.modelos_dir.glob("*.dai"), key=lambda item: item.name.lower())
|
| 263 |
+
by_stem = {caminho.stem.lower(): caminho for caminho in modelos}
|
| 264 |
+
by_name = {caminho.name.lower(): caminho for caminho in modelos}
|
| 265 |
+
|
| 266 |
+
candidato = by_stem.get(chave.lower()) or by_name.get(chave.lower())
|
| 267 |
+
if candidato is None and not chave.lower().endswith(".dai"):
|
| 268 |
+
candidato = by_name.get(f"{chave.lower()}.dai")
|
| 269 |
+
if candidato is None:
|
| 270 |
+
raise HTTPException(status_code=404, detail="Modelo nao encontrado no repositório configurado")
|
| 271 |
+
return candidato
|
backend/app/services/pesquisa_service.py
CHANGED
|
@@ -17,9 +17,9 @@ from fastapi import HTTPException
|
|
| 17 |
from joblib import load
|
| 18 |
|
| 19 |
from app.core.elaboracao.core import _migrar_pacote_v1_para_v2
|
|
|
|
| 20 |
from app.services.serializers import sanitize_value
|
| 21 |
|
| 22 |
-
MODELOS_DIR = Path(__file__).resolve().parent.parent / "core" / "pesquisa" / "modelos_dai"
|
| 23 |
ADMIN_CONFIG_PATH = Path(__file__).resolve().parent.parent / "core" / "pesquisa" / "pesquisa_admin_config.json"
|
| 24 |
|
| 25 |
AREA_PRIVATIVA_ALIASES = ["APRIV", "APRIVEQ", "ATPRIV", "ACOPRIV", "AREAPRIV", "AREA_PRIVATIVA", "AREA PRIVATIVA"]
|
|
@@ -192,15 +192,26 @@ class PesquisaFiltros:
|
|
| 192 |
_CACHE_LOCK = Lock()
|
| 193 |
_CACHE: dict[str, dict[str, Any]] = {}
|
| 194 |
_ADMIN_CONFIG_LOCK = Lock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
|
| 197 |
def ensure_modelos_dir() -> Path:
|
| 198 |
-
|
| 199 |
-
return MODELOS_DIR
|
| 200 |
|
| 201 |
|
| 202 |
def obter_admin_config_pesquisa() -> dict[str, Any]:
|
| 203 |
-
|
|
|
|
| 204 |
modelos = sorted(pasta.glob("*.dai"), key=lambda item: item.name.lower())
|
| 205 |
todos = [_carregar_resumo_com_cache(caminho) for caminho in modelos]
|
| 206 |
colunas_filtro = _montar_config_colunas_filtro(todos)
|
|
@@ -210,12 +221,14 @@ def obter_admin_config_pesquisa() -> dict[str, Any]:
|
|
| 210 |
"colunas_filtro": colunas_filtro,
|
| 211 |
"admin_fontes": admin_fontes,
|
| 212 |
"total_modelos": len(todos),
|
|
|
|
| 213 |
}
|
| 214 |
)
|
| 215 |
|
| 216 |
|
| 217 |
def salvar_admin_config_pesquisa(campos: dict[str, list[str]] | None) -> dict[str, Any]:
|
| 218 |
-
|
|
|
|
| 219 |
modelos = sorted(pasta.glob("*.dai"), key=lambda item: item.name.lower())
|
| 220 |
todos = [_carregar_resumo_com_cache(caminho) for caminho in modelos]
|
| 221 |
colunas_filtro = _montar_config_colunas_filtro(todos)
|
|
@@ -227,12 +240,14 @@ def salvar_admin_config_pesquisa(campos: dict[str, list[str]] | None) -> dict[st
|
|
| 227 |
"admin_fontes": admin_fontes,
|
| 228 |
"total_modelos": len(todos),
|
| 229 |
"status": "Configuracao de busca salva com sucesso.",
|
|
|
|
| 230 |
}
|
| 231 |
)
|
| 232 |
|
| 233 |
|
| 234 |
def listar_modelos(filtros: PesquisaFiltros, limite: int | None = None, somente_contexto: bool = False) -> dict[str, Any]:
|
| 235 |
-
|
|
|
|
| 236 |
modelos = sorted(pasta.glob("*.dai"), key=lambda item: item.name.lower())
|
| 237 |
|
| 238 |
otica = _normalizar_otica(filtros.otica)
|
|
@@ -252,6 +267,7 @@ def listar_modelos(filtros: PesquisaFiltros, limite: int | None = None, somente_
|
|
| 252 |
"total_filtrado": 0,
|
| 253 |
"total_geral": len(todos),
|
| 254 |
"modelos_dir": str(pasta),
|
|
|
|
| 255 |
"admin_fontes": admin_fontes,
|
| 256 |
"filtros_aplicados": {
|
| 257 |
"nome": filtros.nome,
|
|
@@ -316,6 +332,7 @@ def listar_modelos(filtros: PesquisaFiltros, limite: int | None = None, somente_
|
|
| 316 |
"total_filtrado": len(filtrados),
|
| 317 |
"total_geral": len(todos),
|
| 318 |
"modelos_dir": str(pasta),
|
|
|
|
| 319 |
"admin_fontes": admin_fontes,
|
| 320 |
"filtros_aplicados": {
|
| 321 |
"nome": filtros.nome,
|
|
|
|
| 17 |
from joblib import load
|
| 18 |
|
| 19 |
from app.core.elaboracao.core import _migrar_pacote_v1_para_v2
|
| 20 |
+
from app.services import model_repository
|
| 21 |
from app.services.serializers import sanitize_value
|
| 22 |
|
|
|
|
| 23 |
ADMIN_CONFIG_PATH = Path(__file__).resolve().parent.parent / "core" / "pesquisa" / "pesquisa_admin_config.json"
|
| 24 |
|
| 25 |
AREA_PRIVATIVA_ALIASES = ["APRIV", "APRIVEQ", "ATPRIV", "ACOPRIV", "AREAPRIV", "AREA_PRIVATIVA", "AREA PRIVATIVA"]
|
|
|
|
| 192 |
_CACHE_LOCK = Lock()
|
| 193 |
_CACHE: dict[str, dict[str, Any]] = {}
|
| 194 |
_ADMIN_CONFIG_LOCK = Lock()
|
| 195 |
+
_CACHE_SOURCE_SIGNATURE: str | None = None
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def _resolver_repositorio_modelos() -> model_repository.ModelRepositoryResolution:
|
| 199 |
+
global _CACHE_SOURCE_SIGNATURE
|
| 200 |
+
resolved = model_repository.resolve_model_repository()
|
| 201 |
+
with _CACHE_LOCK:
|
| 202 |
+
if _CACHE_SOURCE_SIGNATURE != resolved.signature:
|
| 203 |
+
_CACHE.clear()
|
| 204 |
+
_CACHE_SOURCE_SIGNATURE = resolved.signature
|
| 205 |
+
return resolved
|
| 206 |
|
| 207 |
|
| 208 |
def ensure_modelos_dir() -> Path:
|
| 209 |
+
return _resolver_repositorio_modelos().modelos_dir
|
|
|
|
| 210 |
|
| 211 |
|
| 212 |
def obter_admin_config_pesquisa() -> dict[str, Any]:
|
| 213 |
+
resolved = _resolver_repositorio_modelos()
|
| 214 |
+
pasta = resolved.modelos_dir
|
| 215 |
modelos = sorted(pasta.glob("*.dai"), key=lambda item: item.name.lower())
|
| 216 |
todos = [_carregar_resumo_com_cache(caminho) for caminho in modelos]
|
| 217 |
colunas_filtro = _montar_config_colunas_filtro(todos)
|
|
|
|
| 221 |
"colunas_filtro": colunas_filtro,
|
| 222 |
"admin_fontes": admin_fontes,
|
| 223 |
"total_modelos": len(todos),
|
| 224 |
+
"fonte_modelos": resolved.as_payload(),
|
| 225 |
}
|
| 226 |
)
|
| 227 |
|
| 228 |
|
| 229 |
def salvar_admin_config_pesquisa(campos: dict[str, list[str]] | None) -> dict[str, Any]:
|
| 230 |
+
resolved = _resolver_repositorio_modelos()
|
| 231 |
+
pasta = resolved.modelos_dir
|
| 232 |
modelos = sorted(pasta.glob("*.dai"), key=lambda item: item.name.lower())
|
| 233 |
todos = [_carregar_resumo_com_cache(caminho) for caminho in modelos]
|
| 234 |
colunas_filtro = _montar_config_colunas_filtro(todos)
|
|
|
|
| 240 |
"admin_fontes": admin_fontes,
|
| 241 |
"total_modelos": len(todos),
|
| 242 |
"status": "Configuracao de busca salva com sucesso.",
|
| 243 |
+
"fonte_modelos": resolved.as_payload(),
|
| 244 |
}
|
| 245 |
)
|
| 246 |
|
| 247 |
|
| 248 |
def listar_modelos(filtros: PesquisaFiltros, limite: int | None = None, somente_contexto: bool = False) -> dict[str, Any]:
|
| 249 |
+
resolved = _resolver_repositorio_modelos()
|
| 250 |
+
pasta = resolved.modelos_dir
|
| 251 |
modelos = sorted(pasta.glob("*.dai"), key=lambda item: item.name.lower())
|
| 252 |
|
| 253 |
otica = _normalizar_otica(filtros.otica)
|
|
|
|
| 267 |
"total_filtrado": 0,
|
| 268 |
"total_geral": len(todos),
|
| 269 |
"modelos_dir": str(pasta),
|
| 270 |
+
"fonte_modelos": resolved.as_payload(),
|
| 271 |
"admin_fontes": admin_fontes,
|
| 272 |
"filtros_aplicados": {
|
| 273 |
"nome": filtros.nome,
|
|
|
|
| 332 |
"total_filtrado": len(filtrados),
|
| 333 |
"total_geral": len(todos),
|
| 334 |
"modelos_dir": str(pasta),
|
| 335 |
+
"fonte_modelos": resolved.as_payload(),
|
| 336 |
"admin_fontes": admin_fontes,
|
| 337 |
"filtros_aplicados": {
|
| 338 |
"nome": filtros.nome,
|
backend/app/services/visualizacao_service.py
CHANGED
|
@@ -12,12 +12,24 @@ from app.core.visualizacao import app as viz_app
|
|
| 12 |
from app.core.elaboracao.core import _migrar_pacote_v1_para_v2, avaliar_imovel, exportar_avaliacoes_excel
|
| 13 |
from app.core.elaboracao.formatadores import formatar_avaliacao_html
|
| 14 |
from app.models.session import SessionState
|
|
|
|
| 15 |
from app.services.serializers import dataframe_to_payload, figure_to_payload, sanitize_value
|
| 16 |
|
| 17 |
|
| 18 |
CHAVES_ESPERADAS = ["versao", "dados", "transformacoes", "modelo"]
|
| 19 |
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
def carregar_modelo(session: SessionState, caminho_arquivo: str) -> dict[str, Any]:
|
| 22 |
try:
|
| 23 |
pacote = load(caminho_arquivo)
|
|
|
|
| 12 |
from app.core.elaboracao.core import _migrar_pacote_v1_para_v2, avaliar_imovel, exportar_avaliacoes_excel
|
| 13 |
from app.core.elaboracao.formatadores import formatar_avaliacao_html
|
| 14 |
from app.models.session import SessionState
|
| 15 |
+
from app.services import model_repository
|
| 16 |
from app.services.serializers import dataframe_to_payload, figure_to_payload, sanitize_value
|
| 17 |
|
| 18 |
|
| 19 |
CHAVES_ESPERADAS = ["versao", "dados", "transformacoes", "modelo"]
|
| 20 |
|
| 21 |
|
| 22 |
+
def listar_modelos_repositorio() -> dict[str, Any]:
|
| 23 |
+
return sanitize_value(model_repository.list_repository_models())
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def carregar_modelo_repositorio(session: SessionState, modelo_id: str) -> dict[str, Any]:
|
| 27 |
+
caminho = model_repository.resolve_model_file(modelo_id)
|
| 28 |
+
session.uploaded_file_path = str(caminho)
|
| 29 |
+
session.uploaded_filename = caminho.name
|
| 30 |
+
return carregar_modelo(session, str(caminho))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
def carregar_modelo(session: SessionState, caminho_arquivo: str) -> dict[str, Any]:
|
| 34 |
try:
|
| 35 |
pacote = load(caminho_arquivo)
|
backend/requirements.txt
CHANGED
|
@@ -14,3 +14,4 @@ openpyxl
|
|
| 14 |
geopandas
|
| 15 |
fiona
|
| 16 |
gradio>=4.0
|
|
|
|
|
|
| 14 |
geopandas
|
| 15 |
fiona
|
| 16 |
gradio>=4.0
|
| 17 |
+
huggingface_hub>=0.30.0
|
frontend/src/api.js
CHANGED
|
@@ -83,6 +83,8 @@ export const api = {
|
|
| 83 |
form.append('file', file)
|
| 84 |
return postForm('/api/elaboracao/upload', form)
|
| 85 |
},
|
|
|
|
|
|
|
| 86 |
|
| 87 |
confirmSheet: (sessionId, sheetName) => postJson('/api/elaboracao/confirm-sheet', { session_id: sessionId, sheet_name: sheetName }),
|
| 88 |
mapCoords: (sessionId, colLat, colLon) => postJson('/api/elaboracao/map-coords', { session_id: sessionId, col_lat: colLat, col_lon: colLon }),
|
|
@@ -164,6 +166,8 @@ export const api = {
|
|
| 164 |
form.append('file', file)
|
| 165 |
return postForm('/api/visualizacao/upload', form)
|
| 166 |
},
|
|
|
|
|
|
|
| 167 |
exibirVisualizacao: (sessionId) => postJson('/api/visualizacao/exibir', { session_id: sessionId }),
|
| 168 |
updateVisualizacaoMap: (sessionId, variavelMapa) => postJson('/api/visualizacao/map/update', { session_id: sessionId, variavel_mapa: variavelMapa }),
|
| 169 |
evaluationFieldsViz: (sessionId) => postJson('/api/visualizacao/evaluation/fields', { session_id: sessionId }),
|
|
|
|
| 83 |
form.append('file', file)
|
| 84 |
return postForm('/api/elaboracao/upload', form)
|
| 85 |
},
|
| 86 |
+
elaboracaoRepositorioModelos: () => getJson('/api/elaboracao/repositorio-modelos'),
|
| 87 |
+
elaboracaoRepositorioCarregar: (sessionId, modeloId) => postJson('/api/elaboracao/repositorio-carregar', { session_id: sessionId, modelo_id: modeloId }),
|
| 88 |
|
| 89 |
confirmSheet: (sessionId, sheetName) => postJson('/api/elaboracao/confirm-sheet', { session_id: sessionId, sheet_name: sheetName }),
|
| 90 |
mapCoords: (sessionId, colLat, colLon) => postJson('/api/elaboracao/map-coords', { session_id: sessionId, col_lat: colLat, col_lon: colLon }),
|
|
|
|
| 166 |
form.append('file', file)
|
| 167 |
return postForm('/api/visualizacao/upload', form)
|
| 168 |
},
|
| 169 |
+
visualizacaoRepositorioModelos: () => getJson('/api/visualizacao/repositorio-modelos'),
|
| 170 |
+
visualizacaoRepositorioCarregar: (sessionId, modeloId) => postJson('/api/visualizacao/repositorio-carregar', { session_id: sessionId, modelo_id: modeloId }),
|
| 171 |
exibirVisualizacao: (sessionId) => postJson('/api/visualizacao/exibir', { session_id: sessionId }),
|
| 172 |
updateVisualizacaoMap: (sessionId, variavelMapa) => postJson('/api/visualizacao/map/update', { session_id: sessionId, variavel_mapa: variavelMapa }),
|
| 173 |
evaluationFieldsViz: (sessionId) => postJson('/api/visualizacao/evaluation/fields', { session_id: sessionId }),
|
frontend/src/components/ElaboracaoTab.jsx
CHANGED
|
@@ -544,6 +544,10 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 544 |
const [selectedSheet, setSelectedSheet] = useState('')
|
| 545 |
const [elaborador, setElaborador] = useState(null)
|
| 546 |
const [modeloCarregadoInfo, setModeloCarregadoInfo] = useState(null)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 547 |
|
| 548 |
const [dados, setDados] = useState(null)
|
| 549 |
const [mapaHtml, setMapaHtml] = useState('')
|
|
@@ -953,6 +957,34 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 953 |
}
|
| 954 |
}, [sessionId])
|
| 955 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 956 |
async function withBusy(fn) {
|
| 957 |
setLoading(true)
|
| 958 |
setError('')
|
|
@@ -980,6 +1012,45 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 980 |
})
|
| 981 |
}
|
| 982 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 983 |
function applyBaseResponse(resp, options = {}) {
|
| 984 |
const resetXSelection = Boolean(options.resetXSelection)
|
| 985 |
const colunaYPadrao = String(resp.coluna_y_padrao || '')
|
|
@@ -1169,6 +1240,70 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1169 |
setBaseValue('')
|
| 1170 |
}
|
| 1171 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1172 |
async function onUploadClick(arquivo = null) {
|
| 1173 |
const arquivoUpload = arquivo || uploadedFile
|
| 1174 |
if (!arquivoUpload || !sessionId) return
|
|
@@ -1180,58 +1315,20 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1180 |
const uploadEhDai = nomeArquivo.endsWith('.dai')
|
| 1181 |
setTipoFonteDados(uploadEhDai ? 'dai' : 'tabular')
|
| 1182 |
const resp = await api.uploadElaboracaoFile(sessionId, arquivoUpload)
|
| 1183 |
-
|
| 1184 |
-
|
| 1185 |
-
|
| 1186 |
-
|
| 1187 |
-
|
| 1188 |
-
|
| 1189 |
-
|
| 1190 |
-
|
| 1191 |
-
|
| 1192 |
-
|
| 1193 |
-
|
| 1194 |
-
|
| 1195 |
-
|
| 1196 |
-
|
| 1197 |
-
const resetXSelection = String(resp.tipo || '').toLowerCase() !== 'dai'
|
| 1198 |
-
applyBaseResponse(resp, { resetXSelection })
|
| 1199 |
-
} else if (resp.status) {
|
| 1200 |
-
setTipoFonteDados('tabular')
|
| 1201 |
-
setColunaY('')
|
| 1202 |
-
setColunaYDraft('')
|
| 1203 |
-
setSelection(null)
|
| 1204 |
-
setFit(null)
|
| 1205 |
-
setSelectionAppliedSnapshot(buildSelectionSnapshot())
|
| 1206 |
-
setColunasX([])
|
| 1207 |
-
setDicotomicas([])
|
| 1208 |
-
setCodigoAlocado([])
|
| 1209 |
-
setPercentuais([])
|
| 1210 |
-
setTransformacaoY('(x)')
|
| 1211 |
-
setTransformacoesX({})
|
| 1212 |
-
setTransformacoesAplicadas(null)
|
| 1213 |
-
setOrigemTransformacoes(null)
|
| 1214 |
-
setBuscaTransformAppliedSnapshot(buildGrauSnapshot(0, 0))
|
| 1215 |
-
setManualTransformAppliedSnapshot(buildTransformacoesSnapshot('(x)', {}))
|
| 1216 |
-
setColunasDataMercado([])
|
| 1217 |
-
setColunaDataMercadoSugerida('')
|
| 1218 |
-
setColunaDataMercado('')
|
| 1219 |
-
setColunaDataMercadoAplicada('')
|
| 1220 |
-
setPeriodoDadosMercado(null)
|
| 1221 |
-
setPeriodoDadosMercadoPreview(null)
|
| 1222 |
-
setDataMercadoError('')
|
| 1223 |
-
setFiltros(defaultFiltros())
|
| 1224 |
-
setOutliersTexto('')
|
| 1225 |
-
setReincluirTexto('')
|
| 1226 |
-
setOutlierFiltrosAplicadosSnapshot(buildFiltrosSnapshot(defaultFiltros()))
|
| 1227 |
-
setOutlierTextosAplicadosSnapshot(buildOutlierTextSnapshot('', ''))
|
| 1228 |
-
setCamposAvaliacao([])
|
| 1229 |
-
valoresAvaliacaoRef.current = {}
|
| 1230 |
-
setAvaliacaoFormVersion((prev) => prev + 1)
|
| 1231 |
-
setAvaliacaoPendente(false)
|
| 1232 |
-
setResultadoAvaliacaoHtml('')
|
| 1233 |
-
setStatus(resp.status)
|
| 1234 |
-
}
|
| 1235 |
})
|
| 1236 |
}
|
| 1237 |
|
|
@@ -1963,6 +2060,32 @@ export default function ElaboracaoTab({ sessionId }) {
|
|
| 1963 |
<div className="section1-groups">
|
| 1964 |
<div className="subpanel section1-group">
|
| 1965 |
<h4>Carregar modelo</h4>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1966 |
<div
|
| 1967 |
className={`upload-dropzone${uploadDragOver ? ' is-dragover' : ''}`}
|
| 1968 |
onDragOver={onUploadDropZoneDragOver}
|
|
|
|
| 544 |
const [selectedSheet, setSelectedSheet] = useState('')
|
| 545 |
const [elaborador, setElaborador] = useState(null)
|
| 546 |
const [modeloCarregadoInfo, setModeloCarregadoInfo] = useState(null)
|
| 547 |
+
const [repoModelos, setRepoModelos] = useState([])
|
| 548 |
+
const [repoModeloSelecionado, setRepoModeloSelecionado] = useState('')
|
| 549 |
+
const [repoModelosLoading, setRepoModelosLoading] = useState(false)
|
| 550 |
+
const [repoFonteModelos, setRepoFonteModelos] = useState('')
|
| 551 |
|
| 552 |
const [dados, setDados] = useState(null)
|
| 553 |
const [mapaHtml, setMapaHtml] = useState('')
|
|
|
|
| 957 |
}
|
| 958 |
}, [sessionId])
|
| 959 |
|
| 960 |
+
useEffect(() => {
|
| 961 |
+
let ativo = true
|
| 962 |
+
if (!sessionId) return () => {
|
| 963 |
+
ativo = false
|
| 964 |
+
}
|
| 965 |
+
|
| 966 |
+
setRepoModelosLoading(true)
|
| 967 |
+
api.elaboracaoRepositorioModelos()
|
| 968 |
+
.then((resp) => {
|
| 969 |
+
if (!ativo) return
|
| 970 |
+
aplicarRespostaModelosRepositorio(resp)
|
| 971 |
+
})
|
| 972 |
+
.catch(() => {
|
| 973 |
+
if (!ativo) return
|
| 974 |
+
setRepoModelos([])
|
| 975 |
+
setRepoModeloSelecionado('')
|
| 976 |
+
setRepoFonteModelos('')
|
| 977 |
+
})
|
| 978 |
+
.finally(() => {
|
| 979 |
+
if (!ativo) return
|
| 980 |
+
setRepoModelosLoading(false)
|
| 981 |
+
})
|
| 982 |
+
|
| 983 |
+
return () => {
|
| 984 |
+
ativo = false
|
| 985 |
+
}
|
| 986 |
+
}, [sessionId])
|
| 987 |
+
|
| 988 |
async function withBusy(fn) {
|
| 989 |
setLoading(true)
|
| 990 |
setError('')
|
|
|
|
| 1012 |
})
|
| 1013 |
}
|
| 1014 |
|
| 1015 |
+
function formatarFonteRepositorio(fonte) {
|
| 1016 |
+
if (!fonte || typeof fonte !== 'object') return ''
|
| 1017 |
+
const provider = String(fonte.provider || '').toLowerCase()
|
| 1018 |
+
if (provider === 'hf_dataset') {
|
| 1019 |
+
const repo = String(fonte.repo_id || '').trim()
|
| 1020 |
+
const revision = String(fonte.revision || '').trim()
|
| 1021 |
+
const degradado = Boolean(fonte.degraded)
|
| 1022 |
+
const sufixo = degradado ? ' (modo contingência)' : ''
|
| 1023 |
+
return `Fonte: HF Dataset${repo ? ` (${repo})` : ''}${revision ? ` | revisão ${revision.slice(0, 8)}` : ''}${sufixo}`
|
| 1024 |
+
}
|
| 1025 |
+
return 'Fonte: pasta local'
|
| 1026 |
+
}
|
| 1027 |
+
|
| 1028 |
+
function aplicarRespostaModelosRepositorio(resp) {
|
| 1029 |
+
const modelos = Array.isArray(resp?.modelos) ? resp.modelos : []
|
| 1030 |
+
setRepoModelos(modelos)
|
| 1031 |
+
setRepoFonteModelos(formatarFonteRepositorio(resp?.fonte || null))
|
| 1032 |
+
setRepoModeloSelecionado((prev) => {
|
| 1033 |
+
const atual = String(prev || '')
|
| 1034 |
+
if (atual && modelos.some((item) => String(item.id) === atual)) return atual
|
| 1035 |
+
return String(modelos[0]?.id || '')
|
| 1036 |
+
})
|
| 1037 |
+
}
|
| 1038 |
+
|
| 1039 |
+
async function carregarModelosRepositorio() {
|
| 1040 |
+
setRepoModelosLoading(true)
|
| 1041 |
+
try {
|
| 1042 |
+
const resp = await api.elaboracaoRepositorioModelos()
|
| 1043 |
+
aplicarRespostaModelosRepositorio(resp)
|
| 1044 |
+
} catch (err) {
|
| 1045 |
+
setError(err.message || 'Falha ao carregar modelos do repositório.')
|
| 1046 |
+
setRepoModelos([])
|
| 1047 |
+
setRepoModeloSelecionado('')
|
| 1048 |
+
setRepoFonteModelos('')
|
| 1049 |
+
} finally {
|
| 1050 |
+
setRepoModelosLoading(false)
|
| 1051 |
+
}
|
| 1052 |
+
}
|
| 1053 |
+
|
| 1054 |
function applyBaseResponse(resp, options = {}) {
|
| 1055 |
const resetXSelection = Boolean(options.resetXSelection)
|
| 1056 |
const colunaYPadrao = String(resp.coluna_y_padrao || '')
|
|
|
|
| 1240 |
setBaseValue('')
|
| 1241 |
}
|
| 1242 |
|
| 1243 |
+
function aplicarRespostaCarregamento(resp, tipoFonteFallback = 'tabular') {
|
| 1244 |
+
setManualMapError('')
|
| 1245 |
+
setGeoProcessError('')
|
| 1246 |
+
setGeoStatusHtml('')
|
| 1247 |
+
setGeoFalhasHtml('')
|
| 1248 |
+
setGeoCorrecoes([])
|
| 1249 |
+
setElaborador(resp.elaborador || null)
|
| 1250 |
+
setModeloCarregadoInfo(buildLoadedModelInfo(resp))
|
| 1251 |
+
setAvaliadorSelecionado(resp.elaborador?.nome_completo || '')
|
| 1252 |
+
setRequiresSheet(Boolean(resp.requires_sheet))
|
| 1253 |
+
setSheetOptions(resp.sheets || [])
|
| 1254 |
+
setSelectedSheet(resp.sheet_selected || '')
|
| 1255 |
+
|
| 1256 |
+
if (!resp.requires_sheet) {
|
| 1257 |
+
const origemResp = String(resp.tipo || '').toLowerCase()
|
| 1258 |
+
const tipoFonte = origemResp === 'dai'
|
| 1259 |
+
? 'dai'
|
| 1260 |
+
: String(tipoFonteFallback || '').toLowerCase() === 'dai'
|
| 1261 |
+
? 'dai'
|
| 1262 |
+
: 'tabular'
|
| 1263 |
+
setTipoFonteDados(tipoFonte)
|
| 1264 |
+
const resetXSelection = String(resp.tipo || '').toLowerCase() !== 'dai'
|
| 1265 |
+
applyBaseResponse(resp, { resetXSelection })
|
| 1266 |
+
return
|
| 1267 |
+
}
|
| 1268 |
+
|
| 1269 |
+
if (resp.status) {
|
| 1270 |
+
setTipoFonteDados('tabular')
|
| 1271 |
+
setColunaY('')
|
| 1272 |
+
setColunaYDraft('')
|
| 1273 |
+
setSelection(null)
|
| 1274 |
+
setFit(null)
|
| 1275 |
+
setSelectionAppliedSnapshot(buildSelectionSnapshot())
|
| 1276 |
+
setColunasX([])
|
| 1277 |
+
setDicotomicas([])
|
| 1278 |
+
setCodigoAlocado([])
|
| 1279 |
+
setPercentuais([])
|
| 1280 |
+
setTransformacaoY('(x)')
|
| 1281 |
+
setTransformacoesX({})
|
| 1282 |
+
setTransformacoesAplicadas(null)
|
| 1283 |
+
setOrigemTransformacoes(null)
|
| 1284 |
+
setBuscaTransformAppliedSnapshot(buildGrauSnapshot(0, 0))
|
| 1285 |
+
setManualTransformAppliedSnapshot(buildTransformacoesSnapshot('(x)', {}))
|
| 1286 |
+
setColunasDataMercado([])
|
| 1287 |
+
setColunaDataMercadoSugerida('')
|
| 1288 |
+
setColunaDataMercado('')
|
| 1289 |
+
setColunaDataMercadoAplicada('')
|
| 1290 |
+
setPeriodoDadosMercado(null)
|
| 1291 |
+
setPeriodoDadosMercadoPreview(null)
|
| 1292 |
+
setDataMercadoError('')
|
| 1293 |
+
setFiltros(defaultFiltros())
|
| 1294 |
+
setOutliersTexto('')
|
| 1295 |
+
setReincluirTexto('')
|
| 1296 |
+
setOutlierFiltrosAplicadosSnapshot(buildFiltrosSnapshot(defaultFiltros()))
|
| 1297 |
+
setOutlierTextosAplicadosSnapshot(buildOutlierTextSnapshot('', ''))
|
| 1298 |
+
setCamposAvaliacao([])
|
| 1299 |
+
valoresAvaliacaoRef.current = {}
|
| 1300 |
+
setAvaliacaoFormVersion((prev) => prev + 1)
|
| 1301 |
+
setAvaliacaoPendente(false)
|
| 1302 |
+
setResultadoAvaliacaoHtml('')
|
| 1303 |
+
setStatus(resp.status)
|
| 1304 |
+
}
|
| 1305 |
+
}
|
| 1306 |
+
|
| 1307 |
async function onUploadClick(arquivo = null) {
|
| 1308 |
const arquivoUpload = arquivo || uploadedFile
|
| 1309 |
if (!arquivoUpload || !sessionId) return
|
|
|
|
| 1315 |
const uploadEhDai = nomeArquivo.endsWith('.dai')
|
| 1316 |
setTipoFonteDados(uploadEhDai ? 'dai' : 'tabular')
|
| 1317 |
const resp = await api.uploadElaboracaoFile(sessionId, arquivoUpload)
|
| 1318 |
+
aplicarRespostaCarregamento(resp, uploadEhDai ? 'dai' : 'tabular')
|
| 1319 |
+
})
|
| 1320 |
+
}
|
| 1321 |
+
|
| 1322 |
+
async function onCarregarModeloRepositorio() {
|
| 1323 |
+
if (!sessionId || !repoModeloSelecionado) return
|
| 1324 |
+
await withBusy(async () => {
|
| 1325 |
+
setMapaGerado(false)
|
| 1326 |
+
setMapaHtml('')
|
| 1327 |
+
setGeoAuto200(true)
|
| 1328 |
+
setTipoFonteDados('dai')
|
| 1329 |
+
const resp = await api.elaboracaoRepositorioCarregar(sessionId, repoModeloSelecionado)
|
| 1330 |
+
aplicarRespostaCarregamento(resp, 'dai')
|
| 1331 |
+
setUploadedFile(null)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1332 |
})
|
| 1333 |
}
|
| 1334 |
|
|
|
|
| 2060 |
<div className="section1-groups">
|
| 2061 |
<div className="subpanel section1-group">
|
| 2062 |
<h4>Carregar modelo</h4>
|
| 2063 |
+
<div className="row upload-repo-row">
|
| 2064 |
+
<label>Modelo do repositório</label>
|
| 2065 |
+
<select
|
| 2066 |
+
value={repoModeloSelecionado}
|
| 2067 |
+
onChange={(e) => setRepoModeloSelecionado(e.target.value)}
|
| 2068 |
+
disabled={loading || repoModelosLoading || repoModelos.length === 0}
|
| 2069 |
+
>
|
| 2070 |
+
<option value="">
|
| 2071 |
+
{repoModelosLoading ? 'Carregando lista...' : repoModelos.length > 0 ? 'Selecione um modelo' : 'Nenhum modelo disponível'}
|
| 2072 |
+
</option>
|
| 2073 |
+
{repoModelos.map((item) => (
|
| 2074 |
+
<option key={`repo-elab-${item.id}`} value={item.id}>
|
| 2075 |
+
{item.nome_modelo || item.arquivo}
|
| 2076 |
+
</option>
|
| 2077 |
+
))}
|
| 2078 |
+
</select>
|
| 2079 |
+
<div className="row compact upload-repo-actions">
|
| 2080 |
+
<button type="button" onClick={onCarregarModeloRepositorio} disabled={loading || repoModelosLoading || !repoModeloSelecionado}>
|
| 2081 |
+
Carregar do repositório
|
| 2082 |
+
</button>
|
| 2083 |
+
<button type="button" onClick={() => void carregarModelosRepositorio()} disabled={loading || repoModelosLoading}>
|
| 2084 |
+
Atualizar lista
|
| 2085 |
+
</button>
|
| 2086 |
+
</div>
|
| 2087 |
+
{repoFonteModelos ? <div className="section1-empty-hint">{repoFonteModelos}</div> : null}
|
| 2088 |
+
</div>
|
| 2089 |
<div
|
| 2090 |
className={`upload-dropzone${uploadDragOver ? ' is-dragover' : ''}`}
|
| 2091 |
onDragOver={onUploadDropZoneDragOver}
|
frontend/src/components/VisualizacaoTab.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import React, { useRef, useState } from 'react'
|
| 2 |
import { api, downloadBlob } from '../api'
|
| 3 |
import DataTable from './DataTable'
|
| 4 |
import LoadingOverlay from './LoadingOverlay'
|
|
@@ -27,6 +27,10 @@ export default function VisualizacaoTab({ sessionId }) {
|
|
| 27 |
|
| 28 |
const [uploadedFile, setUploadedFile] = useState(null)
|
| 29 |
const [uploadDragOver, setUploadDragOver] = useState(false)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
const [dados, setDados] = useState(null)
|
| 32 |
const [estatisticas, setEstatisticas] = useState(null)
|
|
@@ -129,6 +133,73 @@ export default function VisualizacaoTab({ sessionId }) {
|
|
| 129 |
}
|
| 130 |
}
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
async function onUploadModel(arquivo = null) {
|
| 133 |
const arquivoUpload = arquivo || uploadedFile
|
| 134 |
if (!sessionId || !arquivoUpload) return
|
|
@@ -143,6 +214,20 @@ export default function VisualizacaoTab({ sessionId }) {
|
|
| 143 |
})
|
| 144 |
}
|
| 145 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
function onUploadInputChange(event) {
|
| 147 |
const input = event.target
|
| 148 |
const file = input.files?.[0] ?? null
|
|
@@ -277,6 +362,32 @@ export default function VisualizacaoTab({ sessionId }) {
|
|
| 277 |
return (
|
| 278 |
<div className="tab-content">
|
| 279 |
<SectionBlock step="1" title="Carregar Modelo .dai" subtitle="Carregue o arquivo e o conteúdo será exibido automaticamente.">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
<div
|
| 281 |
className={`upload-dropzone${uploadDragOver ? ' is-dragover' : ''}`}
|
| 282 |
onDragOver={onUploadDropZoneDragOver}
|
|
|
|
| 1 |
+
import React, { useEffect, useRef, useState } from 'react'
|
| 2 |
import { api, downloadBlob } from '../api'
|
| 3 |
import DataTable from './DataTable'
|
| 4 |
import LoadingOverlay from './LoadingOverlay'
|
|
|
|
| 27 |
|
| 28 |
const [uploadedFile, setUploadedFile] = useState(null)
|
| 29 |
const [uploadDragOver, setUploadDragOver] = useState(false)
|
| 30 |
+
const [repoModelos, setRepoModelos] = useState([])
|
| 31 |
+
const [repoModeloSelecionado, setRepoModeloSelecionado] = useState('')
|
| 32 |
+
const [repoModelosLoading, setRepoModelosLoading] = useState(false)
|
| 33 |
+
const [repoFonteModelos, setRepoFonteModelos] = useState('')
|
| 34 |
|
| 35 |
const [dados, setDados] = useState(null)
|
| 36 |
const [estatisticas, setEstatisticas] = useState(null)
|
|
|
|
| 133 |
}
|
| 134 |
}
|
| 135 |
|
| 136 |
+
function formatarFonteRepositorio(fonte) {
|
| 137 |
+
if (!fonte || typeof fonte !== 'object') return ''
|
| 138 |
+
const provider = String(fonte.provider || '').toLowerCase()
|
| 139 |
+
if (provider === 'hf_dataset') {
|
| 140 |
+
const repo = String(fonte.repo_id || '').trim()
|
| 141 |
+
const revision = String(fonte.revision || '').trim()
|
| 142 |
+
const degradado = Boolean(fonte.degraded)
|
| 143 |
+
const sufixo = degradado ? ' (modo contingência)' : ''
|
| 144 |
+
return `Fonte: HF Dataset${repo ? ` (${repo})` : ''}${revision ? ` | revisão ${revision.slice(0, 8)}` : ''}${sufixo}`
|
| 145 |
+
}
|
| 146 |
+
return 'Fonte: pasta local'
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
function aplicarRespostaModelosRepositorio(resp) {
|
| 150 |
+
const modelos = Array.isArray(resp?.modelos) ? resp.modelos : []
|
| 151 |
+
setRepoModelos(modelos)
|
| 152 |
+
setRepoFonteModelos(formatarFonteRepositorio(resp?.fonte || null))
|
| 153 |
+
setRepoModeloSelecionado((prev) => {
|
| 154 |
+
const atual = String(prev || '')
|
| 155 |
+
if (atual && modelos.some((item) => String(item.id) === atual)) return atual
|
| 156 |
+
return String(modelos[0]?.id || '')
|
| 157 |
+
})
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
async function carregarModelosRepositorio() {
|
| 161 |
+
setRepoModelosLoading(true)
|
| 162 |
+
try {
|
| 163 |
+
const resp = await api.visualizacaoRepositorioModelos()
|
| 164 |
+
aplicarRespostaModelosRepositorio(resp)
|
| 165 |
+
} catch (err) {
|
| 166 |
+
setError(err.message || 'Falha ao carregar modelos do repositório.')
|
| 167 |
+
setRepoModelos([])
|
| 168 |
+
setRepoModeloSelecionado('')
|
| 169 |
+
setRepoFonteModelos('')
|
| 170 |
+
} finally {
|
| 171 |
+
setRepoModelosLoading(false)
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
useEffect(() => {
|
| 176 |
+
let ativo = true
|
| 177 |
+
if (!sessionId) return () => {
|
| 178 |
+
ativo = false
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
setRepoModelosLoading(true)
|
| 182 |
+
api.visualizacaoRepositorioModelos()
|
| 183 |
+
.then((resp) => {
|
| 184 |
+
if (!ativo) return
|
| 185 |
+
aplicarRespostaModelosRepositorio(resp)
|
| 186 |
+
})
|
| 187 |
+
.catch(() => {
|
| 188 |
+
if (!ativo) return
|
| 189 |
+
setRepoModelos([])
|
| 190 |
+
setRepoModeloSelecionado('')
|
| 191 |
+
setRepoFonteModelos('')
|
| 192 |
+
})
|
| 193 |
+
.finally(() => {
|
| 194 |
+
if (!ativo) return
|
| 195 |
+
setRepoModelosLoading(false)
|
| 196 |
+
})
|
| 197 |
+
|
| 198 |
+
return () => {
|
| 199 |
+
ativo = false
|
| 200 |
+
}
|
| 201 |
+
}, [sessionId])
|
| 202 |
+
|
| 203 |
async function onUploadModel(arquivo = null) {
|
| 204 |
const arquivoUpload = arquivo || uploadedFile
|
| 205 |
if (!sessionId || !arquivoUpload) return
|
|
|
|
| 214 |
})
|
| 215 |
}
|
| 216 |
|
| 217 |
+
async function onCarregarModeloRepositorio() {
|
| 218 |
+
if (!sessionId || !repoModeloSelecionado) return
|
| 219 |
+
await withBusy(async () => {
|
| 220 |
+
resetConteudoVisualizacao()
|
| 221 |
+
const uploadResp = await api.visualizacaoRepositorioCarregar(sessionId, repoModeloSelecionado)
|
| 222 |
+
setStatus(uploadResp.status || '')
|
| 223 |
+
setBadgeHtml(uploadResp.badge_html || '')
|
| 224 |
+
|
| 225 |
+
const exibirResp = await api.exibirVisualizacao(sessionId)
|
| 226 |
+
applyExibicaoResponse(exibirResp)
|
| 227 |
+
setUploadedFile(null)
|
| 228 |
+
})
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
function onUploadInputChange(event) {
|
| 232 |
const input = event.target
|
| 233 |
const file = input.files?.[0] ?? null
|
|
|
|
| 362 |
return (
|
| 363 |
<div className="tab-content">
|
| 364 |
<SectionBlock step="1" title="Carregar Modelo .dai" subtitle="Carregue o arquivo e o conteúdo será exibido automaticamente.">
|
| 365 |
+
<div className="row upload-repo-row">
|
| 366 |
+
<label>Modelo do repositório</label>
|
| 367 |
+
<select
|
| 368 |
+
value={repoModeloSelecionado}
|
| 369 |
+
onChange={(e) => setRepoModeloSelecionado(e.target.value)}
|
| 370 |
+
disabled={loading || repoModelosLoading || repoModelos.length === 0}
|
| 371 |
+
>
|
| 372 |
+
<option value="">
|
| 373 |
+
{repoModelosLoading ? 'Carregando lista...' : repoModelos.length > 0 ? 'Selecione um modelo' : 'Nenhum modelo disponível'}
|
| 374 |
+
</option>
|
| 375 |
+
{repoModelos.map((item) => (
|
| 376 |
+
<option key={`repo-viz-${item.id}`} value={item.id}>
|
| 377 |
+
{item.nome_modelo || item.arquivo}
|
| 378 |
+
</option>
|
| 379 |
+
))}
|
| 380 |
+
</select>
|
| 381 |
+
<div className="row compact upload-repo-actions">
|
| 382 |
+
<button type="button" onClick={onCarregarModeloRepositorio} disabled={loading || repoModelosLoading || !repoModeloSelecionado}>
|
| 383 |
+
Carregar do repositório
|
| 384 |
+
</button>
|
| 385 |
+
<button type="button" onClick={() => void carregarModelosRepositorio()} disabled={loading || repoModelosLoading}>
|
| 386 |
+
Atualizar lista
|
| 387 |
+
</button>
|
| 388 |
+
</div>
|
| 389 |
+
{repoFonteModelos ? <div className="section1-empty-hint">{repoFonteModelos}</div> : null}
|
| 390 |
+
</div>
|
| 391 |
<div
|
| 392 |
className={`upload-dropzone${uploadDragOver ? ' is-dragover' : ''}`}
|
| 393 |
onDragOver={onUploadDropZoneDragOver}
|
frontend/src/styles.css
CHANGED
|
@@ -1345,6 +1345,14 @@ button:disabled {
|
|
| 1345 |
transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
|
| 1346 |
}
|
| 1347 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1348 |
.upload-dropzone.is-dragover {
|
| 1349 |
border-color: #3b7fb8;
|
| 1350 |
background: linear-gradient(180deg, #f3f9ff 0%, #ebf4ff 100%);
|
|
|
|
| 1345 |
transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
|
| 1346 |
}
|
| 1347 |
|
| 1348 |
+
.upload-repo-row {
|
| 1349 |
+
margin-bottom: 10px;
|
| 1350 |
+
}
|
| 1351 |
+
|
| 1352 |
+
.upload-repo-actions {
|
| 1353 |
+
margin-top: 2px;
|
| 1354 |
+
}
|
| 1355 |
+
|
| 1356 |
.upload-dropzone.is-dragover {
|
| 1357 |
border-color: #3b7fb8;
|
| 1358 |
background: linear-gradient(180deg, #f3f9ff 0%, #ebf4ff 100%);
|