diff --git a/app.py b/app.py
index 7e47378..d7d9e83 100644
--- a/app.py
+++ b/app.py
@@ -124,6 +124,12 @@ def init_db():
c.execute("ALTER TABLE jobs ADD COLUMN llm_thinking TEXT")
except Exception:
pass
+ try:
+ c.execute(
+ "CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
+ )
+ except Exception:
+ pass
# defaults
c.execute("INSERT OR IGNORE INTO projects(name, created_at) VALUES (?,?)", ("Default", now_iso()))
@@ -147,6 +153,17 @@ def init_db():
)
+def get_setting(key: str, default: str) -> str:
+ with db() as c:
+ row = c.execute("SELECT value FROM settings WHERE key=?", (key,)).fetchone()
+ return row["value"] if row else default
+
+
+def set_setting(key: str, value: str):
+ with db() as c:
+ c.execute("INSERT OR REPLACE INTO settings(key, value) VALUES (?,?)", (key, value))
+
+
def layout(title: str, body: str) -> str:
return f"""
@@ -272,6 +289,7 @@ window.addEventListener('DOMContentLoaded', () => {{
Prompt ausführen
Hintergrundjobs
@@ -383,18 +401,24 @@ def _process_analysis_job(job_id: int):
+ f"\\nTEXT:\\n{doc['content_md']}\\n"
)
- num_ctx = _estimate_num_ctx(llm_prompt)
+ num_ctx_setting = get_setting("ollama_num_ctx", "auto")
+ num_ctx = _estimate_num_ctx(llm_prompt) if num_ctx_setting == "auto" else int(num_ctx_setting)
_job_set(job_id, llm_prompt=f"[num_ctx={num_ctx}]\n\n{llm_prompt}")
with _JOB_STREAM_LOCK:
_JOB_STREAMS[job_id] = {"thinking": "", "response": ""}
+ model = get_setting("ollama_model", OLLAMA_MODEL)
+ think = get_setting("ollama_think", "true" if OLLAMA_THINK else "false") == "true"
+ num_predict = int(get_setting("ollama_num_predict", str(OLLAMA_NUM_PREDICT)))
+ repeat_penalty = float(get_setting("ollama_repeat_penalty", "1.15"))
+ repeat_last_n = int(get_setting("ollama_repeat_last_n", "128"))
r = requests.post(
f"{OLLAMA_BASE_URL}/api/generate",
- json={"model": OLLAMA_MODEL, "prompt": llm_prompt, "stream": True, "think": OLLAMA_THINK, "options": {
+ json={"model": model, "prompt": llm_prompt, "stream": True, "think": think, "options": {
"num_ctx": num_ctx,
- "num_predict": OLLAMA_NUM_PREDICT,
- "repeat_penalty": 1.15,
- "repeat_last_n": 128,
+ "num_predict": num_predict,
+ "repeat_penalty": repeat_penalty,
+ "repeat_last_n": repeat_last_n,
}},
stream=True,
timeout=1200,
@@ -529,6 +553,168 @@ def healthz():
return {"ok": True, "api_base": API_BASE, "ollama_base_url": OLLAMA_BASE_URL, "ollama_model": OLLAMA_MODEL, "db_path": DB_PATH}
+@app.get("/settings/models")
+def settings_models():
+ try:
+ r = requests.get(f"{OLLAMA_BASE_URL}/api/tags", timeout=5)
+ r.raise_for_status()
+ names = sorted(m["name"] for m in r.json().get("models", []))
+ return JSONResponse({"ok": True, "models": names})
+ except Exception as e:
+ return JSONResponse({"ok": False, "error": str(e), "models": []})
+
+
+@app.get("/settings", response_class=HTMLResponse)
+def settings_page(msg: str = ""):
+ current_model = get_setting("ollama_model", OLLAMA_MODEL)
+ current_think = get_setting("ollama_think", "true" if OLLAMA_THINK else "false") == "true"
+ current_num_ctx = get_setting("ollama_num_ctx", "auto")
+ current_num_predict = get_setting("ollama_num_predict", str(OLLAMA_NUM_PREDICT))
+ current_repeat_penalty = get_setting("ollama_repeat_penalty", "1.15")
+ current_repeat_last_n = get_setting("ollama_repeat_last_n", "128")
+
+ think_checked = "checked" if current_think else ""
+
+ ctx_options = [("auto", "Automatisch (empfohlen)"), ("4096", "4 096"), ("8192", "8 192"),
+ ("16384", "16 384"), ("32768", "32 768"), ("65536", "65 536"), ("131072", "131 072")]
+ ctx_opts_html = "".join(
+ f"
"
+ for v, l in ctx_options
+ )
+
+ body = f"""
+
+
Einstellungen
+
+{f"
{msg}
" if msg else ""}
+
+
+
+
Systemkonfiguration (read-only)
+
+
+ | API_BASE | {API_BASE} |
+ | OLLAMA_BASE_URL | {OLLAMA_BASE_URL} |
+ | DB_PATH | {DB_PATH} |
+
+
+
+
+
+"""
+ return layout("Einstellungen – VoiceLog", body)
+
+
+@app.post("/settings", response_class=HTMLResponse)
+def settings_save(
+ ollama_model: str = Form(...),
+ ollama_think: Optional[str] = Form(None),
+ ollama_num_ctx: str = Form("auto"),
+ ollama_num_predict: str = Form("16384"),
+ ollama_repeat_penalty: str = Form("1.15"),
+ ollama_repeat_last_n: str = Form("128"),
+):
+ set_setting("ollama_model", ollama_model.strip())
+ set_setting("ollama_think", "true" if ollama_think == "true" else "false")
+ set_setting("ollama_num_ctx", ollama_num_ctx.strip())
+ set_setting("ollama_num_predict", str(int(float(ollama_num_predict))))
+ set_setting("ollama_repeat_penalty", str(float(ollama_repeat_penalty)))
+ set_setting("ollama_repeat_last_n", str(int(float(ollama_repeat_last_n))))
+ return RedirectResponse("/settings?msg=Einstellungen+gespeichert.", status_code=303)
+
+
@app.get("/", response_class=HTMLResponse)
def upload_page(msg: str = ""):
projects = get_projects()
diff --git a/docker-compose.yml b/docker-compose.yml
index ec550bf..48e48d6 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -14,6 +14,18 @@ services:
- DB_PATH=/data/ui.db
volumes:
- diarization_ui_data:/data
+ networks:
+ - traefik
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.openwebui.rule=Host(`voicelog.aquantico.lan`)"
+ - "traefik.http.routers.openwebui.entrypoints=websecure"
+ - "traefik.http.routers.openwebui.tls=true"
+ - "traefik.http.services.openwebui.loadbalancer.server.port=8094"
volumes:
diarization_ui_data:
+
+networks:
+ traefik:
+ external: true