Skip to content

Commit 1f1581c

Browse files
author
Paweł Kędzia
committed
Merge branch 'features/refactoring'
2 parents a9f4566 + c552d21 commit 1f1581c

40 files changed

+5101
-0
lines changed

.version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.0.1

README.md

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# LLM Router Web
2+
3+
Web interface for managing LLM Router configurations and text anonymization.
4+
5+
## Overview
6+
7+
`llm_router_web` provides two Flask-based web applications:
8+
9+
1. **Config Manager** – Manage LLM Router model configurations with multi-user support
10+
2. **Anonymizer** – Web UI for text anonymization and chat with anonymization
11+
12+
## Features
13+
14+
### Config Manager (`app_cfg_manager`)
15+
16+
- **User Management**: Admin panel, authentication, role-based access
17+
- **Project Management**: Organize configurations into projects
18+
- **Model Configuration**:
19+
- Create, edit, import/export configurations (JSON)
20+
- Manage models across families (Google, OpenAI, Qwen)
21+
- Configure providers (API hosts, tokens, weights, input sizes)
22+
- Version control with restore capability
23+
- **Active Model Selection**: Choose which models to activate
24+
25+
### Anonymizer (`app_anonymizer`)
26+
27+
- **Text Anonymization**: Multiple algorithms (fast, GenAI, PrivMasker)
28+
- **Chat Interface**: Interactive chat with optional anonymization
29+
- **Model Selection**: Browse and select available LLM models
30+
- **Real-time Processing**: Direct integration with LLM Router API
31+
32+
## Installation
33+
34+
```bash
35+
pip install -r requirements.txt
36+
```
37+
38+
### Requirements
39+
40+
- Flask
41+
- Flask-SQLAlchemy
42+
- Gunicorn
43+
44+
## Usage
45+
46+
### Config Manager
47+
48+
**Run with Gunicorn (recommended):**
49+
50+
```bash
51+
./run-configs-manager.sh
52+
```
53+
54+
**Run directly:**
55+
56+
```bash
57+
python app_cfg_manager.py
58+
```
59+
60+
**Configuration (environment variables):**
61+
62+
- `LLM_ROUTER_WEB_CFG_HOST` – Bind address (default: `0.0.0.0`)
63+
- `LLM_ROUTER_WEB_CFG_PORT` – Port (default: `8081`)
64+
- `LLM_ROUTER_WEB_CFG_DEBUG` – Debug mode (default: `true`)
65+
66+
**First-time setup:**
67+
68+
1. Access the web interface
69+
2. Create initial admin account via setup page
70+
3. Log in and start managing configurations
71+
72+
**Default URL:** http://localhost:8081
73+
74+
### Anonymizer
75+
76+
**Run with Gunicorn (recommended):**
77+
78+
```bash
79+
./run-anonymizer.sh
80+
```
81+
82+
**Run directly:**
83+
84+
```bash
85+
python app_anonymizer.py
86+
```
87+
88+
**Configuration (environment variables):**
89+
90+
- `LLM_ROUTER_WEB_ANO_HOST` – Bind address (default: `0.0.0.0`)
91+
- `LLM_ROUTER_WEB_ANO_PORT` – Port (default: `8082`)
92+
- `LLM_ROUTER_WEB_ANO_DEBUG` – Debug mode (default: `true`)
93+
- `LLM_ROUTER_HOST` – LLM Router API endpoint (default: `http://192.168.100.65:8080`)
94+
- `LLM_ROUTER_GENAI_MODEL_ANONYMISATION` – Model for GenAI anonymization (default: `gtp-oss:120b`)
95+
96+
**Default URL:** http://localhost:8082/anonymize
97+
98+
## API Endpoints
99+
100+
### Config Manager
101+
102+
| Endpoint | Method | Description |
103+
|----------|--------|-------------|
104+
| `/` | GET | Configuration list (home) |
105+
| `/login` | GET/POST | User login |
106+
| `/logout` | GET | User logout |
107+
| `/setup` | GET/POST | Initial admin setup |
108+
| `/admin/users` | GET/POST | User management (admin) |
109+
| `/projects` | GET/POST | Project management |
110+
| `/configs` | GET | List configurations |
111+
| `/configs/new` | GET/POST | Create configuration |
112+
| `/configs/import` | GET/POST | Import JSON configuration |
113+
| `/configs/<id>` | GET | View configuration |
114+
| `/configs/<id>/edit` | GET/POST | Edit configuration |
115+
| `/configs/<id>/export` | GET | Export configuration (JSON) |
116+
| `/configs/<id>/activate` | POST | Activate configuration |
117+
| `/configs/<id>/delete` | POST | Delete configuration |
118+
| `/configs/<id>/models/add` | POST | Add model |
119+
| `/models/<id>/delete` | POST | Delete model |
120+
| `/models/<id>/providers/add` | POST | Add provider |
121+
| `/providers/<id>/update` | POST | Update provider |
122+
| `/providers/<id>/delete` | POST | Delete provider |
123+
124+
### Anonymizer
125+
126+
| Endpoint | Method | Description |
127+
|----------|--------|-------------|
128+
| `/anonymize/` | GET | Anonymization form |
129+
| `/anonymize/` | POST | Process text anonymization |
130+
| `/anonymize/chat` | GET | Chat interface |
131+
| `/anonymize/chat/message` | POST | Send chat message |
132+
| `/anonymize/models` | GET | List available models |
133+
134+
## Project Structure
135+
136+
```
137+
llm_router_web/
138+
├── app_anonymizer.py # Anonymizer Flask app entry point
139+
├── app_cfg_manager.py # Config Manager Flask app entry point
140+
├── run-anonymizer.sh # Anonymizer startup script (gunicorn)
141+
├── run-configs-manager.sh # Config Manager startup script (gunicorn)
142+
├── requirements.txt # Python dependencies
143+
├── instance/
144+
│ └── configs.db # SQLite database (auto-created)
145+
└── web/
146+
├── anonymizer/ # Anonymizer blueprint & routes
147+
├── configs_manager/ # Config Manager blueprint, models & routes
148+
├── templates/ # HTML templates
149+
└── static/ # Static assets (CSS, JS)
150+
```
151+
152+
## Database
153+
154+
Config Manager uses SQLite with SQLAlchemy ORM:
155+
156+
- **Models**: `User`, `Project`, `Config`, `Model`, `Provider`, `ActiveModel`, `ConfigVersion`
157+
- **Location**: `instance/configs.db` (auto-created on first run)
158+
- **Version Control**: Automatic configuration snapshots on changes
159+
160+
## Security
161+
162+
- Password hashing via Werkzeug
163+
- Session-based authentication
164+
- Role-based access control (admin/user)
165+
- User account blocking capability
166+
- Per-user project isolation
167+
168+
## Notes
169+
170+
- Config Manager requires initial admin setup on first run
171+
- Anonymizer requires a running LLM Router instance
172+
- Both apps run independently on different ports
173+
- Configurations are stored in SQLite database (Config Manager only)
174+
- All configuration changes are versioned and can be restored

llm_router_web/__init__.py

Whitespace-only changes.

llm_router_web/app_anonymizer.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import os
2+
3+
from llm_router_web.web.anonymizer import create_anonymize_app
4+
5+
# ----------------------------------------------
6+
# Configuration – read from environment variables with the prefix
7+
# LLM_ROUTER_WEB_ANO_*. Fallback to the original defaults if not set.
8+
# ----------------------------------------------
9+
HOST = os.getenv("LLM_ROUTER_WEB_ANO_HOST", "0.0.0.0")
10+
PORT = int(os.getenv("LLM_ROUTER_WEB_ANO_PORT", "8082"))
11+
12+
# DEBUG is expected to be a truthy string (e.g. "true", "1") or falsy.
13+
_debug_val = os.getenv("LLM_ROUTER_WEB_ANO_DEBUG", "true").lower()
14+
DEBUG = _debug_val in {"1", "true", "yes", "on"}
15+
16+
# Expose the Flask app as a module‑level variable for gunicorn.
17+
app = create_anonymize_app()
18+
19+
if __name__ == "__main__":
20+
app.run(host=HOST, port=PORT, debug=DEBUG)

llm_router_web/app_cfg_manager.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import os
2+
3+
from llm_router_web.web.configs_manager import create_config_manager_app
4+
5+
# ----------------------------------------------------------------------
6+
# Configuration – read from environment variables with the prefix
7+
# LLM_ROUTER_WEB_*. Fallback to the original defaults if not set.
8+
# ----------------------------------------------------------------------
9+
HOST = os.getenv("LLM_ROUTER_WEB_CFG_HOST", "0.0.0.0")
10+
PORT = int(os.getenv("LLM_ROUTER_WEB_CFG_PORT", "8081"))
11+
12+
# DEBUG is expected to be a truthy string (e.g. "true", "1") or falsy.
13+
_debug_val = os.getenv("LLM_ROUTER_WEB_CFG_DEBUG", "true").lower()
14+
DEBUG = _debug_val in {"1", "true", "yes", "on"}
15+
16+
# Expose the Flask app as a module‑level variable for gunicorn.
17+
app = create_config_manager_app()
18+
19+
if __name__ == "__main__":
20+
# When run directly, start the built‑in development server using the
21+
# values obtained from the environment.
22+
app.run(host=HOST, port=PORT, debug=DEBUG)

llm_router_web/web/__init__.py

Whitespace-only changes.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# llm_router_web.anonymizer
2+
3+
**llm_router_web: anonymizer** is a lightweight Flask web interface that provides a simple UI for text anonymization.
4+
It forwards the supplied text to an external LLM‑router anonymization service (`/api/fast_text_mask`)
5+
and displays the masked result, with on‑the‑fly highlighting of detected tags (`{{…}}`).
6+
7+
**llm_router_web.anonymizer** is the web interface component of the
8+
[llm-router](https://github.com/radlab-dev-group/llm-router) library.
9+
10+
## Features
11+
12+
- **Web form** – Paste text and trigger anonymization with a single click.
13+
- **HTMX‑powered UI** – Asynchronous request/response without a full page reload.
14+
- **Result highlighting** – Detected placeholders are wrapped in a colored span for easy spotting.
15+
- **Spinner indicator** – Visual feedback while the request is in progress.
16+
- **Error handling** – Returns clear messages for missing input or communication problems.
17+
- **Configurable backend** – Target anonymization service URL is supplied via `LLM_ROUTER_HOST` environment variable.
18+
19+
## Installation
20+
21+
The project follows the same conventions as the rest of the **llm‑router** repository and uses
22+
**Python 3.10.6** with **virtualenv**.
23+
24+
```shell script
25+
# Clone the repository (if not already done)
26+
git clone https://github.com/radlab-dev-group/llm-router.git
27+
cd llm-router/llm_router_web
28+
29+
# Create and activate a virtual environment
30+
python3 -m venv venv
31+
source venv/bin/activate
32+
33+
# Install dependencies (the base requirements already contain Flask)
34+
pip install -r requirements.txt
35+
```
36+
37+
> **Note:** The only additional packages required for the anonymizer are already listed in `requirements.txt` (`flask`,
38+
`requests`, `htmx`, `alpine.js` are loaded from CDN).
39+
40+
## Running the Application
41+
42+
### Development server
43+
44+
```shell script
45+
# Start the Flask development server for the anonymizer module
46+
python -m web.anonymizer
47+
```
48+
49+
The UI will be reachable at `http://localhost:5000/anonymize`.
50+
The root path (`/`) redirects to the form page.
51+
52+
### Production (Gunicorn)
53+
54+
A small launch script is included in the main repository. Example:
55+
56+
```shell script
57+
LLM_ROUTER_HOST=http://localhost:8000 \
58+
FLASK_SECRET_KEY=super-secret \
59+
gunicorn -w 4 -b 0.0.0.0:8082 "web.anonymizer:create_anonymize_app()"
60+
```
61+
62+
- `LLM_ROUTER_HOST` – base URL of the external anonymization service (default: `http://localhost:8000`).
63+
- `FLASK_SECRET_KEY` – secret key for session signing (default: `change-me-anonymizer`).
64+
65+
Adjust the number of workers (`-w`) as needed.
66+
67+
## Configuration
68+
69+
All configuration is performed via environment variables:
70+
71+
| Variable | Description | Default |
72+
|--------------------|-------------------------------------------|-------------------------|
73+
| `FLASK_SECRET_KEY` | Secret key for Flask session signing | `change-me-anonymizer` |
74+
| `LLM_ROUTER_HOST` | URL of the external anonymization service | `http://localhost:8000` |
75+
76+
The variables are read in `web/anonymizer/__init__.py` when `create_anonymize_app()` is called.
77+
78+
## Endpoints Overview
79+
80+
| URL | Methods | Description |
81+
|------------------|---------|------------------------------------------------------------------------------------------------------------------------------------|
82+
| `/` (root) | GET | Redirects to `/anonymize/`. |
83+
| `/anonymize/` | GET | Renders the anonymization form (`anonymize.html`). |
84+
| `/anonymize/` | POST | Accepts `text` form field, forwards it to the external service, and returns the rendered result (`anonymize_result_partial.html`). |
85+
| *Error handlers* || Returns JSON payloads for 400, 404, and 500 errors. |
86+
87+
### Request flow (POST `/anonymize/`)
88+
89+
1. The form posts the `text` field via HTMX.
90+
2. The server builds the target URL: `"{LLM_ROUTER_HOST.rstrip('/')}/api/fast_text_mask"`.
91+
3. It sends a JSON payload `{ "text": "<raw text>" }` to the external service.
92+
4. On success the response text (or the `text` field from JSON) is injected back into the page, where JavaScript
93+
highlights any `{{…}}` tags.
94+
95+
## Development
96+
97+
### Project layout
98+
99+
```
100+
web/
101+
└─ anonymizer/
102+
├─ templates/
103+
│ ├─ anonymize.html # Main form page (HTMX enabled)
104+
│ ├─ anonymize_result_partial.html # Partial used to render the result
105+
│ └─ base_anonymizer.html # Base layout shared by both templates
106+
├─ __init__.py # Flask app factory (create_anonymize_app)
107+
└─ routes.py # Blueprint with view functions
108+
```
109+
110+
### Adding new features
111+
112+
1. **New routes** – Define them in `routes.py` and register on `anonymize_bp`.
113+
2. **Templates** – Place additional Jinja2 files in `templates/` and extend `base_anonymizer.html`.
114+
3. **Static assets** – Add CSS/JS to the main `static/` directory; they are automatically served because the Flask app
115+
points to the parent static folder.
116+
4. **Configuration** – Extend `create_anonymize_app()` to read extra environment variables as needed.
117+
118+
## License
119+
120+
`llm_router_web.anonymizer` is part of the **llm-router** project and is released under the same license
121+
as the parent repository. See the repository’s `LICENSE` file for details.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import os
2+
from flask import Flask, redirect, url_for
3+
4+
# Blueprint is located in the same package
5+
from .routes import anonymize_bp
6+
7+
8+
def create_anonymize_app() -> Flask:
9+
"""
10+
A lightweight Flask application whose sole purpose is to handle
11+
the /anonymize endpoint.
12+
"""
13+
app = Flask(
14+
__name__,
15+
# Share static resources with the main application
16+
static_folder=os.path.abspath(
17+
os.path.join(os.path.dirname(__file__), "..", "static")
18+
),
19+
# Templates are located in web/anonymize/templates
20+
template_folder=os.path.abspath(
21+
os.path.join(os.path.dirname(__file__), "templates")
22+
),
23+
)
24+
25+
# ---- Configuration (environment variables) ----
26+
app.config["SECRET_KEY"] = os.getenv("FLASK_SECRET_KEY", "change-me-anonymizer")
27+
# Address of the external anonymizing service
28+
app.config["LLM_ROUTER_HOST"] = os.getenv(
29+
"LLM_ROUTER_HOST", "http://localhost:8000"
30+
)
31+
32+
# ---- Blueprint registration ----
33+
app.register_blueprint(anonymize_bp)
34+
35+
@app.route("/", endpoint="index")
36+
def root():
37+
# You can redirect to a form or display a short page
38+
return redirect(url_for("anonymize_web.show_form"))
39+
40+
# ---- Simple error handlers (return JSON) ----
41+
@app.errorhandler(400)
42+
def handle_400(error):
43+
return {"error": error.description or "Bad request"}, 400
44+
45+
@app.errorhandler(404)
46+
def handle_404(error):
47+
return {"error": "Resource not found"}, 404
48+
49+
@app.errorhandler(500)
50+
def handle_500(error):
51+
return {"error": "Internal server error"}, 500
52+
53+
return app

0 commit comments

Comments
 (0)