@absolutejs/voice 0.0.22-beta.94 → 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.
@@ -5,5 +5,6 @@ export { VoiceProviderCapabilitiesService } from './voice-provider-capabilities.
5
5
  export { VoiceProviderStatusService } from './voice-provider-status.service';
6
6
  export { VoiceRoutingStatusService } from './voice-routing-status.service';
7
7
  export { VoiceTraceTimelineService } from './voice-trace-timeline.service';
8
+ export { VoiceTurnLatencyService } from './voice-turn-latency.service';
8
9
  export { VoiceTurnQualityService } from './voice-turn-quality.service';
9
10
  export { VoiceWorkflowStatusService } from './voice-workflow-status.service';
@@ -1955,9 +1955,127 @@ VoiceTraceTimelineService = __decorateElement(_init, 0, "VoiceTraceTimelineServi
1955
1955
  __runInitializers(_init, 1, VoiceTraceTimelineService);
1956
1956
  __decoratorMetadata(_init, VoiceTraceTimelineService);
1957
1957
  let _VoiceTraceTimelineService = VoiceTraceTimelineService;
1958
- // src/angular/voice-turn-quality.service.ts
1958
+ // src/angular/voice-turn-latency.service.ts
1959
1959
  import { computed as computed7, Injectable as Injectable8, signal as signal8 } from "@angular/core";
1960
1960
 
1961
+ // src/client/turnLatency.ts
1962
+ var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) => {
1963
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1964
+ const response = await fetchImpl(path);
1965
+ if (!response.ok) {
1966
+ throw new Error(`Voice turn latency failed: HTTP ${response.status}`);
1967
+ }
1968
+ return await response.json();
1969
+ };
1970
+ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
1971
+ const listeners = new Set;
1972
+ let closed = false;
1973
+ let timer;
1974
+ let snapshot = {
1975
+ error: null,
1976
+ isLoading: false
1977
+ };
1978
+ const emit = () => {
1979
+ for (const listener of listeners) {
1980
+ listener();
1981
+ }
1982
+ };
1983
+ const refresh = async () => {
1984
+ if (closed) {
1985
+ return snapshot.report;
1986
+ }
1987
+ snapshot = { ...snapshot, error: null, isLoading: true };
1988
+ emit();
1989
+ try {
1990
+ const report = await fetchVoiceTurnLatency(path, options);
1991
+ snapshot = {
1992
+ error: null,
1993
+ isLoading: false,
1994
+ report,
1995
+ updatedAt: Date.now()
1996
+ };
1997
+ emit();
1998
+ return report;
1999
+ } catch (error) {
2000
+ snapshot = {
2001
+ ...snapshot,
2002
+ error: error instanceof Error ? error.message : String(error),
2003
+ isLoading: false
2004
+ };
2005
+ emit();
2006
+ throw error;
2007
+ }
2008
+ };
2009
+ const close = () => {
2010
+ closed = true;
2011
+ if (timer) {
2012
+ clearInterval(timer);
2013
+ timer = undefined;
2014
+ }
2015
+ listeners.clear();
2016
+ };
2017
+ if (options.intervalMs && options.intervalMs > 0) {
2018
+ timer = setInterval(() => {
2019
+ refresh().catch(() => {});
2020
+ }, options.intervalMs);
2021
+ }
2022
+ return {
2023
+ close,
2024
+ getServerSnapshot: () => snapshot,
2025
+ getSnapshot: () => snapshot,
2026
+ refresh,
2027
+ subscribe: (listener) => {
2028
+ listeners.add(listener);
2029
+ return () => {
2030
+ listeners.delete(listener);
2031
+ };
2032
+ }
2033
+ };
2034
+ };
2035
+
2036
+ // src/angular/voice-turn-latency.service.ts
2037
+ var _dec = [
2038
+ Injectable8({ providedIn: "root" })
2039
+ ];
2040
+ var _init = __decoratorStart(undefined);
2041
+
2042
+ class VoiceTurnLatencyService {
2043
+ connect(path = "/api/turn-latency", options = {}) {
2044
+ const store = createVoiceTurnLatencyStore(path, options);
2045
+ const errorSignal = signal8(null);
2046
+ const isLoadingSignal = signal8(false);
2047
+ const reportSignal = signal8(undefined);
2048
+ const updatedAtSignal = signal8(undefined);
2049
+ const sync = () => {
2050
+ const snapshot = store.getSnapshot();
2051
+ errorSignal.set(snapshot.error);
2052
+ isLoadingSignal.set(snapshot.isLoading);
2053
+ reportSignal.set(snapshot.report);
2054
+ updatedAtSignal.set(snapshot.updatedAt);
2055
+ };
2056
+ const unsubscribe = store.subscribe(sync);
2057
+ sync();
2058
+ store.refresh().catch(() => {});
2059
+ return {
2060
+ close: () => {
2061
+ unsubscribe();
2062
+ store.close();
2063
+ },
2064
+ error: computed7(() => errorSignal()),
2065
+ isLoading: computed7(() => isLoadingSignal()),
2066
+ refresh: store.refresh,
2067
+ report: computed7(() => reportSignal()),
2068
+ updatedAt: computed7(() => updatedAtSignal())
2069
+ };
2070
+ }
2071
+ }
2072
+ VoiceTurnLatencyService = __decorateElement(_init, 0, "VoiceTurnLatencyService", _dec, VoiceTurnLatencyService);
2073
+ __runInitializers(_init, 1, VoiceTurnLatencyService);
2074
+ __decoratorMetadata(_init, VoiceTurnLatencyService);
2075
+ let _VoiceTurnLatencyService = VoiceTurnLatencyService;
2076
+ // src/angular/voice-turn-quality.service.ts
2077
+ import { computed as computed8, Injectable as Injectable9, signal as signal9 } from "@angular/core";
2078
+
1961
2079
  // src/client/turnQuality.ts
1962
2080
  var fetchVoiceTurnQuality = async (path = "/api/turn-quality", options = {}) => {
1963
2081
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -2039,17 +2157,17 @@ var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) =>
2039
2157
 
2040
2158
  // src/angular/voice-turn-quality.service.ts
2041
2159
  var _dec = [
2042
- Injectable8({ providedIn: "root" })
2160
+ Injectable9({ providedIn: "root" })
2043
2161
  ];
2044
2162
  var _init = __decoratorStart(undefined);
2045
2163
 
2046
2164
  class VoiceTurnQualityService {
2047
2165
  connect(path = "/api/turn-quality", options = {}) {
2048
2166
  const store = createVoiceTurnQualityStore(path, options);
2049
- const errorSignal = signal8(null);
2050
- const isLoadingSignal = signal8(false);
2051
- const reportSignal = signal8(undefined);
2052
- const updatedAtSignal = signal8(undefined);
2167
+ const errorSignal = signal9(null);
2168
+ const isLoadingSignal = signal9(false);
2169
+ const reportSignal = signal9(undefined);
2170
+ const updatedAtSignal = signal9(undefined);
2053
2171
  const sync = () => {
2054
2172
  const snapshot = store.getSnapshot();
2055
2173
  errorSignal.set(snapshot.error);
@@ -2065,11 +2183,11 @@ class VoiceTurnQualityService {
2065
2183
  unsubscribe();
2066
2184
  store.close();
2067
2185
  },
2068
- error: computed7(() => errorSignal()),
2069
- isLoading: computed7(() => isLoadingSignal()),
2186
+ error: computed8(() => errorSignal()),
2187
+ isLoading: computed8(() => isLoadingSignal()),
2070
2188
  refresh: store.refresh,
2071
- report: computed7(() => reportSignal()),
2072
- updatedAt: computed7(() => updatedAtSignal())
2189
+ report: computed8(() => reportSignal()),
2190
+ updatedAt: computed8(() => updatedAtSignal())
2073
2191
  };
2074
2192
  }
2075
2193
  }
@@ -2078,7 +2196,7 @@ __runInitializers(_init, 1, VoiceTurnQualityService);
2078
2196
  __decoratorMetadata(_init, VoiceTurnQualityService);
2079
2197
  let _VoiceTurnQualityService = VoiceTurnQualityService;
2080
2198
  // src/angular/voice-workflow-status.service.ts
2081
- import { computed as computed8, Injectable as Injectable9, signal as signal9 } from "@angular/core";
2199
+ import { computed as computed9, Injectable as Injectable10, signal as signal10 } from "@angular/core";
2082
2200
 
2083
2201
  // src/client/workflowStatus.ts
2084
2202
  var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
@@ -2161,17 +2279,17 @@ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options =
2161
2279
 
2162
2280
  // src/angular/voice-workflow-status.service.ts
2163
2281
  var _dec = [
2164
- Injectable9({ providedIn: "root" })
2282
+ Injectable10({ providedIn: "root" })
2165
2283
  ];
2166
2284
  var _init = __decoratorStart(undefined);
2167
2285
 
2168
2286
  class VoiceWorkflowStatusService {
2169
2287
  connect(path = "/evals/scenarios/json", options = {}) {
2170
2288
  const store = createVoiceWorkflowStatusStore(path, options);
2171
- const errorSignal = signal9(null);
2172
- const isLoadingSignal = signal9(false);
2173
- const reportSignal = signal9(undefined);
2174
- const updatedAtSignal = signal9(undefined);
2289
+ const errorSignal = signal10(null);
2290
+ const isLoadingSignal = signal10(false);
2291
+ const reportSignal = signal10(undefined);
2292
+ const updatedAtSignal = signal10(undefined);
2175
2293
  const sync = () => {
2176
2294
  const snapshot = store.getSnapshot();
2177
2295
  errorSignal.set(snapshot.error);
@@ -2189,11 +2307,11 @@ class VoiceWorkflowStatusService {
2189
2307
  unsubscribe();
2190
2308
  store.close();
2191
2309
  },
2192
- error: computed8(() => errorSignal()),
2193
- isLoading: computed8(() => isLoadingSignal()),
2310
+ error: computed9(() => errorSignal()),
2311
+ isLoading: computed9(() => isLoadingSignal()),
2194
2312
  refresh: store.refresh,
2195
- report: computed8(() => reportSignal()),
2196
- updatedAt: computed8(() => updatedAtSignal())
2313
+ report: computed9(() => reportSignal()),
2314
+ updatedAt: computed9(() => updatedAtSignal())
2197
2315
  };
2198
2316
  }
2199
2317
  }
@@ -2204,6 +2322,7 @@ let _VoiceWorkflowStatusService = VoiceWorkflowStatusService;
2204
2322
  export {
2205
2323
  VoiceWorkflowStatusService,
2206
2324
  VoiceTurnQualityService,
2325
+ VoiceTurnLatencyService,
2207
2326
  VoiceTraceTimelineService,
2208
2327
  VoiceStreamService,
2209
2328
  VoiceRoutingStatusService,
@@ -0,0 +1,12 @@
1
+ import { type VoiceTurnLatencyClientOptions } from '../client/turnLatency';
2
+ import type { VoiceTurnLatencyReport } from '../turnLatency';
3
+ export declare class VoiceTurnLatencyService {
4
+ connect(path?: string, options?: VoiceTurnLatencyClientOptions): {
5
+ close: () => void;
6
+ error: import("@angular/core").Signal<string | null>;
7
+ isLoading: import("@angular/core").Signal<boolean>;
8
+ refresh: () => Promise<VoiceTurnLatencyReport | undefined>;
9
+ report: import("@angular/core").Signal<VoiceTurnLatencyReport | undefined>;
10
+ updatedAt: import("@angular/core").Signal<number | undefined>;
11
+ };
12
+ }
@@ -13,12 +13,14 @@ export { createVoiceRoutingStatusViewModel, defineVoiceRoutingStatusElement, get
13
13
  export { createVoiceProviderStatusStore, fetchVoiceProviderStatus } from './providerStatus';
14
14
  export { createVoiceProviderCapabilitiesStore, fetchVoiceProviderCapabilities } from './providerCapabilities';
15
15
  export { createVoiceTurnQualityStore, fetchVoiceTurnQuality } from './turnQuality';
16
+ export { createVoiceTurnLatencyStore, fetchVoiceTurnLatency } from './turnLatency';
16
17
  export { createVoiceTraceTimelineStore, fetchVoiceTraceTimeline } from './traceTimeline';
17
18
  export { createVoiceProviderSimulationControlsStore } from './providerSimulationControls';
18
19
  export { bindVoiceProviderSimulationControls, createVoiceProviderSimulationControlsViewModel, defineVoiceProviderSimulationControlsElement, mountVoiceProviderSimulationControls, renderVoiceProviderSimulationControlsHTML } from './providerSimulationControlsWidget';
19
20
  export { createVoiceProviderStatusViewModel, defineVoiceProviderStatusElement, getVoiceProviderStatusCSS, mountVoiceProviderStatus, renderVoiceProviderStatusHTML } from './providerStatusWidget';
20
21
  export { createVoiceProviderCapabilitiesViewModel, defineVoiceProviderCapabilitiesElement, getVoiceProviderCapabilitiesCSS, mountVoiceProviderCapabilities, renderVoiceProviderCapabilitiesHTML } from './providerCapabilitiesWidget';
21
22
  export { createVoiceTurnQualityViewModel, defineVoiceTurnQualityElement, getVoiceTurnQualityCSS, mountVoiceTurnQuality, renderVoiceTurnQualityHTML } from './turnQualityWidget';
23
+ export { createVoiceTurnLatencyViewModel, defineVoiceTurnLatencyElement, mountVoiceTurnLatency, renderVoiceTurnLatencyHTML } from './turnLatencyWidget';
22
24
  export { createVoiceTraceTimelineViewModel, defineVoiceTraceTimelineElement, getVoiceTraceTimelineCSS, mountVoiceTraceTimeline, renderVoiceTraceTimelineWidgetHTML } from './traceTimelineWidget';
23
25
  export { createVoiceWorkflowStatusStore, fetchVoiceWorkflowStatus } from './workflowStatus';
24
26
  export type { VoiceAppKitStatusClientOptions, VoiceAppKitStatusSnapshot } from './appKitStatus';
@@ -29,11 +31,13 @@ export type { VoiceRoutingStatusViewModel, VoiceRoutingStatusWidgetOptions } fro
29
31
  export type { VoiceProviderStatusClientOptions, VoiceProviderStatusSnapshot } from './providerStatus';
30
32
  export type { VoiceProviderCapabilitiesClientOptions, VoiceProviderCapabilitiesSnapshot } from './providerCapabilities';
31
33
  export type { VoiceTurnQualityClientOptions, VoiceTurnQualitySnapshot } from './turnQuality';
34
+ export type { VoiceTurnLatencyClientOptions, VoiceTurnLatencySnapshot } from './turnLatency';
32
35
  export type { VoiceTraceTimelineClientOptions, VoiceTraceTimelineSnapshot } from './traceTimeline';
33
36
  export type { VoiceProviderSimulationControlsOptions, VoiceProviderSimulationControlsSnapshot, VoiceProviderSimulationProvider } from './providerSimulationControls';
34
37
  export type { VoiceProviderSimulationControlsViewModel } from './providerSimulationControlsWidget';
35
38
  export type { VoiceProviderStatusCardView, VoiceProviderStatusViewModel, VoiceProviderStatusWidgetOptions } from './providerStatusWidget';
36
39
  export type { VoiceProviderCapabilitiesViewModel, VoiceProviderCapabilitiesWidgetOptions, VoiceProviderCapabilityCardView } from './providerCapabilitiesWidget';
37
40
  export type { VoiceTurnQualityCardView, VoiceTurnQualityViewModel, VoiceTurnQualityWidgetOptions } from './turnQualityWidget';
41
+ export type { VoiceTurnLatencyCardView, VoiceTurnLatencyViewModel, VoiceTurnLatencyWidgetOptions } from './turnLatencyWidget';
38
42
  export type { VoiceTraceTimelineSessionView, VoiceTraceTimelineViewModel, VoiceTraceTimelineWidgetOptions } from './traceTimelineWidget';
39
43
  export type { VoiceWorkflowStatusClientOptions, VoiceWorkflowStatusSnapshot } from './workflowStatus';
@@ -2330,6 +2330,80 @@ var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) =>
2330
2330
  }
2331
2331
  };
2332
2332
  };
2333
+ // src/client/turnLatency.ts
2334
+ var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) => {
2335
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2336
+ const response = await fetchImpl(path);
2337
+ if (!response.ok) {
2338
+ throw new Error(`Voice turn latency failed: HTTP ${response.status}`);
2339
+ }
2340
+ return await response.json();
2341
+ };
2342
+ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
2343
+ const listeners = new Set;
2344
+ let closed = false;
2345
+ let timer;
2346
+ let snapshot = {
2347
+ error: null,
2348
+ isLoading: false
2349
+ };
2350
+ const emit = () => {
2351
+ for (const listener of listeners) {
2352
+ listener();
2353
+ }
2354
+ };
2355
+ const refresh = async () => {
2356
+ if (closed) {
2357
+ return snapshot.report;
2358
+ }
2359
+ snapshot = { ...snapshot, error: null, isLoading: true };
2360
+ emit();
2361
+ try {
2362
+ const report = await fetchVoiceTurnLatency(path, options);
2363
+ snapshot = {
2364
+ error: null,
2365
+ isLoading: false,
2366
+ report,
2367
+ updatedAt: Date.now()
2368
+ };
2369
+ emit();
2370
+ return report;
2371
+ } catch (error) {
2372
+ snapshot = {
2373
+ ...snapshot,
2374
+ error: error instanceof Error ? error.message : String(error),
2375
+ isLoading: false
2376
+ };
2377
+ emit();
2378
+ throw error;
2379
+ }
2380
+ };
2381
+ const close = () => {
2382
+ closed = true;
2383
+ if (timer) {
2384
+ clearInterval(timer);
2385
+ timer = undefined;
2386
+ }
2387
+ listeners.clear();
2388
+ };
2389
+ if (options.intervalMs && options.intervalMs > 0) {
2390
+ timer = setInterval(() => {
2391
+ refresh().catch(() => {});
2392
+ }, options.intervalMs);
2393
+ }
2394
+ return {
2395
+ close,
2396
+ getServerSnapshot: () => snapshot,
2397
+ getSnapshot: () => snapshot,
2398
+ refresh,
2399
+ subscribe: (listener) => {
2400
+ listeners.add(listener);
2401
+ return () => {
2402
+ listeners.delete(listener);
2403
+ };
2404
+ }
2405
+ };
2406
+ };
2333
2407
  // src/client/traceTimeline.ts
2334
2408
  var fetchVoiceTraceTimeline = async (path = "/api/voice-traces", options = {}) => {
2335
2409
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -2927,51 +3001,136 @@ var defineVoiceTurnQualityElement = (tagName = "absolute-voice-turn-quality") =>
2927
3001
  }
2928
3002
  });
2929
3003
  };
2930
- // src/client/traceTimelineWidget.ts
2931
- var DEFAULT_TITLE6 = "Voice Traces";
2932
- var DEFAULT_DESCRIPTION6 = "Latest call timelines with provider latency, fallbacks, handoffs, and errors from your self-hosted trace store.";
3004
+ // src/client/turnLatencyWidget.ts
3005
+ var DEFAULT_TITLE6 = "Turn Latency";
3006
+ var DEFAULT_DESCRIPTION6 = "Per-turn timing from first transcript to commit and assistant response start.";
2933
3007
  var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2934
- var formatMs = (value) => typeof value === "number" ? `${value}ms` : "n/a";
3008
+ var formatMs = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
3009
+ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
3010
+ const turns = (snapshot.report?.turns ?? []).map((turn) => ({
3011
+ ...turn,
3012
+ label: turn.text || "Empty turn",
3013
+ rows: turn.stages.map((stage) => ({
3014
+ label: stage.label,
3015
+ value: formatMs(stage.valueMs)
3016
+ }))
3017
+ }));
3018
+ const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
3019
+ const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
3020
+ return {
3021
+ description: options.description ?? DEFAULT_DESCRIPTION6,
3022
+ error: snapshot.error,
3023
+ isLoading: snapshot.isLoading,
3024
+ label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} slow` : warningCount > 0 ? `${warningCount} warnings` : `avg ${formatMs(snapshot.report?.averageTotalMs)}` : snapshot.isLoading ? "Checking" : "No turns",
3025
+ status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
3026
+ title: options.title ?? DEFAULT_TITLE6,
3027
+ turns,
3028
+ updatedAt: snapshot.updatedAt
3029
+ };
3030
+ };
3031
+ var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
3032
+ const model = createVoiceTurnLatencyViewModel(snapshot, options);
3033
+ 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)}">
3034
+ <header>
3035
+ <strong>${escapeHtml7(turn.label)}</strong>
3036
+ <span>${escapeHtml7(turn.status)}</span>
3037
+ </header>
3038
+ <dl>${turn.rows.map((row) => `<div>
3039
+ <dt>${escapeHtml7(row.label)}</dt>
3040
+ <dd>${escapeHtml7(row.value)}</dd>
3041
+ </div>`).join("")}</dl>
3042
+ </article>`).join("")}</div>` : '<p class="absolute-voice-turn-latency__empty">Complete a voice turn to see latency diagnostics.</p>';
3043
+ return `<section class="absolute-voice-turn-latency absolute-voice-turn-latency--${escapeHtml7(model.status)}">
3044
+ <header class="absolute-voice-turn-latency__header">
3045
+ <span class="absolute-voice-turn-latency__eyebrow">${escapeHtml7(model.title)}</span>
3046
+ <strong class="absolute-voice-turn-latency__label">${escapeHtml7(model.label)}</strong>
3047
+ </header>
3048
+ <p class="absolute-voice-turn-latency__description">${escapeHtml7(model.description)}</p>
3049
+ ${turns}
3050
+ ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml7(model.error)}</p>` : ""}
3051
+ </section>`;
3052
+ };
3053
+ var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {}) => {
3054
+ const store = createVoiceTurnLatencyStore(path, options);
3055
+ const render = () => {
3056
+ element.innerHTML = renderVoiceTurnLatencyHTML(store.getSnapshot(), options);
3057
+ };
3058
+ const unsubscribe = store.subscribe(render);
3059
+ render();
3060
+ store.refresh().catch(() => {});
3061
+ return {
3062
+ close: () => {
3063
+ unsubscribe();
3064
+ store.close();
3065
+ },
3066
+ refresh: store.refresh
3067
+ };
3068
+ };
3069
+ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") => {
3070
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
3071
+ return;
3072
+ }
3073
+ customElements.define(tagName, class AbsoluteVoiceTurnLatencyElement extends HTMLElement {
3074
+ mounted;
3075
+ connectedCallback() {
3076
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
3077
+ this.mounted = mountVoiceTurnLatency(this, this.getAttribute("path") ?? "/api/turn-latency", {
3078
+ description: this.getAttribute("description") ?? undefined,
3079
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
3080
+ title: this.getAttribute("title") ?? undefined
3081
+ });
3082
+ }
3083
+ disconnectedCallback() {
3084
+ this.mounted?.close();
3085
+ this.mounted = undefined;
3086
+ }
3087
+ });
3088
+ };
3089
+ // src/client/traceTimelineWidget.ts
3090
+ var DEFAULT_TITLE7 = "Voice Traces";
3091
+ var DEFAULT_DESCRIPTION7 = "Latest call timelines with provider latency, fallbacks, handoffs, and errors from your self-hosted trace store.";
3092
+ var escapeHtml8 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3093
+ var formatMs2 = (value) => typeof value === "number" ? `${value}ms` : "n/a";
2935
3094
  var formatProviders = (session) => session.providers.length ? session.providers.map((provider) => provider.provider).join(", ") : "No providers";
2936
3095
  var createVoiceTraceTimelineViewModel = (snapshot, options = {}) => {
2937
3096
  const sessions = (snapshot.report?.sessions ?? []).slice(0, options.limit ?? 3).map((session) => ({
2938
3097
  ...session,
2939
3098
  detailHref: `${options.detailBasePath ?? "/traces"}/${encodeURIComponent(session.sessionId)}`,
2940
- durationLabel: formatMs(session.summary.callDurationMs),
3099
+ durationLabel: formatMs2(session.summary.callDurationMs),
2941
3100
  label: `${session.summary.eventCount} events / ${session.summary.turnCount} turns`,
2942
3101
  providerLabel: formatProviders(session)
2943
3102
  }));
2944
3103
  const failed = sessions.filter((session) => session.status === "failed").length;
2945
3104
  const warnings = sessions.filter((session) => session.status === "warning").length;
2946
3105
  return {
2947
- description: options.description ?? DEFAULT_DESCRIPTION6,
3106
+ description: options.description ?? DEFAULT_DESCRIPTION7,
2948
3107
  error: snapshot.error,
2949
3108
  isLoading: snapshot.isLoading,
2950
3109
  label: snapshot.error ? "Unavailable" : failed > 0 ? `${failed} failed` : warnings > 0 ? `${warnings} warning` : sessions.length ? `${sessions.length} recent` : snapshot.isLoading ? "Checking" : "No traces yet",
2951
3110
  sessions,
2952
3111
  status: snapshot.error ? "error" : failed > 0 ? "failed" : warnings > 0 ? "warning" : sessions.length ? "ready" : snapshot.isLoading ? "loading" : "empty",
2953
- title: options.title ?? DEFAULT_TITLE6,
3112
+ title: options.title ?? DEFAULT_TITLE7,
2954
3113
  updatedAt: snapshot.updatedAt
2955
3114
  };
2956
3115
  };
2957
3116
  var renderVoiceTraceTimelineWidgetHTML = (snapshot, options = {}) => {
2958
3117
  const model = createVoiceTraceTimelineViewModel(snapshot, options);
2959
- const sessions = model.sessions.length ? `<div class="absolute-voice-trace-timeline__sessions">${model.sessions.map((session) => `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml7(session.status)}">
3118
+ const sessions = model.sessions.length ? `<div class="absolute-voice-trace-timeline__sessions">${model.sessions.map((session) => `<article class="absolute-voice-trace-timeline__session absolute-voice-trace-timeline__session--${escapeHtml8(session.status)}">
2960
3119
  <header>
2961
- <strong>${escapeHtml7(session.sessionId)}</strong>
2962
- <span>${escapeHtml7(session.status)}</span>
3120
+ <strong>${escapeHtml8(session.sessionId)}</strong>
3121
+ <span>${escapeHtml8(session.status)}</span>
2963
3122
  </header>
2964
- <p>${escapeHtml7(session.label)} \xB7 ${escapeHtml7(session.durationLabel)} \xB7 ${escapeHtml7(session.providerLabel)}</p>
2965
- <a href="${escapeHtml7(session.detailHref)}">Open timeline</a>
3123
+ <p>${escapeHtml8(session.label)} \xB7 ${escapeHtml8(session.durationLabel)} \xB7 ${escapeHtml8(session.providerLabel)}</p>
3124
+ <a href="${escapeHtml8(session.detailHref)}">Open timeline</a>
2966
3125
  </article>`).join("")}</div>` : '<p class="absolute-voice-trace-timeline__empty">Run a voice session to see call timelines.</p>';
2967
- return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml7(model.status)}">
3126
+ return `<section class="absolute-voice-trace-timeline absolute-voice-trace-timeline--${escapeHtml8(model.status)}">
2968
3127
  <header class="absolute-voice-trace-timeline__header">
2969
- <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml7(model.title)}</span>
2970
- <strong class="absolute-voice-trace-timeline__label">${escapeHtml7(model.label)}</strong>
3128
+ <span class="absolute-voice-trace-timeline__eyebrow">${escapeHtml8(model.title)}</span>
3129
+ <strong class="absolute-voice-trace-timeline__label">${escapeHtml8(model.label)}</strong>
2971
3130
  </header>
2972
- <p class="absolute-voice-trace-timeline__description">${escapeHtml7(model.description)}</p>
3131
+ <p class="absolute-voice-trace-timeline__description">${escapeHtml8(model.description)}</p>
2973
3132
  ${sessions}
2974
- ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml7(model.error)}</p>` : ""}
3133
+ ${model.error ? `<p class="absolute-voice-trace-timeline__error">${escapeHtml8(model.error)}</p>` : ""}
2975
3134
  </section>`;
2976
3135
  };
2977
3136
  var getVoiceTraceTimelineCSS = () => `.absolute-voice-trace-timeline{border:1px solid #bad7d3;border-radius:20px;background:#f3fffb;color:#09201c;padding:18px;box-shadow:0 18px 40px rgba(9,32,28,.12);font-family:inherit}.absolute-voice-trace-timeline--error,.absolute-voice-trace-timeline--failed{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-trace-timeline--warning{border-color:#fbbf24;background:#fffaf0}.absolute-voice-trace-timeline__header,.absolute-voice-trace-timeline__session header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-trace-timeline__eyebrow{color:#17665b;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-trace-timeline__label{font-size:24px;line-height:1}.absolute-voice-trace-timeline__description,.absolute-voice-trace-timeline__session p,.absolute-voice-trace-timeline__empty{color:#35544f}.absolute-voice-trace-timeline__sessions{display:grid;gap:12px;margin-top:14px}.absolute-voice-trace-timeline__session{background:#fff;border:1px solid #cfe7e2;border-radius:16px;padding:14px}.absolute-voice-trace-timeline__session--failed{border-color:#f2a7a7}.absolute-voice-trace-timeline__session--warning{border-color:#fbbf24}.absolute-voice-trace-timeline__session p{margin:10px 0}.absolute-voice-trace-timeline__session a{color:#0f766e;font-weight:800}.absolute-voice-trace-timeline__empty{margin:14px 0 0}.absolute-voice-trace-timeline__error{color:#9f1239;font-weight:700}`;
@@ -3094,6 +3253,7 @@ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options =
3094
3253
  };
3095
3254
  export {
3096
3255
  renderVoiceTurnQualityHTML,
3256
+ renderVoiceTurnLatencyHTML,
3097
3257
  renderVoiceTraceTimelineWidgetHTML,
3098
3258
  renderVoiceRoutingStatusHTML,
3099
3259
  renderVoiceProviderStatusHTML,
@@ -3101,6 +3261,7 @@ export {
3101
3261
  renderVoiceProviderCapabilitiesHTML,
3102
3262
  renderVoiceOpsStatusHTML,
3103
3263
  mountVoiceTurnQuality,
3264
+ mountVoiceTurnLatency,
3104
3265
  mountVoiceTraceTimeline,
3105
3266
  mountVoiceRoutingStatus,
3106
3267
  mountVoiceProviderStatus,
@@ -3116,12 +3277,14 @@ export {
3116
3277
  getVoiceOpsStatusCSS,
3117
3278
  fetchVoiceWorkflowStatus,
3118
3279
  fetchVoiceTurnQuality,
3280
+ fetchVoiceTurnLatency,
3119
3281
  fetchVoiceTraceTimeline,
3120
3282
  fetchVoiceRoutingStatus,
3121
3283
  fetchVoiceProviderStatus,
3122
3284
  fetchVoiceProviderCapabilities,
3123
3285
  fetchVoiceAppKitStatus,
3124
3286
  defineVoiceTurnQualityElement,
3287
+ defineVoiceTurnLatencyElement,
3125
3288
  defineVoiceTraceTimelineElement,
3126
3289
  defineVoiceRoutingStatusElement,
3127
3290
  defineVoiceProviderStatusElement,
@@ -3132,6 +3295,8 @@ export {
3132
3295
  createVoiceWorkflowStatusStore,
3133
3296
  createVoiceTurnQualityViewModel,
3134
3297
  createVoiceTurnQualityStore,
3298
+ createVoiceTurnLatencyViewModel,
3299
+ createVoiceTurnLatencyStore,
3135
3300
  createVoiceTraceTimelineViewModel,
3136
3301
  createVoiceTraceTimelineStore,
3137
3302
  createVoiceStream,
@@ -0,0 +1,19 @@
1
+ import type { VoiceTurnLatencyReport } from '../turnLatency';
2
+ export type VoiceTurnLatencyClientOptions = {
3
+ fetch?: typeof fetch;
4
+ intervalMs?: number;
5
+ };
6
+ export type VoiceTurnLatencySnapshot = {
7
+ error: string | null;
8
+ isLoading: boolean;
9
+ report?: VoiceTurnLatencyReport;
10
+ updatedAt?: number;
11
+ };
12
+ export declare const fetchVoiceTurnLatency: (path?: string, options?: Pick<VoiceTurnLatencyClientOptions, "fetch">) => Promise<VoiceTurnLatencyReport>;
13
+ export declare const createVoiceTurnLatencyStore: (path?: string, options?: VoiceTurnLatencyClientOptions) => {
14
+ close: () => void;
15
+ getServerSnapshot: () => VoiceTurnLatencySnapshot;
16
+ getSnapshot: () => VoiceTurnLatencySnapshot;
17
+ refresh: () => Promise<VoiceTurnLatencyReport | undefined>;
18
+ subscribe: (listener: () => void) => () => void;
19
+ };
@@ -0,0 +1,30 @@
1
+ import type { VoiceTurnLatencyItem } from '../turnLatency';
2
+ import { type VoiceTurnLatencyClientOptions, type VoiceTurnLatencySnapshot } from './turnLatency';
3
+ export type VoiceTurnLatencyCardView = VoiceTurnLatencyItem & {
4
+ label: string;
5
+ rows: Array<{
6
+ label: string;
7
+ value: string;
8
+ }>;
9
+ };
10
+ export type VoiceTurnLatencyViewModel = {
11
+ description: string;
12
+ error: string | null;
13
+ isLoading: boolean;
14
+ label: string;
15
+ status: 'empty' | 'error' | 'loading' | 'ready' | 'warning';
16
+ title: string;
17
+ turns: VoiceTurnLatencyCardView[];
18
+ updatedAt?: number;
19
+ };
20
+ export type VoiceTurnLatencyWidgetOptions = VoiceTurnLatencyClientOptions & {
21
+ description?: string;
22
+ title?: string;
23
+ };
24
+ export declare const createVoiceTurnLatencyViewModel: (snapshot: VoiceTurnLatencySnapshot, options?: VoiceTurnLatencyWidgetOptions) => VoiceTurnLatencyViewModel;
25
+ export declare const renderVoiceTurnLatencyHTML: (snapshot: VoiceTurnLatencySnapshot, options?: VoiceTurnLatencyWidgetOptions) => string;
26
+ export declare const mountVoiceTurnLatency: (element: Element, path?: string, options?: VoiceTurnLatencyWidgetOptions) => {
27
+ close: () => void;
28
+ refresh: () => Promise<import("..").VoiceTurnLatencyReport | undefined>;
29
+ };
30
+ export declare const defineVoiceTurnLatencyElement: (tagName?: string) => void;
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, crea
10
10
  export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool } from './agent';
11
11
  export { createVoiceToolIdempotencyKey, createVoiceToolRuntime } from './toolRuntime';
12
12
  export { createVoiceToolContract, createVoiceToolContractHTMLHandler, createVoiceToolContractJSONHandler, createVoiceToolContractRoutes, createVoiceToolRuntimeContractDefaults, renderVoiceToolContractHTML, runVoiceToolContractSuite, runVoiceToolContract } from './toolContract';
13
+ export { createVoiceTurnLatencyHTMLHandler, createVoiceTurnLatencyJSONHandler, createVoiceTurnLatencyRoutes, renderVoiceTurnLatencyHTML, summarizeVoiceTurnLatency } from './turnLatency';
13
14
  export { createVoiceTurnQualityHTMLHandler, createVoiceTurnQualityJSONHandler, createVoiceTurnQualityRoutes, renderVoiceTurnQualityHTML, summarizeVoiceTurnQuality } from './turnQuality';
14
15
  export { createVoiceOutcomeContractHTMLHandler, createVoiceOutcomeContractJSONHandler, createVoiceOutcomeContractRoutes, renderVoiceOutcomeContractHTML, runVoiceOutcomeContractSuite } from './outcomeContract';
15
16
  export { applyVoiceTelephonyOutcome, createMemoryVoiceTelephonyWebhookIdempotencyStore, createVoiceTelephonyOutcomePolicy, createVoiceTelephonyWebhookHandler, createVoiceTelephonyWebhookRoutes, parseVoiceTelephonyWebhookEvent, resolveVoiceTelephonyOutcome, signVoiceTwilioWebhook, verifyVoiceTwilioWebhookSignature, voiceTelephonyOutcomeToRouteResult } from './telephonyOutcome';
@@ -61,6 +62,7 @@ export type { AnthropicVoiceAssistantModelOptions, GeminiVoiceAssistantModelOpti
61
62
  export type { OpenAIVoiceTTSOptions, OpenAIVoiceTTSVoice } from './openaiTTS';
62
63
  export type { VoiceProviderHealthStatus, VoiceProviderHealthSummary, VoiceProviderHealthSummaryOptions } from './providerHealth';
63
64
  export type { VoiceProviderCapabilityDefinition, VoiceProviderCapabilityHandlerOptions, VoiceProviderCapabilityHTMLHandlerOptions, VoiceProviderCapabilityKind, VoiceProviderCapabilityOptions, VoiceProviderCapabilityReport, VoiceProviderCapabilityRoutesOptions, VoiceProviderCapabilitySummary } from './providerCapabilities';
65
+ export type { VoiceTurnLatencyHTMLHandlerOptions, VoiceTurnLatencyItem, VoiceTurnLatencyOptions, VoiceTurnLatencyReport, VoiceTurnLatencyRoutesOptions, VoiceTurnLatencyStage, VoiceTurnLatencyStatus } from './turnLatency';
64
66
  export type { VoiceTurnQualityHTMLHandlerOptions, VoiceTurnQualityItem, VoiceTurnQualityOptions, VoiceTurnQualityReport, VoiceTurnQualityRoutesOptions, VoiceTurnQualityStatus } from './turnQuality';
65
67
  export type { VoiceOutcomeContractDefinition, VoiceOutcomeContractHTMLHandlerOptions, VoiceOutcomeContractIssue, VoiceOutcomeContractOptions, VoiceOutcomeContractReport, VoiceOutcomeContractRoutesOptions, VoiceOutcomeContractStatus, VoiceOutcomeContractSuiteReport } from './outcomeContract';
66
68
  export type { VoiceTelephonyOutcomeAction, VoiceTelephonyOutcomeDecision, VoiceTelephonyOutcomePolicy, VoiceTelephonyOutcomeProviderEvent, VoiceTelephonyOutcomeRouteResult, VoiceTelephonyOutcomeStatusDecision, VoiceTelephonyWebhookDecision, VoiceTelephonyWebhookHandlerOptions, VoiceTelephonyWebhookIdempotencyStore, VoiceTelephonyWebhookParseInput, VoiceTelephonyWebhookProvider, VoiceTelephonyWebhookRoutesOptions, VoiceTelephonyWebhookVerificationResult, StoredVoiceTelephonyWebhookDecision } from './telephonyOutcome';