Add optional per-image-profile Qwen prompt enhancers

This commit is contained in:
Mikei386 committed 2026-09-29 21:16:40 +02:00
1 parent 7ba17308f5
commit c5a028cba9
13 files changed
+363 -19

No files matched your search

+27 -4
View File
@@ -1,16 +1,39 @@
window.ImageTestUI=(()=>{
const e=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
async function api(path,data){const r=await fetch('/api/v1/'+path,data?{method:'POST',headers:{'Content-Type':'application/json','X-Athena-Deck':'1'},body:JSON.stringify(data)}:{});const v=await r.json();if(!r.ok)throw Error(v.error||'Anfrage fehlgeschlagen');return v;}
function html(readonly=false){return `<section id="image-test"><div class="section-heading"><div><h2>${readonly?'Bildlaufzeit':'Bildmodell testen'}</h2><p>Ein Bild mit deinem gespeicherten Profil erzeugen. Der eigene Worker beendet sich nach dem Auftrag.</p></div></div>${readonly?'':`<form id="image-test-form" class="card"><label>Bildprofil<select name="profile_id" id="image-test-profile" required><option value="">Profile werden geladen …</option></select></label><p id="image-profile-info"></p><label>Prompt<textarea name="prompt" maxlength="4000" rows="5" required placeholder="Beschreibe das gewünschte Bild …"></textarea></label><label id="image-reference-label">Referenzbilder (optional)<input id="image-reference-files" name="image[]" type="file" accept="image/png,image/jpeg,image/webp" multiple></label><p id="image-reference-info" class="small"></p><button id="image-generate" disabled>Bild generieren</button><p class="small">Auflösung, Schritte, Guidance und Seed stammen aus dem Profil. Maximal 1024 × 1024 Pixel. Beide GPUs müssen frei sein; Deck stoppt keine produktiven Modelle. Der erste Start kann einige Minuten dauern.</p></form>`}<p class="note">Textencoder: RTX 3060 · Diffusionsmodell und VAE: RTX 5080. CPU/RAM werden weiterhin zum Laden und Auslagern benötigt. GPU-Last entsteht nicht während aller Verarbeitungsschritte. <a class="link" href="#hardware">Aktuelle Hardware-Messwerte →</a></p><p id="image-test-message" role="status"></p><section id="image-test-state" class="card"></section><section id="image-test-result"></section></section>`;}
function html(readonly=false){return `<section id="image-test"><div class="section-heading"><div><h2>${readonly?'Bildlaufzeit':'Bildmodell testen'}</h2><p>Ein Bild mit deinem gespeicherten Profil erzeugen. Der eigene Worker beendet sich nach dem Auftrag.</p></div></div>${readonly?'':`<form id="image-test-form" class="card"><label>Bildprofil<select name="profile_id" id="image-test-profile" required><option value="">Profile werden geladen …</option></select></label><p id="image-profile-info"></p><label>Prompt<textarea name="prompt" maxlength="4000" rows="5" required placeholder="Beschreibe das gewünschte Bild …"></textarea></label><label id="image-reference-label">Referenzbilder (optional)<input id="image-reference-files" name="image[]" type="file" accept="image/png,image/jpeg,image/webp" multiple></label><p id="image-reference-info" class="small"></p><input type="hidden" name="skip_enhancer" id="image-skip-enhancer" value="0"><button type="button" class="secondary" id="image-preview-enhancer">Prompt-Aufbereitung ansehen</button><button id="image-generate" disabled>Bild generieren</button><div id="image-enhancer-preview" role="status"></div><p class="small">Auflösung, Schritte, Guidance und Seed stammen aus dem Profil. Maximal 1024 × 1024 Pixel. Beide GPUs müssen frei sein; Deck stoppt keine produktiven Modelle. Der erste Start kann einige Minuten dauern.</p></form>`}<p class="note">Textencoder: RTX 3060 · Diffusionsmodell und VAE: RTX 5080. CPU/RAM werden weiterhin zum Laden und Auslagern benötigt. GPU-Last entsteht nicht während aller Verarbeitungsschritte. <a class="link" href="#hardware">Aktuelle Hardware-Messwerte →</a></p><p id="image-test-message" role="status"></p><section id="image-test-state" class="card"></section><section id="image-test-result"></section></section>`;}
function bind(readonly=false){
const root=document.querySelector('#image-test'),el=id=>root.querySelector('#'+id);let profiles=[],status=null,lastJob='',timer,submitting=false;
const root=document.querySelector('#image-test'),el=id=>root.querySelector('#'+id);let profiles=[],status=null,lastJob='',timer,submitting=false,previewId=null;
const message=t=>{if(root.isConnected)el('image-test-message').textContent=t;};
function selection(){if(readonly)return;const p=profiles.find(p=>p.id===el('image-test-profile').value),max=p?.capabilities?.reference_images||0;el('image-profile-info').textContent=p?`${p.parameters.width} × ${p.parameters.height} · ${p.parameters.steps} Schritte · Guidance ${p.parameters.guidance} · Seed ${p.parameters.seed}${p.blockers.length?' · '+p.blockers.join(' '):''}`:'';el('image-reference-label').hidden=!max;el('image-reference-files').disabled=!max;el('image-reference-info').textContent=max?`Dieses Profil erlaubt bis zu ${max} Referenzbilder. Reihenfolge: wie ausgewählt; PNG/JPEG/WebP, je höchstens 10 MiB.`:'Dieses Profil unterstützt keine Referenzbilder.';el('image-generate').disabled=submitting||!p?.runnable||!status?.runtime_installed||status?.job?.state==='running';}
function selection(){if(readonly)return;const p=profiles.find(p=>p.id===el('image-test-profile').value),max=p?.capabilities?.reference_images||0;el('image-profile-info').textContent=p?`${p.parameters.width} × ${p.parameters.height} · ${p.parameters.steps} Schritte · Guidance ${p.parameters.guidance} · Seed ${p.parameters.seed}${p.blockers.length?' · '+p.blockers.join(' '):''}`:'';el('image-reference-label').hidden=!max;el('image-reference-files').disabled=!max;el('image-reference-info').textContent=max?`Dieses Profil erlaubt bis zu ${max} Referenzbilder. Reihenfolge: wie ausgewählt; PNG/JPEG/WebP, je höchstens 10 MiB.`:'Dieses Profil unterstützt keine Referenzbilder.';el('image-generate').disabled=submitting||!p?.runnable||!status?.runtime_installed||status?.job?.state==='running';el('image-preview-enhancer').disabled=submitting||!p?.prompt_enhancer?.[el('image-reference-files').files.length?'i2i':'t2i'];}
async function preview(){
const button=el('image-preview-enhancer'),area=el('image-enhancer-preview');button.disabled=true;area.textContent='Prompt-Aufwerter startet; Bildmodell wartet.';
try{
const form=new FormData();form.set('profile_id',el('image-test-profile').value);form.set('prompt',el('image-test-form').elements.prompt.value);
for(const file of el('image-reference-files').files)form.append('image[]',file);
const response=await fetch('/api/v1/prompt-enhancers/preview',{method:'POST',headers:{'X-Athena-Deck':'1'},body:form});const job=await response.json();
if(!response.ok)throw Error(job.error||'Vorschau konnte nicht starten.');previewId=job.id;
async function poll(){
if(!root.isConnected||previewId!==job.id)return;
const state=(await api('prompt-enhancers')).preview;if(!state||state.id!==job.id)return;
if(state.state==='running'){area.textContent=state.phase;setTimeout(poll,2000);return;}
if(state.state!=='complete')throw Error(state.phase||'Aufbereitung fehlgeschlagen.');
area.innerHTML=`<p><strong>Vorschlag</strong> · Seitenverhältnis: ${e(state.result.wh_ratio||'kein Vorschlag')}</p><textarea id="image-enhanced-text" rows="8" maxlength="4000"></textarea><p><button type="button" id="image-use-enhanced">Vorschlag übernehmen</button><button type="button" class="secondary" id="image-use-original">Original behalten</button></p>`;
area.querySelector('textarea').value=state.result.prompt;
area.querySelector('#image-use-enhanced').onclick=()=>{el('image-test-form').elements.prompt.value=area.querySelector('textarea').value;el('image-skip-enhancer').value='1';area.textContent='Überarbeiteter Prompt übernommen. Bildgenerierung bereitet ihn nicht erneut auf.';};
area.querySelector('#image-use-original').onclick=()=>{el('image-skip-enhancer').value='1';area.textContent='Originalprompt bleibt. Für diesen Bildtest wird die Aufbereitung übersprungen.';};
}
poll().catch(error=>{area.textContent=error.message;});
}catch(error){area.textContent=error.message;}finally{button.disabled=false;}
}
async function refresh(){try{status=await api('image-tests');if(!root.isConnected)return;const j=status.job,signature=JSON.stringify(j);if(signature!==lastJob){lastJob=signature;el('image-test-state').innerHTML=j?`<span class="pill">${e({running:'LÄUFT',complete:'FERTIG',failed:'FEHLGESCHLAGEN',cancelled:'ABGEBROCHEN',interrupted:'UNTERBROCHEN'}[j.state]||j.state)}</span><h3>${e(j.profile_name)}</h3><p>${e(j.phase)}</p><p class="small">${e(j.gpu)}${j.encoder_gpu?' · Textencoder: '+e(j.encoder_gpu):''} · Seed ${e(j.seed)}</p>${j.state==='running'?'<button class="secondary" id="image-cancel">Bildtest abbrechen</button>':''}`:`<h3>${status.runtime_installed?'Bildlaufzeit ist installiert':'Bildlaufzeit noch nicht installiert'}</h3><p>Noch kein Bildtest gestartet.</p>`;
el('image-test-result').innerHTML=j?.state==='complete'?`<figure class="card"><img class="generated-image" src="/api/v1/image-tests/image?id=${encodeURIComponent(j.id)}" alt="Ergebnis des Bildtests"><figcaption><a class="link" href="/api/v1/image-tests/image?id=${encodeURIComponent(j.id)}" download="athena-${e(j.id)}.png">PNG herunterladen</a></figcaption></figure>`:'';
el('image-cancel')?.addEventListener('click',async event=>{event.target.disabled=true;try{await api('image-tests/cancel',{});message('Abbruch angefordert; der eigene Bildworker wird beendet.');}catch(error){message(error.message);}});
}selection();}catch(error){message(error.message);}finally{if(root.isConnected)timer=setTimeout(refresh,2000);}}
if(!readonly){api('profiles').then(v=>{if(!root.isConnected)return;profiles=v.profiles.filter(p=>p.kind==='image');el('image-test-profile').innerHTML=profiles.map(p=>`<option value="${p.id}">${e(p.name)}${p.runnable?'':' · Komponenten / Laufzeit prüfen'}</option>`).join('')||'<option value="">Zuerst ein Bildprofil anlegen</option>';selection();}).catch(error=>message(error.message));el('image-test-profile').onchange=()=>{el('image-reference-files').value='';selection();};
if(!readonly){api('profiles').then(v=>{if(!root.isConnected)return;profiles=v.profiles.filter(p=>p.kind==='image');el('image-test-profile').innerHTML=profiles.map(p=>`<option value="${p.id}">${e(p.name)}${p.runnable?'':' · Komponenten / Laufzeit prüfen'}</option>`).join('')||'<option value="">Zuerst ein Bildprofil anlegen</option>';selection();}).catch(error=>message(error.message));const invalidate=()=>{previewId=null;el('image-skip-enhancer').value='0';el('image-enhancer-preview').replaceChildren();};el('image-test-profile').onchange=()=>{el('image-reference-files').value='';invalidate();selection();};
el('image-reference-files').onchange=()=>{invalidate();selection();};
el('image-test-form').elements.prompt.oninput=invalidate;
el('image-preview-enhancer').onclick=preview;
el('image-test-form').onsubmit=async event=>{event.preventDefault();submitting=true;selection();try{const form=new FormData(event.target),p=profiles.find(p=>p.id===el('image-test-profile').value),files=[...el('image-reference-files').files],max=p?.capabilities?.reference_images||0;if(files.length>max)throw Error(`Dieses Profil erlaubt höchstens ${max} Referenzbilder.`);if(files.some(f=>f.size>10*1024*1024))throw Error('Jedes Referenzbild darf höchstens 10 MiB groß sein.');form.delete('image[]');for(const file of files)form.append('image[]',file);const r=await fetch('/api/v1/image-tests/start',{method:'POST',headers:{'X-Athena-Deck':'1'},body:form});const result=await r.json();if(!r.ok)throw Error(result.error||'Bildtest fehlgeschlagen.');message('Bildtest gestartet.');}catch(error){message(error.message);}finally{submitting=false;selection();}};
}
refresh();