okforge-webui

mcp
Security Audit
Fail
Health Warn
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Fail
  • exec() — Shell command execution in webui/static/app.js
  • network request — Outbound network request in webui/static/app.js
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

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.

README.md

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) knowledgebase loaded: the five workflow stages, job queue, KB stats, and wikibrowser

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.

Reviews (0)

No results found