@absolutejs/voice 0.0.22-beta.97 → 0.0.22-beta.98

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.
@@ -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
  }
@@ -13,7 +13,7 @@ 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
+ export { createVoiceTurnLatencyStore, fetchVoiceTurnLatency, runVoiceTurnLatencyProof } from './turnLatency';
17
17
  export { createVoiceTraceTimelineStore, fetchVoiceTraceTimeline } from './traceTimeline';
18
18
  export { createVoiceProviderSimulationControlsStore } from './providerSimulationControls';
19
19
  export { bindVoiceProviderSimulationControls, createVoiceProviderSimulationControlsViewModel, defineVoiceProviderSimulationControlsElement, mountVoiceProviderSimulationControls, renderVoiceProviderSimulationControlsHTML } from './providerSimulationControlsWidget';
@@ -2339,6 +2339,14 @@ var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) =>
2339
2339
  }
2340
2340
  return await response.json();
2341
2341
  };
2342
+ var runVoiceTurnLatencyProof = async (path, options = {}) => {
2343
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2344
+ const response = await fetchImpl(path, { method: "POST" });
2345
+ if (!response.ok) {
2346
+ throw new Error(`Voice turn latency proof failed: HTTP ${response.status}`);
2347
+ }
2348
+ return response.json();
2349
+ };
2342
2350
  var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
2343
2351
  const listeners = new Set;
2344
2352
  let closed = false;
@@ -2378,6 +2386,25 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
2378
2386
  throw error;
2379
2387
  }
2380
2388
  };
2389
+ const runProof = async () => {
2390
+ if (!options.proofPath) {
2391
+ throw new Error("Voice turn latency proof path is not configured.");
2392
+ }
2393
+ snapshot = { ...snapshot, error: null, isLoading: true };
2394
+ emit();
2395
+ try {
2396
+ await runVoiceTurnLatencyProof(options.proofPath, options);
2397
+ return await refresh();
2398
+ } catch (error) {
2399
+ snapshot = {
2400
+ ...snapshot,
2401
+ error: error instanceof Error ? error.message : String(error),
2402
+ isLoading: false
2403
+ };
2404
+ emit();
2405
+ throw error;
2406
+ }
2407
+ };
2381
2408
  const close = () => {
2382
2409
  closed = true;
2383
2410
  if (timer) {
@@ -2396,6 +2423,7 @@ var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) =>
2396
2423
  getServerSnapshot: () => snapshot,
2397
2424
  getSnapshot: () => snapshot,
2398
2425
  refresh,
2426
+ runProof,
2399
2427
  subscribe: (listener) => {
2400
2428
  listeners.add(listener);
2401
2429
  return () => {
@@ -3004,6 +3032,7 @@ var defineVoiceTurnQualityElement = (tagName = "absolute-voice-turn-quality") =>
3004
3032
  // src/client/turnLatencyWidget.ts
3005
3033
  var DEFAULT_TITLE6 = "Turn Latency";
3006
3034
  var DEFAULT_DESCRIPTION6 = "Per-turn timing from first transcript to commit and assistant response start.";
3035
+ var DEFAULT_PROOF_LABEL = "Run latency proof";
3007
3036
  var escapeHtml7 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
3008
3037
  var formatMs = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
3009
3038
  var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
@@ -3022,6 +3051,8 @@ var createVoiceTurnLatencyViewModel = (snapshot, options = {}) => {
3022
3051
  error: snapshot.error,
3023
3052
  isLoading: snapshot.isLoading,
3024
3053
  label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} slow` : warningCount > 0 ? `${warningCount} warnings` : `avg ${formatMs(snapshot.report?.averageTotalMs)}` : snapshot.isLoading ? "Checking" : "No turns",
3054
+ proofLabel: options.proofPath ? options.proofLabel ?? DEFAULT_PROOF_LABEL : undefined,
3055
+ showProofAction: Boolean(options.proofPath),
3025
3056
  status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
3026
3057
  title: options.title ?? DEFAULT_TITLE6,
3027
3058
  turns,
@@ -3046,6 +3077,7 @@ var renderVoiceTurnLatencyHTML = (snapshot, options = {}) => {
3046
3077
  <strong class="absolute-voice-turn-latency__label">${escapeHtml7(model.label)}</strong>
3047
3078
  </header>
3048
3079
  <p class="absolute-voice-turn-latency__description">${escapeHtml7(model.description)}</p>
3080
+ ${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
3081
  ${turns}
3050
3082
  ${model.error ? `<p class="absolute-voice-turn-latency__error">${escapeHtml7(model.error)}</p>` : ""}
3051
3083
  </section>`;
@@ -3055,11 +3087,19 @@ var mountVoiceTurnLatency = (element, path = "/api/turn-latency", options = {})
3055
3087
  const render = () => {
3056
3088
  element.innerHTML = renderVoiceTurnLatencyHTML(store.getSnapshot(), options);
3057
3089
  };
3090
+ const handleClick = (event) => {
3091
+ const target = event.target;
3092
+ if (target instanceof Element && target.closest("[data-absolute-voice-turn-latency-proof]")) {
3093
+ store.runProof().catch(() => {});
3094
+ }
3095
+ };
3058
3096
  const unsubscribe = store.subscribe(render);
3097
+ element.addEventListener("click", handleClick);
3059
3098
  render();
3060
3099
  store.refresh().catch(() => {});
3061
3100
  return {
3062
3101
  close: () => {
3102
+ element.removeEventListener("click", handleClick);
3063
3103
  unsubscribe();
3064
3104
  store.close();
3065
3105
  },
@@ -3077,6 +3117,8 @@ var defineVoiceTurnLatencyElement = (tagName = "absolute-voice-turn-latency") =>
3077
3117
  this.mounted = mountVoiceTurnLatency(this, this.getAttribute("path") ?? "/api/turn-latency", {
3078
3118
  description: this.getAttribute("description") ?? undefined,
3079
3119
  intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
3120
+ proofLabel: this.getAttribute("proof-label") ?? undefined,
3121
+ proofPath: this.getAttribute("proof-path") ?? undefined,
3080
3122
  title: this.getAttribute("title") ?? undefined
3081
3123
  });
3082
3124
  }
@@ -3252,6 +3294,7 @@ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options =
3252
3294
  };
3253
3295
  };
3254
3296
  export {
3297
+ runVoiceTurnLatencyProof,
3255
3298
  renderVoiceTurnQualityHTML,
3256
3299
  renderVoiceTurnLatencyHTML,
3257
3300
  renderVoiceTraceTimelineWidgetHTML,
@@ -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;
@@ -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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
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 snapshot = useVoiceTurnLatency(path, options);
1951
- const model = createVoiceTurnLatencyViewModel(snapshot, options);
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
  };
@@ -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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
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 { error, isLoading, refresh: store.refresh, report, updatedAt };
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.97",
3
+ "version": "0.0.22-beta.98",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",