@alexkroman1/aai-ui 0.15.0 → 0.15.2
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/components/controls.d.ts +6 -1
- package/dist/components/controls.js +12 -2
- package/dist/components/session-controls.d.ts +4 -4
- package/dist/default-client/assets/{audio-e2nPxd2V.js → audio-W0yIAgHB.js} +1 -1
- package/dist/default-client/assets/{capture-processor-HtaKKIjy.js → capture-processor-CY8uqYKS.js} +1 -1
- package/dist/default-client/assets/{index-ClDvrM_h.js → index-Cc5bATbP.js} +4 -4
- package/dist/default-client/assets/{playback-processor-D5Wq6a9n.js → playback-processor-Cg3OLY-i.js} +1 -1
- package/dist/default-client/index.html +1 -1
- package/dist/index.js +4 -4
- package/package.json +2 -2
|
@@ -12,7 +12,12 @@ export type ControlsProps = {
|
|
|
12
12
|
className?: string;
|
|
13
13
|
};
|
|
14
14
|
/**
|
|
15
|
-
* Session control buttons: **Stop / Resume
|
|
15
|
+
* Session control buttons: **Stop / Resume**, **New Conversation** and
|
|
16
|
+
* **End Call**.
|
|
17
|
+
*
|
|
18
|
+
* End Call is `end()`: it hangs up, drops the resume identity and returns the
|
|
19
|
+
* shell to its start screen, so the provider sockets close and the next start
|
|
20
|
+
* is a new session. Stop only pauses — the session is kept for a resume.
|
|
16
21
|
*
|
|
17
22
|
* Reads session state from {@link useSession}. Must be rendered inside a
|
|
18
23
|
* `SessionProvider`.
|
|
@@ -7,7 +7,12 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
//#region src/components/controls.tsx
|
|
8
8
|
/** @jsxImportSource react */
|
|
9
9
|
/**
|
|
10
|
-
* Session control buttons: **Stop / Resume
|
|
10
|
+
* Session control buttons: **Stop / Resume**, **New Conversation** and
|
|
11
|
+
* **End Call**.
|
|
12
|
+
*
|
|
13
|
+
* End Call is `end()`: it hangs up, drops the resume identity and returns the
|
|
14
|
+
* shell to its start screen, so the provider sockets close and the next start
|
|
15
|
+
* is a new session. Stop only pauses — the session is kept for a resume.
|
|
11
16
|
*
|
|
12
17
|
* Reads session state from {@link useSession}. Must be rendered inside a
|
|
13
18
|
* `SessionProvider`.
|
|
@@ -27,7 +32,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
27
32
|
*/
|
|
28
33
|
const Controls = memo(function Controls({ className }) {
|
|
29
34
|
const running = useSessionSelector((s) => s.running);
|
|
30
|
-
const { toggle, restart } = useSessionCore();
|
|
35
|
+
const { toggle, restart, end } = useSessionCore();
|
|
31
36
|
return /* @__PURE__ */ jsxs("div", {
|
|
32
37
|
className: clsx("flex flex-wrap items-center gap-3 shrink-0", className),
|
|
33
38
|
children: [
|
|
@@ -41,6 +46,11 @@ const Controls = memo(function Controls({ className }) {
|
|
|
41
46
|
onClick: restart,
|
|
42
47
|
children: "New Conversation"
|
|
43
48
|
}),
|
|
49
|
+
/* @__PURE__ */ jsx(Button, {
|
|
50
|
+
variant: "ghost",
|
|
51
|
+
onClick: end,
|
|
52
|
+
children: "End Call"
|
|
53
|
+
}),
|
|
44
54
|
/* @__PURE__ */ jsx(SessionUrlChips, { className: "basis-full sm:basis-auto sm:ml-auto sm:max-w-[60%]" })
|
|
45
55
|
]
|
|
46
56
|
});
|
|
@@ -65,10 +65,10 @@ export type SessionControlsProps = {
|
|
|
65
65
|
* The full control row of a custom chrome: **Start** before the call, then
|
|
66
66
|
* **Pause / Resume**, **New Conversation** and **End** once it is up.
|
|
67
67
|
*
|
|
68
|
-
* {@link Controls} is the stock console's footer — Stop/Resume
|
|
69
|
-
* Conversation, with the URL chips — and it has no Start branch
|
|
70
|
-
* because the default shell shows a start SCREEN
|
|
71
|
-
*
|
|
68
|
+
* {@link Controls} is the stock console's footer — Stop/Resume, New
|
|
69
|
+
* Conversation and End Call, with the URL chips — and it has no Start branch,
|
|
70
|
+
* because the default shell shows a start SCREEN, which End Call returns to. A
|
|
71
|
+
* chrome that owns its whole frame has no start screen, so
|
|
72
72
|
* three of them each wrote this row: the same `!started` branch, the same
|
|
73
73
|
* three buttons behind it, and the same twelve-line comment on why the middle
|
|
74
74
|
* one is `end(); start()` and not `reset()`. The buttons' look is the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,i as t,t as n}from"./index-
|
|
1
|
+
import{a as e,i as t,t as n}from"./index-Cc5bATbP.js";var r={echoCancellation:!0,noiseSuppression:!1,autoGainControl:!1,voiceIsolation:!1};function i(e,t,n){if(e!==t)throw Error(`Browser refused the ${n} sample rate: asked for ${t} Hz, got ${e} Hz`)}function a(e){let t=Error(`Audio ${e} worklet crashed`);return console.error(`[aai-ui]`,t.message),t}function o(e){e.then(e=>{for(let t of e.getTracks())t.stop()}).catch(()=>{})}function s(e,t,n){let r=new AudioWorkletNode(e,`capture-processor`,{channelCount:1,channelCountMode:`explicit`}),i=null;return r.port.onmessage=e=>{let r=e.data;r.event===`chunk`&&r.buffer?t(r.buffer):r.event===`silent`?n?.():r.event===`stopped`&&(i?.(),i=null)},{node:r,start(){r.port.postMessage({event:`start`})},stop(){let{promise:e,resolve:t}=Promise.withResolvers(),n=setTimeout(t,250);return i=()=>{clearTimeout(n),t()},r.port.postMessage({event:`stop`}),e}}}async function c(e){let{sampleRate:t,captureWorkletSrc:n,maxSeconds:a}=e,c=new AudioContext({sampleRate:t,latencyHint:`interactive`}),l=navigator.mediaDevices.getUserMedia({audio:{deviceId:{ideal:`default`},...r}}),u;try{[u]=await Promise.all([l,c.resume(),c.audioWorklet.addModule(n)]),i(c.sampleRate,t,`capture`)}catch(e){throw o(l),await c.close().catch(()=>{}),e}let d=Math.round(t*a)*2,f=[],p=0,m=!1,h=null,g=c.createMediaStreamSource(u),_=s(c,e=>{if(h){h.onChunk(e);return}for(f.push(e),p+=e.byteLength;p>d&&f.length>1;)p-=f.shift()?.byteLength??0},()=>{h?h.onSilent?.():m=!0});g.connect(_.node),_.start();let v=()=>{let e=f;return f=[],p=0,e},y=!1,b=async()=>{y||(y=!0,g.disconnect(),_.node.disconnect(),await c.close().catch(e=>{console.warn(`AudioContext close failed:`,e)}))};return{sampleRate:t,stream:u,ctx:c,handOff(e){h=e;let t=v();return t.length>0&&e.onBuffered(t),m&&e.onSilent?.(),{mic:g,capture:_}},async detach(){return await _.stop(),await b(),{chunks:v(),sampleRate:t}},async close(){y||await _.stop();for(let e of u.getTracks())e.stop();await b()}}}function l(e){let t=new Int16Array(e.length);for(let n=0;n<e.length;n++){let r=Math.max(-1,Math.min(1,e[n]??0));t[n]=r<0?r*32768:r*32767}return t.buffer}async function u(e,t,r){let i=e.reduce((e,t)=>e+t.byteLength/2,0);if(i===0)return[];if(t===r)return e;try{let a=Math.round(i*r/t),o=new OfflineAudioContext(1,a,r),s=o.createBuffer(1,i,t),c=s.getChannelData(0),u=0;for(let t of e)for(let e of new Int16Array(t))c[u++]=e/32768;let d=o.createBufferSource();d.buffer=s,d.connect(o.destination),d.start();let f=(await o.startRendering()).getChannelData(0),p=Math.max(1,Math.round(r*n)),m=[];for(let e=0;e<f.length;e+=p)m.push(l(f.subarray(e,e+p)));return m}catch(e){return console.warn(`[aai-ui] pre-connect audio dropped: resample failed`,e),[]}}async function d(e,t,n){let r=e.createMediaStreamSource(t),i=s(e,n.onMicData,n.onMicSilent);if(r.connect(i.node),n.buffered){let{chunks:t,sampleRate:r}=n.buffered,i=await u(t,r,e.sampleRate);i.length>0&&n.onBuffered(i)}return i.start(),{mic:r,capture:i}}async function f(n){let{sttSampleRate:s,ttsSampleRate:c,captureWorkletSrc:l,playbackWorkletSrc:u,onMicData:f,onError:p,onPlaybackStats:m,onPlaybackProgress:h,onMicSilent:g,preConnect:_}=n,v=n.onPreConnectAudio??(e=>{for(let t of e)f(t)}),y=_?.sampleRate===s?_:null,b=_&&!y?await _.detach():null,x=new AudioContext({sampleRate:c,latencyHint:`playback`}),S=!y&&s===c,C=y?.ctx??(S?x:new AudioContext({sampleRate:s,latencyHint:`interactive`}));async function w(){let e=S?[x]:[x,C];await Promise.all(e.map(e=>e.close().catch(e=>{console.warn(`AudioContext close failed:`,e)})))}let T=_?Promise.resolve(_.stream):navigator.mediaDevices.getUserMedia({audio:{deviceId:{ideal:`default`},...r}}),E;try{[E]=await Promise.all([T,x.resume(),C.resume(),y?void 0:C.audioWorklet.addModule(l),x.audioWorklet.addModule(u)]),i(C.sampleRate,s,`capture`),i(x.sampleRate,c,`playback`)}catch(e){throw o(T),await w(),e}let{mic:D,capture:O}=y?y.handOff({onBuffered:v,onChunk:f,onSilent:g}):await d(C,E,{onMicData:f,onMicSilent:g,buffered:b,onBuffered:v});O.node.onprocessorerror=()=>p?.(a(`capture`));let k=null,A=0,j=null,M=new AbortController;function N(){let e=j;j=null,e?.settle()}function P(e){e.stats&&e.stats.concealedSamples>0&&m?.(e.stats),e.reason!==`interrupt`&&(j===null||e.turn===j.turn)&&N()}function F(){if(k)return k;let e=new AudioWorkletNode(x,`playback-processor`);return e.connect(x.destination),e.port.onmessage=e=>{e.data.event===`stop`?P(e.data):e.data.event===`progress`&&h?.(e.data.bufferedMs)},e.onprocessorerror=()=>{let e=a(`playback`);N(),p?.(e)},k=e,e}let I={enqueue(e){M.signal.aborted||e.byteLength!==0&&F().port.postMessage({event:`write`,buffer:new Uint8Array(e)},[e])},done(){if(!k)return Promise.resolve();let n=++A;if(k.port.postMessage({event:`done`,turn:n}),x.state!==`running`)return N(),Promise.resolve();let{promise:r,resolve:i}=Promise.withResolvers();N();let a=()=>{clearInterval(o),clearTimeout(s),j?.settle===a&&(j=null),i()},o=setInterval(()=>{x.state!==`running`&&a()},e),s=setTimeout(a,t);return j={turn:n,settle:a},r},flush(){k&&(N(),k.port.postMessage({event:`interrupt`}))},async close(){if(!M.signal.aborted){M.abort(),await O.stop(),D.disconnect(),O.node.disconnect(),k&&k.disconnect();for(let e of E.getTracks())e.stop();await w()}},async[Symbol.asyncDispose](){await I.close()}};return I}export{f as createVoiceIO,c as openPreConnectCapture};
|
package/dist/default-client/assets/{capture-processor-HtaKKIjy.js → capture-processor-CY8uqYKS.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{n as e,t}from"./index-
|
|
1
|
+
import{n as e,t}from"./index-Cc5bATbP.js";import{t as n}from"./_module-url-BX0RuRU2.js";var r=`
|
|
2
2
|
class CaptureProcessor extends AudioWorkletProcessor {
|
|
3
3
|
constructor(options) {
|
|
4
4
|
super();
|