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
|
||||
c.execute("INSERT OR IGNORE INTO projects(name, created_at) VALUES (?,?)", ("Default", now_iso()))
|
||||
c.execute(
|
||||
@@ -342,10 +348,12 @@ def _process_analysis_job(job_id: int):
|
||||
if not doc or not prm:
|
||||
raise RuntimeError("Dokument oder Prompt nicht gefunden")
|
||||
|
||||
user_extra = (j.get("user_prompt") or "").strip()
|
||||
llm_prompt = (
|
||||
"Du bist ein präziser Assistent. Antworte auf Deutsch.\\n"
|
||||
f"AUFTRAG:\\n{prm['prompt']}\\n\\n"
|
||||
f"TEXT:\\n{doc['content_md']}\\n"
|
||||
f"AUFTRAG:\\n{prm['prompt']}\\n"
|
||||
+ (f"\\nZUSATZINFOS:\\n{user_extra}\\n" if user_extra else "")
|
||||
+ f"\\nTEXT:\\n{doc['content_md']}\\n"
|
||||
)
|
||||
|
||||
r = requests.post(
|
||||
@@ -389,8 +397,8 @@ def enqueue_job(kind: str, **kwargs) -> int:
|
||||
with db() as c:
|
||||
cur = c.execute(
|
||||
"""
|
||||
INSERT INTO jobs(kind,status,project_id,document_id,prompt_id,title,file_path,created_at)
|
||||
VALUES (?,?,?,?,?,?,?,?)
|
||||
INSERT INTO jobs(kind,status,project_id,document_id,prompt_id,title,file_path,user_prompt,created_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?)
|
||||
""",
|
||||
(
|
||||
kind,
|
||||
@@ -400,6 +408,7 @@ def enqueue_job(kind: str, **kwargs) -> int:
|
||||
kwargs.get("prompt_id"),
|
||||
kwargs.get("title"),
|
||||
kwargs.get("file_path"),
|
||||
kwargs.get("user_prompt") or None,
|
||||
now_iso(),
|
||||
),
|
||||
)
|
||||
@@ -1253,34 +1262,90 @@ def run_page():
|
||||
with db() as c:
|
||||
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()
|
||||
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])
|
||||
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"""
|
||||
<h2 class='h4 mb-3'>Prompt ausführen</h2>
|
||||
<form method='post' action='/run' class='card'>
|
||||
<div class='mb-2'>
|
||||
<label class='form-label'>Dokument</label>
|
||||
<select class='form-select' name='document_id'>{d_opts}</select>
|
||||
<ul class='nav nav-tabs mb-3' id='runTabs' role='tablist'>
|
||||
<li class='nav-item' role='presentation'>
|
||||
<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>
|
||||
</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 class='mb-2'>
|
||||
<label class='form-label'>Prompt</label>
|
||||
<select class='form-select' name='prompt_id'>{p_opts}</select>
|
||||
<div class='tab-pane fade' id='pane-project' role='tabpanel'>
|
||||
<form method='post' action='/run/project' class='card'>
|
||||
<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>
|
||||
<button class='btn btn-primary' type='submit'>Ausführen (Qwen)</button>
|
||||
</form>
|
||||
</div>
|
||||
"""
|
||||
return layout("Run", body)
|
||||
|
||||
|
||||
@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:
|
||||
doc = c.execute("SELECT id FROM documents WHERE id=?", (document_id,)).fetchone()
|
||||
prm = c.execute("SELECT id FROM prompts WHERE id=?", (prompt_id,)).fetchone()
|
||||
if not doc or not prm:
|
||||
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}'>")
|
||||
|
||||
|
||||
@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