') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - KatherLab/llmaixweb: A web interface for the LLMAIx framework. Information Extraction. · GitHub
Skip to content

Latest commit

History

449 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

TestsDocsLicense: AGPL v3

Important

This application is a research prototype. It may not run stably and extracted results can be inaccurate. Always check outputs for validity before using them in practice.

LLMAIx (v2) Web

cover.png

A web application that turns unstructured medical/lab documents into structured JSON using LLMs. Upload PDFs, images, or spreadsheets — extract data with configurable schemas and prompts, then evaluate results against ground truth.

Works with any OpenAI-compatible API: use official services (OpenAI, Mistral OCR) for convenience, or run everything fully local with self-hosted models (DeepSeek-OCR-2 via KatDocExtract, vision LLMs like Gemma 4 via vLLM) for sensitive environments.

eval_single_doc.png

📖 Full documentation → katherlab.github.io/llmaixweb Installation, a per-page user guide, an operator handbook, and the developer reference.


Features

  • Upload & organize — PDF, DOC/DOCX, images, CSV/XLSX, TXT files with column selection and previews.
  • Preprocessing & OCR — four extraction engines to choose from (see OCR engines).
  • Visual schema editor — tree-based JSON schema editor with nested objects, arrays, all JSON types, import/export, and validation.
  • LLM trials — run extraction trials across different prompts, schemas, and models. Temperature control, token tracking, batch execution. Works with any OpenAI-compatible endpoint.
  • Evaluation — upload ground truth CSVs, compare field-by-field, compute per-field and overall accuracy metrics.
  • Privacy-first — run fully local or with self-hosted providers. No forced external calls.
  • Admin dashboard — user management (invitations, roles), provider configuration, Celery monitoring.
  • Authentication & SSO — email/password login with account lockout, refresh tokens, and optional OpenID Connect single sign-on (Google, Keycloak, Azure AD, …) with just-in-time user provisioning.

Tech stack: Vue 3 + Vite + TailwindCSS (frontend), FastAPI (backend), SQLAlchemy, Celery, Pydantic for configuration.


Screenshots

The full end-to-end workflow — from raw files to a scored extraction — using the eight fictitious lung-embolism reports that ship with the repo. Walk through it step by step in the quickstart.

Files & Preprocessing tab with uploaded files

1. Upload & preprocess
Bring in PDFs, images, and spreadsheets; extract text with your chosen OCR engine.

Document viewer with extracted text and metadata

2. Review documents
Inspect the extracted text and its provenance before running a trial.

Start New Trial dialog

3. Run a trial
Pick a schema, prompt, model, and documents — against any OpenAI-compatible endpoint.

Trial results: source report next to extracted JSON

4. Inspect extractions
See each source report next to the structured JSON the model produced.

Ground truth field mapping dialog

5. Map ground truth
Pair schema fields with known-correct columns and choose a comparison method per field.

Evaluation analysis with per-field accuracy

6. Evaluate
Overall and per-field accuracy, per-document breakdowns, and confusion matrices.


Quick start

git clone https://github.com/KatherLab/llmaixweb
cd llmaixweb
cp .env.example .env
# Edit .env — at minimum SECRET_KEY (OPENAI_API_* are optional)
docker compose up -d

Open http://localhost:5173 and create an admin account on first visit.

For OCR engines, compose overlays, environment variables, and self-hosted setups, see the Installation guide.


Documentation

GuideLink
Quickstart walkthrough — CSV of 8 reports → evaluated resultsdocs
User guide — a page per tab (Files → Preprocessing → Documents → Schemas → Trials → Evaluation)docs
Administration — users, SSO, settings, task monitoringdocs
Operations — deployment, configuration, upgrading, backups, troubleshootingdocs
Security & governance — threat model, data flow, retention, DPIAdocs
Development — contributing, developer guide, architecturedocs

The long-form guides also live in-repo: USAGE.md, DEPLOY.md, DEVELOPER.md, and AGENTS.md.


Security & privacy

  • Keep PHI strictly local unless you explicitly configure a remote provider.
  • Prefer self-hosted, OpenAI-compatible endpoints for clinical data.
  • Review your .env secrets and never commit them.
  • Report vulnerabilities privately — see SECURITY.md.

Not a certified medical device. The deploying institution is the data controller; see the security & governance docs.


License

AGPL-3.0 — see LICENSE. Third-party components are listed in THIRD_PARTY_NOTICES.md.

About

A web interface for the LLMAIx framework. Information Extraction.

Resources

Security policy

Stars

22 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages