@absolutejs/voice 0.0.22-beta.97 → 0.0.22-beta.99
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.js +29 -0
- package/dist/angular/voice-turn-latency.service.d.ts +1 -0
- package/dist/client/index.d.ts +3 -1
- package/dist/client/index.js +154 -0
- package/dist/client/liveTurnLatency.d.ts +38 -0
- package/dist/client/turnLatency.d.ts +3 -0
- package/dist/client/turnLatencyWidget.d.ts +3 -0
- package/dist/react/index.js +54 -3
- package/dist/react/useVoiceTurnLatency.d.ts +1 -0
- package/dist/svelte/createVoiceTurnLatency.d.ts +1 -0
- package/dist/svelte/index.js +42 -0
- package/dist/vue/VoiceTurnLatency.d.ts +18 -0
- package/dist/vue/index.js +61 -1
- package/dist/vue/useVoiceTurnLatency.d.ts +1 -0
- package/package.json +1 -1
package/dist/angular/index.js
CHANGED
|
@@ -1967,6 +1967,14 @@ var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) =>
|
|
|
1967
1967
|
}
|
|
1968
1968
|
return await response.json();
|
|
1969
1969
|
};
|
|
1970
|
+
var runVoiceTurnLatencyProof = async (path, options = {}) => {
|
|
1971
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1972
|
+
const response = await fetchImpl(path, { method: "POST" });
|
|
1973
|
+
if (!response.ok) {
|
|
1974
|
+
throw new Error(`Voice turn latency proof failed: HTTP ${response.status}`);
|
|
1975
|
+
}
|
|
1976
|
+
return response.json();
|
|
1977
|
+
};
|
|
1970
1978
|
var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
|
|
1971
1979
|
const listeners = new Set;
|
|
1972
1980
|
let closed = false;
|
|
@@ -2006,6 +2014,25 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
|
|
|
2006
2014
|
throw error;
|
|
2007
2015
|
}
|
|
2008
2016
|
};
|
|
2017
|
+
const runProof = async () => {
|
|
2018
|
+
if (!options.proofPath) {
|
|
2019
|
+
throw new Error("Voice turn latency proof path is not configured.");
|
|
2020
|
+
}
|
|
2021
|
+
snapshot = { ...snapshot, error: null, isLoading: true };
|
|
2022
|
+
emit();
|
|
2023
|
+
try {
|
|
2024
|
+
await runVoiceTurnLatencyProof(options.proofPath, options);
|
|
2025
|
+
return await refresh();
|
|
2026
|
+
} catch (error) {
|
|
2027
|
+
snapshot = {
|
|
2028
|
+
...snapshot,
|
|
2029
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2030
|
+
isLoading: false
|
|
2031
|
+
};
|
|
2032
|
+
emit();
|
|
2033
|
+
throw error;
|
|
2034
|
+
}
|
|
2035
|
+
};
|
|
2009
2036
|
const close = () => {
|
|
2010
2037
|
closed = true;
|
|
2011
2038
|
if (timer) {
|
|
@@ -2024,6 +2051,7 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
|
|
|
2024
2051
|
getServerSnapshot: () => snapshot,
|
|
2025
2052
|
getSnapshot: () => snapshot,
|
|
2026
2053
|
refresh,
|
|
2054
|
+
runProof,
|
|
2027
2055
|
subscribe: (listener) => {
|
|
2028
2056
|
listeners.add(listener);
|
|
2029
2057
|
return () => {
|
|
@@ -2065,6 +2093,7 @@ class VoiceTurnLatencyService {
|
|
|
2065
2093
|
isLoading: computed7(() => isLoadingSignal()),
|
|
2066
2094
|
refresh: store.refresh,
|
|
2067
2095
|
report: computed7(() => reportSignal()),
|
|
2096
|
+
runProof: store.runProof,
|
|
2068
2097
|
updatedAt: computed7(() => updatedAtSignal())
|
|
2069
2098
|
};
|
|
2070
2099
|
}
|
|
@@ -7,6 +7,7 @@ export declare class VoiceTurnLatencyService {
|
|
|
7
7
|
isLoading: import("@angular/core").Signal<boolean>;
|
|
8
8
|
refresh: () => Promise<VoiceTurnLatencyReport | undefined>;
|
|
9
9
|
report: import("@angular/core").Signal<VoiceTurnLatencyReport | undefined>;
|
|
10
|
+
runProof: () => Promise<VoiceTurnLatencyReport | undefined>;
|
|
10
11
|
updatedAt: import("@angular/core").Signal<number | undefined>;
|
|
11
12
|
};
|
|
12
13
|
}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { bindVoiceBargeIn, createVoiceDuplexController } from './duplex';
|
|
|
6
6
|
export { bindVoiceHTMX } from './htmx';
|
|
7
7
|
export { createMicrophoneCapture } from './microphone';
|
|
8
8
|
export { createVoiceBargeInMonitor } from './bargeInMonitor';
|
|
9
|
+
export { createVoiceLiveTurnLatencyMonitor } from './liveTurnLatency';
|
|
9
10
|
export { createVoiceAppKitStatusStore, fetchVoiceAppKitStatus } from './appKitStatus';
|
|
10
11
|
export { createVoiceOpsStatusViewModel, defineVoiceOpsStatusElement, getVoiceOpsStatusCSS, getVoiceOpsStatusLabel, mountVoiceOpsStatus, renderVoiceOpsStatusHTML } from './opsStatusWidget';
|
|
11
12
|
export { createVoiceRoutingStatusStore, fetchVoiceRoutingStatus } from './routingStatus';
|
|
@@ -13,7 +14,7 @@ export { createVoiceRoutingStatusViewModel, defineVoiceRoutingStatusElement, get
|
|
|
13
14
|
export { createVoiceProviderStatusStore, fetchVoiceProviderStatus } from './providerStatus';
|
|
14
15
|
export { createVoiceProviderCapabilitiesStore, fetchVoiceProviderCapabilities } from './providerCapabilities';
|
|
15
16
|
export { createVoiceTurnQualityStore, fetchVoiceTurnQuality } from './turnQuality';
|
|
16
|
-
export { createVoiceTurnLatencyStore, fetchVoiceTurnLatency } from './turnLatency';
|
|
17
|
+
export { createVoiceTurnLatencyStore, fetchVoiceTurnLatency, runVoiceTurnLatencyProof } from './turnLatency';
|
|
17
18
|
export { createVoiceTraceTimelineStore, fetchVoiceTraceTimeline } from './traceTimeline';
|
|
18
19
|
export { createVoiceProviderSimulationControlsStore } from './providerSimulationControls';
|
|
19
20
|
export { bindVoiceProviderSimulationControls, createVoiceProviderSimulationControlsViewModel, defineVoiceProviderSimulationControlsElement, mountVoiceProviderSimulationControls, renderVoiceProviderSimulationControlsHTML } from './providerSimulationControlsWidget';
|
|
@@ -25,6 +26,7 @@ export { createVoiceTraceTimelineViewModel, defineVoiceTraceTimelineElement, get
|
|
|
25
26
|
export { createVoiceWorkflowStatusStore, fetchVoiceWorkflowStatus } from './workflowStatus';
|
|
26
27
|
export type { VoiceAppKitStatusClientOptions, VoiceAppKitStatusSnapshot } from './appKitStatus';
|
|
27
28
|
export type { VoiceBargeInMonitorOptions } from './bargeInMonitor';
|
|
29
|
+
export type { VoiceLiveTurnLatencyEvent, VoiceLiveTurnLatencyMonitorOptions, VoiceLiveTurnLatencySnapshot, VoiceLiveTurnLatencyStatus } from './liveTurnLatency';
|
|
28
30
|
export type { VoiceOpsStatusSurfaceView, VoiceOpsStatusViewModel, VoiceOpsStatusWidgetOptions } from './opsStatusWidget';
|
|
29
31
|
export type { VoiceRoutingStatusClientOptions, VoiceRoutingStatusSnapshot } from './routingStatus';
|
|
30
32
|
export type { VoiceRoutingStatusViewModel, VoiceRoutingStatusWidgetOptions } from './routingStatusWidget';
|
package/dist/client/index.js
CHANGED
|
@@ -1724,6 +1724,116 @@ var createVoiceBargeInMonitor = (options = {}) => {
|
|
|
1724
1724
|
}
|
|
1725
1725
|
};
|
|
1726
1726
|
};
|
|
1727
|
+
// src/client/liveTurnLatency.ts
|
|
1728
|
+
var getAudioLevel = (audio) => {
|
|
1729
|
+
const bytes = audio instanceof Uint8Array ? audio : new Uint8Array(audio);
|
|
1730
|
+
if (bytes.byteLength < 2) {
|
|
1731
|
+
return 0;
|
|
1732
|
+
}
|
|
1733
|
+
const samples = new Int16Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 2));
|
|
1734
|
+
if (samples.length === 0) {
|
|
1735
|
+
return 0;
|
|
1736
|
+
}
|
|
1737
|
+
let sumSquares = 0;
|
|
1738
|
+
for (const sample of samples) {
|
|
1739
|
+
const normalized = sample / 32768;
|
|
1740
|
+
sumSquares += normalized * normalized;
|
|
1741
|
+
}
|
|
1742
|
+
return Math.min(1, Math.max(0, Math.sqrt(sumSquares / samples.length) * 5.5));
|
|
1743
|
+
};
|
|
1744
|
+
var createVoiceLiveTurnLatencyMonitor = (options = {}) => {
|
|
1745
|
+
const listeners = new Set;
|
|
1746
|
+
const clock = options.clock ?? (() => Date.now());
|
|
1747
|
+
const failAfterMs = options.failAfterMs ?? 3200;
|
|
1748
|
+
const maxEvents = options.maxEvents ?? 20;
|
|
1749
|
+
const speechThreshold = options.speechThreshold ?? 0.04;
|
|
1750
|
+
const warnAfterMs = options.warnAfterMs ?? 1800;
|
|
1751
|
+
let events = [];
|
|
1752
|
+
let pending;
|
|
1753
|
+
let lastAudioCount = 0;
|
|
1754
|
+
let lastTextCount = 0;
|
|
1755
|
+
let lastSessionId;
|
|
1756
|
+
const emit = () => {
|
|
1757
|
+
for (const listener of listeners) {
|
|
1758
|
+
listener();
|
|
1759
|
+
}
|
|
1760
|
+
};
|
|
1761
|
+
const completePending = (input) => {
|
|
1762
|
+
if (!pending) {
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
const completedAt = input.assistantAudioAt ?? input.assistantTextAt ?? clock();
|
|
1766
|
+
const latencyMs = Math.max(0, completedAt - pending.startedAt);
|
|
1767
|
+
const status = latencyMs > failAfterMs ? "fail" : latencyMs > warnAfterMs ? "warn" : "pass";
|
|
1768
|
+
pending = {
|
|
1769
|
+
...pending,
|
|
1770
|
+
...input,
|
|
1771
|
+
completedAt,
|
|
1772
|
+
latencyMs,
|
|
1773
|
+
status
|
|
1774
|
+
};
|
|
1775
|
+
events = [pending, ...events].slice(0, maxEvents);
|
|
1776
|
+
pending = undefined;
|
|
1777
|
+
emit();
|
|
1778
|
+
};
|
|
1779
|
+
const observe = (state) => {
|
|
1780
|
+
const now = clock();
|
|
1781
|
+
if (pending) {
|
|
1782
|
+
if (state.assistantAudio.length > lastAudioCount) {
|
|
1783
|
+
completePending({ assistantAudioAt: now });
|
|
1784
|
+
} else if (state.assistantTexts.length > lastTextCount) {
|
|
1785
|
+
completePending({ assistantTextAt: now });
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
lastAudioCount = state.assistantAudio.length;
|
|
1789
|
+
lastTextCount = state.assistantTexts.length;
|
|
1790
|
+
lastSessionId = state.sessionId;
|
|
1791
|
+
};
|
|
1792
|
+
const recordAudio = (audio) => {
|
|
1793
|
+
if (pending || getAudioLevel(audio) < speechThreshold) {
|
|
1794
|
+
return pending;
|
|
1795
|
+
}
|
|
1796
|
+
pending = {
|
|
1797
|
+
id: `live-turn-${crypto.randomUUID()}`,
|
|
1798
|
+
sessionId: lastSessionId ?? null,
|
|
1799
|
+
startedAt: clock(),
|
|
1800
|
+
status: "pending",
|
|
1801
|
+
thresholdMs: failAfterMs
|
|
1802
|
+
};
|
|
1803
|
+
emit();
|
|
1804
|
+
return pending;
|
|
1805
|
+
};
|
|
1806
|
+
const getSnapshot = () => {
|
|
1807
|
+
const completed = events.filter((event) => typeof event.latencyMs === "number");
|
|
1808
|
+
const latencies = completed.map((event) => event.latencyMs);
|
|
1809
|
+
const failed = events.filter((event) => event.status === "fail").length;
|
|
1810
|
+
const warnings = events.filter((event) => event.status === "warn").length;
|
|
1811
|
+
const passed = events.filter((event) => event.status === "pass").length;
|
|
1812
|
+
return {
|
|
1813
|
+
averageLatencyMs: latencies.length ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
|
|
1814
|
+
events,
|
|
1815
|
+
failed,
|
|
1816
|
+
lastEvent: events[0],
|
|
1817
|
+
passed,
|
|
1818
|
+
pending,
|
|
1819
|
+
status: pending ? "pending" : events.length === 0 ? "empty" : failed > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
|
|
1820
|
+
thresholdMs: failAfterMs,
|
|
1821
|
+
total: events.length,
|
|
1822
|
+
warnings
|
|
1823
|
+
};
|
|
1824
|
+
};
|
|
1825
|
+
return {
|
|
1826
|
+
getSnapshot,
|
|
1827
|
+
observe,
|
|
1828
|
+
recordAudio,
|
|
1829
|
+
subscribe: (listener) => {
|
|
1830
|
+
listeners.add(listener);
|
|
1831
|
+
return () => {
|
|
1832
|
+
listeners.delete(listener);
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
};
|
|
1836
|
+
};
|
|
1727
1837
|
// src/client/appKitStatus.ts
|
|
1728
1838
|
var fetchVoiceAppKitStatus = async (path = "/app-kit/status", options = {}) => {
|
|
1729
1839
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
@@ -2339,6 +2449,14 @@ var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) =>
|
|
|
2339
2449
|
}
|
|
2340
2450
|
return await response.json();
|
|
2341
2451
|
};
|
|
2452
|
+
var runVoiceTurnLatencyProof = async (path, options = {}) => {
|
|
2453
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2454
|
+
const response = await fetchImpl(path, { method: "POST" });
|
|
2455
|
+
if (!response.ok) {
|
|
2456
|
+
throw new Error(`Voice turn latency proof failed: HTTP ${response.status}`);
|
|
2457
|
+
}
|
|
2458
|
+
return response.json();
|
|
2459
|
+
};
|
|
2342
2460
|
var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
|
|
2343
2461
|
const listeners = new Set;
|
|
2344
2462
|
let closed = false;
|
|
@@ -2378,6 +2496,25 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
|
|
|
2378
2496
|
throw error;
|
|
2379
2497
|
}
|
|
2380
2498
|
};
|
|
2499
|
+
const runProof = async () => {
|
|
2500
|
+
if (!options.proofPath) {
|
|
2501
|
+
throw new Error("Voice turn latency proof path is not configured.");
|
|
2502
|
+
}
|
|
2503
|
+
snapshot = { ...snapshot, error: null, isLoading: true };
|
|
2504
|
+
emit();
|
|
2505
|
+
try {
|
|
2506
|
+
await runVoiceTurnLatencyProof(options.proofPath, options);
|
|
2507
|
+
return await refresh();
|
|
2508
|
+
} catch (error) {
|
|
2509
|
+
snapshot = {
|
|
2510
|
+
...snapshot,
|
|
2511
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2512
|
+
isLoading: false
|
|
2513
|
+
};
|
|
2514
|
+
emit();
|
|
2515
|
+
throw error;
|
|
2516
|
+
}
|
|
2517
|
+
};
|
|
2381
2518
|
const close = () => {
|
|
2382
2519
|
closed = true;
|
|
2383
2520
|
if (timer) {
|
|
@@ -2396,6 +2533,7 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
|
|
|
2396
2533
|
getServerSnapshot: () => snapshot,
|
|
2397
2534
|
getSnapshot: () => snapshot,
|
|
2398
2535
|
refresh,
|
|
2536
|
+
runProof,
|
|
2399
2537
|
subscribe: (listener) => {
|
|
2400
2538
|
listeners.add(listener);
|
|
2401
2539
|
return () => {
|
|
@@ -3004,6 +3142,7 @@ var defineVoiceTurnQualityElement = (tagName = "absolute-voice-turn-quality") =>
|
|
|
3004
3142
|
// src/client/turnLatencyWidget.ts
|
|
3005
3143
|
var DEFAULT_TITLE6 = "Turn Latency";
|
|
3006
3144
|
var DEFAULT_DESCRIPTION6 = "Per-turn timing from first transcript to commit and assistant response start.";
|
|
3145
|
+
var DEFAULT_PROOF_LABEL = "Run latency proof";
|
|
3007
3146
|
var escapeHtml7 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3008
3147
|
var formatMs = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
|
|
3009
3148
|
var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
|
|
@@ -3022,6 +3161,8 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
|
|
|
3022
3161
|
error: snapshot.error,
|
|
3023
3162
|
isLoading: snapshot.isLoading,
|
|
3024
3163
|
label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} slow` : warningCount > 0 ? `${warningCount} warnings` : `avg ${formatMs(snapshot.report?.averageTotalMs)}` : snapshot.isLoading ? "Checking" : "No turns",
|
|
3164
|
+
proofLabel: options.proofPath ? options.proofLabel ?? DEFAULT_PROOF_LABEL : undefined,
|
|
3165
|
+
showProofAction: Boolean(options.proofPath),
|
|
3025
3166
|
status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
3026
3167
|
title: options.title ?? DEFAULT_TITLE6,
|
|
3027
3168
|
turns,
|
|
@@ -3046,6 +3187,7 @@ var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
|
|
|
3046
3187
|
<strong class="absolute-voice-turn-latency__label">${escapeHtml7(model.label)}</strong>
|
|
3047
3188
|
</header>
|
|
3048
3189
|
<p class="absolute-voice-turn-latency__description">${escapeHtml7(model.description)}</p>
|
|
3190
|
+
${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml7(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
|
|
3049
3191
|
${turns}
|
|
3050
3192
|
${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml7(model.error)}</p>` : ""}
|
|
3051
3193
|
</section>`;
|
|
@@ -3055,11 +3197,19 @@ var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {})
|
|
|
3055
3197
|
const render = () => {
|
|
3056
3198
|
element.innerHTML = renderVoiceTurnLatencyHTML(store.getSnapshot(), options);
|
|
3057
3199
|
};
|
|
3200
|
+
const handleClick = (event) => {
|
|
3201
|
+
const target = event.target;
|
|
3202
|
+
if (target instanceof Element && target.closest("[data-absolute-voice-turn-latency-proof]")) {
|
|
3203
|
+
store.runProof().catch(() => {});
|
|
3204
|
+
}
|
|
3205
|
+
};
|
|
3058
3206
|
const unsubscribe = store.subscribe(render);
|
|
3207
|
+
element.addEventListener("click", handleClick);
|
|
3059
3208
|
render();
|
|
3060
3209
|
store.refresh().catch(() => {});
|
|
3061
3210
|
return {
|
|
3062
3211
|
close: () => {
|
|
3212
|
+
element.removeEventListener("click", handleClick);
|
|
3063
3213
|
unsubscribe();
|
|
3064
3214
|
store.close();
|
|
3065
3215
|
},
|
|
@@ -3077,6 +3227,8 @@ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") =>
|
|
|
3077
3227
|
this.mounted = mountVoiceTurnLatency(this, this.getAttribute("path") ?? "/api/turn-latency", {
|
|
3078
3228
|
description: this.getAttribute("description") ?? undefined,
|
|
3079
3229
|
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
3230
|
+
proofLabel: this.getAttribute("proof-label") ?? undefined,
|
|
3231
|
+
proofPath: this.getAttribute("proof-path") ?? undefined,
|
|
3080
3232
|
title: this.getAttribute("title") ?? undefined
|
|
3081
3233
|
});
|
|
3082
3234
|
}
|
|
@@ -3252,6 +3404,7 @@ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options =
|
|
|
3252
3404
|
};
|
|
3253
3405
|
};
|
|
3254
3406
|
export {
|
|
3407
|
+
runVoiceTurnLatencyProof,
|
|
3255
3408
|
renderVoiceTurnQualityHTML,
|
|
3256
3409
|
renderVoiceTurnLatencyHTML,
|
|
3257
3410
|
renderVoiceTraceTimelineWidgetHTML,
|
|
@@ -3309,6 +3462,7 @@ export {
|
|
|
3309
3462
|
createVoiceProviderCapabilitiesViewModel,
|
|
3310
3463
|
createVoiceProviderCapabilitiesStore,
|
|
3311
3464
|
createVoiceOpsStatusViewModel,
|
|
3465
|
+
createVoiceLiveTurnLatencyMonitor,
|
|
3312
3466
|
createVoiceDuplexController,
|
|
3313
3467
|
createVoiceController,
|
|
3314
3468
|
createVoiceConnection,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { VoiceStreamState } from '../types';
|
|
2
|
+
export type VoiceLiveTurnLatencyStatus = 'empty' | 'pending' | 'pass' | 'warn' | 'fail';
|
|
3
|
+
export type VoiceLiveTurnLatencyEvent = {
|
|
4
|
+
assistantAudioAt?: number;
|
|
5
|
+
assistantTextAt?: number;
|
|
6
|
+
completedAt?: number;
|
|
7
|
+
id: string;
|
|
8
|
+
latencyMs?: number;
|
|
9
|
+
sessionId?: string | null;
|
|
10
|
+
startedAt: number;
|
|
11
|
+
status: Exclude<VoiceLiveTurnLatencyStatus, 'empty'>;
|
|
12
|
+
thresholdMs: number;
|
|
13
|
+
};
|
|
14
|
+
export type VoiceLiveTurnLatencySnapshot = {
|
|
15
|
+
averageLatencyMs?: number;
|
|
16
|
+
events: VoiceLiveTurnLatencyEvent[];
|
|
17
|
+
failed: number;
|
|
18
|
+
lastEvent?: VoiceLiveTurnLatencyEvent;
|
|
19
|
+
passed: number;
|
|
20
|
+
pending?: VoiceLiveTurnLatencyEvent;
|
|
21
|
+
status: VoiceLiveTurnLatencyStatus;
|
|
22
|
+
thresholdMs: number;
|
|
23
|
+
total: number;
|
|
24
|
+
warnings: number;
|
|
25
|
+
};
|
|
26
|
+
export type VoiceLiveTurnLatencyMonitorOptions = {
|
|
27
|
+
clock?: () => number;
|
|
28
|
+
failAfterMs?: number;
|
|
29
|
+
maxEvents?: number;
|
|
30
|
+
speechThreshold?: number;
|
|
31
|
+
warnAfterMs?: number;
|
|
32
|
+
};
|
|
33
|
+
export declare const createVoiceLiveTurnLatencyMonitor: (options?: VoiceLiveTurnLatencyMonitorOptions) => {
|
|
34
|
+
getSnapshot: () => VoiceLiveTurnLatencySnapshot;
|
|
35
|
+
observe: <TResult = unknown>(state: VoiceStreamState<TResult>) => void;
|
|
36
|
+
recordAudio: (audio: Uint8Array | ArrayBuffer) => VoiceLiveTurnLatencyEvent | undefined;
|
|
37
|
+
subscribe: (listener: () => void) => () => void;
|
|
38
|
+
};
|
|
@@ -2,6 +2,7 @@ import type { VoiceTurnLatencyReport } from '../turnLatency';
|
|
|
2
2
|
export type VoiceTurnLatencyClientOptions = {
|
|
3
3
|
fetch?: typeof fetch;
|
|
4
4
|
intervalMs?: number;
|
|
5
|
+
proofPath?: string;
|
|
5
6
|
};
|
|
6
7
|
export type VoiceTurnLatencySnapshot = {
|
|
7
8
|
error: string | null;
|
|
@@ -10,10 +11,12 @@ export type VoiceTurnLatencySnapshot = {
|
|
|
10
11
|
updatedAt?: number;
|
|
11
12
|
};
|
|
12
13
|
export declare const fetchVoiceTurnLatency: (path?: string, options?: Pick<VoiceTurnLatencyClientOptions, "fetch">) => Promise<VoiceTurnLatencyReport>;
|
|
14
|
+
export declare const runVoiceTurnLatencyProof: (path: string, options?: Pick<VoiceTurnLatencyClientOptions, "fetch">) => Promise<unknown>;
|
|
13
15
|
export declare const createVoiceTurnLatencyStore: (path?: string, options?: VoiceTurnLatencyClientOptions) => {
|
|
14
16
|
close: () => void;
|
|
15
17
|
getServerSnapshot: () => VoiceTurnLatencySnapshot;
|
|
16
18
|
getSnapshot: () => VoiceTurnLatencySnapshot;
|
|
17
19
|
refresh: () => Promise<VoiceTurnLatencyReport | undefined>;
|
|
20
|
+
runProof: () => Promise<VoiceTurnLatencyReport | undefined>;
|
|
18
21
|
subscribe: (listener: () => void) => () => void;
|
|
19
22
|
};
|
|
@@ -12,6 +12,8 @@ export type VoiceTurnLatencyViewModel = {
|
|
|
12
12
|
error: string | null;
|
|
13
13
|
isLoading: boolean;
|
|
14
14
|
label: string;
|
|
15
|
+
proofLabel?: string;
|
|
16
|
+
showProofAction: boolean;
|
|
15
17
|
status: 'empty' | 'error' | 'loading' | 'ready' | 'warning';
|
|
16
18
|
title: string;
|
|
17
19
|
turns: VoiceTurnLatencyCardView[];
|
|
@@ -19,6 +21,7 @@ export type VoiceTurnLatencyViewModel = {
|
|
|
19
21
|
};
|
|
20
22
|
export type VoiceTurnLatencyWidgetOptions = VoiceTurnLatencyClientOptions & {
|
|
21
23
|
description?: string;
|
|
24
|
+
proofLabel?: string;
|
|
22
25
|
title?: string;
|
|
23
26
|
};
|
|
24
27
|
export declare const createVoiceTurnLatencyViewModel: (snapshot: VoiceTurnLatencySnapshot, options?: VoiceTurnLatencyWidgetOptions) => VoiceTurnLatencyViewModel;
|
package/dist/react/index.js
CHANGED
|
@@ -1771,6 +1771,14 @@ var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) =>
|
|
|
1771
1771
|
}
|
|
1772
1772
|
return await response.json();
|
|
1773
1773
|
};
|
|
1774
|
+
var runVoiceTurnLatencyProof = async (path, options = {}) => {
|
|
1775
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1776
|
+
const response = await fetchImpl(path, { method: "POST" });
|
|
1777
|
+
if (!response.ok) {
|
|
1778
|
+
throw new Error(`Voice turn latency proof failed: HTTP ${response.status}`);
|
|
1779
|
+
}
|
|
1780
|
+
return response.json();
|
|
1781
|
+
};
|
|
1774
1782
|
var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
|
|
1775
1783
|
const listeners = new Set;
|
|
1776
1784
|
let closed = false;
|
|
@@ -1810,6 +1818,25 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
|
|
|
1810
1818
|
throw error;
|
|
1811
1819
|
}
|
|
1812
1820
|
};
|
|
1821
|
+
const runProof = async () => {
|
|
1822
|
+
if (!options.proofPath) {
|
|
1823
|
+
throw new Error("Voice turn latency proof path is not configured.");
|
|
1824
|
+
}
|
|
1825
|
+
snapshot = { ...snapshot, error: null, isLoading: true };
|
|
1826
|
+
emit();
|
|
1827
|
+
try {
|
|
1828
|
+
await runVoiceTurnLatencyProof(options.proofPath, options);
|
|
1829
|
+
return await refresh();
|
|
1830
|
+
} catch (error) {
|
|
1831
|
+
snapshot = {
|
|
1832
|
+
...snapshot,
|
|
1833
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1834
|
+
isLoading: false
|
|
1835
|
+
};
|
|
1836
|
+
emit();
|
|
1837
|
+
throw error;
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1813
1840
|
const close = () => {
|
|
1814
1841
|
closed = true;
|
|
1815
1842
|
if (timer) {
|
|
@@ -1828,6 +1855,7 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
|
|
|
1828
1855
|
getServerSnapshot: () => snapshot,
|
|
1829
1856
|
getSnapshot: () => snapshot,
|
|
1830
1857
|
refresh,
|
|
1858
|
+
runProof,
|
|
1831
1859
|
subscribe: (listener) => {
|
|
1832
1860
|
listeners.add(listener);
|
|
1833
1861
|
return () => {
|
|
@@ -1850,13 +1878,15 @@ var useVoiceTurnLatency = (path = "/api/turn-latency", options = {}) => {
|
|
|
1850
1878
|
}, [store]);
|
|
1851
1879
|
return {
|
|
1852
1880
|
...useSyncExternalStore7(store.subscribe, store.getSnapshot, store.getServerSnapshot),
|
|
1853
|
-
refresh: store.refresh
|
|
1881
|
+
refresh: store.refresh,
|
|
1882
|
+
runProof: store.runProof
|
|
1854
1883
|
};
|
|
1855
1884
|
};
|
|
1856
1885
|
|
|
1857
1886
|
// src/client/turnLatencyWidget.ts
|
|
1858
1887
|
var DEFAULT_TITLE6 = "Turn Latency";
|
|
1859
1888
|
var DEFAULT_DESCRIPTION6 = "Per-turn timing from first transcript to commit and assistant response start.";
|
|
1889
|
+
var DEFAULT_PROOF_LABEL = "Run latency proof";
|
|
1860
1890
|
var escapeHtml7 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1861
1891
|
var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
|
|
1862
1892
|
var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
|
|
@@ -1875,6 +1905,8 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
|
|
|
1875
1905
|
error: snapshot.error,
|
|
1876
1906
|
isLoading: snapshot.isLoading,
|
|
1877
1907
|
label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} slow` : warningCount > 0 ? `${warningCount} warnings` : `avg ${formatMs2(snapshot.report?.averageTotalMs)}` : snapshot.isLoading ? "Checking" : "No turns",
|
|
1908
|
+
proofLabel: options.proofPath ? options.proofLabel ?? DEFAULT_PROOF_LABEL : undefined,
|
|
1909
|
+
showProofAction: Boolean(options.proofPath),
|
|
1878
1910
|
status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
1879
1911
|
title: options.title ?? DEFAULT_TITLE6,
|
|
1880
1912
|
turns,
|
|
@@ -1899,6 +1931,7 @@ var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
|
|
|
1899
1931
|
<strong class="absolute-voice-turn-latency__label">${escapeHtml7(model.label)}</strong>
|
|
1900
1932
|
</header>
|
|
1901
1933
|
<p class="absolute-voice-turn-latency__description">${escapeHtml7(model.description)}</p>
|
|
1934
|
+
${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml7(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
|
|
1902
1935
|
${turns}
|
|
1903
1936
|
${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml7(model.error)}</p>` : ""}
|
|
1904
1937
|
</section>`;
|
|
@@ -1908,11 +1941,19 @@ var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {})
|
|
|
1908
1941
|
const render = () => {
|
|
1909
1942
|
element.innerHTML = renderVoiceTurnLatencyHTML(store.getSnapshot(), options);
|
|
1910
1943
|
};
|
|
1944
|
+
const handleClick = (event) => {
|
|
1945
|
+
const target = event.target;
|
|
1946
|
+
if (target instanceof Element && target.closest("[data-absolute-voice-turn-latency-proof]")) {
|
|
1947
|
+
store.runProof().catch(() => {});
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1911
1950
|
const unsubscribe = store.subscribe(render);
|
|
1951
|
+
element.addEventListener("click", handleClick);
|
|
1912
1952
|
render();
|
|
1913
1953
|
store.refresh().catch(() => {});
|
|
1914
1954
|
return {
|
|
1915
1955
|
close: () => {
|
|
1956
|
+
element.removeEventListener("click", handleClick);
|
|
1916
1957
|
unsubscribe();
|
|
1917
1958
|
store.close();
|
|
1918
1959
|
},
|
|
@@ -1930,6 +1971,8 @@ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") =>
|
|
|
1930
1971
|
this.mounted = mountVoiceTurnLatency(this, this.getAttribute("path") ?? "/api/turn-latency", {
|
|
1931
1972
|
description: this.getAttribute("description") ?? undefined,
|
|
1932
1973
|
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
1974
|
+
proofLabel: this.getAttribute("proof-label") ?? undefined,
|
|
1975
|
+
proofPath: this.getAttribute("proof-path") ?? undefined,
|
|
1933
1976
|
title: this.getAttribute("title") ?? undefined
|
|
1934
1977
|
});
|
|
1935
1978
|
}
|
|
@@ -1947,8 +1990,8 @@ var VoiceTurnLatency = ({
|
|
|
1947
1990
|
path = "/api/turn-latency",
|
|
1948
1991
|
...options
|
|
1949
1992
|
}) => {
|
|
1950
|
-
const
|
|
1951
|
-
const model = createVoiceTurnLatencyViewModel(
|
|
1993
|
+
const latency = useVoiceTurnLatency(path, options);
|
|
1994
|
+
const model = createVoiceTurnLatencyViewModel(latency, options);
|
|
1952
1995
|
return /* @__PURE__ */ jsxDEV7("section", {
|
|
1953
1996
|
className: [
|
|
1954
1997
|
"absolute-voice-turn-latency",
|
|
@@ -1973,6 +2016,14 @@ var VoiceTurnLatency = ({
|
|
|
1973
2016
|
className: "absolute-voice-turn-latency__description",
|
|
1974
2017
|
children: model.description
|
|
1975
2018
|
}, undefined, false, undefined, this),
|
|
2019
|
+
model.showProofAction ? /* @__PURE__ */ jsxDEV7("button", {
|
|
2020
|
+
className: "absolute-voice-turn-latency__proof",
|
|
2021
|
+
onClick: () => {
|
|
2022
|
+
latency.runProof().catch(() => {});
|
|
2023
|
+
},
|
|
2024
|
+
type: "button",
|
|
2025
|
+
children: model.proofLabel
|
|
2026
|
+
}, undefined, false, undefined, this) : null,
|
|
1976
2027
|
model.turns.length ? /* @__PURE__ */ jsxDEV7("div", {
|
|
1977
2028
|
className: "absolute-voice-turn-latency__turns",
|
|
1978
2029
|
children: model.turns.map((turn) => /* @__PURE__ */ jsxDEV7("article", {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type VoiceTurnLatencyClientOptions } from '../client/turnLatency';
|
|
2
2
|
export declare const useVoiceTurnLatency: (path?: string, options?: VoiceTurnLatencyClientOptions) => {
|
|
3
3
|
refresh: () => Promise<import("..").VoiceTurnLatencyReport | undefined>;
|
|
4
|
+
runProof: () => Promise<import("..").VoiceTurnLatencyReport | undefined>;
|
|
4
5
|
error: string | null;
|
|
5
6
|
isLoading: boolean;
|
|
6
7
|
report?: import("..").VoiceTurnLatencyReport;
|
|
@@ -6,5 +6,6 @@ export declare const createVoiceTurnLatency: (path?: string, options?: VoiceTurn
|
|
|
6
6
|
getServerSnapshot: () => import("../client").VoiceTurnLatencySnapshot;
|
|
7
7
|
getSnapshot: () => import("../client").VoiceTurnLatencySnapshot;
|
|
8
8
|
refresh: () => Promise<import("..").VoiceTurnLatencyReport | undefined>;
|
|
9
|
+
runProof: () => Promise<import("..").VoiceTurnLatencyReport | undefined>;
|
|
9
10
|
subscribe: (listener: () => void) => () => void;
|
|
10
11
|
};
|
package/dist/svelte/index.js
CHANGED
|
@@ -1766,6 +1766,14 @@ var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) =>
|
|
|
1766
1766
|
}
|
|
1767
1767
|
return await response.json();
|
|
1768
1768
|
};
|
|
1769
|
+
var runVoiceTurnLatencyProof = async (path, options = {}) => {
|
|
1770
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1771
|
+
const response = await fetchImpl(path, { method: "POST" });
|
|
1772
|
+
if (!response.ok) {
|
|
1773
|
+
throw new Error(`Voice turn latency proof failed: HTTP ${response.status}`);
|
|
1774
|
+
}
|
|
1775
|
+
return response.json();
|
|
1776
|
+
};
|
|
1769
1777
|
var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
|
|
1770
1778
|
const listeners = new Set;
|
|
1771
1779
|
let closed = false;
|
|
@@ -1805,6 +1813,25 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
|
|
|
1805
1813
|
throw error;
|
|
1806
1814
|
}
|
|
1807
1815
|
};
|
|
1816
|
+
const runProof = async () => {
|
|
1817
|
+
if (!options.proofPath) {
|
|
1818
|
+
throw new Error("Voice turn latency proof path is not configured.");
|
|
1819
|
+
}
|
|
1820
|
+
snapshot = { ...snapshot, error: null, isLoading: true };
|
|
1821
|
+
emit();
|
|
1822
|
+
try {
|
|
1823
|
+
await runVoiceTurnLatencyProof(options.proofPath, options);
|
|
1824
|
+
return await refresh();
|
|
1825
|
+
} catch (error) {
|
|
1826
|
+
snapshot = {
|
|
1827
|
+
...snapshot,
|
|
1828
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1829
|
+
isLoading: false
|
|
1830
|
+
};
|
|
1831
|
+
emit();
|
|
1832
|
+
throw error;
|
|
1833
|
+
}
|
|
1834
|
+
};
|
|
1808
1835
|
const close = () => {
|
|
1809
1836
|
closed = true;
|
|
1810
1837
|
if (timer) {
|
|
@@ -1823,6 +1850,7 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
|
|
|
1823
1850
|
getServerSnapshot: () => snapshot,
|
|
1824
1851
|
getSnapshot: () => snapshot,
|
|
1825
1852
|
refresh,
|
|
1853
|
+
runProof,
|
|
1826
1854
|
subscribe: (listener) => {
|
|
1827
1855
|
listeners.add(listener);
|
|
1828
1856
|
return () => {
|
|
@@ -1835,6 +1863,7 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
|
|
|
1835
1863
|
// src/client/turnLatencyWidget.ts
|
|
1836
1864
|
var DEFAULT_TITLE6 = "Turn Latency";
|
|
1837
1865
|
var DEFAULT_DESCRIPTION6 = "Per-turn timing from first transcript to commit and assistant response start.";
|
|
1866
|
+
var DEFAULT_PROOF_LABEL = "Run latency proof";
|
|
1838
1867
|
var escapeHtml7 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1839
1868
|
var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
|
|
1840
1869
|
var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
|
|
@@ -1853,6 +1882,8 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
|
|
|
1853
1882
|
error: snapshot.error,
|
|
1854
1883
|
isLoading: snapshot.isLoading,
|
|
1855
1884
|
label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} slow` : warningCount > 0 ? `${warningCount} warnings` : `avg ${formatMs2(snapshot.report?.averageTotalMs)}` : snapshot.isLoading ? "Checking" : "No turns",
|
|
1885
|
+
proofLabel: options.proofPath ? options.proofLabel ?? DEFAULT_PROOF_LABEL : undefined,
|
|
1886
|
+
showProofAction: Boolean(options.proofPath),
|
|
1856
1887
|
status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
1857
1888
|
title: options.title ?? DEFAULT_TITLE6,
|
|
1858
1889
|
turns,
|
|
@@ -1877,6 +1908,7 @@ var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
|
|
|
1877
1908
|
<strong class="absolute-voice-turn-latency__label">${escapeHtml7(model.label)}</strong>
|
|
1878
1909
|
</header>
|
|
1879
1910
|
<p class="absolute-voice-turn-latency__description">${escapeHtml7(model.description)}</p>
|
|
1911
|
+
${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml7(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
|
|
1880
1912
|
${turns}
|
|
1881
1913
|
${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml7(model.error)}</p>` : ""}
|
|
1882
1914
|
</section>`;
|
|
@@ -1886,11 +1918,19 @@ var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {})
|
|
|
1886
1918
|
const render = () => {
|
|
1887
1919
|
element.innerHTML = renderVoiceTurnLatencyHTML(store.getSnapshot(), options);
|
|
1888
1920
|
};
|
|
1921
|
+
const handleClick = (event) => {
|
|
1922
|
+
const target = event.target;
|
|
1923
|
+
if (target instanceof Element && target.closest("[data-absolute-voice-turn-latency-proof]")) {
|
|
1924
|
+
store.runProof().catch(() => {});
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1889
1927
|
const unsubscribe = store.subscribe(render);
|
|
1928
|
+
element.addEventListener("click", handleClick);
|
|
1890
1929
|
render();
|
|
1891
1930
|
store.refresh().catch(() => {});
|
|
1892
1931
|
return {
|
|
1893
1932
|
close: () => {
|
|
1933
|
+
element.removeEventListener("click", handleClick);
|
|
1894
1934
|
unsubscribe();
|
|
1895
1935
|
store.close();
|
|
1896
1936
|
},
|
|
@@ -1908,6 +1948,8 @@ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") =>
|
|
|
1908
1948
|
this.mounted = mountVoiceTurnLatency(this, this.getAttribute("path") ?? "/api/turn-latency", {
|
|
1909
1949
|
description: this.getAttribute("description") ?? undefined,
|
|
1910
1950
|
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
1951
|
+
proofLabel: this.getAttribute("proof-label") ?? undefined,
|
|
1952
|
+
proofPath: this.getAttribute("proof-path") ?? undefined,
|
|
1911
1953
|
title: this.getAttribute("title") ?? undefined
|
|
1912
1954
|
});
|
|
1913
1955
|
}
|
|
@@ -15,6 +15,14 @@ export declare const VoiceTurnLatency: import("vue").DefineComponent<import("vue
|
|
|
15
15
|
default: string;
|
|
16
16
|
type: StringConstructor;
|
|
17
17
|
};
|
|
18
|
+
proofLabel: {
|
|
19
|
+
default: undefined;
|
|
20
|
+
type: StringConstructor;
|
|
21
|
+
};
|
|
22
|
+
proofPath: {
|
|
23
|
+
default: undefined;
|
|
24
|
+
type: StringConstructor;
|
|
25
|
+
};
|
|
18
26
|
title: {
|
|
19
27
|
default: undefined;
|
|
20
28
|
type: StringConstructor;
|
|
@@ -38,6 +46,14 @@ export declare const VoiceTurnLatency: import("vue").DefineComponent<import("vue
|
|
|
38
46
|
default: string;
|
|
39
47
|
type: StringConstructor;
|
|
40
48
|
};
|
|
49
|
+
proofLabel: {
|
|
50
|
+
default: undefined;
|
|
51
|
+
type: StringConstructor;
|
|
52
|
+
};
|
|
53
|
+
proofPath: {
|
|
54
|
+
default: undefined;
|
|
55
|
+
type: StringConstructor;
|
|
56
|
+
};
|
|
41
57
|
title: {
|
|
42
58
|
default: undefined;
|
|
43
59
|
type: StringConstructor;
|
|
@@ -47,5 +63,7 @@ export declare const VoiceTurnLatency: import("vue").DefineComponent<import("vue
|
|
|
47
63
|
title: string;
|
|
48
64
|
path: string;
|
|
49
65
|
intervalMs: number;
|
|
66
|
+
proofPath: string;
|
|
67
|
+
proofLabel: string;
|
|
50
68
|
class: string;
|
|
51
69
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
package/dist/vue/index.js
CHANGED
|
@@ -1543,6 +1543,14 @@ var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) =>
|
|
|
1543
1543
|
}
|
|
1544
1544
|
return await response.json();
|
|
1545
1545
|
};
|
|
1546
|
+
var runVoiceTurnLatencyProof = async (path, options = {}) => {
|
|
1547
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1548
|
+
const response = await fetchImpl(path, { method: "POST" });
|
|
1549
|
+
if (!response.ok) {
|
|
1550
|
+
throw new Error(`Voice turn latency proof failed: HTTP ${response.status}`);
|
|
1551
|
+
}
|
|
1552
|
+
return response.json();
|
|
1553
|
+
};
|
|
1546
1554
|
var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
|
|
1547
1555
|
const listeners = new Set;
|
|
1548
1556
|
let closed = false;
|
|
@@ -1582,6 +1590,25 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
|
|
|
1582
1590
|
throw error;
|
|
1583
1591
|
}
|
|
1584
1592
|
};
|
|
1593
|
+
const runProof = async () => {
|
|
1594
|
+
if (!options.proofPath) {
|
|
1595
|
+
throw new Error("Voice turn latency proof path is not configured.");
|
|
1596
|
+
}
|
|
1597
|
+
snapshot = { ...snapshot, error: null, isLoading: true };
|
|
1598
|
+
emit();
|
|
1599
|
+
try {
|
|
1600
|
+
await runVoiceTurnLatencyProof(options.proofPath, options);
|
|
1601
|
+
return await refresh();
|
|
1602
|
+
} catch (error) {
|
|
1603
|
+
snapshot = {
|
|
1604
|
+
...snapshot,
|
|
1605
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1606
|
+
isLoading: false
|
|
1607
|
+
};
|
|
1608
|
+
emit();
|
|
1609
|
+
throw error;
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1585
1612
|
const close = () => {
|
|
1586
1613
|
closed = true;
|
|
1587
1614
|
if (timer) {
|
|
@@ -1600,6 +1627,7 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
|
|
|
1600
1627
|
getServerSnapshot: () => snapshot,
|
|
1601
1628
|
getSnapshot: () => snapshot,
|
|
1602
1629
|
refresh,
|
|
1630
|
+
runProof,
|
|
1603
1631
|
subscribe: (listener) => {
|
|
1604
1632
|
listeners.add(listener);
|
|
1605
1633
|
return () => {
|
|
@@ -1612,6 +1640,7 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
|
|
|
1612
1640
|
// src/client/turnLatencyWidget.ts
|
|
1613
1641
|
var DEFAULT_TITLE5 = "Turn Latency";
|
|
1614
1642
|
var DEFAULT_DESCRIPTION5 = "Per-turn timing from first transcript to commit and assistant response start.";
|
|
1643
|
+
var DEFAULT_PROOF_LABEL = "Run latency proof";
|
|
1615
1644
|
var escapeHtml6 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1616
1645
|
var formatMs = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
|
|
1617
1646
|
var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
|
|
@@ -1630,6 +1659,8 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
|
|
|
1630
1659
|
error: snapshot.error,
|
|
1631
1660
|
isLoading: snapshot.isLoading,
|
|
1632
1661
|
label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} slow` : warningCount > 0 ? `${warningCount} warnings` : `avg ${formatMs(snapshot.report?.averageTotalMs)}` : snapshot.isLoading ? "Checking" : "No turns",
|
|
1662
|
+
proofLabel: options.proofPath ? options.proofLabel ?? DEFAULT_PROOF_LABEL : undefined,
|
|
1663
|
+
showProofAction: Boolean(options.proofPath),
|
|
1633
1664
|
status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
1634
1665
|
title: options.title ?? DEFAULT_TITLE5,
|
|
1635
1666
|
turns,
|
|
@@ -1654,6 +1685,7 @@ var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
|
|
|
1654
1685
|
<strong class="absolute-voice-turn-latency__label">${escapeHtml6(model.label)}</strong>
|
|
1655
1686
|
</header>
|
|
1656
1687
|
<p class="absolute-voice-turn-latency__description">${escapeHtml6(model.description)}</p>
|
|
1688
|
+
${model.showProofAction ? `<button class="absolute-voice-turn-latency__proof" data-absolute-voice-turn-latency-proof type="button">${escapeHtml6(model.proofLabel ?? DEFAULT_PROOF_LABEL)}</button>` : ""}
|
|
1657
1689
|
${turns}
|
|
1658
1690
|
${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml6(model.error)}</p>` : ""}
|
|
1659
1691
|
</section>`;
|
|
@@ -1663,11 +1695,19 @@ var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {})
|
|
|
1663
1695
|
const render = () => {
|
|
1664
1696
|
element.innerHTML = renderVoiceTurnLatencyHTML(store.getSnapshot(), options);
|
|
1665
1697
|
};
|
|
1698
|
+
const handleClick = (event) => {
|
|
1699
|
+
const target = event.target;
|
|
1700
|
+
if (target instanceof Element && target.closest("[data-absolute-voice-turn-latency-proof]")) {
|
|
1701
|
+
store.runProof().catch(() => {});
|
|
1702
|
+
}
|
|
1703
|
+
};
|
|
1666
1704
|
const unsubscribe = store.subscribe(render);
|
|
1705
|
+
element.addEventListener("click", handleClick);
|
|
1667
1706
|
render();
|
|
1668
1707
|
store.refresh().catch(() => {});
|
|
1669
1708
|
return {
|
|
1670
1709
|
close: () => {
|
|
1710
|
+
element.removeEventListener("click", handleClick);
|
|
1671
1711
|
unsubscribe();
|
|
1672
1712
|
store.close();
|
|
1673
1713
|
},
|
|
@@ -1685,6 +1725,8 @@ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") =>
|
|
|
1685
1725
|
this.mounted = mountVoiceTurnLatency(this, this.getAttribute("path") ?? "/api/turn-latency", {
|
|
1686
1726
|
description: this.getAttribute("description") ?? undefined,
|
|
1687
1727
|
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
1728
|
+
proofLabel: this.getAttribute("proof-label") ?? undefined,
|
|
1729
|
+
proofPath: this.getAttribute("proof-path") ?? undefined,
|
|
1688
1730
|
title: this.getAttribute("title") ?? undefined
|
|
1689
1731
|
});
|
|
1690
1732
|
}
|
|
@@ -1717,7 +1759,14 @@ var useVoiceTurnLatency = (path = "/api/turn-latency", options = {}) => {
|
|
|
1717
1759
|
unsubscribe();
|
|
1718
1760
|
store.close();
|
|
1719
1761
|
});
|
|
1720
|
-
return {
|
|
1762
|
+
return {
|
|
1763
|
+
error,
|
|
1764
|
+
isLoading,
|
|
1765
|
+
refresh: store.refresh,
|
|
1766
|
+
report,
|
|
1767
|
+
runProof: store.runProof,
|
|
1768
|
+
updatedAt
|
|
1769
|
+
};
|
|
1721
1770
|
};
|
|
1722
1771
|
|
|
1723
1772
|
// src/vue/VoiceTurnLatency.ts
|
|
@@ -1728,12 +1777,16 @@ var VoiceTurnLatency = defineComponent6({
|
|
|
1728
1777
|
description: { default: undefined, type: String },
|
|
1729
1778
|
intervalMs: { default: 5000, type: Number },
|
|
1730
1779
|
path: { default: "/api/turn-latency", type: String },
|
|
1780
|
+
proofLabel: { default: undefined, type: String },
|
|
1781
|
+
proofPath: { default: undefined, type: String },
|
|
1731
1782
|
title: { default: undefined, type: String }
|
|
1732
1783
|
},
|
|
1733
1784
|
setup(props) {
|
|
1734
1785
|
const options = {
|
|
1735
1786
|
description: props.description,
|
|
1736
1787
|
intervalMs: props.intervalMs,
|
|
1788
|
+
proofLabel: props.proofLabel,
|
|
1789
|
+
proofPath: props.proofPath,
|
|
1737
1790
|
title: props.title
|
|
1738
1791
|
};
|
|
1739
1792
|
const latency = useVoiceTurnLatency(props.path, options);
|
|
@@ -1755,6 +1808,13 @@ var VoiceTurnLatency = defineComponent6({
|
|
|
1755
1808
|
h6("strong", { class: "absolute-voice-turn-latency__label" }, model.value.label)
|
|
1756
1809
|
]),
|
|
1757
1810
|
h6("p", { class: "absolute-voice-turn-latency__description" }, model.value.description),
|
|
1811
|
+
model.value.showProofAction ? h6("button", {
|
|
1812
|
+
class: "absolute-voice-turn-latency__proof",
|
|
1813
|
+
onClick: () => {
|
|
1814
|
+
latency.runProof().catch(() => {});
|
|
1815
|
+
},
|
|
1816
|
+
type: "button"
|
|
1817
|
+
}, model.value.proofLabel) : null,
|
|
1758
1818
|
model.value.turns.length ? h6("div", { class: "absolute-voice-turn-latency__turns" }, model.value.turns.map((turn) => h6("article", {
|
|
1759
1819
|
class: [
|
|
1760
1820
|
"absolute-voice-turn-latency__turn",
|
|
@@ -5,5 +5,6 @@ export declare const useVoiceTurnLatency: (path?: string, options?: VoiceTurnLat
|
|
|
5
5
|
isLoading: import("vue").ShallowRef<boolean, boolean>;
|
|
6
6
|
refresh: () => Promise<VoiceTurnLatencyReport | undefined>;
|
|
7
7
|
report: import("vue").ShallowRef<VoiceTurnLatencyReport | undefined, VoiceTurnLatencyReport | undefined>;
|
|
8
|
+
runProof: () => Promise<VoiceTurnLatencyReport | undefined>;
|
|
8
9
|
updatedAt: import("vue").ShallowRef<number | undefined, number | undefined>;
|
|
9
10
|
};
|