Files
diarization-ui/__pycache__/app.cpython-312.pyc

715 lines
65 KiB
Plaintext
Raw Normal View History

<EFBFBD>
<00> <0B>iI<69><00><00><> <00>ddlZddlZddlZddlZddlmZddlmZddlmZddl m
Z
ddl Z ddl Z ddlmZmZmZmZmZddlmZmZmZmZmZej4dd <09>j7d
<EFBFBD>Zej4d d <0C>j7d
<EFBFBD>Zej4d d<0E>Zej4dd<10>Zed<11><12>Z eej4dd<14><00>Z!ed<15><16>Z"ejF<00>Z$d<17>Z%de&fd<19>Z'd<1A>Z(de&de&de&fd<1D>Z)d<1E>Z*de+de&fd <20>Z,d!<21>Z-d"e+fd#<23>Z.d"e+fd$<24>Z/d"e+fd%<25>Z0d"e+fd&<26>Z1d'e&de+fd(<28>Z2e jgd)<29>d*<2A><00>Z4e jkd+<2B>d,<2C><00>Z6e jkd-<2D>d.<2E><00>Z7e jkd/<2F>d0<64><00>Z8e jkd1<64>d2<64><00>Z9e jkd
e<15>3<EFBFBD>dvd5e&fd6<64><05>Z:e jwd7e<15>3<EFBFBD>ed8<64>fd9e&fd:<3A><05>Z<e jwd;e<15>3<EFBFBD>ed8<64>ed8<64>fd<e+d9e&fd=<3D><05>Z=e jwd>e<15>3<EFBFBD>de+fd?<3F><04>Z>e jwd@e<15>3<EFBFBD>ed8<64>ed4<64>ed8<64>fde+de&dAefdB<64><05>Z?e jkdCe<15>3<EFBFBD>dwde
e&dDe&dEe&fdF<64><05>Z@e jkdGe<15>3<EFBFBD>dHe+fdI<64><04>ZAe jkdJe<16>3<EFBFBD>dHe+fdK<64><04>ZBe jwdLe<15>3<EFBFBD>ed8<64>fdHe+de&fdM<64><05>ZCe jwdNe<15>3<EFBFBD>ed8<64>fdHe+de+fdO<64><05>ZDe jwdPe<15>3<EFBFBD>dHe+fdQ<64><04>ZEe jkdRe<15>3<EFBFBD>dS<64><00>ZFe jkdT<64>dUe+fdV<64><04>ZGe jwdWe<15>3<EFBFBD>ed8<64>ed8<64>fd9e&dXe&fdY<64><05>ZHe jwdZe<15>3<EFBFBD>ed8<64>ed8<64>ed8<64>fd<e+d9e&dXe&fd[<5B><05>ZIe jwd\e<15>3<EFBFBD>dUe+fd]<5D><04>ZJd^e
e&de
efd_<64>ZKdxd`e
e&dae
e&de&fdb<64>ZLdydce+fdd<64>ZMe jkde<64>dydce+fdf<64><05>ZNe jwdg<64>d"e+fdh<64><04>ZOe jwdi<64>d"e+fdj<64><04>ZPe jwdk<64>d"e+fdl<64><04>ZQe jwdm<64>d"e+fdn<64><04>ZRe jkdoe<15>3<EFBFBD>dxdpe
e+fdq<64><05>ZSe jkdre<15>3<EFBFBD>ds<64><00>ZTe jwdre<15>3<EFBFBD>ed8<64>ed8<64>fdte+dUe+fdu<64><05>ZUy)z<>N)<01>ThreadPoolExecutor)<01>datetime)<01>Path)<01>Optional)<05>FastAPI<50>File<6C>Form<72> HTTPException<6F>
UploadFile)<05> HTMLResponse<73>PlainTextResponse<73>Response<73> JSONResponse<73>RedirectResponse<73>API_BASEzhttp://gx10.aquantico.lan:8093<39>/<2F>OLLAMA_BASE_URLzhttp://gx10.aquantico.lan:11434<33> OLLAMA_MODELz
qwen3.5:9b<39>DB_PATHz /data/ui.dbzDiarization UI)<01>title<6C>JOB_DIRz
/data/jobs<62>)<01> max_workersc<00>b<00>tjt<00>}tj|_|S<00>N)<05>sqlite3<65>connectr<00>Row<6F> row_factory)<01>conns <20>4/home/guru/.openclaw/workspace/diarization-ui/app.py<70>dbr"s!<00><00> <12>?<3F>?<3F>7<EFBFBD> #<23>D<EFBFBD><1E>{<7B>{<7B>D<EFBFBD><14> <0F>K<EFBFBD><00>returnc<00>F<00>tj<00>j<00>Sr)r<00>utcnow<6F> isoformat<61>r#r!<00>now_isor)!s<00><00> <13>?<3F>?<3F> <1C> &<26> &<26> (<28>(r#c <00>
<00>tjtjjt<00>d<01><02>t <00>5}|j d<03>|j d<04>|j d<05>|j d<06>|j ddt<00>f<02>|j d d
d t<00>t<00>f<04>|j d d d t<00>t<00>f<04>ddd<00>y#1swYyxYw)NT)<01>exist_okz<6B>
CREATE TABLE IF NOT EXISTS projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
created_at TEXT NOT NULL
)
a
CREATE TABLE IF NOT EXISTS prompts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
prompt TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
a<>
CREATE TABLE IF NOT EXISTS documents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
kind TEXT NOT NULL, -- transcript|analysis
title TEXT NOT NULL,
content_md TEXT NOT NULL,
source_document_id INTEGER,
prompt_id INTEGER,
raw_json TEXT,
created_at TEXT NOT NULL,
FOREIGN KEY(project_id) REFERENCES projects(id),
FOREIGN KEY(source_document_id) REFERENCES documents(id),
FOREIGN KEY(prompt_id) REFERENCES prompts(id)
)
a;
CREATE TABLE IF NOT EXISTS jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
kind TEXT NOT NULL, -- upload|analysis
status TEXT NOT NULL, -- queued|running|done|error
project_id INTEGER,
document_id INTEGER,
prompt_id INTEGER,
title TEXT,
file_path TEXT,
error TEXT,
result_document_id INTEGER,
created_at TEXT NOT NULL,
started_at TEXT,
finished_at TEXT
)
z=INSERT OR IGNORE INTO projects(name, created_at) VALUES (?,?)<29>DefaultzTINSERT OR IGNORE INTO prompts(name, prompt, created_at, updated_at) VALUES (?,?,?,?)<29>ZusammenfassunguHErstelle eine prägnante Zusammenfassung des Gesprächs in Stichpunkten.<2E>AufgabenukExtrahiere alle Aufgaben. Gib pro Aufgabe: Verantwortlich, Aufgabe, Deadline (falls vorhanden), Priorität.)<08>os<6F>makedirs<72>path<74>dirnamerr"<00>executer)<00><01>cs r!<00>init_dbr6%s<><00><00><06>K<EFBFBD>K<EFBFBD><02><07><07><0F><0F><07>(<28>4<EFBFBD>8<> <0B><14>N
<EFBFBD><11> <09> <09> <09> <10>
<EFBFBD>
<EFBFBD> <09> <09> <10>
<EFBFBD>
<EFBFBD> <09> <09> <10>
<EFBFBD>$
<EFBFBD> <09> <09> <10>
<EFBFBD>*
<EFBFBD> <09> <09>Q<>T]<5D>_f<5F>_h<5F>Si<53>j<> <09> <09> <09> b<>!<21>Z<><17> <09><17> <09>  <0E>
<EFBFBD>
<EFBFBD> <09> <09> b<><1A>}<7D><17> <09><17> <09>  <0E>
<EFBFBD>MN
<EFBFBD>N
<EFBFBD>N
<EFBFBD>s <00>B-C9<03>9Dr<00>bodyc<00><00>d|<00>d|<00>d|<01>d<04>S)Na
<!doctype html>
<html>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1, viewport-fit=cover'>
<meta name='theme-color' content='#0f172a'>
<link rel='manifest' href='/manifest.webmanifest'>
<link rel='icon' href='/icon.svg' type='image/svg+xml'>
<link href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css' rel='stylesheet'>
<link href='https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css' rel='stylesheet'>
<title>u</title>
<style>
:root{--sidebar:#0b1220;--sidebar-border:#1e293b;--page:#f3f6fb;--card:#ffffff;--text:#0f172a;--muted:#64748b;--accent:#2563eb}
*{box-sizing:border-box}
body{font-family:Inter,system-ui,Arial,sans-serif;background:var(--page);color:var(--text);margin:0;display:flex;min-height:100vh}
nav{width:260px;background:linear-gradient(180deg,#0b1220,#0f172a);color:#e2e8f0;border-right:1px solid var(--sidebar-border);padding:14px 10px;position:sticky;top:0;height:100vh;overflow:auto;z-index:60}
.brand{font-weight:800;letter-spacing:.2px;margin:6px 10px 14px;color:#dbeafe;font-size:1.05rem}
.navsection{margin:14px 10px 8px;color:#94a3b8;font-size:.72rem;text-transform:uppercase;letter-spacing:.08em}
nav a{display:flex;gap:10px;align-items:center;color:#cbd5e1;text-decoration:none;padding:10px 12px;border-radius:12px;margin:4px 6px;border:1px solid transparent;font-weight:500}
nav a i{font-size:1rem;opacity:.95;width:18px;text-align:center}
nav a:hover{background:#16243d;color:#fff;border-color:#2c3d63}
.app{flex:1;display:flex;flex-direction:column;min-width:0}
.topbar{height:62px;background:rgba(255,255,255,.92);backdrop-filter:blur(8px);border-bottom:1px solid #e2e8f0;display:flex;align-items:center;padding:0 18px;gap:12px;position:sticky;top:0;z-index:20}
.topbar h1{font-size:1.02rem;margin:0;font-weight:700}
.menu-btn{display:none;border:1px solid #dbe3ef;background:#fff;color:#0f172a;border-radius:10px;padding:.35rem .55rem;font-size:1rem;line-height:1}
main{padding:18px;max-width:1400px;width:100%;margin:0 auto}
.card{background:var(--card);border:1px solid #e2e8f0;border-radius:14px;padding:14px;margin:10px 0;box-shadow:0 4px 16px rgba(15,23,42,.05)}
pre{white-space:pre-wrap;background:#0f172a;color:#c7f9cc;padding:12px;border-radius:10px;border:1px solid #1e293b}
.mdview{line-height:1.6}
.mdview blockquote{border-left:3px solid #94a3b8;padding-left:10px;color:#334155}
.hint,small{color:var(--muted)}
.btn{--bs-btn-padding-y:.34rem;--bs-btn-padding-x:.72rem;--bs-btn-font-size:.9rem;white-space:nowrap;width:auto;max-width:100%}
.btn-group>.btn{width:auto}
.oc-modal-backdrop{position:fixed;inset:0;background:rgba(2,6,23,.55);display:none;align-items:center;justify-content:center;z-index:2000}
.oc-modal{width:min(92vw,540px);background:#fff;border:1px solid #dbe3ef;border-radius:16px;padding:16px;box-shadow:0 24px 60px rgba(2,6,23,.3)}
.oc-modal .actions{display:flex;gap:8px;justify-content:flex-end;margin-top:12px}
.iconbtn{text-decoration:none}
@media (max-width:980px){
nav{width:84px;padding:10px 6px}
nav a span:last-child,.navsection{display:none}
.brand{font-size:.78rem;margin:8px 8px 12px}
}
@media (max-width:760px){
body{display:block}
nav{position:fixed;left:0;top:0;bottom:0;width:270px;height:100vh;transform:translateX(-105%);transition:transform .22s ease;box-shadow:0 22px 42px rgba(2,6,23,.45)}
body.nav-open nav{transform:translateX(0)}
.nav-backdrop{display:none;position:fixed;inset:0;background:rgba(2,6,23,.45);z-index:55}
body.nav-open .nav-backdrop{display:block}
.menu-btn{display:inline-flex;align-items:center;justify-content:center}
.topbar{padding:0 12px}
main{padding:12px}
nav a span:last-child,.navsection{display:block}
}
</style>
<script>
if ('serviceWorker' in navigator) { window.addEventListener('load', () => navigator.serviceWorker.register('/sw.js').catch(()=>{})); }
window.toggleNav = function() { document.body.classList.toggle('nav-open'); };
window.closeNav = function() { document.body.classList.remove('nav-open'); };
window.showModal = function({title='Eingabe', html='', ok='OK', cancel='Abbrechen'}) {
return new Promise((resolve) => {
const bd=document.getElementById('modal-backdrop');
const box=document.getElementById('modal-box');
box.innerHTML = `<h4>${title}</h4><div>${html}</div><div class='actions'><button id='m-cancel' type='button' style='background:#334155;color:#fff'>${cancel}</button><button id='m-ok' type='button'>${ok}</button></div>`;
bd.style.display='flex';
const close=(v)=>{bd.style.display='none'; resolve(v);};
box.querySelector('#m-cancel').onclick=()=>close(null);
box.querySelector('#m-ok').onclick=()=>{
const inp=box.querySelector('[data-modal-input]');
if(inp) close(inp.value); else close(true);
};
});
};
window.uiPrompt = async function(title, value='') {
const v = await window.showModal({title, html:`<input data-modal-input value="${String(value).replace(/"/g,'&quot;')}">`});
return v;
};
window.uiConfirm = async function(title) {
const v = await window.showModal({title, html:`<p class='hint'>Bitte bestätigen.</p>`, ok:'Ja', cancel:'Nein'});
return v !== null;
};
window.uiSelect = async function(title, options, placeholder='') {
const opts = options.map(o=>`<option value="${o.value}">${o.label}</option>`).join('');
return await window.showModal({title, html:`<select data-modal-input><option value="">${placeholder}</option>${opts}</select>`});
};
window.applyBootstrap53 = function() {
document.querySelectorAll('button').forEach(el => { if (!el.className.includes('btn') && !el.classList.contains('menu-btn')) el.classList.add('btn','btn-primary'); });
document.querySelectorAll('input,select,textarea').forEach(el => {
if (!el.classList.contains('form-control') && !el.classList.contains('form-select')) {
if (el.tagName === 'SELECT') el.classList.add('form-select');
else el.classList.add('form-control');
}
});
document.querySelectorAll('table').forEach(el => { if (!el.className.includes('table')) el.classList.add('table','table-sm','table-striped','align-middle'); });
document.querySelectorAll('.row').forEach(el => { if (!el.className.includes('g-2')) el.classList.add('g-2'); });
};
window.addEventListener('DOMContentLoaded', () => {
window.applyBootstrap53();
const p = location.pathname;
document.querySelectorAll('nav a').forEach(a => {
if (a.getAttribute('href') === p) {
a.style.background = '#1d2d4a';
a.style.color = '#fff';
a.style.borderColor = '#34507c';
}
});
});
</script>
</head>
<body>
<div id='modal-backdrop' class='oc-modal-backdrop'><div id='modal-box' class='oc-modal'></div></div>
<div class='nav-backdrop' onclick='closeNav()'></div>
<nav>
<div class='brand'>VoiceLog</div>
<div class='navsection'>Workspace</div>
<a href='/' onclick='closeNav()'><i class='bi bi-cloud-arrow-up'></i><span>Upload</span></a>
<a href='/library' onclick='closeNav()'><i class='bi bi-folder2-open'></i><span>Library</span></a>
<a href='/prompts' onclick='closeNav()'><i class='bi bi-sliders2'></i><span>Prompts & Projekte</span></a>
<div class='navsection'>Automation</div>
<a href='/run' onclick='closeNav()'><i class='bi bi-play-circle'></i><span>Prompt ausführen</span></a>
<a href='/jobs' onclick='closeNav()'><i class='bi bi-cpu'></i><span>Hintergrundjobs</span></a>
<div class='navsection'>System</div>
<a href='/healthz' onclick='closeNav()'><i class='bi bi-heart-pulse'></i><span>Health</span></a>
</nav>
<div class='app'>
<div class='topbar'>
<button class='menu-btn' type='button' onclick='toggleNav()'><i class='bi bi-list'></i></button>
<h1>z4</h1>
</div>
<main class='container-fluid py-2'>z<></main>
</div>
<script src='https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js'></script>
</body></html>
r()rr7s r!<00>layoutr9xs8<00><00> <08> <0E>w<EFBFBD>v <09>l
<0F><07>&<26>&*<2A>V<EFBFBD>,<01>GG <04>Gr#c<00><><00>t<00>5}|jd<01>j<00>cddd<00>S#1swYyxYw)N<>*SELECT id,name FROM projects ORDER BY name<6D>r"r3<00>fetchallr4s r!<00> get_projectsr>s8<00><00> <0B><14>R<01><11><10>y<EFBFBD>y<EFBFBD>E<>F<>O<>O<>Q<>R<01>R<01>R<01><><00>4<03>=<07>
project_idc<00><><00>t<00>5}|jd|f<01>j<00>}ddd<00>r|dSdS#1swY<00>xYw)Nz$SELECT name FROM projects WHERE id=?r<00><00>r"r3<00>fetchone)r@r5<00>rs r!<00>get_project_namerFsO<00><00> <0B><14>X<01><11> <0A>I<EFBFBD>I<EFBFBD><<3C>z<EFBFBD>m<EFBFBD> L<> U<> U<> W<><01>X<01><14>1<EFBFBD>Q<EFBFBD>4<EFBFBD><1C>"<22><1C>X<01>X<01>s <00>"><03>Ac<00><><00>t<00>5}|jd<01>j<00>cddd<00>S#1swYyxYw)Nz0SELECT id,name,prompt FROM prompts ORDER BY namer<r4s r!<00> get_promptsrHs8<00><00> <0B><14>X<01><11><10>y<EFBFBD>y<EFBFBD>K<>L<>U<>U<>W<>X<01>X<01>X<01>r?<00>job_idc<00><><00>t<00>5}|jd|f<01>j<00>cddd<00>S#1swYyxYw)NzSELECT * FROM jobs WHERE id=?rC<00>rIr5s r!<00>_job_getrLs<<00><00> <0B><14>P<01><11><10>y<EFBFBD>y<EFBFBD>8<>6<EFBFBD>)<29>D<>M<>M<>O<>P<01>P<01>P<01>s<00>!6<03>?c <00><00>|sydj|j<00>D<00>cgc]}|<02>d<02><02><02> c}<02>}t|j<00><00>|gz}t <00>5}|j d|<03>d<04>|<04>ddd<00>ycc}w#1swYyxYw)N<>, z=?zUPDATE jobs SET z WHERE id=?)<06>join<69>keys<79>list<73>valuesr"r3)rI<00>fields<64>k<>cols<6C>valsr5s r!<00>_job_setrWs<><00><00> <11><0E> <0F>9<EFBFBD>9<EFBFBD><06> <0B> <0B> <0A>6<>1<EFBFBD><11><03>2<EFBFBD>h<EFBFBD>6<> 7<>D<EFBFBD> <0F><06> <0A> <0A><0F> <20>F<EFBFBD>8<EFBFBD> +<2B>D<EFBFBD> <0B><14>><3E><11> <09> <09> <09>$<24>T<EFBFBD>F<EFBFBD>+<2B>6<><04>=<3D>><3E>><3E><>7<>><3E>><3E>s<00> A;<08>B<03>B c <00>R<00>t|<00>}|sy|ddk(ryt|dt<00><00><04> t|<00>}|r|ddk(ryt|d<00>}|j <00>}|j
}d||dfi}t jt<00>d<08>|d <09>
<EFBFBD>}|j<00>|j<00>}t|<00>}|r|ddk(ry|jd d <0C>}|d xsd j<00>xs|} t<00>5}
|
jd|dd| |tj|d<11><12>t<00>f<06>} | j } ddd<00>t|d t<00><00><14>y#1swY<00>!xYw#t"$r*} t|dt%| <0A>t<00><00><16>Yd} ~ yd} ~ wwxYw)N<>status<75> cancelled<65>running<6E>rY<00>
started_at<EFBFBD> file_path<74>filezapplication/octet-streamz/transcribe-diarizei)<02>files<65>timeout<75>formatted_textrBrzeINSERT INTO documents(project_id, kind, title, content_md, raw_json, created_at) VALUES (?,?,?,?,?,?)r@<00>
transcriptF<EFBFBD><01> ensure_ascii<69>done<6E>rY<00>result_document_id<69> finished_at<61>error<6F>rYrjri)rLrWr)r<00>
read_bytes<EFBFBD>name<6D>requests<74>postr<00>raise_for_status<75>json<6F>get<65>stripr"r3<00>dumps<70> lastrowid<69> Exception<6F>str)rI<00>j<>p<>data<74>filenamer`rE<00>payload<61>
content_md<EFBFBD> doc_titler5<00>cur<75>
new_doc_id<EFBFBD>es r!<00>_process_upload_jobr<62>!s<><00><00><10><16><18>A<EFBFBD> <0C><0E><08><18>{<7B>k<EFBFBD>!<21><0E> <0C>V<EFBFBD>I<EFBFBD>'<27>)<29><<3C>N<01> <14>V<EFBFBD> <1C><01><10>A<EFBFBD>h<EFBFBD>K<EFBFBD>;<3B>.<2E> <12> <10><11>;<3B><1E> <20><01><10>|<7C>|<7C>~<7E><04><14>6<EFBFBD>6<EFBFBD><08><17>(<28>D<EFBFBD>*D<>E<>F<><05> <14>M<EFBFBD>M<EFBFBD>X<EFBFBD>J<EFBFBD>&9<>:<3A>%<25>QU<51> V<><01> <09><1A><1A><1C><13>&<26>&<26>(<28><07> <14>V<EFBFBD> <1C><01><10>A<EFBFBD>h<EFBFBD>K<EFBFBD>;<3B>.<2E> <12><1C>[<5B>[<5B>!1<>2<EFBFBD>6<>
<EFBFBD><16>w<EFBFBD>Z<EFBFBD>%<25>2<EFBFBD>,<2C>,<2C>.<2E>:<3A>(<28> <09> <0F>T<EFBFBD> '<27>Q<EFBFBD><13>)<29>)<29>w<><12><<3C><1F>,<2C> <09>:<3A>t<EFBFBD>z<EFBFBD>z<EFBFBD>RY<52>hm<68>Gn<47>pw<70>py<70>z<><0E>C<EFBFBD><1D><1D><1D>J<EFBFBD>  '<27> <11><16><06>:<3A>SZ<53>S\<5C>]<5D> '<27> '<27><> <15>N<01><10><16><07>s<EFBFBD>1<EFBFBD>v<EFBFBD>7<EFBFBD>9<EFBFBD>M<>M<><4D>N<01>s=<00>E3<00>BE3<00> 7E3<00>AE'<03>E3<00>'E0<07>,E3<00>3 F&<03>< F!<03>!F&c<00><00>t|<00>}|sy|ddk(ryt|dt<00><00><04> t|<00>}|r|ddk(ryt<00>5}|j d|df<01>j <00>}|j d|df<01>j <00>}ddd<00>rs t d <09><00>d
|d <00>d |d <00>d<0E>}tjt<00>d<0F>t|dd<11>d<12><13>}|j<00>|j<00>jdd<15>}t|<00>}|r|ddk(ryt<00>5}|j d|ddd|d<00>d|d<00><00>||d|dtjd|j<00>id<10><1F>t<00>f<08>}|j} ddd<00>t|d  t<00><00>!<21>y#1swY<00><01>(xYw#1swY<00>.xYw#t $r*}
t|d"t#|
<EFBFBD>t<00><00>#<23>Yd}
~
yd}
~
wwxYw)$NrYrZr[r\z"SELECT * FROM documents WHERE id=?<3F> document_idz SELECT * FROM prompts WHERE id=?<3F> prompt_id<69>#Dokument oder Prompt nicht gefundenuBDu bist ein präziser Assistent. Antworte auf Deutsch.\nAUFTRAG:\n<>promptz \n\nTEXT:\nr}z\nz /api/generateF)<03>modelr<6C><00>streami<6D>)rqra<00>responserBz<>
INSERT INTO documents(project_id, kind, title, content_md, source_document_id, prompt_id, raw_json, created_at)
VALUES (?,?,?,?,?,?,?,?)
r@<00>analysisz Analyse: rm<00> · r<00>id<69>ollama_responserdrfrgrjrk)rLrWr)r"r3rD<00> RuntimeErrorrnrorrrprqrrrtrurvrw) rIrxr5<00>doc<6F>prm<72>
llm_promptrE<00>answerrr<>r<>s r!<00>_process_analysis_jobr<62>Gs*<00><00><10><16><18>A<EFBFBD> <0C><0E><08><18>{<7B>k<EFBFBD>!<21><0E> <0C>V<EFBFBD>I<EFBFBD>'<27>)<29><<3C>1N<01> <14>V<EFBFBD> <1C><01><10>A<EFBFBD>h<EFBFBD>K<EFBFBD>;<3B>.<2E> <12> <0F>T<EFBFBD> ^<01>Q<EFBFBD><13>)<29>)<29>@<40>1<EFBFBD>]<5D>CS<43>BU<42>V<>_<>_<>a<>C<EFBFBD><13>)<29>)<29>><3E><11>;<3B><1E>@Q<>R<>[<5B>[<5B>]<5D>C<EFBFBD> ^<01><13>#<23><1E>D<>E<> E<> <1A><1D>h<EFBFBD>-<2D><1F>)<17><1A><<3C>(<28>)<29><13> .<2E> <13> <15>M<EFBFBD>M<EFBFBD><1E><1F>}<7D> -<2D>'<27>:<3A><15>O<><18>
<EFBFBD><01>
<EFBFBD><1A><1A><1C><12><16><16><18><1C><1C>j<EFBFBD>"<22>-<2D><06> <14>V<EFBFBD> <1C><01><10>A<EFBFBD>h<EFBFBD>K<EFBFBD>;<3B>.<2E> <12> <0F>T<EFBFBD> '<27>Q<EFBFBD><13>)<29>)<29><14>
<18> <0C>%<25><1E><1F><03>F<EFBFBD> <0B>}<7D>D<EFBFBD><13>W<EFBFBD><1C><0E>?<3F><1A><17><04>I<EFBFBD><17><04>I<EFBFBD><18>J<EFBFBD>J<EFBFBD> 1<>1<EFBFBD>6<EFBFBD>6<EFBFBD>8<EFBFBD><<3C>5<EFBFBD>Q<><1B>I<EFBFBD> <12> <0E>C<EFBFBD> <1D><1D><1D>J<EFBFBD># '<27>& <11><16><06>:<3A>SZ<53>S\<5C>]<5D>W ^<01> ^<01><>0 '<27> '<27><>( <15>N<01><10><16><07>s<EFBFBD>1<EFBFBD>v<EFBFBD>7<EFBFBD>9<EFBFBD>M<>M<><4D>N<01>sV<00>G<00>
G<00>A F><03>BG<00>+
G<00>5A)G <03>G<00>>G<07>G<00> G<07>G<00> H
<03> H<03>H
<03>kindc <00><><00>t5t<00>5}|jd|d|jd<03>|jd<04>|jd<05>|jd<06>|jd<07>t <00>f<08>}|j
}ddd<00>ddd<00>|dk(rt jt<04>|S|d k(rt jt<04>S#1swY<00>SxYw#1swY<00>WxYw)
Nz<EFBFBD>
INSERT INTO jobs(kind,status,project_id,document_id,prompt_id,title,file_path,created_at)
VALUES (?,?,?,?,?,?,?,?)
<20>queuedr@r<>r<>rr^<00>uploadr<64>)
<EFBFBD>JOB_LOCKr"r3rrr)ru<00>EXECUTOR<4F>submitr<74>r<>)r<><00>kwargsr5rrIs r!<00> enqueue_jobr<62><00>s<><00><00> <11>#<23> <0F>T<EFBFBD> #<23>Q<EFBFBD><13>)<29>)<29><14>
<19><1C><1A>J<EFBFBD>J<EFBFBD>|<7C>,<2C><1A>J<EFBFBD>J<EFBFBD>}<7D>-<2D><1A>J<EFBFBD>J<EFBFBD>{<7B>+<2B><1A>J<EFBFBD>J<EFBFBD>w<EFBFBD>'<27><1A>J<EFBFBD>J<EFBFBD>{<7B>+<2B><1B>I<EFBFBD> <12> <0E>C<EFBFBD> <19>]<5D>]<5D>F<EFBFBD># #<23>#<23>( <0C>x<EFBFBD><17><10><0F><0F>+<2B>V<EFBFBD>4<> <12>M<EFBFBD>
<0E><1A> <1B><10><0F><0F>-<2D>v<EFBFBD>6<> <11>M<EFBFBD>/ #<23> #<23><>#<23>#<23>s#<00> C*<03>A:C<05> C*<03>C' <09>#C*<03>*C3<07>startupc<00>F<00>t<00>tjdd<01><02>y)NT<4E><02>parentsr+)r6r<00>mkdirr(r#r!r<>r<><00>s<00><00> <0B>I<EFBFBD> <0B>M<EFBFBD>M<EFBFBD>$<24><14>M<EFBFBD>.r#z/manifest.webmanifestc <00>"<00>dddddddddd d
<EFBFBD>gd <0B>S) N<>VoiceLogr<00>
standalonez#020617z#0f172a<32> /icon.svg<76>any<6E> image/svg+xmlz any maskable)<04>src<72>sizes<65>type<70>purpose)rm<00>
short_name<EFBFBD> start_url<72>display<61>background_color<6F> theme_color<6F>iconsr(r(r#r!<00>manifestr<74><00>s2<00><00><1B> <20><18><1F>%<25> <20><1F>%<25><1F>Uc<55> d<>
<EFBFBD>
<06>
r#r<>c<00> <00>d}t|d<02><03>S)Nu<4E><svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'><rect rx='24' width='128' height='128' fill='#0f172a'/><text x='64' y='82' font-size='72' text-anchor='middle'>🎙️</text></svg>r<><00><02>content<6E>
media_type<EFBFBD>r)<01>svgs r!<00>icon_svgr<67><00>s<00><00> R<03>C<EFBFBD> <13>C<EFBFBD>O<EFBFBD> <<3C><r#z/sw.jsc<00> <00>d}t|d<02><03>S)NaY
self.addEventListener('install', (event) => { self.skipWaiting(); });
self.addEventListener('activate', (event) => { event.waitUntil(clients.claim()); });
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return;
event.respondWith(fetch(event.request).catch(() => new Response('offline', {status: 503})));
});
zapplication/javascriptr<74>r<>)<01>jss r!<00>sw_jsr<73><00>s<00><00>
<04>B<EFBFBD> <14>B<EFBFBD>+C<> D<>Dr#z/healthzc<00>2<00>dttttd<02>S)NT)<05>ok<6F>api_base<73>ollama_base_url<72> ollama_model<65>db_path)rrrrr(r#r!<00>healthzr<7A><00>s<00><00><16>H<EFBFBD><1F>bn<62>|C<02> D<02>Dr#)<01>response_classrB<00>msgc
<00><><00>t<00>}dj|D<00>cgc]}d|d<00>d|d<00>d<06><05><02>c}<02>}d|rd|<00>d <09>nd<01>d
|<03>d <0B>}td |<04>Scc}w) NrB<00><option value='r<><00>'>rm<00> </option>z<>
<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'>Audio wird transkribiert + mit Sprechern angereichert und als Dokument gespeichert.</p>
z#<div class='alert alert-info py-2'><3E></div>z<>
<form action='/upload' method='post' enctype='multipart/form-data' class='card'>
<div class='row g-2'>
<div class='col-12 col-lg-4'>
<label class='form-label'>Projekt</label>
<select class='form-select' name='project_id'>a</select>
</div>
<div class='col-12 col-lg-4'>
<label class='form-label'>Titel (optional)</label>
<input class='form-control' name='title' placeholder='z. B. Team-Call 23.03'>
</div>
<div class='col-12 col-lg-4'>
<label class='form-label'>Audio-Datei</label>
<input class='form-control' type='file' name='file' accept='audio/*' required>
</div>
</div>
<div class='mt-3'>
<button class='btn btn-primary' type='submit'>Verarbeiten & speichern</button>
</div>
</form>
<EFBFBD>Upload)r>rOr9)r<><00>projectsry<00>optsr7s r!<00> upload_pager<65><00>s<><00><00><1B>~<7E>H<EFBFBD> <0A>7<EFBFBD>7<EFBFBD>x<EFBFBD>X<>!<21>o<EFBFBD>a<EFBFBD><04>g<EFBFBD>Y<EFBFBD>b<EFBFBD><11>6<EFBFBD><19> <0B>9<EFBFBD>E<>X<> Y<>D<EFBFBD><01>
7:<3A>&<26>s<EFBFBD>e<EFBFBD>6<EFBFBD>2<>r<EFBFBD>B<>C5<>
6:<3A>F<EFBFBD>;<01> <04>D<EFBFBD>4 <12>(<28>D<EFBFBD> !<21>!<21><>7Ys<00>Az /projects.rmc<00><><00>t<00>5}|jd|j<00>t<00>f<02>ddd<00>t d<02>S#1swYt d<02>SxYw)N<>3INSERT INTO projects(name, created_at) VALUES (?,?)<29>5<meta http-equiv='refresh' content='0; url=/prompts'><3E>r"r3rsr)r )rmr5s r!<00> add_projectr<74><00>sR<00><00> <0B><14>d<01><11> <09> <09> <09>G<>$<24>*<2A>*<2A>,<2C>X_<58>Xa<58>Ib<49>c<>d<01> <17>O<> P<>P<>d<01> <17>O<> P<>P<>s <00>+A <03> Az/projects/updater<65>c<00><><00>t<00>5}|jd|j<00>|f<02>ddd<00>td<02>S#1swYtd<02>SxYw)Nz%UPDATE projects SET name=? WHERE id=?r<><00>r"r3rsr )r<>rmr5s r!<00>rename_projectr<74><00>sN<00><00> <0B><14>O<01><11> <09> <09> <09>9<>D<EFBFBD>J<EFBFBD>J<EFBFBD>L<EFBFBD>"<22>;M<>N<>O<01> <17>O<> P<>P<>O<01> <17>O<> P<>P<><50> <00>#A<03>Az/projects/{project_id}/deletec<00><><00>t<00>5}|jd<01>j<00>}|s?|jddt<00>f<02>|jd<01>j<00>d}n|d}||k(rt d<05>cddd<00>S|jd||f<02>|jd|f<01>ddd<00>t d<05>S#1swYt d<05>SxYw)Nz,SELECT id FROM projects WHERE name='Default'r<>r,rr<>z4UPDATE documents SET project_id=? WHERE project_id=?zDELETE FROM projects WHERE id=?)r"r3rDr)r )r@r5<00>default<6C>
default_ids r!<00>delete_projectr<74><00>s<><00><00> <0B><14>
D<01><11><13>)<29>)<29>J<>K<>T<>T<>V<><07><16> <0A>I<EFBFBD>I<EFBFBD>K<>i<EFBFBD>Y`<60>Yb<59>Mc<4D> d<><1A><19><19>#Q<>R<>[<5B>[<5B>]<5D>^_<>`<60>J<EFBFBD> <20><11><1A>J<EFBFBD> <15><1A> #<23><1F> W<>X<>
D<01>
D<01>
<EFBFBD> <09> <09>H<>:<3A>Wa<57>Jb<4A>c<> <09> <09> <09>3<>j<EFBFBD>]<5D>C<>
D<01> <18>O<> P<>P<>
D<01> <18>O<> P<>P<>s<00>A5C<03>
'C<03>Cz/uploadr_c<00><00><>K<00>|j<00><00>d{<00><02><03>}|s tdd<02><00>tjdd<03><04>|jxsdj dd<07>}tt <00>j dd <09><00>d|<04><00>z }|j|<03>td
||xsd j<00>xs|t|<05><00> <0C>}td |<06>d<0E><03>S7<00><><EFBFBD>w)Ni<4E>z Leere DateiTr<54>z audio.binr<00>_<>:<3A>-r<>rB)r@rr^<00>8<meta http-equiv='refresh' content='0; url=/jobs?queued=r<>) <0C>readr
rr<>r{<00>replacer)<00> write_bytesr<73>rsrwr )r@rr_rzr{<00> temp_pathrIs r!r<>r<> s<><00><00><><00><15><19><19><1B> <1C>D<EFBFBD> <0F><1B>C<EFBFBD><1D>/<2F>/<2F> <0B>M<EFBFBD>M<EFBFBD>$<24><14>M<EFBFBD>.<2E><14> <0A> <0A>,<2C><1B>5<>5<>c<EFBFBD>3<EFBFBD>?<3F>H<EFBFBD><17>W<EFBFBD>Y<EFBFBD>.<2E>.<2E>s<EFBFBD>3<EFBFBD>7<>8<><01>(<28><1A>D<>D<>I<EFBFBD> <0A><19><19>$<24><1F> <18><10><1D><14>{<7B><02>!<21>!<21>#<23>/<2F>x<EFBFBD><15>i<EFBFBD>.<2E> <06>F<EFBFBD> <18>R<>SY<53>RZ<52>Z\<5C>]<5D> ^<5E>^<5E> <1D>s<00>C<01>C<04>B>Cz/library<72>q_title<6C> q_contentc<00><00>|xsdj<00>}|xsdj<00>}|r$t|<00>j<00>r t|<00>nd}g}g}|r"|jd<02>|j|<05>|r&|jd<03>|jd|<03>d<04><03>|r&|jd<05>|jd|<04>d<04><03>|rddj |<06>znd}t <00>5} | j d<08>j<00>}
| j d |<08>d
<EFBFBD>t|<07><00>j<00>} ddd<00>d dj 
D<00> cgc]} d | d <00>d|| d k(rdnd<01>d| d<00>d<12><07><02> c} <0C>z} dj  D<00>cgc]Z}d|d <00>d|d<00>d|d<00>d|d<00>d|d<00>d|d <00>d|d <00>d|d <00>dtj|d<00><00>d|d <00>d|d <00>d <20><17><02>\c}<0E>}tj|
D<00> cgc] } | d | dd!<21><02><02>c} d"<22>#<23>}d$t| <0B><00>d%| <0A>d&|jd'd(<28><00>d)|jd'd(<28><00>d*|xsd+<2B>d,|<10>d-<2D> }td.|<11>S#1swY<00><01>7xYwcc} wcc}wcc} w)/NrBzd.project_id=?zLOWER(d.title) LIKE LOWER(?)<29>%z!LOWER(d.content_md) LIKE LOWER(?)zWHERE z AND r;z<>
SELECT d.id,d.kind,d.title,d.created_at,p.name AS project
FROM documents d JOIN projects p ON p.id=d.project_id
z6
ORDER BY d.id DESC LIMIT 500
z<option value=''>Alle</option>r<>r<>z' <20>selected<65>>rmr<>z <tr><td>#z </td><td>rr<><00>projectz</td><td><small><3E>
created_atzU</small></td><td><div class='btn-group btn-group-sm' role='group'><a href='/document/uR' class='btn btn-outline-secondary' title='Ansehen'>👁️</a><a href='/document/u<>/download.md' class='btn btn-outline-secondary' title='Download'>⬇️</a><a href='#' class='btn btn-outline-secondary' title='Umbenennen' onclick='libRename(rNun);return false;'>✏️</a><a href='#' class='btn btn-outline-secondary' title='Verschieben' onclick='libMove(uh);return false;'>📁</a><a href='#' class='btn btn-outline-danger' title='Löschen' onclick='libDelete(u,);return false;'>🗑️</a></div></td></tr><3E><02>value<75>labelFrdu<>
<div class='d-flex justify-content-between align-items-center mb-2'>
<h2 class='h4 mb-0'>Projekte · Dokumente</h2>
<span class='badge text-bg-secondary'>aF Treffer</span>
</div>
<div class='text-secondary small mb-2'>Ansicht im Projektlisten-Stil mit Schnellaktionen.</div>
<form method='get' class='card'>
<div class='row g-2 align-items-end'>
<div class='col-12 col-md-3'>
<label class='form-label'>Projekt</label>
<select class='form-select' name='project_id'>u<></select>
</div>
<div class='col-12 col-md-3'>
<label class='form-label'>Titel enthält</label>
<input class='form-control' name='q_title' placeholder='Titel enthält …' value='<27>'z&#39;u<>'>
</div>
<div class='col-12 col-md-4'>
<label class='form-label'>Inhalt enthält</label>
<input class='form-control' name='q_content' placeholder='Inhalt enthält …' value='a<>'>
</div>
<div class='col-6 col-md-1 d-grid'><button class='btn btn-primary' type='submit'>Filtern</button></div>
<div class='col-6 col-md-1 d-grid'><a class='btn btn-outline-secondary' href='/library'>Reset</a></div>
</div>
</form>
<div class='card'>
<div class='table-responsive'>
<table class='table table-sm table-striped align-middle mb-0'>
<thead><tr><th>ID</th><th>Titel</th><th>Typ</th><th>Projekt</th><th>Erstellt</th><th>Aktionen</th></tr></thead>
<tbody>uE<tr><td colspan='6' class='text-secondary'>Keine Einträge.</td></tr>a,</tbody>
</table>
</div>
</div>
<script>
async function libPost(url, data) {
const r = await fetch(url, {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:new URLSearchParams(data)});
if(!r.ok) { alert('Fehler '+r.status); return; }
location.reload();
}
window.libRename = async function(id, current) {
const v = await window.uiPrompt('Dokument umbenennen', current || '');
if(v===null) return;
await libPost(`/document/${id}/rename`, {title:v});
};
window.libMove = async function(id) {
const options = ui;
const v = await window.uiSelect('In Projekt verschieben', options, 'Projekt wählen');
if(v===null || v==='') return;
await libPost(`/document/${id}/move`, {project_id:v});
};
window.libDelete = async function(id) {
const ok = await window.uiConfirm('Dokument löschen?');
if(!ok) return;
await libPost(`/document/${id}/delete`, {});
};
</script>
<EFBFBD>Library)rsrw<00>int<6E>appendrOr"r3r=<00>tuplerqrt<00>lenr<6E>r9)r@r<>r<><00>title_q<5F> content_q<5F>project_id_int<6E>where<72>params<6D> where_sqlr5r<><00>docsry<00>p_opts<74>d<>rows<77>
project_jsr7s r!<00>libraryrsY<00><00><16>}<7D>"<22>#<23>#<23>%<25>G<EFBFBD><1A><1F>b<EFBFBD>'<27>'<27>)<29>I<EFBFBD>)3<><03>J<EFBFBD><0F>8M<38>8M<38>8O<38>S<EFBFBD><1A>_<EFBFBD>VZ<56>N<EFBFBD> <0E>E<EFBFBD> <0F>F<EFBFBD><15> <0A> <0C> <0C>%<25>&<26><0E> <0A> <0A>n<EFBFBD>%<25><0E> <0A> <0C> <0C>3<>4<><0E> <0A> <0A><01>'<27><19>!<21>n<EFBFBD>%<25><10> <0A> <0C> <0C>8<>9<><0E> <0A> <0A><01>)<29><1B>A<EFBFBD>&<26>'<27>49<34><18>G<EFBFBD>L<EFBFBD>L<EFBFBD><15>/<2F>/<2F>r<EFBFBD>I<EFBFBD> <0B><14>
<15><11><14>9<EFBFBD>9<EFBFBD>I<>J<>S<>S<>U<><08><10>y<EFBFBD>y<EFBFBD> <0A><17>K<EFBFBD> <0A> <10> <12>&<26>M<EFBFBD>
<EFBFBD> <13>(<28>*<2A> <0A>
<15>.<2E><02><07><07>t|<7C>}<7D>op<6F>?<3F>1<EFBFBD>T<EFBFBD>7<EFBFBD>)<29>2<EFBFBD>N<EFBFBD>A<EFBFBD>d<EFBFBD>G<EFBFBD>4K<34>j<EFBFBD>QS<51>%T<>TU<54>VW<56>X^<5E>V_<56>U`<60>`i<> j<>}<7D>1<06><06>F<EFBFBD> <0E>7<EFBFBD>7<EFBFBD> <1A>
<EFBFBD><12><14><15>d<EFBFBD>G<EFBFBD>9<EFBFBD><13><14>W<EFBFBD>:<3A>,<2C><13><14>V<EFBFBD>9<EFBFBD>+<2B><13><14>Y<EFBFBD><<3C>.<2E>!<1A><1B>L<EFBFBD>/<2F>*<2A>+"<22>"#<23>D<EFBFBD>'<27><19>+"<22>"#<23>D<EFBFBD>'<27><19>+c<01>cd<63>ei<65>cj<63>bk<62>km<6B>nr<6E>nx<6E>nx<6E>yz<79>|C<02>zD<02>oE<02>nF<02>Fb<01>bc<62>dh<64>bi<62>aj<61>k^<01>^_<>`d<>^e<>]f<>g<14> <15>
<EFBFBD> <06>D<EFBFBD>&<16><1A><1A>X<EFBFBD>V<><01>q<EFBFBD><14>w<EFBFBD><11>6<EFBFBD><19>C<>V<>ej<65>k<>J<EFBFBD>)<29>*-<2D>T<EFBFBD><19> <0B>45<34>6<<3C>H<EFBFBD>=Z<01>[b<01>Zi<5A>Zi<5A>jm<6A>ov<6F>Zw<5A>Yx<59>y]<01>^g<01>]n<>]n<>or<6F>t{<7B>]|<7C>\}<7D>
~<0E><13>]<5D>]<5D> ^<5E>_<13> <1E>,<2C> <01>Y7 <04>D<EFBFBD>p <12>)<29>T<EFBFBD> "<22>"<22>w
<15>
<15><> ~<01><>
<EFBFBD><EFBFBD>$Ws <00>+A I-<03>#I:
<EFBFBD> AI?<08>J<08>-I7z/document/{doc_id}<7D>doc_idc<00><00>t<00>5}|jd|f<01>j<00>}ddd<00>s tdd<03><00>t j
|dxsdgd<06><01><07>}t <00>}d|d <00>d
|d <00>d |d <00>d|d<00>d|d<00>d|<00>d|<03>dtj|d <00><00>d|<00>dtj|D<00>cgc] }|d |dd<18><02><02>c}d<19><1A><00>d|<00>d|<00>d<1D>}td|<06>S#1swY<00><>xYwcc}w)Nz<4E>
SELECT d.*, p.name AS project, pr.name AS prompt_name
FROM documents d
JOIN projects p ON p.id=d.project_id
LEFT JOIN prompts pr ON pr.id=d.prompt_id
WHERE d.id=?
<20><><00> not foundr}rB<00><03> fenced_code<64>tables<65>nl2br<62><01>
extensionsz
<div class='d-flex justify-content-between align-items-start flex-wrap gap-2 mb-2'>
<div>
<h2 class='h4 mb-1'>Dokument #r<>u rz5</h2>
<div class='text-secondary small'>Projekt: r<>u · Typ: r<>r<>r<>z<></div>
</div>
<div class='btn-group btn-group-sm'>
<a class='btn btn-outline-secondary' title='Download .md' href='/document/u<>/download.md'>⬇️</a>
<a class='btn btn-outline-secondary' title='Umbenennen' href='#' onclick='renameDoc();return false;'>✏️</a>
<a class='btn btn-outline-secondary' title='Verschieben' href='#' onclick='moveDoc();return false;'>📁</a>
<a class='btn btn-outline-danger' title='Löschen' href='#' onclick='deleteDoc();return false;'>🗑️</a>
</div>
</div>
<div class='card mdview'>a<></div>
<script>
window.postForm = async function(url, data) {
const body = new URLSearchParams(data);
const r = await fetch(url, {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body});
if (!r.ok) { alert('Fehler: '+r.status); return; }
location.href = '/library';
};
window.renameDoc = async function() {
const v = await window.uiPrompt('Neuer Dokumentname', z8);
if (v===null) return;
window.postForm('/document/zO/rename', {title:v});
};
window.moveDoc = async function() {
const options = rmr<>Frdu<>;
const v = await window.uiSelect('In Projekt verschieben', options, 'Projekt wählen');
if (v===null || v==='') return;
window.postForm('/document/u<>/move', {project_id:v});
};
window.deleteDoc = async function() {
const ok = await window.uiConfirm('Dokument wirklich löschen?');
if (!ok) return;
window.postForm('/document/z/delete', {});
};
</script>
<EFBFBD>Dokument)
r"r3rDr
<00>md<6D>markdownr>rqrtr9)rr5r<><00>renderedr<64>ryr7s r!<00> view_documentr<00>sh<00><00> <0B><14>
<15><11> <0A>I<EFBFBD>I<EFBFBD> <10><14>I<EFBFBD>
<EFBFBD> <13>(<28>*<2A>
<EFBFBD>
<15> <0A><1B>C<EFBFBD><1B>-<2D>-<2D><11>{<7B>{<7B>1<EFBFBD>\<5C>?<3F>0<>b<EFBFBD>=_<>`<60>H<EFBFBD><1B>~<7E>H<EFBFBD>#<23>$%<25>T<EFBFBD>7<EFBFBD>)<29>5<EFBFBD><11>7<EFBFBD><1A> <0C>=0<>01<30>)<29> <0C>~<7E>Y<EFBFBD>q<EFBFBD><16>y<EFBFBD>k<EFBFBD>QU<51>VW<56>Xd<58>Ve<56>Uf<55>gO<01>PV<01>h<EFBFBD>W<1A> #<23><1A>
$9<>:><3E><1A><1A>A<EFBFBD>g<EFBFBD>J<EFBFBD>9O<39>8P<38>Q<1E>$<24>X<EFBFBD>&<13><18>:<3A>:<3A>x<EFBFBD>X<>!<21><11>4<EFBFBD><17>1<EFBFBD>V<EFBFBD>9<EFBFBD>E<>X<>gl<67>m<>n<>o<1E>%<25>X<EFBFBD>&<1E>
%<25>X<EFBFBD>&<01>I' <04>D<EFBFBD>P <12>*<2A>d<EFBFBD> #<23>#<23>s
<15>
<15><>ZYs<00>"C1<03>C=.<2E>1C:z/document/{doc_id}/download.mdc<00><><00>t<00>5}|jd|f<01>j<00>}ddd<00>s tdd<03><00>|dxsd|<00><00>j <00>}dj d<07>|D<00><00>j <00>}|j dd <09>xsd|<00><00>}|<04>d
<EFBFBD>}t|d d d |<05><00>i<01><0E>S#1swY<00><>xYw)Nz1SELECT title,content_md FROM documents WHERE id=?rrr<00> document_rBc3<00>LK<00>|]}|j<00>s|dvr|nd<01><01><00>y<02>w))r<>r<><00> r<>N)<01>isalnum)<02>.0<EFBFBD>chs r!<00> <genexpr>zdownload_md.<locals>.<genexpr><3E>s$<00><00><><00>W<>B<EFBFBD><12><1A><1A><1C><12><EFBFBD>)><3E>2<EFBFBD>C<EFBFBD>G<>W<>s<00>"$rr<>z.mdr}zContent-Dispositionzattachment; filename=)<01>headers)r"r3rDr
rsrOr<>r )rr5r<><00>base<73>safer{s r!<00> download_mdr<00>s<><00><00> <0B><14>a<01><11> <0A>I<EFBFBD>I<EFBFBD>I<>F<EFBFBD>9<EFBFBD> U<> ^<5E> ^<5E> `<60><01>a<01> <0C><1B>C<EFBFBD><1B>-<2D>-<2D> <0A>g<EFBFBD>J<EFBFBD> .<2E>I<EFBFBD>f<EFBFBD>X<EFBFBD>.<2E> 5<> 5<> 7<>D<EFBFBD> <0A>7<EFBFBD>7<EFBFBD>W<>RV<52>W<> W<> ]<5D> ]<5D> _<>D<EFBFBD> <0F><<3C><<3C><03>S<EFBFBD> !<21> 9<>y<EFBFBD><16><08>%9<>D<EFBFBD><16><16>s<EFBFBD>|<7C>H<EFBFBD> <1C> <09>,<2C><0F>&<26>*?<3F><08>z<EFBFBD>(J<>K<> <06><06>a<01>a<01>s <00>"B6<03>6B?z/document/{doc_id}/renamec<00><><00>t<00>5}|jd|j<00>|f<02>ddd<00>td<02>S#1swYtd<02>SxYw)Nz'UPDATE documents SET title=? WHERE id=?<3F>5<meta http-equiv='refresh' content='0; url=/library'>r<>)rrr5s r!<00>rename_documentr <00>sN<00><00> <0B><14>V<01><11> <09> <09> <09>;<3B>e<EFBFBD>k<EFBFBD>k<EFBFBD>m<EFBFBD>V<EFBFBD>=T<>U<>V<01> <17>O<> P<>P<>V<01> <17>O<> P<>P<>r<EFBFBD>z/document/{doc_id}/movec<00><><00>t<00>5}|jd||f<02>ddd<00>td<02>S#1swYtd<02>SxYw)Nz,UPDATE documents SET project_id=? WHERE id=?r<00>r"r3r )rr@r5s r!<00> move_documentr#<00>sH<00><00> <0B><14>X<01><11> <09> <09> <09>@<40>:<3A>v<EFBFBD>BV<42>W<>X<01> <17>O<> P<>P<>X<01> <17>O<> P<>P<>s <00>3<03>Az/document/{doc_id}/deletec<00><><00>t<00>5}|jd|f<01>ddd<00>td<02>S#1swYtd<02>SxYw)Nz DELETE FROM documents WHERE id=?rr")rr5s r!<00>delete_documentr%<00>sE<00><00> <0B><14>A<01><11> <09> <09> <09>4<>v<EFBFBD>i<EFBFBD>@<40>A<01> <17>O<> P<>P<>A<01> <17>O<> P<>P<><50> <00>2<03>Az/promptsc<00><><00>t<00>5}|jd<01>j<00>}|jd<02>j<00>}ddd<00>djD<00>cgc]}d|d<00>d|d<00>d<07><05><02>c}<03>}dj|D<00>cgc]}d|d<00>d |d
<00>d |d<00>d |d
<00>d <0A> <09><02>c}<03>xsd}djD<00>cgc];}d|d<00>d|d
<00>d|d
<00>d|d<00>d|d
<00>d|d
<00>d|d<00>d|d
<00>d|d
<00>d<18><13><02>=c}<03>xsd}dt |<01><00>dt |<02><00>d|<04>d|<05>d|<06>d<1F> }t d |<07>S#1swY<00><>xYwcc}wcc}wcc}w)!Nz#SELECT * FROM prompts ORDER BY namer;rBr<>rmr<>r<>zo<div class='card'><div class='d-flex justify-content-between align-items-center mb-2'><div class='fw-semibold'>z<></div><span class='badge rounded-pill text-bg-light'>Projekt</span></div><form method='post' action='/projects/update'><input type='hidden' name='id' value='r<>z<>'><div class='row g-2 align-items-end'><div class='col-12 col-md-8'><label class='form-label small text-secondary mb-1'>Name</label><input class='form-control' name='name' value='z<>'></div><div class='col-12 col-md-4'><button class='btn btn-primary btn-sm' type='submit'><i class='bi bi-pencil-square'></i> Umbenennen</button></div></div></form><form method='post' action='/projects/u<>/delete' onsubmit='return confirm("Projekt löschen? Dokumente werden auf Default verschoben.")' class='mt-2'><button class='btn btn-outline-danger btn-sm' type='submit'><i class='bi bi-trash'></i> Löschen</button></form></div>z7<p class='text-secondary'>Keine Projekte vorhanden.</p>z6</div><span class='badge rounded-pill text-bg-light'>#z`</span></div><form method='post' action='/prompts/update'><input type='hidden' name='id' value='z<>'><div class='row g-2'><div class='col-12 col-lg-4'><label class='form-label small text-secondary mb-1'>Name</label><input class='form-control' name='name' value='a '></div><div class='col-12 col-lg-8'><div class='d-flex justify-content-between align-items-center'><label class='form-label small text-secondary mb-1'>Prompttext</label><button type='button' class='btn btn-outline-secondary btn-sm py-0 px-2' onclick='openPromptEditor(zk)' title='Vollbild-Editor'><i class='bi bi-arrows-fullscreen'></i></button></div><textarea id='prompt_text_zO' class='form-control' name='prompt' style='min-height:110px; resize:vertical'>r<>a</textarea></div></div><div class='mt-3 d-flex flex-wrap gap-2'><button class='btn btn-primary btn-sm' type='submit'><i class='bi bi-check2-circle'></i> Speichern</button><button class='btn btn-outline-secondary btn-sm' type='button' onclick='previewPrompt(z`)'><i class='bi bi-eye'></i> Anzeigen</button></div></form><form method='post' action='/prompts/u<>/delete' onsubmit='return confirm("Prompt löschen?")' class='mt-2'><button class='btn btn-outline-danger btn-sm' type='submit'><i class='bi bi-trash'></i> Löschen</button></form></div>z6<p class='text-secondary'>Keine Prompts vorhanden.</p>zn
<div class='d-flex justify-content-between align-items-center mb-3'>
<span class='badge text-bg-secondary'>u Prompts · a Projekte</span>
</div>
<ul class='nav nav-tabs mb-3' id='cfgTabs' role='tablist'>
<li class='nav-item' role='presentation'>
<button class='nav-link active' data-bs-toggle='tab' data-bs-target='#pane-projects' type='button' role='tab'>Projekte</button>
</li>
<li class='nav-item' role='presentation'>
<button class='nav-link' data-bs-toggle='tab' data-bs-target='#pane-prompts' type='button' role='tab'>Prompts</button>
</li>
</ul>
<div class='tab-content'>
<div class='tab-pane fade show active' id='pane-projects' role='tabpanel'>
<div class='card'>
<div class='d-flex align-items-center gap-2 mb-2'><i class='bi bi-folder-plus text-primary'></i><h4 class='h6 mb-0'>Neues Projekt anlegen</h4></div>
<form method='post' action='/projects' class='row g-2 align-items-end'>
<div class='col-12 col-md-8'><label class='form-label small text-secondary mb-1'>Projektname</label><input class='form-control' name='name' list='projectNames' placeholder='Projektname' required></div>
<datalist id='projectNames'>z<></datalist>
<div class='col-12 col-md-4'><button class='btn btn-primary btn-sm' type='submit'><i class='bi bi-plus-lg'></i> Anlegen</button></div>
</form>
</div>
a/
</div>
<div class='tab-pane fade' id='pane-prompts' role='tabpanel'>
<div class='card'>
<div class='d-flex align-items-center gap-2 mb-2'><i class='bi bi-plus-circle text-primary'></i><h4 class='h6 mb-0'>Neuen Prompt anlegen</h4></div>
<form method='post' action='/prompts/add'>
<div class='mb-2'><label class='form-label small text-secondary mb-1'>Name</label><input class='form-control' name='name' placeholder='z. B. Executive Summary' required></div>
<div class='mb-2'><label class='form-label small text-secondary mb-1'>Prompttext</label><textarea class='form-control' name='prompt' placeholder='Prompttext' required></textarea></div>
<button class='btn btn-primary btn-sm' type='submit'><i class='bi bi-plus-lg'></i> Anlegen</button>
</form>
</div>
ud
</div>
</div>
<div class='modal fade' id='promptPreviewModal' tabindex='-1' aria-hidden='true'>
<div class='modal-dialog modal-lg modal-dialog-scrollable'>
<div class='modal-content'>
<div class='modal-header'>
<h5 class='modal-title'>Prompt Vorschau</h5>
<button type='button' class='btn-close' data-bs-dismiss='modal' aria-label='Close'></button>
</div>
<div class='modal-body'>
<div id='promptPreviewBody' class='mdview'>Lade …</div>
</div>
</div>
</div>
</div>
<div class='modal fade' id='promptEditorModal' tabindex='-1' aria-hidden='true'>
<div class='modal-dialog modal-fullscreen'>
<div class='modal-content'>
<div class='modal-header'>
<h5 class='modal-title'>Prompt bearbeiten (Vollbild)</h5>
<button type='button' class='btn btn-outline-secondary btn-sm' onclick='closePromptEditor()'><i class='bi bi-fullscreen-exit'></i> Minimize</button>
</div>
<div class='modal-body'>
<textarea id='promptEditorTextarea' class='form-control' style='height:100%; min-height:70vh; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;'></textarea>
</div>
</div>
</div>
</div>
<script>
let currentPromptEditorId = null;
async function previewPrompt(id) {
const body = document.getElementById('promptPreviewBody');
body.innerHTML = 'Lade …';
const modalEl = document.getElementById('promptPreviewModal');
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
modal.show();
const r = await fetch('/prompts/' + id + '/preview');
const j = await r.json();
body.innerHTML = j.html || '<p class="text-secondary">Keine Vorschau.</p>';
}
function openPromptEditor(id) {
currentPromptEditorId = id;
const src = document.getElementById('prompt_text_' + id);
if(!src) return;
const ta = document.getElementById('promptEditorTextarea');
ta.value = src.value;
const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('promptEditorModal'));
modal.show();
}
function syncPromptEditorBack() {
const ta = document.getElementById('promptEditorTextarea');
if(currentPromptEditorId !== null) {
const dst = document.getElementById('prompt_text_' + currentPromptEditorId);
if(dst) dst.value = ta.value;
}
}
function closePromptEditor() {
syncPromptEditorBack();
const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('promptEditorModal'));
modal.hide();
}
document.addEventListener('DOMContentLoaded', () => {
const el = document.getElementById('promptEditorModal');
if(el) el.addEventListener('hide.bs.modal', syncPromptEditorBack);
});
</script>
zPrompts & Projekte)r"r3r=rOr<>r9)r5<00>promptsr<73>ry<00> project_opts<74> project_list<73> prompt_listr7s r!<00> prompts_pager,<00>s<><00><00> <0B><14>V<01><11><13>)<29>)<29>A<>B<>K<>K<>M<><07><14>9<EFBFBD>9<EFBFBD>I<>J<>S<>S<>U<><08>V<01><16>7<EFBFBD>7<EFBFBD>Ya<59>b<>TU<54>o<EFBFBD>a<EFBFBD><06>i<EFBFBD>[<5B><02>1<EFBFBD>V<EFBFBD>9<EFBFBD>+<2B>Y<EFBFBD>O<>b<>c<>L<EFBFBD><15>7<EFBFBD>7<EFBFBD>$<24><06><18> h<01>hi<68>jp<6A>hq<68>gr<67>s1<>12<31>4<EFBFBD><17> <09>:W<02>XY<02>Z`<02>Xa<02>Wb<02>b1<>23<32>4<EFBFBD><17> <09>:<11> <12><06><07>D<01> D<01><11> <15>'<27>'<27>2<1D>1
<EFBFBD>0<12>/l<01>lm<6C>nt<6E>lu<6C>kv<6B>wm<02>no<02>pt<02>nu<02>mv<02>v5<>56<35>t<EFBFBD>W<EFBFBD>I<EFBFBD>>[<02>\]<02>^d<02>\e<02>[f<02>fr<01>st<01>tx<74>ry<72>qz<71>{)<29>)*<2A>4<EFBFBD><17> <09>2A<02>BC<02>DL<02>BM<02>AN<02>Ne<01>
fg<01>gk<67>el<65>dm<64>n4<>56<35>d<EFBFBD>G<EFBFBD>9<EFBFBD>=<15>' <16>
<EFBFBD><06>B<01>6
B<01>7<10>:)<29>),<2C>W<EFBFBD><1C><0E>l<EFBFBD>3<EFBFBD>x<EFBFBD>=<3D>/<2F>R%<25>$&2<>N<EFBFBD>3<05><12>N<EFBFBD> <05><11>M<EFBFBD>K<01>Io <04>D<EFBFBD>` <12>&<26><04> -<2D>-<2D>GV<01>V<01><>c<01><><06><>"
s<00>?E<03>!E<08> "E<08> AE<08>E z/prompts/{prompt_id}/previewr<77>c<00><><00>t<00>5}|jd|f<01>j<00>}ddd<00>s tdd<03><00>t j
|dxsdgd<06><01><07>}|d|d |d
<EFBFBD>S#1swY<00>CxYw) Nz-SELECT id,name,prompt FROM prompts WHERE id=?rzPrompt nicht gefundenr<6E>rBrr r<>rm)r<>rm<00>html)r"r3rDr
rr)r<>r5ryr.s r!<00>prompt_previewr/<00>s~<00><00> <0B><14>`<01><11> <0A>I<EFBFBD>I<EFBFBD>E<> <09>|<7C> T<> ]<5D> ]<5D> _<><01>`<01> <0C><1B>C<EFBFBD>!8<>9<>9<> <0A>;<3B>;<3B>q<EFBFBD><18>{<7B>(<28>b<EFBFBD>5W<35> X<>D<EFBFBD><13>D<EFBFBD>'<27>1<EFBFBD>V<EFBFBD>9<EFBFBD>d<EFBFBD> ;<3B>;<3B> `<01>`<01>s <00>"A/<03>/A8z /prompts/addr<64>c <00><><00>t<00>5}|jd|j<00>|j<00>t<00>t<00>f<04>ddd<00>t d<02>S#1swYt d<02>SxYw)NzGINSERT INTO prompts(name,prompt,created_at,updated_at) VALUES (?,?,?,?)r<>r<>)rmr<>r5s r!<00>
prompt_addr1<00>s`<00><00> <0B><14>
<EFBFBD><11> <09> <09> <09> U<> <11>Z<EFBFBD>Z<EFBFBD>\<5C>6<EFBFBD><<3C><<3C>><3E>7<EFBFBD>9<EFBFBD>g<EFBFBD>i<EFBFBD> @<40>
<EFBFBD>
<EFBFBD>
<18>O<> P<>P<> 
<EFBFBD>
<18>O<> P<>P<>s <00>AA!<03>!A4z/prompts/updatec<00><><00>t<00>5}|jd|j<00>|j<00>t<00>|f<04>ddd<00>t d<02>S#1swYt d<02>SxYw)Nz<UPDATE prompts SET name=?, prompt=?, updated_at=? WHERE id=?r<>r<>)r<>rmr<>r5s r!<00> prompt_updater3<00>sh<00><00> <0B><14>A<02><11> <09> <09> <09>P<>SW<53>S]<5D>S]<5D>S_<53>ag<61>am<61>am<61>ao<61>qx<71>qz<71>|~<7E>R<52> A<02>A<02> <17>O<> P<>P<>A<02> <17>O<> P<>P<>s <00>;A<03>A,z/prompts/{prompt_id}/deletec<00><><00>t<00>5}|jd|f<01>ddd<00>td<02>S#1swYtd<02>SxYw)NzDELETE FROM prompts WHERE id=?r<>r")r<>r5s r!<00> prompt_deleter5<00>sE<00><00> <0B><14>B<01><11> <09> <09> <09>2<>Y<EFBFBD>L<EFBFBD>A<>B<01> <17>O<> P<>P<>B<01> <17>O<> P<>P<>r&<00>tsc<00><00>|sy tjt|<00>jdd<02><00>jd<00><03>S#t$r2 tjt|<00><00>cYS#t$rYYywxYwwxYw)N<>Zz+00:00)<01>tzinfo)r<00> fromisoformatrwr<>rv)r6s r!<00> _parse_utcishr;<00>sw<00><00> <0A><13><18><17>%<25>%<25>c<EFBFBD>"<22>g<EFBFBD>o<EFBFBD>o<EFBFBD>c<EFBFBD>8<EFBFBD>&D<>E<>M<>M<>UY<55>M<>Z<>Z<><5A> <14><18> <18><1B>)<29>)<29>#<23>b<EFBFBD>'<27>2<> 2<><32><18> <18><17> <18><><18>s/<00>=A<00> A><03> A-<02>*A><03>- A:<05>6A><03>9A:<05>:A><03> start_iso<73>end_isoc<00>r<00>t|<00>}|sy |r t|<01>ntj<00>}|stj<00>}tdt ||z
j <00><00><00>}|dkr|<04>d<04>St |d<03>\}}|dkr|<05>d|<06>d<04>St |d<03>\}}|<07>d|<08>d<07>S#t$rYywxYw)Nr<4E>r<00><<00>szm zh <20>m)r;rr&<00>maxr<78><00> total_seconds<64>divmodrv) r<r=r@r<><00>secrA<00>s2<73>h<>m2s r!<00> _fmt_elapsedrI<00>s<><00><00><15>i<EFBFBD> <20>A<EFBFBD> <0C><12> <13>&-<2D>M<EFBFBD>'<27> "<22>8<EFBFBD>?<3F>?<3F>3D<33><01><10><18><0F><0F>!<21>A<EFBFBD><11>!<21>S<EFBFBD>!<21>a<EFBFBD>%<25>.<2E>.<2E>0<>1<>2<><03> <0E><12>8<EFBFBD><19>U<EFBFBD>!<21>9<EFBFBD> <1C><16>s<EFBFBD>B<EFBFBD><0F><05><01>2<EFBFBD> <0C>r<EFBFBD>6<EFBFBD><17>S<EFBFBD><02>2<EFBFBD>$<24>a<EFBFBD>=<3D> <20><16>q<EFBFBD>"<22> <0A><05><01>2<EFBFBD><13><13>B<EFBFBD>r<EFBFBD>d<EFBFBD>!<21>}<7D><1C><> <14><13><12><13>s<00>A&B*<00>7B*<00>B*<00>* B6<03>5B6<03>limitc<00><><00>t<00>5}|jd|f<01>j<00>}ddd<00>D<00>cgc] }t|<03><00><02>c}S#1swY<00>"xYwcc}w)NaR
SELECT j.*, p.name AS project_name, d.title AS document_title, pr.name AS prompt_name
FROM jobs j
LEFT JOIN projects p ON p.id=j.project_id
LEFT JOIN documents d ON d.id=j.document_id
LEFT JOIN prompts pr ON pr.id=j.prompt_id
ORDER BY j.id DESC LIMIT ?
)r"r3r=<00>dict)rJr5<00>jobsrxs r!<00> _jobs_payloadrN<00>s_<00><00> <0B><14> <15><11><10>y<EFBFBD>y<EFBFBD> <10><13>H<EFBFBD>

<EFBFBD> <13>(<28>*<2A> <0A> <15>"<22> "<22><01>D<EFBFBD><11>G<EFBFBD> "<22>"<22> <15> <15><> #s<00>"A<03>A<04>Az
/jobs/datac<00><00>dt|<00>iS)N<>items)rN)rJs r!<00> jobs_datarQ<00>s<00><00> <13>]<5D>5<EFBFBD>)<29> *<2A>*r#z/jobs/{job_id}/cancelc<00>z<00>t|<00>}|s tdd<02><00>|ddvrt|dt<00>d<06><07>ddd <09>S)
Nrz job not foundrY<00>rfrjrZrZzCancelled by user)rYrirjT)r<>rY)rLr
rWr))rIrxs r!<00> jobs_cancelrT<00>sE<00><00><10><16><18>A<EFBFBD> <0C><1B>C<EFBFBD><1F>1<>1<><08><18>{<7B>8<>8<><10><16> <0B><17><19>J]<5D>^<5E><16>+<2B> .<2E>.r#z/jobs/{job_id}/cancel-formc<00>2<00>t|<00>tdd<02><03>S<00>N<>/jobsi/)<02>url<72> status_code)rTr<00>rIs r!<00>jobs_cancel_formr[<00><00><00><00><0F><06><17> <1B><07>S<EFBFBD> 9<>9r#z/jobs/{job_id}/deletec<00>t<00>t<00>5}|jd|f<01>ddd<00>ddiS#1swYddiSxYw)NzDELETE FROM jobs WHERE id=?r<>T)r"r3rKs r!<00> jobs_deleter^s><00><00> <0B><14><<3C><11> <09> <09> <09>/<2F>&<26><19>;<3B><<3C> <10>$<24><<3C><17><<3C> <10>$<24><<3C><17>s<00>+<03>7z/jobs/{job_id}/delete-formc<00>2<00>t|<00>tdd<02><03>SrV)r^rrZs r!<00>jobs_delete_formr` r\r#rWr<>c <00>p<00>td<01>}dtdtfd<04>}g}|D<00>]`}|jd<05>xs|jd<06>xsd}|jd<08>xsd}d}|dd vr |d
|d <00>d <0C>z }|d |d <00>d<0E>z }|jd<0F>r |d|d<00>d<11>z }|jd<12>r"dt|d<00>jdd<15><00>d<16>nd}|j d|d <00>d|d <00>d|d <00>d|d<00>d|d<00>d|<05>d|<06>dt ||<06><00>d ||d<00><00>d|d<00>d!|jd"<22>rd#|d"znd<07>|jd$<24>rd%|d$znd<07>|jd&<26>rd'|d&znd<07>d(|<07>d|<08>d<16><1D><00><01>cdj |<03>xsd)} |rd*|<00>d+<2B>nd}
d,|
<EFBFBD>d-| <09>d.<2E>} td/| <0B>S)0N<30><4E>rYr$c<00>N<00>|xsdj<00>}|dk(ry|dvry|dk(ryy) NrBrf<00>success)r[r<><00>primaryrZ<00>warning<6E>danger)<01>lower)rYr@s r!<00>_badgezjobs_page.<locals>._badges8<00><00> <13>\<5C>r<EFBFBD> <20> <20> "<22><01> <0C><06>;<3B><1C> <0C>%<25> %<25><1C> <0C> <0B> <1B><1C>r#r]r<>rBrirSzB<button class='btn btn-outline-warning btn-sm' onclick='cancelJob(r<>z5)'><i class='bi bi-x-circle'></i> Abbrechen</button> zA<button class='btn btn-outline-danger btn-sm' onclick='deleteJob(u1)'><i class='bi bi-trash'></i> Löschen</button> rhz2<a class='btn btn-primary btn-sm' href='/document/z7'><i class='bi bi-box-arrow-up-right'></i> Ergebnis</a>rjz5<div class='alert alert-danger mt-2 mb-0 py-2 small'><3E><z&lt;r<>z(<div class='card job-card' data-job-id='z<>'><div class='d-flex justify-content-between align-items-center flex-wrap gap-2'><div class='d-flex align-items-center gap-2'><input class='form-check-input job-select' type='checkbox' value='z%'><div><div class='fw-semibold'>Job #r<>r<>z2</div><div class='text-secondary small'>erstellt: u. · läuft: <span class='elapsed' data-start='z ' data-end='r<>z4</span></div></div></div><span class='badge text-bg-z%</span></div><div class='small mt-2'><3E> project_namez Projekt: <20>document_titlez<br>Dokument: <20> prompt_namez <br>Prompt: z/</div><div class='d-flex flex-wrap gap-2 mt-3'>z)<p class='text-secondary'>Keine Jobs.</p>z+<div class='alert alert-info py-2'><b>Job #z wurde eingereiht.</b></div>z<>
<h2 class='h4 mb-2'>Hintergrundverarbeitung</h2>
<p class='text-secondary small'>Maximal 2 Jobs gleichzeitig. Seite aktualisiert automatisch.</p>
u|
<div class='card py-2'>
<div class='d-flex flex-wrap gap-2 align-items-center'>
<button class='btn btn-outline-secondary btn-sm' type='button' onclick='selectAllJobs()'><i class='bi bi-check2-square'></i> Alle wählen</button>
<button class='btn btn-outline-secondary btn-sm' type='button' onclick='clearJobSelection()'><i class='bi bi-square'></i> Auswahl löschen</button>
<button class='btn btn-outline-danger btn-sm' type='button' onclick='deleteSelectedJobs()'><i class='bi bi-trash'></i> Ausgewählte löschen</button>
<button class='btn btn-danger btn-sm' type='button' onclick='deleteAllJobs()'><i class='bi bi-trash3'></i> Alle Jobs löschen</button>
<span id='jobs-selected-count' class='text-secondary small ms-auto'>0 ausgewählt</span>
</div>
</div>
<div id='jobs-status' class='text-secondary small mb-2'>Live-Update aktiv …</div>
<div id='jobs-root'>u<> </div>
<script>
function parseUtcish(ts) {
if(!ts) return NaN;
const hasZone = /Z$|[+-]\d\d:\d\d$/.test(ts);
return Date.parse(hasZone ? ts : (ts + 'Z'));
}
function since(ts, endTs=null) {
if(!ts) return '-';
const startMs = parseUtcish(ts);
const end = endTs ? parseUtcish(endTs) : Date.now();
const s = Math.max(0, Math.floor((end-startMs)/1000));
if (s<60) return s+'s';
const m = Math.floor(s/60); if (m<60) return m+'m '+(s%60)+'s';
const h = Math.floor(m/60); return h+'h '+(m%60)+'m';
}
function tickElapsed() {
document.querySelectorAll('.elapsed').forEach(el => {
const start = el.getAttribute('data-start') || '';
const end = el.getAttribute('data-end') || '';
el.textContent = since(start, end || null);
});
}
async function post(url) {
const r = await fetch(url, {method:'POST'});
if(!r.ok) throw new Error('Fehler '+r.status);
}
function selectedJobIds() {
return Array.from(document.querySelectorAll('.job-select:checked')).map(el => Number(el.value));
}
function updateSelectionCount() {
const el = document.getElementById('jobs-selected-count');
if(!el) return;
el.textContent = `${selectedJobIds().length} ausgewählt`;
}
function selectAllJobs() {
document.querySelectorAll('.job-select').forEach(el => el.checked = true);
updateSelectionCount();
}
function clearJobSelection() {
document.querySelectorAll('.job-select').forEach(el => el.checked = false);
updateSelectionCount();
}
async function deleteSelectedJobs() {
const ids = selectedJobIds();
if(!ids.length) return alert('Keine Jobs ausgewählt');
if(!confirm(`${ids.length} Jobs wirklich löschen?`)) return;
for (const id of ids) { await post('/jobs/'+id+'/delete'); }
location.reload();
}
async function deleteAllJobs() {
const ids = Array.from(document.querySelectorAll('.job-select')).map(el => Number(el.value));
if(!ids.length) return;
if(!confirm(`Wirklich ALLE ${ids.length} Jobs löschen?`)) return;
for (const id of ids) { await post('/jobs/'+id+'/delete'); }
location.reload();
}
async function cancelJob(id) { if(!confirm('Job abbrechen?')) return; await post('/jobs/'+id+'/cancel'); location.reload(); }
async function deleteJob(id) { if(!confirm('Job löschen?')) return; await post('/jobs/'+id+'/delete'); location.reload(); }
document.addEventListener('change', (e) => { if(e.target && e.target.classList && e.target.classList.contains('job-select')) updateSelectionCount(); });
setInterval(tickElapsed, 1000); tickElapsed(); updateSelectionCount();
</script>
<EFBFBD>Jobs)rNrwrrr<>r<>rIrOr9) r<>rPri<00>cards<64>it<69>start_ts<74>end_ts<74>actions<6E>err<72>pre<72>noticer7s r!<00> jobs_pagerws<00><00> <19>#<23> <1E>E<EFBFBD><18>s<EFBFBD><18>s<EFBFBD><18> <0F>E<EFBFBD><13>
<EFBFBD><02><15>6<EFBFBD>6<EFBFBD>,<2C>'<27>E<>2<EFBFBD>6<EFBFBD>6<EFBFBD>,<2C>+?<3F>E<>2<EFBFBD><08><13><16><16> <0A>&<26>,<2C>"<22><06><14><07> <0A>h<EFBFBD><<3C>=<3D> =<3D> <13>[<5B>\^<5E>_c<5F>\d<>[e<>f]<02>^<02> ^<02>G<EFBFBD><0F>V<>WY<57>Z^<5E>W_<57>V`<60>aT<02>U<02> U<02><07> <0A>6<EFBFBD>6<EFBFBD>&<26> '<27> <13>K<>B<EFBFBD>Oc<4F>Ld<4C>Ke<4B>f]<02>^<02> ^<02>G<EFBFBD>vx<76>v|<7C>v|<7C>~E<02>wF<02>E<>c<EFBFBD>"<22>W<EFBFBD>+<2B>FV<46>F^<5E>F^<5E>_b<5F>ci<63>Fj<46>Ek<45>kq<6B>r<>LN<02><03> <0A> <0C> <0C>6<>r<EFBFBD>$<24>x<EFBFBD>j<EFBFBD>A~<01>A<02>BF<02>G<02>~H<02>Hm<02>np<02>qu<02>nv<02>mw<02>w{<02>|~<02>E<03>|F<03>{G<03>G;<3B>;=<3D>l<EFBFBD>;K<>:L<>Lz<4C>|D<02>{E<02>EQ<02>RX<02>QY<02>Y[<02>\h<02>iq<02>sy<02>\z<02>[{<02>{*<2A>*0<><12>H<EFBFBD><1C>*><3E>)?<3F>r<EFBFBD>"<22>X<EFBFBD>,<2C><1E>P'<27>35<33>6<EFBFBD>6<EFBFBD>.<2E>3I<33> <0B>B<EFBFBD>~<7E>.<2E>.<2E>r<EFBFBD>R<>:<<3C>&<26>&<26>AQ<41>:R<> <20><12>$4<>!5<>5<>XZ<58>[<5B>57<35>V<EFBFBD>V<EFBFBD>M<EFBFBD>5J<35><0E>r<EFBFBD>-<2D>0<>0<>PR<50>S<>8<>8?<3F>y<EFBFBD><06><12>e<EFBFBD><14> <16>
<EFBFBD>
<EFBFBD>6 <0A>'<27>'<27>%<25>.<2E>
G<EFBFBD>G<>C<EFBFBD>ci<63>:<3A>6<EFBFBD>(<28>B^<5E> _<>oq<6F>F<EFBFBD><01><08><08> <15><19>E<EFBFBD>><01>L <04>D<EFBFBD>Z <12>&<26>$<24> <1F>r#z/runc<00><><00>t<00>5}|jd<01>j<00>}|jd<02>j<00>}ddd<00>djD<00>cgc]}d|d<00>d|d<00>d|d<00>d |d
<00>d <0B> <09><02>c}<03>}djD<00>cgc]}d|d<00>d |d <00>d <0B><05><02>c}<05>}d|<04>d|<06>d<10>}t d|<07>S#1swY<00><>xYwcc}wcc}w)NzISELECT id,title,kind,created_at FROM documents ORDER BY id DESC LIMIT 200z)SELECT id,name FROM prompts ORDER BY namerBr<>r<>z'>#z [r<>z] rr<>r<>rmu<>
<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'>z<></select>
</div>
<div class='mb-2'>
<label class='form-label'>Prompt</label>
<select class='form-select' name='prompt_id'>uf</select>
</div>
<button class='btn btn-primary' type='submit'>Ausführen (Qwen)</button>
</form>
<EFBFBD>Run)r"r3r=rOr9)r5r<>r(r<><00>d_optsryr<>r7s r!<00>run_pager{<00>s<00><00> <0B><14>T<01><11><10>y<EFBFBD>y<EFBFBD>d<>e<>n<>n<>p<><04><13>)<29>)<29>G<>H<>Q<>Q<>S<><07>T<01><10>W<EFBFBD>W<EFBFBD>ko<6B>p<>fg<66><0F><01>$<24><07>y<EFBFBD><03>A<EFBFBD>d<EFBFBD>G<EFBFBD>9<EFBFBD>B<EFBFBD>q<EFBFBD><16>y<EFBFBD>k<EFBFBD><12>A<EFBFBD>g<EFBFBD>J<EFBFBD><<3C>W`<60>a<>p<> q<>F<EFBFBD> <0F>W<EFBFBD>W<EFBFBD>QX<51>Y<>A<EFBFBD><0F><01>$<24><07>y<EFBFBD><02>1<EFBFBD>V<EFBFBD>9<EFBFBD>+<2B>Y<EFBFBD>G<>Y<> Z<>F<EFBFBD>4<>
5;<3B>8<EFBFBD><2<>39<33><18>:<01> <04>D<EFBFBD> <12>%<25><14> <1E><1E>+T<01>T<01><>q<01><>Ys<00>?C <03>!"C<08>C<08> Cr<>c<00><00>t<00>5}|jd|f<01>j<00>}|jd|f<01>j<00>}ddd<00>rs tdd<04><00>t d||<01><06>}t d|<05>d<08><03>S#1swY<00>6xYw) Nz#SELECT id FROM documents WHERE id=?z!SELECT id FROM prompts WHERE id=?rr<>r<>)r<>r<>r<>r<>)r"r3rDr
r<>r )r<>r<>r5r<>r<>rIs r!<00>
run_promptr}<00>s<><00><00> <0B><14>V<01><11><0F>i<EFBFBD>i<EFBFBD>=<3D> <0B>~<7E>N<>W<>W<>Y<><03><0F>i<EFBFBD>i<EFBFBD>;<3B>i<EFBFBD>\<5C>J<>S<>S<>U<><03>V<01> <0F>c<EFBFBD><1B>C<EFBFBD>!F<>G<>G<> <18><1A><1B> <09> R<>F<EFBFBD> <17>R<>SY<53>RZ<52>Z\<5C>]<5D> ^<5E>^<5E>V<01>V<01>s <00>AB<03>B )rB)NrBrBr)rb)Vrqr/r<00> threading<6E>concurrent.futuresrr<00>pathlibr<00>typingrrrrn<00>fastapirrr r
r <00>fastapi.responsesr r rrr<00>getenv<6E>rstriprrrr<00>apprr<><00>Lockr<6B>r"rwr)r6r9r>r<>rFrHrLrWr<>r<>r<><00>on_eventr<74>rrr<>r<>r<>r<>r<>ror<>r<>r<>r<>rrrr r#r%r,r/r1r3r5r;rIrNrQrTr[r^r`rwr{r}r(r#r!<00><module>r<>s<><00><01> <0B> <09><0E><10>1<><1D><18><1B><15><0F>B<>B<>g<>g<> <14>2<EFBFBD>9<EFBFBD>9<EFBFBD>Z<EFBFBD>!A<> B<> I<> I<>#<23> N<><08><1B>"<22>)<29>)<29>-<2D>/P<>Q<>X<>X<>Y\<5C>]<5D><0F><18>r<EFBFBD>y<EFBFBD>y<EFBFBD><1E><1C>6<> <0C>
<13>"<22>)<29>)<29>I<EFBFBD>}<7D>
-<2D><07> <0A>$<24>%<25><03>
<0E>y<EFBFBD>r<EFBFBD>y<EFBFBD>y<EFBFBD><19>L<EFBFBD>1<>
2<EFBFBD><07> <1D>!<21> ,<2C><08> <19>9<EFBFBD>><3E>><3E> <1B><08><10> )<29><13>)<29>P
<EFBFBD>fH<04>#<23>H<04>S<EFBFBD>H<04>S<EFBFBD>H<04>VR<01>
<1D><13><1D><13><1D> X<01>
P<01>S<EFBFBD>P<01>
><3E>S<EFBFBD>><3E>#N<01><03>#N<01>L9N<01>#<23>9N<01>x<12>c<EFBFBD><12><03><12>8<05><1C><1C>i<EFBFBD><18>/<2F><19>/<2F>
<05><17><17> <20>!<21> <06>"<22> <06><05><17><17><1B><15>=<3D><16>=<3D>
<05><17><17><18><12> E<01><13> E<01><05><17><17><1A><14>D<02><15>D<02><05><17><17><13>\<5C><17>*<2A>"<22>S<EFBFBD>"<22>+<2B>"<22>@<05><18><18>+<2B>l<EFBFBD><18>3<> <20><13>I<EFBFBD>Q<01>c<EFBFBD>Q<01>4<>Q<01> <05><18><18>
<1C>\<5C><18>:<3A>!<21>#<23>Y<EFBFBD>D<EFBFBD><13>I<EFBFBD>Q<01>s<EFBFBD>Q<01>c<EFBFBD>Q<01>;<3B>Q<01> <05><18><18>
)<29>,<2C><18>G<> Q<01>s<EFBFBD> Q<01>H<01> Q<01><05><18><18>)<29>L<EFBFBD><18>1<>#'<27><03>9<EFBFBD>4<EFBFBD><02>8<EFBFBD>X\<5C>]`<60>Xa<58>_<01>S<EFBFBD>_<01>S<EFBFBD>_<01>:<3A>_<01>2<>_<01>&<05><17><17><1A>L<EFBFBD><17>1<>n#<23><08><13> <0A>n#<23>s<EFBFBD>n#<23>C<EFBFBD>n#<23>2<>n#<23>b<05><17><17> <1D>l<EFBFBD><17>;<3B>:$<24>#<23>:$<24><<3C>:$<24>z<05><17><17> )<29>:K<><17>L<><06><03><06>M<01><06>"<05><18><18>
%<25>l<EFBFBD><18>C<>.2<EFBFBD>3<EFBFBD>i<EFBFBD>Q<01>C<EFBFBD>Q<01><03>Q<01>D<01>Q<01> <05><18><18>
#<23>L<EFBFBD><18>A<>15<31>c<EFBFBD><19>Q<01>#<23>Q<01>3<EFBFBD>Q<01>B<01>Q<01> <05><18><18>
%<25>l<EFBFBD><18>C<>Q<01>C<EFBFBD>Q<01>D<01>Q<01> <05><17><17><1A>L<EFBFBD><17>1<>d.<2E>2<>d.<2E>N<05><17><17> '<27>(<28><<3C>c<EFBFBD><<3C>)<29><<3C><05><18><18>.<2E><1C><18>6<><1F><03>9<EFBFBD>D<EFBFBD><13>I<EFBFBD>Q<01>S<EFBFBD>Q<01>c<EFBFBD>Q<01>7<>Q<01><05><18><18>
<1B>L<EFBFBD><18>9<> <20><13>I<EFBFBD>4<EFBFBD><03>9<EFBFBD>D<EFBFBD>QT<51>I<EFBFBD>Q<01>c<EFBFBD>Q<01>S<EFBFBD>Q<01>c<EFBFBD>Q<01>:<3A>Q<01> <05><18><18>
'<27> <0C><18>E<>Q<01>S<EFBFBD>Q<01>F<01>Q<01> <18>h<EFBFBD>s<EFBFBD>m<EFBFBD> <18><08><18>(:<3A> <18><13>H<EFBFBD>S<EFBFBD>M<EFBFBD><13>H<EFBFBD>S<EFBFBD>M<EFBFBD><13>S<EFBFBD><13>( #<23><13> #<23> <05><17><17><1C><16>+<2B>S<EFBFBD>+<2B><17>+<2B><05><18><18>
!<21>"<22>/<2F><03>/<2F>#<23>/<2F><05><18><18>
&<26>'<27>:<3A>S<EFBFBD>:<3A>(<28>:<3A>
<05><18><18>
!<21>"<22><18><03><18>#<23><18> <05><18><18>
&<26>'<27>:<3A>S<EFBFBD>:<3A>(<28>:<3A>
<05><17><17><17><1C><17>.<2E>y <20>h<EFBFBD>s<EFBFBD>m<EFBFBD>y <20>/<2F>y <20>x<05><17><17><16> <0C><17>-<2D><1F>.<2E><1F>2<05><18><18>&<26><1C><18>.<2E>"&<26>s<EFBFBD>)<29>d<EFBFBD>3<EFBFBD>i<EFBFBD>_<01>C<EFBFBD>_<01><03>_<01>/<2F>_r#