@absolutejs/voice 0.0.22-beta.94 → 0.0.22-beta.96
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/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 +278 -68
- 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/testing/index.js +57 -1
- package/dist/trace.d.ts +1 -1
- package/dist/turnLatency.d.ts +95 -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/svelte/index.js
CHANGED
|
@@ -1757,6 +1757,176 @@ var createVoiceTraceTimeline = (path = "/api/voice-traces", options = {}) => {
|
|
|
1757
1757
|
getViewModel: () => createVoiceTraceTimelineViewModel(store.getSnapshot(), options)
|
|
1758
1758
|
};
|
|
1759
1759
|
};
|
|
1760
|
+
// src/client/turnLatency.ts
|
|
1761
|
+
var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) => {
|
|
1762
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1763
|
+
const response = await fetchImpl(path);
|
|
1764
|
+
if (!response.ok) {
|
|
1765
|
+
throw new Error(`Voice turn latency failed: HTTP ${response.status}`);
|
|
1766
|
+
}
|
|
1767
|
+
return await response.json();
|
|
1768
|
+
};
|
|
1769
|
+
var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
|
|
1770
|
+
const listeners = new Set;
|
|
1771
|
+
let closed = false;
|
|
1772
|
+
let timer;
|
|
1773
|
+
let snapshot = {
|
|
1774
|
+
error: null,
|
|
1775
|
+
isLoading: false
|
|
1776
|
+
};
|
|
1777
|
+
const emit = () => {
|
|
1778
|
+
for (const listener of listeners) {
|
|
1779
|
+
listener();
|
|
1780
|
+
}
|
|
1781
|
+
};
|
|
1782
|
+
const refresh = async () => {
|
|
1783
|
+
if (closed) {
|
|
1784
|
+
return snapshot.report;
|
|
1785
|
+
}
|
|
1786
|
+
snapshot = { ...snapshot, error: null, isLoading: true };
|
|
1787
|
+
emit();
|
|
1788
|
+
try {
|
|
1789
|
+
const report = await fetchVoiceTurnLatency(path, options);
|
|
1790
|
+
snapshot = {
|
|
1791
|
+
error: null,
|
|
1792
|
+
isLoading: false,
|
|
1793
|
+
report,
|
|
1794
|
+
updatedAt: Date.now()
|
|
1795
|
+
};
|
|
1796
|
+
emit();
|
|
1797
|
+
return report;
|
|
1798
|
+
} catch (error) {
|
|
1799
|
+
snapshot = {
|
|
1800
|
+
...snapshot,
|
|
1801
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1802
|
+
isLoading: false
|
|
1803
|
+
};
|
|
1804
|
+
emit();
|
|
1805
|
+
throw error;
|
|
1806
|
+
}
|
|
1807
|
+
};
|
|
1808
|
+
const close = () => {
|
|
1809
|
+
closed = true;
|
|
1810
|
+
if (timer) {
|
|
1811
|
+
clearInterval(timer);
|
|
1812
|
+
timer = undefined;
|
|
1813
|
+
}
|
|
1814
|
+
listeners.clear();
|
|
1815
|
+
};
|
|
1816
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
1817
|
+
timer = setInterval(() => {
|
|
1818
|
+
refresh().catch(() => {});
|
|
1819
|
+
}, options.intervalMs);
|
|
1820
|
+
}
|
|
1821
|
+
return {
|
|
1822
|
+
close,
|
|
1823
|
+
getServerSnapshot: () => snapshot,
|
|
1824
|
+
getSnapshot: () => snapshot,
|
|
1825
|
+
refresh,
|
|
1826
|
+
subscribe: (listener) => {
|
|
1827
|
+
listeners.add(listener);
|
|
1828
|
+
return () => {
|
|
1829
|
+
listeners.delete(listener);
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
};
|
|
1833
|
+
};
|
|
1834
|
+
|
|
1835
|
+
// src/client/turnLatencyWidget.ts
|
|
1836
|
+
var DEFAULT_TITLE6 = "Turn Latency";
|
|
1837
|
+
var DEFAULT_DESCRIPTION6 = "Per-turn timing from first transcript to commit and assistant response start.";
|
|
1838
|
+
var escapeHtml7 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1839
|
+
var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
|
|
1840
|
+
var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
|
|
1841
|
+
const turns = (snapshot.report?.turns ?? []).map((turn) => ({
|
|
1842
|
+
...turn,
|
|
1843
|
+
label: turn.text || "Empty turn",
|
|
1844
|
+
rows: turn.stages.map((stage) => ({
|
|
1845
|
+
label: stage.label,
|
|
1846
|
+
value: formatMs2(stage.valueMs)
|
|
1847
|
+
}))
|
|
1848
|
+
}));
|
|
1849
|
+
const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
|
|
1850
|
+
const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
|
|
1851
|
+
return {
|
|
1852
|
+
description: options.description ?? DEFAULT_DESCRIPTION6,
|
|
1853
|
+
error: snapshot.error,
|
|
1854
|
+
isLoading: snapshot.isLoading,
|
|
1855
|
+
label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} slow` : warningCount > 0 ? `${warningCount} warnings` : `avg ${formatMs2(snapshot.report?.averageTotalMs)}` : snapshot.isLoading ? "Checking" : "No turns",
|
|
1856
|
+
status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
1857
|
+
title: options.title ?? DEFAULT_TITLE6,
|
|
1858
|
+
turns,
|
|
1859
|
+
updatedAt: snapshot.updatedAt
|
|
1860
|
+
};
|
|
1861
|
+
};
|
|
1862
|
+
var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
|
|
1863
|
+
const model = createVoiceTurnLatencyViewModel(snapshot, options);
|
|
1864
|
+
const turns = model.turns.length ? `<div class="absolute-voice-turn-latency__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-latency__turn absolute-voice-turn-latency__turn--${escapeHtml7(turn.status)}">
|
|
1865
|
+
<header>
|
|
1866
|
+
<strong>${escapeHtml7(turn.label)}</strong>
|
|
1867
|
+
<span>${escapeHtml7(turn.status)}</span>
|
|
1868
|
+
</header>
|
|
1869
|
+
<dl>${turn.rows.map((row) => `<div>
|
|
1870
|
+
<dt>${escapeHtml7(row.label)}</dt>
|
|
1871
|
+
<dd>${escapeHtml7(row.value)}</dd>
|
|
1872
|
+
</div>`).join("")}</dl>
|
|
1873
|
+
</article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
|
|
1874
|
+
return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml7(model.status)}">
|
|
1875
|
+
<header class="absolute-voice-turn-latency__header">
|
|
1876
|
+
<span class="absolute-voice-turn-latency__eyebrow">${escapeHtml7(model.title)}</span>
|
|
1877
|
+
<strong class="absolute-voice-turn-latency__label">${escapeHtml7(model.label)}</strong>
|
|
1878
|
+
</header>
|
|
1879
|
+
<p class="absolute-voice-turn-latency__description">${escapeHtml7(model.description)}</p>
|
|
1880
|
+
${turns}
|
|
1881
|
+
${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml7(model.error)}</p>` : ""}
|
|
1882
|
+
</section>`;
|
|
1883
|
+
};
|
|
1884
|
+
var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
|
|
1885
|
+
const store = createVoiceTurnLatencyStore(path, options);
|
|
1886
|
+
const render = () => {
|
|
1887
|
+
element.innerHTML = renderVoiceTurnLatencyHTML(store.getSnapshot(), options);
|
|
1888
|
+
};
|
|
1889
|
+
const unsubscribe = store.subscribe(render);
|
|
1890
|
+
render();
|
|
1891
|
+
store.refresh().catch(() => {});
|
|
1892
|
+
return {
|
|
1893
|
+
close: () => {
|
|
1894
|
+
unsubscribe();
|
|
1895
|
+
store.close();
|
|
1896
|
+
},
|
|
1897
|
+
refresh: store.refresh
|
|
1898
|
+
};
|
|
1899
|
+
};
|
|
1900
|
+
var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") => {
|
|
1901
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
customElements.define(tagName, class AbsoluteVoiceTurnLatencyElement extends HTMLElement {
|
|
1905
|
+
mounted;
|
|
1906
|
+
connectedCallback() {
|
|
1907
|
+
const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
|
|
1908
|
+
this.mounted = mountVoiceTurnLatency(this, this.getAttribute("path") ?? "/api/turn-latency", {
|
|
1909
|
+
description: this.getAttribute("description") ?? undefined,
|
|
1910
|
+
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
1911
|
+
title: this.getAttribute("title") ?? undefined
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
disconnectedCallback() {
|
|
1915
|
+
this.mounted?.close();
|
|
1916
|
+
this.mounted = undefined;
|
|
1917
|
+
}
|
|
1918
|
+
});
|
|
1919
|
+
};
|
|
1920
|
+
|
|
1921
|
+
// src/svelte/createVoiceTurnLatency.ts
|
|
1922
|
+
var createVoiceTurnLatency = (path = "/api/turn-latency", options = {}) => {
|
|
1923
|
+
const store = createVoiceTurnLatencyStore(path, options);
|
|
1924
|
+
return {
|
|
1925
|
+
...store,
|
|
1926
|
+
getHTML: () => renderVoiceTurnLatencyHTML(store.getSnapshot(), options),
|
|
1927
|
+
getViewModel: () => createVoiceTurnLatencyViewModel(store.getSnapshot(), options)
|
|
1928
|
+
};
|
|
1929
|
+
};
|
|
1760
1930
|
// src/client/turnQuality.ts
|
|
1761
1931
|
var fetchVoiceTurnQuality = async (path = "/api/turn-quality", options = {}) => {
|
|
1762
1932
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
@@ -1837,9 +2007,9 @@ var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) =>
|
|
|
1837
2007
|
};
|
|
1838
2008
|
|
|
1839
2009
|
// src/client/turnQualityWidget.ts
|
|
1840
|
-
var
|
|
1841
|
-
var
|
|
1842
|
-
var
|
|
2010
|
+
var DEFAULT_TITLE7 = "Turn Quality";
|
|
2011
|
+
var DEFAULT_DESCRIPTION7 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
|
|
2012
|
+
var escapeHtml8 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1843
2013
|
var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
|
|
1844
2014
|
var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
|
|
1845
2015
|
var getTurnDetail = (turn) => {
|
|
@@ -1877,37 +2047,37 @@ var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
|
|
|
1877
2047
|
const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
|
|
1878
2048
|
const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
|
|
1879
2049
|
return {
|
|
1880
|
-
description: options.description ??
|
|
2050
|
+
description: options.description ?? DEFAULT_DESCRIPTION7,
|
|
1881
2051
|
error: snapshot.error,
|
|
1882
2052
|
isLoading: snapshot.isLoading,
|
|
1883
2053
|
label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} failed` : warningCount > 0 ? `${warningCount} warnings` : `${turns.length} healthy` : snapshot.isLoading ? "Checking" : "No turns",
|
|
1884
2054
|
status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
1885
|
-
title: options.title ??
|
|
2055
|
+
title: options.title ?? DEFAULT_TITLE7,
|
|
1886
2056
|
turns,
|
|
1887
2057
|
updatedAt: snapshot.updatedAt
|
|
1888
2058
|
};
|
|
1889
2059
|
};
|
|
1890
2060
|
var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
|
|
1891
2061
|
const model = createVoiceTurnQualityViewModel(snapshot, options);
|
|
1892
|
-
const turns = model.turns.length ? `<div class="absolute-voice-turn-quality__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-quality__turn absolute-voice-turn-quality__turn--${
|
|
2062
|
+
const turns = model.turns.length ? `<div class="absolute-voice-turn-quality__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-quality__turn absolute-voice-turn-quality__turn--${escapeHtml8(turn.status)}">
|
|
1893
2063
|
<header>
|
|
1894
|
-
<strong>${
|
|
1895
|
-
<span>${
|
|
2064
|
+
<strong>${escapeHtml8(turn.label)}</strong>
|
|
2065
|
+
<span>${escapeHtml8(turn.status)}</span>
|
|
1896
2066
|
</header>
|
|
1897
|
-
<p>${
|
|
2067
|
+
<p>${escapeHtml8(turn.detail)}</p>
|
|
1898
2068
|
<dl>${turn.rows.map((row) => `<div>
|
|
1899
|
-
<dt>${
|
|
1900
|
-
<dd>${
|
|
2069
|
+
<dt>${escapeHtml8(row.label)}</dt>
|
|
2070
|
+
<dd>${escapeHtml8(row.value)}</dd>
|
|
1901
2071
|
</div>`).join("")}</dl>
|
|
1902
2072
|
</article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
|
|
1903
|
-
return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${
|
|
2073
|
+
return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml8(model.status)}">
|
|
1904
2074
|
<header class="absolute-voice-turn-quality__header">
|
|
1905
|
-
<span class="absolute-voice-turn-quality__eyebrow">${
|
|
1906
|
-
<strong class="absolute-voice-turn-quality__label">${
|
|
2075
|
+
<span class="absolute-voice-turn-quality__eyebrow">${escapeHtml8(model.title)}</span>
|
|
2076
|
+
<strong class="absolute-voice-turn-quality__label">${escapeHtml8(model.label)}</strong>
|
|
1907
2077
|
</header>
|
|
1908
|
-
<p class="absolute-voice-turn-quality__description">${
|
|
2078
|
+
<p class="absolute-voice-turn-quality__description">${escapeHtml8(model.description)}</p>
|
|
1909
2079
|
${turns}
|
|
1910
|
-
${model.error ? `<p class="absolute-voice-turn-quality__error">${
|
|
2080
|
+
${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml8(model.error)}</p>` : ""}
|
|
1911
2081
|
</section>`;
|
|
1912
2082
|
};
|
|
1913
2083
|
var getVoiceTurnQualityCSS = () => `.absolute-voice-turn-quality{border:1px solid #e4d1a3;border-radius:20px;background:#fff9eb;color:#17120a;padding:18px;box-shadow:0 18px 40px rgba(73,48,14,.12);font-family:inherit}.absolute-voice-turn-quality--error,.absolute-voice-turn-quality--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-turn-quality__header,.absolute-voice-turn-quality__turn header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-turn-quality__eyebrow{color:#8a5a0a;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-turn-quality__label{font-size:24px;line-height:1}.absolute-voice-turn-quality__description,.absolute-voice-turn-quality__turn p,.absolute-voice-turn-quality__turn dt,.absolute-voice-turn-quality__empty{color:#5a4930}.absolute-voice-turn-quality__turns{display:grid;gap:12px;margin-top:14px}.absolute-voice-turn-quality__turn{background:#fff;border:1px solid #f0dfba;border-radius:16px;padding:14px}.absolute-voice-turn-quality__turn--pass{border-color:#86efac}.absolute-voice-turn-quality__turn--warn,.absolute-voice-turn-quality__turn--unknown{border-color:#fbbf24}.absolute-voice-turn-quality__turn--fail{border-color:#f2a7a7}.absolute-voice-turn-quality__turn p{margin:10px 0}.absolute-voice-turn-quality__turn dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-turn-quality__turn div{background:#fff9eb;border:1px solid #f0dfba;border-radius:12px;padding:8px}.absolute-voice-turn-quality__turn dt{font-size:12px}.absolute-voice-turn-quality__turn dd{font-weight:800;margin:4px 0 0}.absolute-voice-turn-quality__empty{margin:14px 0 0}.absolute-voice-turn-quality__error{color:#9f1239;font-weight:700}`;
|
|
@@ -2676,6 +2846,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
2676
2846
|
export {
|
|
2677
2847
|
createVoiceWorkflowStatus,
|
|
2678
2848
|
createVoiceTurnQuality,
|
|
2849
|
+
createVoiceTurnLatency,
|
|
2679
2850
|
createVoiceTraceTimeline,
|
|
2680
2851
|
createVoiceStream2 as createVoiceStream,
|
|
2681
2852
|
createVoiceRoutingStatus,
|
package/dist/testing/index.js
CHANGED
|
@@ -5216,7 +5216,7 @@ var createVoiceSession = (options) => {
|
|
|
5216
5216
|
} : undefined;
|
|
5217
5217
|
const appendTrace = async (input) => {
|
|
5218
5218
|
await options.trace?.append({
|
|
5219
|
-
at: Date.now(),
|
|
5219
|
+
at: input.at ?? Date.now(),
|
|
5220
5220
|
metadata: input.metadata,
|
|
5221
5221
|
payload: input.payload,
|
|
5222
5222
|
scenarioId: input.session?.scenarioId ?? options.scenarioId,
|
|
@@ -5225,6 +5225,13 @@ var createVoiceSession = (options) => {
|
|
|
5225
5225
|
type: input.type
|
|
5226
5226
|
});
|
|
5227
5227
|
};
|
|
5228
|
+
const appendTurnLatencyStage = async (input) => appendTrace({
|
|
5229
|
+
at: input.at,
|
|
5230
|
+
payload: { stage: input.stage },
|
|
5231
|
+
session: input.session,
|
|
5232
|
+
turnId: input.turnId,
|
|
5233
|
+
type: "turn_latency.stage"
|
|
5234
|
+
});
|
|
5228
5235
|
const phraseHints = options.phraseHints ?? [];
|
|
5229
5236
|
const lexicon = options.lexicon ?? [];
|
|
5230
5237
|
let socket = options.socket;
|
|
@@ -6175,6 +6182,13 @@ var createVoiceSession = (options) => {
|
|
|
6175
6182
|
turnId: activeTTSTurnId,
|
|
6176
6183
|
type: "audio"
|
|
6177
6184
|
});
|
|
6185
|
+
if (activeTTSTurnId) {
|
|
6186
|
+
await appendTurnLatencyStage({
|
|
6187
|
+
at: receivedAt,
|
|
6188
|
+
stage: "assistant_audio_received",
|
|
6189
|
+
turnId: activeTTSTurnId
|
|
6190
|
+
});
|
|
6191
|
+
}
|
|
6178
6192
|
});
|
|
6179
6193
|
});
|
|
6180
6194
|
openedSession.on("error", (event) => {
|
|
@@ -6233,6 +6247,7 @@ var createVoiceSession = (options) => {
|
|
|
6233
6247
|
voicemail: committedOutput?.voicemail
|
|
6234
6248
|
};
|
|
6235
6249
|
if (output?.assistantText) {
|
|
6250
|
+
const assistantTextStartedAt = Date.now();
|
|
6236
6251
|
await writeSession((currentSession) => {
|
|
6237
6252
|
setTurnResult(currentSession, turn.id, {
|
|
6238
6253
|
assistantText: output.assistantText
|
|
@@ -6243,6 +6258,12 @@ var createVoiceSession = (options) => {
|
|
|
6243
6258
|
turnId: turn.id,
|
|
6244
6259
|
type: "assistant"
|
|
6245
6260
|
});
|
|
6261
|
+
await appendTurnLatencyStage({
|
|
6262
|
+
at: assistantTextStartedAt,
|
|
6263
|
+
session,
|
|
6264
|
+
stage: "assistant_text_started",
|
|
6265
|
+
turnId: turn.id
|
|
6266
|
+
});
|
|
6246
6267
|
await appendTrace({
|
|
6247
6268
|
payload: {
|
|
6248
6269
|
text: output.assistantText,
|
|
@@ -6257,7 +6278,18 @@ var createVoiceSession = (options) => {
|
|
|
6257
6278
|
if (activeTTSSession) {
|
|
6258
6279
|
const ttsStartedAt = Date.now();
|
|
6259
6280
|
activeTTSTurnId = turn.id;
|
|
6281
|
+
await appendTurnLatencyStage({
|
|
6282
|
+
at: ttsStartedAt,
|
|
6283
|
+
session,
|
|
6284
|
+
stage: "tts_send_started",
|
|
6285
|
+
turnId: turn.id
|
|
6286
|
+
});
|
|
6260
6287
|
await activeTTSSession.send(output.assistantText);
|
|
6288
|
+
await appendTurnLatencyStage({
|
|
6289
|
+
session,
|
|
6290
|
+
stage: "tts_send_completed",
|
|
6291
|
+
turnId: turn.id
|
|
6292
|
+
});
|
|
6261
6293
|
await appendTrace({
|
|
6262
6294
|
payload: {
|
|
6263
6295
|
elapsedMs: Date.now() - ttsStartedAt,
|
|
@@ -6454,6 +6486,30 @@ var createVoiceSession = (options) => {
|
|
|
6454
6486
|
turnId: turn.id,
|
|
6455
6487
|
type: "turn.cost"
|
|
6456
6488
|
});
|
|
6489
|
+
const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
6490
|
+
const finalTranscriptAt = turn.transcripts.filter((transcript) => transcript.isFinal).map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
6491
|
+
if (firstTranscriptAt !== undefined) {
|
|
6492
|
+
await appendTurnLatencyStage({
|
|
6493
|
+
at: firstTranscriptAt,
|
|
6494
|
+
session: updatedSession,
|
|
6495
|
+
stage: "speech_detected",
|
|
6496
|
+
turnId: turn.id
|
|
6497
|
+
});
|
|
6498
|
+
}
|
|
6499
|
+
if (finalTranscriptAt !== undefined) {
|
|
6500
|
+
await appendTurnLatencyStage({
|
|
6501
|
+
at: finalTranscriptAt,
|
|
6502
|
+
session: updatedSession,
|
|
6503
|
+
stage: "final_transcript",
|
|
6504
|
+
turnId: turn.id
|
|
6505
|
+
});
|
|
6506
|
+
}
|
|
6507
|
+
await appendTurnLatencyStage({
|
|
6508
|
+
at: turn.committedAt,
|
|
6509
|
+
session: updatedSession,
|
|
6510
|
+
stage: "turn_committed",
|
|
6511
|
+
turnId: turn.id
|
|
6512
|
+
});
|
|
6457
6513
|
await send({
|
|
6458
6514
|
turn,
|
|
6459
6515
|
type: "turn"
|
package/dist/trace.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type VoiceTraceEventType = 'assistant.guardrail' | 'assistant.memory' | 'assistant.run' | 'agent.handoff' | 'agent.model' | 'agent.result' | 'agent.tool' | 'call.handoff' | 'call.lifecycle' | 'client.barge_in' | 'session.error' | 'turn.assistant' | 'turn.committed' | 'turn.cost' | 'turn.transcript' | 'workflow.contract';
|
|
1
|
+
export type VoiceTraceEventType = 'assistant.guardrail' | 'assistant.memory' | 'assistant.run' | 'agent.handoff' | 'agent.model' | 'agent.result' | 'agent.tool' | 'call.handoff' | 'call.lifecycle' | 'client.barge_in' | 'session.error' | 'turn.assistant' | 'turn.committed' | 'turn.cost' | 'turn_latency.stage' | 'turn.transcript' | 'workflow.contract';
|
|
2
2
|
export type VoiceTraceEvent<TPayload extends Record<string, unknown> = Record<string, unknown>> = {
|
|
3
3
|
at: number;
|
|
4
4
|
id?: string;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import type { VoiceTraceEventStore } from './trace';
|
|
3
|
+
import type { VoiceSessionRecord, VoiceSessionStore } from './types';
|
|
4
|
+
export type VoiceTurnLatencyStatus = 'pass' | 'warn' | 'fail' | 'empty';
|
|
5
|
+
export type VoiceTurnLatencyStage = {
|
|
6
|
+
label: string;
|
|
7
|
+
valueMs?: number;
|
|
8
|
+
};
|
|
9
|
+
export type VoiceTurnLatencyItem = {
|
|
10
|
+
assistantTextStartedAt?: number;
|
|
11
|
+
committedAt: number;
|
|
12
|
+
finalTranscriptAt?: number;
|
|
13
|
+
firstTranscriptAt?: number;
|
|
14
|
+
sessionId: string;
|
|
15
|
+
stages: VoiceTurnLatencyStage[];
|
|
16
|
+
status: VoiceTurnLatencyStatus;
|
|
17
|
+
text: string;
|
|
18
|
+
totalMs?: number;
|
|
19
|
+
turnId: string;
|
|
20
|
+
};
|
|
21
|
+
export type VoiceTurnLatencyReport = {
|
|
22
|
+
averageTotalMs?: number;
|
|
23
|
+
checkedAt: number;
|
|
24
|
+
failed: number;
|
|
25
|
+
sessions: number;
|
|
26
|
+
status: VoiceTurnLatencyStatus;
|
|
27
|
+
total: number;
|
|
28
|
+
turns: VoiceTurnLatencyItem[];
|
|
29
|
+
warnings: number;
|
|
30
|
+
};
|
|
31
|
+
export type VoiceTurnLatencyOptions<TSession extends VoiceSessionRecord = VoiceSessionRecord> = {
|
|
32
|
+
limit?: number;
|
|
33
|
+
sessionIds?: string[];
|
|
34
|
+
sessions?: TSession[];
|
|
35
|
+
store?: VoiceSessionStore<TSession>;
|
|
36
|
+
traceStore?: VoiceTraceEventStore;
|
|
37
|
+
warnAfterMs?: number;
|
|
38
|
+
failAfterMs?: number;
|
|
39
|
+
};
|
|
40
|
+
export type VoiceTurnLatencyHTMLHandlerOptions<TSession extends VoiceSessionRecord = VoiceSessionRecord> = VoiceTurnLatencyOptions<TSession> & {
|
|
41
|
+
headers?: HeadersInit;
|
|
42
|
+
render?: (report: VoiceTurnLatencyReport) => string | Promise<string>;
|
|
43
|
+
title?: string;
|
|
44
|
+
};
|
|
45
|
+
export type VoiceTurnLatencyRoutesOptions<TSession extends VoiceSessionRecord = VoiceSessionRecord> = VoiceTurnLatencyHTMLHandlerOptions<TSession> & {
|
|
46
|
+
htmlPath?: false | string;
|
|
47
|
+
name?: string;
|
|
48
|
+
path?: string;
|
|
49
|
+
};
|
|
50
|
+
export declare const summarizeVoiceTurnLatency: <TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: VoiceTurnLatencyOptions<TSession>) => Promise<VoiceTurnLatencyReport>;
|
|
51
|
+
export declare const renderVoiceTurnLatencyHTML: (report: VoiceTurnLatencyReport, options?: {
|
|
52
|
+
title?: string;
|
|
53
|
+
}) => string;
|
|
54
|
+
export declare const createVoiceTurnLatencyJSONHandler: <TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: VoiceTurnLatencyOptions<TSession>) => () => Promise<VoiceTurnLatencyReport>;
|
|
55
|
+
export declare const createVoiceTurnLatencyHTMLHandler: <TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: VoiceTurnLatencyHTMLHandlerOptions<TSession>) => () => Promise<Response>;
|
|
56
|
+
export declare const createVoiceTurnLatencyRoutes: <TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: VoiceTurnLatencyRoutesOptions<TSession>) => Elysia<"", {
|
|
57
|
+
decorator: {};
|
|
58
|
+
store: {};
|
|
59
|
+
derive: {};
|
|
60
|
+
resolve: {};
|
|
61
|
+
}, {
|
|
62
|
+
typebox: {};
|
|
63
|
+
error: {};
|
|
64
|
+
}, {
|
|
65
|
+
schema: {};
|
|
66
|
+
standaloneSchema: {};
|
|
67
|
+
macro: {};
|
|
68
|
+
macroFn: {};
|
|
69
|
+
parser: {};
|
|
70
|
+
response: {};
|
|
71
|
+
}, {
|
|
72
|
+
[x: string]: {
|
|
73
|
+
get: {
|
|
74
|
+
body: unknown;
|
|
75
|
+
params: {};
|
|
76
|
+
query: unknown;
|
|
77
|
+
headers: unknown;
|
|
78
|
+
response: {
|
|
79
|
+
200: VoiceTurnLatencyReport;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
}, {
|
|
84
|
+
derive: {};
|
|
85
|
+
resolve: {};
|
|
86
|
+
schema: {};
|
|
87
|
+
standaloneSchema: {};
|
|
88
|
+
response: {};
|
|
89
|
+
}, {
|
|
90
|
+
derive: {};
|
|
91
|
+
resolve: {};
|
|
92
|
+
schema: {};
|
|
93
|
+
standaloneSchema: {};
|
|
94
|
+
response: {};
|
|
95
|
+
}>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export declare const VoiceTurnLatency: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
2
|
+
class: {
|
|
3
|
+
default: string;
|
|
4
|
+
type: StringConstructor;
|
|
5
|
+
};
|
|
6
|
+
description: {
|
|
7
|
+
default: undefined;
|
|
8
|
+
type: StringConstructor;
|
|
9
|
+
};
|
|
10
|
+
intervalMs: {
|
|
11
|
+
default: number;
|
|
12
|
+
type: NumberConstructor;
|
|
13
|
+
};
|
|
14
|
+
path: {
|
|
15
|
+
default: string;
|
|
16
|
+
type: StringConstructor;
|
|
17
|
+
};
|
|
18
|
+
title: {
|
|
19
|
+
default: undefined;
|
|
20
|
+
type: StringConstructor;
|
|
21
|
+
};
|
|
22
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
23
|
+
[key: string]: any;
|
|
24
|
+
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
25
|
+
class: {
|
|
26
|
+
default: string;
|
|
27
|
+
type: StringConstructor;
|
|
28
|
+
};
|
|
29
|
+
description: {
|
|
30
|
+
default: undefined;
|
|
31
|
+
type: StringConstructor;
|
|
32
|
+
};
|
|
33
|
+
intervalMs: {
|
|
34
|
+
default: number;
|
|
35
|
+
type: NumberConstructor;
|
|
36
|
+
};
|
|
37
|
+
path: {
|
|
38
|
+
default: string;
|
|
39
|
+
type: StringConstructor;
|
|
40
|
+
};
|
|
41
|
+
title: {
|
|
42
|
+
default: undefined;
|
|
43
|
+
type: StringConstructor;
|
|
44
|
+
};
|
|
45
|
+
}>> & Readonly<{}>, {
|
|
46
|
+
description: string;
|
|
47
|
+
title: string;
|
|
48
|
+
path: string;
|
|
49
|
+
intervalMs: number;
|
|
50
|
+
class: string;
|
|
51
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
package/dist/vue/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { VoiceProviderSimulationControls } from './VoiceProviderSimulationContro
|
|
|
3
3
|
export { VoiceProviderCapabilities } from './VoiceProviderCapabilities';
|
|
4
4
|
export { VoiceProviderStatus } from './VoiceProviderStatus';
|
|
5
5
|
export { VoiceRoutingStatus } from './VoiceRoutingStatus';
|
|
6
|
+
export { VoiceTurnLatency } from './VoiceTurnLatency';
|
|
6
7
|
export { VoiceTurnQuality } from './VoiceTurnQuality';
|
|
7
8
|
export { useVoiceAppKitStatus } from './useVoiceAppKitStatus';
|
|
8
9
|
export { useVoiceStream } from './useVoiceStream';
|
|
@@ -12,5 +13,6 @@ export { useVoiceProviderCapabilities } from './useVoiceProviderCapabilities';
|
|
|
12
13
|
export { useVoiceProviderSimulationControls } from './useVoiceProviderSimulationControls';
|
|
13
14
|
export { useVoiceRoutingStatus } from './useVoiceRoutingStatus';
|
|
14
15
|
export { useVoiceTraceTimeline } from './useVoiceTraceTimeline';
|
|
16
|
+
export { useVoiceTurnLatency } from './useVoiceTurnLatency';
|
|
15
17
|
export { useVoiceTurnQuality } from './useVoiceTurnQuality';
|
|
16
18
|
export { useVoiceWorkflowStatus } from './useVoiceWorkflowStatus';
|