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("")