@absolutejs/voice 0.0.22-beta.58 → 0.0.22-beta.59

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.
@@ -387,9 +387,256 @@ var VoiceOpsStatus = ({
387
387
  ]
388
388
  }, undefined, true, undefined, this);
389
389
  };
390
- // src/react/useVoiceStream.tsx
390
+ // src/react/useVoiceRoutingStatus.tsx
391
391
  import { useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
392
392
 
393
+ // src/client/routingStatus.ts
394
+ var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
395
+ const fetchImpl = options.fetch ?? globalThis.fetch;
396
+ const response = await fetchImpl(path);
397
+ if (!response.ok) {
398
+ throw new Error(`Voice routing status failed: HTTP ${response.status}`);
399
+ }
400
+ return await response.json();
401
+ };
402
+ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
403
+ const listeners = new Set;
404
+ let closed = false;
405
+ let timer;
406
+ let snapshot = {
407
+ decision: null,
408
+ error: null,
409
+ isLoading: false
410
+ };
411
+ const emit = () => {
412
+ for (const listener of listeners) {
413
+ listener();
414
+ }
415
+ };
416
+ const refresh = async () => {
417
+ if (closed) {
418
+ return snapshot.decision;
419
+ }
420
+ snapshot = {
421
+ ...snapshot,
422
+ error: null,
423
+ isLoading: true
424
+ };
425
+ emit();
426
+ try {
427
+ const decision = await fetchVoiceRoutingStatus(path, options);
428
+ snapshot = {
429
+ decision,
430
+ error: null,
431
+ isLoading: false,
432
+ updatedAt: Date.now()
433
+ };
434
+ emit();
435
+ return decision;
436
+ } catch (error) {
437
+ snapshot = {
438
+ ...snapshot,
439
+ error: error instanceof Error ? error.message : String(error),
440
+ isLoading: false
441
+ };
442
+ emit();
443
+ throw error;
444
+ }
445
+ };
446
+ const close = () => {
447
+ closed = true;
448
+ if (timer) {
449
+ clearInterval(timer);
450
+ timer = undefined;
451
+ }
452
+ listeners.clear();
453
+ };
454
+ if (options.intervalMs && options.intervalMs > 0) {
455
+ timer = setInterval(() => {
456
+ refresh().catch(() => {});
457
+ }, options.intervalMs);
458
+ }
459
+ return {
460
+ close,
461
+ getServerSnapshot: () => snapshot,
462
+ getSnapshot: () => snapshot,
463
+ refresh,
464
+ subscribe: (listener) => {
465
+ listeners.add(listener);
466
+ return () => {
467
+ listeners.delete(listener);
468
+ };
469
+ }
470
+ };
471
+ };
472
+
473
+ // src/react/useVoiceRoutingStatus.tsx
474
+ var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
475
+ const storeRef = useRef2(null);
476
+ if (!storeRef.current) {
477
+ storeRef.current = createVoiceRoutingStatusStore(path, options);
478
+ }
479
+ const store = storeRef.current;
480
+ useEffect2(() => {
481
+ store.refresh().catch(() => {});
482
+ return () => store.close();
483
+ }, [store]);
484
+ return {
485
+ ...useSyncExternalStore2(store.subscribe, store.getSnapshot, store.getServerSnapshot),
486
+ refresh: store.refresh
487
+ };
488
+ };
489
+
490
+ // src/client/routingStatusWidget.ts
491
+ var DEFAULT_TITLE2 = "Voice Routing";
492
+ var DEFAULT_DESCRIPTION2 = "Latest provider routing decision from the self-hosted trace store.";
493
+ var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
494
+ var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
495
+ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
496
+ const decision = snapshot.decision;
497
+ const rows = decision ? [
498
+ { label: "Kind", value: decision.kind.toUpperCase() },
499
+ { label: "Policy", value: formatValue(decision.routing, "Unknown") },
500
+ { label: "Provider", value: formatValue(decision.provider, "Unknown") },
501
+ {
502
+ label: "Selected",
503
+ value: formatValue(decision.selectedProvider, "Unknown")
504
+ },
505
+ {
506
+ label: "Fallback",
507
+ value: formatValue(decision.fallbackProvider)
508
+ },
509
+ { label: "Status", value: formatValue(decision.status, "unknown") },
510
+ {
511
+ label: "Latency budget",
512
+ value: typeof decision.latencyBudgetMs === "number" ? `${decision.latencyBudgetMs}ms` : "None"
513
+ }
514
+ ] : [];
515
+ return {
516
+ decision,
517
+ description: options.description ?? DEFAULT_DESCRIPTION2,
518
+ error: snapshot.error,
519
+ isLoading: snapshot.isLoading,
520
+ label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
521
+ rows,
522
+ status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
523
+ title: options.title ?? DEFAULT_TITLE2,
524
+ updatedAt: snapshot.updatedAt
525
+ };
526
+ };
527
+ var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
528
+ const model = createVoiceRoutingStatusViewModel(snapshot, options);
529
+ const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
530
+ <span>${escapeHtml2(row.label)}</span>
531
+ <strong>${escapeHtml2(row.value)}</strong>
532
+ </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
533
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml2(model.status)}">
534
+ <header class="absolute-voice-routing-status__header">
535
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml2(model.title)}</span>
536
+ <strong class="absolute-voice-routing-status__label">${escapeHtml2(model.label)}</strong>
537
+ </header>
538
+ <p class="absolute-voice-routing-status__description">${escapeHtml2(model.description)}</p>
539
+ ${rows}
540
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml2(model.error)}</p>` : ""}
541
+ </section>`;
542
+ };
543
+ var getVoiceRoutingStatusCSS = () => `.absolute-voice-routing-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-routing-status--error{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-routing-status__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-routing-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-routing-status__label{font-size:24px;line-height:1}.absolute-voice-routing-status__description{color:#514733;margin:12px 0 0}.absolute-voice-routing-status__grid{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin-top:14px}.absolute-voice-routing-status__grid div{background:#fff;border:1px solid #eee4d2;border-radius:14px;padding:10px 12px}.absolute-voice-routing-status__grid span{color:#655944;display:block;font-size:12px;margin-bottom:4px}.absolute-voice-routing-status__grid strong{overflow-wrap:anywhere}.absolute-voice-routing-status__empty{color:#655944;margin:14px 0 0}.absolute-voice-routing-status__error{color:#9f1239;font-weight:700}`;
544
+ var mountVoiceRoutingStatus = (element, path = "/api/routing/latest", options = {}) => {
545
+ const store = createVoiceRoutingStatusStore(path, options);
546
+ const render = () => {
547
+ element.innerHTML = renderVoiceRoutingStatusHTML(store.getSnapshot(), options);
548
+ };
549
+ const unsubscribe = store.subscribe(render);
550
+ render();
551
+ store.refresh().catch(() => {});
552
+ return {
553
+ close: () => {
554
+ unsubscribe();
555
+ store.close();
556
+ },
557
+ refresh: store.refresh
558
+ };
559
+ };
560
+ var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status") => {
561
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
562
+ return;
563
+ }
564
+ customElements.define(tagName, class AbsoluteVoiceRoutingStatusElement extends HTMLElement {
565
+ mounted;
566
+ connectedCallback() {
567
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
568
+ this.mounted = mountVoiceRoutingStatus(this, this.getAttribute("path") ?? "/api/routing/latest", {
569
+ description: this.getAttribute("description") ?? undefined,
570
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
571
+ title: this.getAttribute("title") ?? undefined
572
+ });
573
+ }
574
+ disconnectedCallback() {
575
+ this.mounted?.close();
576
+ this.mounted = undefined;
577
+ }
578
+ });
579
+ };
580
+
581
+ // src/react/VoiceRoutingStatus.tsx
582
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
583
+ var VoiceRoutingStatus = ({
584
+ className,
585
+ path = "/api/routing/latest",
586
+ ...options
587
+ }) => {
588
+ const snapshot = useVoiceRoutingStatus(path, options);
589
+ const model = createVoiceRoutingStatusViewModel(snapshot, options);
590
+ return /* @__PURE__ */ jsxDEV2("section", {
591
+ className: [
592
+ "absolute-voice-routing-status",
593
+ `absolute-voice-routing-status--${model.status}`,
594
+ className
595
+ ].filter(Boolean).join(" "),
596
+ children: [
597
+ /* @__PURE__ */ jsxDEV2("header", {
598
+ className: "absolute-voice-routing-status__header",
599
+ children: [
600
+ /* @__PURE__ */ jsxDEV2("span", {
601
+ className: "absolute-voice-routing-status__eyebrow",
602
+ children: model.title
603
+ }, undefined, false, undefined, this),
604
+ /* @__PURE__ */ jsxDEV2("strong", {
605
+ className: "absolute-voice-routing-status__label",
606
+ children: model.label
607
+ }, undefined, false, undefined, this)
608
+ ]
609
+ }, undefined, true, undefined, this),
610
+ /* @__PURE__ */ jsxDEV2("p", {
611
+ className: "absolute-voice-routing-status__description",
612
+ children: model.description
613
+ }, undefined, false, undefined, this),
614
+ model.rows.length ? /* @__PURE__ */ jsxDEV2("div", {
615
+ className: "absolute-voice-routing-status__grid",
616
+ children: model.rows.map((row) => /* @__PURE__ */ jsxDEV2("div", {
617
+ children: [
618
+ /* @__PURE__ */ jsxDEV2("span", {
619
+ children: row.label
620
+ }, undefined, false, undefined, this),
621
+ /* @__PURE__ */ jsxDEV2("strong", {
622
+ children: row.value
623
+ }, undefined, false, undefined, this)
624
+ ]
625
+ }, row.label, true, undefined, this))
626
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2("p", {
627
+ className: "absolute-voice-routing-status__empty",
628
+ children: "Start a voice session to see the selected provider."
629
+ }, undefined, false, undefined, this),
630
+ model.error ? /* @__PURE__ */ jsxDEV2("p", {
631
+ className: "absolute-voice-routing-status__error",
632
+ children: model.error
633
+ }, undefined, false, undefined, this) : null
634
+ ]
635
+ }, undefined, true, undefined, this);
636
+ };
637
+ // src/react/useVoiceStream.tsx
638
+ import { useEffect as useEffect3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore3 } from "react";
639
+
393
640
  // src/client/actions.ts
394
641
  var normalizeErrorMessage = (value) => {
395
642
  if (typeof value === "string" && value.trim()) {
@@ -916,13 +1163,13 @@ var EMPTY_SNAPSHOT = {
916
1163
  turns: []
917
1164
  };
918
1165
  var useVoiceStream = (path, options = {}) => {
919
- const streamRef = useRef2(null);
1166
+ const streamRef = useRef3(null);
920
1167
  if (!streamRef.current) {
921
1168
  streamRef.current = createVoiceStream(path, options);
922
1169
  }
923
1170
  const stream = streamRef.current;
924
- useEffect2(() => () => stream.close(), [stream]);
925
- const snapshot = useSyncExternalStore2(stream.subscribe, stream.getSnapshot, stream.getServerSnapshot) ?? EMPTY_SNAPSHOT;
1171
+ useEffect3(() => () => stream.close(), [stream]);
1172
+ const snapshot = useSyncExternalStore3(stream.subscribe, stream.getSnapshot, stream.getServerSnapshot) ?? EMPTY_SNAPSHOT;
926
1173
  return {
927
1174
  ...snapshot,
928
1175
  callControl: (message) => stream.callControl(message),
@@ -932,7 +1179,7 @@ var useVoiceStream = (path, options = {}) => {
932
1179
  };
933
1180
  };
934
1181
  // src/react/useVoiceController.tsx
935
- import { useEffect as useEffect3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore3 } from "react";
1182
+ import { useEffect as useEffect4, useRef as useRef4, useSyncExternalStore as useSyncExternalStore4 } from "react";
936
1183
 
937
1184
  // src/client/htmx.ts
938
1185
  var DEFAULT_EVENT_NAME = "voice-refresh";
@@ -1579,13 +1826,13 @@ var EMPTY_SNAPSHOT2 = {
1579
1826
  turns: []
1580
1827
  };
1581
1828
  var useVoiceController = (path, options = {}) => {
1582
- const controllerRef = useRef3(null);
1829
+ const controllerRef = useRef4(null);
1583
1830
  if (!controllerRef.current) {
1584
1831
  controllerRef.current = createVoiceController(path, options);
1585
1832
  }
1586
1833
  const controller = controllerRef.current;
1587
- useEffect3(() => () => controller.close(), [controller]);
1588
- const snapshot = useSyncExternalStore3(controller.subscribe, controller.getSnapshot, controller.getServerSnapshot) ?? EMPTY_SNAPSHOT2;
1834
+ useEffect4(() => () => controller.close(), [controller]);
1835
+ const snapshot = useSyncExternalStore4(controller.subscribe, controller.getSnapshot, controller.getServerSnapshot) ?? EMPTY_SNAPSHOT2;
1589
1836
  return {
1590
1837
  ...snapshot,
1591
1838
  bindHTMX: controller.bindHTMX,
@@ -1599,7 +1846,7 @@ var useVoiceController = (path, options = {}) => {
1599
1846
  };
1600
1847
  };
1601
1848
  // src/react/useVoiceProviderStatus.tsx
1602
- import { useEffect as useEffect4, useRef as useRef4, useSyncExternalStore as useSyncExternalStore4 } from "react";
1849
+ import { useEffect as useEffect5, useRef as useRef5, useSyncExternalStore as useSyncExternalStore5 } from "react";
1603
1850
 
1604
1851
  // src/client/providerStatus.ts
1605
1852
  var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
@@ -1683,22 +1930,22 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
1683
1930
 
1684
1931
  // src/react/useVoiceProviderStatus.tsx
1685
1932
  var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
1686
- const storeRef = useRef4(null);
1933
+ const storeRef = useRef5(null);
1687
1934
  if (!storeRef.current) {
1688
1935
  storeRef.current = createVoiceProviderStatusStore(path, options);
1689
1936
  }
1690
1937
  const store = storeRef.current;
1691
- useEffect4(() => {
1938
+ useEffect5(() => {
1692
1939
  store.refresh().catch(() => {});
1693
1940
  return () => store.close();
1694
1941
  }, [store]);
1695
1942
  return {
1696
- ...useSyncExternalStore4(store.subscribe, store.getSnapshot, store.getServerSnapshot),
1943
+ ...useSyncExternalStore5(store.subscribe, store.getSnapshot, store.getServerSnapshot),
1697
1944
  refresh: store.refresh
1698
1945
  };
1699
1946
  };
1700
1947
  // src/react/useVoiceWorkflowStatus.tsx
1701
- import { useEffect as useEffect5, useRef as useRef5, useSyncExternalStore as useSyncExternalStore5 } from "react";
1948
+ import { useEffect as useEffect6, useRef as useRef6, useSyncExternalStore as useSyncExternalStore6 } from "react";
1702
1949
 
1703
1950
  // src/client/workflowStatus.ts
1704
1951
  var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
@@ -1781,25 +2028,27 @@ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options =
1781
2028
 
1782
2029
  // src/react/useVoiceWorkflowStatus.tsx
1783
2030
  var useVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => {
1784
- const storeRef = useRef5(null);
2031
+ const storeRef = useRef6(null);
1785
2032
  if (!storeRef.current) {
1786
2033
  storeRef.current = createVoiceWorkflowStatusStore(path, options);
1787
2034
  }
1788
2035
  const store = storeRef.current;
1789
- useEffect5(() => {
2036
+ useEffect6(() => {
1790
2037
  store.refresh().catch(() => {});
1791
2038
  return () => store.close();
1792
2039
  }, [store]);
1793
2040
  return {
1794
- ...useSyncExternalStore5(store.subscribe, store.getSnapshot, store.getServerSnapshot),
2041
+ ...useSyncExternalStore6(store.subscribe, store.getSnapshot, store.getServerSnapshot),
1795
2042
  refresh: store.refresh
1796
2043
  };
1797
2044
  };
1798
2045
  export {
1799
2046
  useVoiceWorkflowStatus,
1800
2047
  useVoiceStream,
2048
+ useVoiceRoutingStatus,
1801
2049
  useVoiceProviderStatus,
1802
2050
  useVoiceController,
1803
2051
  useVoiceAppKitStatus,
2052
+ VoiceRoutingStatus,
1804
2053
  VoiceOpsStatus
1805
2054
  };
@@ -0,0 +1,8 @@
1
+ import { type VoiceRoutingStatusClientOptions } from '../client/routingStatus';
2
+ export declare const useVoiceRoutingStatus: (path?: string, options?: VoiceRoutingStatusClientOptions) => {
3
+ refresh: () => Promise<import("..").VoiceRoutingEvent | null>;
4
+ decision: import("..").VoiceRoutingDecisionSummary | null;
5
+ error: string | null;
6
+ isLoading: boolean;
7
+ updatedAt?: number;
8
+ };
@@ -0,0 +1,10 @@
1
+ import { type VoiceRoutingStatusWidgetOptions } from '../client/routingStatusWidget';
2
+ export declare const createVoiceRoutingStatus: (path?: string, options?: VoiceRoutingStatusWidgetOptions) => {
3
+ getHTML: () => string;
4
+ getViewModel: () => import("../client").VoiceRoutingStatusViewModel;
5
+ close: () => void;
6
+ getServerSnapshot: () => import("../client").VoiceRoutingStatusSnapshot;
7
+ getSnapshot: () => import("../client").VoiceRoutingStatusSnapshot;
8
+ refresh: () => Promise<import("..").VoiceRoutingEvent | null>;
9
+ subscribe: (listener: () => void) => () => void;
10
+ };
@@ -2,5 +2,6 @@ export { createVoiceAppKitStatus } from './createVoiceAppKitStatus';
2
2
  export { createVoiceOpsStatus } from './createVoiceOpsStatus';
3
3
  export { createVoiceStream } from './createVoiceStream';
4
4
  export { createVoiceProviderStatus } from './createVoiceProviderStatus';
5
+ export { createVoiceRoutingStatus } from './createVoiceRoutingStatus';
5
6
  export { createVoiceWorkflowStatus } from './createVoiceWorkflowStatus';
6
7
  export { createVoiceController } from '../client/controller';
@@ -884,6 +884,186 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
884
884
 
885
885
  // src/svelte/createVoiceProviderStatus.ts
886
886
  var createVoiceProviderStatus = (path = "/api/provider-status", options = {}) => createVoiceProviderStatusStore(path, options);
887
+ // src/client/routingStatus.ts
888
+ var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
889
+ const fetchImpl = options.fetch ?? globalThis.fetch;
890
+ const response = await fetchImpl(path);
891
+ if (!response.ok) {
892
+ throw new Error(`Voice routing status failed: HTTP ${response.status}`);
893
+ }
894
+ return await response.json();
895
+ };
896
+ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
897
+ const listeners = new Set;
898
+ let closed = false;
899
+ let timer;
900
+ let snapshot = {
901
+ decision: null,
902
+ error: null,
903
+ isLoading: false
904
+ };
905
+ const emit = () => {
906
+ for (const listener of listeners) {
907
+ listener();
908
+ }
909
+ };
910
+ const refresh = async () => {
911
+ if (closed) {
912
+ return snapshot.decision;
913
+ }
914
+ snapshot = {
915
+ ...snapshot,
916
+ error: null,
917
+ isLoading: true
918
+ };
919
+ emit();
920
+ try {
921
+ const decision = await fetchVoiceRoutingStatus(path, options);
922
+ snapshot = {
923
+ decision,
924
+ error: null,
925
+ isLoading: false,
926
+ updatedAt: Date.now()
927
+ };
928
+ emit();
929
+ return decision;
930
+ } catch (error) {
931
+ snapshot = {
932
+ ...snapshot,
933
+ error: error instanceof Error ? error.message : String(error),
934
+ isLoading: false
935
+ };
936
+ emit();
937
+ throw error;
938
+ }
939
+ };
940
+ const close = () => {
941
+ closed = true;
942
+ if (timer) {
943
+ clearInterval(timer);
944
+ timer = undefined;
945
+ }
946
+ listeners.clear();
947
+ };
948
+ if (options.intervalMs && options.intervalMs > 0) {
949
+ timer = setInterval(() => {
950
+ refresh().catch(() => {});
951
+ }, options.intervalMs);
952
+ }
953
+ return {
954
+ close,
955
+ getServerSnapshot: () => snapshot,
956
+ getSnapshot: () => snapshot,
957
+ refresh,
958
+ subscribe: (listener) => {
959
+ listeners.add(listener);
960
+ return () => {
961
+ listeners.delete(listener);
962
+ };
963
+ }
964
+ };
965
+ };
966
+
967
+ // src/client/routingStatusWidget.ts
968
+ var DEFAULT_TITLE2 = "Voice Routing";
969
+ var DEFAULT_DESCRIPTION2 = "Latest provider routing decision from the self-hosted trace store.";
970
+ var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
971
+ var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
972
+ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
973
+ const decision = snapshot.decision;
974
+ const rows = decision ? [
975
+ { label: "Kind", value: decision.kind.toUpperCase() },
976
+ { label: "Policy", value: formatValue(decision.routing, "Unknown") },
977
+ { label: "Provider", value: formatValue(decision.provider, "Unknown") },
978
+ {
979
+ label: "Selected",
980
+ value: formatValue(decision.selectedProvider, "Unknown")
981
+ },
982
+ {
983
+ label: "Fallback",
984
+ value: formatValue(decision.fallbackProvider)
985
+ },
986
+ { label: "Status", value: formatValue(decision.status, "unknown") },
987
+ {
988
+ label: "Latency budget",
989
+ value: typeof decision.latencyBudgetMs === "number" ? `${decision.latencyBudgetMs}ms` : "None"
990
+ }
991
+ ] : [];
992
+ return {
993
+ decision,
994
+ description: options.description ?? DEFAULT_DESCRIPTION2,
995
+ error: snapshot.error,
996
+ isLoading: snapshot.isLoading,
997
+ label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
998
+ rows,
999
+ status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
1000
+ title: options.title ?? DEFAULT_TITLE2,
1001
+ updatedAt: snapshot.updatedAt
1002
+ };
1003
+ };
1004
+ var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
1005
+ const model = createVoiceRoutingStatusViewModel(snapshot, options);
1006
+ const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
1007
+ <span>${escapeHtml2(row.label)}</span>
1008
+ <strong>${escapeHtml2(row.value)}</strong>
1009
+ </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
1010
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml2(model.status)}">
1011
+ <header class="absolute-voice-routing-status__header">
1012
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml2(model.title)}</span>
1013
+ <strong class="absolute-voice-routing-status__label">${escapeHtml2(model.label)}</strong>
1014
+ </header>
1015
+ <p class="absolute-voice-routing-status__description">${escapeHtml2(model.description)}</p>
1016
+ ${rows}
1017
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml2(model.error)}</p>` : ""}
1018
+ </section>`;
1019
+ };
1020
+ var getVoiceRoutingStatusCSS = () => `.absolute-voice-routing-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-routing-status--error{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-routing-status__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-routing-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-routing-status__label{font-size:24px;line-height:1}.absolute-voice-routing-status__description{color:#514733;margin:12px 0 0}.absolute-voice-routing-status__grid{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin-top:14px}.absolute-voice-routing-status__grid div{background:#fff;border:1px solid #eee4d2;border-radius:14px;padding:10px 12px}.absolute-voice-routing-status__grid span{color:#655944;display:block;font-size:12px;margin-bottom:4px}.absolute-voice-routing-status__grid strong{overflow-wrap:anywhere}.absolute-voice-routing-status__empty{color:#655944;margin:14px 0 0}.absolute-voice-routing-status__error{color:#9f1239;font-weight:700}`;
1021
+ var mountVoiceRoutingStatus = (element, path = "/api/routing/latest", options = {}) => {
1022
+ const store = createVoiceRoutingStatusStore(path, options);
1023
+ const render = () => {
1024
+ element.innerHTML = renderVoiceRoutingStatusHTML(store.getSnapshot(), options);
1025
+ };
1026
+ const unsubscribe = store.subscribe(render);
1027
+ render();
1028
+ store.refresh().catch(() => {});
1029
+ return {
1030
+ close: () => {
1031
+ unsubscribe();
1032
+ store.close();
1033
+ },
1034
+ refresh: store.refresh
1035
+ };
1036
+ };
1037
+ var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status") => {
1038
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
1039
+ return;
1040
+ }
1041
+ customElements.define(tagName, class AbsoluteVoiceRoutingStatusElement extends HTMLElement {
1042
+ mounted;
1043
+ connectedCallback() {
1044
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
1045
+ this.mounted = mountVoiceRoutingStatus(this, this.getAttribute("path") ?? "/api/routing/latest", {
1046
+ description: this.getAttribute("description") ?? undefined,
1047
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
1048
+ title: this.getAttribute("title") ?? undefined
1049
+ });
1050
+ }
1051
+ disconnectedCallback() {
1052
+ this.mounted?.close();
1053
+ this.mounted = undefined;
1054
+ }
1055
+ });
1056
+ };
1057
+
1058
+ // src/svelte/createVoiceRoutingStatus.ts
1059
+ var createVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
1060
+ const store = createVoiceRoutingStatusStore(path, options);
1061
+ return {
1062
+ ...store,
1063
+ getHTML: () => renderVoiceRoutingStatusHTML(store.getSnapshot(), options),
1064
+ getViewModel: () => createVoiceRoutingStatusViewModel(store.getSnapshot(), options)
1065
+ };
1066
+ };
887
1067
  // src/client/workflowStatus.ts
888
1068
  var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
889
1069
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -1597,6 +1777,7 @@ var createVoiceController = (path, options = {}) => {
1597
1777
  export {
1598
1778
  createVoiceWorkflowStatus,
1599
1779
  createVoiceStream2 as createVoiceStream,
1780
+ createVoiceRoutingStatus,
1600
1781
  createVoiceProviderStatus,
1601
1782
  createVoiceOpsStatus,
1602
1783
  createVoiceController,
@@ -0,0 +1,51 @@
1
+ export declare const VoiceRoutingStatus: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
2
+ class: {
3
+ default: string;
4
+ type: StringConstructor;
5
+ };
6
+ description: {
7
+ default: undefined;
8
+ type: StringConstructor;
9
+ };
10
+ intervalMs: {
11
+ default: number;
12
+ type: NumberConstructor;
13
+ };
14
+ path: {
15
+ default: string;
16
+ type: StringConstructor;
17
+ };
18
+ title: {
19
+ default: undefined;
20
+ type: StringConstructor;
21
+ };
22
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
23
+ [key: string]: any;
24
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
25
+ class: {
26
+ default: string;
27
+ type: StringConstructor;
28
+ };
29
+ description: {
30
+ default: undefined;
31
+ type: StringConstructor;
32
+ };
33
+ intervalMs: {
34
+ default: number;
35
+ type: NumberConstructor;
36
+ };
37
+ path: {
38
+ default: string;
39
+ type: StringConstructor;
40
+ };
41
+ title: {
42
+ default: undefined;
43
+ type: StringConstructor;
44
+ };
45
+ }>> & Readonly<{}>, {
46
+ description: string;
47
+ title: string;
48
+ path: string;
49
+ intervalMs: number;
50
+ class: string;
51
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -1,6 +1,8 @@
1
1
  export { VoiceOpsStatus } from './VoiceOpsStatus';
2
+ export { VoiceRoutingStatus } from './VoiceRoutingStatus';
2
3
  export { useVoiceAppKitStatus } from './useVoiceAppKitStatus';
3
4
  export { useVoiceStream } from './useVoiceStream';
4
5
  export { useVoiceController } from './useVoiceController';
5
6
  export { useVoiceProviderStatus } from './useVoiceProviderStatus';
7
+ export { useVoiceRoutingStatus } from './useVoiceRoutingStatus';
6
8
  export { useVoiceWorkflowStatus } from './useVoiceWorkflowStatus';