feat(diarization-ui): prompt pipeline on upload + email delivery
- Upload page: select and reorder prompts via drag & drop; after transcription each prompt runs sequentially, feeding its output into the next prompt in the chain - After the last pipeline step the final document is optionally sent as a Markdown attachment via SMTP to selected recipients - Settings: new SMTP configuration section (host, port, user, password, sender, STARTTLS toggle) - Settings: managed list of email recipient addresses (add/delete) - DB migrations: jobs.pipeline_prompt_ids, jobs.email_to, email_addresses table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
363
app.py
363
app.py
@@ -1,5 +1,11 @@
|
||||
import email as email_lib
|
||||
import email.encoders
|
||||
import email.mime.base
|
||||
import email.mime.multipart
|
||||
import email.mime.text
|
||||
import json
|
||||
import os
|
||||
import smtplib
|
||||
import sqlite3
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
@@ -144,6 +150,26 @@ def init_db():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for col in ["pipeline_prompt_ids TEXT", "email_to TEXT"]:
|
||||
try:
|
||||
c.execute(f"ALTER TABLE jobs ADD COLUMN {col}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
c.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS email_addresses (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
label TEXT NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# defaults
|
||||
c.execute("INSERT OR IGNORE INTO projects(name, created_at) VALUES (?,?)", ("Default", now_iso()))
|
||||
c.execute(
|
||||
@@ -177,6 +203,41 @@ def set_setting(key: str, value: str):
|
||||
c.execute("INSERT OR REPLACE INTO settings(key, value) VALUES (?,?)", (key, value))
|
||||
|
||||
|
||||
def _send_email(to_addrs: list[str], subject: str, body_md: str, attachment_name: str):
|
||||
host = get_setting("smtp_host", "")
|
||||
if not host:
|
||||
raise RuntimeError("SMTP-Host nicht konfiguriert")
|
||||
port = int(get_setting("smtp_port", "587"))
|
||||
user = get_setting("smtp_user", "")
|
||||
password = get_setting("smtp_password", "")
|
||||
from_addr = get_setting("smtp_from", user)
|
||||
use_tls = get_setting("smtp_tls", "true") == "true"
|
||||
|
||||
msg = email.mime.multipart.MIMEMultipart()
|
||||
msg["From"] = from_addr
|
||||
msg["To"] = ", ".join(to_addrs)
|
||||
msg["Subject"] = subject
|
||||
msg.attach(email.mime.text.MIMEText("Anbei das Ergebnis als Markdown-Datei.", "plain", "utf-8"))
|
||||
|
||||
part = email.mime.base.MIMEBase("text", "markdown")
|
||||
part.set_payload(body_md.encode("utf-8"))
|
||||
email.encoders.encode_base64(part)
|
||||
part.add_header("Content-Disposition", "attachment", filename=attachment_name)
|
||||
msg.attach(part)
|
||||
|
||||
if use_tls:
|
||||
with smtplib.SMTP(host, port, timeout=30) as s:
|
||||
s.starttls()
|
||||
if user:
|
||||
s.login(user, password)
|
||||
s.sendmail(from_addr, to_addrs, msg.as_string())
|
||||
else:
|
||||
with smtplib.SMTP(host, port, timeout=30) as s:
|
||||
if user:
|
||||
s.login(user, password)
|
||||
s.sendmail(from_addr, to_addrs, msg.as_string())
|
||||
|
||||
|
||||
def layout(title: str, body: str) -> str:
|
||||
return f"""
|
||||
<!doctype html>
|
||||
@@ -381,7 +442,20 @@ def _process_upload_job(job_id: int):
|
||||
)
|
||||
new_doc_id = cur.lastrowid
|
||||
|
||||
pipeline_ids = json.loads(j.get("pipeline_prompt_ids") or "[]")
|
||||
email_to = json.loads(j.get("email_to") or "[]")
|
||||
_job_set(job_id, status="done", result_document_id=new_doc_id, finished_at=now_iso())
|
||||
|
||||
if pipeline_ids:
|
||||
first_id, rest = pipeline_ids[0], pipeline_ids[1:]
|
||||
enqueue_job(
|
||||
"analysis",
|
||||
project_id=j["project_id"],
|
||||
document_id=new_doc_id,
|
||||
prompt_id=first_id,
|
||||
pipeline_prompt_ids=json.dumps(rest),
|
||||
email_to=json.dumps(email_to),
|
||||
)
|
||||
except Exception as e:
|
||||
_job_set(job_id, status="error", error=str(e), finished_at=now_iso())
|
||||
|
||||
@@ -492,7 +566,27 @@ def _process_analysis_job(job_id: int):
|
||||
)
|
||||
new_doc_id = cur.lastrowid
|
||||
|
||||
pipeline_ids = json.loads(j.get("pipeline_prompt_ids") or "[]")
|
||||
email_to = json.loads(j.get("email_to") or "[]")
|
||||
_job_set(job_id, status="done", result_document_id=new_doc_id, finished_at=now_iso(), llm_response=answer, llm_thinking=acc_thinking or None)
|
||||
|
||||
if pipeline_ids:
|
||||
first_id, rest = pipeline_ids[0], pipeline_ids[1:]
|
||||
enqueue_job(
|
||||
"analysis",
|
||||
project_id=doc["project_id"],
|
||||
document_id=new_doc_id,
|
||||
prompt_id=first_id,
|
||||
pipeline_prompt_ids=json.dumps(rest),
|
||||
email_to=json.dumps(email_to),
|
||||
)
|
||||
elif email_to:
|
||||
safe_title = (doc["title"] or "ergebnis").replace("/", "_").replace(" ", "_")
|
||||
subject = f"VoiceLog: {prm['name']} – {doc['title']}"
|
||||
try:
|
||||
_send_email(email_to, subject, answer, f"{safe_title}_{prm['name']}.md")
|
||||
except Exception as mail_err:
|
||||
_job_set(job_id, error=f"[E-Mail-Fehler] {mail_err}")
|
||||
except Exception as e:
|
||||
_job_set(job_id, status="error", error=str(e), finished_at=now_iso())
|
||||
finally:
|
||||
@@ -505,8 +599,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,user_prompt,created_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?)
|
||||
INSERT INTO jobs(kind,status,project_id,document_id,prompt_id,title,file_path,user_prompt,pipeline_prompt_ids,email_to,created_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
||||
""",
|
||||
(
|
||||
kind,
|
||||
@@ -517,6 +611,8 @@ def enqueue_job(kind: str, **kwargs) -> int:
|
||||
kwargs.get("title"),
|
||||
kwargs.get("file_path"),
|
||||
kwargs.get("user_prompt") or None,
|
||||
kwargs.get("pipeline_prompt_ids") or None,
|
||||
kwargs.get("email_to") or None,
|
||||
now_iso(),
|
||||
),
|
||||
)
|
||||
@@ -594,6 +690,31 @@ def settings_page(msg: str = ""):
|
||||
current_repeat_penalty = get_setting("ollama_repeat_penalty", "1.15")
|
||||
current_repeat_last_n = get_setting("ollama_repeat_last_n", "128")
|
||||
|
||||
smtp_host = get_setting("smtp_host", "")
|
||||
smtp_port = get_setting("smtp_port", "587")
|
||||
smtp_user = get_setting("smtp_user", "")
|
||||
smtp_password = get_setting("smtp_password", "")
|
||||
smtp_from = get_setting("smtp_from", "")
|
||||
smtp_tls = get_setting("smtp_tls", "true") == "true"
|
||||
smtp_tls_checked = "checked" if smtp_tls else ""
|
||||
|
||||
with db() as c:
|
||||
email_addrs = c.execute("SELECT id, label, email FROM email_addresses ORDER BY label").fetchall()
|
||||
|
||||
email_addr_rows = "".join(
|
||||
f"""<tr>
|
||||
<td>{a["label"]}</td>
|
||||
<td>{a["email"]}</td>
|
||||
<td class='text-end'>
|
||||
<form method='post' action='/settings/email-addresses/{a["id"]}/delete' class='d-inline'
|
||||
onsubmit='return confirm("Adresse löschen?")'>
|
||||
<button class='btn btn-sm btn-outline-danger py-0 px-2'><i class='bi bi-trash'></i></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>"""
|
||||
for a in email_addrs
|
||||
)
|
||||
|
||||
think_checked = "checked" if current_think else ""
|
||||
|
||||
ctx_options = [("auto", "Automatisch (empfohlen)"), ("4096", "4 096"), ("8192", "8 192"),
|
||||
@@ -669,6 +790,62 @@ def settings_page(msg: str = ""):
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class='card mt-3' id='smtp'>
|
||||
<h5 class='h6 fw-semibold mb-3'><i class='bi bi-envelope text-primary me-1'></i> E-Mail / SMTP</h5>
|
||||
<form method='post' action='/settings/smtp'>
|
||||
<div class='row g-3'>
|
||||
<div class='col-12 col-md-6'>
|
||||
<label class='form-label'>SMTP-Host</label>
|
||||
<input class='form-control' name='smtp_host' value='{smtp_host}' placeholder='mail.example.com'>
|
||||
</div>
|
||||
<div class='col-6 col-md-2'>
|
||||
<label class='form-label'>Port</label>
|
||||
<input class='form-control' type='number' name='smtp_port' value='{smtp_port}' min='1' max='65535'>
|
||||
</div>
|
||||
<div class='col-6 col-md-4'>
|
||||
<label class='form-label'>Absender (From)</label>
|
||||
<input class='form-control' type='email' name='smtp_from' value='{smtp_from}' placeholder='voicelog@example.com'>
|
||||
</div>
|
||||
<div class='col-12 col-md-6'>
|
||||
<label class='form-label'>Benutzername</label>
|
||||
<input class='form-control' name='smtp_user' value='{smtp_user}' autocomplete='off'>
|
||||
</div>
|
||||
<div class='col-12 col-md-6'>
|
||||
<label class='form-label'>Passwort</label>
|
||||
<input class='form-control' type='password' name='smtp_password' value='{smtp_password}' autocomplete='new-password'>
|
||||
</div>
|
||||
<div class='col-12'>
|
||||
<div class='form-check form-switch'>
|
||||
<input class='form-check-input' type='checkbox' role='switch' id='smtpTls'
|
||||
name='smtp_tls' value='true' {smtp_tls_checked}>
|
||||
<label class='form-check-label' for='smtpTls'>STARTTLS verwenden</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='mt-3'>
|
||||
<button class='btn btn-primary' type='submit'><i class='bi bi-check2-circle'></i> Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class='card mt-3' id='email-adressen'>
|
||||
<h5 class='h6 fw-semibold mb-3'><i class='bi bi-person-lines-fill text-primary me-1'></i> E-Mail-Adressen</h5>
|
||||
{"<table class='table table-sm mb-3'><thead><tr><th>Name</th><th>E-Mail</th><th></th></tr></thead><tbody>" + email_addr_rows + "</tbody></table>" if email_addrs else "<p class='text-secondary small'>Noch keine Adressen gespeichert.</p>"}
|
||||
<form method='post' action='/settings/email-addresses' class='row g-2 align-items-end'>
|
||||
<div class='col-12 col-md-4'>
|
||||
<label class='form-label'>Name / Label</label>
|
||||
<input class='form-control' name='label' placeholder='z.B. Max Mustermann' required>
|
||||
</div>
|
||||
<div class='col-12 col-md-5'>
|
||||
<label class='form-label'>E-Mail-Adresse</label>
|
||||
<input class='form-control' type='email' name='email' placeholder='max@example.com' required>
|
||||
</div>
|
||||
<div class='col-auto'>
|
||||
<button class='btn btn-outline-primary' type='submit'><i class='bi bi-plus-lg'></i> Hinzufügen</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class='card mt-3'>
|
||||
<h5 class='h6 fw-semibold mb-2'><i class='bi bi-info-circle text-secondary me-1'></i> Systemkonfiguration (read-only)</h5>
|
||||
<table class='table table-sm mb-0'>
|
||||
@@ -736,16 +913,94 @@ def settings_save(
|
||||
return RedirectResponse("/settings?msg=Einstellungen+gespeichert.", status_code=303)
|
||||
|
||||
|
||||
@app.post("/settings/smtp", response_class=HTMLResponse)
|
||||
def settings_smtp_save(
|
||||
smtp_host: str = Form(""),
|
||||
smtp_port: str = Form("587"),
|
||||
smtp_user: str = Form(""),
|
||||
smtp_password: str = Form(""),
|
||||
smtp_from: str = Form(""),
|
||||
smtp_tls: Optional[str] = Form(None),
|
||||
):
|
||||
set_setting("smtp_host", smtp_host.strip())
|
||||
set_setting("smtp_port", smtp_port.strip() or "587")
|
||||
set_setting("smtp_user", smtp_user.strip())
|
||||
set_setting("smtp_from", smtp_from.strip())
|
||||
if smtp_password:
|
||||
set_setting("smtp_password", smtp_password)
|
||||
set_setting("smtp_tls", "true" if smtp_tls == "true" else "false")
|
||||
return RedirectResponse("/settings?msg=SMTP-Einstellungen+gespeichert.#smtp", status_code=303)
|
||||
|
||||
|
||||
@app.post("/settings/email-addresses", response_class=HTMLResponse)
|
||||
def add_email_address(label: str = Form(...), email: str = Form(...)):
|
||||
with db() as c:
|
||||
c.execute(
|
||||
"INSERT OR REPLACE INTO email_addresses(label, email, created_at) VALUES (?,?,?)",
|
||||
(label.strip(), email.strip().lower(), now_iso()),
|
||||
)
|
||||
return RedirectResponse("/settings?msg=E-Mail-Adresse+gespeichert.#email-adressen", status_code=303)
|
||||
|
||||
|
||||
@app.post("/settings/email-addresses/{addr_id}/delete", response_class=HTMLResponse)
|
||||
def delete_email_address(addr_id: int):
|
||||
with db() as c:
|
||||
c.execute("DELETE FROM email_addresses WHERE id=?", (addr_id,))
|
||||
return RedirectResponse("/settings?msg=E-Mail-Adresse+gelöscht.#email-adressen", status_code=303)
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
def upload_page(msg: str = ""):
|
||||
projects = get_projects()
|
||||
existing_names = json.dumps([p["name"] for p in projects], ensure_ascii=False)
|
||||
existing_map = json.dumps({p["name"]: p["id"] for p in projects}, ensure_ascii=False)
|
||||
|
||||
with db() as c:
|
||||
prompts = c.execute("SELECT id, name FROM prompts ORDER BY name").fetchall()
|
||||
email_addrs = c.execute("SELECT id, label, email FROM email_addresses ORDER BY label").fetchall()
|
||||
|
||||
smtp_configured = bool(get_setting("smtp_host", ""))
|
||||
|
||||
prompt_items = "".join(
|
||||
f"""<li class='list-group-item d-flex align-items-center gap-2 py-2' draggable='true'
|
||||
data-id='{p["id"]}' style='cursor:grab'>
|
||||
<span class='drag-handle text-secondary' style='cursor:grab'><i class='bi bi-grip-vertical'></i></span>
|
||||
<input class='form-check-input flex-shrink-0 mt-0' type='checkbox' id='pchk_{p["id"]}' value='{p["id"]}' onchange='updateOrder()'>
|
||||
<label class='form-check-label mb-0 flex-grow-1' for='pchk_{p["id"]}'>{p["name"]}</label>
|
||||
<span class='badge bg-secondary order-badge d-none'></span>
|
||||
</li>"""
|
||||
for p in prompts
|
||||
)
|
||||
|
||||
email_items = "".join(
|
||||
f"""<div class='form-check'>
|
||||
<input class='form-check-input' type='checkbox' id='eml_{a["id"]}' value='{a["email"]}' onchange='updateEmails()'>
|
||||
<label class='form-check-label' for='eml_{a["id"]}'>{a["label"]} <span class='text-secondary small'><{a["email"]}></span></label>
|
||||
</div>"""
|
||||
for a in email_addrs
|
||||
)
|
||||
|
||||
email_section = ""
|
||||
if smtp_configured and email_addrs:
|
||||
email_section = f"""
|
||||
<div class='card mt-3'>
|
||||
<h6 class='fw-semibold mb-2'><i class='bi bi-envelope text-primary me-1'></i> E-Mail-Versand (optional)</h6>
|
||||
<p class='text-secondary small mb-2'>Letztes Ergebnis der Pipeline nach Abschluss als Anhang versenden:</p>
|
||||
{email_items}
|
||||
<div id='emailPreview' class='text-secondary small mt-1 d-none'>Sende an: <span id='emailPreviewList'></span></div>
|
||||
</div>"""
|
||||
elif not smtp_configured:
|
||||
email_section = "<div class='text-secondary small mt-3'><i class='bi bi-envelope-x me-1'></i> E-Mail-Versand nicht konfiguriert — <a href='/settings#smtp'>Einstellungen</a></div>"
|
||||
elif not email_addrs:
|
||||
email_section = "<div class='text-secondary small mt-3'><i class='bi bi-envelope-x me-1'></i> Keine E-Mail-Adressen gespeichert — <a href='/settings#email-adressen'>Einstellungen</a></div>"
|
||||
|
||||
no_prompts_hint = "" if prompts else "<div class='text-secondary small'>Keine Prompts vorhanden — <a href='/prompts'>Prompts anlegen</a></div>"
|
||||
|
||||
body = f"""
|
||||
<div class='d-flex justify-content-between align-items-center mb-3'>
|
||||
<h2 class='h4 mb-0'>Audio Upload</h2>
|
||||
</div>
|
||||
<p class='text-secondary'>Mehrere Audiodateien gleichzeitig hochladen — je Datei wird ein Transkriptions-Job erstellt.</p>
|
||||
<p class='text-secondary'>Audiodateien hochladen, transkribieren lassen und optional eine Prompt-Pipeline durchlaufen.</p>
|
||||
{f"<div class='alert alert-info py-2'>{msg}</div>" if msg else ""}
|
||||
<div class='card'>
|
||||
<div class='row g-3 mb-3'>
|
||||
@@ -768,7 +1023,20 @@ def upload_page(msg: str = ""):
|
||||
<i class='bi bi-cloud-upload'></i> <span id='uploadBtnLabel'>Hochladen & verarbeiten</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id='uploadProgress' class='d-none'>
|
||||
|
||||
<div class='card mt-3'>
|
||||
<h6 class='fw-semibold mb-2'><i class='bi bi-list-ol text-primary me-1'></i> Prompt-Pipeline <span class='text-secondary fw-normal small'>(optional, Reihenfolge per Drag & Drop)</span></h6>
|
||||
{no_prompts_hint}
|
||||
<ul class='list-group list-group-flush' id='promptList' style='user-select:none'>
|
||||
{prompt_items}
|
||||
</ul>
|
||||
<div id='pipelinePreview' class='text-secondary small mt-2 d-none'>
|
||||
Pipeline: <span id='pipelinePreviewText'></span>
|
||||
</div>
|
||||
</div>
|
||||
{email_section}
|
||||
|
||||
<div id='uploadProgress' class='d-none mt-3'>
|
||||
<div class='progress mb-2' style='height:8px'>
|
||||
<div class='progress-bar progress-bar-striped progress-bar-animated' id='progressBar' style='width:0%'></div>
|
||||
</div>
|
||||
@@ -803,6 +1071,64 @@ fileInput.addEventListener('change', () => {{
|
||||
}});
|
||||
projectInput.addEventListener('input', updateBtn);
|
||||
|
||||
// --- Prompt pipeline ordering ---
|
||||
function updateOrder() {{
|
||||
const items = document.querySelectorAll('#promptList li');
|
||||
let n = 1;
|
||||
items.forEach(li => {{
|
||||
const chk = li.querySelector('input[type=checkbox]');
|
||||
const badge = li.querySelector('.order-badge');
|
||||
if (chk.checked) {{
|
||||
badge.textContent = n++;
|
||||
badge.classList.remove('d-none');
|
||||
}} else {{
|
||||
badge.classList.add('d-none');
|
||||
}}
|
||||
}});
|
||||
const checked = [...items].filter(li => li.querySelector('input').checked);
|
||||
const names = checked.map(li => li.querySelector('label').textContent.trim());
|
||||
const preview = document.getElementById('pipelinePreview');
|
||||
const previewText = document.getElementById('pipelinePreviewText');
|
||||
if (names.length) {{
|
||||
previewText.textContent = names.join(' → ');
|
||||
preview.classList.remove('d-none');
|
||||
}} else {{
|
||||
preview.classList.add('d-none');
|
||||
}}
|
||||
}}
|
||||
|
||||
// Drag-and-drop reorder
|
||||
let _dragSrc = null;
|
||||
document.getElementById('promptList').addEventListener('dragstart', e => {{
|
||||
_dragSrc = e.target.closest('li');
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
}});
|
||||
document.getElementById('promptList').addEventListener('dragover', e => {{
|
||||
e.preventDefault();
|
||||
const target = e.target.closest('li');
|
||||
if (target && target !== _dragSrc) {{
|
||||
const rect = target.getBoundingClientRect();
|
||||
const after = e.clientY > rect.top + rect.height / 2;
|
||||
target.parentNode.insertBefore(_dragSrc, after ? target.nextSibling : target);
|
||||
}}
|
||||
}});
|
||||
document.getElementById('promptList').addEventListener('dragend', () => {{ updateOrder(); }});
|
||||
|
||||
// --- Email selection ---
|
||||
let _selectedEmails = [];
|
||||
function updateEmails() {{
|
||||
_selectedEmails = [...document.querySelectorAll('#emailCheckboxes input:checked')].map(i => i.value);
|
||||
const preview = document.getElementById('emailPreview');
|
||||
const list = document.getElementById('emailPreviewList');
|
||||
if (!preview) return;
|
||||
if (_selectedEmails.length) {{
|
||||
list.textContent = _selectedEmails.join(', ');
|
||||
preview.classList.remove('d-none');
|
||||
}} else {{
|
||||
preview.classList.add('d-none');
|
||||
}}
|
||||
}}
|
||||
|
||||
async function startUpload() {{
|
||||
const name = projectInput.value.trim();
|
||||
if(!name || fileInput.files.length === 0) return;
|
||||
@@ -821,6 +1147,10 @@ async function startUpload() {{
|
||||
projectId = (await r.json()).id;
|
||||
}}
|
||||
|
||||
// collect pipeline prompt IDs in current list order, only checked ones
|
||||
const pipelineIds = [...document.querySelectorAll('#promptList li input:checked')].map(i => parseInt(i.value));
|
||||
const emailTo = [...document.querySelectorAll('[id^="eml_"]:checked')].map(i => i.value);
|
||||
|
||||
for(let i=0; i<files.length; i++) {{
|
||||
const f = files[i];
|
||||
const stem = f.name.replace(/\\.[^/.]+$/, '');
|
||||
@@ -829,6 +1159,8 @@ async function startUpload() {{
|
||||
const fd = new FormData();
|
||||
fd.append('project_id', projectId);
|
||||
fd.append('file', f);
|
||||
fd.append('pipeline_prompt_ids', JSON.stringify(pipelineIds));
|
||||
fd.append('email_to', JSON.stringify(emailTo));
|
||||
const r = await fetch('/upload', {{method:'POST', body: fd}});
|
||||
if(!r.ok) {{ status.textContent = `Fehler bei ${{stem}}`; }}
|
||||
}}
|
||||
@@ -885,7 +1217,12 @@ def create_project_api(name: str = Form(...)):
|
||||
|
||||
|
||||
@app.post("/upload")
|
||||
async def upload(project_id: int = Form(...), file: UploadFile = File(...)):
|
||||
async def upload(
|
||||
project_id: int = Form(...),
|
||||
file: UploadFile = File(...),
|
||||
pipeline_prompt_ids: str = Form("[]"),
|
||||
email_to: str = Form("[]"),
|
||||
):
|
||||
data = await file.read()
|
||||
if not data:
|
||||
raise HTTPException(400, "Leere Datei")
|
||||
@@ -896,11 +1233,27 @@ async def upload(project_id: int = Form(...), file: UploadFile = File(...)):
|
||||
temp_path = JOB_DIR / f"{now_iso().replace(':','-')}_{filename}"
|
||||
temp_path.write_bytes(data)
|
||||
|
||||
try:
|
||||
ids = json.loads(pipeline_prompt_ids)
|
||||
if not isinstance(ids, list):
|
||||
ids = []
|
||||
except Exception:
|
||||
ids = []
|
||||
|
||||
try:
|
||||
emails = json.loads(email_to)
|
||||
if not isinstance(emails, list):
|
||||
emails = []
|
||||
except Exception:
|
||||
emails = []
|
||||
|
||||
job_id = enqueue_job(
|
||||
"upload",
|
||||
project_id=project_id,
|
||||
title=stem,
|
||||
file_path=str(temp_path),
|
||||
pipeline_prompt_ids=json.dumps(ids) if ids else None,
|
||||
email_to=json.dumps(emails) if emails else None,
|
||||
)
|
||||
return {"job_id": job_id}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user