@alexkroman1/aai-ui 0.12.3 → 0.14.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/README.md +253 -0
- package/dist/_colors-CpZO-88A.js +78 -0
- package/dist/_module-url-C13kAJ87.js +15 -0
- package/dist/_page-shell.d.ts +11 -0
- package/dist/_recover-run.d.ts +79 -0
- package/dist/_repeat-until.d.ts +30 -0
- package/dist/_run-controls.d.ts +33 -0
- package/dist/_submission-state.d.ts +92 -0
- package/dist/_tap-to-talk-state.d.ts +88 -0
- package/dist/_utils-Mi-SCFA1.js +47 -0
- package/dist/_utils.d.ts +26 -0
- package/dist/_web-storage.d.ts +43 -0
- package/dist/_workflow-api-ref.d.ts +37 -0
- package/dist/_workflow-files.d.ts +34 -0
- package/dist/_workflow-test-defs.d.ts +34 -0
- package/dist/aai-logo-D8pMVn9_.js +85 -0
- package/dist/agent-state-labels.d.ts +65 -0
- package/dist/agent-state.d.ts +108 -0
- package/dist/audio/capture.d.ts +44 -0
- package/dist/audio/index.d.ts +18 -0
- package/dist/audio/index.js +431 -0
- package/dist/audio/preconnect.d.ts +87 -0
- package/dist/audio/voice-io.d.ts +112 -0
- package/dist/build-default-client.d.ts +1 -0
- package/dist/chat-view-BCbI7gD4.js +396 -0
- package/dist/client-config-C7fQrioM.js +139 -0
- package/dist/client-config.d.ts +119 -0
- package/dist/client-dir.d.ts +44 -0
- package/dist/client-dir.js +58 -0
- package/dist/client-identity.d.ts +131 -0
- package/dist/client-tool.d.ts +51 -0
- package/dist/components/_colors.d.ts +73 -0
- package/dist/components/_form-readiness.d.ts +56 -0
- package/dist/components/_form-values.d.ts +19 -0
- package/dist/components/aai-logo.d.ts +12 -0
- package/dist/components/audio-result.d.ts +94 -0
- package/dist/components/auto-scroll.d.ts +74 -0
- package/dist/components/bullet-list.d.ts +74 -0
- package/dist/components/button.d.ts +29 -21
- package/dist/components/button.js +62 -23
- package/dist/components/chat-view.d.ts +24 -18
- package/dist/components/chat-view.js +2 -60
- package/dist/components/console-shell.d.ts +85 -0
- package/dist/components/controls.d.ts +21 -6
- package/dist/components/controls.js +31 -19
- package/dist/components/conversation-view.d.ts +130 -0
- package/dist/components/eyebrow.d.ts +12 -0
- package/dist/components/facts.d.ts +81 -0
- package/dist/components/form-fields.d.ts +142 -0
- package/dist/components/form-types.d.ts +73 -0
- package/dist/components/form.d.ts +96 -0
- package/dist/components/markdown.d.ts +57 -0
- package/dist/components/message-list.d.ts +34 -7
- package/dist/components/message-list.js +2 -75
- package/dist/components/session-controls.d.ts +125 -0
- package/dist/components/session-error-banner.d.ts +69 -0
- package/dist/components/session-state-dot.d.ts +111 -0
- package/dist/components/sidebar-layout.d.ts +31 -10
- package/dist/components/sidebar-layout.js +32 -12
- package/dist/components/start-screen.d.ts +17 -6
- package/dist/components/start-screen.js +34 -14
- package/dist/components/tool-call-block.d.ts +15 -14
- package/dist/components/tool-call-block.js +2 -117
- package/dist/components/tool-call-row.d.ts +75 -0
- package/dist/components/tool-config-context.d.ts +22 -0
- package/dist/components/upload-progress.d.ts +76 -0
- package/dist/components/url-chips.d.ts +29 -0
- package/dist/components/workflow-fields.d.ts +46 -0
- package/dist/components/workflow-pending-note.d.ts +81 -0
- package/dist/components/workflow-progress.d.ts +81 -0
- package/dist/components/workflow-run-error.d.ts +48 -0
- package/dist/components/workflow-run-panel.d.ts +79 -0
- package/dist/context.d.ts +288 -0
- package/dist/context.js +382 -0
- package/dist/conversation-log.d.ts +88 -0
- package/dist/default-client/assets/_module-url-BX0RuRU2.js +1 -0
- package/dist/default-client/assets/audio-mpgipo2Q.js +1 -0
- package/dist/default-client/assets/capture-processor-D0q7Qrdd.js +90 -0
- package/dist/default-client/assets/index-8ihb1Urm.css +2 -0
- package/dist/default-client/assets/index-DPfmSFmc.js +126 -0
- package/dist/default-client/assets/playback-processor-CjDbc9iH.js +314 -0
- package/dist/default-client/favicon.ico +0 -0
- package/dist/default-client/index.html +15 -0
- package/dist/default-client-entry.d.ts +6 -0
- package/dist/default-client.d.ts +19 -0
- package/dist/define-client.d.ts +169 -26
- package/dist/define-client.js +209 -55
- package/dist/eyebrow-UfmSz9yy.js +27 -0
- package/dist/hooks-DN4gHYsj.js +182 -0
- package/dist/hooks.d.ts +172 -0
- package/dist/hooks.js +3 -0
- package/dist/hooks.test-d.d.ts +14 -0
- package/dist/inbox-protocol.d.ts +128 -0
- package/dist/inbox.d.ts +132 -0
- package/dist/index.d.ts +126 -29
- package/dist/index.js +5500 -15
- package/dist/internal.d.ts +43 -0
- package/dist/internal.js +8 -0
- package/dist/linked-client.d.ts +70 -0
- package/dist/message-list-DHZcLhRA.js +682 -0
- package/dist/notice-player.d.ts +70 -0
- package/dist/page.d.ts +114 -0
- package/dist/phone.d.ts +50 -0
- package/dist/route-fetch.d.ts +79 -0
- package/dist/session/audio-effects.d.ts +37 -0
- package/dist/session/audio-setup.d.ts +57 -0
- package/dist/session/audio-state.d.ts +157 -0
- package/dist/session/browser-session.d.ts +39 -0
- package/dist/session/close.d.ts +26 -0
- package/dist/session/dial.d.ts +65 -0
- package/dist/session/handshake.d.ts +60 -0
- package/dist/session/index.d.ts +17 -0
- package/dist/session/index.js +2 -0
- package/dist/session/messages.d.ts +63 -0
- package/dist/session/mic.d.ts +64 -0
- package/dist/session/preconnect.d.ts +48 -0
- package/dist/session/reconnect.d.ts +25 -0
- package/dist/session/resume-store.d.ts +57 -0
- package/dist/session/state.d.ts +146 -0
- package/dist/session/ticket.d.ts +44 -0
- package/dist/session/types.d.ts +476 -0
- package/dist/session/url.d.ts +17 -0
- package/dist/session/user-turn.d.ts +55 -0
- package/dist/session-D9jM5-TF.js +2140 -0
- package/dist/stored-value.d.ts +103 -0
- package/dist/tool-call-block-uXtMOUWN.js +204 -0
- package/dist/tool-config-context-Es4YUzV2.js +19 -0
- package/dist/types.d.ts +272 -34
- package/dist/types.js +35 -4
- package/dist/upload/files.d.ts +83 -0
- package/dist/upload/index.d.ts +15 -0
- package/dist/upload/pause.d.ts +31 -0
- package/dist/upload/recall.d.ts +67 -0
- package/dist/upload/report.d.ts +26 -0
- package/dist/upload/session.d.ts +99 -0
- package/dist/url-chips-oyy_Eiqh.js +239 -0
- package/dist/use-client-runs.d.ts +87 -0
- package/dist/use-conversation-log.d.ts +86 -0
- package/dist/use-conversation.d.ts +131 -0
- package/dist/use-copy.d.ts +74 -0
- package/dist/use-download-url.d.ts +84 -0
- package/dist/use-flash.d.ts +61 -0
- package/dist/use-inbox.d.ts +101 -0
- package/dist/use-push-to-talk.d.ts +105 -0
- package/dist/use-route-mutation.d.ts +113 -0
- package/dist/use-route.d.ts +82 -0
- package/dist/use-run-key.d.ts +133 -0
- package/dist/use-session-controls.d.ts +69 -0
- package/dist/use-session-id.d.ts +57 -0
- package/dist/use-tap-to-talk.d.ts +138 -0
- package/dist/use-user-transcript-uyHhzy4d.js +73 -0
- package/dist/use-user-transcript.d.ts +75 -0
- package/dist/use-workflow-form.d.ts +285 -0
- package/dist/use-workflow-progress.d.ts +137 -0
- package/dist/use-workflow-run-DdhWmbx-.js +697 -0
- package/dist/use-workflow-run.d.ts +100 -0
- package/dist/use-workflow-runs.d.ts +71 -0
- package/dist/use-workflow-stream.d.ts +154 -0
- package/dist/use-workflows.d.ts +77 -0
- package/dist/workflow-client.d.ts +136 -0
- package/dist/workflow-def-types.d.ts +31 -0
- package/dist/workflow-events.d.ts +39 -0
- package/dist/workflow-field-kind.d.ts +95 -0
- package/dist/workflow-status-labels.d.ts +35 -0
- package/dist/worklets/_module-url.d.ts +10 -0
- 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/capture-processor.d.ts +4 -2
- package/dist/worklets/capture-processor.js +73 -33
- package/dist/worklets/playback-processor.d.ts +4 -2
- package/dist/worklets/playback-processor.js +258 -42
- package/package.json +59 -32
- package/styles.css +152 -30
- package/dist/_jsdom-setup.d.ts +0 -0
- package/dist/audio.d.ts +0 -45
- package/dist/audio.js +0 -121
- package/dist/client-context.d.ts +0 -33
- package/dist/client-context.js +0 -15
- package/dist/client-handler.d.ts +0 -43
- package/dist/components/app.d.ts +0 -23
- package/dist/components/app.js +0 -41
- package/dist/components/error-banner.d.ts +0 -21
- package/dist/components/error-banner.js +0 -27
- package/dist/components/message-bubble.d.ts +0 -23
- package/dist/components/message-bubble.js +0 -35
- package/dist/components/state-indicator.d.ts +0 -29
- package/dist/components/state-indicator.js +0 -37
- package/dist/components/thinking-indicator.d.ts +0 -16
- package/dist/components/thinking-indicator.js +0 -34
- package/dist/components/tool-icons.d.ts +0 -17
- package/dist/components/tool-icons.js +0 -128
- package/dist/components/transcript.d.ts +0 -25
- package/dist/components/transcript.js +0 -35
- package/dist/session-CWB7vmqz.js +0 -429
- package/dist/session.d.ts +0 -69
- package/dist/session.js +0 -2
- package/dist/signals.d.ts +0 -120
- package/dist/signals.js +0 -161
- package/dist/tsdown.config.d.ts +0 -2
- package/dist/types.test-d.d.ts +0 -7
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import{r as e}from"./index-DPfmSFmc.js";import{t}from"./_module-url-BX0RuRU2.js";var n=`
|
|
2
|
+
class PlaybackProcessor extends AudioWorkletProcessor {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
super();
|
|
5
|
+
const opts = options.processorOptions || {};
|
|
6
|
+
// The node's context IS the playback context (audio/voice-io.ts asserts its rate),
|
|
7
|
+
// so the worklet-global sampleRate is authoritative; the option exists
|
|
8
|
+
// for the node-less test harness.
|
|
9
|
+
const rate = opts.sampleRate ?? sampleRate;
|
|
10
|
+
// Kept, because every derived quantity below reads it and \`bufferedMs\` in
|
|
11
|
+
// process() is the one that used to read the worklet global instead. Those
|
|
12
|
+
// agree in production (the option is unset, so \`rate\` IS \`sampleRate\`) and
|
|
13
|
+
// diverge only in the node-less harness — which is the one place a spec
|
|
14
|
+
// could ever assert on the reported backlog, so the divergence made the
|
|
15
|
+
// number untestable rather than wrong.
|
|
16
|
+
this.rate = rate;
|
|
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);
|
|
21
|
+
// Concealment source: a ring of the most recently played samples, looped
|
|
22
|
+
// under a decaying gain to cover a gap. Sized to the fade window, with a
|
|
23
|
+
// per-sample decay that reaches the floor exactly at its end.
|
|
24
|
+
this.concealCapacity = Math.max(1, Math.floor((rate * 40) / 1000));
|
|
25
|
+
this.concealBuf = new Float32Array(this.concealCapacity);
|
|
26
|
+
this.concealDecay = Math.exp(Math.log(${e}) / this.concealCapacity);
|
|
27
|
+
// Float32 ring buffer — PLAYBACK_BUFFER_SECONDS at the context sample
|
|
28
|
+
// rate. Allocated once for the node's lifetime; per-turn state resets via
|
|
29
|
+
// resetTurn(). writePos and readPos are absolute (monotonic) sample
|
|
30
|
+
// counts; the buffer is indexed modulo capacity so a longer reply keeps
|
|
31
|
+
// playing instead of writing past the end and going silent.
|
|
32
|
+
this.capacity = rate * 60;
|
|
33
|
+
this.samples = new Float32Array(this.capacity);
|
|
34
|
+
// Cadence for the 'progress' report in process(), counted in samples so
|
|
35
|
+
// the hot path never reads a clock. Survives resetTurn(): the host clamps
|
|
36
|
+
// upward only, so a stale count costs at most one extra report.
|
|
37
|
+
this.reportIntervalSamples = Math.floor((rate * 500) / 1000);
|
|
38
|
+
this.sinceReportSamples = 0;
|
|
39
|
+
// Platform endianness probe: the wire format is PCM16 little-endian, so
|
|
40
|
+
// the Int16Array fast path in ingestBytes is only valid on LE hosts
|
|
41
|
+
// (every shipping browser target; the DataView path is the fallback).
|
|
42
|
+
this.littleEndian = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
|
|
43
|
+
this.resetTurn();
|
|
44
|
+
|
|
45
|
+
this.port.onmessage = (e) => {
|
|
46
|
+
const d = e.data;
|
|
47
|
+
if (d.event === 'write') {
|
|
48
|
+
this.ingestBytes(d.buffer);
|
|
49
|
+
} else if (d.event === 'interrupt') {
|
|
50
|
+
// Applied eagerly, not deferred to the next process(): onmessage and
|
|
51
|
+
// process() never interleave (one audio thread), and a deferred
|
|
52
|
+
// interrupt would let 'write'/'done' frames for the NEXT turn
|
|
53
|
+
// coalesce in ahead of it and be wiped by the reset along with the
|
|
54
|
+
// cancelled turn's audio.
|
|
55
|
+
this.stopTurn('interrupt');
|
|
56
|
+
} else if (d.event === 'done') {
|
|
57
|
+
this.isDone = true;
|
|
58
|
+
// Echoed back on this turn's 'stop' so the host can tell WHICH turn
|
|
59
|
+
// drained: a stop already in flight when a barge-in lands would
|
|
60
|
+
// otherwise settle the next turn's done() the moment it arrives.
|
|
61
|
+
this.doneTurn = d.turn ?? null;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Reset per-turn state so the node is reusable across replies without
|
|
67
|
+
// reallocating the sample buffer or re-instantiating the worklet.
|
|
68
|
+
resetTurn() {
|
|
69
|
+
this.isDone = false;
|
|
70
|
+
// Host turn id carried by this turn's 'done' (null until one arrives).
|
|
71
|
+
this.doneTurn = null;
|
|
72
|
+
this.playing = false;
|
|
73
|
+
// Whether any real audio has been rendered this turn. Separates a turn's
|
|
74
|
+
// pre-roll (nothing to extrapolate from, and not a defect) from a
|
|
75
|
+
// mid-turn underrun.
|
|
76
|
+
this.hasPlayed = false;
|
|
77
|
+
// Carry-over byte for split samples across chunks
|
|
78
|
+
this.carry = null;
|
|
79
|
+
this.writePos = 0;
|
|
80
|
+
this.readPos = 0;
|
|
81
|
+
// Concealment ring state and the current fade position.
|
|
82
|
+
this.concealLen = 0;
|
|
83
|
+
this.concealWrite = 0;
|
|
84
|
+
this.concealPos = 0;
|
|
85
|
+
this.concealGain = 1;
|
|
86
|
+
// Episode flags, so a multi-quantum gap counts as one event.
|
|
87
|
+
this.concealing = false;
|
|
88
|
+
this.concealedSilence = false;
|
|
89
|
+
// Reported to the host on 'stop'. A fresh object per turn: the one just
|
|
90
|
+
// posted must not be mutated by the next turn.
|
|
91
|
+
this.stats = {
|
|
92
|
+
concealedSamples: 0,
|
|
93
|
+
silentConcealedSamples: 0,
|
|
94
|
+
concealmentEvents: 0,
|
|
95
|
+
silentConcealmentEvents: 0,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// End the current turn: notify the host and rearm for the next reply.
|
|
100
|
+
// Must NOT return false from process() — a processor that stops is dead
|
|
101
|
+
// for good, forcing a new node (and buffer) per reply.
|
|
102
|
+
// \`reason\` ('interrupt' | 'done') tells the host which turn boundary this
|
|
103
|
+
// stop belongs to — interrupt-stops are dropped host-side, flush() having
|
|
104
|
+
// already settled that turn — and \`turn\` names WHICH turn drained (the id
|
|
105
|
+
// this turn's 'done' carried), so a drain-stop still in flight when a
|
|
106
|
+
// barge-in lands cannot settle the next turn's done() early.
|
|
107
|
+
stopTurn(reason) {
|
|
108
|
+
this.port.postMessage({ event: 'stop', reason, turn: this.doneTurn, stats: this.stats });
|
|
109
|
+
this.resetTurn();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Cover a quantum (from \`start\`) where real audio should have been.
|
|
113
|
+
//
|
|
114
|
+
// Before the turn's first samples there is nothing to extrapolate from, so
|
|
115
|
+
// the gap is plain silence and counted as nothing — WebRTC likewise only
|
|
116
|
+
// counts concealment once playout has begun. After that, loop the retained
|
|
117
|
+
// tail under a decaying gain: a hard zero-fill is a discontinuity mid-word,
|
|
118
|
+
// which is the click that makes a brief stall sound like breakage.
|
|
119
|
+
coverGap(out, start) {
|
|
120
|
+
if (!this.hasPlayed) {
|
|
121
|
+
out.fill(0, start);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!this.concealing) {
|
|
125
|
+
this.concealing = true;
|
|
126
|
+
this.concealedSilence = false;
|
|
127
|
+
this.stats.concealmentEvents++;
|
|
128
|
+
}
|
|
129
|
+
const total = out.length - start;
|
|
130
|
+
const len = this.concealLen;
|
|
131
|
+
let silent = 0;
|
|
132
|
+
// Second condition: once the fade has decayed to the floor, every sample
|
|
133
|
+
// the loop would emit is 0 anyway — bulk-fill instead of running 128
|
|
134
|
+
// branchy iterations per quantum for the whole tail of a long stall.
|
|
135
|
+
if (len === 0 || this.concealGain < ${e}) {
|
|
136
|
+
out.fill(0, start);
|
|
137
|
+
silent = total;
|
|
138
|
+
} else {
|
|
139
|
+
let g = this.concealGain;
|
|
140
|
+
for (let i = start; i < out.length; i++) {
|
|
141
|
+
if (g < ${e}) {
|
|
142
|
+
// The fade has run out: keep counting the gap, but stop looping a
|
|
143
|
+
// fragment that is now inaudible anyway.
|
|
144
|
+
out[i] = 0;
|
|
145
|
+
silent++;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
out[i] = this.concealBuf[this.concealPos] * g;
|
|
149
|
+
this.concealPos = this.concealPos + 1 === len ? 0 : this.concealPos + 1;
|
|
150
|
+
g *= this.concealDecay;
|
|
151
|
+
}
|
|
152
|
+
this.concealGain = g;
|
|
153
|
+
}
|
|
154
|
+
this.stats.concealedSamples += total;
|
|
155
|
+
if (silent > 0) {
|
|
156
|
+
this.stats.silentConcealedSamples += silent;
|
|
157
|
+
if (!this.concealedSilence) {
|
|
158
|
+
this.concealedSilence = true;
|
|
159
|
+
this.stats.silentConcealmentEvents++;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Retain the tail of a rendered quantum as the next gap's concealment
|
|
165
|
+
// source, and close any episode the real audio just ended.
|
|
166
|
+
rememberTail(out, n) {
|
|
167
|
+
const cap = this.concealCapacity;
|
|
168
|
+
const take = Math.min(n, cap);
|
|
169
|
+
// Bulk copies (this runs on every cleanly rendered quantum): the tail is
|
|
170
|
+
// one contiguous source run, landing in at most two ring runs.
|
|
171
|
+
const tail = out.subarray(n - take, n);
|
|
172
|
+
const first = Math.min(take, cap - this.concealWrite);
|
|
173
|
+
this.concealBuf.set(tail.subarray(0, first), this.concealWrite);
|
|
174
|
+
if (take > first) this.concealBuf.set(tail.subarray(first), 0);
|
|
175
|
+
this.concealWrite = (this.concealWrite + take) % cap;
|
|
176
|
+
this.concealLen = Math.min(cap, this.concealLen + take);
|
|
177
|
+
// Read the loop oldest-first; once the ring is full the write cursor is
|
|
178
|
+
// the oldest retained sample.
|
|
179
|
+
this.concealPos = this.concealLen === cap ? this.concealWrite : 0;
|
|
180
|
+
this.concealing = false;
|
|
181
|
+
this.concealGain = 1;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
ingestBytes(uint8) {
|
|
185
|
+
let bytes = uint8;
|
|
186
|
+
|
|
187
|
+
if (this.carry !== null) {
|
|
188
|
+
const merged = new Uint8Array(1 + bytes.length);
|
|
189
|
+
merged[0] = this.carry;
|
|
190
|
+
merged.set(bytes, 1);
|
|
191
|
+
bytes = merged;
|
|
192
|
+
this.carry = null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (bytes.length % 2 !== 0) {
|
|
196
|
+
this.carry = bytes[bytes.length - 1];
|
|
197
|
+
bytes = bytes.subarray(0, bytes.length - 1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (bytes.length === 0) return;
|
|
201
|
+
const numSamples = bytes.length / 2;
|
|
202
|
+
const cap = this.capacity;
|
|
203
|
+
const samples = this.samples;
|
|
204
|
+
if (this.littleEndian && (bytes.byteOffset & 1) === 0) {
|
|
205
|
+
// Fast path: 2-byte-aligned LE bytes wrap directly as an Int16Array;
|
|
206
|
+
// copy wrap-aware in at most two runs with no per-sample DataView call
|
|
207
|
+
// or modulo. This runs on the realtime audio thread.
|
|
208
|
+
const int16 = new Int16Array(bytes.buffer, bytes.byteOffset, numSamples);
|
|
209
|
+
let src = 0;
|
|
210
|
+
let dst = this.writePos % cap;
|
|
211
|
+
while (src < numSamples) {
|
|
212
|
+
const run = Math.min(numSamples - src, cap - dst);
|
|
213
|
+
for (let j = 0; j < run; j++) {
|
|
214
|
+
samples[dst + j] = int16[src + j] / 0x8000;
|
|
215
|
+
}
|
|
216
|
+
src += run;
|
|
217
|
+
dst = 0;
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
// Odd byte offset (or big-endian host): fall back to per-sample reads.
|
|
221
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.length);
|
|
222
|
+
let dst = this.writePos % cap;
|
|
223
|
+
for (let i = 0; i < numSamples; i++) {
|
|
224
|
+
samples[dst] = view.getInt16(i * 2, true) / 0x8000;
|
|
225
|
+
dst++;
|
|
226
|
+
if (dst === cap) dst = 0;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
this.writePos += numSamples;
|
|
230
|
+
// If the producer outran the consumer by more than the buffer holds, drop
|
|
231
|
+
// the oldest unplayed audio rather than reading samples we've overwritten.
|
|
232
|
+
if (this.writePos - this.readPos > this.capacity) {
|
|
233
|
+
this.readPos = this.writePos - this.capacity;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
process(inputs, outputs) {
|
|
238
|
+
// No output wired up yet — nothing to render this quantum. Throwing here
|
|
239
|
+
// would permanently kill the processor (the node is persistent per
|
|
240
|
+
// session), so guard like the capture processor does.
|
|
241
|
+
if (!outputs[0] || !outputs[0][0]) return true;
|
|
242
|
+
const out = outputs[0][0];
|
|
243
|
+
const avail = this.writePos - this.readPos;
|
|
244
|
+
|
|
245
|
+
// Report the unplayed backlog to the host (\`playback_progress\`). This IS
|
|
246
|
+
// the closed-loop signal: the host otherwise models playback open-loop —
|
|
247
|
+
// every forwarded chunk assumed to start playing on arrival at exactly
|
|
248
|
+
// 1.0x — and cannot see a buffer that has run ahead of the wall clock.
|
|
249
|
+
// Measured against a client draining at 0.6x, the host declared the line
|
|
250
|
+
// silent while it still held seconds of the reply, and then opened the
|
|
251
|
+
// speaking-edge gate over speech the caller had not heard.
|
|
252
|
+
//
|
|
253
|
+
// Sent from process() rather than a timer because this is the only place
|
|
254
|
+
// that sees the buffer on the audio thread; the counter is in QUANTA, so
|
|
255
|
+
// it costs an integer compare per render and no clock read. Silence is not
|
|
256
|
+
// reported: an empty buffer is what the host already assumes.
|
|
257
|
+
this.sinceReportSamples += out.length;
|
|
258
|
+
if (this.sinceReportSamples >= this.reportIntervalSamples) {
|
|
259
|
+
this.sinceReportSamples = 0;
|
|
260
|
+
if (avail > 0) {
|
|
261
|
+
this.port.postMessage({ event: 'progress', bufferedMs: (avail / this.rate) * 1000 });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Filling: wait for the target. 'done' short-circuits it — what is
|
|
266
|
+
// buffered is all there will be, so there is nothing left to wait for.
|
|
267
|
+
if (!this.playing) {
|
|
268
|
+
if (avail >= this.fillSamples || this.isDone) {
|
|
269
|
+
this.playing = true;
|
|
270
|
+
} else {
|
|
271
|
+
this.coverGap(out, 0);
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Underrun: this quantum cannot be filled and more audio is still coming.
|
|
277
|
+
// Go back to filling and cover the gap, leaving
|
|
278
|
+
// readPos untouched — the fragment stays buffered and plays intact once
|
|
279
|
+
// the buffer recovers, instead of being dribbled out a few samples at a
|
|
280
|
+
// time for the rest of the turn.
|
|
281
|
+
if (avail < out.length && !this.isDone) {
|
|
282
|
+
this.playing = false;
|
|
283
|
+
this.coverGap(out, 0);
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (avail > 0) {
|
|
288
|
+
const n = Math.min(avail, out.length);
|
|
289
|
+
// Copy from the ring buffer, splitting across the wrap boundary.
|
|
290
|
+
const start = this.readPos % this.capacity;
|
|
291
|
+
const first = Math.min(n, this.capacity - start);
|
|
292
|
+
out.set(this.samples.subarray(start, start + first), 0);
|
|
293
|
+
if (n > first) out.set(this.samples.subarray(0, n - first), first);
|
|
294
|
+
this.readPos += n;
|
|
295
|
+
// Only reachable with n < out.length on the turn's final partial
|
|
296
|
+
// quantum (the underrun branch above catches every other case).
|
|
297
|
+
out.fill(0, n);
|
|
298
|
+
this.hasPlayed = true;
|
|
299
|
+
this.rememberTail(out, n);
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Drained and done: end the turn. Not reachable mid-turn — an empty
|
|
304
|
+
// buffer with audio still coming is the underrun branch above.
|
|
305
|
+
out.fill(0);
|
|
306
|
+
if (this.isDone) {
|
|
307
|
+
this.stopTurn('done');
|
|
308
|
+
}
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
registerProcessor('playback-processor', PlaybackProcessor);
|
|
314
|
+
`,r=t(n);export{r as default};
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
6
|
+
<title>aai</title>
|
|
7
|
+
<link rel="icon" href="./favicon.ico" />
|
|
8
|
+
<style>html,body{background:#fbf8f2;margin:0}</style>
|
|
9
|
+
<script type="module" crossorigin src="./assets/index-DPfmSFmc.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="./assets/index-8ihb1Urm.css">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<main id="app"></main>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** The two mounts the default client chooses between, injectable for a spec. */
|
|
2
|
+
export type DefaultClientMounts = {
|
|
3
|
+
mountClient: (config: {
|
|
4
|
+
name?: string;
|
|
5
|
+
}) => unknown;
|
|
6
|
+
mountPage: (config: {
|
|
7
|
+
name?: string;
|
|
8
|
+
}) => unknown;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Look up the agent's front door and mount the matching shell.
|
|
12
|
+
*
|
|
13
|
+
* A function rather than the module's own side effect so the CHOICE can be
|
|
14
|
+
* driven with fake mounts; `default-client-entry.tsx` is the bundle entry that
|
|
15
|
+
* calls it. The lookup already degrades to "the agent declared nothing" on
|
|
16
|
+
* every failure path — which resolves to `page: "voice"`, the front door this
|
|
17
|
+
* can always mount.
|
|
18
|
+
*/
|
|
19
|
+
export declare function bootDefaultClient(mounts?: DefaultClientMounts): Promise<void>;
|
package/dist/define-client.d.ts
CHANGED
|
@@ -1,57 +1,200 @@
|
|
|
1
|
-
|
|
2
|
-
import { type
|
|
3
|
-
import { type
|
|
4
|
-
import {
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import { type ComponentType, type ReactNode } from "react";
|
|
3
|
+
import { type ToolDisplayConfig } from "./components/tool-config-context.ts";
|
|
4
|
+
import type { BrowserSession } from "./session/index.ts";
|
|
5
|
+
import { createBrowserSession } from "./session/index.ts";
|
|
6
|
+
import type { ClientTheme, VoiceSessionOptions } from "./types.ts";
|
|
5
7
|
/**
|
|
6
|
-
*
|
|
8
|
+
* Configuration passed to {@link mountClient}.
|
|
9
|
+
*
|
|
10
|
+
* The session-forwarded fields are picked from {@link VoiceSessionOptions}
|
|
11
|
+
* (one source of truth for types and docs) rather than re-declared — a
|
|
12
|
+
* re-declared copy is exactly how doc comments drift. It is NOT the session's
|
|
13
|
+
* own options type: that is {@link VoiceSessionOptions}, which
|
|
14
|
+
* `createBrowserSession` takes and which three of these fields come from.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* **One flat type, not a union of tiers.** `component` is what decides which
|
|
18
|
+
* shell renders — absent, the default one (StartScreen + ChatView, optional
|
|
19
|
+
* sidebar); present, the caller's own component inside the same providers —
|
|
20
|
+
* and that decision is made at runtime, where every field can be honoured. It
|
|
21
|
+
* used to be a union whose two arms banned each other's fields with `?: never`,
|
|
22
|
+
* and the failure that shape produces is recorded twice in this file's history:
|
|
23
|
+
* `mountClient({ name, component })` and `mountClient({ component, tools })` were both
|
|
24
|
+
* the natural thing to write, both were refused with *"Type 'string' is not
|
|
25
|
+
* assignable to type 'undefined'"*, and both cost a build round each time
|
|
26
|
+
* before the ban was lifted. What was left banned was `sidebar` beside a
|
|
27
|
+
* `component`, which invited the identical failure for a combination
|
|
28
|
+
* {@link mountClient} can simply render.
|
|
7
29
|
*
|
|
8
30
|
* @public
|
|
9
31
|
*/
|
|
10
|
-
export type
|
|
32
|
+
export type ClientConfig = Pick<VoiceSessionOptions, "onSessionId" | "resumeSessionId" | "location" | "phone" | "client" | "token" | "WebSocket"> & {
|
|
11
33
|
/** CSS selector or DOM element to render into. Defaults to `"#app"`. */
|
|
12
34
|
target?: string | HTMLElement;
|
|
13
35
|
/** Base URL of the AAI platform server. Derived from `location.href` by default. */
|
|
14
36
|
platformUrl?: string;
|
|
15
|
-
/** Agent title shown in the header and start screen. */
|
|
16
|
-
title?: string;
|
|
17
37
|
/** Theme color overrides. */
|
|
18
38
|
theme?: ClientTheme;
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Full custom component to render instead of the default shell.
|
|
41
|
+
*
|
|
42
|
+
* It is rendered inside the same providers the default shell gets, so every
|
|
43
|
+
* session hook, `useTheme` and the tool display config work in it unchanged.
|
|
44
|
+
*/
|
|
45
|
+
component?: ComponentType;
|
|
46
|
+
/**
|
|
47
|
+
* Agent name shown in the header and start screen — and, with a `component`,
|
|
48
|
+
* the page title, there being no shell header to put it in. Left out, the
|
|
49
|
+
* default shell asks the agent for its own declared name.
|
|
50
|
+
*/
|
|
51
|
+
name?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Optional sidebar component rendered alongside the main pane.
|
|
54
|
+
*
|
|
55
|
+
* Beside a `component` it is the custom component that becomes the main pane,
|
|
56
|
+
* in the same {@link SidebarLayout} the default shell uses.
|
|
57
|
+
*/
|
|
58
|
+
sidebar?: ComponentType;
|
|
59
|
+
/** CSS width of the sidebar. Defaults to `"18rem"`. */
|
|
60
|
+
sidebarWidth?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Which side the sidebar sits on. Defaults to `"left"`.
|
|
63
|
+
*
|
|
64
|
+
* Routed through the same {@link SidebarLayout} whether the main pane is the
|
|
65
|
+
* default shell or a `component`, for the reason `sidebar` itself is: the two
|
|
66
|
+
* branches build the same layout and a field honoured by only one of them is
|
|
67
|
+
* the shape this config used to have.
|
|
68
|
+
*/
|
|
69
|
+
sidebarPosition?: "left" | "right";
|
|
70
|
+
/**
|
|
71
|
+
* Element rendered in place of the AAI logo — on the start card, and in the
|
|
72
|
+
* shell header once the session begins.
|
|
73
|
+
*
|
|
74
|
+
* Both, because they are one mark: an agent whose start screen shows a slice
|
|
75
|
+
* of pizza and whose header shows our logo reads as two products.
|
|
76
|
+
*/
|
|
77
|
+
icon?: ReactNode;
|
|
78
|
+
/** A line under the title on the start card. */
|
|
79
|
+
subtitle?: string;
|
|
80
|
+
/** Label of the start CTA. Defaults to `"Start Conversation"`. */
|
|
81
|
+
buttonText?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Tool display config: icon and label overrides keyed by tool name.
|
|
84
|
+
*
|
|
85
|
+
* Honoured with a custom `component` too: {@link mountClient} installs it into
|
|
86
|
+
* `ToolConfigContext`, and the consumer is `ToolCallBlock` — which a custom
|
|
87
|
+
* component renders as soon as it uses `MessageList` or `ChatView`, the usual
|
|
88
|
+
* way to build one.
|
|
89
|
+
*/
|
|
90
|
+
tools?: ToolDisplayConfig;
|
|
25
91
|
};
|
|
26
92
|
/**
|
|
27
|
-
* Handle returned by {@link
|
|
93
|
+
* Handle returned by {@link mountClient} for cleanup.
|
|
28
94
|
*
|
|
29
95
|
* Implements `Disposable` so it can be used with `using`.
|
|
30
96
|
*
|
|
31
97
|
* @public
|
|
32
98
|
*/
|
|
33
99
|
export type ClientHandle = {
|
|
34
|
-
/** The underlying
|
|
35
|
-
session:
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
/**
|
|
100
|
+
/** The underlying session core. */
|
|
101
|
+
session: BrowserSession;
|
|
102
|
+
/** Unmount the UI and disconnect the session. */
|
|
103
|
+
dispose(): void;
|
|
104
|
+
/** Alias for `dispose` for use with `using`. */
|
|
105
|
+
[Symbol.dispose](): void;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* The element a mount renders into.
|
|
109
|
+
*
|
|
110
|
+
* Exported so `mountPage()` resolves its target the same way `mountClient()` does — the
|
|
111
|
+
* default selector and the "element not found" sentence are part of what an
|
|
112
|
+
* author has learned, and a second copy is how the two mounts come to disagree
|
|
113
|
+
* about which one they were given.
|
|
114
|
+
*
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
export declare function resolveContainer(target?: string | HTMLElement): HTMLElement;
|
|
118
|
+
/**
|
|
119
|
+
* What a mount hands back: unmount, and `using` support.
|
|
120
|
+
*
|
|
121
|
+
* @internal
|
|
122
|
+
*/
|
|
123
|
+
export type MountedRoot = {
|
|
124
|
+
/** Unmount the React tree (and, for `mountClient()`, dispose the session). */
|
|
39
125
|
dispose(): void;
|
|
40
126
|
/** Alias for `dispose` for use with `using`. */
|
|
41
127
|
[Symbol.dispose](): void;
|
|
42
128
|
};
|
|
129
|
+
/**
|
|
130
|
+
* Render `node` into `container` and hand back its teardown.
|
|
131
|
+
*
|
|
132
|
+
* Shared by {@link mountClient} and `mountPage()` because the plumbing is identical and
|
|
133
|
+
* the two decisions in it are not obvious enough to be re-derived: the root is
|
|
134
|
+
* created here rather than by the caller so nothing else can hold one, and the
|
|
135
|
+
* render is `flushSync` so the mount is observable to the caller's NEXT
|
|
136
|
+
* STATEMENT (and to a test) rather than scheduled — `mountClient()` returns a handle
|
|
137
|
+
* whose session is expected to be live, and `mountPage()` returns one a caller may
|
|
138
|
+
* dispose immediately.
|
|
139
|
+
*
|
|
140
|
+
* `onDispose` runs after the unmount, which is the order `mountClient()` needs: the
|
|
141
|
+
* tree comes down before the session under it goes away.
|
|
142
|
+
*
|
|
143
|
+
* @internal
|
|
144
|
+
*/
|
|
145
|
+
export declare function mountRoot(container: HTMLElement, node: ReactNode, onDispose?: () => void): MountedRoot;
|
|
43
146
|
/**
|
|
44
147
|
* Define and mount a client UI for a voice agent.
|
|
45
148
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
149
|
+
* **Config only:** leave `component` out and the default shell renders
|
|
150
|
+
* (StartScreen + ChatView, optional sidebar).
|
|
151
|
+
*
|
|
152
|
+
* **A custom component:** pass `component` and it is rendered inside the same
|
|
153
|
+
* providers instead of the default shell — beside a `sidebar` if one is given,
|
|
154
|
+
* in the same {@link SidebarLayout}. A provided `name` then also sets
|
|
155
|
+
* `document.title`, there being no shell header to show it in.
|
|
156
|
+
*
|
|
157
|
+
* Mounts into `target` — a CSS selector or DOM element, defaulting to
|
|
158
|
+
* `"#app"` — and throws `Element not found: <target>` when the selector
|
|
159
|
+
* matches nothing.
|
|
160
|
+
*
|
|
161
|
+
* @example The default shell
|
|
162
|
+
* ```tsx
|
|
163
|
+
* import { mountClient } from "@alexkroman1/aai-ui";
|
|
164
|
+
*
|
|
165
|
+
* function OrderPanel() {
|
|
166
|
+
* return <div>Cart</div>;
|
|
167
|
+
* }
|
|
168
|
+
*
|
|
169
|
+
* mountClient({
|
|
170
|
+
* name: "Pizza Ordering",
|
|
171
|
+
* theme: { bg: "#1a1a1a", primary: "#e55" },
|
|
172
|
+
* sidebar: OrderPanel,
|
|
173
|
+
* tools: { add_pizza: { icon: "🍕", label: "Adding pizza" } },
|
|
174
|
+
* });
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* @example A custom component
|
|
178
|
+
* ```tsx
|
|
179
|
+
* import { mountClient, useSession } from "@alexkroman1/aai-ui";
|
|
180
|
+
*
|
|
181
|
+
* function MyCustomApp() {
|
|
182
|
+
* const session = useSession();
|
|
183
|
+
* return <div>{session.state}</div>;
|
|
184
|
+
* }
|
|
185
|
+
*
|
|
186
|
+
* mountClient({ component: MyCustomApp });
|
|
187
|
+
* ```
|
|
49
188
|
*
|
|
50
|
-
* @param Component - The Preact component to render.
|
|
51
|
-
* @param options - Client options (target element, platform URL, theme).
|
|
52
189
|
* @returns A {@link ClientHandle} for cleanup.
|
|
53
190
|
* @throws If the target element is not found in the DOM.
|
|
54
191
|
*
|
|
55
192
|
* @public
|
|
56
193
|
*/
|
|
57
|
-
export declare function
|
|
194
|
+
export declare function mountClient(config: ClientConfig): ClientHandle;
|
|
195
|
+
/**
|
|
196
|
+
* {@link mountClient} with the session factory handed in. Not on the barrel:
|
|
197
|
+
* it is the seam a spec drives with a fake core rather than replacing the
|
|
198
|
+
* session module.
|
|
199
|
+
*/
|
|
200
|
+
export declare function mountClientWith(config: ClientConfig, createSession: typeof createBrowserSession): ClientHandle;
|