Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
88736cd
Merge dev into main for v1.1.12 (#57)
jansaldo Nov 18, 2025
4d9f99a
Merge branch 'main' of github.com:AymurAI/backend into dev
jansaldo Nov 18, 2025
e438a02
Merge commit 'b9dc26d81479d8812fdb028fcf72f0377c8a3beb' as 'frontend'
jansaldo Apr 24, 2026
b9dc26d
Squashed 'frontend/' content from commit 9123e6f
jansaldo Apr 24, 2026
7b25ce6
Merge branch 'release/v1.5.0' of github.com:AymurAI/backend into feat…
jansaldo May 4, 2026
835cb1b
Merge commit '47cc0eaab36ce97101255c1bc66d32c7185a16f9' into feature/…
jansaldo May 4, 2026
47cc0ea
Squashed 'frontend/' changes from 9123e6ff..8add5c45
jansaldo May 4, 2026
6f6aaf2
✨ Add frontend routing and settings for frontend distribution directory
jansaldo May 6, 2026
29d6c6c
Merge branch 'dev' of github.com:AymurAI/backend into feature/fronten…
jansaldo May 12, 2026
f4bbb81
Merge commit 'dbf500bc05f53d34fc0f61d1e55ca6566d49e1db' into feature/…
jansaldo May 12, 2026
dbf500b
Squashed 'frontend/' changes from 8add5c45..ff882164
jansaldo May 12, 2026
c7b2db2
🔧 Remove commented-out router for anonymizer database
jansaldo May 15, 2026
b48f256
✨ Add Node.js and npm installation for frontend build in Dockerfile
jansaldo May 15, 2026
e9fc2a5
📝 Update API documentation URLs to include '/api' prefix
jansaldo May 15, 2026
e1622f5
✨ Add frontend build commands to Makefile
jansaldo May 15, 2026
b5a08e4
🙈 Update .dockerignore and .gitignore to include frontend build outpu…
jansaldo May 15, 2026
1d508e1
✅ Update API routes to include '/api' prefix in tests and add fronten…
jansaldo May 15, 2026
db3ca77
♻️ Refactor routing and API integration to remove '/app' prefix and s…
jansaldo May 15, 2026
e0d36a2
Squashed 'frontend/' changes from ff882164..d3e14b5e
dmazzini May 19, 2026
44c1ae4
Merge commit 'e0d36a229272d72e96780a15bb30222b7fd476e9' into feature/…
dmazzini May 19, 2026
eb143d8
refactor: update feature routing, migrate to pnpm, and refine dev env…
dmazzini May 20, 2026
063a724
Squashed 'frontend/' changes from d3e14b5e..879309c8
dmazzini May 22, 2026
dc4a659
Merge commit '063a724c248a0b7e5392e0e6b9b0ba6c3c94a270' into feature/…
dmazzini May 22, 2026
d0c80b5
Squashed 'frontend/' changes from 879309c8..a37adc20
dmazzini May 26, 2026
dd36408
Merge commit 'd0c80b57a19eed8e4e98569e0f9b06126fbc97b4' into feature/…
dmazzini May 26, 2026
a6f8af4
Merge branch 'release/v1.5.0' of github.com:AymurAI/backend into feat…
dmazzini May 26, 2026
47b8783
Merge branch 'release/v1.5.0' of github.com:AymurAI/backend into feat…
jansaldo May 27, 2026
3f92641
chore: migrate frontend build process from npm to pnpm in API Dockerfile
dmazzini May 27, 2026
1841795
Merge branch 'feature/frontend-integration' of github.com:AymurAI/bac…
jansaldo May 27, 2026
19dafb6
🐛 fix: add support for numpy integer and floating types in EnhancedJS…
jansaldo May 27, 2026
d8176a8
Merge branch 'release/v1.5.0' of github.com:AymurAI/backend into feat…
jansaldo May 27, 2026
d7551da
Merge branch 'release/v1.5.0' of github.com:AymurAI/backend into feat…
dmazzini May 28, 2026
48bef63
Merge branch 'release/v1.5.0' of https://github.com/AymurAI/backend i…
jansaldo Jun 2, 2026
61722eb
Merge branch 'feature/frontend-integration' of https://github.com/Aym…
jansaldo Jun 2, 2026
1b3df73
fix: update Stack component to use height instead of minHeight for co…
jansaldo Jun 3, 2026
6829025
fix: update imports for Label and Text components in UncontrolledInpu…
jansaldo Jun 3, 2026
37493d4
chore: regenerate routeTree.gen.ts after removing $feature parent lay…
jansaldo Jun 3, 2026
60ac412
feat: add default anonymization policies to settings
jansaldo Jun 3, 2026
32d7cef
chore: bump frontend version to 1.5.0
jansaldo Jun 3, 2026
995a5ed
fix(api): preserve pipeline cache for configured ttl
jansaldo Jun 3, 2026
27cec37
refactor: remove torch dependency and configure threads via settings
jansaldo Jun 3, 2026
7a0d559
fix(frontend): replace previous anonymizer file on load
jansaldo Jun 3, 2026
066439e
Merge branch 'feature/frontend-integration' of github.com:AymurAI/bac…
jansaldo Jun 3, 2026
ba253dd
fix(frontend): support dataset export in web mode
jansaldo Jun 3, 2026
605915a
fix(tests): add SQLALCHEMY_DATABASE_URI environment variable for api …
jansaldo Jun 10, 2026
ec07878
fix(api): improve error logging during startup
jansaldo Jun 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 5 additions & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ RUN apt update && apt install -y \
&& rm -rf /tmp/* /var/tmp/* \
&& rm -rf /var/lib/apt/lists/*


# Node.js & npm for frontend build
RUN apt-get update && \
apt-get install -y curl && \
curl -fsSL https://deb.nodesource.com/setup_24.x | bash - && \
apt-get install -y nodejs

WORKDIR /tmp

Expand Down
7 changes: 5 additions & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ resources/
!resources/api
!resources/pipelines
!resources/cache/aymurai
!resources/cache/tfhub_modules
notebooks/

*.egg-info/
*.egg-info/

frontend/node_modules/
frontend/out/
frontend-dist/
1 change: 1 addition & 0 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ jobs:
- name: Run api tests
env:
DISKCACHE_ROOT: /tmp
SQLALCHEMY_DATABASE_URI: sqlite:////tmp/aymurai-test.db
run: uv run --no-sync pytest -q --tb=short --disable-warnings --color=yes --maxfail=5 tests/api

- name: Download pipelines data
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,9 @@ aymurai/version.py
notebooks/**
!notebooks/**/
!notebooks/**/*.ipynb

# Frontend build output
frontend-dist/

# pnpm local package store
.pnpm-store/
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,19 @@ alembic-regenerate:
cd aymurai && \
uv run alembic revision --autogenerate -m "Create database" && \
uv run alembic upgrade head

# --- Frontend build ---
FRONTEND_DIR=frontend
FRONTEND_DIST_DIR=frontend-dist

frontend-install:
cd $(FRONTEND_DIR) && pnpm install

frontend-build: frontend-install
cd $(FRONTEND_DIR) && pnpm run build:web
# Copy build output to frontend-dist
rm -rf $(FRONTEND_DIST_DIR)
cp -r $(FRONTEND_DIR)/out/renderer $(FRONTEND_DIST_DIR)

frontend-clean:
rm -rf $(FRONTEND_DIST_DIR)
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ docker run -d --name aymurai-backend-gpu --gpus all \
Open Swagger UI:

```text
http://localhost:8899/docs
http://localhost:8899/api/docs
```

Open the bundled frontend:

```text
http://localhost:8899/
```

## Quick Start (Docker Compose)
Expand Down Expand Up @@ -80,23 +86,24 @@ make api-logs
## Runtime Overview
- Framework: `FastAPI`
- Default API port: `8899`
- Bundled frontend path: `GET /`
- DB engine: `SQLModel` + Alembic migrations on startup
- Default DB URI: `sqlite:////resources/cache/sqlite/database.db`
- Production pipeline configs:
- `resources/pipelines/production/flair-anonymizer/pipeline.json`
- `resources/pipelines/production/datapublic/pipeline.json`

## Main Public Endpoints
- `GET /server/healthcheck`
- `GET /server/stats/summary`
- `POST /misc/document-extract` (and deprecated alias `POST /document-extract`)
- `POST /anonymizer/predict`
- `POST /anonymizer/disambiguate`
- `POST /anonymizer/validation`
- `POST /anonymizer/anonymize-document`
- `POST /datapublic/predict/{document_id}`
- `GET /datapublic/validation/document/{document_id}`
- `POST /datapublic/validation/document/{document_id}`
- `GET /api/server/healthcheck`
- `GET /api/server/stats/summary`
- `POST /api/misc/document-extract` (and deprecated alias `POST /api/document-extract`)
- `POST /api/anonymizer/predict`
- `POST /api/anonymizer/disambiguate`
- `POST /api/anonymizer/validation`
- `POST /api/anonymizer/anonymize-document`
- `POST /api/datapublic/predict/{document_id}`
- `GET /api/datapublic/validation/document/{document_id}`
- `POST /api/datapublic/validation/document/{document_id}`

For full request/response contracts and examples, see [docs/api/README.md](docs/api/README.md).

Expand Down
7 changes: 0 additions & 7 deletions aymurai/api/core.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
from fastapi.routing import APIRouter

from .endpoints.routers.anonymizer import anonymizer
from .endpoints.routers.anonymizer import database as anonymizer_database
from .endpoints.routers.datapublic import datapublic

from .endpoints.routers.misc import convert, document_extract
from .endpoints.routers.server import stats

Expand All @@ -23,11 +21,6 @@
prefix="/anonymizer",
tags=["anonymization/model"],
)
# router.include_router(
# anonymizer_database.router,
# prefix="/anonymizer/database",
# tags=["anonymization/database"],
# )

# Datapublic
router.include_router(
Expand Down
9 changes: 2 additions & 7 deletions aymurai/api/endpoints/routers/anonymizer/anonymizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from collections.abc import Iterable
from threading import Lock

import torch
from fastapi import Body, Depends, Form, HTTPException, Query, UploadFile
from fastapi.responses import FileResponse
from fastapi.routing import APIRouter
Expand All @@ -31,7 +30,7 @@
RenderPolicy,
TextRequest,
)
from aymurai.settings import settings
from aymurai.settings import DEFAULT_RENDER_POLICY, settings
from aymurai.text.anonymization import (
InvalidDocumentAnonymizer,
get_anonymizer,
Expand All @@ -48,7 +47,6 @@


RESOURCES_BASEPATH = settings.RESOURCES_BASEPATH
torch.set_num_threads = 100 # FIXME: polemic ?
pipeline_lock = Lock()


Expand Down Expand Up @@ -198,10 +196,7 @@ def _merge_render_policy(
Returns:
RenderPolicy: Effective render policy.
"""
policy = RenderPolicy(
suffix_mode="auto",
suffix_threshold=1,
)
policy = RenderPolicy.model_validate(DEFAULT_RENDER_POLICY)

def apply(incoming: RenderPolicy) -> None:
nonlocal policy
Expand Down
2 changes: 0 additions & 2 deletions aymurai/api/endpoints/routers/datapublic/datapublic.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import os
from threading import Lock

import torch
from fastapi import Body, Depends, HTTPException, Query
from fastapi.routing import APIRouter
from pydantic import UUID5
Expand All @@ -28,7 +27,6 @@


RESOURCES_BASEPATH = settings.RESOURCES_BASEPATH
torch.set_num_threads = 100 # FIXME: polemic ?
pipeline_lock = Lock()


Expand Down
1 change: 1 addition & 0 deletions aymurai/api/endpoints/routers/frontend/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

114 changes: 114 additions & 0 deletions aymurai/api/endpoints/routers/frontend/frontend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from pathlib import Path

from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse, RedirectResponse, Response

from aymurai.settings import settings

router = APIRouter(include_in_schema=False)

LEGACY_API_ROUTE_PREFIXES = (
"anonymizer",
"api",
"convert",
"datapublic",
"database",
"document-extract",
"docs",
"misc",
"openapi.json",
"redoc",
"server",
)
FEATURE_ROUTE_SLUGS = ("anonymizer", "data_set")
FEATURE_ROUTE_STEPS = ("", "finish", "onboarding", "preview", "process", "validation")
LEGACY_FEATURE_ROUTE_SLUGS = {
"ANONYMIZER": "anonymizer",
"DATA_SET": "data_set",
}


def _frontend_dist_dir() -> Path:
return Path(settings.FRONTEND_DIST_DIR).expanduser()


def _split_asset_path(asset_path: str) -> list[str]:
return [segment for segment in asset_path.lstrip("/").split("/") if segment]


def _is_frontend_feature_route(asset_path: str) -> bool:
segments = _split_asset_path(asset_path)
if not segments or segments[0] not in FEATURE_ROUTE_SLUGS:
return False

return len(segments) == 1 or segments[1] in FEATURE_ROUTE_STEPS


def _is_legacy_api_route(asset_path: str) -> bool:
first_segment = asset_path.lstrip("/").split("/", maxsplit=1)[0]
if _is_frontend_feature_route(asset_path):
return False
return first_segment in LEGACY_API_ROUTE_PREFIXES


def _canonical_frontend_path(asset_path: str) -> str | None:
segments = _split_asset_path(asset_path)
if not segments:
return None

if segments[0] == "app":
canonical_segments = segments[1:]
if canonical_segments:
canonical_feature = LEGACY_FEATURE_ROUTE_SLUGS.get(canonical_segments[0])
if canonical_feature:
canonical_segments = [canonical_feature, *canonical_segments[1:]]
return "/" + "/".join(canonical_segments)

canonical_feature = LEGACY_FEATURE_ROUTE_SLUGS.get(segments[0])
if canonical_feature:
return "/" + "/".join([canonical_feature, *segments[1:]])

return None


def _resolve_frontend_path(asset_path: str) -> Path:
dist_dir = _frontend_dist_dir().resolve()
requested_path = (dist_dir / asset_path.lstrip("/")).resolve()
requested_path.relative_to(dist_dir)
return requested_path


def _serve_frontend(asset_path: str = "") -> Response:
canonical_path = _canonical_frontend_path(asset_path)
if canonical_path is not None:
return RedirectResponse(canonical_path or "/")

if asset_path and _is_legacy_api_route(asset_path):
raise HTTPException(status_code=404, detail="API route not found.")

dist_dir = _frontend_dist_dir()
index_file = dist_dir / "index.html"
if not dist_dir.is_dir() or not index_file.is_file():
raise HTTPException(status_code=404, detail="Frontend build not found.")

if asset_path:
try:
requested_path = _resolve_frontend_path(asset_path)
except ValueError as error:
raise HTTPException(
status_code=404, detail="Frontend asset not found."
) from error

if requested_path.is_file():
return FileResponse(requested_path)

if Path(asset_path).suffix:
raise HTTPException(status_code=404, detail="Frontend asset not found.")

return FileResponse(index_file)


@router.get("/")
@router.get("/{asset_path:path}")
async def frontend_app(asset_path: str = "") -> Response:
return _serve_frontend(asset_path)
37 changes: 14 additions & 23 deletions aymurai/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@
import time
from contextlib import asynccontextmanager

import torch
from alembic import command
from alembic.config import Config
from fastapi import FastAPI, Request
from fastapi.responses import RedirectResponse
from fastapi.middleware.cors import CORSMiddleware
from starlette.formparsers import MultiPartParser

from aymurai.api import core
from aymurai.api.endpoints.routers.frontend import frontend
from aymurai.api.startup.database import check_db_connection
from aymurai.api.utils import configure_torch_threads
from aymurai.logger import get_logger
from aymurai.settings import settings
from aymurai.pipeline import AymurAIPipeline
from aymurai.api.startup.database import check_db_connection
from aymurai.settings import settings

try:
from aymurai.version import __version__
Expand All @@ -24,7 +24,7 @@
logger = get_logger(__name__)


torch.set_num_threads = 100 # FIXME: polemic ?
configure_torch_threads()

RESOURCES_BASEPATH = settings.RESOURCES_BASEPATH

Expand Down Expand Up @@ -54,8 +54,9 @@ async def lifespan(app: FastAPI):
logger.info(">> Running Alembic migrations")
alembic_cfg = Config(str(settings.ALEMBIC_INI_PATH))
command.upgrade(alembic_cfg, "head")
except Exception as error:
logger.error("Error while starting up:", error)
except Exception:
logger.exception("Error while starting up")
raise

yield

Expand All @@ -64,6 +65,9 @@ async def lifespan(app: FastAPI):
title="AymurAI API",
version=__version__,
lifespan=lifespan,
docs_url="/api/docs",
redoc_url="/api/redoc",
openapi_url="/api/openapi.json",
)


Expand All @@ -90,33 +94,20 @@ async def add_process_time_header(request: Request, call_next):
return response


@api.get("/", response_class=RedirectResponse, include_in_schema=False)
async def index():
return "/docs"


# @api.get("/docs", include_in_schema=False)
# async def custom_swagger_ui_html():
# return get_swagger_ui_html(
# openapi_url=api.openapi_url,
# title=f"{api.title} - Swagger UI",
# swagger_css_url="https://cdn.jsdelivr.net/gh/danielperezrubio/swagger-dark-theme@main/assets/swagger-ui.min.css",
# )


################################################################################
# MARK: API ENDPOINTS
################################################################################


# Healthcheck
@api.get("/server/healthcheck", status_code=200, tags=["server"])
@api.get("/api/server/healthcheck", status_code=200, tags=["server"])
def healthcheck():
return {"status": "ok"}


# Api endpoints
api.include_router(core.router)
api.include_router(core.router, prefix="/api")
api.include_router(frontend.router)


if __name__ == "__main__":
Expand Down
Loading
Loading