Skip to content

okforge/okforge-webui

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

59 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

okforge-webui

A LAN web UI and job runner for okforge knowledge bases: drop a scanned book in an inbox and drive it through VLM OCR, optional translation, and ingestion into an LLM-synthesized wiki — then browse, query, and publish the result. Built for local-first setups where the LLM is your own llama.cpp/vLLM box, not a cloud API.

The pipeline is five screens: probe (inspect the PDF: text layer? language? page count) → pilot (OCR a page or three, check the transcription and image crops before committing) → project (pick or name the project that collects the output) → run (chunked OCR → translate → markdown with live progress) → verify & use (review the markdown, ingest it into the project's knowledge base, then query and publish). OCR and ingestion are always separate steps, so the same tool doubles as a pure PDF→markdown converter — skip the ingest and take the files from md-out/.

Inputs: PDFs, page-scan images (jpg/png/tif/bmp — wrapped into PDF on upload so the OCR pipeline handles them), and your own markdown/text documents (added straight to a project, no OCR). Selecting several PDFs/images at once combines them into one PDF in natural file-name order — the upload shows the exact order and combined name before anything is sent. Other formats (docx, pptx, html …) should be pre-converted to markdown first.

okforge-webui with the Dade County Building Code (1935) knowledge base loaded: the five workflow stages, job queue, KB stats, and wiki browser

What's in the box

  • Serial job queue (sqlite + one worker, on purpose): one add or OCR run at a time protects single-slot LLM hosts and the engine's per-KB ingest lock. Jobs survive backend restarts; every finished job keeps its log. One-click resume/retry, a stall watchdog (flags, never kills), per-chunk ETA from real history, and a git pre-ingest snapshot of the KB before every add.
  • Markdown first, ingest second: every run OCRs into the project's md-out/<name>/ folder (chunked .md + page maps + image crops); hand-made markdown/text files can be added to the same folder from the UI ("Add markdown…" — no OCR involved); ingesting that markdown into the knowledge base is a separate step — a one-click button in the verify stage (KB stats update chunk by chunk) or an auto-ingest toggle on the run. The KB is created on first ingest — or never, if all you wanted was the markdown.
  • Archive-first deletes: removing an uploaded PDF, a project's markdown, a published site, or a whole project moves it to trash/ (KBs retire to kbs-retired/) — nothing in the UI is destructive, restore is a mv back.
  • OCR + image extraction via okforge-vision-ocr (one VLM call per page: markdown transcription + photo bounding boxes together), with a table mode for pages the fast path mangles and a per-page re-OCR + re-ingest repair loop.
  • Translation workflow for non-English scans: faithful transcription first, page-by-page translation second — both language versions share one image directory and page citations survive.
  • Wiki browser with lexical search (source hits carry real page numbers), image lightbox, and markdown rendering.
  • MCP server at /mcp (streamable HTTP): list_projects, project_status, ask, search, read_wiki_page. Connect any MCP client, e.g. claude mcp add --transport http okforge http://<host>/mcp. Clients that don't surface MCP server instructions (Open-WebUI and other OpenAPI-bridged clients) should get the recommended system prompt from docs/MCP_CLIENT_PROMPT.md.
  • Static-site publishing per KB via Quartz — full-text search, graph view, backlinks — one button, then a printed rsync command to go public.

Prerequisites

  • An OpenAI-compatible LLM endpoint — the whole pipeline's brain (llama.cpp or vLLM on your own hardware, or a hosted service; see Configuration). For the OCR path the endpoint's model must be vision-capable (Qwen-VL family or similar) — it reads page images. Text-layer extraction, ingestion, and querying work with any capable chat model.
  • Python 3.10+
  • Git — for the clone; on Windows it also provides the grep binary the engine's query agent uses.
  • Node.js 18+ — only for the optional static-site publishing (Quartz); everything else runs without it. Quartz is a one-time, once-per-machine install into the shared quartz dir (<base>/quartz, or OKFORGE_WEBUI_QUARTZ_DIR) — clone it, check out v5, npm ci, then npx quartz plugin install; the exact steps are in OPERATIONS. Skipping the plugin install step is the usual first-publish failure: the build aborts with Could not resolve "../../.quartz/plugins", which is exactly the dir that step generates. On a LAN-only / offline box, also disable the default og-image emitter in quartz.config.ts (it fetches a font over the network at build time and otherwise fails); nothing else in Quartz needs outbound access.

Directory layout

<base>/                 e.g. /opt/okforge/  or  C:\okforge\
    okforge-webui/      ← this repo; .venv/ inside it
    kbs/<Subject>/      ← one self-contained knowledge base per subject
    inbox/              ← PDF drop point for the web UI
    md-out/<Project>/   ← OCR'd markdown per project (created on demand)
    kbs-retired/        ← retired KBs (archive-first "delete")
    trash/              ← web-UI deletes move things here, never erase
    quartz/             ← shared Quartz install (site publishing, optional)
    sites/<Subject>/    ← published static sites (optional)

The base directory can be anywhere — all defaults are relative to where this repo sits (each is individually overridable by env var, see Configuration). Every KB is self-contained (sources, wiki, engine state, .env, its own git history) — copy the directory and you've copied the KB. The UI discovers KBs by scanning the KB root; nothing is registered anywhere else.

Install

Linux/macOS:

cd /opt/okforge   # or any base dir
git clone https://github.com/okforge/okforge-webui
cd okforge-webui
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
mkdir -p ../kbs ../inbox

Windows (PowerShell):

cd C:\okforge     # or any base dir
git clone https://github.com/okforge/okforge-webui
cd okforge-webui
python -m venv .venv
.venv\Scripts\pip install -r requirements.txt
md ..\kbs, ..\inbox

(PowerShell gotcha: anything in the current directory needs a .\ prefix to run — .\script.ps1, not script.ps1. The .venv\Scripts\… forms above already qualify.)

requirements.txt pins the two okforge packages from PyPI — the okforge engine (ingestion, wiki compilation, query; see its GETTING_STARTED) and okforge-vision-ocr (pre-conversion console scripts) — plus the FastAPI backend's own dependencies.

Run it

One process serves the frontend, the API, and the MCP server. Run it from this repo's directorypython -m webui resolves the webui package relative to the current dir, so from anywhere else Python exits with No module named webui:

cd <base>/okforge-webui
.venv/bin/python -m webui          # Linux/macOS
.venv\Scripts\python -m webui      # Windows
# browse http://<host>:8500/

OKFORGE_WEBUI_HOST / OKFORGE_WEBUI_PORT change the bind (default 0.0.0.0:8500 — LAN-visible; use 127.0.0.1 to keep it local). Same trust model in every mode: LAN-only, no auth — don't expose it beyond a network you trust.

First time? Follow the small-test walkthrough below before pointing it at a whole book.

To run it as a service: on Linux, webui/deploy/okforge-webui-standalone.service is a ready-to-edit systemd unit; on Windows, use Task Scheduler ("At startup", run <repo>\.venv\Scripts\python.exe -m webui) or NSSM.

Optional: Apache in front (Linux)

For port 80, a LAN vhost name, and an easy basic-auth option, deploy.sh installs Apache (static docroot + /api/ reverse proxy) in front of the same backend under systemd:

SERVER_NAME=okforge.local OKFORGE_WEBUI_ENDPOINTS="gpu1=http://gpu1:8080/v1" \
    webui/deploy.sh

deploy.sh is idempotent — rerun it after changes (it restarts the backend, so never while a job is running). In this mode frontend files are served by Apache, so frontend-only changes are sudo rsync -a --delete webui/static/ /var/www/okforge-webui/ (standalone mode serves them straight from the repo — nothing to copy).

First run — start with a small test

Prove the whole loop — endpoint, OCR quality, ingest, query — on a handful of pages before committing to a book. Five pages take about ten minutes on a local GPU; a 300-page book is an overnight-plus run (see ingest cost). Everything below happens in the browser at http://<host>:8500/.

  1. Check the header. Pick your LLM server in the dropdown; the status light beside it polls the server, so a steady light means you're actually talking to it. This choice gets baked into the knowledge base at first ingest (queries and MCP clients then use it too — changeable later).
  2. Stage 1 — get a document in. Upload a short PDF — or a few phone photos of pages, which combine into one PDF (the panel shows the page order before anything uploads; it comes from the file names). The probe runs automatically: scan means the OCR pipeline (the normal path), text means an embedded text layer you can optionally trust in stage 4.
  3. Stage 2 — pilot one page. Enter one page number with real content on it (not the cover) and Run pilot. Read the transcription beside the rendered page; check the image crops. Bad OCR here means bad OCR everywhere, so fix it now — table mode for complex tables, --figures if line drawings were missed, or an OCR hint ("ignore marginalia"). Re-run until the page reads right.
  4. Stage 3 — create a project. Use a throwaway name like MyBook-test — you'll delete it after the test (one click, and nothing is ever erased — it all moves to trash/).
  5. Stage 4 — run a small range. Set From page / to to a few content pages, tick ingest into KB when OCR finishes, and Start run. The queue shows one plain-language row ("working — n/m chunks OCR'd"; ▸ expands the technical steps) and markdown appears in stage 5 as chunks finish.
  6. Stage 5 — verify and ask. Read the markdown. Watch the knowledge-base stats tick up as chunks ingest; a one-line project description is written automatically at the end, and Publish unlocks when the last chunk is in. Then ask the knowledge base a question — answers cite source pages as (p. N).
  7. Happy? Delete the test and run for real. Delete project… in stage 3, then repeat with the real project name and the full page range. (If you'd rather keep the test: make its range exactly the first chunk — e.g. pages 1–20 at the default 20 pages per chunk — and the full run will skip it instead of re-OCRing it.)

What a small test catches early: a wrong endpoint or non-vision model (pilot fails or returns junk), OCR quirks your document needs hints for, and a misconfigured model paying a hidden reasoning block on every call — a 20-page chunk should ingest in a couple of minutes on a local 27B model, not 27 (see the llm_extra_body note below).

Configuration

Everything is an environment variable (for the systemd deployments, set them in the unit — deploy.sh passes any that are exported when it runs). <base> below means the directory this repo sits in.

Note: these use the OKFORGE_WEBUI_* prefix. The pre-rebrand OPENKB_WEBUI_* names (and OPENKB_DIR for the engine dir) still work — the backend reads the new name first and falls back to the old one, printing a one-time deprecation line on stderr naming what to rename.

variable default meaning
OKFORGE_WEBUI_HOST 0.0.0.0 bind address (python -m webui)
OKFORGE_WEBUI_PORT 8500 bind port (python -m webui)
OKFORGE_WEBUI_ENDPOINTS local=http://localhost:8080/v1 LLM endpoints for the UI dropdown, comma-separated label=url[|key[|model]] — key and model only for hosted services (see below)
OKFORGE_WEBUI_DEFAULT_ENDPOINT first label pre-selected endpoint
OKFORGE_WEBUI_MODEL openai/Qwen3.6-27B-MTP model string new KBs are initialized with (per-endpoint model overrides it)
OKFORGE_WEBUI_KB_ROOT <base>/kbs where KBs live
OKFORGE_WEBUI_INBOX <base>/inbox PDF drop dir
OKFORGE_WEBUI_MD_OUT <base>/md-out per-project OCR'd markdown
OKFORGE_WEBUI_RETIRED_DIR <base>/kbs-retired where retired KBs move
OKFORGE_WEBUI_TRASH <base>/trash where web-UI deletes move things
OKFORGE_WEBUI_QUARTZ_DIR <base>/quartz shared Quartz install
OKFORGE_WEBUI_SITES_DIR <base>/sites published-site output
OKFORGE_WEBUI_PUBLIC_SITE_HOST localhost public host for published sites' baseUrl
OKFORGE_WEBUI_PUBLIC_SITE_DEST user@host:/var/www/sites rsync target shown by the go-public helper
OKFORGE_WEBUI_NODE node on PATH, else /usr/bin/node node binary for Quartz builds
OKFORGE_WEBUI_ENGINE_DIR this repo dir whose .venv holds the engine + okforge-vision-ocr console scripts — set this only if that .venv lives somewhere other than this repo (shared or parent-dir venv)

The documented install puts .venv inside the repo, which is why OKFORGE_WEBUI_ENGINE_DIR needs no setting by default. If you instead share one .venv across checkouts or keep it in the base dir, point this var at the directory that contains it — otherwise the UI looks for openkb/okforge-vision-ocr under <repo>/.venv/bin and reports them missing.

Local llama.cpp/vLLM endpoints need only label=url. A hosted OpenAI-compatible service takes two more |-separated fields — its API key and the model in LiteLLM provider/model format:

OKFORGE_WEBUI_ENDPOINTS="gpu1=http://gpu1:8080/v1,openrouter=https://openrouter.ai/api/v1|sk-or-v1-...|openrouter/qwen/qwen3.6-27b"

KBs created on such an endpoint get the key in their .env, the model in their config.yaml, and a provider-appropriate thinking-off block (OpenRouter's reasoning.enabled=false instead of llama.cpp's chat_template_kwargs). OCR/translate jobs strip the LiteLLM provider prefix and pass the rest as the OpenAI-protocol model name. Note the key lives in plain text in the unit file/environment — acceptable on a single-operator LAN box; protect the systemd drop-in accordingly (chmod 640).

How the endpoint choice binds to a KB

The endpoint picked in the header is baked into each knowledge base at first ingest: that's when the KB's own .env (OPENAI_API_BASE, LLM_API_KEY) and config.yaml (model, thinking-off block) are written. From then on, everything that touches that KB — later ingests, the stage-5 Ask box, and the MCP server's ask tool — uses the KB's own endpoint, regardless of what the header currently shows. (The header selection still drives OCR/translate runs, which produce markdown before any KB exists.) So two KBs on one machine can happily run against two different LLM servers, and an MCP client querying a project lights up whichever server that project pins.

To repoint an existing KB (LLM moved to a new box, or a new model): edit those two files in the KB directory — .env for the URL and key, config.yaml for model: — and the next call uses them; no restart, nothing to re-register. Keep the llm_extra_body block appropriate for the new host (see below). The stage-3 project info box shows which endpoint a KB currently points at.

One setting matters more than all the others on llama.cpp hosts serving Qwen-family models with thinking enabled by default — without it every ingest silently pays a hidden reasoning block (measured: a 2-minute add becomes 27):

llm_extra_body:
  chat_template_kwargs:
    enable_thinking: false

KBs created through the web UI get this automatically. Pilot and OCR runs are the exception — they happen before a KB exists, calling the vision model directly. That path already sends enable_thinking: false (and reasoning.enabled: false) and strips any stray <think> block from the transcript, so nothing is normally needed. But if your model's own chat template overrides that request-level flag — the Qwen3.6-27B-MTP template is one that keeps thinking on regardless — the model still generates (and you still pay for) the reasoning block even though the text is discarded. That can only be fixed at the server: use a llama.cpp/vLLM preset (or a --jinja chat template) that honors enable_thinking: false. Symptom: pilot/OCR pages take far longer than their token count suggests.

One more expectation worth setting for large collections: ingest cost scales with the size of the wiki, not the document being added — recurring concept and entity pages are rewritten on every mention — so a document that takes 3 minutes into a fresh KB takes ~9 into the same KB 300 documents later. llama.cpp's prefix cache absorbs most of the growth (84% of input tokens were cache hits over a measured 364-document run); see docs/OPERATIONS.md for the measured figures before planning a multi-day run.

Docs

  • First run: the small-test walkthrough above is the intended on-ramp.
  • docs/INSTALL_PROMPT.md — a prompt to hand a coding agent so it walks you through install and configuration interactively, Quartz included.
  • docs/OPERATIONS.md — operating KBs day to day: anatomy, copying between machines, re-ingest semantics (what happens when you add the same document twice), Obsidian editing safety, Quartz publishing, troubleshooting.
  • docs/MCP_CLIENT_PROMPT.md — the system prompt to paste into MCP clients that don't surface the server's own instructions (Open WebUI and most OpenAPI-bridged clients); also served from the server as the MCP prompt kb-search-guide.
  • webui/PLAN.md — architecture and API design notes.

License

MIT — see LICENSE.

About

LAN web UI + job runner for okforge knowledge bases: scanned PDF -> VLM OCR -> translated, page-cited LLM wiki. Serial job queue, MCP server, Quartz publishing.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages