t}function i(f){return r(f)||s(f)}function o(f){return i(f)?r(f)?e:t:f}function l(f){return n?f-n*Math.ceil((f-t)/n):f}return{length:n,max:t,min:e,constrain:o,reachedAny:i,reachedMax:s,reachedMin:r,removeOffset:l}}function Gf(e,t,n){const{constrain:r}=Qt(0,e),s=e+1;let i=o(t);function o(p){return n?ge((s+p)%s):r(p)}function l(){return i}function a(p){return i=o(p),u}function f(p){return c().set(l()+p)}function c(){return Gf(e,l(),n)}const u={get:l,set:a,add:f,clone:c};return u}function Mm(e,t,n,r,s,i,o,l,a,f,c,u,p,g,m,b,M,y,x){const{cross:_,direction:S}=e,N=["INPUT","SELECT","TEXTAREA"],E={passive:!1},O=sr(),A=sr(),P=Qt(50,225).constrain(g.measure(20)),z={mouse:300,touch:400},H={mouse:500,touch:600},k=m?43:25;let U=!1,Z=0,se=0,ee=!1,J=!1,Y=!1,ne=!1;function me(d){if(!x)return;function h(w){(xs(x)||x(d,w))&&We(w)}const v=t;O.add(v,"dragstart",w=>w.preventDefault(),E).add(v,"touchmove",()=>{},E).add(v,"touchend",()=>{}).add(v,"touchstart",h).add(v,"mousedown",h).add(v,"touchcancel",Oe).add(v,"contextmenu",Oe).add(v,"click",Qe,!0)}function ce(){O.clear(),A.clear()}function fe(){const d=ne?n:t;A.add(d,"touchmove",be,E).add(d,"touchend",Oe).add(d,"mousemove",be,E).add(d,"mouseup",Oe)}function Ve(d){const h=d.nodeName||"";return N.includes(h)}function Be(){return(m?H:z)[ne?"mouse":"touch"]}function mt(d,h){const v=u.add(Po(d)*-1),w=c.byDistance(d,!m).distance;return m||ge(d)=2,!(h&&d.button!==0)&&(Ve(d.target)||(ee=!0,i.pointerDown(d),f.useFriction(0).useDuration(0),s.set(o),fe(),Z=i.readPoint(d),se=i.readPoint(d,_),p.emit("pointerDown")))}function be(d){if(!Hi(d,r)&&d.touches.length>=2)return Oe(d);const v=i.readPoint(d),w=i.readPoint(d,_),T=Bn(v,Z),C=Bn(w,se);if(!J&&!ne&&(!d.cancelable||(J=T>C,!J)))return Oe(d);const j=i.pointerMove(d);T>b&&(Y=!0),f.useFriction(.3).useDuration(.75),l.start(),s.add(S(j)),d.preventDefault()}function Oe(d){const v=c.byDistance(0,!1).index!==u.get(),w=i.pointerUp(d)*Be(),T=mt(S(w),v),C=mm(w,T),j=k-10*C,R=y+C/50;J=!1,ee=!1,A.clear(),f.useDuration(j).useFriction(R),a.distance(T,!m),ne=!1,p.emit("pointerUp")}function Qe(d){Y&&(d.stopPropagation(),d.preventDefault(),Y=!1)}function Ae(){return ee}return{init:me,destroy:ce,pointerDown:Ae}}function _m(e,t){let r,s;function i(u){return u.timeStamp}function o(u,p){const m=`client${(p||e.scroll)==="x"?"X":"Y"}`;return(Hi(u,t)?u:u.touches[0])[m]}function l(u){return r=u,s=u,o(u)}function a(u){const p=o(u)-o(s),g=i(u)-i(r)>170;return s=u,g&&(r=u),p}function f(u){if(!r||!s)return 0;const p=o(s)-o(r),g=i(u)-i(r),m=i(u)-i(s)>170,b=p/g;return g&&!m&&ge(b)>.1?b:0}return{pointerDown:l,pointerMove:a,pointerUp:f,readPoint:o}}function Sm(){function e(n){const{offsetTop:r,offsetLeft:s,offsetWidth:i,offsetHeight:o}=n;return{top:r,right:s+i,bottom:r+o,left:s,width:i,height:o}}return{measure:e}}function Tm(e){function t(r){return e*(r/100)}return{measure:t}}function wm(e,t,n,r,s,i,o){const l=[e].concat(r);let a,f,c=[],u=!1;function p(M){return s.measureSize(o.measure(M))}function g(M){if(!i)return;f=p(e),c=r.map(p);function y(x){for(const _ of x){if(u)return;const S=_.target===e,N=r.indexOf(_.target),E=S?f:c[N],O=p(S?e:r[N]);if(ge(O-E)>=.5){M.reInit(),t.emit("resize");break}}}a=new ResizeObserver(x=>{(xs(i)||i(M,x))&&y(x)}),n.requestAnimationFrame(()=>{l.forEach(x=>a.observe(x))})}function m(){u=!0,a&&a.disconnect()}return{init:g,destroy:m}}function Om(e,t,n,r,s,i){let o=0,l=0,a=s,f=i,c=e.get(),u=0;function p(){const E=r.get()-e.get(),O=!a;let A=0;return O?(o=0,n.set(r),e.set(r),A=E):(n.set(e),o+=E/a,o*=f,c+=o,e.add(o),A=c-u),l=Po(A),u=c,N}function g(){const E=r.get()-t.get();return ge(E)<.001}function m(){return a}function b(){return l}function M(){return o}function y(){return _(s)}function x(){return S(i)}function _(E){return a=E,N}function S(E){return f=E,N}const N={direction:b,duration:m,velocity:M,seek:p,settled:g,useBaseFriction:x,useBaseDuration:y,useFriction:S,useDuration:_};return N}function Cm(e,t,n,r,s){const i=s.measure(10),o=s.measure(50),l=Qt(.1,.99);let a=!1;function f(){return!(a||!e.reachedAny(n.get())||!e.reachedAny(t.get()))}function c(g){if(!f())return;const m=e.reachedMin(t.get())?"min":"max",b=ge(e[m]-t.get()),M=n.get()-t.get(),y=l.constrain(b/o);n.subtract(M*y),!g&&ge(M){const{min:M,max:y}=i,x=i.constrain(m),_=!b,S=ko(n,b);return _?y:S||f(M,x)?M:f(y,x)?y:x}).map(m=>parseFloat(m.toFixed(3)))}function p(){if(t<=e+s)return[i.max];if(r==="keepSnaps")return o;const{min:m,max:b}=l;return o.slice(m,b)}return{snapsContained:a,scrollContainLimit:l}}function Em(e,t,n){const r=t[0],s=n?r-e:ot(t);return{limit:Qt(s,r)}}function Im(e,t,n,r){const i=t.min+.1,o=t.max+.1,{reachedMin:l,reachedMax:a}=Qt(i,o);function f(p){return p===1?a(n.get()):p===-1?l(n.get()):!1}function c(p){if(!f(p))return;const g=e*(p*-1);r.forEach(m=>m.add(g))}return{loop:c}}function Pm(e){const{max:t,length:n}=e;function r(i){const o=i-t;return n?o/-n:0}return{get:r}}function km(e,t,n,r,s){const{startEdge:i,endEdge:o}=e,{groupSlides:l}=s,a=u().map(t.measure),f=p(),c=g();function u(){return l(r).map(b=>ot(b)[o]-b[0][i]).map(ge)}function p(){return r.map(b=>n[i]-b[i]).map(b=>-ge(b))}function g(){return l(f).map(b=>b[0]).map((b,M)=>b+a[M])}return{snaps:f,snapsAligned:c}}function $m(e,t,n,r,s,i){const{groupSlides:o}=s,{min:l,max:a}=r,f=c();function c(){const p=o(i),g=!e||t==="keepSnaps";return n.length===1?[i]:g?p:p.slice(l,a).map((m,b,M)=>{const y=!b,x=ko(M,b);if(y){const _=ot(M[0])+1;return ca(_)}if(x){const _=hr(i)-ot(M)[0]+1;return ca(_,ot(M)[0])}return m})}return{slideRegistry:f}}function Lm(e,t,n,r,s){const{reachedAny:i,removeOffset:o,constrain:l}=r;function a(m){return m.concat().sort((b,M)=>ge(b)-ge(M))[0]}function f(m){const b=e?o(m):l(m),M=t.map((x,_)=>({diff:c(x-b,0),index:_})).sort((x,_)=>ge(x.diff)-ge(_.diff)),{index:y}=M[0];return{index:y,distance:b}}function c(m,b){const M=[m,m+n,m-n];if(!e)return m;if(!b)return a(M);const y=M.filter(x=>Po(x)===b);return y.length?a(y):ot(M)-n}function u(m,b){const M=t[m]-s.get(),y=c(M,b);return{index:m,distance:y}}function p(m,b){const M=s.get()+m,{index:y,distance:x}=f(M),_=!e&&i(M);if(!b||_)return{index:y,distance:m};const S=t[y]-x,N=m+c(S,0);return{index:y,distance:N}}return{byDistance:p,byIndex:u,shortcut:c}}function Rm(e,t,n,r,s,i,o){function l(u){const p=u.distance,g=u.index!==t.get();i.add(p),p&&(r.duration()?e.start():(e.update(),e.render(1),e.update())),g&&(n.set(t.get()),t.set(u.index),o.emit("select"))}function a(u,p){const g=s.byDistance(u,p);l(g)}function f(u,p){const g=t.clone().set(u),m=s.byIndex(g.get(),p);l(m)}return{distance:a,index:f}}function Nm(e,t,n,r,s,i,o,l){const a={passive:!0,capture:!0};let f=0;function c(g){if(!l)return;function m(b){if(new Date().getTime()-f>10)return;o.emit("slideFocusStart"),e.scrollLeft=0;const x=n.findIndex(_=>_.includes(b));Io(x)&&(s.useDuration(0),r.index(x,0),o.emit("slideFocus"))}i.add(document,"keydown",u,!1),t.forEach((b,M)=>{i.add(b,"focus",y=>{(xs(l)||l(g,y))&&m(M)},a)})}function u(g){g.code==="Tab"&&(f=new Date().getTime())}return{init:c}}function Pn(e){let t=e;function n(){return t}function r(a){t=o(a)}function s(a){t+=o(a)}function i(a){t-=o(a)}function o(a){return Io(a)?a:a.get()}return{get:n,set:r,add:s,subtract:i}}function Zf(e,t){const n=e.scroll==="x"?o:l,r=t.style;let s=null,i=!1;function o(p){return`translate3d(${p}px,0px,0px)`}function l(p){return`translate3d(0px,${p}px,0px)`}function a(p){if(i)return;const g=bm(e.direction(p));g!==s&&(r.transform=n(g),s=g)}function f(p){i=!p}function c(){i||(r.transform="",t.getAttribute("style")||t.removeAttribute("style"))}return{clear:c,to:a,toggleActive:f}}function jm(e,t,n,r,s,i,o,l,a){const c=nr(s),u=nr(s).reverse(),p=y().concat(x());function g(O,A){return O.reduce((P,z)=>P-s[z],A)}function m(O,A){return O.reduce((P,z)=>g(P,A)>0?P.concat([z]):P,[])}function b(O){return i.map((A,P)=>({start:A-r[P]+.5+O,end:A+t-.5+O}))}function M(O,A,P){const z=b(A);return O.map(H=>{const k=P?0:-n,U=P?n:0,Z=P?"end":"start",se=z[H][Z];return{index:H,loopPoint:se,slideLocation:Pn(-1),translate:Zf(e,a[H]),target:()=>l.get()>se?k:U}})}function y(){const O=o[0],A=m(u,O);return M(A,n,!1)}function x(){const O=t-o[0]-1,A=m(c,O);return M(A,-n,!0)}function _(){return p.every(({index:O})=>{const A=c.filter(P=>P!==O);return g(A,t)<=.1})}function S(){p.forEach(O=>{const{target:A,translate:P,slideLocation:z}=O,H=A();H!==z.get()&&(P.to(H),z.set(H))})}function N(){p.forEach(O=>O.translate.clear())}return{canLoop:_,clear:N,loop:S,loopPoints:p}}function Dm(e,t,n){let r,s=!1;function i(a){if(!n)return;function f(c){for(const u of c)if(u.type==="childList"){a.reInit(),t.emit("slidesChanged");break}}r=new MutationObserver(c=>{s||(xs(n)||n(a,c))&&f(c)}),r.observe(e,{childList:!0})}function o(){r&&r.disconnect(),s=!0}return{init:i,destroy:o}}function Fm(e,t,n,r){const s={};let i=null,o=null,l,a=!1;function f(){l=new IntersectionObserver(m=>{a||(m.forEach(b=>{const M=t.indexOf(b.target);s[M]=b}),i=null,o=null,n.emit("slidesInView"))},{root:e.parentElement,threshold:r}),t.forEach(m=>l.observe(m))}function c(){l&&l.disconnect(),a=!0}function u(m){return rr(s).reduce((b,M)=>{const y=parseInt(M),{isIntersecting:x}=s[y];return(m&&x||!m&&!x)&&b.push(y),b},[])}function p(m=!0){if(m&&i)return i;if(!m&&o)return o;const b=u(m);return m&&(i=b),m||(o=b),b}return{init:f,destroy:c,get:p}}function zm(e,t,n,r,s,i){const{measureSize:o,startEdge:l,endEdge:a}=e,f=n[0]&&s,c=m(),u=b(),p=n.map(o),g=M();function m(){if(!f)return 0;const x=n[0];return ge(t[l]-x[l])}function b(){if(!f)return 0;const x=i.getComputedStyle(ot(r));return parseFloat(x.getPropertyValue(`margin-${a}`))}function M(){return n.map((x,_,S)=>{const N=!_,E=ko(S,_);return N?p[_]+c:E?p[_]+u:S[_+1][l]-x[l]}).map(ge)}return{slideSizes:p,slideSizesWithGaps:g,startGap:c,endGap:u}}function Hm(e,t,n,r,s,i,o,l,a){const{startEdge:f,endEdge:c,direction:u}=e,p=Io(n);function g(y,x){return nr(y).filter(_=>_%x===0).map(_=>y.slice(_,_+x))}function m(y){return y.length?nr(y).reduce((x,_,S)=>{const N=ot(x)||0,E=N===0,O=_===hr(y),A=s[f]-i[N][f],P=s[f]-i[_][c],z=!r&&E?u(o):0,H=!r&&O?u(l):0,k=ge(P-H-(A+z));return S&&k>t+a&&x.push(_),O&&x.push(y.length),x},[]).map((x,_,S)=>{const N=Math.max(S[_-1]||0);return y.slice(N,x)}):[]}function b(y){return p?g(y,n):m(y)}return{groupSlides:b}}function Vm(e,t,n,r,s,i,o){const{align:l,axis:a,direction:f,startIndex:c,loop:u,duration:p,dragFree:g,dragThreshold:m,inViewThreshold:b,slidesToScroll:M,skipSnaps:y,containScroll:x,watchResize:_,watchSlides:S,watchDrag:N,watchFocus:E}=i,O=2,A=Sm(),P=A.measure(t),z=n.map(A.measure),H=xm(a,f),k=H.measureSize(P),U=Tm(k),Z=ym(l,k),se=!u&&!!x,ee=u||!!x,{slideSizes:J,slideSizesWithGaps:Y,startGap:ne,endGap:me}=zm(H,P,z,n,ee,s),ce=Hm(H,k,M,u,P,z,ne,me,O),{snaps:fe,snapsAligned:Ve}=km(H,Z,P,z,ce),Be=-ot(fe)+ot(Y),{snapsContained:mt,scrollContainLimit:We}=Am(k,Be,Ve,x,O),be=se?mt:Ve,{limit:Oe}=Em(Be,be,u),Qe=Gf(hr(be),c,u),Ae=Qe.clone(),he=nr(n),d=({dragHandler:ye,scrollBody:Xe,scrollBounds:Ye,options:{loop:at}})=>{at||Ye.constrain(ye.pointerDown()),Xe.seek()},h=({scrollBody:ye,translate:Xe,location:Ye,offsetLocation:at,previousLocation:gr,scrollLooper:Ue,slideLooper:et,dragHandler:mr,animation:Qf,eventHandler:Lo,scrollBounds:eu,options:{loop:Ro}},No)=>{const jo=ye.settled(),tu=!eu.shouldConstrain(),Do=Ro?jo:jo&&tu,Fo=Do&&!mr.pointerDown();Fo&&Qf.stop();const nu=Ye.get()*No+gr.get()*(1-No);at.set(nu),Ro&&(Ue.loop(ye.direction()),et.loop()),Xe.to(at.get()),Fo&&Lo.emit("settle"),Do||Lo.emit("scroll")},v=vm(r,s,()=>d(ke),ye=>h(ke,ye)),w=.68,T=be[Qe.get()],C=Pn(T),j=Pn(T),R=Pn(T),L=Pn(T),I=Om(C,R,j,L,p,w),B=Lm(u,be,Be,Oe,L),D=Rm(v,Qe,Ae,I,B,L,o),V=Pm(Oe),K=sr(),Q=Fm(t,n,o,b),{slideRegistry:le}=$m(se,x,be,We,ce,he),re=Nm(e,n,le,D,I,K,o,E),ke={ownerDocument:r,ownerWindow:s,eventHandler:o,containerRect:P,slideRects:z,animation:v,axis:H,dragHandler:Mm(H,e,r,s,L,_m(H,s),C,v,D,I,B,Qe,o,U,g,m,y,w,N),eventStore:K,percentOfView:U,index:Qe,indexPrevious:Ae,limit:Oe,location:C,offsetLocation:R,previousLocation:j,options:i,resizeHandler:wm(t,o,s,n,H,_,A),scrollBody:I,scrollBounds:Cm(Oe,R,L,I,U),scrollLooper:Im(Be,Oe,R,[C,R,j,L]),scrollProgress:V,scrollSnapList:be.map(V.get),scrollSnaps:be,scrollTarget:B,scrollTo:D,slideLooper:jm(H,k,Be,J,Y,fe,be,R,n),slideFocus:re,slidesHandler:Dm(t,o,S),slidesInView:Q,slideIndexes:he,slideRegistry:le,slidesToScroll:ce,target:L,translate:Zf(H,t)};return ke}function Bm(){let e={},t;function n(f){t=f}function r(f){return e[f]||[]}function s(f){return r(f).forEach(c=>c(t,f)),a}function i(f,c){return e[f]=r(f).concat([c]),a}function o(f,c){return e[f]=r(f).filter(u=>u!==c),a}function l(){e={}}const a={init:n,emit:s,off:o,on:i,clear:l};return a}const Um={align:"center",axis:"x",container:null,slides:null,containScroll:"trimSnaps",direction:"ltr",slidesToScroll:1,inViewThreshold:0,breakpoints:{},dragFree:!1,dragThreshold:10,loop:!1,skipSnaps:!1,duration:25,startIndex:0,active:!0,watchDrag:!0,watchResize:!0,watchSlides:!0,watchFocus:!0};function qm(e){function t(i,o){return Yf(i,o||{})}function n(i){const o=i.breakpoints||{},l=rr(o).filter(a=>e.matchMedia(a).matches).map(a=>o[a]).reduce((a,f)=>t(a,f),{});return t(i,l)}function r(i){return i.map(o=>rr(o.breakpoints||{})).reduce((o,l)=>o.concat(l),[]).map(e.matchMedia)}return{mergeOptions:t,optionsAtMedia:n,optionsMediaQueries:r}}function Km(e){let t=[];function n(i,o){return t=o.filter(({options:l})=>e.optionsAtMedia(l).active!==!1),t.forEach(l=>l.init(i,e)),o.reduce((l,a)=>Object.assign(l,{[a.name]:a}),{})}function r(){t=t.filter(i=>i.destroy())}return{init:n,destroy:r}}function Yr(e,t,n){const r=e.ownerDocument,s=r.defaultView,i=qm(s),o=Km(i),l=sr(),a=Bm(),{mergeOptions:f,optionsAtMedia:c,optionsMediaQueries:u}=i,{on:p,off:g,emit:m}=a,b=H;let M=!1,y,x=f(Um,Yr.globalOptions),_=f(x),S=[],N,E,O;function A(){const{container:he,slides:d}=_;E=(zi(he)?e.querySelector(he):he)||e.children[0];const v=zi(d)?E.querySelectorAll(d):d;O=[].slice.call(v||E.children)}function P(he){const d=Vm(e,E,O,r,s,he,a);if(he.loop&&!d.slideLooper.canLoop()){const h=Object.assign({},he,{loop:!1});return P(h)}return d}function z(he,d){M||(x=f(x,he),_=c(x),S=d||S,A(),y=P(_),u([x,...S.map(({options:h})=>h)]).forEach(h=>l.add(h,"change",H)),_.active&&(y.translate.to(y.location.get()),y.animation.init(),y.slidesInView.init(),y.slideFocus.init(Ae),y.eventHandler.init(Ae),y.resizeHandler.init(Ae),y.slidesHandler.init(Ae),y.options.loop&&y.slideLooper.loop(),E.offsetParent&&O.length&&y.dragHandler.init(Ae),N=o.init(Ae,S)))}function H(he,d){const h=ce();k(),z(f({startIndex:h},he),d),a.emit("reInit")}function k(){y.dragHandler.destroy(),y.eventStore.clear(),y.translate.clear(),y.slideLooper.clear(),y.resizeHandler.destroy(),y.slidesHandler.destroy(),y.slidesInView.destroy(),y.animation.destroy(),o.destroy(),l.clear()}function U(){M||(M=!0,l.clear(),k(),a.emit("destroy"),a.clear())}function Z(he,d,h){!_.active||M||(y.scrollBody.useBaseFriction().useDuration(d===!0?0:_.duration),y.scrollTo.index(he,h||0))}function se(he){const d=y.index.add(1).get();Z(d,he,-1)}function ee(he){const d=y.index.add(-1).get();Z(d,he,1)}function J(){return y.index.add(1).get()!==ce()}function Y(){return y.index.add(-1).get()!==ce()}function ne(){return y.scrollSnapList}function me(){return y.scrollProgress.get(y.offsetLocation.get())}function ce(){return y.index.get()}function fe(){return y.indexPrevious.get()}function Ve(){return y.slidesInView.get()}function Be(){return y.slidesInView.get(!1)}function mt(){return N}function We(){return y}function be(){return e}function Oe(){return E}function Qe(){return O}const Ae={canScrollNext:J,canScrollPrev:Y,containerNode:Oe,internalEngine:We,destroy:U,off:g,on:p,emit:m,plugins:mt,previousScrollSnap:fe,reInit:b,rootNode:be,scrollNext:se,scrollPrev:ee,scrollProgress:me,scrollSnapList:ne,scrollTo:Z,selectedScrollSnap:ce,slideNodes:Qe,slidesInView:Ve,slidesNotInView:Be};return z(t,n),setTimeout(()=>a.emit("init"),0),Ae}Yr.globalOptions=void 0;function $o(e={},t=[]){const n=Ie(e),r=Ie(t);let s=n?e.value:e,i=r?t.value:t;const o=kr(),l=kr();function a(){l.value&&l.value.reInit(s,i)}return Mn(()=>{!hm()||!o.value||(Yr.globalOptions=$o.globalOptions,l.value=Yr(o.value,s,i))}),eo(()=>{l.value&&l.value.destroy()}),n&&Se(e,f=>{Eo(s,f)||(s=f,a())}),r&&Se(t,f=>{gm(i,f)||(i=f,a())}),[o,l]}$o.globalOptions=void 0;const[Wm,Xm]=P0(({opts:e,orientation:t,plugins:n},r)=>{const[s,i]=$o({...e,axis:t==="horizontal"?"x":"y"},n);function o(){var u;(u=i.value)==null||u.scrollPrev()}function l(){var u;(u=i.value)==null||u.scrollNext()}const a=ae(!1),f=ae(!1);function c(u){a.value=(u==null?void 0:u.canScrollNext())||!1,f.value=(u==null?void 0:u.canScrollPrev())||!1}return Mn(()=>{var u,p,g;i.value&&((u=i.value)==null||u.on("init",c),(p=i.value)==null||p.on("reInit",c),(g=i.value)==null||g.on("select",c),r("init-api",i.value))}),{carouselRef:s,carouselApi:i,canScrollPrev:f,canScrollNext:a,scrollPrev:o,scrollNext:l,orientation:t}});function Jf(){const e=Xm();if(!e)throw new Error("useCarousel must be used within a ");return e}const Ym=xn({__name:"Carousel",props:{opts:{},plugins:{},orientation:{default:"horizontal"},class:{}},emits:["init-api"],setup(e,{expose:t,emit:n}){const r=e,s=n,{canScrollNext:i,canScrollPrev:o,carouselApi:l,carouselRef:a,orientation:f,scrollNext:c,scrollPrev:u}=Wm(r,s);t({canScrollNext:i,canScrollPrev:o,carouselApi:l,carouselRef:a,orientation:f,scrollNext:c,scrollPrev:u});function p(g){const m=r.orientation==="vertical"?"ArrowUp":"ArrowLeft",b=r.orientation==="vertical"?"ArrowDown":"ArrowRight";if(g.key===m){g.preventDefault(),u();return}g.key===b&&(g.preventDefault(),c())}return(g,m)=>(xe(),je("div",{class:st(["relative",[r.class]]),role:"region","aria-roledescription":"carousel",tabindex:"0",onKeydown:p},[no(g.$slots,"default",{canScrollNext:Me(i),canScrollPrev:Me(o),carouselApi:Me(l),carouselRef:Me(a),orientation:Me(f),scrollNext:Me(c),scrollPrev:Me(u)})],34))}}),Gm=xn({inheritAttrs:!1,__name:"CarouselContent",props:{class:{}},setup(e){const t=e,{carouselRef:n,orientation:r}=Jf();return(s,i)=>(xe(),je("div",{ref_key:"carouselRef",ref:n,class:"h-full"},[ve("div",xc({class:["flex",[Me(r)!=="horizontal"?"-mt-4 flex-col":"",t.class]]},s.$attrs),[no(s.$slots,"default")],16)],512))}}),Zm=xn({__name:"CarouselItem",props:{class:{}},setup(e){const t=e,{orientation:n}=Jf();return(r,s)=>(xe(),je("div",{role:"group","aria-roledescription":"slide",class:st(["min-w-0 shrink-0 grow-0 basis-full",[Me(n)==="horizontal"?"pl-0":"pt-4",t.class]])},[no(r.$slots,"default")],2))}}),fa=16e3,Jm="/assets/play-worklet-CqUYQx_r.js",Qm="/assets/vad-processor-0sEQXaXZ.js",eb="/assets/worker-yoCrhISy.ts",tb={class:"relative h-100dvh w-100dvw flex items-center justify-center"},nb={"h-full":"",flex:"","items-center":"","justify-center":""},rb={key:0},sb={key:1,"h-full":"",flex:"","flex-col":"","items-center":"","justify-between":"","p-4":""},ib={class:"mb-4 flex items-center gap-2"},ob={text:"cyan-400 dark:cyan-500","text-lg":""},lb={class:"relative aspect-square h-32 w-32 flex flex-shrink-0 items-center justify-center"},ab={key:0,class:"h-full w-full flex items-center justify-center gap-4 overflow-hidden rounded-lg"},cb={"text-2xl":""},fb={key:1,class:"h-full w-full flex items-center justify-center gap-4 overflow-hidden rounded-lg"},ub={"text-lg":""},db=xn({__name:"App",setup(e){const t=ae(!1),n=ae(null),r=ae(!1),s=ae(!1),i=ae("af_heart"),o=ae({}),l=ae(!1),a=ae(!1),f=ae(1),c=ae(1),u=ae([]),p=ae(!1),g=ae(null),m=ae("00:00"),b=ae(null),M=ae(null),y=ae(null);Se(r,()=>{var E;r.value||(E=b.value)==null||E.postMessage({type:"end_call"})}),Se(r,()=>{if(r.value&&n.value){const E=setInterval(()=>{const O=Math.floor((Date.now()-n.value)/1e3),A=String(Math.floor(O/60)).padStart(2,"0"),P=String(O%60).padStart(2,"0");m.value=`${A}:${P}`},1e3);return()=>clearInterval(E)}else m.value="00:00"}),Mn(()=>{t.value=!0;try{b.value??(b.value=new Worker(eb,{type:"module"}));const E=A=>{g.value=A instanceof Error?A.message:String(A)},O=({data:A})=>{switch(A.type){case"error":return t.value=!1,E(A.data.error);case"status":A.data.status==="recording_start"?(l.value=!0,a.value=!1):A.data.status==="recording_end"?l.value=!1:A.data.status==="ready"&&(o.value=A.data.voices||{},p.value=!0,t.value=!1);break;case"output":!s.value&&y.value&&A.data.result&&(y.value.port.postMessage(A.data.result.audio),s.value=!0,a.value=!0,l.value=!1);break;case"set_voice_response":_();break}};return b.value.addEventListener("message",O),b.value.addEventListener("error",A=>E(A.error)),()=>{var A,P;(A=b.value)==null||A.removeEventListener("message",O),(P=b.value)==null||P.removeEventListener("error",z=>E(z.error))}}catch(E){console.error("Failed to initialize worker:",E),t.value=!1}}),Se(r,()=>{if(!r.value)return;let E,O,A,P=!1,z;const H=Promise.resolve(M.value);return H.then(async k=>{if(P||!k)return;O=new AudioContext({sampleRate:fa});const U=O.createAnalyser();U.fftSize=256,A=O.createMediaStreamSource(k),A.connect(U);const Z=new Uint8Array(U.frequencyBinCount);function se(ne){let me=0;for(let fe=0;fe{var ce;const{buffer:me}=ne.data;(ce=b.value)==null||ce.postMessage({type:"audio",buffer:me})},z=new AudioContext({sampleRate:24e3}),z.resume(),await z.audioWorklet.addModule(new URL(Jm,import.meta.url)),y.value=new AudioWorkletNode(z,"buffered-audio-worklet-processor"),y.value.port.onmessage=ne=>{var me;ne.data.type==="playback_ended"&&(s.value=!1,a.value=!1,(me=b.value)==null||me.postMessage({type:"playback_ended"}))};const ee=z.createAnalyser();ee.fftSize=256,y.value.connect(ee),ee.connect(z.destination);const J=new Uint8Array(ee.frequencyBinCount);function Y(){U.getByteTimeDomainData(Z);const ne=se(Z),me=1+Math.min(1.25*ne,.25);f.value+=(me-f.value)*.25,ee.getByteTimeDomainData(J);const ce=se(J),fe=1+Math.min(1.25*ce,.25);c.value+=(fe-c.value)*.25,requestAnimationFrame(Y)}Y()}).catch(k=>{g.value=k.message,console.error(k)}),()=>{P=!0,H.then(k=>k==null?void 0:k.getTracks().forEach(U=>U.stop())),A==null||A.disconnect(),E==null||E.disconnect(),O==null||O.close(),z==null||z.close()}}),Se(r,()=>{if(!r.value)return;const E=setInterval(()=>{const O=Date.now();u.value=[...u.value,O],setTimeout(()=>{u.value=u.value.filter(A=>A!==O)},1500)},1e3);return()=>clearInterval(E)});async function x(E){var O;i.value=E,(O=b.value)==null||O.postMessage({type:"set_voice",voice:E})}async function _(){var E;try{const O=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,autoGainControl:!0,noiseSuppression:!0,sampleRate:fa}});M.value=O,n.value=Date.now(),r.value=!0,(E=b.value)==null||E.postMessage({type:"start_call"})}catch(O){O instanceof Error&&(g.value=O.message,console.error(O))}}function S(){r.value=!1,n.value=null,s.value=!1,l.value=!1,a.value=!1}function N(E,O,A){const P=(A==null?void 0:A.baseAmplify)??.5,z=(A==null?void 0:A.factor)??.5,H=(A==null?void 0:A.inverted)??!1,k=(A==null?void 0:A.reversed)??!1;let U=P+E/O*z;k&&(U=1-U);const Z=H?1-U:U,se=dm({mode:"oklch",l:Z,h:220,c:.1});return um(Mo("rgb")(se))}return(E,O)=>(xe(),je("div",tb,[ve("div",nb,[Re(il,{name:"fade",mode:"out-in"},{default:an(()=>{var A;return[t.value?(xe(),je("div",rb,O[0]||(O[0]=[ve("div",{"i-svg-spinners:3-dots-bounce":"","text-2xl":""},null,-1)]))):r.value?(xe(),je("div",sb,[ve("div",null,[ve("div",ib,[ve("div",ob,on(((A=o.value[i.value])==null?void 0:A.name)||i.value)+" "+on(m.value),1)])]),ve("div",lb,[r.value?(xe(!0),je(_e,{key:0},qo(u.value,P=>(xe(),je("div",{key:P,class:"pointer-events-none absolute inset-0 border-2 border-cyan-200 rounded-full dark:border-cyan-500",style:{animation:"ripple 1.5s ease-out forwards"}}))),128)):Mr("",!0),ve("div",{class:st(["absolute h-32 w-32 rounded-full",[g.value?"bg-red-200 dark:bg-red-400":"bg-cyan-200 dark:bg-cyan-800",p.value?"":"animate-ping opacity-75"]]),style:{"animation-duration":"1.5s"}},null,2),ve("div",{class:st(["absolute h-32 w-32 rounded-full shadow-inner transition-transform duration-300 ease-out",[g.value?"bg-red-300 dark:bg-red-400":"bg-cyan-300 dark:bg-cyan-800",p.value?"":"opacity-0"]]),style:un({transform:`scale(${c.value})`})},null,6),ve("div",{class:st(["absolute h-32 w-32 rounded-full shadow-inner transition-transform duration-300 ease-out",[g.value?"bg-red-200 dark:bg-red-400":"bg-cyan-200 dark:bg-cyan-600",p.value?"":"opacity-0"]]),style:un({transform:`scale(${f.value})`})},null,6),ve("div",{class:st(["absolute z-10 text-center text-sm",[g.value?"text-red-700":"text-gray-700 dark:text-white"]])},[g.value?(xe(),je(_e,{key:0},[In(on(g.value),1)],64)):(xe(),je(_e,{key:1},[p.value?Mr("",!0):(xe(),je(_e,{key:0},[In(" Loading... ")],64)),l.value?(xe(),je(_e,{key:1},[In(" Listening... ")],64)):Mr("",!0),a.value?(xe(),je(_e,{key:2},[In(" Speaking... ")],64)):Mr("",!0)],64))],2)]),ve("div",{bg:"cyan-50 dark:cyan-950","w-fit":"",flex:"","rounded-xl":"","px-1":"","py-1":"","text-sm":"","outline-none":""},[ve("button",{bg:"hover:cyan-100 dark:hover:cyan-900",text:"red-400 hover:red-300 active:red-400",flex:"","items-center":"","gap-2":"","rounded-lg":"","px-4":"","py-2":"","outline-none":"",transition:"all duration-300 ease-in-out",onClick:S},O[1]||(O[1]=[ve("div",{"i-solar:end-call-rounded-bold":""},null,-1),ve("div",{text:"black dark:white"}," End Call ",-1)]))])])):(xe(),Yn(Me(Ym),{key:2,class:st(["embla outline-none",[r.value?"embla-edge-disabled px-16 h-80 w-140":"px-8 h-50 w-120"]]),transition:"all duration-500 ease-in-out"},{default:an(()=>[Re(Me(Gm),{class:"h-full flex gap-4",style:{touchAction:"pan-y pinch-zoom"}},{default:an(()=>[(xe(!0),je(_e,null,qo(Object.entries(o.value),([P,z],H)=>(xe(),Yn(Me(Zm),{key:H,style:un({backgroundColor:N(H,Object.values(o.value).length,{baseAmplify:.85,factor:.4}),color:N(H,Object.values(o.value).length,{baseAmplify:.5,factor:.2})}),class:st(["h-full w-full flex-[0_0_80%] cursor-pointer rounded-lg",[r.value&&i.value!==P?"opacity-0":""]]),transition:"all duration-500 ease-in-out",onClick:()=>x(P)},{default:an(()=>[Re(il,{name:"fade",mode:"out-in"},{default:an(()=>[r.value?(xe(),je("div",fb,[O[3]||(O[3]=ve("div",{"i-svg-spinners:3-dots-bounce":"","text-2xl":""},null,-1)),ve("div",ub," Connecting to "+on(z.name)+"... ",1)])):(xe(),je("div",ab,[O[2]||(O[2]=ve("div",{"i-solar:phone-bold":"","text-2xl":""},null,-1)),ve("div",cb,on(z.name),1)]))]),_:2},1024)]),_:2},1032,["style","class","onClick"]))),128))]),_:1})]),_:1},8,["class"]))]}),_:1})])]))}}),pb=(e,t)=>{const n=e.__vccOpts||e;for(const[r,s]of t)n[r]=s;return n},hb=pb(db,[["__scopeId","data-v-314fbe9f"]]);O0(hb).use(M1).mount("#app");
diff --git a/assets/index-cAxkOY9l.css b/assets/index-cAxkOY9l.css
new file mode 100644
index 0000000000000000000000000000000000000000..3b6a54801ec148f43448d1ee9d60e8b903be056a
--- /dev/null
+++ b/assets/index-cAxkOY9l.css
@@ -0,0 +1 @@
+@keyframes ripple-314fbe9f{0%{transform:scale(1);opacity:.7}to{transform:scale(2);opacity:0}}.embla[data-v-314fbe9f]{position:relative;overflow:hidden}.embla[data-v-314fbe9f]:before,.embla[data-v-314fbe9f]:after{content:"";position:absolute;top:0;bottom:0;width:48px;z-index:1;pointer-events:none}.embla-edge-disabled.embla[data-v-314fbe9f]:before,.embla-edge-disabled.embla[data-v-314fbe9f]:after{display:none}.embla[data-v-314fbe9f]:before{left:-24px;background:linear-gradient(to right,#ffffff 32px,transparent)}.embla[data-v-314fbe9f]:after{right:-24px;background:linear-gradient(to left,#ffffff 32px,transparent)}.dark .embla[data-v-314fbe9f]:before{left:-24px;background:linear-gradient(to right,#121212 32px,transparent)}.dark .embla[data-v-314fbe9f]:after{right:-24px;background:linear-gradient(to left,#121212 32px,transparent)}.fade-enter-active[data-v-314fbe9f],.fade-leave-active[data-v-314fbe9f]{transition:opacity .5s ease}.fade-enter-from[data-v-314fbe9f],.fade-leave-to[data-v-314fbe9f]{opacity:0}.fade-enter-to[data-v-314fbe9f],.fade-leave-from[data-v-314fbe9f]{opacity:1}.fade-scale-enter-active[data-v-314fbe9f],.fade-scale-leave-active[data-v-314fbe9f]{transition:all .2s ease-in-out}.fade-scale-enter-from[data-v-314fbe9f],.fade-scale-leave-to[data-v-314fbe9f]{opacity:0;transform:scale(.8)}.fade-scale-enter-to[data-v-314fbe9f],.fade-scale-leave-from[data-v-314fbe9f]{opacity:1;transform:scale(1)}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:var(--un-default-border-color, #e5e7eb)}:before,:after{--un-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--bg-color-light: rgb(255 255 255);--bg-color-dark: rgb(18 18 18);--bg-color: var(--bg-color-light)}html,body,#app{height:100%;margin:0;padding:0;overscroll-behavior:none}html{background:var(--bg-color);transition:all .3s ease-in-out}html.dark{--bg-color: var(--bg-color-dark);color-scheme:dark}*,:before,:after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }@font-face{font-family:DM Mono;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/dmmono/v15/aFTU7PB1QTsUX8KYthSQBK6PYK3EXw.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:DM Mono;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/dmmono/v15/aFTU7PB1QTsUX8KYthqQBK6PYK0.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:DM Sans;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/dmsans/v16/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwAopxRR232RmYJp8I5zzw.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:DM Sans;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/dmsans/v16/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwAopxRSW32RmYJp8I5.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"DM Serif Display";font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/dmserifdisplay/v16/-nFnOHM81r4j6k0gjAW3mujVU2B2G_5x0vrx52jJ3Q.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"DM Serif Display";font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/dmserifdisplay/v16/-nFnOHM81r4j6k0gjAW3mujVU2B2G_Bx0vrx52g.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}.i-solar\:end-call-rounded-bold,[i-solar\:end-call-rounded-bold=""]{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 24 24' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m5.607 16.897l1.34-.38C8.156 16.174 9 14.983 9 13.618c0 0 0-1.654 3-1.654s3 1.654 3 1.654c0 1.365.844 2.556 2.053 2.9l1.34.38C20.218 17.414 22 15.91 22 13.85c0-1.237-.277-2.477-1.083-3.347C19.56 9.04 16.807 7 12 7s-7.56 2.039-8.917 3.503C2.277 11.373 2 12.613 2 13.85c0 2.06 1.782 3.565 3.607 3.047'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.2em;height:1.2em}.i-solar\:phone-bold,[i-solar\:phone-bold=""]{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 24 24' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m16.556 12.906l-.455.453s-1.083 1.076-4.038-1.862s-1.872-4.014-1.872-4.014l.286-.286c.707-.702.774-1.83.157-2.654L9.374 2.86C8.61 1.84 7.135 1.705 6.26 2.575l-1.57 1.56c-.433.432-.723.99-.688 1.61c.09 1.587.808 5 4.812 8.982c4.247 4.222 8.232 4.39 9.861 4.238c.516-.048.964-.31 1.325-.67l1.42-1.412c.96-.953.69-2.588-.538-3.255l-1.91-1.039c-.806-.437-1.787-.309-2.417.317'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.2em;height:1.2em}.i-svg-spinners\:3-dots-bounce,[i-svg-spinners\:3-dots-bounce=""]{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 24 24' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Ccircle cx='4' cy='12' r='3' fill='currentColor'%3E%3Canimate id='svgSpinners3DotsBounce0' attributeName='cy' begin='0;svgSpinners3DotsBounce1.end+0.25s' calcMode='spline' dur='0.6s' keySplines='.33,.66,.66,1;.33,0,.66,.33' values='12;6;12'/%3E%3C/circle%3E%3Ccircle cx='12' cy='12' r='3' fill='currentColor'%3E%3Canimate attributeName='cy' begin='svgSpinners3DotsBounce0.begin+0.1s' calcMode='spline' dur='0.6s' keySplines='.33,.66,.66,1;.33,0,.66,.33' values='12;6;12'/%3E%3C/circle%3E%3Ccircle cx='20' cy='12' r='3' fill='currentColor'%3E%3Canimate id='svgSpinners3DotsBounce1' attributeName='cy' begin='svgSpinners3DotsBounce0.begin+0.2s' calcMode='spline' dur='0.6s' keySplines='.33,.66,.66,1;.33,0,.66,.33' values='12;6;12'/%3E%3C/circle%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.2em;height:1.2em}.prose :where(h1,h2,h3,h4,h5,h6):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-headings);font-weight:600;line-height:1.25}.prose :where(a):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-links);text-decoration:underline;font-weight:500}.prose :where(a code):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-links)}.prose :where(p,ul,ol,pre):not(:where(.not-prose,.not-prose *)){margin:1em 0;line-height:1.75}.prose :where(blockquote):not(:where(.not-prose,.not-prose *)){margin:1em 0;padding-left:1em;font-style:italic;border-left:.25em solid var(--un-prose-borders)}.prose :where(h1):not(:where(.not-prose,.not-prose *)){margin:1rem 0;font-size:2.25em}.prose :where(h2):not(:where(.not-prose,.not-prose *)){margin:1.75em 0 .5em;font-size:1.75em}.prose :where(h3):not(:where(.not-prose,.not-prose *)){margin:1.5em 0 .5em;font-size:1.375em}.prose :where(h4):not(:where(.not-prose,.not-prose *)){margin:1em 0;font-size:1.125em}.prose :where(img,video):not(:where(.not-prose,.not-prose *)){max-width:100%}.prose :where(figure,picture):not(:where(.not-prose,.not-prose *)){margin:1em 0}.prose :where(figcaption):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-captions);font-size:.875em}.prose :where(code):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-code);font-size:.875em;font-weight:600;font-family:DM Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.prose :where(:not(pre)>code):not(:where(.not-prose,.not-prose *)):before,.prose :where(:not(pre)>code):not(:where(.not-prose,.not-prose *)):after{content:"`"}.prose :where(pre):not(:where(.not-prose,.not-prose *)){padding:1.25rem 1.5rem;overflow-x:auto;border-radius:.375rem}.prose :where(pre,code):not(:where(.not-prose,.not-prose *)){white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;hyphens:none;background:transparent}.prose :where(pre code):not(:where(.not-prose,.not-prose *)){font-weight:inherit}.prose :where(ol,ul):not(:where(.not-prose,.not-prose *)){padding-left:1.25em}.prose :where(ol):not(:where(.not-prose,.not-prose *)){list-style-type:decimal}.prose :where(ol[type=A]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where(.not-prose,.not-prose *)){list-style-type:decimal}.prose :where(ul):not(:where(.not-prose,.not-prose *)){list-style-type:disc}.prose :where(ol>li):not(:where(.not-prose,.not-prose *))::marker,.prose :where(ul>li):not(:where(.not-prose,.not-prose *))::marker,.prose :where(summary):not(:where(.not-prose,.not-prose *))::marker{color:var(--un-prose-lists)}.prose :where(hr):not(:where(.not-prose,.not-prose *)){margin:2em 0;border:1px solid var(--un-prose-hr)}.prose :where(table):not(:where(.not-prose,.not-prose *)){display:block;margin:1em 0;border-collapse:collapse;overflow-x:auto}.prose :where(tr):not(:where(.not-prose,.not-prose *)):nth-child(2n){background:var(--un-prose-bg-soft)}.prose :where(td,th):not(:where(.not-prose,.not-prose *)){border:1px solid var(--un-prose-borders);padding:.625em 1em}.prose :where(abbr):not(:where(.not-prose,.not-prose *)){cursor:help}.prose :where(kbd):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-code);border:1px solid;padding:.25rem .5rem;font-size:.875em;border-radius:.25rem}.prose :where(details):not(:where(.not-prose,.not-prose *)){margin:1em 0;padding:1.25rem 1.5rem;background:var(--un-prose-bg-soft)}.prose :where(summary):not(:where(.not-prose,.not-prose *)){cursor:pointer;font-weight:600}.prose{color:var(--un-prose-body);max-width:65ch}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.z-10{z-index:10}.m-auto{margin:auto}.-mt-4{margin-top:-1rem}.mb-4{margin-bottom:1rem}.hidden{display:none}.aspect-square{aspect-ratio:1/1}.h-100dvh{height:100dvh}.h-32{height:8rem}.h-50{height:12.5rem}.h-80{height:20rem}.h-full,[h-full=""]{height:100%}.min-w-0{min-width:0}.w-100dvw{width:100dvw}.w-120{width:30rem}.w-140{width:35rem}.w-32{width:8rem}.w-fit,[w-fit=""]{width:fit-content}.w-full{width:100%}.flex,[flex=""]{display:flex}.flex-\[0_0_80\%\]{flex:0 0 80%}.flex-shrink-0,.shrink-0{flex-shrink:0}.grow-0{flex-grow:0}.basis-full{flex-basis:100%}.flex-col,[flex-col=""]{flex-direction:column}.transform{transform:translate(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotate(var(--un-rotate-z)) skew(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}@keyframes ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}.animate-ping{animation:ping 1s cubic-bezier(0,0,.2,1) infinite}.cursor-pointer{cursor:pointer}.items-center,[items-center=""]{align-items:center}.justify-center,[justify-center=""]{justify-content:center}.justify-between,[justify-between=""]{justify-content:space-between}.gap-2,[gap-2=""]{gap:.5rem}.gap-4{gap:1rem}.overflow-hidden{overflow:hidden}.border-2{border-width:2px}.border-cyan-200{--un-border-opacity:1;border-color:rgb(165 243 252 / var(--un-border-opacity))}.dark .dark\:border-cyan-500{--un-border-opacity:1;border-color:rgb(6 182 212 / var(--un-border-opacity))}.rounded-full{border-radius:9999px}.rounded-lg,[rounded-lg=""]{border-radius:.5rem}.rounded-xl,[rounded-xl=""]{border-radius:.75rem}.bg-cyan-200{--un-bg-opacity:1;background-color:rgb(165 243 252 / var(--un-bg-opacity))}.bg-cyan-300{--un-bg-opacity:1;background-color:rgb(103 232 249 / var(--un-bg-opacity))}.bg-red-200{--un-bg-opacity:1;background-color:rgb(254 202 202 / var(--un-bg-opacity))}.bg-red-300{--un-bg-opacity:1;background-color:rgb(252 165 165 / var(--un-bg-opacity))}.dark .dark\:bg-cyan-600{--un-bg-opacity:1;background-color:rgb(8 145 178 / var(--un-bg-opacity))}.dark .dark\:bg-cyan-800{--un-bg-opacity:1;background-color:rgb(21 94 117 / var(--un-bg-opacity))}.dark .dark\:bg-red-400{--un-bg-opacity:1;background-color:rgb(248 113 113 / var(--un-bg-opacity))}.dark [bg~="dark:cyan-950"]{--un-bg-opacity:1;background-color:rgb(8 51 68 / var(--un-bg-opacity))}[bg~=cyan-50]{--un-bg-opacity:1;background-color:rgb(236 254 255 / var(--un-bg-opacity))}.dark [bg~="dark:hover:cyan-900"]:hover{--un-bg-opacity:1;background-color:rgb(22 78 99 / var(--un-bg-opacity))}[bg~="hover:cyan-100"]:hover{--un-bg-opacity:1;background-color:rgb(207 250 254 / var(--un-bg-opacity))}.p-4,[p-4=""]{padding:1rem}.px-1,[px-1=""]{padding-left:.25rem;padding-right:.25rem}.px-16{padding-left:4rem;padding-right:4rem}.px-4,[px-4=""]{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1,[py-1=""]{padding-top:.25rem;padding-bottom:.25rem}.py-2,[py-2=""]{padding-top:.5rem;padding-bottom:.5rem}.pl-0{padding-left:0}.pt-4{padding-top:1rem}.text-center{text-align:center}.text-left{text-align:left}.text-2xl,[text-2xl=""]{font-size:1.5rem;line-height:2rem}.text-lg,[text-lg=""]{font-size:1.125rem;line-height:1.75rem}.text-sm,[text-sm=""]{font-size:.875rem;line-height:1.25rem}.dark .dark\:text-white,.dark [text~="dark:white"]{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.dark [text~="dark:cyan-500"]{--un-text-opacity:1;color:rgb(6 182 212 / var(--un-text-opacity))}.text-gray-700{--un-text-opacity:1;color:rgb(55 65 81 / var(--un-text-opacity))}.text-red-700{--un-text-opacity:1;color:rgb(185 28 28 / var(--un-text-opacity))}[text~=black]{--un-text-opacity:1;color:rgb(0 0 0 / var(--un-text-opacity))}[text~=cyan-400]{--un-text-opacity:1;color:rgb(34 211 238 / var(--un-text-opacity))}[text~=red-400]{--un-text-opacity:1;color:rgb(248 113 113 / var(--un-text-opacity))}[text~="hover:red-300"]:hover{--un-text-opacity:1;color:rgb(252 165 165 / var(--un-text-opacity))}[text~="active:red-400"]:active{--un-text-opacity:1;color:rgb(248 113 113 / var(--un-text-opacity))}.font-sans{font-family:DM Sans,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.opacity-0{opacity:0}.opacity-75{opacity:.75}.shadow-inner{--un-shadow:inset 0 2px 4px 0 var(--un-shadow-color, rgb(0 0 0 / .05));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.outline-none,[outline-none=""]{outline:2px solid transparent;outline-offset:2px}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}[transition~=all]{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300,[transition~=duration-300]{transition-duration:.3s}.duration-500,[transition~=duration-500]{transition-duration:.5s}.ease,.ease-in-out,[transition~=ease-in-out]{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}
diff --git a/assets/play-worklet-CqUYQx_r.js b/assets/play-worklet-CqUYQx_r.js
new file mode 100644
index 0000000000000000000000000000000000000000..a067fe973bef50407505c8cd9657cbd6152e07d1
--- /dev/null
+++ b/assets/play-worklet-CqUYQx_r.js
@@ -0,0 +1 @@
+var c=Object.defineProperty;var l=(s,e,t)=>e in s?c(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t;var n=(s,e,t)=>l(s,typeof e!="symbol"?e+"":e,t);class d extends AudioWorkletProcessor{constructor(){super();n(this,"bufferQueue",[]);n(this,"currentChunkOffset",0);n(this,"hadData",!1);this.bufferQueue=[],this.currentChunkOffset=0,this.hadData=!1,this.port.onmessage=t=>{const r=t.data;r instanceof Float32Array?(this.hadData=!0,this.bufferQueue.push(r)):r==="stop"&&(this.bufferQueue=[],this.currentChunkOffset=0)}}process(t,r){const f=r[0][0];if(!f)return!0;const h=f.length;let u=0;for(this.hadData&&this.bufferQueue.length===0&&(this.port.postMessage({type:"playback_ended"}),this.hadData=!1);u0){const a=this.bufferQueue[0],o=a.length-this.currentChunkOffset,i=Math.min(o,h-u);f.set(a.subarray(this.currentChunkOffset,this.currentChunkOffset+i),u),this.currentChunkOffset+=i,u+=i,this.currentChunkOffset>=a.length&&(this.bufferQueue.shift(),this.currentChunkOffset=0)}else f.fill(0,u),u=h;return!0}}registerProcessor("buffered-audio-worklet-processor",d);
diff --git a/assets/vad-processor-0sEQXaXZ.js b/assets/vad-processor-0sEQXaXZ.js
new file mode 100644
index 0000000000000000000000000000000000000000..67392775fb9dd8584f91e8c2d1444d7f7b249e6b
--- /dev/null
+++ b/assets/vad-processor-0sEQXaXZ.js
@@ -0,0 +1 @@
+let s=0;const r=new Float32Array(512);class l extends AudioWorkletProcessor{process(o,n,f){const e=o[0][0];if(!e)return!1;if(e.length>512)this.port.postMessage({buffer:e});else{const t=512-s;e.length>=t?(r.set(e.subarray(0,t),s),this.port.postMessage({buffer:r}),r.fill(0),r.set(e.subarray(t),0),s=e.length-t):(r.set(e,s),s+=e.length)}return!0}}registerProcessor("vad-processor",l);
diff --git a/assets/worker-yoCrhISy.ts b/assets/worker-yoCrhISy.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b0fcd319be7203755cf4436a594fc06b5ee3fa14
--- /dev/null
+++ b/assets/worker-yoCrhISy.ts
@@ -0,0 +1,465 @@
+import type {
+ AutomaticSpeechRecognitionPipeline,
+ CausalLMOutputWithPast,
+ GPT2Tokenizer,
+ LlamaForCausalLM,
+ PreTrainedModel,
+ StoppingCriteriaList,
+} from '@huggingface/transformers'
+import type { Device, DType } from '@xsai-transformers/shared/types'
+import type { GenerateOptions } from 'kokoro-js'
+import type {
+ WorkerMessageEventError,
+ WorkerMessageEventInfo,
+ WorkerMessageEventOutput,
+ WorkerMessageEventProgress,
+ WorkerMessageEventSetVoiceResponse,
+ WorkerMessageEventStatus,
+} from '../types/worker'
+
+import {
+ // VAD
+ AutoModel,
+
+ AutoModelForCausalLM,
+ // LLM
+ AutoTokenizer,
+ InterruptableStoppingCriteria,
+ pipeline,
+
+ // Speech recognition
+ Tensor,
+ TextStreamer,
+} from '@huggingface/transformers'
+import { isWebGPUSupported } from 'gpuu/webgpu'
+import { KokoroTTS, TextSplitterStream } from 'kokoro-js'
+
+import {
+ EXIT_THRESHOLD,
+ INPUT_SAMPLE_RATE,
+ MAX_BUFFER_DURATION,
+ MAX_NUM_PREV_BUFFERS,
+ MIN_SILENCE_DURATION_SAMPLES,
+ MIN_SPEECH_DURATION_SAMPLES,
+ SPEECH_PAD_SAMPLES,
+ SPEECH_THRESHOLD,
+} from '../constants'
+
+interface Message {
+ role: 'system' | 'user' | 'assistant'
+ content: string
+}
+
+type Voices = GenerateOptions['voice']
+export type PretrainedConfig = NonNullable[1]>['config']
+
+const whisperDtypeMap: Record = {
+ webgpu: {
+ encoder_model: 'fp32',
+ decoder_model_merged: 'fp32',
+ },
+ wasm: {
+ encoder_model: 'fp32',
+ decoder_model_merged: 'q8',
+ },
+}
+
+const model_id = 'onnx-community/Kokoro-82M-v1.0-ONNX'
+let voice: Voices | undefined
+let silero_vad: PreTrainedModel
+let transcriber: AutomaticSpeechRecognitionPipeline
+let tts: KokoroTTS
+
+const SYSTEM_MESSAGE: Message = {
+ role: 'system',
+ content:
+ 'You\'re a helpful and conversational voice assistant. Keep your responses short, clear, and casual.',
+}
+let messages: Message[] = [SYSTEM_MESSAGE]
+let past_key_values_cache: any = null
+let stopping_criteria: InterruptableStoppingCriteria | null = null
+
+// Global audio buffer to store incoming audio
+const BUFFER = new Float32Array(MAX_BUFFER_DURATION * INPUT_SAMPLE_RATE)
+let bufferPointer = 0
+
+// Initial state for VAD
+const sr = new Tensor('int64', [INPUT_SAMPLE_RATE], [])
+let state = new Tensor('float32', new Float32Array(2 * 1 * 128), [2, 1, 128])
+
+// Whether we are in the process of adding audio to the buffer
+let isRecording = false
+let isPlaying = false // new flag
+
+let tokenizer: GPT2Tokenizer
+let llm: LlamaForCausalLM
+
+const prevBuffers: Float32Array[] = []
+
+export async function loadModels() {
+ tts = await KokoroTTS.from_pretrained(model_id, {
+ dtype: 'fp32',
+ device: 'webgpu',
+ })
+
+ const device = 'webgpu'
+ globalThis.postMessage({ type: 'info', data: { message: `Using device: "${device}"` } } satisfies WorkerMessageEventInfo)
+ globalThis.postMessage({ type: 'info', data: { message: 'Loading models...', duration: 'until_next' } } satisfies WorkerMessageEventInfo)
+
+ // Load models
+ silero_vad = await AutoModel.from_pretrained(
+ 'onnx-community/silero-vad',
+ {
+ config: { model_type: 'custom' } as PretrainedConfig,
+ dtype: 'fp32', // Full-precision
+ progress_callback: progress => globalThis.postMessage({ type: 'progress', data: { message: progress } } satisfies WorkerMessageEventProgress),
+ },
+ ).catch((error: Error) => {
+ globalThis.postMessage({ type: 'error', data: { error, message: error.message } } satisfies WorkerMessageEventError)
+ throw error
+ })
+
+ transcriber = await pipeline(
+ 'automatic-speech-recognition',
+ 'onnx-community/whisper-base', // or "onnx-community/moonshine-base-ONNX",
+ {
+ device,
+ dtype: whisperDtypeMap[device as keyof typeof whisperDtypeMap],
+ progress_callback: progress => globalThis.postMessage({ type: 'progress', data: { message: progress } } satisfies WorkerMessageEventProgress),
+ },
+ ).catch((error: Error) => {
+ globalThis.postMessage({ type: 'error', data: { error, message: error.message } } satisfies WorkerMessageEventError)
+ throw error
+ })
+
+ await transcriber(new Float32Array(INPUT_SAMPLE_RATE)) // Compile shaders
+
+ llm = await AutoModelForCausalLM.from_pretrained(
+ 'HuggingFaceTB/SmolLM2-1.7B-Instruct',
+ {
+ dtype: await isWebGPUSupported() ? 'q4f16' : 'int8',
+ device: await isWebGPUSupported() ? 'webgpu' : 'wasm',
+ progress_callback: progress => globalThis.postMessage({ type: 'progress', data: { message: progress } } satisfies WorkerMessageEventProgress),
+ },
+ ).catch((error: Error) => {
+ globalThis.postMessage({ type: 'error', data: { error, message: error.message } } satisfies WorkerMessageEventError)
+ throw error
+ })
+
+ tokenizer = await AutoTokenizer.from_pretrained(
+ 'HuggingFaceTB/SmolLM2-1.7B-Instruct',
+ ).catch((error: Error) => {
+ globalThis.postMessage({ type: 'error', data: { error, message: error.message } } satisfies WorkerMessageEventError)
+ throw error
+ })
+
+ await llm.generate({ ...tokenizer('x'), max_new_tokens: 1 }) // Compile shaders
+
+ globalThis.postMessage({
+ type: 'status',
+ data: {
+ status: 'ready',
+ message: 'Ready!',
+ voices: tts.voices,
+ },
+ } as WorkerMessageEventStatus)
+}
+
+loadModels()
+
+/**
+ * Perform Voice Activity Detection (VAD)
+ * @param buffer The new audio buffer
+ * @returns `true` if the buffer is speech, `false` otherwise.
+ */
+async function vad(buffer?: Float32Array): Promise {
+ if (!buffer) {
+ // Possibly closed or interrupted
+ return false
+ }
+
+ const input = new Tensor('float32', buffer, [1, buffer.length])
+
+ const { stateN, output } = await silero_vad({ input, sr, state })
+ state = stateN // Update state
+
+ const isSpeech = output.data[0]
+
+ // Use heuristics to determine if the buffer is speech or not
+ return (
+ // Case 1: We are above the threshold (definitely speech)
+ isSpeech > SPEECH_THRESHOLD
+ // Case 2: We are in the process of recording, and the probability is above the negative (exit) threshold
+ || (isRecording && isSpeech >= EXIT_THRESHOLD)
+ )
+}
+
+interface SpeechData {
+ start: number
+ end: number
+ duration: number
+}
+
+type BatchEncodingItem = number[] | number[][] | Tensor
+/**
+ * Holds the output of the tokenizer's call function.
+ */
+interface BatchEncoding {
+ /**
+ * List of token ids to be fed to a model.
+ */
+ input_ids: BatchEncodingItem
+ /**
+ * List of indices specifying which tokens should be attended to by the model.
+ */
+ attention_mask: BatchEncodingItem
+ /**
+ * List of token type ids to be fed to a model.
+ */
+ token_type_ids?: BatchEncodingItem
+}
+
+/**
+ * Transcribe the audio buffer
+ * @param buffer The audio buffer
+ * @param _data Additional data
+ */
+async function speechToSpeech(buffer: Float32Array, _data: SpeechData): Promise {
+ isPlaying = true
+
+ // 1. Transcribe the audio from the user
+ const result = await transcriber(buffer)
+ const text = (result as { text: string }).text.trim()
+
+ if (['', '[BLANK_AUDIO]'].includes(text)) {
+ // If the transcription is empty or a blank audio, we skip the rest of the processing
+ return
+ }
+
+ messages.push({ role: 'user', content: text })
+
+ // Set up text-to-speech streaming
+ const splitter = new TextSplitterStream()
+ const stream = tts!.stream(splitter, { voice });
+ (async () => {
+ for await (const { text, audio } of stream) {
+ globalThis.postMessage({ type: 'output', data: { text, result: audio } } satisfies WorkerMessageEventOutput)
+ }
+ })()
+
+ // 2. Generate a response using the LLM
+ const inputs = tokenizer.apply_chat_template(messages, {
+ add_generation_prompt: true,
+ return_dict: true,
+ }) as BatchEncoding
+
+ const streamer = new TextStreamer(tokenizer, {
+ skip_prompt: true,
+ skip_special_tokens: true,
+ callback_function: (text: string) => {
+ splitter.push(text)
+ },
+ token_callback_function: () => {},
+ })
+
+ stopping_criteria = new InterruptableStoppingCriteria()
+ type GenerationFunctionParameters = Parameters[0] & Record
+
+ const generatedRes = await llm.generate({
+ ...inputs,
+ past_key_values: past_key_values_cache,
+ do_sample: false, // TODO: do_sample: true is bugged (invalid data location on top-k sample)
+ max_new_tokens: 1024,
+ streamer,
+ stopping_criteria: stopping_criteria as unknown as StoppingCriteriaList,
+ return_dict_in_generate: true,
+ } as GenerationFunctionParameters)
+
+ const { past_key_values, sequences } = generatedRes as CausalLMOutputWithPast & { sequences: Tensor }
+ past_key_values_cache = past_key_values
+
+ // Finally, close the stream to signal that no more text will be added.
+ splitter.close()
+
+ const decoded = tokenizer.batch_decode(
+ // TODO: fix null as any
+ sequences.slice(null, [(inputs.input_ids as Tensor).dims[1], null as any]),
+ { skip_special_tokens: true },
+ )
+
+ messages.push({ role: 'assistant', content: decoded[0] })
+}
+
+// Track the number of samples after the last speech chunk
+let postSpeechSamples = 0
+function resetAfterRecording(offset = 0): void {
+ globalThis.postMessage({
+ type: 'status',
+ data: {
+ status: 'recording_end',
+ message: 'Transcribing...',
+ duration: 'until_next',
+ },
+ } satisfies WorkerMessageEventStatus)
+
+ BUFFER.fill(0, offset)
+ bufferPointer = offset
+ isRecording = false
+ postSpeechSamples = 0
+}
+
+function dispatchForTranscriptionAndResetAudioBuffer(overflow?: Float32Array): void {
+ // Get start and end time of the speech segment, minus the padding
+ const now = Date.now()
+ const end
+ = now - ((postSpeechSamples + SPEECH_PAD_SAMPLES) / INPUT_SAMPLE_RATE) * 1000
+ const start = end - (bufferPointer / INPUT_SAMPLE_RATE) * 1000
+ const duration = end - start
+ const overflowLength = overflow?.length ?? 0
+
+ // Send the audio buffer to the worker
+ const buffer = BUFFER.slice(0, bufferPointer + SPEECH_PAD_SAMPLES)
+
+ const prevLength = prevBuffers.reduce((acc, b) => acc + b.length, 0)
+ const paddedBuffer = new Float32Array(prevLength + buffer.length)
+ let offset = 0
+ for (const prev of prevBuffers) {
+ paddedBuffer.set(prev, offset)
+ offset += prev.length
+ }
+ paddedBuffer.set(buffer, offset)
+ speechToSpeech(paddedBuffer, { start, end, duration })
+
+ // Set overflow (if present) and reset the rest of the audio buffer
+ if (overflow) {
+ BUFFER.set(overflow, 0)
+ }
+ resetAfterRecording(overflowLength)
+}
+
+globalThis.onmessage = async (event: MessageEvent) => {
+ const { type, buffer } = event.data
+
+ // refuse new audio while playing back
+ if (type === 'audio' && isPlaying)
+ return
+
+ switch (type) {
+ case 'start_call': {
+ const name = tts!.voices[voice ?? 'af_heart']?.name ?? 'Heart'
+ greet(`Hey there, my name is ${name}! How can I help you today?`)
+ return
+ }
+ case 'end_call':
+ messages = [SYSTEM_MESSAGE]
+ past_key_values_cache = null
+ break
+ case 'interrupt':
+ stopping_criteria?.interrupt()
+ return
+ case 'set_voice':
+ voice = event.data.voice
+
+ globalThis.postMessage({
+ type: 'set_voice_response',
+ data: {
+ ok: true,
+ },
+ } satisfies WorkerMessageEventSetVoiceResponse)
+
+ return
+ case 'playback_ended':
+ isPlaying = false
+ return
+ }
+
+ const wasRecording = isRecording // Save current state
+ const isSpeech = await vad(buffer)
+
+ if (!wasRecording && !isSpeech) {
+ // We are not recording, and the buffer is not speech,
+ // so we will probably discard the buffer. So, we insert
+ // into a FIFO queue with maximum size of PREV_BUFFER_SIZE
+ if (prevBuffers.length >= MAX_NUM_PREV_BUFFERS) {
+ // If the queue is full, we discard the oldest buffer
+ prevBuffers.shift()
+ }
+ prevBuffers.push(buffer)
+ return
+ }
+
+ const remaining = BUFFER.length - bufferPointer
+ if (buffer.length >= remaining) {
+ // The buffer is larger than (or equal to) the remaining space in the global buffer,
+ // so we perform transcription and copy the overflow to the global buffer
+ BUFFER.set(buffer.subarray(0, remaining), bufferPointer)
+ bufferPointer += remaining
+
+ // Dispatch the audio buffer
+ const overflow = buffer.subarray(remaining)
+ dispatchForTranscriptionAndResetAudioBuffer(overflow)
+ return
+ }
+ else {
+ // The buffer is smaller than the remaining space in the global buffer,
+ // so we copy it to the global buffer
+ BUFFER.set(buffer, bufferPointer)
+ bufferPointer += buffer.length
+ }
+
+ if (isSpeech) {
+ if (!isRecording) {
+ // Indicate start of recording
+ globalThis.postMessage({
+ type: 'status',
+ data: {
+ status: 'recording_start',
+ message: 'Listening...',
+ duration: 'until_next',
+ },
+ } satisfies WorkerMessageEventStatus)
+ }
+
+ // Start or continue recording
+ isRecording = true
+ postSpeechSamples = 0 // Reset the post-speech samples
+
+ return
+ }
+
+ postSpeechSamples += buffer.length
+
+ // At this point we're confident that we were recording (wasRecording === true), but the latest buffer is not speech.
+ // So, we check whether we have reached the end of the current audio chunk.
+ if (postSpeechSamples < MIN_SILENCE_DURATION_SAMPLES) {
+ // There was a short pause, but not long enough to consider the end of a speech chunk
+ // (e.g., the speaker took a breath), so we continue recording
+ return
+ }
+
+ if (bufferPointer < MIN_SPEECH_DURATION_SAMPLES) {
+ // The entire buffer (including the new chunk) is smaller than the minimum
+ // duration of a speech chunk, so we can safely discard the buffer.
+ resetAfterRecording()
+ return
+ }
+
+ dispatchForTranscriptionAndResetAudioBuffer()
+}
+
+function greet(text: string): void {
+ isPlaying = true
+
+ const splitter = new TextSplitterStream()
+ const stream = tts!.stream(splitter, { voice });
+
+ (async () => {
+ for await (const { text: chunkText, audio } of stream) {
+ globalThis.postMessage({ type: 'output', data: { text: chunkText, result: audio } } satisfies WorkerMessageEventOutput)
+ }
+ })()
+
+ splitter.push(text)
+ splitter.close()
+ messages.push({ role: 'assistant', content: text })
+}
diff --git a/favicon-96x96.png b/favicon-96x96.png
new file mode 100644
index 0000000000000000000000000000000000000000..a70eda1afe66bcdb77cedc2cdae6ac440753e4c5
Binary files /dev/null and b/favicon-96x96.png differ
diff --git a/favicon.svg b/favicon.svg
new file mode 100644
index 0000000000000000000000000000000000000000..17940af4792c2c855fcb5b330db53786f28ddc5a
--- /dev/null
+++ b/favicon.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/index.html b/index.html
index b0c4b3666032a737f3903db53e6a8a9272483e28..dee57150efad2435bcf48cd5da4d12b5b3b6d052 100644
--- a/index.html
+++ b/index.html
@@ -1,19 +1,24 @@
-
-
-
-
- My static Space
-
-
-
-
-
Welcome to your static Space!
-
You can modify this app directly by editing index.html in the Files and versions tab.
-
- Also don't forget to check the
- Spaces documentation.
-
-
-
+
+
+
+ Realtime Conversational WebGPU (Vue)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/style.css b/style.css
deleted file mode 100644
index 114adf441e9032febb46bc056b2a8bb651075f0d..0000000000000000000000000000000000000000
--- a/style.css
+++ /dev/null
@@ -1,28 +0,0 @@
-body {
- padding: 2rem;
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
-}
-
-h1 {
- font-size: 16px;
- margin-top: 0;
-}
-
-p {
- color: rgb(107, 114, 128);
- font-size: 15px;
- margin-bottom: 10px;
- margin-top: 5px;
-}
-
-.card {
- max-width: 620px;
- margin: 0 auto;
- padding: 16px;
- border: 1px solid lightgray;
- border-radius: 16px;
-}
-
-.card p:last-child {
- margin-bottom: 0;
-}