window.STTUI=(()=>{ const e=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); async function api(path,data){const response=await fetch('/api/v1/'+path,data===undefined?{}:{method:'POST',headers:{'X-Athena-Deck':'1',...(data instanceof FormData?{}:{'Content-Type':'application/json'})},body:data instanceof FormData?data:JSON.stringify(data)});const result=await response.json();if(!response.ok)throw Error(result.error||'STT-Anfrage fehlgeschlagen');return result;} function html(setup=false){return `

${setup?'Spracherkennung einrichten':'Audio in Text umwandeln'}

${setup?'

Qwen3-ASR 0.6B Q8_0 nutzt die vorhandene llama.cpp-Laufzeit auf der CPU. Installiert werden die geprüfte Modellvariante von ggml-org (rund 805 MB) und der Audio-Projektor (214 MB). Andere GGUF-Konvertierungen können inkompatibel sein. Bestehende Dienste werden nicht verwendet oder verändert.

llama.cpp installieren / Build auswählen →

Der Download erscheint unter STT → Downloads. Nach Abschluss ein neues Profil aus der Bibliothek anlegen oder ein bestehendes Profil unten ausdrücklich umstellen. Alte Dateien bleiben erhalten.

Vorhandenes Profil umstellen

Ersetzt die Modellzuordnung durch die geprüfte ggml-org-Variante. Profilname bleibt erhalten.

Speicherverhalten

Automatisch hält das Modell nach der ersten Anfrage geladen. Immer bereit lädt das gewählte Profil im LLM-Betrieb vor, soweit Ressourcen frei sind. Nach jeder Anfrage entladen gibt Speicher sofort frei.

':'

Transkript

Die Audiodatei wird im Browser in Mono-WAV mit 16 kHz umgewandelt. Unterstützte Eingabeformate hängen vom Browser ab. Audio und Transkript werden nicht dauerhaft gespeichert; das letzte Ergebnis bleibt bis zum nächsten Auftrag im Arbeitsspeicher.

'}
`;} async function wav(file){if(file.size>20*1024**2)throw Error('Datei ist größer als 20 MiB.');const context=new AudioContext();let decoded;try{decoded=await context.decodeAudioData(await file.arrayBuffer());}finally{await context.close();}if(!decoded.duration||decoded.duration>120)throw Error('Maximal 120 Sekunden Audio erlaubt.');const offline=new OfflineAudioContext(1,Math.ceil(decoded.duration*16000),16000),source=offline.createBufferSource();source.buffer=decoded;source.connect(offline.destination);source.start();const samples=(await offline.startRendering()).getChannelData(0),buffer=new ArrayBuffer(44+samples.length*2),v=new DataView(buffer);const str=(at,s)=>{for(let i=0;iv.setInt16(44+i*2,Math.max(-1,Math.min(1,s))*(s<0?32768:32767),true));return new Blob([buffer],{type:'audio/wav'});} function bind(setup=false){const root=document.querySelector('#stt-panel');if(!root)return;const el=id=>root.querySelector('#'+id);let busy=false,profiles=[]; async function refresh(){try{const s=await api('stt');if(!root.isConnected)return;el('stt-status').textContent=setup?`llama.cpp: ${s.installed?'vorhanden':'fehlt'} · Modell: ${s.model?'vorhanden':'fehlt'} · Audio-Projektor: ${s.projector?'vorhanden':'fehlt'}`:(s.job?.phase||'Profil und Audiodatei auswählen.')+` · CPU-Modell ${s.loaded?'geladen':'nicht geladen'}`;if(setup){el('stt-setup').disabled=busy||(s.projector&&s.model);el('stt-assign').disabled=busy||!s.model||!profiles.length;}else{el('stt-start').disabled=busy||s.job?.state==='running'||!profiles.length;el('stt-cancel').disabled=s.job?.state!=='running';el('stt-result').textContent=s.job?.text||'';}}catch(err){if(root.isConnected)el('stt-error').textContent=err.message;}finally{if(root.isConnected)setTimeout(refresh,2000);}} if(setup){api('profiles').then(p=>{if(!root.isConnected)return;profiles=p.profiles.filter(p=>p.kind==='stt');el('stt-profile').innerHTML=profiles.map(p=>``).join('');}).catch(err=>{if(root.isConnected)el('stt-error').textContent=err.message;});el('stt-assign').onclick=async()=>{const p=profiles.find(p=>p.id===el('stt-profile').value);if(!p)return;busy=true;try{await api('stt/assign',{id:p.id,revision:p.revision});el('stt-error').textContent='Profil umgestellt. Jetzt STT → Testen öffnen.';p.revision++;}catch(err){el('stt-error').textContent=err.message;}finally{busy=false;}};el('stt-setup').onclick=async()=>{busy=true;try{await api('stt/setup',{});el('stt-error').textContent='Download ist eingeplant. Fortschritt unter Downloads.';}catch(err){el('stt-error').textContent=err.message;}finally{busy=false;}};} else{api('profiles').then(p=>{if(!root.isConnected)return;profiles=p.profiles.filter(p=>p.kind==='stt'&&p.runnable);el('stt-profile').innerHTML=profiles.map(p=>``).join('')||'';}).catch(err=>{if(root.isConnected)el('stt-error').textContent=err.message;});el('stt-form').onsubmit=async ev=>{ev.preventDefault();busy=true;el('stt-start').disabled=true;el('stt-error').textContent='';try{const body=new FormData();body.set('file',await wav(el('stt-file').files[0]),'audio.wav');body.set('profile_id',el('stt-profile').value);body.set('language',el('stt-language').value);if(root.isConnected)await api('stt/start',body);}catch(err){if(root.isConnected)el('stt-error').textContent=err.message;}finally{busy=false;}};el('stt-cancel').onclick=()=>api('stt/cancel',{}).catch(err=>{if(root.isConnected)el('stt-error').textContent=err.message;});} if(setup){ const policy=el('stt-policy'),warm=el('stt-warm-profile'),save=el('stt-policy-save'),status=el('stt-policy-state'); Promise.all([api('audio-policy'),api('profiles')]).then(([current,rows])=>{if(!root.isConnected)return;warm.innerHTML=rows.profiles.filter(p=>p.kind==='stt'&&p.runnable).map(p=>``).join('');const selected=current.settings.stt;policy.value=selected.mode;if(selected.profile_id)warm.value=selected.profile_id;status.textContent=current.errors.stt||'';}).catch(err=>{if(root.isConnected)status.textContent=err.message;}); save.onclick=async()=>{save.disabled=true;try{const updated=await api('audio-policy',{kind:'stt',mode:policy.value,profile_id:warm.value||null});status.textContent=updated.errors.stt||'Gespeichert.';}catch(err){status.textContent=err.message;}finally{save.disabled=false;}}; } refresh(); } return {html,bind}; })();