okforge-webui
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.
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.
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.

What's in the box
- Serial job queue (sqlite + one worker, on purpose): one
addor
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 totrash/(KBs retire tokbs-retired/) — nothing in the UI is
destructive, restore is amvback. - 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 fromdocs/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,
orOKFORGE_WEBUI_QUARTZ_DIR) — clone it, check out v5,npm ci, thennpx quartz plugin install; the exact steps are in
OPERATIONS.
Skipping theplugin installstep is the usual first-publish failure:
the build aborts withCould 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 inquartz.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 directory — python -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 (default0.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 aresudo 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/.
- 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). - 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. - 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. - 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 totrash/). - 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. - 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). - 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-rebrandOPENKB_WEBUI_*names (andOPENKB_DIRfor 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'schat_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 honorsenable_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); seedocs/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 owninstructions(Open WebUI and most OpenAPI-bridged clients); also
served from the server as the MCP promptkb-search-guide.webui/PLAN.md— architecture and API design notes.
License
MIT — see LICENSE.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found