@absolutejs/voice 0.0.22-beta.93 → 0.0.22-beta.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/angular/index.d.ts +1 -0
- package/dist/angular/index.js +139 -20
- package/dist/angular/voice-turn-latency.service.d.ts +12 -0
- package/dist/client/htmxBootstrap.js +437 -39
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +182 -17
- package/dist/client/turnLatency.d.ts +19 -0
- package/dist/client/turnLatencyWidget.d.ts +30 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +175 -67
- package/dist/react/VoiceTurnLatency.d.ts +6 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +309 -50
- package/dist/react/useVoiceTurnLatency.d.ts +8 -0
- package/dist/svelte/createVoiceTurnLatency.d.ts +10 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.js +187 -16
- package/dist/turnLatency.d.ts +93 -0
- package/dist/vue/VoiceTurnLatency.d.ts +51 -0
- package/dist/vue/index.d.ts +2 -0
- package/dist/vue/index.js +303 -57
- package/dist/vue/useVoiceTurnLatency.d.ts +9 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11168,10 +11168,113 @@ var createVoiceToolContractRoutes = (options) => {
|
|
|
11168
11168
|
}
|
|
11169
11169
|
return routes;
|
|
11170
11170
|
};
|
|
11171
|
-
// src/
|
|
11171
|
+
// src/turnLatency.ts
|
|
11172
11172
|
import { Elysia as Elysia18 } from "elysia";
|
|
11173
|
-
var
|
|
11173
|
+
var DEFAULT_WARN_AFTER_MS = 1800;
|
|
11174
|
+
var DEFAULT_FAIL_AFTER_MS = 3200;
|
|
11174
11175
|
var escapeHtml19 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11176
|
+
var firstNumber2 = (values) => values.filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
11177
|
+
var summarizeTurn = (sessionId, turn, options) => {
|
|
11178
|
+
const firstTranscriptAt = firstNumber2(turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs));
|
|
11179
|
+
const finalTranscriptAt = firstNumber2(turn.transcripts.filter((transcript) => transcript.isFinal).map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs));
|
|
11180
|
+
const commitAfterFirstMs = firstTranscriptAt === undefined ? undefined : Math.max(0, turn.committedAt - firstTranscriptAt);
|
|
11181
|
+
const commitAfterFinalMs = finalTranscriptAt === undefined ? undefined : Math.max(0, turn.committedAt - finalTranscriptAt);
|
|
11182
|
+
const assistantTextStartedAt = turn.assistantText ? turn.committedAt : undefined;
|
|
11183
|
+
const totalMs = commitAfterFirstMs;
|
|
11184
|
+
const status = totalMs === undefined ? "warn" : totalMs > options.failAfterMs ? "fail" : totalMs > options.warnAfterMs ? "warn" : "pass";
|
|
11185
|
+
return {
|
|
11186
|
+
assistantTextStartedAt,
|
|
11187
|
+
committedAt: turn.committedAt,
|
|
11188
|
+
finalTranscriptAt,
|
|
11189
|
+
firstTranscriptAt,
|
|
11190
|
+
sessionId,
|
|
11191
|
+
stages: [
|
|
11192
|
+
{ label: "Speech to commit", valueMs: commitAfterFirstMs },
|
|
11193
|
+
{ label: "Final transcript to commit", valueMs: commitAfterFinalMs },
|
|
11194
|
+
{
|
|
11195
|
+
label: "Commit to assistant text",
|
|
11196
|
+
valueMs: assistantTextStartedAt === undefined ? undefined : 0
|
|
11197
|
+
}
|
|
11198
|
+
],
|
|
11199
|
+
status,
|
|
11200
|
+
text: turn.text,
|
|
11201
|
+
totalMs,
|
|
11202
|
+
turnId: turn.id
|
|
11203
|
+
};
|
|
11204
|
+
};
|
|
11205
|
+
var resolveSessions = async (options) => {
|
|
11206
|
+
if (options.sessions) {
|
|
11207
|
+
return options.sessions;
|
|
11208
|
+
}
|
|
11209
|
+
if (!options.store) {
|
|
11210
|
+
return [];
|
|
11211
|
+
}
|
|
11212
|
+
const summaries = await options.store.list();
|
|
11213
|
+
const ids = options.sessionIds ?? summaries.map((summary) => summary.id);
|
|
11214
|
+
const hydrated = await Promise.all(ids.slice(0, options.limit ?? 25).map((id) => options.store?.get(id)));
|
|
11215
|
+
const sessions = [];
|
|
11216
|
+
for (const session of hydrated) {
|
|
11217
|
+
if (session) {
|
|
11218
|
+
sessions.push(session);
|
|
11219
|
+
}
|
|
11220
|
+
}
|
|
11221
|
+
return sessions;
|
|
11222
|
+
};
|
|
11223
|
+
var summarizeVoiceTurnLatency = async (options) => {
|
|
11224
|
+
const sessions = await resolveSessions(options);
|
|
11225
|
+
const warnAfterMs = options.warnAfterMs ?? DEFAULT_WARN_AFTER_MS;
|
|
11226
|
+
const failAfterMs = options.failAfterMs ?? DEFAULT_FAIL_AFTER_MS;
|
|
11227
|
+
const turns = sessions.flatMap((session) => session.turns.map((turn) => summarizeTurn(session.id, turn, { failAfterMs, warnAfterMs }))).sort((left, right) => right.committedAt - left.committedAt);
|
|
11228
|
+
const totals = turns.map((turn) => turn.totalMs).filter((value) => typeof value === "number");
|
|
11229
|
+
const failed = turns.filter((turn) => turn.status === "fail").length;
|
|
11230
|
+
const warnings = turns.filter((turn) => turn.status === "warn").length;
|
|
11231
|
+
return {
|
|
11232
|
+
averageTotalMs: totals.length > 0 ? Math.round(totals.reduce((total, value) => total + value, 0) / totals.length) : undefined,
|
|
11233
|
+
checkedAt: Date.now(),
|
|
11234
|
+
failed,
|
|
11235
|
+
sessions: sessions.length,
|
|
11236
|
+
status: turns.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
|
|
11237
|
+
total: turns.length,
|
|
11238
|
+
turns,
|
|
11239
|
+
warnings
|
|
11240
|
+
};
|
|
11241
|
+
};
|
|
11242
|
+
var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
|
|
11243
|
+
var renderVoiceTurnLatencyHTML = (report, options = {}) => {
|
|
11244
|
+
const title = options.title ?? "Voice Turn Latency";
|
|
11245
|
+
const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml19(turn.status)}">
|
|
11246
|
+
<header><div><p class="eyebrow">${escapeHtml19(turn.sessionId)} \xB7 ${escapeHtml19(turn.turnId)}</p><h2>${escapeHtml19(turn.text || "Empty turn")}</h2></div><strong>${escapeHtml19(turn.status)}</strong></header>
|
|
11247
|
+
<dl>${turn.stages.map((stage) => `<div><dt>${escapeHtml19(stage.label)}</dt><dd>${escapeHtml19(formatMs2(stage.valueMs))}</dd></div>`).join("")}</dl>
|
|
11248
|
+
</article>`).join("");
|
|
11249
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml19(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(94,234,212,.16),rgba(251,191,36,.1))}.eyebrow{color:#5eead4;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.turn header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.empty{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{font-weight:900;margin:0}@media(max-width:800px){main{padding:18px}.turn header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">End-to-end responsiveness</p><h1>${escapeHtml19(title)}</h1><div class="summary"><span class="pill ${escapeHtml19(report.status)}">${escapeHtml19(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">avg ${escapeHtml19(formatMs2(report.averageTotalMs))}</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
|
|
11250
|
+
};
|
|
11251
|
+
var createVoiceTurnLatencyJSONHandler = (options) => async () => summarizeVoiceTurnLatency(options);
|
|
11252
|
+
var createVoiceTurnLatencyHTMLHandler = (options) => async () => {
|
|
11253
|
+
const report = await summarizeVoiceTurnLatency(options);
|
|
11254
|
+
const render = options.render ?? ((input) => renderVoiceTurnLatencyHTML(input, options));
|
|
11255
|
+
const body = await render(report);
|
|
11256
|
+
return new Response(body, {
|
|
11257
|
+
headers: {
|
|
11258
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
11259
|
+
...options.headers
|
|
11260
|
+
}
|
|
11261
|
+
});
|
|
11262
|
+
};
|
|
11263
|
+
var createVoiceTurnLatencyRoutes = (options) => {
|
|
11264
|
+
const path = options.path ?? "/api/turn-latency";
|
|
11265
|
+
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
11266
|
+
const routes = new Elysia18({
|
|
11267
|
+
name: options.name ?? "absolutejs-voice-turn-latency"
|
|
11268
|
+
}).get(path, createVoiceTurnLatencyJSONHandler(options));
|
|
11269
|
+
if (htmlPath) {
|
|
11270
|
+
routes.get(htmlPath, createVoiceTurnLatencyHTMLHandler(options));
|
|
11271
|
+
}
|
|
11272
|
+
return routes;
|
|
11273
|
+
};
|
|
11274
|
+
// src/turnQuality.ts
|
|
11275
|
+
import { Elysia as Elysia19 } from "elysia";
|
|
11276
|
+
var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
|
|
11277
|
+
var escapeHtml20 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11175
11278
|
var getTurnLatencyMs = (turn) => {
|
|
11176
11279
|
const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
11177
11280
|
if (firstTranscriptAt === undefined) {
|
|
@@ -11179,7 +11282,7 @@ var getTurnLatencyMs = (turn) => {
|
|
|
11179
11282
|
}
|
|
11180
11283
|
return Math.max(0, turn.committedAt - firstTranscriptAt);
|
|
11181
11284
|
};
|
|
11182
|
-
var
|
|
11285
|
+
var summarizeTurn2 = (sessionId, turn, options) => {
|
|
11183
11286
|
const quality = turn.quality;
|
|
11184
11287
|
const correctionChanged = quality?.correction?.changed === true;
|
|
11185
11288
|
const fallbackUsed = quality?.fallbackUsed === true;
|
|
@@ -11206,7 +11309,7 @@ var summarizeTurn = (sessionId, turn, options) => {
|
|
|
11206
11309
|
turnId: turn.id
|
|
11207
11310
|
};
|
|
11208
11311
|
};
|
|
11209
|
-
var
|
|
11312
|
+
var resolveSessions2 = async (options) => {
|
|
11210
11313
|
if (options.sessions) {
|
|
11211
11314
|
return options.sessions;
|
|
11212
11315
|
}
|
|
@@ -11225,9 +11328,9 @@ var resolveSessions = async (options) => {
|
|
|
11225
11328
|
return sessions;
|
|
11226
11329
|
};
|
|
11227
11330
|
var summarizeVoiceTurnQuality = async (options) => {
|
|
11228
|
-
const sessions = await
|
|
11331
|
+
const sessions = await resolveSessions2(options);
|
|
11229
11332
|
const confidenceWarnThreshold = options.confidenceWarnThreshold ?? DEFAULT_CONFIDENCE_WARN_THRESHOLD;
|
|
11230
|
-
const turns = sessions.flatMap((session) => session.turns.map((turn) =>
|
|
11333
|
+
const turns = sessions.flatMap((session) => session.turns.map((turn) => summarizeTurn2(session.id, turn, { confidenceWarnThreshold }))).sort((left, right) => right.committedAt - left.committedAt);
|
|
11231
11334
|
const failed = turns.filter((turn) => turn.status === "fail").length;
|
|
11232
11335
|
const warnings = turns.filter((turn) => turn.status === "warn").length;
|
|
11233
11336
|
return {
|
|
@@ -11242,24 +11345,24 @@ var summarizeVoiceTurnQuality = async (options) => {
|
|
|
11242
11345
|
};
|
|
11243
11346
|
var renderVoiceTurnQualityHTML = (report, options = {}) => {
|
|
11244
11347
|
const title = options.title ?? "Voice Turn Quality";
|
|
11245
|
-
const turns = report.turns.map((turn) => `<article class="turn ${
|
|
11348
|
+
const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml20(turn.status)}">
|
|
11246
11349
|
<div class="turn-header">
|
|
11247
11350
|
<div>
|
|
11248
|
-
<p class="eyebrow">${
|
|
11249
|
-
<h2>${
|
|
11351
|
+
<p class="eyebrow">${escapeHtml20(turn.sessionId)} \xB7 ${escapeHtml20(turn.turnId)}</p>
|
|
11352
|
+
<h2>${escapeHtml20(turn.text || "Empty turn")}</h2>
|
|
11250
11353
|
</div>
|
|
11251
|
-
<strong>${
|
|
11354
|
+
<strong>${escapeHtml20(turn.status)}</strong>
|
|
11252
11355
|
</div>
|
|
11253
11356
|
<dl>
|
|
11254
|
-
<div><dt>Source</dt><dd>${
|
|
11357
|
+
<div><dt>Source</dt><dd>${escapeHtml20(turn.source ?? "unknown")}</dd></div>
|
|
11255
11358
|
<div><dt>Confidence</dt><dd>${turn.averageConfidence === undefined ? "n/a" : `${Math.round(turn.averageConfidence * 100)}%`}</dd></div>
|
|
11256
|
-
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${
|
|
11257
|
-
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${
|
|
11359
|
+
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml20(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
|
|
11360
|
+
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml20(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
|
|
11258
11361
|
<div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
|
|
11259
11362
|
<div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
|
|
11260
11363
|
</dl>
|
|
11261
11364
|
</article>`).join("");
|
|
11262
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11365
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml20(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(251,191,36,.16),rgba(34,197,94,.1))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.turn-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.unknown{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{margin:0}@media(max-width:800px){main{padding:18px}.turn-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Realtime STT Debugging</p><h1>${escapeHtml20(title)}</h1><div class="summary"><span class="pill ${escapeHtml20(report.status)}">${escapeHtml20(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span><span class="pill">${String(report.sessions)} sessions</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
|
|
11263
11366
|
};
|
|
11264
11367
|
var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
|
|
11265
11368
|
var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
@@ -11276,7 +11379,7 @@ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
|
11276
11379
|
var createVoiceTurnQualityRoutes = (options) => {
|
|
11277
11380
|
const path = options.path ?? "/api/turn-quality";
|
|
11278
11381
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
11279
|
-
const routes = new
|
|
11382
|
+
const routes = new Elysia19({
|
|
11280
11383
|
name: options.name ?? "absolutejs-voice-turn-quality"
|
|
11281
11384
|
}).get(path, createVoiceTurnQualityJSONHandler(options));
|
|
11282
11385
|
if (htmlPath) {
|
|
@@ -11285,8 +11388,8 @@ var createVoiceTurnQualityRoutes = (options) => {
|
|
|
11285
11388
|
return routes;
|
|
11286
11389
|
};
|
|
11287
11390
|
// src/outcomeContract.ts
|
|
11288
|
-
import { Elysia as
|
|
11289
|
-
var
|
|
11391
|
+
import { Elysia as Elysia20 } from "elysia";
|
|
11392
|
+
var escapeHtml21 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11290
11393
|
var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
|
|
11291
11394
|
var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
|
|
11292
11395
|
var hydrateSessions = async (input) => {
|
|
@@ -11394,9 +11497,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
11394
11497
|
const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
|
|
11395
11498
|
<div class="contract-header">
|
|
11396
11499
|
<div>
|
|
11397
|
-
<p class="eyebrow">${
|
|
11398
|
-
<h2>${
|
|
11399
|
-
${contract.description ? `<p>${
|
|
11500
|
+
<p class="eyebrow">${escapeHtml21(contract.contractId)}</p>
|
|
11501
|
+
<h2>${escapeHtml21(contract.label ?? contract.contractId)}</h2>
|
|
11502
|
+
${contract.description ? `<p>${escapeHtml21(contract.description)}</p>` : ""}
|
|
11400
11503
|
</div>
|
|
11401
11504
|
<strong>${contract.pass ? "pass" : "fail"}</strong>
|
|
11402
11505
|
</div>
|
|
@@ -11407,9 +11510,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
11407
11510
|
<span>handoffs ${String(contract.matched.handoffs)}</span>
|
|
11408
11511
|
<span>events ${String(contract.matched.integrationEvents)}</span>
|
|
11409
11512
|
</div>
|
|
11410
|
-
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${
|
|
11513
|
+
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml21(issue.message)}</li>`).join("")}</ul>` : ""}
|
|
11411
11514
|
</section>`).join("");
|
|
11412
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11515
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml21(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(14,165,233,.12))}.eyebrow{color:#7dd3fc;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0}.summary,.grid{display:flex;flex-wrap:wrap;gap:10px}.pill,.grid span{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}li{margin:8px 0}@media(max-width:800px){main{padding:18px}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Business Outcome Verification</p><h1>${escapeHtml21(title)}</h1><div class="summary"><span class="pill ${report.status}">${report.status}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No outcome contracts configured.</p></section>'}</main></body></html>`;
|
|
11413
11516
|
};
|
|
11414
11517
|
var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
|
|
11415
11518
|
var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
@@ -11425,7 +11528,7 @@ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
|
11425
11528
|
var createVoiceOutcomeContractRoutes = (options) => {
|
|
11426
11529
|
const path = options.path ?? "/api/outcome-contracts";
|
|
11427
11530
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
11428
|
-
const routes = new
|
|
11531
|
+
const routes = new Elysia20({
|
|
11429
11532
|
name: options.name ?? "absolutejs-voice-outcome-contracts"
|
|
11430
11533
|
}).get(path, createVoiceOutcomeContractJSONHandler(options));
|
|
11431
11534
|
if (htmlPath) {
|
|
@@ -11434,7 +11537,7 @@ var createVoiceOutcomeContractRoutes = (options) => {
|
|
|
11434
11537
|
return routes;
|
|
11435
11538
|
};
|
|
11436
11539
|
// src/telephonyOutcome.ts
|
|
11437
|
-
import { Elysia as
|
|
11540
|
+
import { Elysia as Elysia21 } from "elysia";
|
|
11438
11541
|
var DEFAULT_COMPLETED_STATUSES = [
|
|
11439
11542
|
"answered",
|
|
11440
11543
|
"completed",
|
|
@@ -11505,7 +11608,7 @@ var firstString2 = (source, keys) => {
|
|
|
11505
11608
|
}
|
|
11506
11609
|
}
|
|
11507
11610
|
};
|
|
11508
|
-
var
|
|
11611
|
+
var firstNumber3 = (source, keys) => {
|
|
11509
11612
|
for (const key of keys) {
|
|
11510
11613
|
const value = source[key];
|
|
11511
11614
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
@@ -11877,7 +11980,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
|
11877
11980
|
"event_type",
|
|
11878
11981
|
"type"
|
|
11879
11982
|
]);
|
|
11880
|
-
const durationMs =
|
|
11983
|
+
const durationMs = firstNumber3(payload, ["durationMs", "duration_ms"]) ?? durationMsFromSeconds(firstNumber3(payload, [
|
|
11881
11984
|
"CallDuration",
|
|
11882
11985
|
"call_duration",
|
|
11883
11986
|
"callDuration",
|
|
@@ -11885,7 +11988,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
|
11885
11988
|
"dial_call_duration",
|
|
11886
11989
|
"duration"
|
|
11887
11990
|
]));
|
|
11888
|
-
const sipCode =
|
|
11991
|
+
const sipCode = firstNumber3(payload, [
|
|
11889
11992
|
"SipResponseCode",
|
|
11890
11993
|
"sip_response_code",
|
|
11891
11994
|
"sipCode",
|
|
@@ -12081,7 +12184,7 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
|
|
|
12081
12184
|
var createVoiceTelephonyWebhookRoutes = (options = {}) => {
|
|
12082
12185
|
const path = options.path ?? "/api/voice/telephony/webhook";
|
|
12083
12186
|
const handler = createVoiceTelephonyWebhookHandler(options);
|
|
12084
|
-
return new
|
|
12187
|
+
return new Elysia21({
|
|
12085
12188
|
name: options.name ?? "absolutejs-voice-telephony-webhooks"
|
|
12086
12189
|
}).post(path, async ({ query, request }) => {
|
|
12087
12190
|
try {
|
|
@@ -14349,7 +14452,7 @@ var createVoiceMemoryStore = () => {
|
|
|
14349
14452
|
return { get, getOrCreate, list, remove, set };
|
|
14350
14453
|
};
|
|
14351
14454
|
// src/opsWebhook.ts
|
|
14352
|
-
import { Elysia as
|
|
14455
|
+
import { Elysia as Elysia22 } from "elysia";
|
|
14353
14456
|
var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
14354
14457
|
var signVoiceOpsWebhookBody = async (input) => {
|
|
14355
14458
|
const encoder = new TextEncoder;
|
|
@@ -14479,7 +14582,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
|
|
|
14479
14582
|
};
|
|
14480
14583
|
var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
|
|
14481
14584
|
const path = options.path ?? "/api/voice-ops/webhook";
|
|
14482
|
-
return new
|
|
14585
|
+
return new Elysia22().post(path, async ({ body, request, set }) => {
|
|
14483
14586
|
const bodyText = typeof body === "string" ? body : JSON.stringify(body);
|
|
14484
14587
|
if (options.signingSecret) {
|
|
14485
14588
|
const verification = await verifyVoiceOpsWebhookSignature({
|
|
@@ -16208,7 +16311,7 @@ var createVoiceSTTRoutingCorrectionHandler = (mode = "generic") => {
|
|
|
16208
16311
|
};
|
|
16209
16312
|
// src/telephony/twilio.ts
|
|
16210
16313
|
import { Buffer as Buffer3 } from "buffer";
|
|
16211
|
-
import { Elysia as
|
|
16314
|
+
import { Elysia as Elysia23 } from "elysia";
|
|
16212
16315
|
var TWILIO_MULAW_SAMPLE_RATE = 8000;
|
|
16213
16316
|
var VOICE_PCM_SAMPLE_RATE = 16000;
|
|
16214
16317
|
var escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
@@ -16238,7 +16341,7 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
|
|
|
16238
16341
|
return parameters;
|
|
16239
16342
|
};
|
|
16240
16343
|
var joinUrlPath = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
16241
|
-
var
|
|
16344
|
+
var escapeHtml22 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16242
16345
|
var getWebhookVerificationUrl = (webhook, input) => {
|
|
16243
16346
|
if (!webhook?.verificationUrl) {
|
|
16244
16347
|
return;
|
|
@@ -16281,23 +16384,23 @@ var buildTwilioVoiceSetupStatus = async (options, input) => {
|
|
|
16281
16384
|
};
|
|
16282
16385
|
var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16283
16386
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
|
|
16284
|
-
<h1>${
|
|
16387
|
+
<h1>${escapeHtml22(title)}</h1>
|
|
16285
16388
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
16286
16389
|
<section>
|
|
16287
16390
|
<h2>URLs</h2>
|
|
16288
16391
|
<ul>
|
|
16289
|
-
<li><strong>TwiML:</strong> <code>${
|
|
16290
|
-
<li><strong>Media stream:</strong> <code>${
|
|
16291
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
16392
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml22(status.urls.twiml)}</code></li>
|
|
16393
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml22(status.urls.stream)}</code></li>
|
|
16394
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml22(status.urls.webhook)}</code></li>
|
|
16292
16395
|
</ul>
|
|
16293
16396
|
</section>
|
|
16294
16397
|
<section>
|
|
16295
16398
|
<h2>Signing</h2>
|
|
16296
16399
|
<p>Mode: <code>${status.signing.mode}</code></p>
|
|
16297
|
-
${status.signing.verificationUrl ? `<p>Verification URL: <code>${
|
|
16400
|
+
${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml22(status.signing.verificationUrl)}</code></p>` : ""}
|
|
16298
16401
|
</section>
|
|
16299
|
-
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
16300
|
-
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
16402
|
+
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml22(name)}</code></li>`).join("")}</ul></section>` : ""}
|
|
16403
|
+
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml22(warning)}</li>`).join("")}</ul></section>` : ""}
|
|
16301
16404
|
</main>`;
|
|
16302
16405
|
var extractTwilioStreamUrl = (twiml) => twiml.match(/<Stream\b[^>]*\surl="([^"]+)"/i)?.[1]?.replaceAll("&", "&");
|
|
16303
16406
|
var createSmokeCheck = (name, status, message, details) => ({
|
|
@@ -16308,20 +16411,20 @@ var createSmokeCheck = (name, status, message, details) => ({
|
|
|
16308
16411
|
});
|
|
16309
16412
|
var renderTwilioVoiceSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16310
16413
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio smoke test</p>
|
|
16311
|
-
<h1>${
|
|
16414
|
+
<h1>${escapeHtml22(title)}</h1>
|
|
16312
16415
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
16313
16416
|
<section>
|
|
16314
16417
|
<h2>Checks</h2>
|
|
16315
16418
|
<ul>
|
|
16316
|
-
${report.checks.map((check) => `<li><strong>${
|
|
16419
|
+
${report.checks.map((check) => `<li><strong>${escapeHtml22(check.name)}</strong>: ${escapeHtml22(check.status)}${check.message ? ` - ${escapeHtml22(check.message)}` : ""}</li>`).join("")}
|
|
16317
16420
|
</ul>
|
|
16318
16421
|
</section>
|
|
16319
16422
|
<section>
|
|
16320
16423
|
<h2>Observed URLs</h2>
|
|
16321
16424
|
<ul>
|
|
16322
|
-
<li><strong>TwiML:</strong> <code>${
|
|
16323
|
-
<li><strong>Stream:</strong> <code>${
|
|
16324
|
-
<li><strong>Webhook:</strong> <code>${
|
|
16425
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml22(report.setup.urls.twiml)}</code></li>
|
|
16426
|
+
<li><strong>Stream:</strong> <code>${escapeHtml22(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
|
|
16427
|
+
<li><strong>Webhook:</strong> <code>${escapeHtml22(report.setup.urls.webhook)}</code></li>
|
|
16325
16428
|
</ul>
|
|
16326
16429
|
</section>
|
|
16327
16430
|
</main>`;
|
|
@@ -16781,7 +16884,7 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
16781
16884
|
const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/twilio/smoke";
|
|
16782
16885
|
const bridges = new WeakMap;
|
|
16783
16886
|
const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
|
|
16784
|
-
const app = new
|
|
16887
|
+
const app = new Elysia23({
|
|
16785
16888
|
name: options.name ?? "absolutejs-voice-twilio"
|
|
16786
16889
|
}).get(twimlPath, async ({ query, request }) => {
|
|
16787
16890
|
const streamUrl = await resolveTwilioStreamUrl(options, {
|
|
@@ -16917,9 +17020,9 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
16917
17020
|
};
|
|
16918
17021
|
// src/telephony/telnyx.ts
|
|
16919
17022
|
import { Buffer as Buffer4 } from "buffer";
|
|
16920
|
-
import { Elysia as
|
|
17023
|
+
import { Elysia as Elysia24 } from "elysia";
|
|
16921
17024
|
var escapeXml3 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16922
|
-
var
|
|
17025
|
+
var escapeHtml23 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16923
17026
|
var joinUrlPath2 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
16924
17027
|
var resolveRequestOrigin2 = (request) => {
|
|
16925
17028
|
const url = new URL(request.url);
|
|
@@ -17120,21 +17223,21 @@ var buildTelnyxVoiceSetupStatus = async (options, input) => {
|
|
|
17120
17223
|
};
|
|
17121
17224
|
var renderTelnyxSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
17122
17225
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx setup</p>
|
|
17123
|
-
<h1>${
|
|
17226
|
+
<h1>${escapeHtml23(title)}</h1>
|
|
17124
17227
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
17125
17228
|
<ul>
|
|
17126
|
-
<li><strong>TeXML:</strong> <code>${
|
|
17127
|
-
<li><strong>Media stream:</strong> <code>${
|
|
17128
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
17229
|
+
<li><strong>TeXML:</strong> <code>${escapeHtml23(status.urls.texml)}</code></li>
|
|
17230
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml23(status.urls.stream)}</code></li>
|
|
17231
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml23(status.urls.webhook)}</code></li>
|
|
17129
17232
|
</ul>
|
|
17130
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
17131
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
17233
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml23(name)}</code></li>`).join("")}</ul>` : ""}
|
|
17234
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml23(warning)}</li>`).join("")}</ul>` : ""}
|
|
17132
17235
|
</main>`;
|
|
17133
17236
|
var renderTelnyxSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
17134
17237
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx smoke test</p>
|
|
17135
|
-
<h1>${
|
|
17238
|
+
<h1>${escapeHtml23(title)}</h1>
|
|
17136
17239
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
17137
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
17240
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml23(check.name)}</strong>: ${escapeHtml23(check.status)}${check.message ? ` - ${escapeHtml23(check.message)}` : ""}</li>`).join("")}</ul>
|
|
17138
17241
|
</main>`;
|
|
17139
17242
|
var runTelnyxSmokeTest = async (input) => {
|
|
17140
17243
|
const setup = await buildTelnyxVoiceSetupStatus(input.options, input);
|
|
@@ -17228,7 +17331,7 @@ var createTelnyxVoiceRoutes = (options = {}) => {
|
|
|
17228
17331
|
publicKey: options.webhook?.publicKey,
|
|
17229
17332
|
toleranceSeconds: options.webhook?.toleranceSeconds
|
|
17230
17333
|
}) : undefined);
|
|
17231
|
-
const app = new
|
|
17334
|
+
const app = new Elysia24({
|
|
17232
17335
|
name: options.name ?? "absolutejs-voice-telnyx"
|
|
17233
17336
|
}).get(texmlPath, async ({ query, request }) => {
|
|
17234
17337
|
const streamUrl = await resolveTelnyxStreamUrl(options, {
|
|
@@ -17338,9 +17441,9 @@ var createTelnyxVoiceRoutes = (options = {}) => {
|
|
|
17338
17441
|
};
|
|
17339
17442
|
// src/telephony/plivo.ts
|
|
17340
17443
|
import { Buffer as Buffer5 } from "buffer";
|
|
17341
|
-
import { Elysia as
|
|
17444
|
+
import { Elysia as Elysia25 } from "elysia";
|
|
17342
17445
|
var escapeXml4 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
17343
|
-
var
|
|
17446
|
+
var escapeHtml24 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
17344
17447
|
var joinUrlPath3 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
17345
17448
|
var resolveRequestOrigin3 = (request) => {
|
|
17346
17449
|
const url = new URL(request.url);
|
|
@@ -17591,21 +17694,21 @@ var buildPlivoVoiceSetupStatus = async (options, input) => {
|
|
|
17591
17694
|
};
|
|
17592
17695
|
var renderPlivoSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
17593
17696
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo setup</p>
|
|
17594
|
-
<h1>${
|
|
17697
|
+
<h1>${escapeHtml24(title)}</h1>
|
|
17595
17698
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
17596
17699
|
<ul>
|
|
17597
|
-
<li><strong>Answer XML:</strong> <code>${
|
|
17598
|
-
<li><strong>Audio stream:</strong> <code>${
|
|
17599
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
17700
|
+
<li><strong>Answer XML:</strong> <code>${escapeHtml24(status.urls.answer)}</code></li>
|
|
17701
|
+
<li><strong>Audio stream:</strong> <code>${escapeHtml24(status.urls.stream)}</code></li>
|
|
17702
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml24(status.urls.webhook)}</code></li>
|
|
17600
17703
|
</ul>
|
|
17601
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
17602
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
17704
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml24(name)}</code></li>`).join("")}</ul>` : ""}
|
|
17705
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml24(warning)}</li>`).join("")}</ul>` : ""}
|
|
17603
17706
|
</main>`;
|
|
17604
17707
|
var renderPlivoSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
17605
17708
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo smoke test</p>
|
|
17606
|
-
<h1>${
|
|
17709
|
+
<h1>${escapeHtml24(title)}</h1>
|
|
17607
17710
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
17608
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
17711
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml24(check.name)}</strong>: ${escapeHtml24(check.status)}${check.message ? ` - ${escapeHtml24(check.message)}` : ""}</li>`).join("")}</ul>
|
|
17609
17712
|
</main>`;
|
|
17610
17713
|
var runPlivoSmokeTest = async (input) => {
|
|
17611
17714
|
const setup = await buildPlivoVoiceSetupStatus(input.options, input);
|
|
@@ -17700,7 +17803,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
|
|
|
17700
17803
|
request: input.request
|
|
17701
17804
|
}) : verificationUrl ?? input.request.url
|
|
17702
17805
|
}) : undefined);
|
|
17703
|
-
const app = new
|
|
17806
|
+
const app = new Elysia25({
|
|
17704
17807
|
name: options.name ?? "absolutejs-voice-plivo"
|
|
17705
17808
|
}).get(answerPath, async ({ query, request }) => {
|
|
17706
17809
|
const streamUrl = await resolvePlivoStreamUrl(options, {
|
|
@@ -17873,6 +17976,7 @@ export {
|
|
|
17873
17976
|
transcodeTwilioInboundPayloadToPCM16,
|
|
17874
17977
|
transcodePCMToTwilioOutboundPayload,
|
|
17875
17978
|
summarizeVoiceTurnQuality,
|
|
17979
|
+
summarizeVoiceTurnLatency,
|
|
17876
17980
|
summarizeVoiceTraceTimeline,
|
|
17877
17981
|
summarizeVoiceTraceSinkDeliveries,
|
|
17878
17982
|
summarizeVoiceTrace,
|
|
@@ -17920,6 +18024,7 @@ export {
|
|
|
17920
18024
|
requeueVoiceOpsTask,
|
|
17921
18025
|
reopenVoiceOpsTask,
|
|
17922
18026
|
renderVoiceTurnQualityHTML,
|
|
18027
|
+
renderVoiceTurnLatencyHTML,
|
|
17923
18028
|
renderVoiceTraceTimelineSessionHTML,
|
|
17924
18029
|
renderVoiceTraceTimelineHTML,
|
|
17925
18030
|
renderVoiceTraceMarkdown,
|
|
@@ -17985,6 +18090,9 @@ export {
|
|
|
17985
18090
|
createVoiceTurnQualityRoutes,
|
|
17986
18091
|
createVoiceTurnQualityJSONHandler,
|
|
17987
18092
|
createVoiceTurnQualityHTMLHandler,
|
|
18093
|
+
createVoiceTurnLatencyRoutes,
|
|
18094
|
+
createVoiceTurnLatencyJSONHandler,
|
|
18095
|
+
createVoiceTurnLatencyHTMLHandler,
|
|
17988
18096
|
createVoiceTraceTimelineRoutes,
|
|
17989
18097
|
createVoiceTraceSinkStore,
|
|
17990
18098
|
createVoiceTraceSinkDeliveryWorkerLoop,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type VoiceTurnLatencyWidgetOptions } from '../client/turnLatencyWidget';
|
|
2
|
+
export type VoiceTurnLatencyProps = VoiceTurnLatencyWidgetOptions & {
|
|
3
|
+
className?: string;
|
|
4
|
+
path?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const VoiceTurnLatency: ({ className, path, ...options }: VoiceTurnLatencyProps) => import("react/jsx-runtime").JSX.Element;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { VoiceProviderCapabilities } from './VoiceProviderCapabilities';
|
|
|
4
4
|
export { VoiceProviderStatus } from './VoiceProviderStatus';
|
|
5
5
|
export { VoiceRoutingStatus } from './VoiceRoutingStatus';
|
|
6
6
|
export { VoiceTraceTimeline } from './VoiceTraceTimeline';
|
|
7
|
+
export { VoiceTurnLatency } from './VoiceTurnLatency';
|
|
7
8
|
export { VoiceTurnQuality } from './VoiceTurnQuality';
|
|
8
9
|
export { useVoiceAppKitStatus } from './useVoiceAppKitStatus';
|
|
9
10
|
export { useVoiceStream } from './useVoiceStream';
|
|
@@ -13,5 +14,6 @@ export { useVoiceProviderCapabilities } from './useVoiceProviderCapabilities';
|
|
|
13
14
|
export { useVoiceProviderSimulationControls } from './useVoiceProviderSimulationControls';
|
|
14
15
|
export { useVoiceRoutingStatus } from './useVoiceRoutingStatus';
|
|
15
16
|
export { useVoiceTraceTimeline } from './useVoiceTraceTimeline';
|
|
17
|
+
export { useVoiceTurnLatency } from './useVoiceTurnLatency';
|
|
16
18
|
export { useVoiceTurnQuality } from './useVoiceTurnQuality';
|
|
17
19
|
export { useVoiceWorkflowStatus } from './useVoiceWorkflowStatus';
|