Download code/static/index.html from Modularcomputing/dynamic-accent-duplex: direct link, hf CLI and curl.
- Browser
- Download file 4.61 kB
-
https://huggingface.co/Modularcomputing/dynamic-accent-duplex/resolve/ac573f1b218a7c549ca46573107d3833a92d0ed3/code/static/index.html
- Command line
-
hf download hf://Modularcomputing/dynamic-accent-duplex@ac573f1b218a7c549ca46573107d3833a92d0ed3/code/static/index.html
-
curl -L -o index.html https://huggingface.co/Modularcomputing/dynamic-accent-duplex/resolve/ac573f1b218a7c549ca46573107d3833a92d0ed3/code/static/index.html
4.61 kB
| <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>Dynamic Accent Duplex (experimental)</title> | |
| <style>body{font-family:system-ui,sans-serif;max-width:760px;margin:24px auto;padding:0 14px;color:#222} | |
| button{font-size:16px;padding:10px 18px;margin-right:8px;border-radius:8px;border:1px solid #888;background:#f4f4f4;cursor:pointer} | |
| #log{border:1px solid #ddd;border-radius:8px;padding:10px;height:420px;overflow:auto;font-size:14px;background:#fafafa} | |
| .u{color:#0a4}.a{color:#036}.p{color:#999;font-style:italic}.m{color:#a60;font-size:12px}.s{color:#c00} | |
| #dot{display:inline-block;width:12px;height:12px;border-radius:6px;background:#ccc;vertical-align:middle;margin-left:6px} | |
| small{color:#666}</style></head><body> | |
| <h2>Dynamic Accent Duplex <small>experimental cascaded voice agent</small></h2> | |
| <p>Speak English, Nigerian Pidgin, Igbo, Yoruba or Hausa, mixed however you like. Ada answers in the same mix. You can interrupt her. | |
| <b>Use headphones</b> (otherwise her voice can trigger barge-in).</p> | |
| <button id="go">Start</button><button id="rs">Reset conversation</button> mic<span id="dot"></span> | |
| <div id="log"></div> | |
| <p><small>Pipeline: Silero VAD → Omnilingual ASR (CTC) → per-word language ID → Qwen3 LLM with mix validator → Dynamic Accent Flash TTS. Research demo; replies can be wrong, and Igbo/Yoruba/Hausa beyond simple phrases are weak.</small></p> | |
| <script> | |
| const log=(c,t)=>{const d=document.getElementById('log');const e=document.createElement('div');e.className=c;e.textContent=t;d.appendChild(e);d.scrollTop=1e9;return e}; | |
| const strip=t=>t.replace(/<\/?(eng|ibo|yor|hau|pcm)>/g,m=>m.startsWith('</')?'':'['+m.slice(1,-1)+'] '); | |
| let ws,ctx,next=0,srcs=[],partialEl=null; | |
| function play(buf){const i16=new Int16Array(buf);const f=new Float32Array(i16.length);for(let i=0;i<i16.length;i++)f[i]=i16[i]/32768; | |
| const b=ctx.createBuffer(1,f.length,24000);b.copyToChannel(f,0);const s=ctx.createBufferSource();s.buffer=b;s.connect(ctx.destination); | |
| const t=Math.max(ctx.currentTime+0.02,next);s.start(t);next=t+b.duration;srcs.push(s);s.onended=()=>{srcs=srcs.filter(x=>x!==s)}} | |
| function stopAll(){srcs.forEach(s=>{try{s.stop()}catch(e){}});srcs=[];next=0} | |
| document.getElementById('rs').onclick=()=>{ws&&ws.send(JSON.stringify({type:'reset'}));document.getElementById('log').innerHTML=''}; | |
| document.getElementById('go').onclick=async()=>{ | |
| ctx=new AudioContext({sampleRate:16000}); | |
| const st=await navigator.mediaDevices.getUserMedia({audio:{echoCancellation:true,noiseSuppression:true,autoGainControl:true,channelCount:1}}); | |
| const code=`class P extends AudioWorkletProcessor{constructor(){super();this.b=new Float32Array(512);this.n=0} | |
| process(i){const x=i[0][0];if(!x)return true;for(let k=0;k<x.length;k++){this.b[this.n++]=x[k];if(this.n==512){const o=new Int16Array(512); | |
| for(let j=0;j<512;j++){o[j]=Math.max(-32768,Math.min(32767,this.b[j]*32767))}this.port.postMessage(o.buffer,[o.buffer]);this.n=0}}return true}} | |
| registerProcessor('p',P)`; | |
| await ctx.audioWorklet.addModule(URL.createObjectURL(new Blob([code],{type:'application/javascript'}))); | |
| const node=new AudioWorkletNode(ctx,'p');ctx.createMediaStreamSource(st).connect(node); | |
| ws=new WebSocket((location.protocol==='https:'?'wss':'ws')+'://'+location.host+location.pathname.replace(/\/$/,'')+'/ws');ws.binaryType='arraybuffer'; | |
| node.port.onmessage=e=>{if(ws.readyState===1)ws.send(e.data)}; | |
| ws.onopen=()=>log('m','connected - start talking'); | |
| ws.onmessage=e=>{if(typeof e.data!=='string'){play(e.data);return}const d=JSON.parse(e.data); | |
| if(d.type==='vad'){document.getElementById('dot').style.background=d.speech?'#0a4':'#ccc'} | |
| else if(d.type==='partial'){if(!partialEl)partialEl=log('p','');partialEl.textContent='… '+d.text} | |
| else if(d.type==='final'){if(partialEl){partialEl.remove();partialEl=null}log('u','You: '+d.text)} | |
| else if(d.type==='reply'){log('a','Ada: '+strip(d.text));log('m','target mix '+JSON.stringify(d.target.mix)+' '+(d.target.pattern||[]).join(',')+' | reply mix '+JSON.stringify(d.reply_mix)+' | '+d.how)} | |
| else if(d.type==='metrics'){log('m','latency end-of-speech→first audio '+d.latency_s+' s (EOT wait '+d.eot_ms+' ms, ASR '+d.asr_ms+' ms, LLM '+d.llm_s+' s, TTS first chunk '+d.tts_first_s+' s)')} | |
| else if(d.type==='stop'){stopAll();log('s','[barge-in: Ada stopped]')} | |
| else if(d.type==='backchannel'){log('m','(backchannel)')} | |
| else if(d.type==='info'){log('m',d.text)}}; | |
| ws.onclose=()=>log('s','disconnected');document.getElementById('go').disabled=true}; | |
| </script></body></html> | |