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

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,306 @@ var VoiceOpsStatus = ({
387
387
  ]
388
388
  }, undefined, true, undefined, this);
389
389
  };
390
- // src/react/useVoiceRoutingStatus.tsx
390
+ // src/react/useVoiceProviderStatus.tsx
391
391
  import { useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
392
392
 
393
+ // src/client/providerStatus.ts
394
+ var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
395
+ const fetchImpl = options.fetch ?? globalThis.fetch;
396
+ const response = await fetchImpl(path);
397
+ if (!response.ok) {
398
+ throw new Error(`Voice provider status failed: HTTP ${response.status}`);
399
+ }
400
+ return await response.json();
401
+ };
402
+ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
403
+ const listeners = new Set;
404
+ let closed = false;
405
+ let timer;
406
+ let snapshot = {
407
+ error: null,
408
+ isLoading: false,
409
+ providers: []
410
+ };
411
+ const emit = () => {
412
+ for (const listener of listeners) {
413
+ listener();
414
+ }
415
+ };
416
+ const refresh = async () => {
417
+ if (closed) {
418
+ return snapshot.providers;
419
+ }
420
+ snapshot = {
421
+ ...snapshot,
422
+ error: null,
423
+ isLoading: true
424
+ };
425
+ emit();
426
+ try {
427
+ const providers = await fetchVoiceProviderStatus(path, options);
428
+ snapshot = {
429
+ error: null,
430
+ isLoading: false,
431
+ providers,
432
+ updatedAt: Date.now()
433
+ };
434
+ emit();
435
+ return providers;
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/useVoiceProviderStatus.tsx
474
+ var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
475
+ const storeRef = useRef2(null);
476
+ if (!storeRef.current) {
477
+ storeRef.current = createVoiceProviderStatusStore(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/providerStatusWidget.ts
491
+ var DEFAULT_TITLE2 = "Voice Providers";
492
+ var DEFAULT_DESCRIPTION2 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
493
+ var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
494
+ var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
495
+ var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
496
+ var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
497
+ var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
498
+ var getProviderDetail = (provider) => {
499
+ if (provider.status === "suppressed") {
500
+ return provider.lastError ? `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)} after ${provider.lastError}.` : `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)}.`;
501
+ }
502
+ if (provider.status === "recoverable") {
503
+ return "Cooldown expired; ready for recovery traffic.";
504
+ }
505
+ if (provider.status === "rate-limited") {
506
+ return "Rate limit detected; router should avoid this provider.";
507
+ }
508
+ if (provider.status === "degraded") {
509
+ return provider.lastError ?? "Recent provider errors detected.";
510
+ }
511
+ if (provider.status === "healthy") {
512
+ return provider.recommended ? "Healthy and currently recommended." : "Healthy and available for routing.";
513
+ }
514
+ return "No provider traffic observed yet.";
515
+ };
516
+ var isWarningStatus = (status) => status === "degraded" || status === "rate-limited" || status === "recoverable" || status === "suppressed";
517
+ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
518
+ const providers = snapshot.providers.map((provider) => ({
519
+ ...provider,
520
+ detail: getProviderDetail(provider),
521
+ label: `${formatProvider(provider.provider)}${provider.recommended ? " recommended" : ""}`,
522
+ rows: [
523
+ { label: "Runs", value: String(provider.runCount) },
524
+ { label: "Avg latency", value: formatLatency(provider.averageElapsedMs) },
525
+ { label: "Errors", value: String(provider.errorCount) },
526
+ { label: "Timeouts", value: String(provider.timeoutCount) },
527
+ { label: "Fallbacks", value: String(provider.fallbackCount) },
528
+ {
529
+ label: "Suppression",
530
+ value: formatSuppression(provider.suppressionRemainingMs)
531
+ }
532
+ ]
533
+ }));
534
+ const warningCount = providers.filter((provider) => isWarningStatus(provider.status)).length;
535
+ const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
536
+ return {
537
+ description: options.description ?? DEFAULT_DESCRIPTION2,
538
+ error: snapshot.error,
539
+ isLoading: snapshot.isLoading,
540
+ label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
541
+ providers,
542
+ status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
543
+ title: options.title ?? DEFAULT_TITLE2,
544
+ updatedAt: snapshot.updatedAt
545
+ };
546
+ };
547
+ var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
548
+ const model = createVoiceProviderStatusViewModel(snapshot, options);
549
+ const providers = model.providers.length ? `<div class="absolute-voice-provider-status__providers">${model.providers.map((provider) => `<article class="absolute-voice-provider-status__provider absolute-voice-provider-status__provider--${escapeHtml2(provider.status)}">
550
+ <header>
551
+ <strong>${escapeHtml2(provider.label)}</strong>
552
+ <span>${escapeHtml2(formatStatus(provider.status))}</span>
553
+ </header>
554
+ <p>${escapeHtml2(provider.detail)}</p>
555
+ <dl>${provider.rows.map((row) => `<div>
556
+ <dt>${escapeHtml2(row.label)}</dt>
557
+ <dd>${escapeHtml2(row.value)}</dd>
558
+ </div>`).join("")}</dl>
559
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
560
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml2(model.status)}">
561
+ <header class="absolute-voice-provider-status__header">
562
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml2(model.title)}</span>
563
+ <strong class="absolute-voice-provider-status__label">${escapeHtml2(model.label)}</strong>
564
+ </header>
565
+ <p class="absolute-voice-provider-status__description">${escapeHtml2(model.description)}</p>
566
+ ${providers}
567
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml2(model.error)}</p>` : ""}
568
+ </section>`;
569
+ };
570
+ var getVoiceProviderStatusCSS = () => `.absolute-voice-provider-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-provider-status--error,.absolute-voice-provider-status--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-provider-status__header,.absolute-voice-provider-status__provider header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-status__label{font-size:24px;line-height:1}.absolute-voice-provider-status__description,.absolute-voice-provider-status__provider p,.absolute-voice-provider-status__provider dt,.absolute-voice-provider-status__empty{color:#514733}.absolute-voice-provider-status__providers{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-status__provider{background:#fff;border:1px solid #eee4d2;border-radius:16px;padding:14px}.absolute-voice-provider-status__provider--degraded,.absolute-voice-provider-status__provider--rate-limited,.absolute-voice-provider-status__provider--suppressed{border-color:#f2a7a7}.absolute-voice-provider-status__provider--recoverable{border-color:#fbbf24}.absolute-voice-provider-status__provider p{margin:10px 0}.absolute-voice-provider-status__provider dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-status__provider div{background:#fffaf0;border:1px solid #eee4d2;border-radius:12px;padding:8px}.absolute-voice-provider-status__provider dt{font-size:12px}.absolute-voice-provider-status__provider dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-status__empty{margin:14px 0 0}.absolute-voice-provider-status__error{color:#9f1239;font-weight:700}`;
571
+ var mountVoiceProviderStatus = (element, path = "/api/provider-status", options = {}) => {
572
+ const store = createVoiceProviderStatusStore(path, options);
573
+ const render = () => {
574
+ element.innerHTML = renderVoiceProviderStatusHTML(store.getSnapshot(), options);
575
+ };
576
+ const unsubscribe = store.subscribe(render);
577
+ render();
578
+ store.refresh().catch(() => {});
579
+ return {
580
+ close: () => {
581
+ unsubscribe();
582
+ store.close();
583
+ },
584
+ refresh: store.refresh
585
+ };
586
+ };
587
+ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-status") => {
588
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
589
+ return;
590
+ }
591
+ customElements.define(tagName, class AbsoluteVoiceProviderStatusElement extends HTMLElement {
592
+ mounted;
593
+ connectedCallback() {
594
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
595
+ this.mounted = mountVoiceProviderStatus(this, this.getAttribute("path") ?? "/api/provider-status", {
596
+ description: this.getAttribute("description") ?? undefined,
597
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
598
+ title: this.getAttribute("title") ?? undefined
599
+ });
600
+ }
601
+ disconnectedCallback() {
602
+ this.mounted?.close();
603
+ this.mounted = undefined;
604
+ }
605
+ });
606
+ };
607
+
608
+ // src/react/VoiceProviderStatus.tsx
609
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
610
+ var VoiceProviderStatus = ({
611
+ className,
612
+ path = "/api/provider-status",
613
+ ...options
614
+ }) => {
615
+ const snapshot = useVoiceProviderStatus(path, options);
616
+ const model = createVoiceProviderStatusViewModel(snapshot, options);
617
+ return /* @__PURE__ */ jsxDEV2("section", {
618
+ className: [
619
+ "absolute-voice-provider-status",
620
+ `absolute-voice-provider-status--${model.status}`,
621
+ className
622
+ ].filter(Boolean).join(" "),
623
+ children: [
624
+ /* @__PURE__ */ jsxDEV2("header", {
625
+ className: "absolute-voice-provider-status__header",
626
+ children: [
627
+ /* @__PURE__ */ jsxDEV2("span", {
628
+ className: "absolute-voice-provider-status__eyebrow",
629
+ children: model.title
630
+ }, undefined, false, undefined, this),
631
+ /* @__PURE__ */ jsxDEV2("strong", {
632
+ className: "absolute-voice-provider-status__label",
633
+ children: model.label
634
+ }, undefined, false, undefined, this)
635
+ ]
636
+ }, undefined, true, undefined, this),
637
+ /* @__PURE__ */ jsxDEV2("p", {
638
+ className: "absolute-voice-provider-status__description",
639
+ children: model.description
640
+ }, undefined, false, undefined, this),
641
+ model.providers.length ? /* @__PURE__ */ jsxDEV2("div", {
642
+ className: "absolute-voice-provider-status__providers",
643
+ children: model.providers.map((provider) => /* @__PURE__ */ jsxDEV2("article", {
644
+ className: [
645
+ "absolute-voice-provider-status__provider",
646
+ `absolute-voice-provider-status__provider--${provider.status}`
647
+ ].join(" "),
648
+ children: [
649
+ /* @__PURE__ */ jsxDEV2("header", {
650
+ children: [
651
+ /* @__PURE__ */ jsxDEV2("strong", {
652
+ children: provider.label
653
+ }, undefined, false, undefined, this),
654
+ /* @__PURE__ */ jsxDEV2("span", {
655
+ children: provider.status
656
+ }, undefined, false, undefined, this)
657
+ ]
658
+ }, undefined, true, undefined, this),
659
+ /* @__PURE__ */ jsxDEV2("p", {
660
+ children: provider.detail
661
+ }, undefined, false, undefined, this),
662
+ /* @__PURE__ */ jsxDEV2("dl", {
663
+ children: provider.rows.map((row) => /* @__PURE__ */ jsxDEV2("div", {
664
+ children: [
665
+ /* @__PURE__ */ jsxDEV2("dt", {
666
+ children: row.label
667
+ }, undefined, false, undefined, this),
668
+ /* @__PURE__ */ jsxDEV2("dd", {
669
+ children: row.value
670
+ }, undefined, false, undefined, this)
671
+ ]
672
+ }, row.label, true, undefined, this))
673
+ }, undefined, false, undefined, this)
674
+ ]
675
+ }, provider.provider, true, undefined, this))
676
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2("p", {
677
+ className: "absolute-voice-provider-status__empty",
678
+ children: "Run voice traffic to see provider health."
679
+ }, undefined, false, undefined, this),
680
+ model.error ? /* @__PURE__ */ jsxDEV2("p", {
681
+ className: "absolute-voice-provider-status__error",
682
+ children: model.error
683
+ }, undefined, false, undefined, this) : null
684
+ ]
685
+ }, undefined, true, undefined, this);
686
+ };
687
+ // src/react/useVoiceRoutingStatus.tsx
688
+ import { useEffect as useEffect3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore3 } from "react";
689
+
393
690
  // src/client/routingStatus.ts
394
691
  var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
395
692
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -472,25 +769,25 @@ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {})
472
769
 
473
770
  // src/react/useVoiceRoutingStatus.tsx
474
771
  var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
475
- const storeRef = useRef2(null);
772
+ const storeRef = useRef3(null);
476
773
  if (!storeRef.current) {
477
774
  storeRef.current = createVoiceRoutingStatusStore(path, options);
478
775
  }
479
776
  const store = storeRef.current;
480
- useEffect2(() => {
777
+ useEffect3(() => {
481
778
  store.refresh().catch(() => {});
482
779
  return () => store.close();
483
780
  }, [store]);
484
781
  return {
485
- ...useSyncExternalStore2(store.subscribe, store.getSnapshot, store.getServerSnapshot),
782
+ ...useSyncExternalStore3(store.subscribe, store.getSnapshot, store.getServerSnapshot),
486
783
  refresh: store.refresh
487
784
  };
488
785
  };
489
786
 
490
787
  // 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;");
788
+ var DEFAULT_TITLE3 = "Voice Routing";
789
+ var DEFAULT_DESCRIPTION3 = "Latest provider routing decision from the self-hosted trace store.";
790
+ var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
494
791
  var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
495
792
  var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
496
793
  const decision = snapshot.decision;
@@ -514,30 +811,30 @@ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
514
811
  ] : [];
515
812
  return {
516
813
  decision,
517
- description: options.description ?? DEFAULT_DESCRIPTION2,
814
+ description: options.description ?? DEFAULT_DESCRIPTION3,
518
815
  error: snapshot.error,
519
816
  isLoading: snapshot.isLoading,
520
817
  label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
521
818
  rows,
522
819
  status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
523
- title: options.title ?? DEFAULT_TITLE2,
820
+ title: options.title ?? DEFAULT_TITLE3,
524
821
  updatedAt: snapshot.updatedAt
525
822
  };
526
823
  };
527
824
  var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
528
825
  const model = createVoiceRoutingStatusViewModel(snapshot, options);
529
826
  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>
827
+ <span>${escapeHtml3(row.label)}</span>
828
+ <strong>${escapeHtml3(row.value)}</strong>
532
829
  </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)}">
830
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml3(model.status)}">
534
831
  <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>
832
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml3(model.title)}</span>
833
+ <strong class="absolute-voice-routing-status__label">${escapeHtml3(model.label)}</strong>
537
834
  </header>
538
- <p class="absolute-voice-routing-status__description">${escapeHtml2(model.description)}</p>
835
+ <p class="absolute-voice-routing-status__description">${escapeHtml3(model.description)}</p>
539
836
  ${rows}
540
- ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml2(model.error)}</p>` : ""}
837
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml3(model.error)}</p>` : ""}
541
838
  </section>`;
542
839
  };
543
840
  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}`;
@@ -579,7 +876,7 @@ var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status"
579
876
  };
580
877
 
581
878
  // src/react/VoiceRoutingStatus.tsx
582
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
879
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
583
880
  var VoiceRoutingStatus = ({
584
881
  className,
585
882
  path = "/api/routing/latest",
@@ -587,47 +884,47 @@ var VoiceRoutingStatus = ({
587
884
  }) => {
588
885
  const snapshot = useVoiceRoutingStatus(path, options);
589
886
  const model = createVoiceRoutingStatusViewModel(snapshot, options);
590
- return /* @__PURE__ */ jsxDEV2("section", {
887
+ return /* @__PURE__ */ jsxDEV3("section", {
591
888
  className: [
592
889
  "absolute-voice-routing-status",
593
890
  `absolute-voice-routing-status--${model.status}`,
594
891
  className
595
892
  ].filter(Boolean).join(" "),
596
893
  children: [
597
- /* @__PURE__ */ jsxDEV2("header", {
894
+ /* @__PURE__ */ jsxDEV3("header", {
598
895
  className: "absolute-voice-routing-status__header",
599
896
  children: [
600
- /* @__PURE__ */ jsxDEV2("span", {
897
+ /* @__PURE__ */ jsxDEV3("span", {
601
898
  className: "absolute-voice-routing-status__eyebrow",
602
899
  children: model.title
603
900
  }, undefined, false, undefined, this),
604
- /* @__PURE__ */ jsxDEV2("strong", {
901
+ /* @__PURE__ */ jsxDEV3("strong", {
605
902
  className: "absolute-voice-routing-status__label",
606
903
  children: model.label
607
904
  }, undefined, false, undefined, this)
608
905
  ]
609
906
  }, undefined, true, undefined, this),
610
- /* @__PURE__ */ jsxDEV2("p", {
907
+ /* @__PURE__ */ jsxDEV3("p", {
611
908
  className: "absolute-voice-routing-status__description",
612
909
  children: model.description
613
910
  }, undefined, false, undefined, this),
614
- model.rows.length ? /* @__PURE__ */ jsxDEV2("div", {
911
+ model.rows.length ? /* @__PURE__ */ jsxDEV3("div", {
615
912
  className: "absolute-voice-routing-status__grid",
616
- children: model.rows.map((row) => /* @__PURE__ */ jsxDEV2("div", {
913
+ children: model.rows.map((row) => /* @__PURE__ */ jsxDEV3("div", {
617
914
  children: [
618
- /* @__PURE__ */ jsxDEV2("span", {
915
+ /* @__PURE__ */ jsxDEV3("span", {
619
916
  children: row.label
620
917
  }, undefined, false, undefined, this),
621
- /* @__PURE__ */ jsxDEV2("strong", {
918
+ /* @__PURE__ */ jsxDEV3("strong", {
622
919
  children: row.value
623
920
  }, undefined, false, undefined, this)
624
921
  ]
625
922
  }, row.label, true, undefined, this))
626
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2("p", {
923
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV3("p", {
627
924
  className: "absolute-voice-routing-status__empty",
628
925
  children: "Start a voice session to see the selected provider."
629
926
  }, undefined, false, undefined, this),
630
- model.error ? /* @__PURE__ */ jsxDEV2("p", {
927
+ model.error ? /* @__PURE__ */ jsxDEV3("p", {
631
928
  className: "absolute-voice-routing-status__error",
632
929
  children: model.error
633
930
  }, undefined, false, undefined, this) : null
@@ -635,7 +932,7 @@ var VoiceRoutingStatus = ({
635
932
  }, undefined, true, undefined, this);
636
933
  };
637
934
  // src/react/useVoiceStream.tsx
638
- import { useEffect as useEffect3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore3 } from "react";
935
+ import { useEffect as useEffect4, useRef as useRef4, useSyncExternalStore as useSyncExternalStore4 } from "react";
639
936
 
640
937
  // src/client/actions.ts
641
938
  var normalizeErrorMessage = (value) => {
@@ -1163,13 +1460,13 @@ var EMPTY_SNAPSHOT = {
1163
1460
  turns: []
1164
1461
  };
1165
1462
  var useVoiceStream = (path, options = {}) => {
1166
- const streamRef = useRef3(null);
1463
+ const streamRef = useRef4(null);
1167
1464
  if (!streamRef.current) {
1168
1465
  streamRef.current = createVoiceStream(path, options);
1169
1466
  }
1170
1467
  const stream = streamRef.current;
1171
- useEffect3(() => () => stream.close(), [stream]);
1172
- const snapshot = useSyncExternalStore3(stream.subscribe, stream.getSnapshot, stream.getServerSnapshot) ?? EMPTY_SNAPSHOT;
1468
+ useEffect4(() => () => stream.close(), [stream]);
1469
+ const snapshot = useSyncExternalStore4(stream.subscribe, stream.getSnapshot, stream.getServerSnapshot) ?? EMPTY_SNAPSHOT;
1173
1470
  return {
1174
1471
  ...snapshot,
1175
1472
  callControl: (message) => stream.callControl(message),
@@ -1179,7 +1476,7 @@ var useVoiceStream = (path, options = {}) => {
1179
1476
  };
1180
1477
  };
1181
1478
  // src/react/useVoiceController.tsx
1182
- import { useEffect as useEffect4, useRef as useRef4, useSyncExternalStore as useSyncExternalStore4 } from "react";
1479
+ import { useEffect as useEffect5, useRef as useRef5, useSyncExternalStore as useSyncExternalStore5 } from "react";
1183
1480
 
1184
1481
  // src/client/htmx.ts
1185
1482
  var DEFAULT_EVENT_NAME = "voice-refresh";
@@ -1826,13 +2123,13 @@ var EMPTY_SNAPSHOT2 = {
1826
2123
  turns: []
1827
2124
  };
1828
2125
  var useVoiceController = (path, options = {}) => {
1829
- const controllerRef = useRef4(null);
2126
+ const controllerRef = useRef5(null);
1830
2127
  if (!controllerRef.current) {
1831
2128
  controllerRef.current = createVoiceController(path, options);
1832
2129
  }
1833
2130
  const controller = controllerRef.current;
1834
- useEffect4(() => () => controller.close(), [controller]);
1835
- const snapshot = useSyncExternalStore4(controller.subscribe, controller.getSnapshot, controller.getServerSnapshot) ?? EMPTY_SNAPSHOT2;
2131
+ useEffect5(() => () => controller.close(), [controller]);
2132
+ const snapshot = useSyncExternalStore5(controller.subscribe, controller.getSnapshot, controller.getServerSnapshot) ?? EMPTY_SNAPSHOT2;
1836
2133
  return {
1837
2134
  ...snapshot,
1838
2135
  bindHTMX: controller.bindHTMX,
@@ -1845,105 +2142,6 @@ var useVoiceController = (path, options = {}) => {
1845
2142
  toggleRecording: () => controller.toggleRecording()
1846
2143
  };
1847
2144
  };
1848
- // src/react/useVoiceProviderStatus.tsx
1849
- import { useEffect as useEffect5, useRef as useRef5, useSyncExternalStore as useSyncExternalStore5 } from "react";
1850
-
1851
- // src/client/providerStatus.ts
1852
- var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
1853
- const fetchImpl = options.fetch ?? globalThis.fetch;
1854
- const response = await fetchImpl(path);
1855
- if (!response.ok) {
1856
- throw new Error(`Voice provider status failed: HTTP ${response.status}`);
1857
- }
1858
- return await response.json();
1859
- };
1860
- var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
1861
- const listeners = new Set;
1862
- let closed = false;
1863
- let timer;
1864
- let snapshot = {
1865
- error: null,
1866
- isLoading: false,
1867
- providers: []
1868
- };
1869
- const emit = () => {
1870
- for (const listener of listeners) {
1871
- listener();
1872
- }
1873
- };
1874
- const refresh = async () => {
1875
- if (closed) {
1876
- return snapshot.providers;
1877
- }
1878
- snapshot = {
1879
- ...snapshot,
1880
- error: null,
1881
- isLoading: true
1882
- };
1883
- emit();
1884
- try {
1885
- const providers = await fetchVoiceProviderStatus(path, options);
1886
- snapshot = {
1887
- error: null,
1888
- isLoading: false,
1889
- providers,
1890
- updatedAt: Date.now()
1891
- };
1892
- emit();
1893
- return providers;
1894
- } catch (error) {
1895
- snapshot = {
1896
- ...snapshot,
1897
- error: error instanceof Error ? error.message : String(error),
1898
- isLoading: false
1899
- };
1900
- emit();
1901
- throw error;
1902
- }
1903
- };
1904
- const close = () => {
1905
- closed = true;
1906
- if (timer) {
1907
- clearInterval(timer);
1908
- timer = undefined;
1909
- }
1910
- listeners.clear();
1911
- };
1912
- if (options.intervalMs && options.intervalMs > 0) {
1913
- timer = setInterval(() => {
1914
- refresh().catch(() => {});
1915
- }, options.intervalMs);
1916
- }
1917
- return {
1918
- close,
1919
- getServerSnapshot: () => snapshot,
1920
- getSnapshot: () => snapshot,
1921
- refresh,
1922
- subscribe: (listener) => {
1923
- listeners.add(listener);
1924
- return () => {
1925
- listeners.delete(listener);
1926
- };
1927
- }
1928
- };
1929
- };
1930
-
1931
- // src/react/useVoiceProviderStatus.tsx
1932
- var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
1933
- const storeRef = useRef5(null);
1934
- if (!storeRef.current) {
1935
- storeRef.current = createVoiceProviderStatusStore(path, options);
1936
- }
1937
- const store = storeRef.current;
1938
- useEffect5(() => {
1939
- store.refresh().catch(() => {});
1940
- return () => store.close();
1941
- }, [store]);
1942
- return {
1943
- ...useSyncExternalStore5(store.subscribe, store.getSnapshot, store.getServerSnapshot),
1944
- refresh: store.refresh
1945
- };
1946
- };
1947
2145
  // src/react/useVoiceWorkflowStatus.tsx
1948
2146
  import { useEffect as useEffect6, useRef as useRef6, useSyncExternalStore as useSyncExternalStore6 } from "react";
1949
2147
 
@@ -2050,5 +2248,6 @@ export {
2050
2248
  useVoiceController,
2051
2249
  useVoiceAppKitStatus,
2052
2250
  VoiceRoutingStatus,
2251
+ VoiceProviderStatus,
2053
2252
  VoiceOpsStatus
2054
2253
  };
@@ -1,5 +1,7 @@
1
- import type { VoiceProviderStatusClientOptions } from '../client/providerStatus';
2
- export declare const createVoiceProviderStatus: <TProvider extends string = string>(path?: string, options?: VoiceProviderStatusClientOptions) => {
1
+ import { type VoiceProviderStatusWidgetOptions } from '../client/providerStatusWidget';
2
+ export declare const createVoiceProviderStatus: <TProvider extends string = string>(path?: string, options?: VoiceProviderStatusWidgetOptions) => {
3
+ getHTML: () => string;
4
+ getViewModel: () => import("../client").VoiceProviderStatusViewModel<TProvider>;
3
5
  close: () => void;
4
6
  getServerSnapshot: () => import("../client").VoiceProviderStatusSnapshot<TProvider>;
5
7
  getSnapshot: () => import("../client").VoiceProviderStatusSnapshot<TProvider>;