From 21712c972d787848c1772ea24b4dbc0c548beef0 Mon Sep 17 00:00:00 2001 From: wb Date: Mon, 25 May 2026 11:50:39 +0200 Subject: [PATCH] feat(diarization-ui): per-prompt LLM settings with default/custom toggle Each prompt can override model, thinking, num_ctx, num_predict, repeat_penalty and repeat_last_n. Default switch uses global settings; custom values are preserved when switching back to default. Co-Authored-By: Claude Sonnet 4.6 --- app.py | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 178 insertions(+), 9 deletions(-) diff --git a/app.py b/app.py index 1795555..5c6beeb 100644 --- a/app.py +++ b/app.py @@ -130,6 +130,19 @@ def init_db(): ) except Exception: pass + for col in [ + "llm_use_default INTEGER NOT NULL DEFAULT 1", + "llm_model TEXT", + "llm_think TEXT", + "llm_num_ctx TEXT", + "llm_num_predict INTEGER", + "llm_repeat_penalty REAL", + "llm_repeat_last_n INTEGER", + ]: + try: + c.execute(f"ALTER TABLE prompts ADD COLUMN {col}") + except Exception: + pass # defaults c.execute("INSERT OR IGNORE INTO projects(name, created_at) VALUES (?,?)", ("Default", now_iso())) @@ -401,17 +414,25 @@ def _process_analysis_job(job_id: int): + f"\\nTEXT:\\n{doc['content_md']}\\n" ) - num_ctx_setting = get_setting("ollama_num_ctx", "auto") + use_default = int(prm["llm_use_default"] if prm["llm_use_default"] is not None else 1) + if use_default: + model = get_setting("ollama_model", OLLAMA_MODEL) + think = get_setting("ollama_think", "true" if OLLAMA_THINK else "false") == "true" + num_ctx_setting = get_setting("ollama_num_ctx", "auto") + 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")) + else: + model = prm["llm_model"] or get_setting("ollama_model", OLLAMA_MODEL) + think = (prm["llm_think"] or get_setting("ollama_think", "true" if OLLAMA_THINK else "false")) == "true" + num_ctx_setting = prm["llm_num_ctx"] or get_setting("ollama_num_ctx", "auto") + num_predict = int(prm["llm_num_predict"] or get_setting("ollama_num_predict", str(OLLAMA_NUM_PREDICT))) + repeat_penalty = float(prm["llm_repeat_penalty"] or get_setting("ollama_repeat_penalty", "1.15")) + repeat_last_n = int(prm["llm_repeat_last_n"] or get_setting("ollama_repeat_last_n", "128")) 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": model, "prompt": llm_prompt, "stream": True, "think": think, "options": { @@ -1224,11 +1245,79 @@ def bulk_delete_documents(ids: List[int] = Form(...)): return HTMLResponse("") +def _prompt_llm_block(p, def_model, def_think, def_num_ctx, def_num_predict, def_repeat_penalty, def_repeat_last_n): + pid = p["id"] + use_default = int(p["llm_use_default"]) if p["llm_use_default"] is not None else 1 + m = p["llm_model"] or def_model + th = p["llm_think"] or def_think + nc = p["llm_num_ctx"] or def_num_ctx + np_ = p["llm_num_predict"] or def_num_predict + rp = p["llm_repeat_penalty"] or def_repeat_penalty + rn = p["llm_repeat_last_n"] or def_repeat_last_n + disabled = "disabled" if use_default else "" + opacity = "opacity:.45;pointer-events:none" if use_default else "" + ctx_opts = [("auto","Automatisch"), ("4096","4 096"), ("8192","8 192"), ("16384","16 384"), + ("32768","32 768"), ("65536","65 536"), ("131072","131 072")] + ctx_html = "".join(f"" for v,l in ctx_opts) + think_checked = "checked" if str(th) == "true" else "" + return ( + f"
" + f"
" + f" KI-Einstellungen" + f"
" + f" " + f" " + f"
" + f"
" + f"" + f"
" + f"
" + f" " + f"
" + f" " + f" " + f"
" + f"
" + f"
" + f" " + f" " + f"
" + f"
" + f" " + f" " + f"
" + f"
" + f" " + f" " + f"
" + f"
" + f" " + f" " + f"
" + f"
" + f"
" + f" " + f" " + f"
" + f"
" + f"
" + ) + + @app.get("/prompts", response_class=HTMLResponse) def prompts_page(): with db() as c: prompts = c.execute("SELECT * FROM prompts ORDER BY name").fetchall() projects = c.execute("SELECT id,name FROM projects ORDER BY name").fetchall() + def_model = get_setting("ollama_model", OLLAMA_MODEL) + def_think = get_setting("ollama_think", "true" if OLLAMA_THINK else "false") + def_num_ctx = get_setting("ollama_num_ctx", "auto") + def_num_predict = get_setting("ollama_num_predict", str(OLLAMA_NUM_PREDICT)) + def_repeat_penalty = get_setting("ollama_repeat_penalty", "1.15") + def_repeat_last_n = get_setting("ollama_repeat_last_n", "128") project_opts = "".join([f"" for p in projects]) @@ -1264,6 +1353,7 @@ def prompts_page(): f"" f"" f"" + + _prompt_llm_block(p, def_model, def_think, def_num_ctx, def_num_predict, def_repeat_penalty, def_repeat_last_n) + f"
" f"" f"" @@ -1387,6 +1477,58 @@ document.addEventListener('DOMContentLoaded', () => {{ const el = document.getElementById('promptEditorModal'); if(el) el.addEventListener('hide.bs.modal', syncPromptEditorBack); }}); + +const _llmDefaults = {{ + model: {json.dumps(def_model)}, + think: {json.dumps(def_think)}, + num_ctx: {json.dumps(def_num_ctx)}, + num_predict: {json.dumps(def_num_predict)}, + repeat_penalty: {json.dumps(def_repeat_penalty)}, + repeat_last_n: {json.dumps(def_repeat_last_n)}, +}}; + +function toggleLlmDef(pid, isDefault) {{ + document.getElementById('llm_use_default_' + pid).value = isDefault ? '1' : '0'; + const wrap = document.getElementById('llmfields_' + pid); + const inputs = wrap.querySelectorAll('input,select,button'); + if (isDefault) {{ + wrap.style.opacity = '.45'; + wrap.style.pointerEvents = 'none'; + inputs.forEach(el => el.disabled = true); + }} else {{ + wrap.style.opacity = ''; + wrap.style.pointerEvents = ''; + inputs.forEach(el => el.disabled = false); + // pre-fill from defaults if field is empty + const modelInp = document.getElementById('llmmodel_' + pid); + if (modelInp && !modelInp.value) modelInp.value = _llmDefaults.model; + }} +}} + +async function loadModelsInto(inputId) {{ + const inp = document.getElementById(inputId); + if (!inp) return; + const btn = inp.nextElementSibling; + if (btn) btn.disabled = true; + try {{ + const res = await fetch('/settings/models'); + const data = await res.json(); + if (!data.ok || !data.models.length) {{ + alert('Keine Modelle gefunden: ' + (data.error || '')); + return; + }} + const current = inp.value.trim(); + const v = await window.uiSelect('Modell wählen', + data.models.map(m => ({{value: m, label: m}})), + '– Modell auswählen –' + ); + if (v) inp.value = v; + }} catch(e) {{ + alert('Fehler: ' + e); + }} finally {{ + if (btn) btn.disabled = false; + }} +}} """ return layout("Prompts & Projekte", body) @@ -1412,9 +1554,36 @@ def prompt_add(name: str = Form(...), prompt: str = Form(...)): @app.post("/prompts/update", response_class=HTMLResponse) -def prompt_update(id: int = Form(...), name: str = Form(...), prompt: str = Form(...)): +def prompt_update( + id: int = Form(...), + name: str = Form(...), + prompt: str = Form(...), + llm_use_default: int = Form(1), + llm_model: Optional[str] = Form(None), + llm_think: Optional[str] = Form(None), + llm_num_ctx: Optional[str] = Form(None), + llm_num_predict: Optional[str] = Form(None), + llm_repeat_penalty: Optional[str] = Form(None), + llm_repeat_last_n: Optional[str] = Form(None), +): with db() as c: - c.execute("UPDATE prompts SET name=?, prompt=?, updated_at=? WHERE id=?", (name.strip(), prompt.strip(), now_iso(), id)) + c.execute( + """UPDATE prompts SET name=?, prompt=?, updated_at=?, + llm_use_default=?, llm_model=?, llm_think=?, + llm_num_ctx=?, llm_num_predict=?, llm_repeat_penalty=?, llm_repeat_last_n=? + WHERE id=?""", + ( + name.strip(), prompt.strip(), now_iso(), + llm_use_default, + (llm_model or "").strip() or None, + "true" if llm_think == "true" else "false", + (llm_num_ctx or "auto").strip(), + int(float(llm_num_predict)) if llm_num_predict else None, + float(llm_repeat_penalty) if llm_repeat_penalty else None, + int(float(llm_repeat_last_n)) if llm_repeat_last_n else None, + id, + ), + ) return HTMLResponse("")