A modern, clean web interface for the smallFactory Git-native PLM system.
- Dashboard: Inventory overview with quick stats and recent items
- Inventory:
- List and view items with per-location breakdown
- Quick Adjust page at
/inventory/adjust(GET, POST) with QR-scanning - Adjust quantities by location
- Note: Deleting inventory items is not supported in the journal model. Use negative adjustments instead.
- Entities (PLM):
- Create, list, and view canonical entities (SFIDs)
- Inline editing on the entity view page (separate Edit page is deprecated)
- Build events: timeline-style notes/tests/files for
b_*entities - Revisions: bump and release; released pointer at
entities/<sfid>/refs/released - BOM: add, remove, set lines, and manage alternates;
rev: releasedresolves via pointer - Files working area: manage under
entities/<sfid>/files/(list, mkdir, upload, move, delete)
- Stickers: Batch PDF generation of QR code labels for multiple SFIDs
- Vision (Ollama/OpenRouter): Generic image Q&A and invoice part extraction
- Modern UI: Clean, responsive Tailwind CSS design
- Git-native: Core APIs commit on writes; web can optionally autopush
-
Install dependencies:
pip3 install -r web/requirements.txt
-
Ensure smallFactory is configured:
# Make sure you have a data repository set up python3 sf.py init -
Start the web server:
# from project root python3 sf.py web --port 8080 # development mode with auto-reload FLASK_ENV=development python3 sf.py web --port 8080 --debug
-
Access the interface: Open your browser to
http://localhost:8080 -
(Optional) Connect AI clients via MCP:
- MCP endpoint (streamable HTTP):
http://localhost:8080/mcp - See detailed client setup examples:
docs/users/mcp.md
- MCP endpoint (streamable HTTP):
To run in development mode with auto-reload:
FLASK_ENV=development python3 sf.py web --port 8080 --debug- SF_WEB_SECRET: Flask secret key. Defaults to an insecure dev value.
- PORT /
--port: Port for the web server (default 8080). - FLASK_ENV /
--debug: Setdevelopmentor pass--debugfor auto-reload. - SF_VISION_PROVIDER: Vision provider. Supported values:
ollama(default) oropenrouter. - SF_VISION_MODEL: Vision model identifier. For Ollama, defaults to
qwen2.5vl:3b. For OpenRouter, set to a provider-qualified name (e.g.,google/gemma-3-12b-it:free). - If using Ollama: SF_OLLAMA_BASE_URL (default
http://localhost:11434). - If using OpenRouter: SF_OPENROUTER_API_KEY (required), SF_OPENROUTER_BASE_URL (default
https://openrouter.ai/api/v1). - Optional: SF_REPO to point to a specific data repository path (follows the same resolution as the CLI).
When the web UI performs write operations, it will use proxy-provided headers to attribute Git commits to the active user. If headers are absent, behavior falls back to existing Git config/environment (no change for CLI).
- Default header names checked (first match wins):
- User:
X-Forwarded-User,X-Auth-Request-User - Email:
X-Forwarded-Email,X-Auth-Request-Email
- User:
- Override header names via env (comma-separated list allowed):
SF_WEB_IDENTITY_HEADER_NAME(e.g.,SF_WEB_IDENTITY_HEADER_NAME=X-Forwarded-Preferred-User)SF_WEB_IDENTITY_HEADER_EMAIL
- If only an email-like value is present, the display name is derived from the local part (e.g.,
jane.doe->Jane Doe). - Applies only to web requests; the CLI continues to use the caller's local Git identity.
The web UI is built as a Flask application that uses the smallFactory core v1 API:
app.py: Main Flask application with routestemplates/: Jinja2 HTML templatesbase.html: Base template with navigation and common elementsindex.html: Dashboard pageinventory/: Inventory pagesentities/: Entity list/view/build and related pagesstickers/: Batch stickers UIvision.html: Mobile-friendly camera/upload page for Vision
static/: Static assets (CSS, images, JS)sf.py web: CLI entrypoint to run the development server
The web UI directly imports the smallFactory core v1 API:
from smallfactory.core.v1.inventory import inventory_onhand_readonly, inventory_post
from smallfactory.core.v1.entities import (
get_entity, create_entity, update_entity_fields,
get_revisions, bump_revision, release_revision,
append_build_event, update_build_event, update_build_event_tags, add_build_event_file_link,
bom_list, bom_add_line, bom_remove_line, bom_set_line, bom_alt_add, bom_alt_remove,
)
from smallfactory.core.v1.files import list_files, mkdir, rmdir, upload_file, delete_file, move_file, move_dir
from smallfactory.core.v1.stickers import generate_sticker_for_entity
from smallfactory.core.v1.vision import ask_image, extract_invoice_partThis ensures feature parity with the CLI while keeping storage Git-native and YAML-based. The working area root for entity files is files/.
- All HTTP GET routes must be side-effect free: no cache writes and no Git mutations.
- Inventory reads in GET paths use the read-only helper
inventory_onhand_readonly()to avoid writing onhand caches. - Mutations happen only via POST routes, which are wrapped in a transaction helper that can optionally push.
- CLI parity: use
sf inventory onhand --readonlyto compute on-hand without writing caches from the command line.
The UI is designed to be extensible for additional PLM modules:
- Project tracking
- Supplier management
- Approval/change control workflows
- Reporting and analytics
Each new module can follow the same pattern with its own template directory and routes.
The web UI can call a local or remote Visual LLM (VLM) via either:
- Ollama (self-hosted, local inference)
- OpenRouter (hosted API compatible with OpenAI chat API)
We recommend qwen2.5vl:3b on Ollama for a lightweight, high-quality local model; or a vision-capable model on OpenRouter such as google/gemma-3-12b-it:free.
macOS (Homebrew):
brew install ollama
ollama serve &
ollama pull qwen2.5vl:3bLinux: install from https://ollama.com/download, then:
ollama serve &
ollama pull qwen2.5vl:3bVerify the API:
curl http://localhost:11434/api/tagsDefaults assume a local Ollama at http://localhost:11434. To override, set:
export SF_VISION_PROVIDER=ollama
export SF_OLLAMA_BASE_URL=http://<ollama-host>:11434
export SF_VISION_MODEL=qwen2.5vl:3b
### 2b) Configure smallFactory to use OpenRouter
Sign up and obtain an API key at https://openrouter.ai/
```bash
export SF_VISION_PROVIDER=openrouter
export SF_OPENROUTER_API_KEY=sk-or-...
# Optional override; defaults to https://openrouter.ai/api/v1
export SF_OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
# Choose a model identifier supported by OpenRouter
export SF_VISION_MODEL=google/gemma-3-12b-it:free
### 3) Install web deps and run
```bash
pip3 install -r requirements.txt
python3 sf.py web --port 8080
- Generic ask (prompt + image):
curl -s -X POST http://localhost:8080/api/vision/ask \
-F "prompt=Summarize the contents of this image in 1-2 sentences." \
-F "file=@/path/to/invoice.jpg" | jq- Extract part fields from an invoice:
curl -s -X POST http://localhost:8080/api/vision/extract/part \
-F "file=@/path/to/invoice.jpg" | jqIf you see an error, ensure the provider is correctly configured:
- For Ollama: verify the service is running and the model is pulled.
- For OpenRouter: verify
SF_OPENROUTER_API_KEYis set and the model id is valid.
You can also open the Vision page at /vision for a camera/upload UI.
-
Missing prompt (400)
- Error:
Missing prompt - Fix: Include
-F "prompt=..."in the form when calling/api/vision/ask.
- Error:
-
No image or wrong field (400)
- Error:
No image file uploaded under field 'file'. - Fix: Send the image as
-F "file=@/path/to/image.jpg".
- Error:
-
Unsupported file type (400)
- Error:
Unsupported file type; expected an image. - Fix: Upload a valid image (jpg/png). The server re-encodes to PNG.
- Error:
-
Image too large (400)
- Error:
Image too large (max 10MB). - Fix: Reduce the image size below 10MB.
- Error:
-
Provider not configured / runtime error (500)
- If using Ollama:
- Start Ollama:
ollama serve - Pull model:
ollama pull qwen2.5vl:3b - Verify:
curl http://localhost:11434/api/tags - Remote host:
export SF_OLLAMA_BASE_URL=http://<host>:11434 - Model name: set/confirm
SF_VISION_MODEL(defaultqwen2.5vl:3b).
- Start Ollama:
- If using OpenRouter:
- Ensure API key is set:
export SF_OPENROUTER_API_KEY=... - Confirm base URL (optional):
export SF_OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 - Choose a valid model id (e.g.,
google/gemma-3-12b-it:free) viaSF_VISION_MODEL
- Ensure API key is set:
- If using Ollama:
The web UI can run in Docker for easy deployment.
-
Build
docker build -t smallfactory-web:latest . -
Run (init new repo)
docker run --rm -p 8080:8080 \ -e SF_WEB_SECRET=replace-me \ -v $PWD/datarepo:/datarepo \ smallfactory-web:latest -
Run (clone existing datarepo)
docker run --rm -p 8080:8080 \ -e SF_WEB_SECRET=replace-me \ -e SF_REPO_GIT_URL=https://github.com/you/your-datarepo.git \ -v $PWD/datarepo:/datarepo \ smallfactory-web:latest -
Environment variables
PORT(default 8080)SF_REPO_PATH(default/datarepo) – container-side path to the data repoSF_REPO_GIT_URL– if set and the repo path is empty, the container willgit clonehereSF_REPO_NAME– name used when initializing a new repo (defaultdatarepo)SF_WEB_SECRET– Flask secret key (required for non-dev)SF_WEB_AUTOPUSH,SF_WEB_AUTOPUSH_ASYNC,SF_GIT_PUSH_TTL_SEC,SF_GIT_PULL_ALLOW_UNTRACKED– Git orchestration knobsSF_OLLAMA_BASE_URL,SF_VISION_MODEL– Vision integration
-
docker-compose.yml (example)
services: web: image: smallfactory-web:latest build: . ports: - "8080:8080" environment: SF_WEB_SECRET: change-me SF_OLLAMA_BASE_URL: http://ollama:11434 volumes: - datarepo:/datarepo healthcheck: test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8080/"] interval: 30s timeout: 5s retries: 3 start_period: 10s volumes: datarepo:
Notes:
- The container entrypoint writes
.smallfactory.ymlsettingdefault_datarepo: /datarepoand prepares the repo (clone or init). - Mount
/datarepoas a persistent volume in production.
- UI Routes
/— Dashboard/vision— Vision camera/upload page/inventory— Inventory list/inventory/<item_id>— Inventory item view/inventory/adjust— Quick adjust (GET, POST)/inventory/<item_id>/edit— Edit inventory item (GET, POST)/entities— Entities list/entities/<sfid>— Entity view (inline editing)/entities/add— Create entity (GET, POST)/entities/<sfid>/edit— Deprecated; use inline editing/entities/<sfid>/retire— Retire entity (POST)/entities/<sfid>/build— Build flow (GET, POST)/entities/<sfid>/build/create-revision— Create next draft revision (POST)/stickers— Redirect to batch stickers UI/stickers/batch— Batch stickers UI (GET, POST)
Note: The legacy POST route /inventory/<item_id>/adjust has been removed in favor of the unified Quick Adjust flow at /inventory/adjust.
- API Endpoints
/api/inventory— Inventory API (GET)/api/inventory/<item_id>— Inventory item (GET)/api/entities— Entities API (GET)/api/entities/<sfid>— Entity (GET)/api/entities/<sfid>/update— Update entity fields (POST)/api/entities/<sfid>/events— List build events (GET;b_*only)/api/entities/<sfid>/events/append— Append build event (POST;b_*only)/api/entities/<sfid>/events/<event_id>/update— Update build event (POST;b_*only)/api/entities/<sfid>/events/<event_id>/tags— Replace build event tags (POST;b_*only)/api/entities/<sfid>/events/<event_id>/files/link— Link existingfiles/path to event (POST;b_*only)/api/entities/<sfid>/revisions— Get revisions and released pointer (GET)/api/entities/<sfid>/revisions/bump— Cut and release a revision (POST; optionalrevin body, otherwise next numeric)/api/entities/<sfid>/revisions/<rev>/release— Release a specific revision (POST)/api/entities/<sfid>/revisions/<rev>/download— Download released revision tarball (GET)/api/entities/<sfid>/bom— BOM list (GET)/api/entities/<sfid>/bom/deep— Resolved BOM tree (GET)/api/entities/<sfid>/bom/add— Add BOM line (POST)/api/entities/<sfid>/bom/remove— Remove BOM line(s) (POST)/api/entities/<sfid>/bom/set— Update BOM line (POST)/api/entities/<sfid>/bom/alt-add— Add alternate part (POST)/api/entities/<sfid>/bom/alt-remove— Remove alternate part (POST)/api/entities/<sfid>/files— List working files (GET)/api/entities/<sfid>/files/mkdir— Create folder (POST)/api/entities/<sfid>/files/rmdir— Remove folder (POST)/api/entities/<sfid>/files/upload— Upload file (POST)/api/entities/<sfid>/files/delete— Delete file (POST)/api/entities/<sfid>/files/move— Move file/folder (POST)/api/entities/<sfid>/files/download— Download file (GET)/api/entities/specs/<sfid>— Entity specs (GET)/api/vision/ask— Vision ask (POST)/api/vision/extract/part— Extract part fields (POST)