@alexkroman1/aai-ui 6.1.0 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/default-client/assets/{index-4u908fff.js → index-DCI51Xz_.js} +2 -2
- package/dist/default-client/assets/{playback-processor-DUsmALNH.js → playback-processor-DwQ9tE7X.js} +6 -10
- package/dist/default-client/index.html +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +2 -2
- package/dist/worklets/_playback-bench-harness.d.ts +181 -0
- package/dist/worklets/_playback-bench-host.d.ts +63 -0
- package/dist/worklets/_playback-bench-page.d.ts +65 -0
- package/dist/worklets/_tts-trace-harness.d.ts +142 -0
- package/dist/worklets/_worklet-test-utils.d.ts +27 -0
- package/dist/worklets/playback-processor.d.ts +1 -1
- package/dist/worklets/playback-processor.js +7 -11
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./audio-fO7SVU64.js","./client-audio-constants-Ck0IJO4c.js","./capture-processor-Dmc-KEpb.js","./_module-url-BX0RuRU2.js","./playback-processor-
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./audio-fO7SVU64.js","./client-audio-constants-Ck0IJO4c.js","./capture-processor-Dmc-KEpb.js","./_module-url-BX0RuRU2.js","./playback-processor-DwQ9tE7X.js"])))=>i.map(i=>d[i]);
|
|
2
2
|
import{t as e}from"./client-audio-constants-Ck0IJO4c.js";var t=Object.create,n=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.getPrototypeOf,o=Object.prototype.hasOwnProperty,s=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),c=(e,t)=>{let r={};for(var i in e)n(r,i,{get:e[i],enumerable:!0});return t||n(r,Symbol.toStringTag,{value:`Module`}),r},l=(e,t,a,s)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=i(t),l=0,u=c.length,d;l<u;l++)d=c[l],!o.call(e,d)&&d!==a&&n(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(s=r(t,d))||s.enumerable});return e},u=(e,r,i)=>(i=e==null?{}:t(a(e)),l(r||!e||!e.__esModule||!o.call(e,`default`)?n(i,`default`,{value:e,enumerable:!0}):i,e));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var d=s((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var ee=Array.isArray;function S(){}var C={H:null,A:null,T:null,S:null},te=Object.prototype.hasOwnProperty;function w(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ne(e,t){return w(e.type,t,e.props)}function T(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function re(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var ie=/\/+/g;function ae(e,t){return typeof e==`object`&&e&&e.key!=null?re(``+e.key):t.toString(36)}function oe(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(S,S):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function se(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,se(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+ae(e,0):a,ee(o)?(i=``,c!=null&&(i=c.replace(ie,`$&/`)+`/`),se(o,r,i,``,function(e){return e})):o!=null&&(T(o)&&(o=ne(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(ie,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(ee(e))for(var u=0;u<e.length;u++)a=e[u],s=l+ae(a,u),c+=se(a,r,i,s,o);else if(u=m(e),typeof u==`function`)for(e=u.call(e),u=0;!(a=e.next()).done;)a=a.value,s=l+ae(a,u++),c+=se(a,r,i,s,o);else if(s===`object`){if(typeof e.then==`function`)return se(oe(e),r,i,a,o);throw r=String(e),Error(`Objects are not valid as a React child (found: `+(r===`[object Object]`?`object with keys {`+Object.keys(e).join(`, `)+`}`:r)+`). If you meant to render a collection of children, use an array instead.`)}return c}function ce(e,t,n){if(e==null)return e;var r=[],i=0;return se(e,r,``,``,function(e){return t.call(n,e,i++)}),r}function le(e){if(e._status===-1){var t=e._result;t=t(),t.then(function(t){(e._status===0||e._status===-1)&&(e._status=1,e._result=t)},function(t){(e._status===0||e._status===-1)&&(e._status=2,e._result=t)}),e._status===-1&&(e._status=0,e._result=t)}if(e._status===1)return e._result.default;throw e._result}var E=typeof reportError==`function`?reportError:function(e){if(typeof window==`object`&&typeof window.ErrorEvent==`function`){var t=new window.ErrorEvent(`error`,{bubbles:!0,cancelable:!0,message:typeof e==`object`&&e&&typeof e.message==`string`?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process==`object`&&typeof process.emit==`function`){process.emit(`uncaughtException`,e);return}console.error(e)},D={map:ce,forEach:function(e,t,n){ce(e,function(){t.apply(this,arguments)},n)},count:function(e){var t=0;return ce(e,function(){t++}),t},toArray:function(e){return ce(e,function(e){return e})||[]},only:function(e){if(!T(e))throw Error(`React.Children.only expected to receive a single React element child.`);return e}};e.Activity=f,e.Children=D,e.Component=v,e.Fragment=r,e.Profiler=a,e.PureComponent=b,e.StrictMode=i,e.Suspense=l,e.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=C,e.__COMPILER_RUNTIME={__proto__:null,c:function(e){return C.H.useMemoCache(e)}},e.cache=function(e){return function(){return e.apply(null,arguments)}},e.cacheSignal=function(){return null},e.cloneElement=function(e,t,n){if(e==null)throw Error(`The argument must be a React element, but you passed `+e+`.`);var r=g({},e.props),i=e.key;if(t!=null)for(a in t.key!==void 0&&(i=``+t.key),t)!te.call(t,a)||a===`key`||a===`__self`||a===`__source`||a===`ref`&&t.ref===void 0||(r[a]=t[a]);var a=arguments.length-2;if(a===1)r.children=n;else if(1<a){for(var o=Array(a),s=0;s<a;s++)o[s]=arguments[s+2];r.children=o}return w(e.type,i,r)},e.createContext=function(e){return e={$$typeof:s,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null},e.Provider=e,e.Consumer={$$typeof:o,_context:e},e},e.createElement=function(e,t,n){var r,i={},a=null;if(t!=null)for(r in t.key!==void 0&&(a=``+t.key),t)te.call(t,r)&&r!==`key`&&r!==`__self`&&r!==`__source`&&(i[r]=t[r]);var o=arguments.length-2;if(o===1)i.children=n;else if(1<o){for(var s=Array(o),c=0;c<o;c++)s[c]=arguments[c+2];i.children=s}if(e&&e.defaultProps)for(r in o=e.defaultProps,o)i[r]===void 0&&(i[r]=o[r]);return w(e,a,i)},e.createRef=function(){return{current:null}},e.forwardRef=function(e){return{$$typeof:c,render:e}},e.isValidElement=T,e.lazy=function(e){return{$$typeof:d,_payload:{_status:-1,_result:e},_init:le}},e.memo=function(e,t){return{$$typeof:u,type:e,compare:t===void 0?null:t}},e.startTransition=function(e){var t=C.T,n={};C.T=n;try{var r=e(),i=C.S;i!==null&&i(n,r),typeof r==`object`&&r&&typeof r.then==`function`&&r.then(S,E)}catch(e){E(e)}finally{t!==null&&n.types!==null&&(t.types=n.types),C.T=t}},e.unstable_useCacheRefresh=function(){return C.H.useCacheRefresh()},e.use=function(e){return C.H.use(e)},e.useActionState=function(e,t,n){return C.H.useActionState(e,t,n)},e.useCallback=function(e,t){return C.H.useCallback(e,t)},e.useContext=function(e){return C.H.useContext(e)},e.useDebugValue=function(){},e.useDeferredValue=function(e,t){return C.H.useDeferredValue(e,t)},e.useEffect=function(e,t){return C.H.useEffect(e,t)},e.useEffectEvent=function(e){return C.H.useEffectEvent(e)},e.useId=function(){return C.H.useId()},e.useImperativeHandle=function(e,t,n){return C.H.useImperativeHandle(e,t,n)},e.useInsertionEffect=function(e,t){return C.H.useInsertionEffect(e,t)},e.useLayoutEffect=function(e,t){return C.H.useLayoutEffect(e,t)},e.useMemo=function(e,t){return C.H.useMemo(e,t)},e.useOptimistic=function(e,t){return C.H.useOptimistic(e,t)},e.useReducer=function(e,t,n){return C.H.useReducer(e,t,n)},e.useRef=function(e){return C.H.useRef(e)},e.useState=function(e){return C.H.useState(e)},e.useSyncExternalStore=function(e,t,n){return C.H.useSyncExternalStore(e,t,n)},e.useTransition=function(){return C.H.useTransition()},e.version=`19.2.8`})),f=s(((e,t)=>{t.exports=d()})),p=s((e=>{var t=f();function n(e){var t=`https://react.dev/errors/`+e;if(1<arguments.length){t+=`?args[]=`+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+=`&args[]=`+encodeURIComponent(arguments[n])}return`Minified React error #`+e+`; visit `+t+` for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`}function r(){}var i={d:{f:r,r:function(){throw Error(n(522))},D:r,C:r,L:r,m:r,X:r,S:r,M:r},p:0,findDOMNode:null},a=Symbol.for(`react.portal`);function o(e,t,n){var r=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:a,key:r==null?null:``+r,children:e,containerInfo:t,implementation:n}}var s=t.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function c(e,t){if(e===`font`)return``;if(typeof t==`string`)return t===`use-credentials`?t:``}e.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=i,e.createPortal=function(e,t){var r=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!t||t.nodeType!==1&&t.nodeType!==9&&t.nodeType!==11)throw Error(n(299));return o(e,t,null,r)},e.flushSync=function(e){var t=s.T,n=i.p;try{if(s.T=null,i.p=2,e)return e()}finally{s.T=t,i.p=n,i.d.f()}},e.preconnect=function(e,t){typeof e==`string`&&(t?(t=t.crossOrigin,t=typeof t==`string`?t===`use-credentials`?t:``:void 0):t=null,i.d.C(e,t))},e.prefetchDNS=function(e){typeof e==`string`&&i.d.D(e)},e.preinit=function(e,t){if(typeof e==`string`&&t&&typeof t.as==`string`){var n=t.as,r=c(n,t.crossOrigin),a=typeof t.integrity==`string`?t.integrity:void 0,o=typeof t.fetchPriority==`string`?t.fetchPriority:void 0;n===`style`?i.d.S(e,typeof t.precedence==`string`?t.precedence:void 0,{crossOrigin:r,integrity:a,fetchPriority:o}):n===`script`&&i.d.X(e,{crossOrigin:r,integrity:a,fetchPriority:o,nonce:typeof t.nonce==`string`?t.nonce:void 0})}},e.preinitModule=function(e,t){if(typeof e==`string`){if(typeof t==`object`&&t){if(t.as==null||t.as===`script`){var n=c(t.as,t.crossOrigin);i.d.M(e,{crossOrigin:n,integrity:typeof t.integrity==`string`?t.integrity:void 0,nonce:typeof t.nonce==`string`?t.nonce:void 0})}}else t??i.d.M(e)}},e.preload=function(e,t){if(typeof e==`string`&&typeof t==`object`&&t&&typeof t.as==`string`){var n=t.as,r=c(n,t.crossOrigin);i.d.L(e,n,{crossOrigin:r,integrity:typeof t.integrity==`string`?t.integrity:void 0,nonce:typeof t.nonce==`string`?t.nonce:void 0,type:typeof t.type==`string`?t.type:void 0,fetchPriority:typeof t.fetchPriority==`string`?t.fetchPriority:void 0,referrerPolicy:typeof t.referrerPolicy==`string`?t.referrerPolicy:void 0,imageSrcSet:typeof t.imageSrcSet==`string`?t.imageSrcSet:void 0,imageSizes:typeof t.imageSizes==`string`?t.imageSizes:void 0,media:typeof t.media==`string`?t.media:void 0})}},e.preloadModule=function(e,t){if(typeof e==`string`){if(t){var n=c(t.as,t.crossOrigin);i.d.m(e,{as:typeof t.as==`string`&&t.as!==`script`?t.as:void 0,crossOrigin:n,integrity:typeof t.integrity==`string`?t.integrity:void 0})}else i.d.m(e)}},e.requestFormReset=function(e){i.d.r(e)},e.unstable_batchedUpdates=function(e,t){return e(t)},e.useFormState=function(e,t,n){return s.H.useFormState(e,t,n)},e.useFormStatus=function(){return s.H.useHostTransitionStatus()},e.version=`19.2.8`})),m=s(((e,t)=>{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=p()})),h=s((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0<n;){var r=n-1>>>1,a=e[r];if(0<i(a,t))e[r]=t,e[n]=a,n=r;else break a}}function n(e){return e.length===0?null:e[0]}function r(e){if(e.length===0)return null;var t=e[0],n=e.pop();if(n!==t){e[0]=n;a:for(var r=0,a=e.length,o=a>>>1;r<o;){var s=2*(r+1)-1,c=e[s],l=s+1,u=e[l];if(0>i(c,n))l<a&&0>i(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(l<a&&0>i(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m){if(n(c)!==null)m=!0,ee||(ee=!0,T());else{var t=n(l);t!==null&&ae(x,t.startTime-e)}}}var ee=!1,S=-1,C=5,te=-1;function w(){return g?!0:!(e.unstable_now()-te<C)}function ne(){if(g=!1,ee){var t=e.unstable_now();te=t;var i=!0;try{a:{m=!1,h&&(h=!1,v(S),S=-1),p=!0;var a=f;try{b:{for(b(t),d=n(c);d!==null&&!(d.expirationTime>t&&w());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&ae(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}}}finally{i?T():ee=!1}}}var T;if(typeof y==`function`)T=function(){y(ne)};else if(typeof MessageChannel<`u`){var re=new MessageChannel,ie=re.port2;re.port1.onmessage=ne,T=function(){ie.postMessage(null)}}else T=function(){_(ne,0)};function ae(t,n){S=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125<e?console.error(`forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported`):C=0<e?Math.floor(1e3/e):5},e.unstable_getCurrentPriorityLevel=function(){return f},e.unstable_next=function(e){switch(f){case 1:case 2:case 3:var t=3;break;default:t=f}var n=f;f=t;try{return e()}finally{f=n}},e.unstable_requestPaint=function(){g=!0},e.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=f;f=e;try{return t()}finally{f=n}},e.unstable_scheduleCallback=function(r,i,a){var o=e.unstable_now();switch(typeof a==`object`&&a?(a=a.delay,a=typeof a==`number`&&0<a?o+a:o):a=o,r){case 1:var s=-1;break;case 2:s=250;break;case 5:s=1073741823;break;case 4:s=1e4;break;default:s=5e3}return s=a+s,r={id:u++,callback:i,priorityLevel:r,startTime:a,expirationTime:s,sortIndex:-1},a>o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(S),S=-1):h=!0,ae(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,ee||(ee=!0,T()))),r},e.unstable_shouldYield=w,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),g=s(((e,t)=>{t.exports=h()})),_=s((e=>{var t=g(),n=f(),r=m();function i(e){var t=`https://react.dev/errors/`+e;if(1<arguments.length){t+=`?args[]=`+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+=`&args[]=`+encodeURIComponent(arguments[n])}return`Minified React error #`+e+`; visit `+t+` for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`}function a(e){return!(!e||e.nodeType!==1&&e.nodeType!==9&&e.nodeType!==11)}function o(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do t=e,t.flags&4098&&(n=t.return),e=t.return;while(e)}return t.tag===3?n:null}function s(e){if(e.tag===13){var t=e.memoizedState;if(t===null&&(e=e.alternate,e!==null&&(t=e.memoizedState)),t!==null)return t.dehydrated}return null}function c(e){if(e.tag===31){var t=e.memoizedState;if(t===null&&(e=e.alternate,e!==null&&(t=e.memoizedState)),t!==null)return t.dehydrated}return null}function l(e){if(o(e)!==e)throw Error(i(188))}function u(e){var t=e.alternate;if(!t){if(t=o(e),t===null)throw Error(i(188));return t===e?e:null}for(var n=e,r=t;;){var a=n.return;if(a===null)break;var s=a.alternate;if(s===null){if(r=a.return,r!==null){n=r;continue}break}if(a.child===s.child){for(s=a.child;s;){if(s===n)return l(a),e;if(s===r)return l(a),t;s=s.sibling}throw Error(i(188))}if(n.return!==r.return)n=a,r=s;else{for(var c=!1,u=a.child;u;){if(u===n){c=!0,n=a,r=s;break}if(u===r){c=!0,r=a,n=s;break}u=u.sibling}if(!c){for(u=s.child;u;){if(u===n){c=!0,n=s,r=a;break}if(u===r){c=!0,r=s,n=a;break}u=u.sibling}if(!c)throw Error(i(189))}}if(n.alternate!==r)throw Error(i(190))}if(n.tag!==3)throw Error(i(188));return n.stateNode.current===n?e:t}function d(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e;for(e=e.child;e!==null;){if(t=d(e),t!==null)return t;e=e.sibling}return null}var p=Object.assign,h=Symbol.for(`react.element`),_=Symbol.for(`react.transitional.element`),v=Symbol.for(`react.portal`),y=Symbol.for(`react.fragment`),b=Symbol.for(`react.strict_mode`),x=Symbol.for(`react.profiler`),ee=Symbol.for(`react.consumer`),S=Symbol.for(`react.context`),C=Symbol.for(`react.forward_ref`),te=Symbol.for(`react.suspense`),w=Symbol.for(`react.suspense_list`),ne=Symbol.for(`react.memo`),T=Symbol.for(`react.lazy`),re=Symbol.for(`react.activity`),ie=Symbol.for(`react.memo_cache_sentinel`),ae=Symbol.iterator;function oe(e){return typeof e!=`object`||!e?null:(e=ae&&e[ae]||e[`@@iterator`],typeof e==`function`?e:null)}var se=Symbol.for(`react.client.reference`);function ce(e){if(e==null)return null;if(typeof e==`function`)return e.$$typeof===se?null:e.displayName||e.name||null;if(typeof e==`string`)return e;switch(e){case y:return`Fragment`;case x:return`Profiler`;case b:return`StrictMode`;case te:return`Suspense`;case w:return`SuspenseList`;case re:return`Activity`}if(typeof e==`object`)switch(e.$$typeof){case v:return`Portal`;case S:return e.displayName||`Context`;case ee:return(e._context.displayName||`Context`)+`.Consumer`;case C:var t=e.render;return e=e.displayName,e||=(e=t.displayName||t.name||``,e===``?`ForwardRef`:`ForwardRef(`+e+`)`),e;case ne:return t=e.displayName||null,t===null?ce(e.type)||`Memo`:t;case T:t=e._payload,e=e._init;try{return ce(e(t))}catch{}}return null}var le=Array.isArray,E=n.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,D=r.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,ue={pending:!1,data:null,method:null,action:null},O=[],de=-1;function fe(e){return{current:e}}function pe(e){0>de||(e.current=O[de],O[de]=null,de--)}function k(e,t){de++,O[de]=e.current,e.current=t}var me=fe(null),he=fe(null),ge=fe(null),_e=fe(null);function ve(e,t){switch(k(ge,t),k(he,e),k(me,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}pe(me),k(me,e)}function ye(){pe(me),pe(he),pe(ge)}function be(e){e.memoizedState!==null&&k(_e,e);var t=me.current,n=Hd(t,e.type);t!==n&&(k(he,e),k(me,n))}function A(e){he.current===e&&(pe(me),pe(he)),_e.current===e&&(pe(_e),Qf._currentValue=ue)}var xe,Se;function Ce(e){if(xe===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);xe=t&&t[1]||``,Se=-1<e.stack.indexOf(`
|
|
3
3
|
at`)?` (<anonymous>)`:-1<e.stack.indexOf(`@`)?`@unknown:0:0`:``}return`
|
|
4
4
|
`+xe+e+Se}var we=!1;function Te(e,t){if(!e||we)return``;we=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var r={DetermineComponentFrameRoot:function(){try{if(t){var n=function(){throw Error()};if(Object.defineProperty(n.prototype,"props",{set:function(){throw Error()}}),typeof Reflect==`object`&&Reflect.construct){try{Reflect.construct(n,[])}catch(e){var r=e}Reflect.construct(e,[],n)}else{try{n.call()}catch(e){r=e}e.call(n.prototype)}}else{try{throw Error()}catch(e){r=e}(n=e())&&typeof n.catch==`function`&&n.catch(function(){})}}catch(e){if(e&&r&&typeof e.stack==`string`)return[e.stack,r.stack]}return[null,null]}};r.DetermineComponentFrameRoot.displayName=`DetermineComponentFrameRoot`;var i=Object.getOwnPropertyDescriptor(r.DetermineComponentFrameRoot,`name`);i&&i.configurable&&Object.defineProperty(r.DetermineComponentFrameRoot,"name",{value:`DetermineComponentFrameRoot`});var a=r.DetermineComponentFrameRoot(),o=a[0],s=a[1];if(o&&s){var c=o.split(`
|
|
@@ -269,7 +269,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
269
269
|
`))}function c(e,t,n,r){let i=n.enter(`tableCell`),o=n.enter(`phrasing`),s=n.containerPhrasing(e,{...r,before:a,after:a});return o(),i(),s}function l(e,t){return Ch(e,{align:t,alignDelimiters:r,padding:n,stringLength:i})}function u(e,t,n){let r=e.children,i=-1,a=[],o=t.enter(`table`);for(;++i<r.length;)a[i]=d(r[i],t,n);return o(),a}function d(e,t,n){let r=e.children,i=-1,a=[],o=t.enter(`tableRow`);for(;++i<r.length;)a[i]=c(r[i],e,t,n);return o(),a}function f(e,t,n){let r=bg.inlineCode(e,t,n);return n.stack.includes(`tableCell`)&&(r=r.replace(/\|/g,`\\$&`)),r}}function Ag(){return{exit:{taskListCheckValueChecked:Mg,taskListCheckValueUnchecked:Mg,paragraph:Ng}}}function jg(){return{unsafe:[{atBreak:!0,character:`-`,after:`[:|-]`}],handlers:{listItem:Pg}}}function Mg(e){let t=this.stack[this.stack.length-2];t.type,t.checked=e.type===`taskListCheckValueChecked`}function Ng(e){let t=this.stack[this.stack.length-2];if(t&&t.type===`listItem`&&typeof t.checked==`boolean`){let e=this.stack[this.stack.length-1];e.type;let n=e.children[0];if(n&&n.type===`text`){let r=t.children,i=-1,a;for(;++i<r.length;){let e=r[i];if(e.type===`paragraph`){a=e;break}}a===e&&(n.value=n.value.slice(1),n.value.length===0?e.children.shift():e.position&&n.position&&typeof n.position.start.offset==`number`&&(n.position.start.column++,n.position.start.offset++,e.position.start=Object.assign({},n.position.start)))}}this.exit(e)}function Pg(e,t,n,r){let i=e.children[0],a=typeof e.checked==`boolean`&&i&&i.type===`paragraph`,o=`[`+(e.checked?`x`:` `)+`] `,s=n.createTracker(r);a&&s.move(o);let c=bg.listItem(e,t,n,{...r,...s.current()});return a&&(c=c.replace(/^(?:[*+-]|\d+\.)([\r\n]| {1,3})/,l)),c;function l(e){return e+o}}function Fg(){return[Vm(),dh(),gh(),xg(),Ag()]}function Ig(e){return{extensions:[Hm(),fh(e),_h(),kg(e),jg()]}}var Lg={tokenize:Zg,partial:!0},Rg={tokenize:Qg,partial:!0},zg={tokenize:$g,partial:!0},Bg={tokenize:e_,partial:!0},Vg={tokenize:t_,partial:!0},Hg={name:`wwwAutolink`,tokenize:Yg,previous:n_},Ug={name:`protocolAutolink`,tokenize:Xg,previous:r_},Wg={name:`emailAutolink`,tokenize:Jg,previous:i_},Gg={};function Kg(){return{text:Gg}}for(var qg=48;qg<123;)Gg[qg]=Wg,qg++,qg===58?qg=65:qg===91&&(qg=97);Gg[43]=Wg,Gg[45]=Wg,Gg[46]=Wg,Gg[95]=Wg,Gg[72]=[Wg,Ug],Gg[104]=[Wg,Ug],Gg[87]=[Wg,Hg],Gg[119]=[Wg,Hg];function Jg(e,t,n){let r=this,i,a;return o;function o(t){return!a_(t)||!i_.call(r,r.previous)||o_(r.events)?n(t):(e.enter(`literalAutolink`),e.enter(`literalAutolinkEmail`),s(t))}function s(t){return a_(t)?(e.consume(t),s):t===64?(e.consume(t),c):n(t)}function c(t){return t===46?e.check(Vg,u,l)(t):t===45||t===95||Kl(t)?(a=!0,e.consume(t),c):u(t)}function l(t){return e.consume(t),i=!0,c}function u(o){return a&&i&&Gl(r.previous)?(e.exit(`literalAutolinkEmail`),e.exit(`literalAutolink`),t(o)):n(o)}}function Yg(e,t,n){let r=this;return i;function i(t){return t!==87&&t!==119||!n_.call(r,r.previous)||o_(r.events)?n(t):(e.enter(`literalAutolink`),e.enter(`literalAutolinkWww`),e.check(Lg,e.attempt(Rg,e.attempt(zg,a),n),n)(t))}function a(n){return e.exit(`literalAutolinkWww`),e.exit(`literalAutolink`),t(n)}}function Xg(e,t,n){let r=this,i=``,a=!1;return o;function o(t){return(t===72||t===104)&&r_.call(r,r.previous)&&!o_(r.events)?(e.enter(`literalAutolink`),e.enter(`literalAutolinkHttp`),i+=String.fromCodePoint(t),e.consume(t),s):n(t)}function s(t){if(Gl(t)&&i.length<5)return i+=String.fromCodePoint(t),e.consume(t),s;if(t===58){let n=i.toLowerCase();if(n===`http`||n===`https`)return e.consume(t),c}return n(t)}function c(t){return t===47?(e.consume(t),a?l:(a=!0,c)):n(t)}function l(t){return t===null||Jl(t)||J(t)||$l(t)||Ql(t)?n(t):e.attempt(Rg,e.attempt(zg,u),n)(t)}function u(n){return e.exit(`literalAutolinkHttp`),e.exit(`literalAutolink`),t(n)}}function Zg(e,t,n){let r=0;return i;function i(t){return(t===87||t===119)&&r<3?(r++,e.consume(t),i):t===46&&r===3?(e.consume(t),a):n(t)}function a(e){return e===null?n(e):t(e)}}function Qg(e,t,n){let r,i,a;return o;function o(t){return t===46||t===95?e.check(Bg,c,s)(t):t===null||J(t)||$l(t)||t!==45&&Ql(t)?c(t):(a=!0,e.consume(t),o)}function s(t){return t===95?r=!0:(i=r,r=void 0),e.consume(t),o}function c(e){return i||r||!a?n(e):t(e)}}function $g(e,t){let n=0,r=0;return i;function i(o){return o===40?(n++,e.consume(o),i):o===41&&r<n?a(o):o===33||o===34||o===38||o===39||o===41||o===42||o===44||o===46||o===58||o===59||o===60||o===63||o===93||o===95||o===126?e.check(Bg,t,a)(o):o===null||J(o)||$l(o)?t(o):(e.consume(o),i)}function a(t){return t===41&&r++,e.consume(t),i}}function e_(e,t,n){return r;function r(o){return o===33||o===34||o===39||o===41||o===42||o===44||o===46||o===58||o===59||o===63||o===95||o===126?(e.consume(o),r):o===38?(e.consume(o),a):o===93?(e.consume(o),i):o===60||o===null||J(o)||$l(o)?t(o):n(o)}function i(e){return e===null||e===40||e===91||J(e)||$l(e)?t(e):r(e)}function a(e){return Gl(e)?o(e):n(e)}function o(t){return t===59?(e.consume(t),r):Gl(t)?(e.consume(t),o):n(t)}}function t_(e,t,n){return r;function r(t){return e.consume(t),i}function i(e){return Kl(e)?n(e):t(e)}}function n_(e){return e===null||e===40||e===42||e===95||e===91||e===93||e===126||J(e)}function r_(e){return!Gl(e)}function i_(e){return!(e===47||a_(e))}function a_(e){return e===43||e===45||e===46||e===95||Kl(e)}function o_(e){let t=e.length,n=!1;for(;t--;){let r=e[t][1];if((r.type===`labelLink`||r.type===`labelImage`)&&!r._balanced){n=!0;break}if(r._gfmAutolinkLiteralWalkedInto){n=!1;break}}return e.length>0&&!n&&(e[e.length-1][1]._gfmAutolinkLiteralWalkedInto=!0),n}var s_={tokenize:h_,partial:!0};function c_(){return{document:{91:{name:`gfmFootnoteDefinition`,tokenize:f_,continuation:{tokenize:p_},exit:m_}},text:{91:{name:`gfmFootnoteCall`,tokenize:d_},93:{name:`gfmPotentialFootnoteCall`,add:`after`,tokenize:l_,resolveTo:u_}}}}function l_(e,t,n){let r=this,i=r.events.length,a=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]),o;for(;i--;){let e=r.events[i][1];if(e.type===`labelImage`){o=e;break}if(e.type===`gfmFootnoteCall`||e.type===`labelLink`||e.type===`label`||e.type===`image`||e.type===`link`)break}return s;function s(i){if(!o||!o._balanced)return n(i);let s=Wl(r.sliceSerialize({start:o.end,end:r.now()}));return s.codePointAt(0)!==94||!a.includes(s.slice(1))?n(i):(e.enter(`gfmFootnoteCallLabelMarker`),e.consume(i),e.exit(`gfmFootnoteCallLabelMarker`),t(i))}}function u_(e,t){let n=e.length;for(;n--;)if(e[n][1].type===`labelImage`&&e[n][0]===`enter`){e[n][1];break}e[n+1][1].type=`data`,e[n+3][1].type=`gfmFootnoteCallLabelMarker`;let r={type:`gfmFootnoteCall`,start:Object.assign({},e[n+3][1].start),end:Object.assign({},e[e.length-1][1].end)},i={type:`gfmFootnoteCallMarker`,start:Object.assign({},e[n+3][1].end),end:Object.assign({},e[n+3][1].end)};i.end.column++,i.end.offset++,i.end._bufferIndex++;let a={type:`gfmFootnoteCallString`,start:Object.assign({},i.end),end:Object.assign({},e[e.length-1][1].start)},o={type:`chunkString`,contentType:`string`,start:Object.assign({},a.start),end:Object.assign({},a.end)},s=[e[n+1],e[n+2],[`enter`,r,t],e[n+3],e[n+4],[`enter`,i,t],[`exit`,i,t],[`enter`,a,t],[`enter`,o,t],[`exit`,o,t],[`exit`,a,t],e[e.length-2],e[e.length-1],[`exit`,r,t]];return e.splice(n,e.length-n+1,...s),e}function d_(e,t,n){let r=this,i=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]),a=0,o;return s;function s(t){return e.enter(`gfmFootnoteCall`),e.enter(`gfmFootnoteCallLabelMarker`),e.consume(t),e.exit(`gfmFootnoteCallLabelMarker`),c}function c(t){return t===94?(e.enter(`gfmFootnoteCallMarker`),e.consume(t),e.exit(`gfmFootnoteCallMarker`),e.enter(`gfmFootnoteCallString`),e.enter(`chunkString`).contentType=`string`,l):n(t)}function l(s){if(a>999||s===93&&!o||s===null||s===91||J(s))return n(s);if(s===93){e.exit(`chunkString`);let a=e.exit(`gfmFootnoteCallString`);return i.includes(Wl(r.sliceSerialize(a)))?(e.enter(`gfmFootnoteCallLabelMarker`),e.consume(s),e.exit(`gfmFootnoteCallLabelMarker`),e.exit(`gfmFootnoteCall`),t):n(s)}return J(s)||(o=!0),a++,e.consume(s),s===92?u:l}function u(t){return t===91||t===92||t===93?(e.consume(t),a++,l):l(t)}}function f_(e,t,n){let r=this,i=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]),a,o=0,s;return c;function c(t){return e.enter(`gfmFootnoteDefinition`)._container=!0,e.enter(`gfmFootnoteDefinitionLabel`),e.enter(`gfmFootnoteDefinitionLabelMarker`),e.consume(t),e.exit(`gfmFootnoteDefinitionLabelMarker`),l}function l(t){return t===94?(e.enter(`gfmFootnoteDefinitionMarker`),e.consume(t),e.exit(`gfmFootnoteDefinitionMarker`),e.enter(`gfmFootnoteDefinitionLabelString`),e.enter(`chunkString`).contentType=`string`,u):n(t)}function u(t){if(o>999||t===93&&!s||t===null||t===91||J(t))return n(t);if(t===93){e.exit(`chunkString`);let n=e.exit(`gfmFootnoteDefinitionLabelString`);return a=Wl(r.sliceSerialize(n)),e.enter(`gfmFootnoteDefinitionLabelMarker`),e.consume(t),e.exit(`gfmFootnoteDefinitionLabelMarker`),e.exit(`gfmFootnoteDefinitionLabel`),f}return J(t)||(s=!0),o++,e.consume(t),t===92?d:u}function d(t){return t===91||t===92||t===93?(e.consume(t),o++,u):u(t)}function f(t){return t===58?(e.enter(`definitionMarker`),e.consume(t),e.exit(`definitionMarker`),i.includes(a)||i.push(a),X(e,p,`gfmFootnoteDefinitionWhitespace`)):n(t)}function p(e){return t(e)}}function p_(e,t,n){return e.check(gu,t,e.attempt(s_,t,n))}function m_(e){e.exit(`gfmFootnoteDefinition`)}function h_(e,t,n){let r=this;return X(e,i,`gfmFootnoteDefinitionIndent`,5);function i(e){let i=r.events[r.events.length-1];return i&&i[1].type===`gfmFootnoteDefinitionIndent`&&i[2].sliceSerialize(i[1],!0).length===4?t(e):n(e)}}function g_(e){let t=(e||{}).singleTilde,n={name:`strikethrough`,tokenize:i,resolveAll:r};return t??=!0,{text:{126:n},insideSpan:{null:[n]},attentionMarkers:{null:[126]}};function r(e,t){let n=-1;for(;++n<e.length;)if(e[n][0]===`enter`&&e[n][1].type===`strikethroughSequenceTemporary`&&e[n][1]._close){let r=n;for(;r--;)if(e[r][0]===`exit`&&e[r][1].type===`strikethroughSequenceTemporary`&&e[r][1]._open&&e[n][1].end.offset-e[n][1].start.offset===e[r][1].end.offset-e[r][1].start.offset){e[n][1].type=`strikethroughSequence`,e[r][1].type=`strikethroughSequence`;let i={type:`strikethrough`,start:Object.assign({},e[r][1].start),end:Object.assign({},e[n][1].end)},a={type:`strikethroughText`,start:Object.assign({},e[r][1].end),end:Object.assign({},e[n][1].start)},o=[[`enter`,i,t],[`enter`,e[r][1],t],[`exit`,e[r][1],t],[`enter`,a,t]],s=t.parser.constructs.insideSpan.null;s&&Vl(o,o.length,0,lu(s,e.slice(r+1,n),t)),Vl(o,o.length,0,[[`exit`,a,t],[`enter`,e[n][1],t],[`exit`,e[n][1],t],[`exit`,i,t]]),Vl(e,r-1,n-r+3,o),n=r+o.length-2;break}}for(n=-1;++n<e.length;)e[n][1].type===`strikethroughSequenceTemporary`&&(e[n][1].type=`data`);return e}function i(e,n,r){let i=this.previous,a=this.events,o=0;return s;function s(t){return i===126&&a[a.length-1][1].type!==`characterEscape`?r(t):(e.enter(`strikethroughSequenceTemporary`),c(t))}function c(a){let s=cu(i);if(a===126)return o>1?r(a):(e.consume(a),o++,c);if(o<2&&!t)return r(a);let l=e.exit(`strikethroughSequenceTemporary`),u=cu(a);return l._open=!u||u===2&&!!s,l._close=!s||s===2&&!!u,n(a)}}}var __=class{constructor(){this.map=[]}add(e,t,n){v_(this,e,t,n)}consume(e){if(this.map.sort(function(e,t){return e[0]-t[0]}),this.map.length===0)return;let t=this.map.length,n=[];for(;t>0;)--t,n.push(e.slice(this.map[t][0]+this.map[t][1]),this.map[t][2]),e.length=this.map[t][0];n.push(e.slice()),e.length=0;let r=n.pop();for(;r;){for(let t of r)e.push(t);r=n.pop()}this.map.length=0}};function v_(e,t,n,r){let i=0;if(n!==0||r.length!==0){for(;i<e.map.length;){if(e.map[i][0]===t){e.map[i][1]+=n,e.map[i][2].push(...r);return}i+=1}e.map.push([t,n,r])}}function y_(e,t){let n=!1,r=[];for(;t<e.length;){let i=e[t];if(n){if(i[0]===`enter`)i[1].type===`tableContent`&&r.push(e[t+1][1].type===`tableDelimiterMarker`?`left`:`none`);else if(i[1].type===`tableContent`){if(e[t-1][1].type===`tableDelimiterMarker`){let e=r.length-1;r[e]=r[e]===`left`?`center`:`right`}}else if(i[1].type===`tableDelimiterRow`)break}else i[0]===`enter`&&i[1].type===`tableDelimiterRow`&&(n=!0);t+=1}return r}function b_(){return{flow:{null:{name:`table`,tokenize:x_,resolveAll:S_}}}}function x_(e,t,n){let r=this,i=0,a=0,o;return s;function s(e){let t=r.events.length-1;for(;t>-1;){let e=r.events[t][1].type;if(e===`lineEnding`||e===`linePrefix`)t--;else break}let i=t>-1?r.events[t][1].type:null,a=i===`tableHead`||i===`tableRow`?ee:c;return a===ee&&r.parser.lazy[r.now().line]?n(e):a(e)}function c(t){return e.enter(`tableHead`),e.enter(`tableRow`),l(t)}function l(e){return e===124?u(e):(o=!0,a+=1,u(e))}function u(t){return t===null?n(t):q(t)?a>1?(a=0,r.interrupt=!0,e.exit(`tableRow`),e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),p):n(t):Y(t)?X(e,u,`whitespace`)(t):(a+=1,o&&(o=!1,i+=1),t===124?(e.enter(`tableCellDivider`),e.consume(t),e.exit(`tableCellDivider`),o=!0,u):(e.enter(`data`),d(t)))}function d(t){return t===null||t===124||J(t)?(e.exit(`data`),u(t)):(e.consume(t),t===92?f:d)}function f(t){return t===92||t===124?(e.consume(t),d):d(t)}function p(t){return r.interrupt=!1,r.parser.lazy[r.now().line]?n(t):(e.enter(`tableDelimiterRow`),o=!1,Y(t)?X(e,m,`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(t):m(t))}function m(t){return t===45||t===58?g(t):t===124?(o=!0,e.enter(`tableCellDivider`),e.consume(t),e.exit(`tableCellDivider`),h):x(t)}function h(t){return Y(t)?X(e,g,`whitespace`)(t):g(t)}function g(t){return t===58?(a+=1,o=!0,e.enter(`tableDelimiterMarker`),e.consume(t),e.exit(`tableDelimiterMarker`),_):t===45?(a+=1,_(t)):t===null||q(t)?b(t):x(t)}function _(t){return t===45?(e.enter(`tableDelimiterFiller`),v(t)):x(t)}function v(t){return t===45?(e.consume(t),v):t===58?(o=!0,e.exit(`tableDelimiterFiller`),e.enter(`tableDelimiterMarker`),e.consume(t),e.exit(`tableDelimiterMarker`),y):(e.exit(`tableDelimiterFiller`),y(t))}function y(t){return Y(t)?X(e,b,`whitespace`)(t):b(t)}function b(n){return n===124?m(n):n===null||q(n)?!o||i!==a?x(n):(e.exit(`tableDelimiterRow`),e.exit(`tableHead`),t(n)):x(n)}function x(e){return n(e)}function ee(t){return e.enter(`tableRow`),S(t)}function S(n){return n===124?(e.enter(`tableCellDivider`),e.consume(n),e.exit(`tableCellDivider`),S):n===null||q(n)?(e.exit(`tableRow`),t(n)):Y(n)?X(e,S,`whitespace`)(n):(e.enter(`data`),C(n))}function C(t){return t===null||t===124||J(t)?(e.exit(`data`),S(t)):(e.consume(t),t===92?te:C)}function te(t){return t===92||t===124?(e.consume(t),C):C(t)}}function S_(e,t){let n=-1,r=!0,i=0,a=[0,0,0,0],o=[0,0,0,0],s=!1,c=0,l,u,d,f=new __;for(;++n<e.length;){let p=e[n],m=p[1];p[0]===`enter`?m.type===`tableHead`?(s=!1,c!==0&&(w_(f,t,c,l,u),u=void 0,c=0),l={type:`table`,start:Object.assign({},m.start),end:Object.assign({},m.end)},f.add(n,0,[[`enter`,l,t]])):m.type===`tableRow`||m.type===`tableDelimiterRow`?(r=!0,d=void 0,a=[0,0,0,0],o=[0,n+1,0,0],s&&(s=!1,u={type:`tableBody`,start:Object.assign({},m.start),end:Object.assign({},m.end)},f.add(n,0,[[`enter`,u,t]])),i=m.type===`tableDelimiterRow`?2:u?3:1):i&&(m.type===`data`||m.type===`tableDelimiterMarker`||m.type===`tableDelimiterFiller`)?(r=!1,o[2]===0&&(a[1]!==0&&(o[0]=o[1],d=C_(f,t,a,i,void 0,d),a=[0,0,0,0]),o[2]=n)):m.type===`tableCellDivider`&&(r?r=!1:(a[1]!==0&&(o[0]=o[1],d=C_(f,t,a,i,void 0,d)),a=o,o=[a[1],n,0,0])):m.type===`tableHead`?(s=!0,c=n):m.type===`tableRow`||m.type===`tableDelimiterRow`?(c=n,a[1]===0?o[1]!==0&&(d=C_(f,t,o,i,n,d)):(o[0]=o[1],d=C_(f,t,a,i,n,d)),i=0):i&&(m.type===`data`||m.type===`tableDelimiterMarker`||m.type===`tableDelimiterFiller`)&&(o[3]=n)}for(c!==0&&w_(f,t,c,l,u),f.consume(t.events),n=-1;++n<t.events.length;){let e=t.events[n];e[0]===`enter`&&e[1].type===`table`&&(e[1]._align=y_(t.events,n))}return e}function C_(e,t,n,r,i,a){let o=r===1?`tableHeader`:r===2?`tableDelimiter`:`tableData`;n[0]!==0&&(a.end=Object.assign({},T_(t.events,n[0])),e.add(n[0],0,[[`exit`,a,t]]));let s=T_(t.events,n[1]);if(a={type:o,start:Object.assign({},s),end:Object.assign({},s)},e.add(n[1],0,[[`enter`,a,t]]),n[2]!==0){let i=T_(t.events,n[2]),a=T_(t.events,n[3]),o={type:`tableContent`,start:Object.assign({},i),end:Object.assign({},a)};if(e.add(n[2],0,[[`enter`,o,t]]),r!==2){let r=t.events[n[2]],i=t.events[n[3]];if(r[1].end=Object.assign({},i[1].end),r[1].type=`chunkText`,r[1].contentType=`text`,n[3]>n[2]+1){let t=n[2]+1,r=n[3]-n[2]-1;e.add(t,r,[])}}e.add(n[3]+1,0,[[`exit`,o,t]])}return i!==void 0&&(a.end=Object.assign({},T_(t.events,i)),e.add(i,0,[[`exit`,a,t]]),a=void 0),a}function w_(e,t,n,r,i){let a=[],o=T_(t.events,n);i&&(i.end=Object.assign({},o),a.push([`exit`,i,t])),r.end=Object.assign({},o),a.push([`exit`,r,t]),e.add(n+1,0,a)}function T_(e,t){let n=e[t],r=n[0]===`enter`?`start`:`end`;return n[1][r]}var E_={name:`tasklistCheck`,tokenize:O_};function D_(){return{text:{91:E_}}}function O_(e,t,n){let r=this;return i;function i(t){return r.previous!==null||!r._gfmTasklistFirstContentOfListItem?n(t):(e.enter(`taskListCheck`),e.enter(`taskListCheckMarker`),e.consume(t),e.exit(`taskListCheckMarker`),a)}function a(t){return J(t)?(e.enter(`taskListCheckValueUnchecked`),e.consume(t),e.exit(`taskListCheckValueUnchecked`),o):t===88||t===120?(e.enter(`taskListCheckValueChecked`),e.consume(t),e.exit(`taskListCheckValueChecked`),o):n(t)}function o(t){return t===93?(e.enter(`taskListCheckMarker`),e.consume(t),e.exit(`taskListCheckMarker`),e.exit(`taskListCheck`),s):n(t)}function s(r){return q(r)?t(r):Y(r)?e.check({tokenize:k_},t,n)(r):n(r)}}function k_(e,t,n){return X(e,r,`whitespace`);function r(e){return e===null?n(e):t(e)}}function A_(e){return W([Kg(),c_(),g_(e),b_(),D_()])}var j_={};function M_(e){let t=this,n=e||j_,r=t.data(),i=r.micromarkExtensions||=[],a=r.fromMarkdownExtensions||=[],o=r.toMarkdownExtensions||=[];i.push(A_(n)),a.push(Fg()),o.push(Ig(n))}var N_=/^(\s*)(\d{1,9})([.)])\s*$/,P_=/^(\s*)([-*+])\s*$/,F_=/^\s*(```|~~~)/;function I_(e){let t=!1;return e.split(`
|
|
270
270
|
`).map(e=>{if(F_.test(e))return t=!t,e;if(t)return e;let n=N_.exec(e);if(n)return`${n[1]}${n[2]}\\${n[3]}`;let r=P_.exec(e);return r?`${r[1]}\\${r[2]}`:e}).join(`
|
|
271
271
|
`)}var L_={default:{h1:`mt-3 mb-1.5 text-[17px] font-semibold`,h2:`mt-3 mb-1.5 text-[16px] font-semibold`,h3:`mt-3 mb-1.5 text-[15px] font-semibold`,code:`text-[12.5px]`,table:`my-1.5 w-full border-collapse text-sm`},compact:{h1:`mt-3 mb-1.5 text-[15px] font-medium`,h2:`mt-3 mb-1.5 text-[14px] font-medium`,h3:`mt-3 mb-1.5 text-[13px] font-medium`,code:`text-[11px]`,table:`my-1.5 w-full border-collapse text-[12px]`}},R_=(0,y.memo)(function({text:e,variant:t=`default`}){let n=Ts(),r=L_[t],i=(0,y.useMemo)(()=>({p:e=>(0,z.jsx)(`p`,{className:`my-1.5 first:mt-0 last:mb-0`,...e}),ul:e=>(0,z.jsx)(`ul`,{className:`my-1.5 list-disc pl-5`,...e}),ol:e=>(0,z.jsx)(`ol`,{className:`my-1.5 list-decimal pl-5`,...e}),li:e=>(0,z.jsx)(`li`,{className:`my-0.5`,...e}),h1:e=>(0,z.jsx)(`h1`,{className:r.h1,...e}),h2:e=>(0,z.jsx)(`h2`,{className:r.h2,...e}),h3:e=>(0,z.jsx)(`h3`,{className:r.h3,...e}),a:({style:e,...t})=>(0,z.jsx)(`a`,{className:`underline underline-offset-2`,style:{color:n.primary,...e},target:`_blank`,rel:`noreferrer noopener`,...t}),code:({className:e,style:t,children:i,...a})=>/language-/.test(e??``)?(0,z.jsx)(`code`,{className:Ds(`font-aai-mono`,r.code),...a,children:i}):(0,z.jsx)(`code`,{className:Ds(`rounded-sm border px-1 py-0.5 font-aai-mono`,r.code),style:{borderColor:n.border,background:Os(n.text,n.surface,3),...t},...a,children:i}),pre:({style:e,...t})=>(0,z.jsx)(`pre`,{className:`my-1.5 overflow-x-auto rounded-md border p-2.5 whitespace-pre-wrap wrap-break-word`,style:{borderColor:n.border,background:Os(n.text,n.surface,3),...e},...t}),blockquote:({style:e,...t})=>(0,z.jsx)(`blockquote`,{className:`my-1.5 border-l-2 pl-3`,style:{borderColor:n.border,color:Os(n.text,n.surface,75),...e},...t}),table:e=>(0,z.jsx)(`table`,{className:r.table,...e}),th:({style:e,...t})=>(0,z.jsx)(`th`,{className:`border px-2 py-1 text-left font-medium`,style:{borderColor:n.border,...e},...t}),td:({style:e,...t})=>(0,z.jsx)(`td`,{className:`border px-2 py-1`,style:{borderColor:n.border,...e},...t}),hr:({style:e,...t})=>(0,z.jsx)(`hr`,{className:`my-2.5 border-t`,style:{borderColor:n.border,...e},...t})}),[n,r]);return(0,z.jsx)(Om,{remarkPlugins:[M_],components:i,children:I_(e)})}),z_={default:{button:`gap-2.5 px-3.5 py-2.5`,title:`text-[13px]`,chevron:`text-[10px]`},compact:{button:`gap-2 px-3 py-2`,title:`text-[11px]`,chevron:`text-[9px]`}};function B_({title:e,detail:t,pending:n=!1,icon:r,variant:i=`default`,className:a,children:o}){let[s,c]=(0,y.useState)(!1),l=Ts(),u=z_[i],d=o!=null;return(0,z.jsxs)(`div`,{className:Ds(`flex flex-col rounded-md border overflow-hidden`,a),style:{borderColor:l.border,background:l.bg},children:[(0,z.jsxs)(`button`,{type:`button`,"aria-expanded":d?s:void 0,disabled:!d,className:Ds(`flex items-center select-none text-left w-full appearance-none border-none bg-transparent`,u.button,d&&`cursor-pointer`),onClick:()=>{d&&c(!s)},children:[r?(0,z.jsx)(`span`,{className:`w-4 h-4 shrink-0 text-center leading-4`,children:r}):(0,z.jsx)(Ps,{className:`shrink-0`,style:{color:Os(l.text,l.bg,65)},children:`Tool`}),(0,z.jsx)(`span`,{className:Ds(`min-w-0 truncate font-aai-mono font-medium`,u.title,n&&`tool-shimmer`),style:{color:l.text},children:e}),(0,z.jsx)(`span`,{className:Ds(`font-aai-mono truncate flex-1 min-w-0`,u.title),style:{color:Os(l.text,l.bg,65)},children:t}),d&&(0,z.jsx)(`span`,{className:Ds(`shrink-0 transition-transform duration-150`,u.chevron,s&&`rotate-90`),style:{color:Os(l.text,l.bg,65)},children:`▶`})]}),s&&d&&(0,z.jsx)(`div`,{className:`border-t max-h-64 overflow-auto`,style:{borderColor:l.border,background:l.surface},children:o})]})}var V_=(0,y.createContext)({});function H_(){return(0,y.useContext)(V_)}function U_(e){let t=le(e);return t===e?e:JSON.stringify(t,null,2)}var W_=(0,y.memo)(function({toolCall:e,className:t}){let n=Ts(),r=H_(),i=Object.hasOwn(r,e.name)?r[e.name]:void 0,a=e.status===`pending`,o=i?.label||e.name,s=i?.icon,c=!a&&!!e.result,l=(0,y.useMemo)(()=>e.result?U_(e.result):``,[e.result]),u=(0,y.useMemo)(()=>{let t=e.args;if(e.name===`run_code`&&t.code)return E(String(t.code).split(`
|
|
272
|
-
`)[0]??``);for(let e of[`query`,`url`,`question`])if(t[e])return String(t[e]);return E(JSON.stringify(t))},[e.name,e.args]);return(0,z.jsx)(B_,{title:o,detail:u,pending:a,icon:s,className:t,children:c?(0,z.jsxs)(z.Fragment,{children:[e.name===`run_code`&&!!e.args.code&&(0,z.jsx)(`pre`,{className:`font-aai-mono text-xs px-3.5 py-3 m-0 whitespace-pre-wrap wrap-break-word border-b`,style:{color:n.text,borderColor:n.border},children:String(e.args.code)}),l&&(0,z.jsx)(`pre`,{className:`font-aai-mono text-xs px-3.5 py-3 m-0 whitespace-pre-wrap wrap-break-word`,style:{color:Os(n.text,n.surface,75)},children:l})]}):void 0})}),G_=[0,.16,.32].map(e=>({animation:`aai-bounce 1.4s infinite ease-in-out both`,animationDelay:`${e}s`}));function K_(){let e=Ts(),t=Os(e.text,e.surface,75);return(0,z.jsx)(`div`,{role:`status`,"aria-label":`Thinking`,className:`flex items-center gap-2 text-sm font-medium min-h-5`,style:{color:t},children:G_.map((e,n)=>(0,z.jsx)(`div`,{className:`w-1.5 h-1.5 rounded-full`,style:{...e,background:t}},n))})}function q_({theme:e,color:t,children:n}){return(0,z.jsx)(`div`,{className:`flex flex-col w-full items-end`,children:(0,z.jsx)(`div`,{className:`max-w-[min(78%,64ch)] border px-3.5 py-2.5 rounded-md whitespace-pre-wrap wrap-break-word text-[15px] font-normal leading-[22px]`,style:{background:js(e.primary,e.surface,7),borderColor:js(e.primary,e.surface,16),color:t},children:n})})}var J_=(0,y.memo)(function({message:e,theme:t}){return e.role===`user`?(0,z.jsx)(q_,{theme:t,color:t.text,children:e.content}):(0,z.jsxs)(`div`,{className:`flex flex-col gap-1 max-w-[82%]`,children:[(0,z.jsx)(`span`,{className:`text-[10px] font-medium tracking-[1.2px] uppercase leading-none`,style:{color:Os(t.text,t.surface,65)},children:`Agent`}),(0,z.jsx)(`div`,{className:`wrap-break-word text-[15px] font-normal leading-[23px]`,style:{color:t.text},children:(0,z.jsx)(R_,{text:e.content})})]})});function Y_(e,t,n,r){let i=[],a=0,o=e=>{let n=t[a];for(;n&&n.afterMessageId<=e;)i.push(r(n)),a++,n=t[a]},s=e[0];s&&o(s.id-1);for(let t of e)i.push(n(t)),o(t.id);return o(1/0),i}var X_=(0,y.memo)(function({className:e}){let t=xs(e=>e.state),n=xs(e=>e.messages),r=xs(e=>e.toolCalls),i=xs(e=>e.userTranscript),a=xs(e=>e.agentTranscript),o=Ts(),s=(0,y.useMemo)(()=>{if(t!==`thinking`)return!1;let e=r.at(-1);if(e?.status===`pending`)return!1;let i=n.at(-1);return!i||i.role===`user`||!!e},[t,r,n]),c=(0,y.useMemo)(()=>a?{role:`assistant`,content:a}:null,[a]),l=(0,y.useMemo)(()=>Y_(n,r,e=>(0,z.jsx)(J_,{message:e,theme:o},e.id),e=>(0,z.jsx)(W_,{toolCall:e},e.callId)),[n,r,o]);return(0,z.jsxs)(rc,{className:e,style:{background:o.surface},contentClassName:`flex flex-col gap-4 p-7`,children:[l,c&&(0,z.jsx)(J_,{message:c,theme:o}),i!==null&&(0,z.jsx)(q_,{theme:o,color:Os(o.text,o.surface,65),children:i||(0,z.jsx)(K_,{})}),s&&(0,z.jsx)(K_,{})]})}),Z_=new Set([`listening`,`speaking`]);function Q_({icon:e,title:t,className:n}){let r=xs(e=>e.state),i=xs(e=>e.error);return(0,z.jsx)(Is,{icon:e,title:t,state:r,pulsing:Z_.has(r),error:i?.message,className:n,footer:(0,z.jsx)(Us,{}),children:(0,z.jsx)(X_,{})})}function $_({sidebar:e,children:t,sidebarWidth:n=`18rem`,sidebarPosition:r=`left`,className:i}){let a=Ts(),o=(0,z.jsx)(`div`,{className:Ds(`shrink-0 flex flex-col overflow-y-auto`,`w-full max-h-[40vh] md:w-(--aai-sidebar-w) md:max-h-none`,r===`left`?`border-b md:border-b-0 md:border-r`:`border-t md:border-t-0 md:border-l order-last md:order-none`),style:{borderColor:a.border},children:e}),s={background:a.bg,"--aai-sidebar-w":n};return(0,z.jsxs)(`div`,{className:Ds(`flex flex-col md:flex-row min-h-screen md:h-screen`,i),style:s,children:[r===`left`&&o,(0,z.jsx)(`div`,{className:`flex-1 min-w-0 min-h-0`,children:t}),r===`right`&&o]})}function ev({children:e,icon:t,title:n,subtitle:r,buttonText:i=`Start Conversation`,className:a}){let o=xs(e=>e.started),{start:s}=bs(),c=Ts();return o?e:(0,z.jsx)(`div`,{className:Ds(`flex items-center justify-center h-screen font-aai`,a),style:{background:c.bg},children:(0,z.jsxs)(`div`,{className:`flex flex-col items-center gap-5 border rounded-xl px-10 py-12 sm:px-16 sm:py-14 max-w-105 text-center`,style:{background:c.surface,borderColor:c.border,boxShadow:`0 4px 12px -2px rgb(20 18 12 / 0.08)`},children:[t??(0,z.jsx)(Ns,{size:24}),(0,z.jsx)(Ps,{children:`Voice Agent`}),n&&(0,z.jsx)(`h1`,{className:`font-aai-serif font-normal text-[32px] leading-[1.15] tracking-[-0.2px] m-0 text-balance`,style:{color:c.text},children:n}),r&&(0,z.jsx)(`p`,{className:`text-[15px] leading-[22px] m-0 max-w-75`,style:{color:Os(c.text,c.surface,75)},children:r}),(0,z.jsx)(Ls,{size:`lg`,className:`mt-2`,onClick:s,children:i})]})})}function tv(){let e=0;return{current:()=>e,bump(){e+=1},isCurrent:t=>t===e}}var nv=`modulepreload`,rv=function(e,t){return new URL(e,t).href},iv={},av=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=rv(t,n),t=s(t),t in iv)return;iv[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:nv,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},ov=null;function sv(){return ov??=Promise.all([av(()=>import(`./audio-fO7SVU64.js`),__vite__mapDeps([0,1]),import.meta.url),av(()=>import(`./capture-processor-Dmc-KEpb.js`).then(e=>e.default),__vite__mapDeps([2,1,3]),import.meta.url),av(()=>import(`./playback-processor-
|
|
272
|
+
`)[0]??``);for(let e of[`query`,`url`,`question`])if(t[e])return String(t[e]);return E(JSON.stringify(t))},[e.name,e.args]);return(0,z.jsx)(B_,{title:o,detail:u,pending:a,icon:s,className:t,children:c?(0,z.jsxs)(z.Fragment,{children:[e.name===`run_code`&&!!e.args.code&&(0,z.jsx)(`pre`,{className:`font-aai-mono text-xs px-3.5 py-3 m-0 whitespace-pre-wrap wrap-break-word border-b`,style:{color:n.text,borderColor:n.border},children:String(e.args.code)}),l&&(0,z.jsx)(`pre`,{className:`font-aai-mono text-xs px-3.5 py-3 m-0 whitespace-pre-wrap wrap-break-word`,style:{color:Os(n.text,n.surface,75)},children:l})]}):void 0})}),G_=[0,.16,.32].map(e=>({animation:`aai-bounce 1.4s infinite ease-in-out both`,animationDelay:`${e}s`}));function K_(){let e=Ts(),t=Os(e.text,e.surface,75);return(0,z.jsx)(`div`,{role:`status`,"aria-label":`Thinking`,className:`flex items-center gap-2 text-sm font-medium min-h-5`,style:{color:t},children:G_.map((e,n)=>(0,z.jsx)(`div`,{className:`w-1.5 h-1.5 rounded-full`,style:{...e,background:t}},n))})}function q_({theme:e,color:t,children:n}){return(0,z.jsx)(`div`,{className:`flex flex-col w-full items-end`,children:(0,z.jsx)(`div`,{className:`max-w-[min(78%,64ch)] border px-3.5 py-2.5 rounded-md whitespace-pre-wrap wrap-break-word text-[15px] font-normal leading-[22px]`,style:{background:js(e.primary,e.surface,7),borderColor:js(e.primary,e.surface,16),color:t},children:n})})}var J_=(0,y.memo)(function({message:e,theme:t}){return e.role===`user`?(0,z.jsx)(q_,{theme:t,color:t.text,children:e.content}):(0,z.jsxs)(`div`,{className:`flex flex-col gap-1 max-w-[82%]`,children:[(0,z.jsx)(`span`,{className:`text-[10px] font-medium tracking-[1.2px] uppercase leading-none`,style:{color:Os(t.text,t.surface,65)},children:`Agent`}),(0,z.jsx)(`div`,{className:`wrap-break-word text-[15px] font-normal leading-[23px]`,style:{color:t.text},children:(0,z.jsx)(R_,{text:e.content})})]})});function Y_(e,t,n,r){let i=[],a=0,o=e=>{let n=t[a];for(;n&&n.afterMessageId<=e;)i.push(r(n)),a++,n=t[a]},s=e[0];s&&o(s.id-1);for(let t of e)i.push(n(t)),o(t.id);return o(1/0),i}var X_=(0,y.memo)(function({className:e}){let t=xs(e=>e.state),n=xs(e=>e.messages),r=xs(e=>e.toolCalls),i=xs(e=>e.userTranscript),a=xs(e=>e.agentTranscript),o=Ts(),s=(0,y.useMemo)(()=>{if(t!==`thinking`)return!1;let e=r.at(-1);if(e?.status===`pending`)return!1;let i=n.at(-1);return!i||i.role===`user`||!!e},[t,r,n]),c=(0,y.useMemo)(()=>a?{role:`assistant`,content:a}:null,[a]),l=(0,y.useMemo)(()=>Y_(n,r,e=>(0,z.jsx)(J_,{message:e,theme:o},e.id),e=>(0,z.jsx)(W_,{toolCall:e},e.callId)),[n,r,o]);return(0,z.jsxs)(rc,{className:e,style:{background:o.surface},contentClassName:`flex flex-col gap-4 p-7`,children:[l,c&&(0,z.jsx)(J_,{message:c,theme:o}),i!==null&&(0,z.jsx)(q_,{theme:o,color:Os(o.text,o.surface,65),children:i||(0,z.jsx)(K_,{})}),s&&(0,z.jsx)(K_,{})]})}),Z_=new Set([`listening`,`speaking`]);function Q_({icon:e,title:t,className:n}){let r=xs(e=>e.state),i=xs(e=>e.error);return(0,z.jsx)(Is,{icon:e,title:t,state:r,pulsing:Z_.has(r),error:i?.message,className:n,footer:(0,z.jsx)(Us,{}),children:(0,z.jsx)(X_,{})})}function $_({sidebar:e,children:t,sidebarWidth:n=`18rem`,sidebarPosition:r=`left`,className:i}){let a=Ts(),o=(0,z.jsx)(`div`,{className:Ds(`shrink-0 flex flex-col overflow-y-auto`,`w-full max-h-[40vh] md:w-(--aai-sidebar-w) md:max-h-none`,r===`left`?`border-b md:border-b-0 md:border-r`:`border-t md:border-t-0 md:border-l order-last md:order-none`),style:{borderColor:a.border},children:e}),s={background:a.bg,"--aai-sidebar-w":n};return(0,z.jsxs)(`div`,{className:Ds(`flex flex-col md:flex-row min-h-screen md:h-screen`,i),style:s,children:[r===`left`&&o,(0,z.jsx)(`div`,{className:`flex-1 min-w-0 min-h-0`,children:t}),r===`right`&&o]})}function ev({children:e,icon:t,title:n,subtitle:r,buttonText:i=`Start Conversation`,className:a}){let o=xs(e=>e.started),{start:s}=bs(),c=Ts();return o?e:(0,z.jsx)(`div`,{className:Ds(`flex items-center justify-center h-screen font-aai`,a),style:{background:c.bg},children:(0,z.jsxs)(`div`,{className:`flex flex-col items-center gap-5 border rounded-xl px-10 py-12 sm:px-16 sm:py-14 max-w-105 text-center`,style:{background:c.surface,borderColor:c.border,boxShadow:`0 4px 12px -2px rgb(20 18 12 / 0.08)`},children:[t??(0,z.jsx)(Ns,{size:24}),(0,z.jsx)(Ps,{children:`Voice Agent`}),n&&(0,z.jsx)(`h1`,{className:`font-aai-serif font-normal text-[32px] leading-[1.15] tracking-[-0.2px] m-0 text-balance`,style:{color:c.text},children:n}),r&&(0,z.jsx)(`p`,{className:`text-[15px] leading-[22px] m-0 max-w-75`,style:{color:Os(c.text,c.surface,75)},children:r}),(0,z.jsx)(Ls,{size:`lg`,className:`mt-2`,onClick:s,children:i})]})})}function tv(){let e=0;return{current:()=>e,bump(){e+=1},isCurrent:t=>t===e}}var nv=`modulepreload`,rv=function(e,t){return new URL(e,t).href},iv={},av=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=rv(t,n),t=s(t),t in iv)return;iv[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:nv,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},ov=null;function sv(){return ov??=Promise.all([av(()=>import(`./audio-fO7SVU64.js`),__vite__mapDeps([0,1]),import.meta.url),av(()=>import(`./capture-processor-Dmc-KEpb.js`).then(e=>e.default),__vite__mapDeps([2,1,3]),import.meta.url),av(()=>import(`./playback-processor-DwQ9tE7X.js`).then(e=>e.default),__vite__mapDeps([4,1,3]),import.meta.url)]).catch(e=>{throw ov=null,e}),ov}async function cv(e,t,n){if(e.audioSetupInFlight)return;e.audioSetupInFlight=!0;let r=e.generation.current(),i=()=>!(e.generation.isCurrent(r)&&e.ws)||e.ws.readyState!==1,a=e=>{n.cleanupAudio(),n.updateState({state:`error`,error:{code:`audio`,message:e},running:!1,recording:!1})};try{let[{createVoiceIO:o},s,c]=await sv(),l=await o({sttSampleRate:t.sampleRate,ttsSampleRate:t.ttsSampleRate,captureWorkletSrc:s,playbackWorkletSrc:c,onMicData:e=>{try{n.sendAudio(e)}catch{console.debug(`[aai-ui] sendAudio dropped: connection closed`)}},onPlaybackProgress:t=>{if(e.generation.isCurrent(r))try{n.sendJson({type:`playback_progress`,bufferedMs:t})}catch{}},onError:t=>{e.generation.isCurrent(r)&&a(t.message)}});if(i()){l.close().catch(()=>{});return}if(e.voiceIO?.close().catch(()=>{}),e.voiceIO=l,e.preInitAudio.length>0){for(let t of e.preInitAudio)l.enqueue(t);e.preInitAudio=[]}n.sendJson({type:`audio_ready`}),n.updateState({recording:!0}),e.preInitDone?(e.preInitDone=!1,n.settleWhenAudioDrained(l)):n.updateState({state:`listening`})}catch(e){if(i())return;a(`Microphone access failed: ${oe(e)}`)}finally{e.generation.isCurrent(r)&&(e.audioSetupInFlight=!1)}}(!globalThis.EventTarget||!globalThis.Event)&&console.error(`
|
|
273
273
|
PartySocket requires a global 'EventTarget' class to be available!
|
|
274
274
|
You can polyfill this global by adding this to your code before any partysocket imports:
|
|
275
275
|
|
package/dist/default-client/assets/{playback-processor-DUsmALNH.js → playback-processor-DwQ9tE7X.js}
RENAMED
|
@@ -14,12 +14,10 @@ class PlaybackProcessor extends AudioWorkletProcessor {
|
|
|
14
14
|
// could ever assert on the reported backlog, so the divergence made the
|
|
15
15
|
// number untestable rather than wrong.
|
|
16
16
|
this.rate = rate;
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// never coming.
|
|
20
|
-
this.
|
|
21
|
-
// Fill target after an underrun — see PLAYBACK_REFILL_MS.
|
|
22
|
-
this.refillSamples = Math.floor((rate * (opts.refillMs ?? 200)) / 1000);
|
|
17
|
+
// The one fill target — for the start of a turn and for recovery after an
|
|
18
|
+
// underrun alike. If 'done' arrives first (short utterance), start
|
|
19
|
+
// immediately instead of waiting for audio that is never coming.
|
|
20
|
+
this.fillSamples = Math.floor((rate * (opts.fillMs ?? 200)) / 1000);
|
|
23
21
|
// Concealment source: a ring of the most recently played samples, looped
|
|
24
22
|
// under a decaying gain to cover a gap. Sized to the fade window, with a
|
|
25
23
|
// per-sample decay that reaches the floor exactly at its end.
|
|
@@ -76,7 +74,6 @@ class PlaybackProcessor extends AudioWorkletProcessor {
|
|
|
76
74
|
// pre-roll (nothing to extrapolate from, and not a defect) from a
|
|
77
75
|
// mid-turn underrun.
|
|
78
76
|
this.hasPlayed = false;
|
|
79
|
-
this.fillTarget = this.jitterSamples;
|
|
80
77
|
// Carry-over byte for split samples across chunks
|
|
81
78
|
this.carry = null;
|
|
82
79
|
this.writePos = 0;
|
|
@@ -268,7 +265,7 @@ class PlaybackProcessor extends AudioWorkletProcessor {
|
|
|
268
265
|
// Filling: wait for the target. 'done' short-circuits it — what is
|
|
269
266
|
// buffered is all there will be, so there is nothing left to wait for.
|
|
270
267
|
if (!this.playing) {
|
|
271
|
-
if (avail >= this.
|
|
268
|
+
if (avail >= this.fillSamples || this.isDone) {
|
|
272
269
|
this.playing = true;
|
|
273
270
|
} else {
|
|
274
271
|
this.coverGap(out, 0);
|
|
@@ -277,13 +274,12 @@ class PlaybackProcessor extends AudioWorkletProcessor {
|
|
|
277
274
|
}
|
|
278
275
|
|
|
279
276
|
// Underrun: this quantum cannot be filled and more audio is still coming.
|
|
280
|
-
// Go back to filling
|
|
277
|
+
// Go back to filling and cover the gap, leaving
|
|
281
278
|
// readPos untouched — the fragment stays buffered and plays intact once
|
|
282
279
|
// the buffer recovers, instead of being dribbled out a few samples at a
|
|
283
280
|
// time for the rest of the turn.
|
|
284
281
|
if (avail < out.length && !this.isDone) {
|
|
285
282
|
this.playing = false;
|
|
286
|
-
this.fillTarget = this.refillSamples;
|
|
287
283
|
this.coverGap(out, 0);
|
|
288
284
|
return true;
|
|
289
285
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>aai</title>
|
|
7
7
|
<link rel="icon" href="./favicon.ico" />
|
|
8
8
|
<style>html, body { background: #FBF8F2; margin: 0; }</style>
|
|
9
|
-
<script type="module" crossorigin src="./assets/index-
|
|
9
|
+
<script type="module" crossorigin src="./assets/index-DCI51Xz_.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="./assets/client-audio-constants-Ck0IJO4c.js">
|
|
11
11
|
<link rel="stylesheet" crossorigin href="./assets/index-CDugAuLK.css">
|
|
12
12
|
</head>
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DefaultToolResult } from "@alexkroman1/aai";
|
|
2
2
|
import type { SessionErrorCode } from "@alexkroman1/aai/protocol";
|
|
3
|
-
export { CAPTURE_STOP_ACK_TIMEOUT_MS, MIC_BUFFER_SECONDS, MIC_SEND_MAX_BUFFERED_BYTES, MIC_SILENCE_PROBE_MS, PLAYBACK_BUFFER_SECONDS, PLAYBACK_CONCEAL_FADE_MS, PLAYBACK_CONCEAL_FLOOR, PLAYBACK_DONE_MAX_WAIT_MS, PLAYBACK_DONE_POLL_MS,
|
|
3
|
+
export { CAPTURE_STOP_ACK_TIMEOUT_MS, CLIENT_AUDIO_LEAD_MS, HEARD_AUDIO_LAG_MS, MIC_BUFFER_SECONDS, MIC_SEND_MAX_BUFFERED_BYTES, MIC_SILENCE_PROBE_MS, PACER_BURST_MS, PIPELINE_PLAYBACK_GRACE_MS, PLAYBACK_BUFFER_SECONDS, PLAYBACK_CONCEAL_FADE_MS, PLAYBACK_CONCEAL_FLOOR, PLAYBACK_DONE_MAX_WAIT_MS, PLAYBACK_DONE_POLL_MS, PLAYBACK_FILL_MS, PLAYBACK_PROGRESS_INTERVAL_MS, } from "@alexkroman1/aai/internal";
|
|
4
4
|
/**
|
|
5
5
|
* `getUserMedia` audio constraints for every capture path in this package.
|
|
6
6
|
*
|
package/dist/types.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CAPTURE_STOP_ACK_TIMEOUT_MS, MIC_BUFFER_SECONDS, MIC_SEND_MAX_BUFFERED_BYTES, MIC_SILENCE_PROBE_MS, PLAYBACK_BUFFER_SECONDS, PLAYBACK_CONCEAL_FADE_MS, PLAYBACK_CONCEAL_FLOOR, PLAYBACK_DONE_MAX_WAIT_MS, PLAYBACK_DONE_POLL_MS,
|
|
1
|
+
import { CAPTURE_STOP_ACK_TIMEOUT_MS, CLIENT_AUDIO_LEAD_MS, HEARD_AUDIO_LAG_MS, MIC_BUFFER_SECONDS, MIC_SEND_MAX_BUFFERED_BYTES, MIC_SILENCE_PROBE_MS, PACER_BURST_MS, PIPELINE_PLAYBACK_GRACE_MS, PLAYBACK_BUFFER_SECONDS, PLAYBACK_CONCEAL_FADE_MS, PLAYBACK_CONCEAL_FLOOR, PLAYBACK_DONE_MAX_WAIT_MS, PLAYBACK_DONE_POLL_MS, PLAYBACK_FILL_MS, PLAYBACK_PROGRESS_INTERVAL_MS } from "@alexkroman1/aai/internal";
|
|
2
2
|
//#region types.ts
|
|
3
3
|
/**
|
|
4
4
|
* `getUserMedia` audio constraints for every capture path in this package.
|
|
@@ -32,4 +32,4 @@ const VOICE_CAPTURE_CONSTRAINTS = {
|
|
|
32
32
|
voiceIsolation: false
|
|
33
33
|
};
|
|
34
34
|
//#endregion
|
|
35
|
-
export { CAPTURE_STOP_ACK_TIMEOUT_MS, MIC_BUFFER_SECONDS, MIC_SEND_MAX_BUFFERED_BYTES, MIC_SILENCE_PROBE_MS, PLAYBACK_BUFFER_SECONDS, PLAYBACK_CONCEAL_FADE_MS, PLAYBACK_CONCEAL_FLOOR, PLAYBACK_DONE_MAX_WAIT_MS, PLAYBACK_DONE_POLL_MS,
|
|
35
|
+
export { CAPTURE_STOP_ACK_TIMEOUT_MS, CLIENT_AUDIO_LEAD_MS, HEARD_AUDIO_LAG_MS, MIC_BUFFER_SECONDS, MIC_SEND_MAX_BUFFERED_BYTES, MIC_SILENCE_PROBE_MS, PACER_BURST_MS, PIPELINE_PLAYBACK_GRACE_MS, PLAYBACK_BUFFER_SECONDS, PLAYBACK_CONCEAL_FADE_MS, PLAYBACK_CONCEAL_FLOOR, PLAYBACK_DONE_MAX_WAIT_MS, PLAYBACK_DONE_POLL_MS, PLAYBACK_FILL_MS, PLAYBACK_PROGRESS_INTERVAL_MS, VOICE_CAPTURE_CONSTRAINTS };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A bench for the playback jitter buffer: real TTS, the real worklet, a
|
|
3
|
+
* deterministic clock, and one setting changed at a time.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists. `PLAYBACK_JITTER_MS` and `PLAYBACK_REFILL_MS` are the two
|
|
6
|
+
* numbers that decide whether a reply is heard as speech or as stutter, and
|
|
7
|
+
* every test that touches them supplies its own arrival pattern:
|
|
8
|
+
* `playback-processor.test.ts` hand-feeds quanta, and `audio-stress.test.ts`
|
|
9
|
+
* records in its own header that its chunk sizes outrun the render loop by an
|
|
10
|
+
* order of magnitude so the buffer "effectively never starves". Neither can
|
|
11
|
+
* answer "is 400 the right number", because neither has ever seen how audio
|
|
12
|
+
* actually arrives.
|
|
13
|
+
*
|
|
14
|
+
* So the bench replays a RECORDED reply (`_tts-trace-harness.ts`) through the
|
|
15
|
+
* chain the audio really crosses:
|
|
16
|
+
*
|
|
17
|
+
* provider frames (recorded arrival times)
|
|
18
|
+
* -> the server's bounded-lead pacer <- MODELLED, see below
|
|
19
|
+
* -> a network profile (latency, jitter, stalls)
|
|
20
|
+
* -> the real playback worklet <- `playbackProcessorSource`
|
|
21
|
+
* -> rendered PCM + the worklet's own stats
|
|
22
|
+
*
|
|
23
|
+
* Everything is virtual-time: the clock advances one 128-sample render quantum
|
|
24
|
+
* per iteration, so a sweep of a hundred settings over a 30-second reply costs
|
|
25
|
+
* seconds and gives byte-identical results every run. {@link renderToWav} is
|
|
26
|
+
* the other half — the rendered output written where a human can listen to it,
|
|
27
|
+
* because a concealment counter does not tell you whether a reply sounds
|
|
28
|
+
* broken.
|
|
29
|
+
*
|
|
30
|
+
* **The pacer is a MODEL, and that is this bench's one fidelity gap.**
|
|
31
|
+
* `createAudioPacer` (`aai/host/audio-pacer.ts`) is not on any published
|
|
32
|
+
* subpath and this package may not import a sibling's internals, so
|
|
33
|
+
* {@link pacedSends} is a transcription of its algorithm rather than the
|
|
34
|
+
* algorithm. It is ~20 lines and reproduces the two properties the pacer's own
|
|
35
|
+
* doc states — free flow until the lead ceiling, then one release per
|
|
36
|
+
* `burstMs` — but a change to the real pacer will NOT fail this file. If the
|
|
37
|
+
* pacer moves, re-read it against `pacedSends`. Exporting the real one and
|
|
38
|
+
* deleting the model is the fix; it needs a non-test change.
|
|
39
|
+
*/
|
|
40
|
+
import { type TtsTrace } from "./_tts-trace-harness.ts";
|
|
41
|
+
/** The render quantum every AudioWorkletProcessor is called with. */
|
|
42
|
+
export declare const QUANTUM = 128;
|
|
43
|
+
/** Sampling interval of {@link RenderResult.earMs}. */
|
|
44
|
+
export declare const EAR_SAMPLE_MS = 20;
|
|
45
|
+
/** One frame arriving at the client: when it lands, and what it carries. */
|
|
46
|
+
export type Delivery = {
|
|
47
|
+
atMs: number;
|
|
48
|
+
bytes: Uint8Array;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* How the server releases audio. Production values are
|
|
52
|
+
* `CLIENT_AUDIO_LEAD_MS` (1000) and `PACER_BURST_MS` (200); they are
|
|
53
|
+
* parameters here because they are half of what the bench is sweeping — the
|
|
54
|
+
* client's cushion is the server's lead, so tuning the jitter buffer without
|
|
55
|
+
* them is tuning one end of one number.
|
|
56
|
+
*/
|
|
57
|
+
export type PacerProfile = {
|
|
58
|
+
leadMs: number;
|
|
59
|
+
burstMs: number;
|
|
60
|
+
};
|
|
61
|
+
/** What the link does to a frame between the server and the ear. */
|
|
62
|
+
export type NetworkProfile = {
|
|
63
|
+
name: string;
|
|
64
|
+
/** One-way latency floor, ms. */
|
|
65
|
+
latencyMs: number;
|
|
66
|
+
/** Peak extra delay added on top, ms (0 = a perfectly even link). */
|
|
67
|
+
jitterMs: number;
|
|
68
|
+
/**
|
|
69
|
+
* Freezes: `{ atMs, forMs }` pairs during which nothing is delivered, every
|
|
70
|
+
* held frame landing at the end. This is what a jitter buffer is FOR, and
|
|
71
|
+
* the only part of a profile that has to be scripted rather than sampled —
|
|
72
|
+
* a stall is an event, not a distribution.
|
|
73
|
+
*/
|
|
74
|
+
stalls?: {
|
|
75
|
+
atMs: number;
|
|
76
|
+
forMs: number;
|
|
77
|
+
}[];
|
|
78
|
+
/**
|
|
79
|
+
* Throughput ceiling in bits/s, or `Infinity`. Both legs are uncompressed
|
|
80
|
+
* PCM16 (384 kbps down at 24 kHz), so a link that cannot carry the bitrate
|
|
81
|
+
* cannot be fixed by any buffer — that claim is in the aai-ui guide and this
|
|
82
|
+
* is what makes it measurable.
|
|
83
|
+
*/
|
|
84
|
+
bitsPerSecond?: number;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* The worklet's one knob. It was two — a startup target and a refill target —
|
|
88
|
+
* until this bench showed the startup one was redundant by construction; see
|
|
89
|
+
* `PLAYBACK_FILL_MS`.
|
|
90
|
+
*/
|
|
91
|
+
export type PlaybackSettings = {
|
|
92
|
+
fillMs: number;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Model the server's bounded-lead pacer over a trace's frames.
|
|
96
|
+
*
|
|
97
|
+
* A transcription of `createAudioPacer`, whose loop is: a frame is sent
|
|
98
|
+
* immediately while the lead (everything sent so far, minus now) is under the
|
|
99
|
+
* ceiling; otherwise it waits until the lead has drained `burstMs` below the
|
|
100
|
+
* ceiling and then goes out with everything else the drain releases.
|
|
101
|
+
*/
|
|
102
|
+
export declare function pacedSends(trace: TtsTrace, pacer: PacerProfile): Delivery[];
|
|
103
|
+
/** Apply a network profile to the server's send schedule. */
|
|
104
|
+
export declare function overNetwork(sends: Delivery[], net: NetworkProfile): Delivery[];
|
|
105
|
+
/** Everything one render of one setting produced. */
|
|
106
|
+
export type RenderResult = {
|
|
107
|
+
/** The audio the ear actually received, at the context sample rate. */
|
|
108
|
+
rendered: Float32Array;
|
|
109
|
+
sampleRate: number;
|
|
110
|
+
/** Ms from the first frame LEAVING the provider to the first audible sample. */
|
|
111
|
+
timeToFirstAudioMs: number;
|
|
112
|
+
/** The worklet's own report, as `onPlaybackStats` would receive it. */
|
|
113
|
+
stats: {
|
|
114
|
+
concealedSamples: number;
|
|
115
|
+
silentConcealedSamples: number;
|
|
116
|
+
concealmentEvents: number;
|
|
117
|
+
silentConcealmentEvents: number;
|
|
118
|
+
};
|
|
119
|
+
/** Every concealment episode's length in ms, in order. */
|
|
120
|
+
gapsMs: number[];
|
|
121
|
+
/** `bufferedMs` values the worklet reported, as `playback_progress` would. */
|
|
122
|
+
progressMs: number[];
|
|
123
|
+
/**
|
|
124
|
+
* Ground truth for the heard cursor: cumulative ms of the REPLY's own audio
|
|
125
|
+
* the ear had received, sampled every {@link EAR_SAMPLE_MS}.
|
|
126
|
+
*
|
|
127
|
+
* Concealed samples are excluded — they are fabricated, not reply audio — so
|
|
128
|
+
* this is exactly the quantity `heardMs()` in
|
|
129
|
+
* `aai/host/transports/pipeline-heard.ts` estimates, which is what makes the
|
|
130
|
+
* two comparable.
|
|
131
|
+
*/
|
|
132
|
+
earMs: number[];
|
|
133
|
+
/** How long the reply took to play out, first sample to last. */
|
|
134
|
+
playedMs: number;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Render one delivery schedule through the real playback worklet.
|
|
138
|
+
*
|
|
139
|
+
* The clock is the render loop itself: quantum `q` happens at
|
|
140
|
+
* `q * QUANTUM / sampleRate` seconds, every delivery at or before that instant
|
|
141
|
+
* is written first, and `done` is posted once the last frame has landed. That
|
|
142
|
+
* is the ordering the audio thread really sees (`onmessage` and `process()`
|
|
143
|
+
* never interleave), and it makes the whole render a pure function of the
|
|
144
|
+
* schedule.
|
|
145
|
+
*/
|
|
146
|
+
export declare function renderSchedule(deliveries: Delivery[], opts: {
|
|
147
|
+
sampleRate: number;
|
|
148
|
+
settings: PlaybackSettings;
|
|
149
|
+
maxSeconds?: number;
|
|
150
|
+
}): RenderResult;
|
|
151
|
+
/** One end-to-end run: trace + pacer + network + settings. */
|
|
152
|
+
export declare function runBench(opts: {
|
|
153
|
+
trace: TtsTrace;
|
|
154
|
+
pacer: PacerProfile;
|
|
155
|
+
net: NetworkProfile;
|
|
156
|
+
settings: PlaybackSettings;
|
|
157
|
+
}): RenderResult;
|
|
158
|
+
/**
|
|
159
|
+
* Score one render. Lower is better, and the WEIGHTS are the opinion in this
|
|
160
|
+
* file — everything above is measurement.
|
|
161
|
+
*
|
|
162
|
+
* The three terms are not interchangeable:
|
|
163
|
+
*
|
|
164
|
+
* - **Startup latency** is paid on every single turn, so it is the term that
|
|
165
|
+
* compounds over a conversation.
|
|
166
|
+
* - **Silent concealment** is an audible hole. Concealment that stays under
|
|
167
|
+
* the fade is a smear the ear largely forgives, which is why the worklet
|
|
168
|
+
* reports the two separately, so silence is weighted an order of magnitude
|
|
169
|
+
* higher than concealment in general.
|
|
170
|
+
* - **Episode COUNT** matters independently of total length: one 300 ms pause
|
|
171
|
+
* reads as a network hiccup, where six 50 ms ones read as a broken codec.
|
|
172
|
+
* That is the whole finding behind the refill re-arm, so a score that
|
|
173
|
+
* summed milliseconds alone would rank the failure it was built to prevent
|
|
174
|
+
* as equal to the fix.
|
|
175
|
+
*/
|
|
176
|
+
export declare function scoreRender(r: RenderResult): {
|
|
177
|
+
score: number;
|
|
178
|
+
parts: Record<string, number>;
|
|
179
|
+
};
|
|
180
|
+
/** Minimal 16-bit PCM WAV, so a render can be listened to. */
|
|
181
|
+
export declare function toWav(samples: Float32Array, sampleRate: number): Buffer;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bench's HOST-side half: what the server believes about the caller's
|
|
3
|
+
* playback, against what the caller actually heard.
|
|
4
|
+
*
|
|
5
|
+
* Split from `_playback-bench-harness.ts` at the seam it already had — that file
|
|
6
|
+
* models the wire and drives the worklet, and this one models
|
|
7
|
+
* `aai/host/transports/pipeline-heard.ts`'s arithmetic over the result. It went
|
|
8
|
+
* out when the file hit the 500-line cap, which is the right time rather than a
|
|
9
|
+
* line later.
|
|
10
|
+
*
|
|
11
|
+
* Everything here is a TRANSCRIPTION of host code this package may not import
|
|
12
|
+
* (`aai`'s `host/` is Node-only and internal), so it carries the same warning the
|
|
13
|
+
* pacer model does: a change to `createPlaybackClock` will not fail this file.
|
|
14
|
+
* Re-read it against these functions if that clock moves.
|
|
15
|
+
*/
|
|
16
|
+
import type { Delivery, RenderResult } from "./_playback-bench-harness.ts";
|
|
17
|
+
/**
|
|
18
|
+
* What the HOST believes about playback, and what actually happened.
|
|
19
|
+
*
|
|
20
|
+
* The barge-in floor is `pending()` in `aai/host/transports/pipeline-heard.ts`:
|
|
21
|
+
* `now() < endsAtMs + PIPELINE_PLAYBACK_GRACE_MS`, where `endsAtMs` accumulates
|
|
22
|
+
* each forwarded chunk's duration from `max(endsAtMs, now())` — an OPEN-LOOP
|
|
23
|
+
* model that assumes playback starts the instant a chunk is forwarded and runs at
|
|
24
|
+
* exactly 1.0x. No real client beats that, so the model is a lower bound and the
|
|
25
|
+
* grace is what covers the difference.
|
|
26
|
+
*
|
|
27
|
+
* This measures the difference. `requiredGraceMs` is how long after the host's
|
|
28
|
+
* estimate the caller was still hearing audio — i.e. the smallest grace that
|
|
29
|
+
* keeps barge-in working to the end of a reply. It is a REQUIREMENT, so a grace
|
|
30
|
+
* at or above it is correct and one below it means a barge-in in the reply's tail
|
|
31
|
+
* is not recognised as arriving during playback.
|
|
32
|
+
*
|
|
33
|
+
* Two clients are modelled because the host serves both: one that wires
|
|
34
|
+
* `onPlaybackProgress` (the browser does) and one that does not (a telephony
|
|
35
|
+
* bridge, a harness). The reports clamp `endsAtMs` UPWARD only, so a reporting
|
|
36
|
+
* client shrinks the requirement and a silent one leaves the host on the
|
|
37
|
+
* open-loop estimate — which is the case the constant has to be safe for.
|
|
38
|
+
*/
|
|
39
|
+
export type PlayoutVsHost = {
|
|
40
|
+
/** When the host's open-loop model thinks forwarded audio finishes playing. */
|
|
41
|
+
openLoopEndMs: number;
|
|
42
|
+
/** The same, with the client's `playback_progress` reports clamped in. */
|
|
43
|
+
reportedEndMs: number;
|
|
44
|
+
/** When the caller actually stopped hearing audio. */
|
|
45
|
+
realEndMs: number;
|
|
46
|
+
/** Smallest grace that keeps `pending()` true to `realEndMs`, unreported. */
|
|
47
|
+
requiredGraceMs: number;
|
|
48
|
+
/** The same for a client that DOES report its backlog. */
|
|
49
|
+
requiredGraceReportingMs: number;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Replay a render against the host's own playback-clock arithmetic.
|
|
53
|
+
*
|
|
54
|
+
* `deliveries` is what the host FORWARDED (the pacer's output, before the link),
|
|
55
|
+
* because that is what `onChunk` sees — the host has no view of the wire.
|
|
56
|
+
*/
|
|
57
|
+
export declare function playoutVsHost(opts: {
|
|
58
|
+
forwarded: Delivery[];
|
|
59
|
+
render: RenderResult;
|
|
60
|
+
sampleRate: number;
|
|
61
|
+
/** Interval of the client's backlog reports; production is 500 ms. */
|
|
62
|
+
reportIntervalMs: number;
|
|
63
|
+
}): PlayoutVsHost;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser half of the playback bench: a page that plays a recorded reply
|
|
3
|
+
* through the REAL playback worklet in a REAL `AudioContext`, so the tuning can
|
|
4
|
+
* be listened to rather than only counted.
|
|
5
|
+
*
|
|
6
|
+
* This is the part the offline renderer cannot be: `renderSchedule` drives
|
|
7
|
+
* `process()` on a synthetic sample clock with `postMessage` delivered exactly
|
|
8
|
+
* between quanta, which is the ordering the audio thread guarantees but says
|
|
9
|
+
* nothing about the browser's own render cadence, its output latency, or what
|
|
10
|
+
* a `port.postMessage` burst does to a live audio callback. If the two agree,
|
|
11
|
+
* the offline sweep is trustworthy; where they disagree, the browser is right.
|
|
12
|
+
*
|
|
13
|
+
* Two ways in, same page:
|
|
14
|
+
*
|
|
15
|
+
* - **A human opens it** and gets a slider for `fillMs`, the
|
|
16
|
+
* network profile, and the pacer lead, with the worklet's own concealment
|
|
17
|
+
* counters live. Audio comes out of the speakers.
|
|
18
|
+
* - **Playwright drives it** through {@link BENCH_API} on `window`, and pulls
|
|
19
|
+
* back the samples the tap captured — the audio that actually reached the
|
|
20
|
+
* destination — so a test can diff it against the offline render.
|
|
21
|
+
*
|
|
22
|
+
* The page is a STRING rather than a file under a bundler because it must stay
|
|
23
|
+
* test-only: aai-ui's build (`tsdown` + `build-default-client.ts`) ships
|
|
24
|
+
* `dist/default-client/`, and a second HTML entry there is a product artifact
|
|
25
|
+
* with a coverage floor and a size budget. Generated into `reports/playback/`,
|
|
26
|
+
* it is neither.
|
|
27
|
+
*/
|
|
28
|
+
/** The name of the object the page exposes for a driver to call. */
|
|
29
|
+
export declare const BENCH_API = "__aaiPlaybackBench";
|
|
30
|
+
/** Options a driver or a human can set on one run. */
|
|
31
|
+
export type BenchRunOptions = {
|
|
32
|
+
fillMs: number;
|
|
33
|
+
/** Deliveries: ms since run start, and the slice of the PCM to write. */
|
|
34
|
+
schedule: {
|
|
35
|
+
atMs: number;
|
|
36
|
+
offset: number;
|
|
37
|
+
length: number;
|
|
38
|
+
}[];
|
|
39
|
+
/** Whether the reply is audible. A sweep run in a headless browser is not. */
|
|
40
|
+
muted?: boolean;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Build the bench page.
|
|
44
|
+
*
|
|
45
|
+
* `pcmUrl` is fetched once and sliced per delivery, so the page replays the
|
|
46
|
+
* same bytes the offline renderer does. `sampleRate` must be the trace's: the
|
|
47
|
+
* context is created at it and the page REFUSES to run if the browser grants
|
|
48
|
+
* another, exactly as `audio.ts` does — PCM written into a context at the wrong
|
|
49
|
+
* rate plays at the wrong speed, which would look like a tuning result.
|
|
50
|
+
*/
|
|
51
|
+
export declare function benchPageHtml(opts: {
|
|
52
|
+
pcmUrl: string;
|
|
53
|
+
sampleRate: number;
|
|
54
|
+
/** Shown in the header, so a saved page names what it is playing. */
|
|
55
|
+
title: string;
|
|
56
|
+
/** Default schedules a human can pick between, by profile name. */
|
|
57
|
+
profiles: Record<string, {
|
|
58
|
+
atMs: number;
|
|
59
|
+
offset: number;
|
|
60
|
+
length: number;
|
|
61
|
+
}[]>;
|
|
62
|
+
defaults?: {
|
|
63
|
+
fillMs: number;
|
|
64
|
+
};
|
|
65
|
+
}): string;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture and replay of REAL TTS arrival timing.
|
|
3
|
+
*
|
|
4
|
+
* The playback worklet's tuning question — how deep should
|
|
5
|
+
* `PLAYBACK_JITTER_MS` be — is entirely a question about how unevenly audio
|
|
6
|
+
* arrives, and every existing test answers it with a generated arrival pattern.
|
|
7
|
+
* `audio-stress.test.ts` says so itself: its chunk-size arbitrary averages
|
|
8
|
+
* ~750 samples against 128 consumed per render, so writes outrun renders by an
|
|
9
|
+
* order of magnitude and the buffer effectively never starves. A jitter buffer
|
|
10
|
+
* tuned against that is tuned against nothing.
|
|
11
|
+
*
|
|
12
|
+
* So a trace is a recording of one real reply as the provider actually emitted
|
|
13
|
+
* it: PCM16 bytes plus the millisecond each frame ARRIVED, relative to the
|
|
14
|
+
* first. Replayed through the real pacer and the real worklet, that makes the
|
|
15
|
+
* tuning question measurable and repeatable — the same reply, the same
|
|
16
|
+
* arrival pattern, one setting changed.
|
|
17
|
+
*
|
|
18
|
+
* Two halves, deliberately separate:
|
|
19
|
+
*
|
|
20
|
+
* - {@link captureTtsTrace} needs a live provider and an API key. It runs once,
|
|
21
|
+
* by hand (`AAI_CAPTURE_TTS_TRACE=1`), and commits its result.
|
|
22
|
+
* - {@link readTtsTrace} needs neither, so every test that CONSUMES a trace is
|
|
23
|
+
* keyless and offline.
|
|
24
|
+
*
|
|
25
|
+
* The bytes live beside the JSON rather than inside it: base64 in a fixture
|
|
26
|
+
* inflates ~1 MB of PCM by a third and makes the file unreadable in a diff,
|
|
27
|
+
* where a `.pcm` sidecar is `ffplay`-able and reviewed by its length alone.
|
|
28
|
+
*/
|
|
29
|
+
/** One provider audio frame, with the moment it arrived. */
|
|
30
|
+
export type TraceFrame = {
|
|
31
|
+
/** Arrival time in ms, relative to the first frame of the reply. */
|
|
32
|
+
tMs: number;
|
|
33
|
+
/** Byte offset of this frame's PCM16 in the sidecar. */
|
|
34
|
+
offset: number;
|
|
35
|
+
/** Byte length of this frame's PCM16. */
|
|
36
|
+
length: number;
|
|
37
|
+
};
|
|
38
|
+
/** A recorded reply: what the provider sent, and when. */
|
|
39
|
+
export type TtsTrace = {
|
|
40
|
+
/** Sample rate of the PCM16 in the sidecar. */
|
|
41
|
+
sampleRate: number;
|
|
42
|
+
/** Provider kind and voice, so a trace names the thing it recorded. */
|
|
43
|
+
provider: string;
|
|
44
|
+
voice: string;
|
|
45
|
+
/** The reply text that was synthesized. */
|
|
46
|
+
text: string;
|
|
47
|
+
/** Ms from the first `sendText` to the first audio frame. */
|
|
48
|
+
firstAudioMs: number;
|
|
49
|
+
/** Ms from the first `sendText` to `done`. */
|
|
50
|
+
doneMs: number;
|
|
51
|
+
frames: TraceFrame[];
|
|
52
|
+
/** All frames' PCM16, concatenated in arrival order. */
|
|
53
|
+
pcm: Int16Array;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Total audio duration in ms. Not the same as {@link TtsTrace.doneMs}: a
|
|
57
|
+
* provider that synthesizes faster than real time produces more audio than the
|
|
58
|
+
* wall clock it took to produce it, and the RATIO of the two is the whole
|
|
59
|
+
* reason a jitter buffer can ever fill.
|
|
60
|
+
*/
|
|
61
|
+
export declare function traceAudioMs(trace: TtsTrace): number;
|
|
62
|
+
/**
|
|
63
|
+
* The slice of the SDK's `TtsSession` a capture drives. Declared structurally
|
|
64
|
+
* so the harness needs no host-only type import: a real session satisfies it.
|
|
65
|
+
*/
|
|
66
|
+
export type CapturableTtsSession = {
|
|
67
|
+
sendText(text: string): void;
|
|
68
|
+
flush(): void;
|
|
69
|
+
on(event: "audio", fn: (pcm: Int16Array) => void): unknown;
|
|
70
|
+
on(event: "done", fn: () => void): unknown;
|
|
71
|
+
on(event: "error", fn: (err: {
|
|
72
|
+
message?: string;
|
|
73
|
+
}) => void): unknown;
|
|
74
|
+
close(): Promise<void>;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Open a real TTS session, synthesize `text`, and record every frame's arrival.
|
|
78
|
+
*
|
|
79
|
+
* The text is sent the way the PIPELINE sends it — as deltas, with a final
|
|
80
|
+
* `flush()` — because the AssemblyAI adapter segments on what it receives and
|
|
81
|
+
* `Generate` only buffers until a `Flush`. Handing it the whole reply in one
|
|
82
|
+
* call would record a different arrival pattern from the one a real turn
|
|
83
|
+
* produces (see `providers/tts/assemblyai-segment.ts`).
|
|
84
|
+
*/
|
|
85
|
+
export declare function captureTtsTrace(opts: {
|
|
86
|
+
text: string;
|
|
87
|
+
/**
|
|
88
|
+
* Opens the real provider session. INJECTED rather than resolved here: the
|
|
89
|
+
* resolver (`aai`'s `host/providers/resolve.ts`) is not on any published
|
|
90
|
+
* subpath, and this package may not import a sibling's internals. The caller
|
|
91
|
+
* that has one is the capture runner, which is not part of the package.
|
|
92
|
+
*/
|
|
93
|
+
open: (o: {
|
|
94
|
+
sampleRate: number;
|
|
95
|
+
signal: AbortSignal;
|
|
96
|
+
}) => Promise<CapturableTtsSession>;
|
|
97
|
+
/** LLM-shaped deltas. Defaults to splitting `text` on word boundaries. */
|
|
98
|
+
deltas?: string[];
|
|
99
|
+
/** Recorded into the trace so a fixture names the voice it captured. */
|
|
100
|
+
voice?: string;
|
|
101
|
+
provider?: string;
|
|
102
|
+
sampleRate?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Gap between deltas, in ms. **Set this to represent a real turn.**
|
|
105
|
+
*
|
|
106
|
+
* Sending every delta in one burst is what a capture does by default, and for
|
|
107
|
+
* the PLAYBACK question that is harmless — the pacer reshapes arrival anyway.
|
|
108
|
+
* For anything about SEGMENTATION it invalidates the measurement outright: the
|
|
109
|
+
* segmenter is handed the whole reply before it makes its first cut, so
|
|
110
|
+
* time-to-first-audio collapses to the service's own latency (~40 ms measured)
|
|
111
|
+
* and every segmentation rule scores the same. An LLM streams at ~30 ms a
|
|
112
|
+
* delta, which is what the segmenter really sees.
|
|
113
|
+
*/
|
|
114
|
+
deltaIntervalMs?: number;
|
|
115
|
+
/** Hard cap, so a provider that never sends `done` cannot hang the capture. */
|
|
116
|
+
timeoutMs?: number;
|
|
117
|
+
}): Promise<TtsTrace>;
|
|
118
|
+
/**
|
|
119
|
+
* Split a reply into LLM-shaped deltas: a few words at a time, which is what
|
|
120
|
+
* `streamText` emits and therefore what the adapter's segmenter sees.
|
|
121
|
+
*/
|
|
122
|
+
export declare function splitIntoDeltas(text: string, wordsPer?: number): string[];
|
|
123
|
+
/** Where a trace's two files live, given its directory and name. */
|
|
124
|
+
export declare function tracePaths(dir: string, name: string): {
|
|
125
|
+
index: string;
|
|
126
|
+
pcm: string;
|
|
127
|
+
};
|
|
128
|
+
export declare function writeTtsTrace(dir: string, name: string, trace: TtsTrace): Promise<void>;
|
|
129
|
+
/** Whether both halves of a trace are present, for a `skipIf` that announces. */
|
|
130
|
+
export declare function hasTtsTrace(dir: string, name: string): boolean;
|
|
131
|
+
export declare function readTtsTrace(dir: string, name: string): Promise<TtsTrace>;
|
|
132
|
+
/**
|
|
133
|
+
* The same read, synchronously.
|
|
134
|
+
*
|
|
135
|
+
* A `describe` body may not `await` — vitest collects it synchronously — so a
|
|
136
|
+
* suite whose every case shares one trace has no other way to load it once.
|
|
137
|
+
* Reading it per `test` instead would decode 375 KiB of PCM ten times over for
|
|
138
|
+
* a value that is immutable.
|
|
139
|
+
*/
|
|
140
|
+
export declare function readTtsTraceSync(dir: string, name: string): TtsTrace;
|
|
141
|
+
/** The PCM16 bytes of one frame, as the wire would carry them. */
|
|
142
|
+
export declare function frameBytes(trace: TtsTrace, frame: TraceFrame): Uint8Array;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test harness for AudioWorklet processor sources.
|
|
3
|
+
*
|
|
4
|
+
* The worklets ship as source strings (compiled to Blob URLs for the real
|
|
5
|
+
* AudioWorklet). This harness evaluates a source string with stubbed
|
|
6
|
+
* AudioWorkletGlobalScope globals so the processor's runtime behavior
|
|
7
|
+
* (batching, resampling, ring buffer) can be exercised directly in unit tests.
|
|
8
|
+
*/
|
|
9
|
+
export type WorkletPort = {
|
|
10
|
+
onmessage: ((e: {
|
|
11
|
+
data: unknown;
|
|
12
|
+
}) => void) | null;
|
|
13
|
+
postMessage(data: unknown, transfer?: unknown[]): void;
|
|
14
|
+
};
|
|
15
|
+
export type WorkletInstance = {
|
|
16
|
+
port: WorkletPort;
|
|
17
|
+
process(inputs: Float32Array[][], outputs: Float32Array[][]): boolean;
|
|
18
|
+
};
|
|
19
|
+
export type WorkletHarness = {
|
|
20
|
+
instance: WorkletInstance;
|
|
21
|
+
/** Messages the processor posted to the main thread, in order. */
|
|
22
|
+
posted: unknown[];
|
|
23
|
+
/** Deliver a message from the main thread to the processor. */
|
|
24
|
+
sendMessage(data: unknown): void;
|
|
25
|
+
};
|
|
26
|
+
/** Evaluate a worklet source string and instantiate its registered processor. */
|
|
27
|
+
export declare function instantiateWorklet(source: string, processorOptions?: Record<string, unknown>, contextSampleRate?: number): WorkletHarness;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Raw worklet source — exported so tests can evaluate the processor directly. */
|
|
2
|
-
export declare const playbackProcessorSource = "\nclass PlaybackProcessor extends AudioWorkletProcessor {\n constructor(options) {\n super();\n const opts = options.processorOptions || {};\n // The node's context IS the playback context (audio.ts asserts its rate),\n // so the worklet-global sampleRate is authoritative; the option exists\n // for the node-less test harness.\n const rate = opts.sampleRate ?? sampleRate;\n // Kept, because every derived quantity below reads it and `bufferedMs` in\n // process() is the one that used to read the worklet global instead. Those\n // agree in production (the option is unset, so `rate` IS `sampleRate`) and\n // diverge only in the node-less harness \u2014 which is the one place a spec\n // could ever assert on the reported backlog, so the divergence made the\n // number untestable rather than wrong.\n this.rate = rate;\n // Fill target for the start of a turn. If 'done' arrives first (short\n // utterance), start immediately instead of waiting for audio that is\n // never coming.\n this.jitterSamples = Math.floor((rate * (opts.jitterMs ?? 400)) / 1000);\n // Fill target after an underrun \u2014 see PLAYBACK_REFILL_MS.\n this.refillSamples = Math.floor((rate * (opts.refillMs ?? 200)) / 1000);\n // Concealment source: a ring of the most recently played samples, looped\n // under a decaying gain to cover a gap. Sized to the fade window, with a\n // per-sample decay that reaches the floor exactly at its end.\n this.concealCapacity = Math.max(1, Math.floor((rate * 40) / 1000));\n this.concealBuf = new Float32Array(this.concealCapacity);\n this.concealDecay = Math.exp(Math.log(0.001) / this.concealCapacity);\n // Float32 ring buffer \u2014 PLAYBACK_BUFFER_SECONDS at the context sample\n // rate. Allocated once for the node's lifetime; per-turn state resets via\n // resetTurn(). writePos and readPos are absolute (monotonic) sample\n // counts; the buffer is indexed modulo capacity so a longer reply keeps\n // playing instead of writing past the end and going silent.\n this.capacity = rate * 60;\n this.samples = new Float32Array(this.capacity);\n // Cadence for the 'progress' report in process(), counted in samples so\n // the hot path never reads a clock. Survives resetTurn(): the host clamps\n // upward only, so a stale count costs at most one extra report.\n this.reportIntervalSamples = Math.floor((rate * 500) / 1000);\n this.sinceReportSamples = 0;\n // Platform endianness probe: the wire format is PCM16 little-endian, so\n // the Int16Array fast path in ingestBytes is only valid on LE hosts\n // (every shipping browser target; the DataView path is the fallback).\n this.littleEndian = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;\n this.resetTurn();\n\n this.port.onmessage = (e) => {\n const d = e.data;\n if (d.event === 'write') {\n this.ingestBytes(d.buffer);\n } else if (d.event === 'interrupt') {\n // Applied eagerly, not deferred to the next process(): onmessage and\n // process() never interleave (one audio thread), and a deferred\n // interrupt would let 'write'/'done' frames for the NEXT turn\n // coalesce in ahead of it and be wiped by the reset along with the\n // cancelled turn's audio.\n this.stopTurn('interrupt');\n } else if (d.event === 'done') {\n this.isDone = true;\n // Echoed back on this turn's 'stop' so the host can tell WHICH turn\n // drained: a stop already in flight when a barge-in lands would\n // otherwise settle the next turn's done() the moment it arrives.\n this.doneTurn = d.turn ?? null;\n }\n };\n }\n\n // Reset per-turn state so the node is reusable across replies without\n // reallocating the sample buffer or re-instantiating the worklet.\n resetTurn() {\n this.isDone = false;\n // Host turn id carried by this turn's 'done' (null until one arrives).\n this.doneTurn = null;\n this.playing = false;\n // Whether any real audio has been rendered this turn. Separates a turn's\n // pre-roll (nothing to extrapolate from, and not a defect) from a\n // mid-turn underrun.\n this.hasPlayed = false;\n this.fillTarget = this.jitterSamples;\n // Carry-over byte for split samples across chunks\n this.carry = null;\n this.writePos = 0;\n this.readPos = 0;\n // Concealment ring state and the current fade position.\n this.concealLen = 0;\n this.concealWrite = 0;\n this.concealPos = 0;\n this.concealGain = 1;\n // Episode flags, so a multi-quantum gap counts as one event.\n this.concealing = false;\n this.concealedSilence = false;\n // Reported to the host on 'stop'. A fresh object per turn: the one just\n // posted must not be mutated by the next turn.\n this.stats = {\n concealedSamples: 0,\n silentConcealedSamples: 0,\n concealmentEvents: 0,\n silentConcealmentEvents: 0,\n };\n }\n\n // End the current turn: notify the host and rearm for the next reply.\n // Must NOT return false from process() \u2014 a processor that stops is dead\n // for good, forcing a new node (and buffer) per reply.\n // `reason` ('interrupt' | 'done') tells the host which turn boundary this\n // stop belongs to \u2014 interrupt-stops are dropped host-side, flush() having\n // already settled that turn \u2014 and `turn` names WHICH turn drained (the id\n // this turn's 'done' carried), so a drain-stop still in flight when a\n // barge-in lands cannot settle the next turn's done() early.\n stopTurn(reason) {\n this.port.postMessage({ event: 'stop', reason, turn: this.doneTurn, stats: this.stats });\n this.resetTurn();\n }\n\n // Cover a quantum (from `start`) where real audio should have been.\n //\n // Before the turn's first samples there is nothing to extrapolate from, so\n // the gap is plain silence and counted as nothing \u2014 WebRTC likewise only\n // counts concealment once playout has begun. After that, loop the retained\n // tail under a decaying gain: a hard zero-fill is a discontinuity mid-word,\n // which is the click that makes a brief stall sound like breakage.\n coverGap(out, start) {\n if (!this.hasPlayed) {\n out.fill(0, start);\n return;\n }\n if (!this.concealing) {\n this.concealing = true;\n this.concealedSilence = false;\n this.stats.concealmentEvents++;\n }\n const total = out.length - start;\n const len = this.concealLen;\n let silent = 0;\n // Second condition: once the fade has decayed to the floor, every sample\n // the loop would emit is 0 anyway \u2014 bulk-fill instead of running 128\n // branchy iterations per quantum for the whole tail of a long stall.\n if (len === 0 || this.concealGain < 0.001) {\n out.fill(0, start);\n silent = total;\n } else {\n let g = this.concealGain;\n for (let i = start; i < out.length; i++) {\n if (g < 0.001) {\n // The fade has run out: keep counting the gap, but stop looping a\n // fragment that is now inaudible anyway.\n out[i] = 0;\n silent++;\n continue;\n }\n out[i] = this.concealBuf[this.concealPos] * g;\n this.concealPos = this.concealPos + 1 === len ? 0 : this.concealPos + 1;\n g *= this.concealDecay;\n }\n this.concealGain = g;\n }\n this.stats.concealedSamples += total;\n if (silent > 0) {\n this.stats.silentConcealedSamples += silent;\n if (!this.concealedSilence) {\n this.concealedSilence = true;\n this.stats.silentConcealmentEvents++;\n }\n }\n }\n\n // Retain the tail of a rendered quantum as the next gap's concealment\n // source, and close any episode the real audio just ended.\n rememberTail(out, n) {\n const cap = this.concealCapacity;\n const take = Math.min(n, cap);\n // Bulk copies (this runs on every cleanly rendered quantum): the tail is\n // one contiguous source run, landing in at most two ring runs.\n const tail = out.subarray(n - take, n);\n const first = Math.min(take, cap - this.concealWrite);\n this.concealBuf.set(tail.subarray(0, first), this.concealWrite);\n if (take > first) this.concealBuf.set(tail.subarray(first), 0);\n this.concealWrite = (this.concealWrite + take) % cap;\n this.concealLen = Math.min(cap, this.concealLen + take);\n // Read the loop oldest-first; once the ring is full the write cursor is\n // the oldest retained sample.\n this.concealPos = this.concealLen === cap ? this.concealWrite : 0;\n this.concealing = false;\n this.concealGain = 1;\n }\n\n ingestBytes(uint8) {\n let bytes = uint8;\n\n if (this.carry !== null) {\n const merged = new Uint8Array(1 + bytes.length);\n merged[0] = this.carry;\n merged.set(bytes, 1);\n bytes = merged;\n this.carry = null;\n }\n\n if (bytes.length % 2 !== 0) {\n this.carry = bytes[bytes.length - 1];\n bytes = bytes.subarray(0, bytes.length - 1);\n }\n\n if (bytes.length === 0) return;\n const numSamples = bytes.length / 2;\n const cap = this.capacity;\n const samples = this.samples;\n if (this.littleEndian && (bytes.byteOffset & 1) === 0) {\n // Fast path: 2-byte-aligned LE bytes wrap directly as an Int16Array;\n // copy wrap-aware in at most two runs with no per-sample DataView call\n // or modulo. This runs on the realtime audio thread.\n const int16 = new Int16Array(bytes.buffer, bytes.byteOffset, numSamples);\n let src = 0;\n let dst = this.writePos % cap;\n while (src < numSamples) {\n const run = Math.min(numSamples - src, cap - dst);\n for (let j = 0; j < run; j++) {\n samples[dst + j] = int16[src + j] / 0x8000;\n }\n src += run;\n dst = 0;\n }\n } else {\n // Odd byte offset (or big-endian host): fall back to per-sample reads.\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.length);\n let dst = this.writePos % cap;\n for (let i = 0; i < numSamples; i++) {\n samples[dst] = view.getInt16(i * 2, true) / 0x8000;\n dst++;\n if (dst === cap) dst = 0;\n }\n }\n this.writePos += numSamples;\n // If the producer outran the consumer by more than the buffer holds, drop\n // the oldest unplayed audio rather than reading samples we've overwritten.\n if (this.writePos - this.readPos > this.capacity) {\n this.readPos = this.writePos - this.capacity;\n }\n }\n\n process(inputs, outputs) {\n // No output wired up yet \u2014 nothing to render this quantum. Throwing here\n // would permanently kill the processor (the node is persistent per\n // session), so guard like the capture processor does.\n if (!outputs[0] || !outputs[0][0]) return true;\n const out = outputs[0][0];\n const avail = this.writePos - this.readPos;\n\n // Report the unplayed backlog to the host (`playback_progress`). This IS\n // the closed-loop signal: the host otherwise models playback open-loop \u2014\n // every forwarded chunk assumed to start playing on arrival at exactly\n // 1.0x \u2014 and cannot see a buffer that has run ahead of the wall clock.\n // Measured against a client draining at 0.6x, the host declared the line\n // silent while it still held seconds of the reply, and then opened the\n // speaking-edge gate over speech the caller had not heard.\n //\n // Sent from process() rather than a timer because this is the only place\n // that sees the buffer on the audio thread; the counter is in QUANTA, so\n // it costs an integer compare per render and no clock read. Silence is not\n // reported: an empty buffer is what the host already assumes.\n this.sinceReportSamples += out.length;\n if (this.sinceReportSamples >= this.reportIntervalSamples) {\n this.sinceReportSamples = 0;\n if (avail > 0) {\n this.port.postMessage({ event: 'progress', bufferedMs: (avail / this.rate) * 1000 });\n }\n }\n\n // Filling: wait for the target. 'done' short-circuits it \u2014 what is\n // buffered is all there will be, so there is nothing left to wait for.\n if (!this.playing) {\n if (avail >= this.fillTarget || this.isDone) {\n this.playing = true;\n } else {\n this.coverGap(out, 0);\n return true;\n }\n }\n\n // Underrun: this quantum cannot be filled and more audio is still coming.\n // Go back to filling (at the refill target) and cover the gap, leaving\n // readPos untouched \u2014 the fragment stays buffered and plays intact once\n // the buffer recovers, instead of being dribbled out a few samples at a\n // time for the rest of the turn.\n if (avail < out.length && !this.isDone) {\n this.playing = false;\n this.fillTarget = this.refillSamples;\n this.coverGap(out, 0);\n return true;\n }\n\n if (avail > 0) {\n const n = Math.min(avail, out.length);\n // Copy from the ring buffer, splitting across the wrap boundary.\n const start = this.readPos % this.capacity;\n const first = Math.min(n, this.capacity - start);\n out.set(this.samples.subarray(start, start + first), 0);\n if (n > first) out.set(this.samples.subarray(0, n - first), first);\n this.readPos += n;\n // Only reachable with n < out.length on the turn's final partial\n // quantum (the underrun branch above catches every other case).\n out.fill(0, n);\n this.hasPlayed = true;\n this.rememberTail(out, n);\n return true;\n }\n\n // Drained and done: end the turn. Not reachable mid-turn \u2014 an empty\n // buffer with audio still coming is the underrun branch above.\n out.fill(0);\n if (this.isDone) {\n this.stopTurn('done');\n }\n return true;\n }\n}\n\nregisterProcessor('playback-processor', PlaybackProcessor);\n";
|
|
2
|
+
export declare const playbackProcessorSource = "\nclass PlaybackProcessor extends AudioWorkletProcessor {\n constructor(options) {\n super();\n const opts = options.processorOptions || {};\n // The node's context IS the playback context (audio.ts asserts its rate),\n // so the worklet-global sampleRate is authoritative; the option exists\n // for the node-less test harness.\n const rate = opts.sampleRate ?? sampleRate;\n // Kept, because every derived quantity below reads it and `bufferedMs` in\n // process() is the one that used to read the worklet global instead. Those\n // agree in production (the option is unset, so `rate` IS `sampleRate`) and\n // diverge only in the node-less harness \u2014 which is the one place a spec\n // could ever assert on the reported backlog, so the divergence made the\n // number untestable rather than wrong.\n this.rate = rate;\n // The one fill target \u2014 for the start of a turn and for recovery after an\n // underrun alike. If 'done' arrives first (short utterance), start\n // immediately instead of waiting for audio that is never coming.\n this.fillSamples = Math.floor((rate * (opts.fillMs ?? 200)) / 1000);\n // Concealment source: a ring of the most recently played samples, looped\n // under a decaying gain to cover a gap. Sized to the fade window, with a\n // per-sample decay that reaches the floor exactly at its end.\n this.concealCapacity = Math.max(1, Math.floor((rate * 40) / 1000));\n this.concealBuf = new Float32Array(this.concealCapacity);\n this.concealDecay = Math.exp(Math.log(0.001) / this.concealCapacity);\n // Float32 ring buffer \u2014 PLAYBACK_BUFFER_SECONDS at the context sample\n // rate. Allocated once for the node's lifetime; per-turn state resets via\n // resetTurn(). writePos and readPos are absolute (monotonic) sample\n // counts; the buffer is indexed modulo capacity so a longer reply keeps\n // playing instead of writing past the end and going silent.\n this.capacity = rate * 60;\n this.samples = new Float32Array(this.capacity);\n // Cadence for the 'progress' report in process(), counted in samples so\n // the hot path never reads a clock. Survives resetTurn(): the host clamps\n // upward only, so a stale count costs at most one extra report.\n this.reportIntervalSamples = Math.floor((rate * 500) / 1000);\n this.sinceReportSamples = 0;\n // Platform endianness probe: the wire format is PCM16 little-endian, so\n // the Int16Array fast path in ingestBytes is only valid on LE hosts\n // (every shipping browser target; the DataView path is the fallback).\n this.littleEndian = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;\n this.resetTurn();\n\n this.port.onmessage = (e) => {\n const d = e.data;\n if (d.event === 'write') {\n this.ingestBytes(d.buffer);\n } else if (d.event === 'interrupt') {\n // Applied eagerly, not deferred to the next process(): onmessage and\n // process() never interleave (one audio thread), and a deferred\n // interrupt would let 'write'/'done' frames for the NEXT turn\n // coalesce in ahead of it and be wiped by the reset along with the\n // cancelled turn's audio.\n this.stopTurn('interrupt');\n } else if (d.event === 'done') {\n this.isDone = true;\n // Echoed back on this turn's 'stop' so the host can tell WHICH turn\n // drained: a stop already in flight when a barge-in lands would\n // otherwise settle the next turn's done() the moment it arrives.\n this.doneTurn = d.turn ?? null;\n }\n };\n }\n\n // Reset per-turn state so the node is reusable across replies without\n // reallocating the sample buffer or re-instantiating the worklet.\n resetTurn() {\n this.isDone = false;\n // Host turn id carried by this turn's 'done' (null until one arrives).\n this.doneTurn = null;\n this.playing = false;\n // Whether any real audio has been rendered this turn. Separates a turn's\n // pre-roll (nothing to extrapolate from, and not a defect) from a\n // mid-turn underrun.\n this.hasPlayed = false;\n // Carry-over byte for split samples across chunks\n this.carry = null;\n this.writePos = 0;\n this.readPos = 0;\n // Concealment ring state and the current fade position.\n this.concealLen = 0;\n this.concealWrite = 0;\n this.concealPos = 0;\n this.concealGain = 1;\n // Episode flags, so a multi-quantum gap counts as one event.\n this.concealing = false;\n this.concealedSilence = false;\n // Reported to the host on 'stop'. A fresh object per turn: the one just\n // posted must not be mutated by the next turn.\n this.stats = {\n concealedSamples: 0,\n silentConcealedSamples: 0,\n concealmentEvents: 0,\n silentConcealmentEvents: 0,\n };\n }\n\n // End the current turn: notify the host and rearm for the next reply.\n // Must NOT return false from process() \u2014 a processor that stops is dead\n // for good, forcing a new node (and buffer) per reply.\n // `reason` ('interrupt' | 'done') tells the host which turn boundary this\n // stop belongs to \u2014 interrupt-stops are dropped host-side, flush() having\n // already settled that turn \u2014 and `turn` names WHICH turn drained (the id\n // this turn's 'done' carried), so a drain-stop still in flight when a\n // barge-in lands cannot settle the next turn's done() early.\n stopTurn(reason) {\n this.port.postMessage({ event: 'stop', reason, turn: this.doneTurn, stats: this.stats });\n this.resetTurn();\n }\n\n // Cover a quantum (from `start`) where real audio should have been.\n //\n // Before the turn's first samples there is nothing to extrapolate from, so\n // the gap is plain silence and counted as nothing \u2014 WebRTC likewise only\n // counts concealment once playout has begun. After that, loop the retained\n // tail under a decaying gain: a hard zero-fill is a discontinuity mid-word,\n // which is the click that makes a brief stall sound like breakage.\n coverGap(out, start) {\n if (!this.hasPlayed) {\n out.fill(0, start);\n return;\n }\n if (!this.concealing) {\n this.concealing = true;\n this.concealedSilence = false;\n this.stats.concealmentEvents++;\n }\n const total = out.length - start;\n const len = this.concealLen;\n let silent = 0;\n // Second condition: once the fade has decayed to the floor, every sample\n // the loop would emit is 0 anyway \u2014 bulk-fill instead of running 128\n // branchy iterations per quantum for the whole tail of a long stall.\n if (len === 0 || this.concealGain < 0.001) {\n out.fill(0, start);\n silent = total;\n } else {\n let g = this.concealGain;\n for (let i = start; i < out.length; i++) {\n if (g < 0.001) {\n // The fade has run out: keep counting the gap, but stop looping a\n // fragment that is now inaudible anyway.\n out[i] = 0;\n silent++;\n continue;\n }\n out[i] = this.concealBuf[this.concealPos] * g;\n this.concealPos = this.concealPos + 1 === len ? 0 : this.concealPos + 1;\n g *= this.concealDecay;\n }\n this.concealGain = g;\n }\n this.stats.concealedSamples += total;\n if (silent > 0) {\n this.stats.silentConcealedSamples += silent;\n if (!this.concealedSilence) {\n this.concealedSilence = true;\n this.stats.silentConcealmentEvents++;\n }\n }\n }\n\n // Retain the tail of a rendered quantum as the next gap's concealment\n // source, and close any episode the real audio just ended.\n rememberTail(out, n) {\n const cap = this.concealCapacity;\n const take = Math.min(n, cap);\n // Bulk copies (this runs on every cleanly rendered quantum): the tail is\n // one contiguous source run, landing in at most two ring runs.\n const tail = out.subarray(n - take, n);\n const first = Math.min(take, cap - this.concealWrite);\n this.concealBuf.set(tail.subarray(0, first), this.concealWrite);\n if (take > first) this.concealBuf.set(tail.subarray(first), 0);\n this.concealWrite = (this.concealWrite + take) % cap;\n this.concealLen = Math.min(cap, this.concealLen + take);\n // Read the loop oldest-first; once the ring is full the write cursor is\n // the oldest retained sample.\n this.concealPos = this.concealLen === cap ? this.concealWrite : 0;\n this.concealing = false;\n this.concealGain = 1;\n }\n\n ingestBytes(uint8) {\n let bytes = uint8;\n\n if (this.carry !== null) {\n const merged = new Uint8Array(1 + bytes.length);\n merged[0] = this.carry;\n merged.set(bytes, 1);\n bytes = merged;\n this.carry = null;\n }\n\n if (bytes.length % 2 !== 0) {\n this.carry = bytes[bytes.length - 1];\n bytes = bytes.subarray(0, bytes.length - 1);\n }\n\n if (bytes.length === 0) return;\n const numSamples = bytes.length / 2;\n const cap = this.capacity;\n const samples = this.samples;\n if (this.littleEndian && (bytes.byteOffset & 1) === 0) {\n // Fast path: 2-byte-aligned LE bytes wrap directly as an Int16Array;\n // copy wrap-aware in at most two runs with no per-sample DataView call\n // or modulo. This runs on the realtime audio thread.\n const int16 = new Int16Array(bytes.buffer, bytes.byteOffset, numSamples);\n let src = 0;\n let dst = this.writePos % cap;\n while (src < numSamples) {\n const run = Math.min(numSamples - src, cap - dst);\n for (let j = 0; j < run; j++) {\n samples[dst + j] = int16[src + j] / 0x8000;\n }\n src += run;\n dst = 0;\n }\n } else {\n // Odd byte offset (or big-endian host): fall back to per-sample reads.\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.length);\n let dst = this.writePos % cap;\n for (let i = 0; i < numSamples; i++) {\n samples[dst] = view.getInt16(i * 2, true) / 0x8000;\n dst++;\n if (dst === cap) dst = 0;\n }\n }\n this.writePos += numSamples;\n // If the producer outran the consumer by more than the buffer holds, drop\n // the oldest unplayed audio rather than reading samples we've overwritten.\n if (this.writePos - this.readPos > this.capacity) {\n this.readPos = this.writePos - this.capacity;\n }\n }\n\n process(inputs, outputs) {\n // No output wired up yet \u2014 nothing to render this quantum. Throwing here\n // would permanently kill the processor (the node is persistent per\n // session), so guard like the capture processor does.\n if (!outputs[0] || !outputs[0][0]) return true;\n const out = outputs[0][0];\n const avail = this.writePos - this.readPos;\n\n // Report the unplayed backlog to the host (`playback_progress`). This IS\n // the closed-loop signal: the host otherwise models playback open-loop \u2014\n // every forwarded chunk assumed to start playing on arrival at exactly\n // 1.0x \u2014 and cannot see a buffer that has run ahead of the wall clock.\n // Measured against a client draining at 0.6x, the host declared the line\n // silent while it still held seconds of the reply, and then opened the\n // speaking-edge gate over speech the caller had not heard.\n //\n // Sent from process() rather than a timer because this is the only place\n // that sees the buffer on the audio thread; the counter is in QUANTA, so\n // it costs an integer compare per render and no clock read. Silence is not\n // reported: an empty buffer is what the host already assumes.\n this.sinceReportSamples += out.length;\n if (this.sinceReportSamples >= this.reportIntervalSamples) {\n this.sinceReportSamples = 0;\n if (avail > 0) {\n this.port.postMessage({ event: 'progress', bufferedMs: (avail / this.rate) * 1000 });\n }\n }\n\n // Filling: wait for the target. 'done' short-circuits it \u2014 what is\n // buffered is all there will be, so there is nothing left to wait for.\n if (!this.playing) {\n if (avail >= this.fillSamples || this.isDone) {\n this.playing = true;\n } else {\n this.coverGap(out, 0);\n return true;\n }\n }\n\n // Underrun: this quantum cannot be filled and more audio is still coming.\n // Go back to filling and cover the gap, leaving\n // readPos untouched \u2014 the fragment stays buffered and plays intact once\n // the buffer recovers, instead of being dribbled out a few samples at a\n // time for the rest of the turn.\n if (avail < out.length && !this.isDone) {\n this.playing = false;\n this.coverGap(out, 0);\n return true;\n }\n\n if (avail > 0) {\n const n = Math.min(avail, out.length);\n // Copy from the ring buffer, splitting across the wrap boundary.\n const start = this.readPos % this.capacity;\n const first = Math.min(n, this.capacity - start);\n out.set(this.samples.subarray(start, start + first), 0);\n if (n > first) out.set(this.samples.subarray(0, n - first), first);\n this.readPos += n;\n // Only reachable with n < out.length on the turn's final partial\n // quantum (the underrun branch above catches every other case).\n out.fill(0, n);\n this.hasPlayed = true;\n this.rememberTail(out, n);\n return true;\n }\n\n // Drained and done: end the turn. Not reachable mid-turn \u2014 an empty\n // buffer with audio still coming is the underrun branch above.\n out.fill(0);\n if (this.isDone) {\n this.stopTurn('done');\n }\n return true;\n }\n}\n\nregisterProcessor('playback-processor', PlaybackProcessor);\n";
|
|
3
3
|
declare const _default: string;
|
|
4
4
|
export default _default;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PLAYBACK_BUFFER_SECONDS, PLAYBACK_CONCEAL_FADE_MS, PLAYBACK_CONCEAL_FLOOR,
|
|
1
|
+
import { PLAYBACK_BUFFER_SECONDS, PLAYBACK_CONCEAL_FADE_MS, PLAYBACK_CONCEAL_FLOOR, PLAYBACK_FILL_MS, PLAYBACK_PROGRESS_INTERVAL_MS } from "../types.js";
|
|
2
2
|
import { t as workletModuleUrl } from "../_module-url-C_4gRVL0.js";
|
|
3
3
|
//#region worklets/playback-processor.ts
|
|
4
4
|
const PlaybackProcessorWorklet = `
|
|
@@ -17,12 +17,10 @@ class PlaybackProcessor extends AudioWorkletProcessor {
|
|
|
17
17
|
// could ever assert on the reported backlog, so the divergence made the
|
|
18
18
|
// number untestable rather than wrong.
|
|
19
19
|
this.rate = rate;
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// never coming.
|
|
23
|
-
this.
|
|
24
|
-
// Fill target after an underrun — see PLAYBACK_REFILL_MS.
|
|
25
|
-
this.refillSamples = Math.floor((rate * (opts.refillMs ?? ${PLAYBACK_REFILL_MS})) / 1000);
|
|
20
|
+
// The one fill target — for the start of a turn and for recovery after an
|
|
21
|
+
// underrun alike. If 'done' arrives first (short utterance), start
|
|
22
|
+
// immediately instead of waiting for audio that is never coming.
|
|
23
|
+
this.fillSamples = Math.floor((rate * (opts.fillMs ?? ${PLAYBACK_FILL_MS})) / 1000);
|
|
26
24
|
// Concealment source: a ring of the most recently played samples, looped
|
|
27
25
|
// under a decaying gain to cover a gap. Sized to the fade window, with a
|
|
28
26
|
// per-sample decay that reaches the floor exactly at its end.
|
|
@@ -79,7 +77,6 @@ class PlaybackProcessor extends AudioWorkletProcessor {
|
|
|
79
77
|
// pre-roll (nothing to extrapolate from, and not a defect) from a
|
|
80
78
|
// mid-turn underrun.
|
|
81
79
|
this.hasPlayed = false;
|
|
82
|
-
this.fillTarget = this.jitterSamples;
|
|
83
80
|
// Carry-over byte for split samples across chunks
|
|
84
81
|
this.carry = null;
|
|
85
82
|
this.writePos = 0;
|
|
@@ -271,7 +268,7 @@ class PlaybackProcessor extends AudioWorkletProcessor {
|
|
|
271
268
|
// Filling: wait for the target. 'done' short-circuits it — what is
|
|
272
269
|
// buffered is all there will be, so there is nothing left to wait for.
|
|
273
270
|
if (!this.playing) {
|
|
274
|
-
if (avail >= this.
|
|
271
|
+
if (avail >= this.fillSamples || this.isDone) {
|
|
275
272
|
this.playing = true;
|
|
276
273
|
} else {
|
|
277
274
|
this.coverGap(out, 0);
|
|
@@ -280,13 +277,12 @@ class PlaybackProcessor extends AudioWorkletProcessor {
|
|
|
280
277
|
}
|
|
281
278
|
|
|
282
279
|
// Underrun: this quantum cannot be filled and more audio is still coming.
|
|
283
|
-
// Go back to filling
|
|
280
|
+
// Go back to filling and cover the gap, leaving
|
|
284
281
|
// readPos untouched — the fragment stays buffered and plays intact once
|
|
285
282
|
// the buffer recovers, instead of being dribbled out a few samples at a
|
|
286
283
|
// time for the rest of the turn.
|
|
287
284
|
if (avail < out.length && !this.isDone) {
|
|
288
285
|
this.playing = false;
|
|
289
|
-
this.fillTarget = this.refillSamples;
|
|
290
286
|
this.coverGap(out, 0);
|
|
291
287
|
return true;
|
|
292
288
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexkroman1/aai-ui",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"remark-gfm": "^4.0.1",
|
|
30
30
|
"use-stick-to-bottom": "^1.1.6",
|
|
31
31
|
"use-sync-external-store": "^1.6.0",
|
|
32
|
-
"@alexkroman1/aai": "6.
|
|
32
|
+
"@alexkroman1/aai": "6.2.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": "^19.0.0",
|