feat(diarization-ui): add project-batch execution and user_prompt to run page
- New tab layout on /run: single-document vs. project-batch mode - POST /run/project enqueues one analysis job per transcript in a project - Optional user_prompt field on both modes for extra context to the LLM - DB migration: adds user_prompt column to jobs table - LLM prompt builder includes ZUSATZINFOS block when user_prompt is set Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
95
app.py
95
app.py
@@ -95,6 +95,12 @@ def init_db():
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# migrations
|
||||||
|
try:
|
||||||
|
c.execute("ALTER TABLE jobs ADD COLUMN user_prompt TEXT")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# defaults
|
# defaults
|
||||||
c.execute("INSERT OR IGNORE INTO projects(name, created_at) VALUES (?,?)", ("Default", now_iso()))
|
c.execute("INSERT OR IGNORE INTO projects(name, created_at) VALUES (?,?)", ("Default", now_iso()))
|
||||||
c.execute(
|
c.execute(
|
||||||
@@ -342,10 +348,12 @@ def _process_analysis_job(job_id: int):
|
|||||||
if not doc or not prm:
|
if not doc or not prm:
|
||||||
raise RuntimeError("Dokument oder Prompt nicht gefunden")
|
raise RuntimeError("Dokument oder Prompt nicht gefunden")
|
||||||
|
|
||||||
|
user_extra = (j.get("user_prompt") or "").strip()
|
||||||
llm_prompt = (
|
llm_prompt = (
|
||||||
"Du bist ein präziser Assistent. Antworte auf Deutsch.\\n"
|
"Du bist ein präziser Assistent. Antworte auf Deutsch.\\n"
|
||||||
f"AUFTRAG:\\n{prm['prompt']}\\n\\n"
|
f"AUFTRAG:\\n{prm['prompt']}\\n"
|
||||||
f"TEXT:\\n{doc['content_md']}\\n"
|
+ (f"\\nZUSATZINFOS:\\n{user_extra}\\n" if user_extra else "")
|
||||||
|
+ f"\\nTEXT:\\n{doc['content_md']}\\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
@@ -389,8 +397,8 @@ def enqueue_job(kind: str, **kwargs) -> int:
|
|||||||
with db() as c:
|
with db() as c:
|
||||||
cur = c.execute(
|
cur = c.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO jobs(kind,status,project_id,document_id,prompt_id,title,file_path,created_at)
|
INSERT INTO jobs(kind,status,project_id,document_id,prompt_id,title,file_path,user_prompt,created_at)
|
||||||
VALUES (?,?,?,?,?,?,?,?)
|
VALUES (?,?,?,?,?,?,?,?,?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
kind,
|
kind,
|
||||||
@@ -400,6 +408,7 @@ def enqueue_job(kind: str, **kwargs) -> int:
|
|||||||
kwargs.get("prompt_id"),
|
kwargs.get("prompt_id"),
|
||||||
kwargs.get("title"),
|
kwargs.get("title"),
|
||||||
kwargs.get("file_path"),
|
kwargs.get("file_path"),
|
||||||
|
kwargs.get("user_prompt") or None,
|
||||||
now_iso(),
|
now_iso(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1253,34 +1262,90 @@ def run_page():
|
|||||||
with db() as c:
|
with db() as c:
|
||||||
docs = c.execute("SELECT id,title,kind,created_at FROM documents ORDER BY id DESC LIMIT 200").fetchall()
|
docs = c.execute("SELECT id,title,kind,created_at FROM documents ORDER BY id DESC LIMIT 200").fetchall()
|
||||||
prompts = c.execute("SELECT id,name FROM prompts ORDER BY name").fetchall()
|
prompts = c.execute("SELECT id,name FROM prompts ORDER BY name").fetchall()
|
||||||
|
projects = c.execute("SELECT id,name FROM projects ORDER BY name").fetchall()
|
||||||
|
|
||||||
d_opts = "".join([f"<option value='{d['id']}'>#{d['id']} [{d['kind']}] {d['title']}</option>" for d in docs])
|
d_opts = "".join([f"<option value='{d['id']}'>#{d['id']} [{d['kind']}] {d['title']}</option>" for d in docs])
|
||||||
p_opts = "".join([f"<option value='{p['id']}'>{p['name']}</option>" for p in prompts])
|
p_opts = "".join([f"<option value='{p['id']}'>{p['name']}</option>" for p in prompts])
|
||||||
|
proj_opts = "".join([f"<option value='{p['id']}'>{p['name']}</option>" for p in projects])
|
||||||
|
|
||||||
body = f"""
|
body = f"""
|
||||||
<h2 class='h4 mb-3'>Prompt ausführen</h2>
|
<h2 class='h4 mb-3'>Prompt ausführen</h2>
|
||||||
<form method='post' action='/run' class='card'>
|
<ul class='nav nav-tabs mb-3' id='runTabs' role='tablist'>
|
||||||
<div class='mb-2'>
|
<li class='nav-item' role='presentation'>
|
||||||
<label class='form-label'>Dokument</label>
|
<button class='nav-link active' data-bs-toggle='tab' data-bs-target='#pane-single' type='button' role='tab'><i class='bi bi-file-earmark-text'></i> Einzeldokument</button>
|
||||||
<select class='form-select' name='document_id'>{d_opts}</select>
|
</li>
|
||||||
|
<li class='nav-item' role='presentation'>
|
||||||
|
<button class='nav-link' data-bs-toggle='tab' data-bs-target='#pane-project' type='button' role='tab'><i class='bi bi-folder2-open'></i> Projekt-Batch</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class='tab-content'>
|
||||||
|
<div class='tab-pane fade show active' id='pane-single' role='tabpanel'>
|
||||||
|
<form method='post' action='/run' class='card'>
|
||||||
|
<div class='mb-3'>
|
||||||
|
<label class='form-label'>Dokument</label>
|
||||||
|
<select class='form-select' name='document_id'>{d_opts}</select>
|
||||||
|
</div>
|
||||||
|
<div class='mb-3'>
|
||||||
|
<label class='form-label'>Prompt</label>
|
||||||
|
<select class='form-select' name='prompt_id'>{p_opts}</select>
|
||||||
|
</div>
|
||||||
|
<div class='mb-3'>
|
||||||
|
<label class='form-label'>Zusatzinfos <span class='text-secondary fw-normal'>(optional — wird dem LLM zusätzlich mitgegeben)</span></label>
|
||||||
|
<textarea class='form-control' name='user_prompt' rows='3' placeholder='z. B. Fokus auf Entscheidungen, Kontext zum Meeting …'></textarea>
|
||||||
|
</div>
|
||||||
|
<button class='btn btn-primary' type='submit'><i class='bi bi-play-circle'></i> Ausführen</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class='mb-2'>
|
<div class='tab-pane fade' id='pane-project' role='tabpanel'>
|
||||||
<label class='form-label'>Prompt</label>
|
<form method='post' action='/run/project' class='card'>
|
||||||
<select class='form-select' name='prompt_id'>{p_opts}</select>
|
<div class='text-secondary small mb-3'>Führt den gewählten Prompt für <strong>alle Transkripte</strong> des Projekts aus — je Transkript ein Job.</div>
|
||||||
|
<div class='mb-3'>
|
||||||
|
<label class='form-label'>Projekt</label>
|
||||||
|
<select class='form-select' name='project_id'>{proj_opts}</select>
|
||||||
|
</div>
|
||||||
|
<div class='mb-3'>
|
||||||
|
<label class='form-label'>Prompt</label>
|
||||||
|
<select class='form-select' name='prompt_id'>{p_opts}</select>
|
||||||
|
</div>
|
||||||
|
<div class='mb-3'>
|
||||||
|
<label class='form-label'>Zusatzinfos <span class='text-secondary fw-normal'>(optional — wird für alle Jobs mitgegeben)</span></label>
|
||||||
|
<textarea class='form-control' name='user_prompt' rows='3' placeholder='z. B. Fokus auf Entscheidungen, Kontext zum Meeting …'></textarea>
|
||||||
|
</div>
|
||||||
|
<button class='btn btn-primary' type='submit'><i class='bi bi-play-fill'></i> Alle Transkripte verarbeiten</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<button class='btn btn-primary' type='submit'>Ausführen (Qwen)</button>
|
</div>
|
||||||
</form>
|
|
||||||
"""
|
"""
|
||||||
return layout("Run", body)
|
return layout("Run", body)
|
||||||
|
|
||||||
|
|
||||||
@app.post("/run", response_class=HTMLResponse)
|
@app.post("/run", response_class=HTMLResponse)
|
||||||
def run_prompt(document_id: int = Form(...), prompt_id: int = Form(...)):
|
def run_prompt(document_id: int = Form(...), prompt_id: int = Form(...), user_prompt: str = Form("")):
|
||||||
with db() as c:
|
with db() as c:
|
||||||
doc = c.execute("SELECT id FROM documents WHERE id=?", (document_id,)).fetchone()
|
doc = c.execute("SELECT id FROM documents WHERE id=?", (document_id,)).fetchone()
|
||||||
prm = c.execute("SELECT id FROM prompts WHERE id=?", (prompt_id,)).fetchone()
|
prm = c.execute("SELECT id FROM prompts WHERE id=?", (prompt_id,)).fetchone()
|
||||||
if not doc or not prm:
|
if not doc or not prm:
|
||||||
raise HTTPException(404, "Dokument oder Prompt nicht gefunden")
|
raise HTTPException(404, "Dokument oder Prompt nicht gefunden")
|
||||||
|
|
||||||
job_id = enqueue_job("analysis", document_id=document_id, prompt_id=prompt_id)
|
job_id = enqueue_job("analysis", document_id=document_id, prompt_id=prompt_id, user_prompt=user_prompt.strip() or None)
|
||||||
return HTMLResponse(f"<meta http-equiv='refresh' content='0; url=/jobs?queued={job_id}'>")
|
return HTMLResponse(f"<meta http-equiv='refresh' content='0; url=/jobs?queued={job_id}'>")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/run/project", response_class=HTMLResponse)
|
||||||
|
def run_project(project_id: int = Form(...), prompt_id: int = Form(...), user_prompt: str = Form("")):
|
||||||
|
with db() as c:
|
||||||
|
prm = c.execute("SELECT id FROM prompts WHERE id=?", (prompt_id,)).fetchone()
|
||||||
|
transcripts = c.execute(
|
||||||
|
"SELECT id FROM documents WHERE project_id=? AND kind='transcript'",
|
||||||
|
(project_id,),
|
||||||
|
).fetchall()
|
||||||
|
if not prm:
|
||||||
|
raise HTTPException(404, "Prompt nicht gefunden")
|
||||||
|
if not transcripts:
|
||||||
|
raise HTTPException(400, "Keine Transkripte im Projekt gefunden")
|
||||||
|
|
||||||
|
up = user_prompt.strip() or None
|
||||||
|
for doc in transcripts:
|
||||||
|
enqueue_job("analysis", document_id=doc["id"], prompt_id=prompt_id, user_prompt=up)
|
||||||
|
|
||||||
|
return HTMLResponse("<meta http-equiv='refresh' content='0; url=/jobs'>")
|
||||||
|
|||||||
Reference in New Issue
Block a user