@absolutejs/voice 0.0.22-beta.526 → 0.0.22-beta.528
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 +5 -391
- package/dist/index.js +12 -13
- package/dist/testing/index.js +12 -7
- package/package.json +2 -147
- package/dist/generated/htmxBootstrapBundle.d.ts +0 -1
- package/fixtures/README.md +0 -57
- package/fixtures/manifest.json +0 -358
- package/fixtures/pcm/dialogue-three-clean.pcm +0 -0
- package/fixtures/pcm/dialogue-three-mixed.pcm +0 -0
- package/fixtures/pcm/dialogue-two-clean.pcm +0 -0
- package/fixtures/pcm/dialogue-two-noisy.pcm +0 -0
- package/fixtures/pcm/multiturn-three-mixed.pcm +0 -0
- package/fixtures/pcm/multiturn-two-clean.pcm +0 -0
- package/fixtures/pcm/quietly-alone-clean.pcm +0 -0
- package/fixtures/pcm/rainstorms-noisy.pcm +0 -0
- package/fixtures/pcm/stella-bulgaria-bulgarian20.pcm +0 -0
- package/fixtures/pcm/stella-ghana-english507.pcm +0 -0
- package/fixtures/pcm/stella-india-english37.pcm +0 -0
- package/fixtures/pcm/stella-jamaica-jamaican-creole-english1.pcm +0 -0
- package/fixtures/pcm/stella-liberia-liberian-pidgin-english2.pcm +0 -0
- package/fixtures/pcm/stella-pakistan-english519.pcm +0 -0
- package/fixtures/pcm/stella-sierra-leone-krio5.pcm +0 -0
- package/fixtures/pcm/stella-singapore-english655.pcm +0 -0
- package/fixtures/pcm/traveled-back-route-clean.pcm +0 -0
package/README.md
CHANGED
|
@@ -4229,24 +4229,6 @@ await voice.startRecording();
|
|
|
4229
4229
|
- partial user speech starts arriving
|
|
4230
4230
|
- manual `sendAudio(...)` is called while assistant audio is playing
|
|
4231
4231
|
|
|
4232
|
-
## Duplex Benchmarks
|
|
4233
|
-
|
|
4234
|
-
The first duplex benchmark lane measures package-level barge-in interruption on the client path. It records scenario pass/fail plus local interruption latency for:
|
|
4235
|
-
|
|
4236
|
-
- manual `sendAudio(...)`
|
|
4237
|
-
- partial transcript start
|
|
4238
|
-
- input-level threshold crossing
|
|
4239
|
-
|
|
4240
|
-
Run it with:
|
|
4241
|
-
|
|
4242
|
-
```bash
|
|
4243
|
-
bun run bench:duplex
|
|
4244
|
-
```
|
|
4245
|
-
|
|
4246
|
-
That writes:
|
|
4247
|
-
|
|
4248
|
-
- `benchmark-results/duplex-barge-in.json`
|
|
4249
|
-
|
|
4250
4232
|
## Telephony
|
|
4251
4233
|
|
|
4252
4234
|
`@absolutejs/voice` now includes a first PSTN bridge layer for Twilio Media Streams. It converts inbound `audio/x-mulaw` 8 kHz frames into the PCM format the voice session expects, and converts assistant PCM audio back into outbound Twilio media events.
|
|
@@ -4293,158 +4275,16 @@ await bridge.handleMessage(mediaMessageFromTwilio);
|
|
|
4293
4275
|
|
|
4294
4276
|
The bridge also sends Twilio `clear` events on new inbound media after assistant audio has started streaming, so telephony barge-in can stop queued outbound playback.
|
|
4295
4277
|
|
|
4296
|
-
You can benchmark the package-level Twilio bridge path with:
|
|
4297
|
-
|
|
4298
|
-
```bash
|
|
4299
|
-
bun run bench:telephony:run
|
|
4300
|
-
```
|
|
4301
|
-
|
|
4302
|
-
That writes:
|
|
4303
|
-
|
|
4304
|
-
- `benchmark-results/telephony-twilio-bridge.json`
|
|
4305
|
-
- `benchmark-results/telephony-run-manifest.json`
|
|
4306
|
-
|
|
4307
|
-
For a live vendor-backed duplex smoke benchmark on the real TTS adapters, run:
|
|
4308
|
-
|
|
4309
|
-
```bash
|
|
4310
|
-
bun run bench:duplex:live:run
|
|
4311
|
-
```
|
|
4312
|
-
|
|
4313
|
-
That writes fresh results to:
|
|
4314
|
-
|
|
4315
|
-
For a live vendor-backed telephony smoke benchmark through the Twilio bridge path, run:
|
|
4316
|
-
|
|
4317
|
-
```bash
|
|
4318
|
-
bun run bench:telephony:live:run
|
|
4319
|
-
```
|
|
4320
|
-
|
|
4321
|
-
That writes:
|
|
4322
|
-
|
|
4323
|
-
- `benchmark-results/telephony-live-deepgram-elevenlabs.json`
|
|
4324
|
-
- `benchmark-results/telephony-live-run-manifest.json`
|
|
4325
|
-
|
|
4326
|
-
For a repeated live telephony stability read, run:
|
|
4327
|
-
|
|
4328
|
-
```bash
|
|
4329
|
-
bun run bench:telephony:live:series
|
|
4330
|
-
```
|
|
4331
|
-
|
|
4332
|
-
That writes:
|
|
4333
|
-
|
|
4334
|
-
- `benchmark-results/telephony-live-series-summary-runs-3.json`
|
|
4335
|
-
|
|
4336
|
-
For a live Deepgram telephony model shootout on the same PSTN path, run:
|
|
4337
|
-
|
|
4338
|
-
```bash
|
|
4339
|
-
bun run bench:telephony:live:shootout
|
|
4340
|
-
```
|
|
4341
|
-
|
|
4342
|
-
That writes:
|
|
4343
|
-
|
|
4344
|
-
- `benchmark-results/telephony-live-flux-general-en.json`
|
|
4345
|
-
- `benchmark-results/telephony-live-nova-3-phone.json`
|
|
4346
|
-
- `benchmark-results/telephony-live-shootout-manifest.json`
|
|
4347
|
-
|
|
4348
|
-
- `benchmark-results/duplex-live-elevenlabs.json`
|
|
4349
|
-
- `benchmark-results/duplex-live-openai.json`
|
|
4350
|
-
- `benchmark-results/duplex-live-all.json`
|
|
4351
|
-
- `benchmark-results/duplex-live-run-manifest.json`
|
|
4352
|
-
|
|
4353
|
-
For a browser-run duplex benchmark that uses a real headless Chrome `AudioContext` instead of the fake Node-side playback context, run:
|
|
4354
|
-
|
|
4355
|
-
```bash
|
|
4356
|
-
bun run bench:duplex:browser:run
|
|
4357
|
-
```
|
|
4358
|
-
|
|
4359
|
-
That writes fresh results to:
|
|
4360
|
-
|
|
4361
|
-
- `benchmark-results/duplex-browser-elevenlabs.json`
|
|
4362
|
-
- `benchmark-results/duplex-browser-openai.json`
|
|
4363
|
-
- `benchmark-results/duplex-browser-all.json`
|
|
4364
|
-
- `benchmark-results/duplex-browser-run-manifest.json`
|
|
4365
|
-
|
|
4366
|
-
To measure browser duplex stability across repeated runs, use:
|
|
4367
|
-
|
|
4368
|
-
```bash
|
|
4369
|
-
bun run bench:duplex:browser:series
|
|
4370
|
-
```
|
|
4371
|
-
|
|
4372
|
-
That writes:
|
|
4373
|
-
|
|
4374
|
-
- `benchmark-results/duplex-browser-series-summary-runs-3.json`
|
|
4375
|
-
- per-run provider artifacts like `benchmark-results/duplex-browser-elevenlabs-series-run-1.json`
|
|
4376
|
-
|
|
4377
|
-
For repeated interrupt-and-resume across several consecutive assistant turns, run:
|
|
4378
|
-
|
|
4379
|
-
```bash
|
|
4380
|
-
bun run bench:duplex:browser:overlap:run
|
|
4381
|
-
```
|
|
4382
|
-
|
|
4383
|
-
That writes:
|
|
4384
|
-
|
|
4385
|
-
- `benchmark-results/duplex-browser-overlap-elevenlabs.json`
|
|
4386
|
-
- `benchmark-results/duplex-browser-overlap-openai.json`
|
|
4387
|
-
- `benchmark-results/duplex-browser-overlap-all.json`
|
|
4388
|
-
- `benchmark-results/duplex-browser-overlap-run-manifest.json`
|
|
4389
|
-
|
|
4390
|
-
To measure overlap stability across repeated live browser runs, use:
|
|
4391
|
-
|
|
4392
|
-
```bash
|
|
4393
|
-
bun run bench:duplex:browser:overlap:series
|
|
4394
|
-
```
|
|
4395
|
-
|
|
4396
|
-
That writes:
|
|
4397
|
-
|
|
4398
|
-
- `benchmark-results/duplex-browser-overlap-series-summary-runs-3.json`
|
|
4399
|
-
- per-run provider artifacts like `benchmark-results/duplex-browser-overlap-elevenlabs-series-run-1.json`
|
|
4400
|
-
|
|
4401
|
-
## TTS Benchmarks
|
|
4402
|
-
|
|
4403
|
-
`@absolutejs/voice` now includes a first TTS benchmark harness for streaming output adapters. The initial metrics are:
|
|
4404
|
-
|
|
4405
|
-
- `firstAudioLatencyMs`
|
|
4406
|
-
- `elapsedMs`
|
|
4407
|
-
- `audioChunkCount`
|
|
4408
|
-
- `totalAudioBytes`
|
|
4409
|
-
- estimated PCM `audioDurationMs`
|
|
4410
|
-
- interruption responsiveness via `interruptionLatencyMs`
|
|
4411
|
-
|
|
4412
|
-
Run the full TTS suite with one command:
|
|
4413
|
-
|
|
4414
|
-
```bash
|
|
4415
|
-
bun run bench:tts:run
|
|
4416
|
-
```
|
|
4417
|
-
|
|
4418
|
-
That writes fresh results to:
|
|
4419
|
-
|
|
4420
|
-
- `benchmark-results/tts-all.json`
|
|
4421
|
-
- `benchmark-results/tts-elevenlabs.json`
|
|
4422
|
-
- `benchmark-results/tts-openai.json`
|
|
4423
|
-
- `benchmark-results/tts-run-manifest.json`
|
|
4424
|
-
|
|
4425
|
-
To measure interruption/cancel responsiveness separately:
|
|
4426
|
-
|
|
4427
|
-
```bash
|
|
4428
|
-
bun run bench:tts:interrupt:run
|
|
4429
|
-
```
|
|
4430
|
-
|
|
4431
|
-
That writes fresh interruption results to:
|
|
4432
|
-
|
|
4433
|
-
- `benchmark-results/tts-all-interrupt.json`
|
|
4434
|
-
- `benchmark-results/tts-elevenlabs-interrupt.json`
|
|
4435
|
-
- `benchmark-results/tts-openai-interrupt.json`
|
|
4436
|
-
- `benchmark-results/tts-interrupt-run-manifest.json`
|
|
4437
|
-
|
|
4438
4278
|
## Recommended Production Path
|
|
4439
4279
|
|
|
4440
|
-
The current best-performing path
|
|
4280
|
+
The current best-performing path is:
|
|
4441
4281
|
|
|
4442
4282
|
- `deepgram-flux` as primary STT
|
|
4443
4283
|
- route-level `lexicon` for pronunciation/domain entries
|
|
4444
4284
|
- route-level `phraseHints`
|
|
4445
4285
|
- route-level `correctTurn` using `createPhraseHintCorrectionHandler()`
|
|
4446
4286
|
|
|
4447
|
-
That combination outperformed the raw vendor-only paths in
|
|
4287
|
+
That combination outperformed the raw vendor-only paths in our benchmarks ([absolutejs/benchmarks](https://github.com/absolutejs/benchmarks)) because it lets AbsoluteJS repair domain-specific terms after strong base transcription instead of depending on a second STT vendor to rescue hard turns.
|
|
4448
4288
|
|
|
4449
4289
|
Minimal production-oriented example:
|
|
4450
4290
|
|
|
@@ -4527,11 +4367,7 @@ app.use(
|
|
|
4527
4367
|
|
|
4528
4368
|
- `best` maps to the current strongest in-package path: Deepgram Flux plus generic deterministic correction.
|
|
4529
4369
|
- `low-cost` maps to a cheaper/raw package path: one primary STT pass with no correction hook.
|
|
4530
|
-
- session
|
|
4531
|
-
- use `bun run bench:stt:routing:run` to benchmark both in parallel and write fresh:
|
|
4532
|
-
- `benchmark-results/sessions-best-stt-runs-3.json`
|
|
4533
|
-
- `benchmark-results/sessions-cheap-stt-runs-3.json`
|
|
4534
|
-
- `benchmark-results/stt-routing-run-manifest.json`
|
|
4370
|
+
- session cost telemetry exposes per-turn fields like `averageRelativeCostUnits`, `averagePrimaryAudioMs`, and `averageFallbackReplayAudioMs`.
|
|
4535
4371
|
|
|
4536
4372
|
## LLM Provider Routing
|
|
4537
4373
|
|
|
@@ -5175,137 +5011,9 @@ voice.bindHTMX({ element: "#voice-htmx-sync" });
|
|
|
5175
5011
|
|
|
5176
5012
|
That keeps HTMX pages declarative without inventing custom fragment endpoints for core voice session UI.
|
|
5177
5013
|
|
|
5178
|
-
##
|
|
5179
|
-
|
|
5180
|
-
The package includes a competitive benchmark harness for STT quality and responsiveness.
|
|
5181
|
-
|
|
5182
|
-
Run:
|
|
5183
|
-
|
|
5184
|
-
```bash
|
|
5185
|
-
bun run bench:vs
|
|
5186
|
-
```
|
|
5187
|
-
|
|
5188
|
-
Use profiles to focus where you want to win:
|
|
5189
|
-
|
|
5190
|
-
- `bun run bench:vs all` (default)
|
|
5191
|
-
- `bun run bench:vs all accents`
|
|
5192
|
-
- `bun run bench:vs all code-switch`
|
|
5193
|
-
- `bun run bench:vs all jargon`
|
|
5194
|
-
- `bun run bench:vs all multilingual`
|
|
5195
|
-
- `bun run bench:vs all multi-speaker`
|
|
5196
|
-
- `bun run bench:vs all telephony`
|
|
5197
|
-
- `bun run bench:vs all clean`
|
|
5198
|
-
- `bun run bench:vs all noisy`
|
|
5199
|
-
- `bun run bench:vs deepgram accents`
|
|
5200
|
-
- `bun run bench:vs deepgram-flux accents` (compare Flux candidate, default includes VAPI output if configured)
|
|
5201
|
-
- `bun run bench:vs deepgram-nova accents`
|
|
5202
|
-
|
|
5203
|
-
Current benchmark guidance:
|
|
5204
|
-
|
|
5205
|
-
- use `deepgram-flux` as the primary conversational STT path
|
|
5206
|
-
- prefer route-level `phraseHints` plus `correctTurn` over cross-vendor fallback for domain-specific accuracy
|
|
5207
|
-
- use fallback vendors only when your own traffic proves they beat the package-level correction path
|
|
5208
|
-
- do not treat `openai` as the default STT path unless your own benchmarks prove it for your traffic
|
|
5209
|
-
|
|
5210
|
-
If you use a VAPI baseline file, you can run a direct model comparison:
|
|
5211
|
-
|
|
5212
|
-
```bash
|
|
5213
|
-
bun run bench:vs:deepgram-flux
|
|
5214
|
-
```
|
|
5215
|
-
|
|
5216
|
-
To benchmark Nova vs Flux back-to-back, set the model explicitly:
|
|
5217
|
-
|
|
5218
|
-
```bash
|
|
5219
|
-
DEEPGRAM_MODEL=flux-general-en bun run bench:deepgram:accents
|
|
5220
|
-
DEEPGRAM_MODEL=nova-3 bun run bench:deepgram:accents
|
|
5221
|
-
```
|
|
5222
|
-
|
|
5223
|
-
To stress the STT path with synthesized narrowband phone audio:
|
|
5224
|
-
|
|
5225
|
-
```bash
|
|
5226
|
-
bun run bench:telephony
|
|
5227
|
-
bun run bench:telephony:run
|
|
5228
|
-
bun run bench:deepgram:telephony
|
|
5229
|
-
bun run bench:deepgram:corrected:telephony
|
|
5230
|
-
bun run bench:jargon
|
|
5231
|
-
bun run bench:deepgram:jargon
|
|
5232
|
-
bun run bench:deepgram:corrected:audit:jargon
|
|
5233
|
-
bun run bench:multi-speaker:run
|
|
5234
|
-
bun run bench:multi-speaker:analyze
|
|
5235
|
-
bun run bench:deepgram:multi-speaker
|
|
5236
|
-
```
|
|
5237
|
-
|
|
5238
|
-
To compare against Vapi or other providers, provide a baseline JSON file:
|
|
5239
|
-
|
|
5240
|
-
```bash
|
|
5241
|
-
bun run bench:vs all accents --compare /path/to/vapi-baseline.json
|
|
5242
|
-
```
|
|
5243
|
-
|
|
5244
|
-
Expected benchmark payload:
|
|
5245
|
-
|
|
5246
|
-
```json
|
|
5247
|
-
{
|
|
5248
|
-
"source": "vapi",
|
|
5249
|
-
"results": [
|
|
5250
|
-
{
|
|
5251
|
-
"adapterId": "vapi-baseline",
|
|
5252
|
-
"summary": {
|
|
5253
|
-
"passRate": 0.0,
|
|
5254
|
-
"averageWordErrorRate": 1.0,
|
|
5255
|
-
"averageTermRecall": 0.0,
|
|
5256
|
-
"averageElapsedMs": 0,
|
|
5257
|
-
"averageTimeToEndOfTurnMs": 0,
|
|
5258
|
-
"averageTimeToFirstFinalMs": 0,
|
|
5259
|
-
"averageTimeToFirstPartialMs": 0,
|
|
5260
|
-
"wordAccuracyRate": 0.0
|
|
5261
|
-
}
|
|
5262
|
-
}
|
|
5263
|
-
]
|
|
5264
|
-
}
|
|
5265
|
-
```
|
|
5266
|
-
|
|
5267
|
-
For a fast parse-only validation of arguments:
|
|
5268
|
-
|
|
5269
|
-
```bash
|
|
5270
|
-
bun run ./scripts/benchmark-vs.ts --dry-run
|
|
5271
|
-
```
|
|
5272
|
-
|
|
5273
|
-
The harness prints:
|
|
5274
|
-
|
|
5275
|
-
- pass rate and recall deltas per adapter
|
|
5276
|
-
- weighted scorecard (`passRate`, term recall, word accuracy)
|
|
5277
|
-
- optional competitor deltas (Vapi)
|
|
5278
|
-
- a markdown report beside the JSON output, for example:
|
|
5279
|
-
- `benchmark-results/vs-all-telephony.json`
|
|
5280
|
-
- `benchmark-results/vs-all-telephony.md`
|
|
5281
|
-
|
|
5282
|
-
For package-level multi-turn behavior, use the session benchmark harness instead of raw STT-only benchmarking:
|
|
5283
|
-
|
|
5284
|
-
```bash
|
|
5285
|
-
bun run bench:sessions
|
|
5286
|
-
bun run bench:deepgram:sessions
|
|
5287
|
-
bun run bench:deepgram:soak:sessions
|
|
5288
|
-
bun run bench:deepgram:hybrid:sessions
|
|
5289
|
-
bun run bench:deepgram:corrected:sessions
|
|
5290
|
-
bun run bench:deepgram:corrected:soak:sessions
|
|
5291
|
-
bun run bench:stt:routing:run
|
|
5292
|
-
bun run bench:assemblyai:sessions
|
|
5293
|
-
bun run bench:openai:sessions
|
|
5294
|
-
bun run bench:soak:run
|
|
5295
|
-
```
|
|
5296
|
-
|
|
5297
|
-
That harness runs the adapter through `VoiceSession` itself, so the output reflects reconnect handling, turn commit stability, and duplicate-turn protection rather than only raw transcript quality.
|
|
5298
|
-
|
|
5299
|
-
`bench:soak:run` is the STT-5 runner. It executes the long-session soak lane for raw Deepgram Flux, corrected Deepgram, and the reconnect resilience suite in parallel, then writes fresh JSON into `benchmark-results/` without the runs deleting each other.
|
|
5300
|
-
|
|
5301
|
-
`bench:stt:routing:run` is the STT-7 runner. It benchmarks the package’s current `best` vs `low-cost` session strategies in parallel, clears stale outputs first, and writes a manifest so the cost-aware summaries are guaranteed fresh.
|
|
5014
|
+
## Benchmarks
|
|
5302
5015
|
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
- Deepgram Flux as primary STT
|
|
5306
|
-
- phrase hints routed through the adapter layer
|
|
5307
|
-
- committed-turn correction via `createPhraseHintCorrectionHandler()`
|
|
5308
|
-
- core turn dedupe, reconnect, and transcript selection still owned by `@absolutejs/voice`
|
|
5016
|
+
Performance & accuracy benchmarks (STT, TTS, duplex, telephony, sessions) and head-to-head comparisons against Vapi live in a dedicated repo: **[absolutejs/benchmarks](https://github.com/absolutejs/benchmarks)**. They consume the published `@absolutejs/voice` package and provider adapters.
|
|
5309
5017
|
|
|
5310
5018
|
## Adapter Contract
|
|
5311
5019
|
|
|
@@ -5426,100 +5134,6 @@ Fallback triggers are evaluated at commit time:
|
|
|
5426
5134
|
|
|
5427
5135
|
The fallback adapter receives the same window of turn audio as the primary (default `8s`, configurable with `replayWindowMs`) and can only run `maxAttemptsPerTurn` times per turn.
|
|
5428
5136
|
|
|
5429
|
-
## Benchmark Fixture Sources
|
|
5430
|
-
|
|
5431
|
-
Bundled fixtures cover the current in-repo English benchmark suite. For multilingual and code-switch evaluation, add external fixture directories and let the benchmark scripts merge them automatically.
|
|
5432
|
-
|
|
5433
|
-
The public corpus builder currently assembles:
|
|
5434
|
-
|
|
5435
|
-
- FLEURS multilingual dev clips
|
|
5436
|
-
- BSC Catalan-Spanish code-switch evaluation clips
|
|
5437
|
-
- CoSHE Hindi-English code-switch evaluation clips
|
|
5438
|
-
|
|
5439
|
-
Set either:
|
|
5440
|
-
|
|
5441
|
-
- `VOICE_FIXTURE_DIR=/abs/path/to/fixtures`
|
|
5442
|
-
- `VOICE_FIXTURE_DIRS=/abs/path/one,/abs/path/two`
|
|
5443
|
-
|
|
5444
|
-
Each fixture directory must include:
|
|
5445
|
-
|
|
5446
|
-
- `manifest.json`
|
|
5447
|
-
- `pcm/*.pcm`
|
|
5448
|
-
|
|
5449
|
-
Each manifest entry can include:
|
|
5450
|
-
|
|
5451
|
-
- `language`
|
|
5452
|
-
- `tags`
|
|
5453
|
-
Use `multilingual`, `bilingual`, or `code-switch` to route fixtures into the multilingual benchmark lane.
|
|
5454
|
-
|
|
5455
|
-
Benchmark commands:
|
|
5456
|
-
|
|
5457
|
-
```bash
|
|
5458
|
-
bun run bench:multilingual
|
|
5459
|
-
bun run bench:code-switch
|
|
5460
|
-
bun run bench:code-switch:series
|
|
5461
|
-
bun run bench:code-switch:ca-es
|
|
5462
|
-
bun run bench:code-switch:ca-es:series
|
|
5463
|
-
bun run bench:code-switch:ca-es:corts:series
|
|
5464
|
-
bun run bench:code-switch:ca-es:parlament:series
|
|
5465
|
-
bun run bench:code-switch:hi-en
|
|
5466
|
-
bun run bench:code-switch:hi-en:series
|
|
5467
|
-
bun run bench:deepgram:multilingual
|
|
5468
|
-
bun run bench:deepgram:code-switch
|
|
5469
|
-
bun run bench:deepgram:code-switch:series
|
|
5470
|
-
bun run bench:deepgram:code-switch:ca-es
|
|
5471
|
-
bun run bench:deepgram:code-switch:ca-es:series
|
|
5472
|
-
bun run bench:deepgram:code-switch:ca-es:corts:series
|
|
5473
|
-
bun run bench:deepgram:code-switch:ca-es:parlament:series
|
|
5474
|
-
bun run bench:deepgram:code-switch:ca-es:nova3-multi:series
|
|
5475
|
-
bun run bench:deepgram:code-switch:ca-es:nova3-ca:series
|
|
5476
|
-
bun run bench:deepgram:code-switch:ca-es:nova3-es:series
|
|
5477
|
-
bun run bench:deepgram:code-switch:ca-es:nova2-ca:series
|
|
5478
|
-
bun run bench:deepgram:code-switch:ca-es:nova2-es:series
|
|
5479
|
-
bun run bench:deepgram:code-switch:ca-es:best:corrected:series
|
|
5480
|
-
bun run bench:deepgram:code-switch:ca-es:parlament:debug
|
|
5481
|
-
bun run bench:deepgram:code-switch:corrected:ca-es
|
|
5482
|
-
bun run bench:deepgram:code-switch:corrected:ca-es:series
|
|
5483
|
-
bun run bench:deepgram:code-switch:corrected:ca-es:corts:series
|
|
5484
|
-
bun run bench:deepgram:code-switch:corrected:ca-es:parlament:series
|
|
5485
|
-
bun run bench:deepgram:code-switch:hi-en
|
|
5486
|
-
bun run bench:deepgram:code-switch:hi-en:series
|
|
5487
|
-
bun run bench:deepgram:code-switch:corrected:hi-en
|
|
5488
|
-
bun run bench:deepgram:code-switch:corrected:hi-en:series
|
|
5489
|
-
bun run bench:deepgram:code-switch:corrected
|
|
5490
|
-
bun run bench:deepgram:code-switch:corrected:series
|
|
5491
|
-
bun run bench:assemblyai:multilingual
|
|
5492
|
-
bun run bench:assemblyai:code-switch
|
|
5493
|
-
bun run bench:openai:multilingual
|
|
5494
|
-
bun run bench:openai:code-switch
|
|
5495
|
-
bun run bench:openai:code-switch:series
|
|
5496
|
-
bun run bench:openai:code-switch:ca-es
|
|
5497
|
-
bun run bench:openai:code-switch:ca-es:series
|
|
5498
|
-
bun run bench:openai:code-switch:corrected:ca-es
|
|
5499
|
-
bun run bench:openai:code-switch:corrected:ca-es:series
|
|
5500
|
-
bun run bench:openai:code-switch:hi-en
|
|
5501
|
-
bun run bench:openai:code-switch:hi-en:series
|
|
5502
|
-
bun run bench:openai:code-switch:corrected:hi-en
|
|
5503
|
-
bun run bench:openai:code-switch:corrected:hi-en:series
|
|
5504
|
-
bun run bench:openai:code-switch:corrected
|
|
5505
|
-
bun run bench:openai:code-switch:corrected:series
|
|
5506
|
-
```
|
|
5507
|
-
|
|
5508
|
-
Current benchmark direction:
|
|
5509
|
-
|
|
5510
|
-
- `openai` is the strongest adapter on the current public multilingual corpus
|
|
5511
|
-
- `deepgram` remains the strongest browser-English path
|
|
5512
|
-
- raw code-switch remains a weaker surface for every adapter and should be benchmarked separately with `bench:code-switch`
|
|
5513
|
-
- jargon-heavy/domain-heavy English terms now have their own profile; use `bench:jargon` for the cross-adapter read and `bench:deepgram:corrected:audit:jargon` to compare `raw` vs `generic` vs `experimental` vs `benchmarkSeeded`
|
|
5514
|
-
- code-switch should be treated as language-pair-specific, not one universal lane; `ca-es` and `hi-en` now have dedicated series commands
|
|
5515
|
-
- `ca-es` also has a dedicated Deepgram model/language shootout lane so you can compare `nova-3`/`nova-2` with `multi`, `ca`, and `es` routing without overwriting results
|
|
5516
|
-
- current best `ca-es` base path is `deepgram` `nova-3` with `language=ca`; the short runner script uses that path for corrected series
|
|
5517
|
-
- `ca-es` is also split by source now: `corts_valencianes` and `parlament_parla` can be benchmarked independently, and `parlament_parla` has a dedicated transcript dump script
|
|
5518
|
-
- corrected code-switch runs now have dedicated lexicon-driven series commands so raw and corrected stability can be compared directly
|
|
5519
|
-
- multi-speaker diarization is now its own benchmark surface; use `bench:multi-speaker:run` for the parallel cross-adapter plus Deepgram-specific read
|
|
5520
|
-
- when tuning diarization specifically, use `bench:multi-speaker:analyze` to split Deepgram into clean vs noisy handoff lanes, include a corrected noisy read, and emit a speaker-pattern debug dump
|
|
5521
|
-
- use the `:series` commands when you need stability rather than a single-pass snapshot
|
|
5522
|
-
|
|
5523
5137
|
## Client Primitives
|
|
5524
5138
|
|
|
5525
5139
|
Browser and framework helpers sit on top of the same connection core:
|
package/dist/index.js
CHANGED
|
@@ -5748,9 +5748,6 @@ var createVoiceSession = (options) => {
|
|
|
5748
5748
|
return api;
|
|
5749
5749
|
};
|
|
5750
5750
|
|
|
5751
|
-
// src/generated/htmxBootstrapBundle.ts
|
|
5752
|
-
var HTMX_BOOTSTRAP_BUNDLE = 'var Hn=(n)=>{if(typeof n!=="string")return n;return document.querySelector(n)},Gn=(n,c,o,e)=>{let i=c??n.getAttribute("hx-get")??"";if(!i)return"";let l=new URL(i,window.location.origin);if(e)l.searchParams.set(o,e);else l.searchParams.delete(o);return`${l.pathname}${l.search}${l.hash}`},gn=(n,c)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let o=Hn(c.element);if(!o)return()=>{};let e=c.eventName??"voice-refresh",i=c.sessionQueryParam??"sessionId",l=()=>{let d=window,g=Gn(o,c.route,i,n.sessionId);if(g)o.setAttribute("hx-get",g);d.htmx?.process?.(o),d.htmx?.trigger?.(o,e)},t=n.subscribe(l);return l(),()=>{t()}};var Bn=(n)=>Math.max(-1,Math.min(1,n)),Wn=(n)=>{let c=new Int16Array(n.length);for(let o=0;o<n.length;o+=1){let e=Bn(n[o]??0);c[o]=e<0?e*32768:e*32767}return new Uint8Array(c.buffer)},$n=(n)=>{let c=n instanceof Uint8Array?n:new Uint8Array(n);if(c.byteLength<2)return 0;let o=new Int16Array(c.buffer,c.byteOffset,Math.floor(c.byteLength/2));if(o.length===0)return 0;let e=0;for(let i of o){let l=i/32768;e+=l*l}return Math.min(1,Math.max(0,Math.sqrt(e/o.length)*5.5))},qn=(n,c,o)=>{if(c===o)return n;let e=c/o,i=Math.round(n.length/e),l=new Float32Array(i),t=0,d=0;while(t<l.length){let g=Math.round((t+1)*e),y=0,a=0;for(let h=d;h<g&&h<n.length;h+=1)y+=n[h]??0,a+=1;l[t]=a>0?y/a:0,t+=1,d=g}return l},An=(n)=>{let c=null,o=null,e=null,i=null;return{start:async()=>{if(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia)throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let d=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!d)throw Error("Browser microphone capture requires AudioContext support.");i=await navigator.mediaDevices.getUserMedia({audio:{channelCount:n.channelCount??1}}),c=new d,o=c.createMediaStreamSource(i),e=c.createScriptProcessor(4096,1,1),e.onaudioprocess=(g)=>{let y=g.inputBuffer.getChannelData(0),a=qn(y,c?.sampleRate??48000,n.sampleRateHz??16000),h=Wn(a);n.onLevel?.($n(h)),n.onAudio(h)},o.connect(e),e.connect(c.destination)},stop:()=>{e?.disconnect(),o?.disconnect(),i?.getTracks().forEach((d)=>d.stop()),c?.close(),n.onLevel?.(0),c=null,i=null,e=null,o=null}}};var nn=(n)=>{if(typeof n==="string"&&n.trim())return n;if(n instanceof Error&&n.message.trim())return n.message;if(n&&typeof n==="object"){let c=n;for(let o of["message","reason","description"]){let e=c[o];if(typeof e==="string"&&e.trim())return e}if("error"in c)return nn(c.error);if("cause"in c)return nn(c.cause);try{return JSON.stringify(n)}catch{}}return"Unexpected error"},hn=(n)=>{switch(n.type){case"audio":return{chunk:Uint8Array.from(atob(n.chunkBase64),(c)=>c.charCodeAt(0)),format:n.format,receivedAt:n.receivedAt,turnId:n.turnId,type:"audio"};case"assistant":return{text:n.text,type:"assistant"};case"complete":return{sessionId:n.sessionId,type:"complete"};case"connection":return{reconnect:n.reconnect,type:"connection"};case"call_lifecycle":return{event:n.event,sessionId:n.sessionId,type:"call_lifecycle"};case"error":return{message:nn(n.message),type:"error"};case"final":return{transcript:n.transcript,type:"final"};case"partial":return{transcript:n.transcript,type:"partial"};case"replay":return{assistantTexts:n.assistantTexts,call:n.call,partial:n.partial,scenarioId:n.scenarioId,sessionId:n.sessionId,sessionMetadata:n.sessionMetadata,status:n.status,turns:n.turns,type:"replay"};case"session":return{sessionId:n.sessionId,sessionMetadata:n.sessionMetadata,scenarioId:n.scenarioId,status:n.status,type:"session"};case"turn":return{turn:n.turn,type:"turn"};default:return null}};var Hc=Math.PI*2;var G=(n,c,o,e)=>{n.push({code:o,message:e,severity:c})};var Xn=(n)=>n.length===0?void 0:n.reduce((c,o)=>c+o,0)/n.length,K=(n)=>n.length===0?void 0:Math.max(...n);var b=(n,c)=>{let o=n[c];return typeof o==="number"&&Number.isFinite(o)?o:void 0},Z=(n,c)=>{let o=n[c];return typeof o==="boolean"?o:void 0},O=(n,c)=>{let o=n[c];return typeof o==="string"?o:void 0},cn=(n)=>String(n.id??O(n,"ssrc")??b(n,"ssrc")??O(n,"trackIdentifier")??O(n,"mid")??"unknown"),yn=(n)=>n===void 0?void 0:n*1000;var un=(n)=>{let c={};for(let[o,e]of Object.entries(n))if(e===null||typeof e==="boolean"||typeof e==="number"||typeof e==="string")c[o]=e;return c};var Cn=(n={})=>{let c=n.stats??[],o=[],e=c.filter((s)=>s.type==="inbound-rtp"&&O(s,"kind")!=="video"),i=c.filter((s)=>s.type==="outbound-rtp"&&O(s,"kind")!=="video"),l=c.filter((s)=>s.type==="candidate-pair"),t=c.filter((s)=>(s.type==="track"||s.type==="media-source")&&O(s,"kind")==="audio"),d=l.filter((s)=>Z(s,"selected")===!0||Z(s,"nominated")===!0||O(s,"state")==="succeeded").length,g=t.filter((s)=>O(s,"readyState")!=="ended"&&O(s,"trackState")!=="ended"&&Z(s,"ended")!==!0).length,y=t.filter((s)=>O(s,"readyState")==="ended"||O(s,"trackState")==="ended"||Z(s,"ended")===!0).length,a=e.reduce((s,V)=>s+(b(V,"packetsReceived")??0),0),h=i.reduce((s,V)=>s+(b(V,"packetsSent")??0),0),r=[...e,...i].reduce((s,V)=>s+Math.max(0,b(V,"packetsLost")??0),0),M=a+r,C=M===0?0:r/M,S=e.reduce((s,V)=>s+(b(V,"bytesReceived")??0),0),w=i.reduce((s,V)=>s+(b(V,"bytesSent")??0),0),E=K(l.map((s)=>yn(b(s,"currentRoundTripTime")??b(s,"roundTripTime"))).filter((s)=>s!==void 0)),_=K([...e,...i].map((s)=>yn(b(s,"jitter"))).filter((s)=>s!==void 0)),U=K(e.map((s)=>{let V=b(s,"jitterBufferDelay"),R=b(s,"jitterBufferEmittedCount");return V!==void 0&&R!==void 0&&R>0?V/R*1000:void 0}).filter((s)=>s!==void 0)),N=t.map((s)=>b(s,"audioLevel")).filter((s)=>s!==void 0);if(n.requireConnectedCandidatePair&&l.length>0&&d===0)G(o,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(n.requireLiveAudioTrack&&g===0)G(o,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(n.maxPacketLossRatio!==void 0&&C>n.maxPacketLossRatio)G(o,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(C)} above ${String(n.maxPacketLossRatio)}.`);if(n.maxRoundTripTimeMs!==void 0&&E!==void 0&&E>n.maxRoundTripTimeMs)G(o,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(E)}ms above ${String(n.maxRoundTripTimeMs)}ms.`);if(n.maxJitterMs!==void 0&&_!==void 0&&_>n.maxJitterMs)G(o,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(_)}ms above ${String(n.maxJitterMs)}ms.`);return{activeCandidatePairs:d,audioLevelAverage:Xn(N),bytesReceived:S,bytesSent:w,checkedAt:Date.now(),endedAudioTracks:y,inboundPackets:a,issues:o,jitterBufferDelayMs:U,jitterMs:_,liveAudioTracks:g,outboundPackets:h,packetLossRatio:C,packetsLost:r,roundTripTimeMs:E,status:o.some((s)=>s.severity==="error")?"fail":o.length>0?"warn":"pass",totalStats:c.length}},Tn=async(n)=>{return[...(await n.peerConnection.getStats(n.selector??null)).values()].map(un)};var In=(n={})=>{let c=n.stats??[],o=n.previousStats??[],e=[],i=new Map(o.map((r)=>[cn(r),r])),t=c.filter((r)=>(r.type==="inbound-rtp"||r.type==="outbound-rtp")&&O(r,"kind")!=="video"&&O(r,"mediaType")!=="video").map((r)=>{let M=r.type==="outbound-rtp"?"outbound":"inbound",C=M==="outbound"?"packetsSent":"packetsReceived",S=M==="outbound"?"bytesSent":"bytesReceived",w=i.get(cn(r)),E=b(r,C),_=w?b(w,C):void 0,U=b(r,S),N=w?b(w,S):void 0,s=r.timestamp!==void 0&&w?.timestamp!==void 0?r.timestamp-w.timestamp:void 0;return{bytesDelta:U!==void 0&&N!==void 0?U-N:void 0,currentPackets:E,direction:M,id:cn(r),packetDelta:E!==void 0&&_!==void 0?E-_:void 0,previousPackets:_,timeDeltaMs:s}}),d=t.filter((r)=>r.direction==="inbound"),g=t.filter((r)=>r.direction==="outbound"),y=K(t.map((r)=>r.timeDeltaMs).filter((r)=>r!==void 0)),a=d.filter((r)=>n.maxInboundPacketStallMs!==void 0&&r.timeDeltaMs!==void 0&&r.timeDeltaMs>=n.maxInboundPacketStallMs&&r.packetDelta!==void 0&&r.packetDelta<=0).length,h=g.filter((r)=>n.maxOutboundPacketStallMs!==void 0&&r.timeDeltaMs!==void 0&&r.timeDeltaMs>=n.maxOutboundPacketStallMs&&r.packetDelta!==void 0&&r.packetDelta<=0).length;if(n.requireInboundAudio&&d.length===0)G(e,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(n.requireOutboundAudio&&g.length===0)G(e,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(n.maxGapMs!==void 0&&y!==void 0&&y>n.maxGapMs)G(e,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(y)}ms above ${String(n.maxGapMs)}ms.`);if(a>0)G(e,"error","media.webrtc_inbound_stalled",`${String(a)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(h>0)G(e,"error","media.webrtc_outbound_stalled",`${String(h)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:d.length,issues:e,maxObservedGapMs:y,outboundAudioStreams:g.length,stalledInboundStreams:a,stalledOutboundStreams:h,status:e.some((r)=>r.severity==="error")?"fail":e.length>0?"warn":"pass",streams:t,totalStats:c.length}};var Yn="/api/voice/browser-media",Jn=5000,Qn=async(n)=>n.peerConnection??await n.getPeerConnection?.()??null,kn=async(n,c)=>{let o=c.fetch??globalThis.fetch;if(!o)return;await o(c.path??Yn,{body:JSON.stringify(n),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},Vn=(n)=>{let c=null,o=[],e=async()=>{let t=await Qn(n);if(!t)return;let d=await Tn({peerConnection:t}),g=Cn({...n,stats:d}),y=n.continuity===!1?void 0:In({...n.continuity,previousStats:o,stats:d}),a={at:Date.now(),continuity:y,report:g,scenarioId:n.getScenarioId?.()??null,sessionId:n.getSessionId?.()??null};return o=d,n.onReport?.(a),await kn(a,n),a},i=()=>{e().catch((t)=>{n.onError?.(t)})},l=()=>{if(c)clearInterval(c),c=null};return{close:l,reportOnce:e,start:()=>{if(c)return;i(),c=setInterval(i,n.intervalMs??Jn)},stop:l}};var W=()=>{},zn=()=>W,Zn={callControl:W,close:W,endTurn:W,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",send:W,sendAudio:W,simulateDisconnect:W,start:()=>{},subscribe:zn},Kn=()=>crypto.randomUUID(),jn=(n,c,o)=>{let{hostname:e,port:i,protocol:l}=window.location,t=l==="https:"?"wss:":"ws:",d=i?`:${i}`:"",g=new URL(`${t}//${e}${d}${n}`);if(g.searchParams.set("sessionId",c),o)g.searchParams.set("scenarioId",o);return g.toString()},Fn=(n)=>{if(!n||typeof n!=="object"||!("type"in n))return!1;switch(n.type){case"audio":case"assistant":case"call_lifecycle":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},vn=(n)=>{if(typeof n.data!=="string")return null;try{let c=JSON.parse(n.data);return Fn(c)?c:null}catch{return null}},Mn=(n,c={})=>{if(typeof window>"u")return Zn;let o=new Set,e=c.reconnect!==!1,i=c.maxReconnectAttempts??10,l=c.pingInterval??30000,t={isConnected:!1,pendingMessages:[],scenarioId:c.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectTimeout:null,sessionId:c.sessionId??Kn(),ws:null},d=(s)=>{o.forEach((V)=>V(s))},g=()=>{if(t.pingInterval)clearInterval(t.pingInterval),t.pingInterval=null;if(t.reconnectTimeout)clearTimeout(t.reconnectTimeout),t.reconnectTimeout=null},y=()=>{if(t.ws?.readyState!==1)return;while(t.pendingMessages.length>0){let s=t.pendingMessages.shift();if(s!==void 0)t.ws.send(s)}},a=()=>{let s=Date.now()+500;t.reconnectAttempts+=1,d({reconnect:{attempts:t.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:i,nextAttemptAt:s,status:"reconnecting"},type:"connection"}),t.reconnectTimeout=setTimeout(()=>{if(t.reconnectAttempts>i){d({reconnect:{attempts:t.reconnectAttempts,maxAttempts:i,status:"exhausted"},type:"connection"});return}h()},500)},h=()=>{let s=new WebSocket(jn(n,t.sessionId,t.scenarioId));s.binaryType="arraybuffer",s.onopen=()=>{let V=t.reconnectAttempts>0;if(t.isConnected=!0,y(),V)d({reconnect:{attempts:t.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:i,status:"resumed"},type:"connection"}),t.reconnectAttempts=0;o.forEach((R)=>R({scenarioId:t.scenarioId??void 0,sessionId:t.sessionId,status:"active",type:"session"})),t.pingInterval=setInterval(()=>{if(s.readyState===1)s.send(JSON.stringify({type:"ping"}))},l)},s.onmessage=(V)=>{let R=vn(V);if(!R)return;if(R.type==="session")t.sessionId=R.sessionId,t.scenarioId=R.scenarioId??t.scenarioId;o.forEach((Y)=>Y(R))},s.onclose=(V)=>{if(t.isConnected=!1,g(),e&&V.code!==1000&&t.reconnectAttempts<i)a();else if(e&&V.code!==1000)d({reconnect:{attempts:t.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:i,status:"exhausted"},type:"connection"})},t.ws=s},r=(s)=>{if(t.ws?.readyState===1){t.ws.send(s);return}t.pendingMessages.push(s)},M=(s)=>{r(JSON.stringify(s))},C=(s={})=>{if(s.sessionId)t.sessionId=s.sessionId;if(s.scenarioId)t.scenarioId=s.scenarioId;M({type:"start",sessionId:t.sessionId,scenarioId:t.scenarioId??void 0})},S=(s)=>{r(s)},w=()=>{M({type:"end_turn"})},E=(s)=>{M({...s,type:"call_control"})},_=()=>{if(g(),t.ws)t.ws.close(1000),t.ws=null;t.isConnected=!1,o.clear()},U=()=>{if(t.ws?.readyState===1)t.ws.close(4000,"absolutejs-voice-reconnect-proof")},N=(s)=>{return o.add(s),()=>{o.delete(s)}};return h(),{callControl:E,close:_,endTurn:w,getReadyState:()=>t.ws?.readyState??3,getScenarioId:()=>t.scenarioId??"",getSessionId:()=>t.sessionId,send:M,sendAudio:S,simulateDisconnect:U,start:C,subscribe:N}};var mn=()=>({attempts:0,maxAttempts:0,status:"idle"}),pn=()=>({assistantAudio:[],assistantTexts:[],call:null,error:null,isConnected:!1,sessionMetadata:null,scenarioId:null,partial:"",reconnect:mn(),sessionId:null,status:"idle",turns:[]}),Sn=()=>{let n=pn(),c=new Set,o=()=>{c.forEach((i)=>i())};return{dispatch:(i)=>{switch(i.type){case"audio":n={...n,assistantAudio:[...n.assistantAudio,{chunk:i.chunk,format:i.format,receivedAt:i.receivedAt,turnId:i.turnId}]};break;case"assistant":n={...n,assistantTexts:[...n.assistantTexts,i.text]};break;case"complete":n={...n,sessionId:i.sessionId,status:"completed"};break;case"call_lifecycle":n={...n,call:{...n.call,disposition:i.event.type==="end"?i.event.disposition:n.call?.disposition,endedAt:i.event.type==="end"?i.event.at:n.call?.endedAt,events:[...n.call?.events??[],i.event],lastEventAt:i.event.at,startedAt:n.call?.startedAt??i.event.at},sessionId:i.sessionId};break;case"connected":n={...n,isConnected:!0,reconnect:n.reconnect.status==="reconnecting"?{...n.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:n.reconnect};break;case"connection":n={...n,reconnect:i.reconnect};break;case"disconnected":n={...n,isConnected:!1};break;case"error":n={...n,error:i.message};break;case"final":n={...n,partial:i.transcript.text,turns:n.turns.map((l)=>l)};break;case"partial":n={...n,partial:i.transcript.text};break;case"replay":n={...n,assistantTexts:[...i.assistantTexts],call:i.call??null,error:null,isConnected:i.status==="active",partial:i.partial,reconnect:n.reconnect.status==="reconnecting"?{...n.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:n.reconnect,scenarioId:i.scenarioId??n.scenarioId,sessionId:i.sessionId,sessionMetadata:i.sessionMetadata??n.sessionMetadata,status:i.status,turns:[...i.turns]};break;case"session":n={...n,error:null,scenarioId:i.scenarioId??n.scenarioId,isConnected:i.status==="active",sessionId:i.sessionId,sessionMetadata:i.sessionMetadata??n.sessionMetadata,status:i.status};break;case"turn":n={...n,partial:"",turns:[...n.turns,i.turn]};break}o()},getServerSnapshot:()=>n,getSnapshot:()=>n,subscribe:(i)=>{return c.add(i),()=>{c.delete(i)}}}};var Ln=(n,c={})=>{let o=Mn(n,c),e=Sn(),i=c.browserMedia&&typeof window<"u"?Vn({...c.browserMedia,getScenarioId:()=>c.browserMedia?c.browserMedia.getScenarioId?.()??o.getScenarioId():o.getScenarioId(),getSessionId:()=>c.browserMedia?c.browserMedia.getSessionId?.()??o.getSessionId():o.getSessionId()}):null,l=new Set,t=(a)=>Promise.resolve().then(()=>{if(!a?.sessionId&&!a?.scenarioId)return;o.start(a),i?.start()}),d=()=>{l.forEach((a)=>a())},g=()=>{if(!c.reconnectReportPath||typeof fetch>"u")return;let a=e.getSnapshot(),h=JSON.stringify({at:Date.now(),reconnect:a.reconnect,scenarioId:a.scenarioId,sessionId:o.getSessionId(),turnIds:a.turns.map((r)=>r.id)});fetch(c.reconnectReportPath,{body:h,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},y=o.subscribe((a)=>{let h=hn(a);if(h){if(e.dispatch(h),a.type==="connection")g();d()}});return{callControl(a){o.callControl(a)},close(){y(),i?.close(),o.close(),e.dispatch({type:"disconnected"}),d()},endTurn(){o.endTurn()},get error(){return e.getSnapshot().error},getServerSnapshot(){return e.getServerSnapshot()},getSnapshot(){return e.getSnapshot()},get isConnected(){return e.getSnapshot().isConnected},get scenarioId(){return e.getSnapshot().scenarioId},get sessionMetadata(){return e.getSnapshot().sessionMetadata},start:t,get partial(){return e.getSnapshot().partial},get reconnect(){return e.getSnapshot().reconnect},get sessionId(){return o.getSessionId()},get status(){return e.getSnapshot().status},get turns(){return e.getSnapshot().turns},get assistantTexts(){return e.getSnapshot().assistantTexts},get assistantAudio(){return e.getSnapshot().assistantAudio},get call(){return e.getSnapshot().call},sendAudio(a){o.sendAudio(a)},simulateDisconnect(){o.simulateDisconnect()},subscribe(a){return l.add(a),()=>{l.delete(a)}}}};var wn=(n)=>{if(!n||n.enabled===!1)return;return{enabled:!0,maxGain:n.maxGain??3,noiseGateAttenuation:n.noiseGateAttenuation??0.15,noiseGateThreshold:n.noiseGateThreshold??0.006,targetLevel:n.targetLevel??0.08}};var nc={balanced:{qualityProfile:"general",silenceMs:1400,speechThreshold:0.012,transcriptStabilityMs:1000},fast:{qualityProfile:"general",silenceMs:700,speechThreshold:0.015,transcriptStabilityMs:450},"long-form":{qualityProfile:"general",silenceMs:2200,speechThreshold:0.01,transcriptStabilityMs:1500}},cc={general:{},"accent-heavy":{silenceMs:1200,speechThreshold:0.01,transcriptStabilityMs:1200},"noisy-room":{silenceMs:2000,speechThreshold:0.02,transcriptStabilityMs:1600},"short-command":{silenceMs:500,speechThreshold:0.016,transcriptStabilityMs:420}};var Rn=(n)=>{let c=n?.profile??"fast",o=n?.qualityProfile??"general",e=nc[c],i=cc[o];return{profile:c,qualityProfile:o,silenceMs:n?.silenceMs??i.silenceMs??e.silenceMs,speechThreshold:n?.speechThreshold??i.speechThreshold??e.speechThreshold,transcriptStabilityMs:n?.transcriptStabilityMs??i.transcriptStabilityMs??e.transcriptStabilityMs}};var ec={chat:{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{qualityProfile:"short-command",profile:"balanced"}},default:{capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{qualityProfile:"general",profile:"fast"}},dictation:{audioConditioning:{enabled:!0,maxGain:2.25,noiseGateAttenuation:0.05,noiseGateThreshold:0.003,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{qualityProfile:"accent-heavy",profile:"long-form"}},"guided-intake":{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"turn-scoped",turnDetection:{qualityProfile:"accent-heavy",profile:"long-form"}},"noisy-room":{audioConditioning:{enabled:!0,maxGain:3,noiseGateAttenuation:0.12,noiseGateThreshold:0.006,targetLevel:0.085},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{qualityProfile:"noisy-room",profile:"long-form",silenceMs:2100,speechThreshold:0.02,transcriptStabilityMs:1650}},"pstn-balanced":{audioConditioning:{enabled:!0,maxGain:2.8,noiseGateAttenuation:0.07,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{qualityProfile:"noisy-room",profile:"long-form",silenceMs:660,speechThreshold:0.012,transcriptStabilityMs:300}},"pstn-fast":{audioConditioning:{enabled:!0,maxGain:2.75,noiseGateAttenuation:0.06,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{qualityProfile:"noisy-room",profile:"long-form",silenceMs:620,speechThreshold:0.012,transcriptStabilityMs:280}},reliability:{audioConditioning:{enabled:!0,maxGain:2.9,noiseGateAttenuation:0.08,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{qualityProfile:"noisy-room",profile:"long-form"}}},_n=(n="default")=>{let c=ec[n];return{audioConditioning:wn(c.audioConditioning),capture:{channelCount:c.capture?.channelCount??1,sampleRateHz:c.capture?.sampleRateHz??16000},connection:{...c.connection},name:n,sttLifecycle:c.sttLifecycle??"continuous",turnDetection:Rn(c.turnDetection)}};var oc=(n)=>({assistantAudio:[...n.assistantAudio],assistantTexts:[...n.assistantTexts],call:n.call,error:n.error,isConnected:n.isConnected,isRecording:!1,partial:n.partial,reconnect:n.reconnect,recordingError:null,sessionId:n.sessionId,sessionMetadata:n.sessionMetadata,scenarioId:n.scenarioId,status:n.status,turns:[...n.turns]}),j=(n,c={})=>{let o=_n(c.preset),e=Ln(n,{...o.connection,...c.connection}),i=null,l=oc(e),t=new Set,d=()=>{for(let C of t)C()},g=()=>{if(l={...l,assistantAudio:[...e.assistantAudio],assistantTexts:[...e.assistantTexts],call:e.call,error:e.error,isConnected:e.isConnected,partial:e.partial,reconnect:e.reconnect,sessionId:e.sessionId,sessionMetadata:e.sessionMetadata,scenarioId:e.scenarioId,status:e.status,turns:[...e.turns]},c.autoStopOnComplete!==!1&&l.status==="completed"&&l.isRecording)i?.stop(),i=null,l={...l,isRecording:!1};d()},y=e.subscribe(g);g();let a=()=>{if(i)return i;return i=An({channelCount:c.capture?.channelCount??o.capture.channelCount,onLevel:c.capture?.onLevel,onAudio:(C)=>{if(c.capture?.onAudio){c.capture.onAudio(C,e.sendAudio);return}e.sendAudio(C)},sampleRateHz:c.capture?.sampleRateHz??o.capture.sampleRateHz}),i},h=()=>{i?.stop(),i=null,l={...l,isRecording:!1},d()},r=async()=>{if(l.isRecording)return;try{l={...l,recordingError:null},d(),await a().start(),l={...l,isRecording:!0},d()}catch(C){throw i=null,l={...l,isRecording:!1,recordingError:C instanceof Error?C.message:String(C)},d(),C}};return{bindHTMX(C){return gn(e,C)},callControl:(C)=>e.callControl(C),close:()=>{y(),h(),e.close()},endTurn:()=>e.endTurn(),get error(){return l.error},getServerSnapshot:()=>l,getSnapshot:()=>l,get isConnected(){return l.isConnected},get isRecording(){return l.isRecording},get partial(){return l.partial},get recordingError(){return l.recordingError},get reconnect(){return l.reconnect},sendAudio:(C)=>e.sendAudio(C),simulateDisconnect:()=>e.simulateDisconnect(),get sessionId(){return l.sessionId},get sessionMetadata(){return l.sessionMetadata},get scenarioId(){return l.scenarioId},startRecording:r,get status(){return l.status},stopRecording:h,subscribe:(C)=>{return t.add(C),()=>{t.delete(C)}},toggleRecording:async()=>{if(l.isRecording){h();return}await r()},get turns(){return l.turns},get assistantTexts(){return l.assistantTexts},get assistantAudio(){return l.assistantAudio},get call(){return l.call}}};var tc=()=>({activeSourceCount:0,error:null,isActive:!1,isPlaying:!1,lastInterruptLatencyMs:void 0,lastPlaybackStopLatencyMs:void 0,processedChunkCount:0,queuedChunkCount:0}),ic=()=>{if(typeof window>"u")return typeof AudioContext>"u"?void 0:AudioContext;return window.AudioContext??window.webkitAudioContext},lc=(n,c)=>{let o=c.format;if(o.container!=="raw"||o.encoding!=="pcm_s16le")throw Error(`Unsupported assistant audio format: ${o.container}/${o.encoding}`);let e=c.chunk,i=Math.max(1,o.channels),l=Math.floor(e.byteLength/2),t=Math.max(1,Math.floor(l/i)),d=n.createBuffer(i,t,o.sampleRateHz),g=new DataView(e.buffer,e.byteOffset,e.byteLength);for(let y=0;y<i;y+=1){let a=d.getChannelData(y);for(let h=0;h<t;h+=1){let M=(h*i+y)*2;if(M+1>=e.byteLength){a[h]=0;continue}a[h]=g.getInt16(M,!0)/32768}}return d},F=(n,c={})=>{let o=new Set,e=new Set,i=(c.lookaheadMs??15)/1000,l=tc(),t=null,d=null,g=0,y=Promise.resolve(),a=null,h=null,r=null,M=null,C=()=>{for(let A of o)A()},S=(A)=>{l={...l,...A},C()},w=()=>{if(l.error!==null)S({error:null})},E=()=>{if(M!==null)clearTimeout(M),M=null},_=(A)=>{E(),a=null,S({activeSourceCount:e.size,isPlaying:!1,lastInterruptLatencyMs:A,lastPlaybackStopLatencyMs:l.lastPlaybackStopLatencyMs??A}),r?.(),r=null,h=null},U=(A)=>{if(!A)return 0;return Math.max(0,((A.baseLatency??0)+(A.outputLatency??0))*1000)},N=(A)=>{if(!d)return;let T=1;if(d.gain.setValueAtTime){d.gain.setValueAtTime(T,A?.currentTime??0);return}d.gain.value=T},s=(A)=>{if(!d)return;let T=0;if(d.gain.setValueAtTime){d.gain.setValueAtTime(T,A?.currentTime??0);return}d.gain.value=T},V=()=>{if(a===null||e.size>0)return;_(Date.now()-a)},R=async()=>{if(t)return t;if(c.createAudioContext)t=c.createAudioContext();else{let A=ic();if(!A)throw Error("Assistant audio playback requires AudioContext support.");t=new A}if(t.createGain)d=t.createGain(),d.connect?.(t.destination);return g=t.currentTime,t},Y=async(A)=>{let T=await R(),P=lc(T,A),L=T.createBufferSource();L.buffer=P,L.connect(d??T.destination),L.onended=()=>{e.delete(L),L.disconnect?.(),S({activeSourceCount:e.size,isPlaying:e.size>0&&l.isActive}),V()};let Q=Math.max(T.currentTime+i,g);g=Q+P.duration,e.add(L),S({activeSourceCount:e.size,isPlaying:!0}),L.start(Q)},f=(A)=>{for(let T of[...e])T.stop?.();if(g=t?t.currentTime:0,A?.forceClear){for(let T of e)T.disconnect?.();e.clear(),V()}},$=async()=>{if(!l.isActive)return;let A=n.assistantAudio.slice(l.processedChunkCount);if(A.length===0)return;try{w();for(let T of A)await Y(T);S({processedChunkCount:n.assistantAudio.length,queuedChunkCount:l.queuedChunkCount+A.length})}catch(T){S({error:T instanceof Error?T.message:String(T)})}},D=()=>{return y=y.then(()=>$(),()=>$()),y},q=n.subscribe(()=>{if(c.autoStart&&!l.isActive&&n.assistantAudio.length>0){H.start();return}if(l.isActive)D()}),H={close:async()=>{if(q(),f({forceClear:!0}),E(),r?.(),r=null,h=null,a=null,t&&t.state!=="closed")await t.close();t=null,d?.disconnect?.(),d=null,g=0,S({activeSourceCount:0,isActive:!1,isPlaying:!1})},get activeSourceCount(){return l.activeSourceCount},get error(){return l.error},getSnapshot:()=>l,get isActive(){return l.isActive},get isPlaying(){return l.isPlaying},interrupt:async()=>{let A=Date.now(),T=await R();a=A,s(T);let P=Date.now()-A+U(T);if(S({isActive:!1,isPlaying:e.size>0,lastPlaybackStopLatencyMs:P}),e.size===0){_(P);return}if(!h)h=new Promise((L)=>{r=L});E(),M=setTimeout(()=>{for(let L of e)L.disconnect?.();e.clear(),_(Date.now()-A)},250),f(),await h},get lastInterruptLatencyMs(){return l.lastInterruptLatencyMs},get lastPlaybackStopLatencyMs(){return l.lastPlaybackStopLatencyMs},pause:async()=>{if(!t){S({activeSourceCount:0,isActive:!1,isPlaying:!1});return}await t.suspend(),S({activeSourceCount:e.size,isActive:!1,isPlaying:!1})},get processedChunkCount(){return l.processedChunkCount},get queuedChunkCount(){return l.queuedChunkCount},start:async()=>{try{w();let A=await R();if(N(A),A.state==="suspended")await A.resume();S({activeSourceCount:e.size,isActive:!0,isPlaying:A.state==="running"}),await D()}catch(A){throw S({error:A instanceof Error?A.message:String(A),isActive:!1,isPlaying:!1}),A}},subscribe:(A)=>{return o.add(A),()=>{o.delete(A)}}};return H};var sc=()=>`barge-in:${Date.now()}:${crypto.randomUUID?.()??Math.random().toString(36).slice(2)}`,dc=(n,c)=>{let o=n.filter((t)=>t.status==="stopped"),e=o.map((t)=>t.latencyMs).filter((t)=>typeof t==="number"),i=o.filter((t)=>typeof t.latencyMs==="number"&&t.latencyMs>c).length,l=o.length-i;return{averageLatencyMs:e.length>0?Math.round(e.reduce((t,d)=>t+d,0)/e.length):void 0,events:[...n],failed:i,lastEvent:n.at(-1),passed:l,status:n.length===0?"empty":i>0?"fail":o.length===0?"warn":"pass",thresholdMs:c,total:o.length}},En=(n={})=>{let c=new Set,o=n.thresholdMs??250,e=n.fetch??globalThis.fetch,i=[],l=()=>{for(let g of c)g()},t=(g)=>{if(!n.path||typeof e!=="function")return;e(n.path,{body:JSON.stringify(g),headers:{"Content-Type":"application/json"},method:"POST"}).catch(()=>{})},d=(g,y)=>{let a={at:Date.now(),id:sc(),latencyMs:y.latencyMs,playbackStopLatencyMs:y.playbackStopLatencyMs,reason:y.reason,sessionId:y.sessionId,status:g,thresholdMs:o};return i.push(a),t(a),l(),a};return{getSnapshot:()=>dc(i,o),recordRequested:(g)=>d("requested",g),recordSkipped:(g)=>d("skipped",g),recordStopped:(g)=>d("stopped",g),subscribe:(g)=>{return c.add(g),()=>{c.delete(g)}}}};var rc=0.08,ac=(n,c={})=>(c.enabled??!0)&&n>=(c.interruptThreshold??rc),en=(n,c,o={})=>{let e=n.partial,i=(t)=>{if(!c.isPlaying||o.enabled===!1){o.monitor?.recordSkipped({reason:t,sessionId:n.sessionId});return}o.monitor?.recordRequested({reason:t,sessionId:n.sessionId}),c.interrupt().then(()=>{o.monitor?.recordStopped({latencyMs:c.lastInterruptLatencyMs,playbackStopLatencyMs:c.lastPlaybackStopLatencyMs,reason:t,sessionId:n.sessionId})})},l=n.subscribe(()=>{if(o.interruptOnPartial===!1){e=n.partial;return}if(!e&&n.partial)i("partial-transcript");e=n.partial});return{close:()=>{l()},handleLevel:(t)=>{if(ac(t,o))i("input-level")},sendAudio:(t)=>{i("manual-audio"),n.sendAudio(t)}}};var dn=48,gc=320,Ac=88,hc="Guided test",yc="General recording",Cc="Pick a scenario to begin the demo.",Tc="I can walk you through a short guided voice test.",Ic="I can capture one freeform recording and confirm that it landed.",Vc="Choose a scenario to begin. Guided test asks follow-up prompts. General recording just captures what you say.",Mc="Click Start general recording to capture one freeform answer.",Pn="Speak freely. When you pause, the recording will be captured.",ln="Recording saved. Start again if you want another capture.",Dn="Guided test complete. Review the saved summary below.",On="All prompts are covered. You can stop the microphone or keep speaking for extra detail.",Sc="Ready. Start guided test or general recording to begin.",Lc="Live. Answer the prompt, then click Stop microphone when finished.",bn=["Start with a quick introduction about who you are.","Now describe what you are trying to do or test.","Finish with any detail that feels blocked, risky, or unclear."],xn=(n,c,o)=>Math.min(o,Math.max(c,n)),u=(n)=>n.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll(\'"\',""").replaceAll("\'","'"),on=(n,c)=>{let o=n[c];if(typeof o==="string"&&o.trim())return o;return null},sn=(n)=>{if(typeof n==="string"&&n.trim())return n;if(n instanceof Error&&n.message.trim())return n.message;if(n&&typeof n==="object"){let c=n,o=on(c,"message")??on(c,"reason")??on(c,"description");if(o)return o;if("error"in c)return sn(c.error);if("cause"in c)return sn(c.cause);try{return JSON.stringify(n)}catch{}}return"Unexpected error"},wc=(n)=>{let c=[n.status];if(n.attempts>0||n.maxAttempts>0)c.push(`${n.attempts}/${n.maxAttempts} attempts`);if(n.nextAttemptAt){let o=Math.max(0,n.nextAttemptAt-Date.now());c.push(`retry in ${Math.ceil(o/100)/10}s`)}return c.join(" \xB7 ")},v=(n=dn)=>Array.from({length:n},()=>0),fn=(n,c,o=dn)=>{let e=n.slice(-(o-1));e.push(xn(c,0,1));while(e.length<o)e.unshift(0);return e},Rc=(n,c=gc,o=Ac)=>{let e=n.length>1?n:v(dn),i=c/(e.length-1),l=o/2,t=o*0.34;if(Math.max(...e,0)<=0.015)return`M 0 ${l} L ${c} ${l}`;let g=e.map((a,h)=>{let r=h*0.76,M=Math.sin(r)*0.78+Math.sin(r*0.41)*0.22,C=a*t,S=i*h,w=xn(l+M*C,8,o-8);return{x:S,y:w}});if(g.length===0)return`M 0 ${l} L ${c} ${l}`;let y=`M ${g[0]?.x??0} ${g[0]?.y??l}`;for(let a=1;a<g.length;a+=1){let h=g[a-1],r=g[a];if(!h||!r)continue;let M=(h.x+r.x)/2;y+=` Q ${M} ${h.y} ${r.x} ${r.y}`}return y},_c=(n)=>{if(!n)return bn;try{let c=JSON.parse(n);if(Array.isArray(c)){let o=c.filter((e)=>typeof e==="string").map((e)=>e.trim()).filter(Boolean);if(o.length>0)return o}}catch{}return bn},tn=(n)=>{if(!n)return;let c=Number(n);return Number.isFinite(c)?c:void 0},Ec=(n,c,o)=>{if(!c)return null;let e=document.querySelector(c);return e instanceof o?e:null},x=(n,c,o,e)=>{let i=c?document.querySelector(c):null;if(i instanceof o)return i;let l=n.querySelector(`#${e}`);if(l instanceof o)return l;throw Error(`Voice HTMX bootstrap could not find the required element "${e}".`)},bc=(n)=>{if(!n.mode)return Cc;if(!n.hasStarted)return n.mode==="guided"?Tc:Ic;if(n.status==="completed")return n.mode==="guided"?Dn:ln;if(n.mode==="general")return Pn;return n.guidedPrompts[n.turnCount]??On},fc=(n)=>{if(!n.mode)return Vc;if(n.status==="completed")return n.mode==="guided"?Dn:ln;if(!n.hasStarted)return n.mode==="guided"?`Click Start guided test to begin. First prompt: ${n.guidedPrompts[0]??"Answer the first prompt."}`:Mc;if(n.mode==="general")return n.turnCount===0?Pn:ln;return n.guidedPrompts[n.turnCount]??On},Pc=(n)=>{let c=n.dataset.voiceGuidedPath,o=n.dataset.voiceGeneralPath;if(!c||!o)throw Error("Voice HTMX bootstrap requires data-voice-guided-path and data-voice-general-path.");let e=_c(n.dataset.voiceGuidedPrompts),i=n.dataset.voiceGuidedLabel??hc,l=n.dataset.voiceGeneralLabel??yc,t=n.dataset.voiceReconnectReportPath,d=n.dataset.voiceBargeInPath,g=d?En({path:d,thresholdMs:tn(n.dataset.voiceBargeInThresholdMs)}):null,y=tn(n.dataset.voiceBargeInRecentWindowMs)??4000,a=tn(n.dataset.voiceBargeInSpeechThreshold)??0.04,h=x(document,n.dataset.voiceSync,HTMLElement,"voice-htmx-sync"),r=x(n,n.dataset.voiceConnection,HTMLElement,"metric-connection"),M=x(n,n.dataset.voiceError,HTMLElement,"status-error"),C=x(n,n.dataset.voiceMicrophone,HTMLElement,"status-mic"),S=x(n,n.dataset.voicePrompt,HTMLElement,"status-prompt"),w=Ec(n,n.dataset.voiceReconnect,HTMLElement),E=x(n,n.dataset.voiceChat,HTMLElement,"chat-list"),_=x(n,n.dataset.voiceStartGuided,HTMLButtonElement,"start-guided"),U=x(n,n.dataset.voiceStartGeneral,HTMLButtonElement,"start-general"),N=x(n,n.dataset.voiceStop,HTMLButtonElement,"stop-mic"),s=x(n,n.dataset.voiceMonitor,HTMLElement,"voice-monitor"),V=x(n,n.dataset.voiceMonitorCopy,HTMLElement,"voice-monitor-copy"),R=x(n,n.dataset.voiceWaveGlow,SVGPathElement,"voice-wave-glow"),Y=x(n,n.dataset.voiceWavePath,SVGPathElement,"voice-wave-path"),f=null,$={general:!1,guided:!1},D=!1,q=null,H=v(),A=null,T=null,P=j(c,{capture:{onAudio:(I,B)=>{if(A){A.sendAudio(I);return}B(I)},onLevel:(I)=>{A?.handleLevel(I),H=fn(H,I),m()}},connection:{reconnectReportPath:t},preset:"guided-intake"}),L=j(o,{capture:{onAudio:(I,B)=>{if(T){T.sendAudio(I);return}B(I)},onLevel:(I)=>{T?.handleLevel(I),H=fn(H,I),m()}},connection:{reconnectReportPath:t},preset:"dictation"}),Q=P.bindHTMX({element:h}),Un=L.bindHTMX({element:h}),k=F(P),z=F(L);A=en(P,k,{interruptThreshold:a,monitor:g??void 0}),T=en(L,z,{interruptThreshold:a,monitor:g??void 0});let J=()=>f==="general"?L:P,Oc=()=>f==="general"?z:k,m=()=>{let I=Rc(H);R.setAttribute("d",I),Y.setAttribute("d",I),V.innerHTML=`<span class="voice-live-dot"></span>${D?"Microphone live":"Microphone idle"}`,V.classList.toggle("is-live",D),s.classList.toggle("is-live",D)},X=()=>{let I=J(),B=(f?$[f]:!1)||I.turns.length>0,an=I.status;if(r.textContent=I.isConnected?"Connected":"Waiting",M.textContent=q||I.error||"None",w)w.textContent=wc(I.reconnect);C.textContent=D?Lc:Sc,S.textContent=fc({guidedPrompts:e,hasStarted:B,mode:f,status:an,turnCount:I.turns.length}),_.hidden=D,U.hidden=D,N.hidden=!D,E.innerHTML=`<article class="voice-chat-message assistant">\n <div class="voice-chat-role">${u(f==="general"?l:f==="guided"?i:"Voice demo")}</div>\n <p class="voice-turn-text">${u(bc({generalLabel:l,guidedLabel:i,guidedPrompts:e,hasStarted:B,mode:f,status:an,turnCount:I.turns.length}))}</p>\n</article>${I.turns.map((p)=>`<div class="voice-chat-stack">\n <article class="voice-chat-message user">\n <div class="voice-chat-role">You</div>\n <p class="voice-turn-text">${u(p.text)}</p>\n </article>\n ${p.assistantText?`<article class="voice-chat-message assistant">\n <div class="voice-chat-role">${u(f==="general"?l:f==="guided"?i:"Guide")}</div>\n <p class="voice-turn-text">${u(p.assistantText)}</p>\n </article>`:""}\n</div>`).join("")}${I.partial?`<article class="voice-chat-message user pending">\n <div class="voice-chat-role">Speaking</div>\n <p class="voice-turn-text">${u(I.partial)}</p>\n</article>`:""}`,m()},Nn=()=>{J().stopRecording(),D=!1,q=null,H=v(),X()},rn=async(I)=>{f=I,$={...$,[I]:!0};try{await J().startRecording(),q=null,D=!0,X()}catch(B){J().stopRecording(),D=!1,H=v(),q=sn(B),X()}};P.subscribe(()=>{if(P.assistantAudio.length>0)k.start().catch(()=>{});X()}),L.subscribe(()=>{if(L.assistantAudio.length>0)z.start().catch(()=>{});X()}),_.addEventListener("click",()=>{rn("guided")}),U.addEventListener("click",()=>{rn("general")}),N.addEventListener("click",()=>{Nn()}),n.addEventListener("absolute-voice-simulate-disconnect",()=>{J().simulateDisconnect()}),window.addEventListener("beforeunload",()=>{P.stopRecording(),L.stopRecording(),A?.close(),T?.close(),k.close(),z.close(),Q(),Un(),P.close(),L.close()}),X()},Dc=()=>{if(typeof window>"u"||typeof document>"u")return;let n=Array.from(document.querySelectorAll("[data-voice-htmx]"));for(let c of n)if(c instanceof HTMLElement)Pc(c)};Dc();export{Dc as initVoiceHTMX};\n';
|
|
5753
|
-
|
|
5754
5751
|
// src/profileSwitchRecommendation.ts
|
|
5755
5752
|
import { Elysia } from "elysia";
|
|
5756
5753
|
|
|
@@ -38391,9 +38388,6 @@ var loadHTMXBootstrap = (() => {
|
|
|
38391
38388
|
return cached;
|
|
38392
38389
|
}
|
|
38393
38390
|
cached = (async () => {
|
|
38394
|
-
if (HTMX_BOOTSTRAP_BUNDLE) {
|
|
38395
|
-
return HTMX_BOOTSTRAP_BUNDLE;
|
|
38396
|
-
}
|
|
38397
38391
|
for (const candidate of HTMX_BOOTSTRAP_DIST_CANDIDATES) {
|
|
38398
38392
|
const asset = Bun.file(candidate);
|
|
38399
38393
|
if (await asset.exists()) {
|
|
@@ -47564,22 +47558,27 @@ var DEFAULT_AUDIO_FORMAT = {
|
|
|
47564
47558
|
};
|
|
47565
47559
|
var DEFAULT_TELEPHONY_SAMPLE_RATE_HZ = 8000;
|
|
47566
47560
|
var DEFAULT_MULTI_SPEAKER_SILENCE_MS = 350;
|
|
47567
|
-
var FIXTURE_DIR_CANDIDATES = [
|
|
47568
|
-
resolve2(import.meta.dir, "..", "..", "fixtures"),
|
|
47569
|
-
resolve2(import.meta.dir, "..", "..", "..", "fixtures"),
|
|
47570
|
-
resolve2(import.meta.dir, "..", "..", "..", "..", "fixtures")
|
|
47571
|
-
];
|
|
47572
47561
|
var EXTERNAL_FIXTURE_ENV_KEYS = [
|
|
47573
47562
|
"VOICE_FIXTURE_DIR",
|
|
47574
47563
|
"VOICE_FIXTURE_DIRS"
|
|
47575
47564
|
];
|
|
47565
|
+
var resolveDefaultFixtureCandidates = () => {
|
|
47566
|
+
const envDirs = EXTERNAL_FIXTURE_ENV_KEYS.flatMap((key) => (process.env[key] ?? "").split(/[\n,]/).map((entry) => entry.trim()).filter((entry) => entry.length > 0));
|
|
47567
|
+
return [
|
|
47568
|
+
...envDirs.map((dir) => resolve2(dir)),
|
|
47569
|
+
resolve2(process.cwd(), "fixtures"),
|
|
47570
|
+
resolve2(process.cwd(), "test", "fixtures", "audio"),
|
|
47571
|
+
resolve2(import.meta.dir, "..", "..", "test", "fixtures", "audio"),
|
|
47572
|
+
resolve2(import.meta.dir, "..", "..", "..", "test", "fixtures", "audio")
|
|
47573
|
+
];
|
|
47574
|
+
};
|
|
47576
47575
|
var resolveFixtureDirectory = async () => {
|
|
47577
|
-
for (const candidate of
|
|
47576
|
+
for (const candidate of resolveDefaultFixtureCandidates()) {
|
|
47578
47577
|
if (await Bun.file(resolve2(candidate, "manifest.json")).exists()) {
|
|
47579
47578
|
return candidate;
|
|
47580
47579
|
}
|
|
47581
47580
|
}
|
|
47582
|
-
throw new Error("Unable to locate
|
|
47581
|
+
throw new Error("Unable to locate voice test fixtures. Set VOICE_FIXTURE_DIR/VOICE_FIXTURE_DIRS, or provide a fixtures/ directory (with manifest.json) in the working directory.");
|
|
47583
47582
|
};
|
|
47584
47583
|
var getVoiceFixtureDirectory = async () => resolveFixtureDirectory();
|
|
47585
47584
|
var toUniqueDirectories = (directories) => directories.filter((directory, index, list) => directory.trim().length > 0 && list.indexOf(directory) === index);
|
package/dist/testing/index.js
CHANGED
|
@@ -3557,22 +3557,27 @@ var DEFAULT_AUDIO_FORMAT = {
|
|
|
3557
3557
|
};
|
|
3558
3558
|
var DEFAULT_TELEPHONY_SAMPLE_RATE_HZ = 8000;
|
|
3559
3559
|
var DEFAULT_MULTI_SPEAKER_SILENCE_MS = 350;
|
|
3560
|
-
var FIXTURE_DIR_CANDIDATES = [
|
|
3561
|
-
resolve(import.meta.dir, "..", "..", "fixtures"),
|
|
3562
|
-
resolve(import.meta.dir, "..", "..", "..", "fixtures"),
|
|
3563
|
-
resolve(import.meta.dir, "..", "..", "..", "..", "fixtures")
|
|
3564
|
-
];
|
|
3565
3560
|
var EXTERNAL_FIXTURE_ENV_KEYS = [
|
|
3566
3561
|
"VOICE_FIXTURE_DIR",
|
|
3567
3562
|
"VOICE_FIXTURE_DIRS"
|
|
3568
3563
|
];
|
|
3564
|
+
var resolveDefaultFixtureCandidates = () => {
|
|
3565
|
+
const envDirs = EXTERNAL_FIXTURE_ENV_KEYS.flatMap((key) => (process.env[key] ?? "").split(/[\n,]/).map((entry) => entry.trim()).filter((entry) => entry.length > 0));
|
|
3566
|
+
return [
|
|
3567
|
+
...envDirs.map((dir) => resolve(dir)),
|
|
3568
|
+
resolve(process.cwd(), "fixtures"),
|
|
3569
|
+
resolve(process.cwd(), "test", "fixtures", "audio"),
|
|
3570
|
+
resolve(import.meta.dir, "..", "..", "test", "fixtures", "audio"),
|
|
3571
|
+
resolve(import.meta.dir, "..", "..", "..", "test", "fixtures", "audio")
|
|
3572
|
+
];
|
|
3573
|
+
};
|
|
3569
3574
|
var resolveFixtureDirectory = async () => {
|
|
3570
|
-
for (const candidate of
|
|
3575
|
+
for (const candidate of resolveDefaultFixtureCandidates()) {
|
|
3571
3576
|
if (await Bun.file(resolve(candidate, "manifest.json")).exists()) {
|
|
3572
3577
|
return candidate;
|
|
3573
3578
|
}
|
|
3574
3579
|
}
|
|
3575
|
-
throw new Error("Unable to locate
|
|
3580
|
+
throw new Error("Unable to locate voice test fixtures. Set VOICE_FIXTURE_DIR/VOICE_FIXTURE_DIRS, or provide a fixtures/ directory (with manifest.json) in the working directory.");
|
|
3576
3581
|
};
|
|
3577
3582
|
var getVoiceFixtureDirectory = async () => resolveFixtureDirectory();
|
|
3578
3583
|
var toUniqueDirectories = (directories) => directories.filter((directory, index, list) => directory.trim().length > 0 && list.indexOf(directory) === index);
|