@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.
package/dist/vue/index.js CHANGED
@@ -374,9 +374,312 @@ var VoiceOpsStatus = defineComponent({
374
374
  };
375
375
  }
376
376
  });
377
- // src/vue/VoiceRoutingStatus.ts
377
+ // src/vue/VoiceProviderStatus.ts
378
378
  import { computed, defineComponent as defineComponent2, h as h2 } from "vue";
379
379
 
380
+ // src/client/providerStatus.ts
381
+ var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
382
+ const fetchImpl = options.fetch ?? globalThis.fetch;
383
+ const response = await fetchImpl(path);
384
+ if (!response.ok) {
385
+ throw new Error(`Voice provider status failed: HTTP ${response.status}`);
386
+ }
387
+ return await response.json();
388
+ };
389
+ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
390
+ const listeners = new Set;
391
+ let closed = false;
392
+ let timer;
393
+ let snapshot = {
394
+ error: null,
395
+ isLoading: false,
396
+ providers: []
397
+ };
398
+ const emit = () => {
399
+ for (const listener of listeners) {
400
+ listener();
401
+ }
402
+ };
403
+ const refresh = async () => {
404
+ if (closed) {
405
+ return snapshot.providers;
406
+ }
407
+ snapshot = {
408
+ ...snapshot,
409
+ error: null,
410
+ isLoading: true
411
+ };
412
+ emit();
413
+ try {
414
+ const providers = await fetchVoiceProviderStatus(path, options);
415
+ snapshot = {
416
+ error: null,
417
+ isLoading: false,
418
+ providers,
419
+ updatedAt: Date.now()
420
+ };
421
+ emit();
422
+ return providers;
423
+ } catch (error) {
424
+ snapshot = {
425
+ ...snapshot,
426
+ error: error instanceof Error ? error.message : String(error),
427
+ isLoading: false
428
+ };
429
+ emit();
430
+ throw error;
431
+ }
432
+ };
433
+ const close = () => {
434
+ closed = true;
435
+ if (timer) {
436
+ clearInterval(timer);
437
+ timer = undefined;
438
+ }
439
+ listeners.clear();
440
+ };
441
+ if (options.intervalMs && options.intervalMs > 0) {
442
+ timer = setInterval(() => {
443
+ refresh().catch(() => {});
444
+ }, options.intervalMs);
445
+ }
446
+ return {
447
+ close,
448
+ getServerSnapshot: () => snapshot,
449
+ getSnapshot: () => snapshot,
450
+ refresh,
451
+ subscribe: (listener) => {
452
+ listeners.add(listener);
453
+ return () => {
454
+ listeners.delete(listener);
455
+ };
456
+ }
457
+ };
458
+ };
459
+
460
+ // src/client/providerStatusWidget.ts
461
+ var DEFAULT_TITLE2 = "Voice Providers";
462
+ var DEFAULT_DESCRIPTION2 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
463
+ var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
464
+ var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
465
+ var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
466
+ var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
467
+ var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
468
+ var getProviderDetail = (provider) => {
469
+ if (provider.status === "suppressed") {
470
+ return provider.lastError ? `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)} after ${provider.lastError}.` : `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)}.`;
471
+ }
472
+ if (provider.status === "recoverable") {
473
+ return "Cooldown expired; ready for recovery traffic.";
474
+ }
475
+ if (provider.status === "rate-limited") {
476
+ return "Rate limit detected; router should avoid this provider.";
477
+ }
478
+ if (provider.status === "degraded") {
479
+ return provider.lastError ?? "Recent provider errors detected.";
480
+ }
481
+ if (provider.status === "healthy") {
482
+ return provider.recommended ? "Healthy and currently recommended." : "Healthy and available for routing.";
483
+ }
484
+ return "No provider traffic observed yet.";
485
+ };
486
+ var isWarningStatus = (status) => status === "degraded" || status === "rate-limited" || status === "recoverable" || status === "suppressed";
487
+ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
488
+ const providers = snapshot.providers.map((provider) => ({
489
+ ...provider,
490
+ detail: getProviderDetail(provider),
491
+ label: `${formatProvider(provider.provider)}${provider.recommended ? " recommended" : ""}`,
492
+ rows: [
493
+ { label: "Runs", value: String(provider.runCount) },
494
+ { label: "Avg latency", value: formatLatency(provider.averageElapsedMs) },
495
+ { label: "Errors", value: String(provider.errorCount) },
496
+ { label: "Timeouts", value: String(provider.timeoutCount) },
497
+ { label: "Fallbacks", value: String(provider.fallbackCount) },
498
+ {
499
+ label: "Suppression",
500
+ value: formatSuppression(provider.suppressionRemainingMs)
501
+ }
502
+ ]
503
+ }));
504
+ const warningCount = providers.filter((provider) => isWarningStatus(provider.status)).length;
505
+ const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
506
+ return {
507
+ description: options.description ?? DEFAULT_DESCRIPTION2,
508
+ error: snapshot.error,
509
+ isLoading: snapshot.isLoading,
510
+ label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
511
+ providers,
512
+ status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
513
+ title: options.title ?? DEFAULT_TITLE2,
514
+ updatedAt: snapshot.updatedAt
515
+ };
516
+ };
517
+ var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
518
+ const model = createVoiceProviderStatusViewModel(snapshot, options);
519
+ 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)}">
520
+ <header>
521
+ <strong>${escapeHtml2(provider.label)}</strong>
522
+ <span>${escapeHtml2(formatStatus(provider.status))}</span>
523
+ </header>
524
+ <p>${escapeHtml2(provider.detail)}</p>
525
+ <dl>${provider.rows.map((row) => `<div>
526
+ <dt>${escapeHtml2(row.label)}</dt>
527
+ <dd>${escapeHtml2(row.value)}</dd>
528
+ </div>`).join("")}</dl>
529
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
530
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml2(model.status)}">
531
+ <header class="absolute-voice-provider-status__header">
532
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml2(model.title)}</span>
533
+ <strong class="absolute-voice-provider-status__label">${escapeHtml2(model.label)}</strong>
534
+ </header>
535
+ <p class="absolute-voice-provider-status__description">${escapeHtml2(model.description)}</p>
536
+ ${providers}
537
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml2(model.error)}</p>` : ""}
538
+ </section>`;
539
+ };
540
+ 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}`;
541
+ var mountVoiceProviderStatus = (element, path = "/api/provider-status", options = {}) => {
542
+ const store = createVoiceProviderStatusStore(path, options);
543
+ const render = () => {
544
+ element.innerHTML = renderVoiceProviderStatusHTML(store.getSnapshot(), options);
545
+ };
546
+ const unsubscribe = store.subscribe(render);
547
+ render();
548
+ store.refresh().catch(() => {});
549
+ return {
550
+ close: () => {
551
+ unsubscribe();
552
+ store.close();
553
+ },
554
+ refresh: store.refresh
555
+ };
556
+ };
557
+ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-status") => {
558
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
559
+ return;
560
+ }
561
+ customElements.define(tagName, class AbsoluteVoiceProviderStatusElement extends HTMLElement {
562
+ mounted;
563
+ connectedCallback() {
564
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
565
+ this.mounted = mountVoiceProviderStatus(this, this.getAttribute("path") ?? "/api/provider-status", {
566
+ description: this.getAttribute("description") ?? undefined,
567
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
568
+ title: this.getAttribute("title") ?? undefined
569
+ });
570
+ }
571
+ disconnectedCallback() {
572
+ this.mounted?.close();
573
+ this.mounted = undefined;
574
+ }
575
+ });
576
+ };
577
+
578
+ // src/vue/useVoiceProviderStatus.ts
579
+ import { onUnmounted as onUnmounted2, ref as ref2, shallowRef as shallowRef2 } from "vue";
580
+ var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
581
+ const store = createVoiceProviderStatusStore(path, options);
582
+ const error = ref2(null);
583
+ const isLoading = ref2(false);
584
+ const providers = shallowRef2([]);
585
+ const updatedAt = ref2(undefined);
586
+ const sync = () => {
587
+ const snapshot = store.getSnapshot();
588
+ error.value = snapshot.error;
589
+ isLoading.value = snapshot.isLoading;
590
+ providers.value = [...snapshot.providers];
591
+ updatedAt.value = snapshot.updatedAt;
592
+ };
593
+ const unsubscribe = store.subscribe(sync);
594
+ sync();
595
+ store.refresh().catch(() => {});
596
+ onUnmounted2(() => {
597
+ unsubscribe();
598
+ store.close();
599
+ });
600
+ return {
601
+ error,
602
+ isLoading,
603
+ providers,
604
+ refresh: store.refresh,
605
+ updatedAt
606
+ };
607
+ };
608
+
609
+ // src/vue/VoiceProviderStatus.ts
610
+ var VoiceProviderStatus = defineComponent2({
611
+ name: "VoiceProviderStatus",
612
+ props: {
613
+ class: {
614
+ default: "",
615
+ type: String
616
+ },
617
+ description: {
618
+ default: undefined,
619
+ type: String
620
+ },
621
+ intervalMs: {
622
+ default: 5000,
623
+ type: Number
624
+ },
625
+ path: {
626
+ default: "/api/provider-status",
627
+ type: String
628
+ },
629
+ title: {
630
+ default: undefined,
631
+ type: String
632
+ }
633
+ },
634
+ setup(props) {
635
+ const options = {
636
+ description: props.description,
637
+ intervalMs: props.intervalMs,
638
+ title: props.title
639
+ };
640
+ const status = useVoiceProviderStatus(props.path, options);
641
+ const model = computed(() => createVoiceProviderStatusViewModel({
642
+ error: status.error.value,
643
+ isLoading: status.isLoading.value,
644
+ providers: status.providers.value,
645
+ updatedAt: status.updatedAt.value
646
+ }, options));
647
+ return () => h2("section", {
648
+ class: [
649
+ "absolute-voice-provider-status",
650
+ `absolute-voice-provider-status--${model.value.status}`,
651
+ props.class
652
+ ]
653
+ }, [
654
+ h2("header", { class: "absolute-voice-provider-status__header" }, [
655
+ h2("span", { class: "absolute-voice-provider-status__eyebrow" }, model.value.title),
656
+ h2("strong", { class: "absolute-voice-provider-status__label" }, model.value.label)
657
+ ]),
658
+ h2("p", { class: "absolute-voice-provider-status__description" }, model.value.description),
659
+ model.value.providers.length ? h2("div", { class: "absolute-voice-provider-status__providers" }, model.value.providers.map((provider) => h2("article", {
660
+ class: [
661
+ "absolute-voice-provider-status__provider",
662
+ `absolute-voice-provider-status__provider--${provider.status}`
663
+ ],
664
+ key: provider.provider
665
+ }, [
666
+ h2("header", [
667
+ h2("strong", provider.label),
668
+ h2("span", provider.status)
669
+ ]),
670
+ h2("p", provider.detail),
671
+ h2("dl", provider.rows.map((row) => h2("div", { key: row.label }, [
672
+ h2("dt", row.label),
673
+ h2("dd", row.value)
674
+ ])))
675
+ ]))) : h2("p", { class: "absolute-voice-provider-status__empty" }, "Run voice traffic to see provider health."),
676
+ model.value.error ? h2("p", { class: "absolute-voice-provider-status__error" }, model.value.error) : null
677
+ ]);
678
+ }
679
+ });
680
+ // src/vue/VoiceRoutingStatus.ts
681
+ import { computed as computed2, defineComponent as defineComponent3, h as h3 } from "vue";
682
+
380
683
  // src/client/routingStatus.ts
381
684
  var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
382
685
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -458,9 +761,9 @@ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {})
458
761
  };
459
762
 
460
763
  // src/client/routingStatusWidget.ts
461
- var DEFAULT_TITLE2 = "Voice Routing";
462
- var DEFAULT_DESCRIPTION2 = "Latest provider routing decision from the self-hosted trace store.";
463
- var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
764
+ var DEFAULT_TITLE3 = "Voice Routing";
765
+ var DEFAULT_DESCRIPTION3 = "Latest provider routing decision from the self-hosted trace store.";
766
+ var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
464
767
  var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
465
768
  var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
466
769
  const decision = snapshot.decision;
@@ -484,30 +787,30 @@ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
484
787
  ] : [];
485
788
  return {
486
789
  decision,
487
- description: options.description ?? DEFAULT_DESCRIPTION2,
790
+ description: options.description ?? DEFAULT_DESCRIPTION3,
488
791
  error: snapshot.error,
489
792
  isLoading: snapshot.isLoading,
490
793
  label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
491
794
  rows,
492
795
  status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
493
- title: options.title ?? DEFAULT_TITLE2,
796
+ title: options.title ?? DEFAULT_TITLE3,
494
797
  updatedAt: snapshot.updatedAt
495
798
  };
496
799
  };
497
800
  var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
498
801
  const model = createVoiceRoutingStatusViewModel(snapshot, options);
499
802
  const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
500
- <span>${escapeHtml2(row.label)}</span>
501
- <strong>${escapeHtml2(row.value)}</strong>
803
+ <span>${escapeHtml3(row.label)}</span>
804
+ <strong>${escapeHtml3(row.value)}</strong>
502
805
  </div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
503
- return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml2(model.status)}">
806
+ return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml3(model.status)}">
504
807
  <header class="absolute-voice-routing-status__header">
505
- <span class="absolute-voice-routing-status__eyebrow">${escapeHtml2(model.title)}</span>
506
- <strong class="absolute-voice-routing-status__label">${escapeHtml2(model.label)}</strong>
808
+ <span class="absolute-voice-routing-status__eyebrow">${escapeHtml3(model.title)}</span>
809
+ <strong class="absolute-voice-routing-status__label">${escapeHtml3(model.label)}</strong>
507
810
  </header>
508
- <p class="absolute-voice-routing-status__description">${escapeHtml2(model.description)}</p>
811
+ <p class="absolute-voice-routing-status__description">${escapeHtml3(model.description)}</p>
509
812
  ${rows}
510
- ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml2(model.error)}</p>` : ""}
813
+ ${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml3(model.error)}</p>` : ""}
511
814
  </section>`;
512
815
  };
513
816
  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}`;
@@ -549,13 +852,13 @@ var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status"
549
852
  };
550
853
 
551
854
  // src/vue/useVoiceRoutingStatus.ts
552
- import { onUnmounted as onUnmounted2, ref as ref2, shallowRef as shallowRef2 } from "vue";
855
+ import { onUnmounted as onUnmounted3, ref as ref3, shallowRef as shallowRef3 } from "vue";
553
856
  var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
554
857
  const store = createVoiceRoutingStatusStore(path, options);
555
- const decision = shallowRef2(null);
556
- const error = ref2(null);
557
- const isLoading = ref2(false);
558
- const updatedAt = ref2(undefined);
858
+ const decision = shallowRef3(null);
859
+ const error = ref3(null);
860
+ const isLoading = ref3(false);
861
+ const updatedAt = ref3(undefined);
559
862
  const sync = () => {
560
863
  const snapshot = store.getSnapshot();
561
864
  decision.value = snapshot.decision;
@@ -566,7 +869,7 @@ var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
566
869
  const unsubscribe = store.subscribe(sync);
567
870
  sync();
568
871
  store.refresh().catch(() => {});
569
- onUnmounted2(() => {
872
+ onUnmounted3(() => {
570
873
  unsubscribe();
571
874
  store.close();
572
875
  });
@@ -580,7 +883,7 @@ var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
580
883
  };
581
884
 
582
885
  // src/vue/VoiceRoutingStatus.ts
583
- var VoiceRoutingStatus = defineComponent2({
886
+ var VoiceRoutingStatus = defineComponent3({
584
887
  name: "VoiceRoutingStatus",
585
888
  props: {
586
889
  class: {
@@ -611,34 +914,34 @@ var VoiceRoutingStatus = defineComponent2({
611
914
  title: props.title
612
915
  };
613
916
  const status = useVoiceRoutingStatus(props.path, options);
614
- const model = computed(() => createVoiceRoutingStatusViewModel({
917
+ const model = computed2(() => createVoiceRoutingStatusViewModel({
615
918
  decision: status.decision.value,
616
919
  error: status.error.value,
617
920
  isLoading: status.isLoading.value,
618
921
  updatedAt: status.updatedAt.value
619
922
  }, options));
620
- return () => h2("section", {
923
+ return () => h3("section", {
621
924
  class: [
622
925
  "absolute-voice-routing-status",
623
926
  `absolute-voice-routing-status--${model.value.status}`,
624
927
  props.class
625
928
  ]
626
929
  }, [
627
- h2("header", { class: "absolute-voice-routing-status__header" }, [
628
- h2("span", { class: "absolute-voice-routing-status__eyebrow" }, model.value.title),
629
- h2("strong", { class: "absolute-voice-routing-status__label" }, model.value.label)
930
+ h3("header", { class: "absolute-voice-routing-status__header" }, [
931
+ h3("span", { class: "absolute-voice-routing-status__eyebrow" }, model.value.title),
932
+ h3("strong", { class: "absolute-voice-routing-status__label" }, model.value.label)
630
933
  ]),
631
- h2("p", { class: "absolute-voice-routing-status__description" }, model.value.description),
632
- model.value.rows.length ? h2("div", { class: "absolute-voice-routing-status__grid" }, model.value.rows.map((row) => h2("div", { key: row.label }, [
633
- h2("span", row.label),
634
- h2("strong", row.value)
635
- ]))) : h2("p", { class: "absolute-voice-routing-status__empty" }, "Start a voice session to see the selected provider."),
636
- model.value.error ? h2("p", { class: "absolute-voice-routing-status__error" }, model.value.error) : null
934
+ h3("p", { class: "absolute-voice-routing-status__description" }, model.value.description),
935
+ model.value.rows.length ? h3("div", { class: "absolute-voice-routing-status__grid" }, model.value.rows.map((row) => h3("div", { key: row.label }, [
936
+ h3("span", row.label),
937
+ h3("strong", row.value)
938
+ ]))) : h3("p", { class: "absolute-voice-routing-status__empty" }, "Start a voice session to see the selected provider."),
939
+ model.value.error ? h3("p", { class: "absolute-voice-routing-status__error" }, model.value.error) : null
637
940
  ]);
638
941
  }
639
942
  });
640
943
  // src/vue/useVoiceStream.ts
641
- import { onUnmounted as onUnmounted3, ref as ref3, shallowRef as shallowRef3 } from "vue";
944
+ import { onUnmounted as onUnmounted4, ref as ref4, shallowRef as shallowRef4 } from "vue";
642
945
 
643
946
  // src/client/actions.ts
644
947
  var normalizeErrorMessage = (value) => {
@@ -1156,15 +1459,15 @@ var createVoiceStream = (path, options = {}) => {
1156
1459
  // src/vue/useVoiceStream.ts
1157
1460
  var useVoiceStream = (path, options = {}) => {
1158
1461
  const stream = createVoiceStream(path, options);
1159
- const assistantAudio = shallowRef3([]);
1160
- const assistantTexts = shallowRef3([]);
1161
- const call = shallowRef3(null);
1162
- const error = ref3(null);
1163
- const isConnected = ref3(false);
1164
- const partial = ref3("");
1165
- const sessionId = ref3(stream.sessionId);
1166
- const status = ref3(stream.status);
1167
- const turns = shallowRef3([]);
1462
+ const assistantAudio = shallowRef4([]);
1463
+ const assistantTexts = shallowRef4([]);
1464
+ const call = shallowRef4(null);
1465
+ const error = ref4(null);
1466
+ const isConnected = ref4(false);
1467
+ const partial = ref4("");
1468
+ const sessionId = ref4(stream.sessionId);
1469
+ const status = ref4(stream.status);
1470
+ const turns = shallowRef4([]);
1168
1471
  const sync = () => {
1169
1472
  assistantAudio.value = [...stream.assistantAudio];
1170
1473
  assistantTexts.value = [...stream.assistantTexts];
@@ -1182,7 +1485,7 @@ var useVoiceStream = (path, options = {}) => {
1182
1485
  unsubscribe();
1183
1486
  stream.close();
1184
1487
  };
1185
- onUnmounted3(destroy);
1488
+ onUnmounted4(destroy);
1186
1489
  return {
1187
1490
  assistantAudio,
1188
1491
  assistantTexts,
@@ -1200,7 +1503,7 @@ var useVoiceStream = (path, options = {}) => {
1200
1503
  };
1201
1504
  };
1202
1505
  // src/vue/useVoiceController.ts
1203
- import { onUnmounted as onUnmounted4, ref as ref4, shallowRef as shallowRef4 } from "vue";
1506
+ import { onUnmounted as onUnmounted5, ref as ref5, shallowRef as shallowRef5 } from "vue";
1204
1507
 
1205
1508
  // src/client/htmx.ts
1206
1509
  var DEFAULT_EVENT_NAME = "voice-refresh";
@@ -1835,16 +2138,16 @@ var createVoiceController = (path, options = {}) => {
1835
2138
  // src/vue/useVoiceController.ts
1836
2139
  var useVoiceController = (path, options = {}) => {
1837
2140
  const controller = createVoiceController(path, options);
1838
- const assistantAudio = shallowRef4([]);
1839
- const assistantTexts = shallowRef4([]);
1840
- const error = ref4(null);
1841
- const isConnected = ref4(false);
1842
- const isRecording = ref4(false);
1843
- const partial = ref4("");
1844
- const recordingError = ref4(null);
1845
- const sessionId = ref4(controller.sessionId);
1846
- const status = ref4(controller.status);
1847
- const turns = shallowRef4([]);
2141
+ const assistantAudio = shallowRef5([]);
2142
+ const assistantTexts = shallowRef5([]);
2143
+ const error = ref5(null);
2144
+ const isConnected = ref5(false);
2145
+ const isRecording = ref5(false);
2146
+ const partial = ref5("");
2147
+ const recordingError = ref5(null);
2148
+ const sessionId = ref5(controller.sessionId);
2149
+ const status = ref5(controller.status);
2150
+ const turns = shallowRef5([]);
1848
2151
  const sync = () => {
1849
2152
  assistantAudio.value = [...controller.assistantAudio];
1850
2153
  assistantTexts.value = [...controller.assistantTexts];
@@ -1863,7 +2166,7 @@ var useVoiceController = (path, options = {}) => {
1863
2166
  unsubscribe();
1864
2167
  controller.close();
1865
2168
  };
1866
- onUnmounted4(destroy);
2169
+ onUnmounted5(destroy);
1867
2170
  return {
1868
2171
  assistantAudio,
1869
2172
  assistantTexts,
@@ -1884,118 +2187,6 @@ var useVoiceController = (path, options = {}) => {
1884
2187
  turns
1885
2188
  };
1886
2189
  };
1887
- // src/vue/useVoiceProviderStatus.ts
1888
- import { onUnmounted as onUnmounted5, ref as ref5, shallowRef as shallowRef5 } from "vue";
1889
-
1890
- // src/client/providerStatus.ts
1891
- var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
1892
- const fetchImpl = options.fetch ?? globalThis.fetch;
1893
- const response = await fetchImpl(path);
1894
- if (!response.ok) {
1895
- throw new Error(`Voice provider status failed: HTTP ${response.status}`);
1896
- }
1897
- return await response.json();
1898
- };
1899
- var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
1900
- const listeners = new Set;
1901
- let closed = false;
1902
- let timer;
1903
- let snapshot = {
1904
- error: null,
1905
- isLoading: false,
1906
- providers: []
1907
- };
1908
- const emit = () => {
1909
- for (const listener of listeners) {
1910
- listener();
1911
- }
1912
- };
1913
- const refresh = async () => {
1914
- if (closed) {
1915
- return snapshot.providers;
1916
- }
1917
- snapshot = {
1918
- ...snapshot,
1919
- error: null,
1920
- isLoading: true
1921
- };
1922
- emit();
1923
- try {
1924
- const providers = await fetchVoiceProviderStatus(path, options);
1925
- snapshot = {
1926
- error: null,
1927
- isLoading: false,
1928
- providers,
1929
- updatedAt: Date.now()
1930
- };
1931
- emit();
1932
- return providers;
1933
- } catch (error) {
1934
- snapshot = {
1935
- ...snapshot,
1936
- error: error instanceof Error ? error.message : String(error),
1937
- isLoading: false
1938
- };
1939
- emit();
1940
- throw error;
1941
- }
1942
- };
1943
- const close = () => {
1944
- closed = true;
1945
- if (timer) {
1946
- clearInterval(timer);
1947
- timer = undefined;
1948
- }
1949
- listeners.clear();
1950
- };
1951
- if (options.intervalMs && options.intervalMs > 0) {
1952
- timer = setInterval(() => {
1953
- refresh().catch(() => {});
1954
- }, options.intervalMs);
1955
- }
1956
- return {
1957
- close,
1958
- getServerSnapshot: () => snapshot,
1959
- getSnapshot: () => snapshot,
1960
- refresh,
1961
- subscribe: (listener) => {
1962
- listeners.add(listener);
1963
- return () => {
1964
- listeners.delete(listener);
1965
- };
1966
- }
1967
- };
1968
- };
1969
-
1970
- // src/vue/useVoiceProviderStatus.ts
1971
- var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
1972
- const store = createVoiceProviderStatusStore(path, options);
1973
- const error = ref5(null);
1974
- const isLoading = ref5(false);
1975
- const providers = shallowRef5([]);
1976
- const updatedAt = ref5(undefined);
1977
- const sync = () => {
1978
- const snapshot = store.getSnapshot();
1979
- error.value = snapshot.error;
1980
- isLoading.value = snapshot.isLoading;
1981
- providers.value = [...snapshot.providers];
1982
- updatedAt.value = snapshot.updatedAt;
1983
- };
1984
- const unsubscribe = store.subscribe(sync);
1985
- sync();
1986
- store.refresh().catch(() => {});
1987
- onUnmounted5(() => {
1988
- unsubscribe();
1989
- store.close();
1990
- });
1991
- return {
1992
- error,
1993
- isLoading,
1994
- providers,
1995
- refresh: store.refresh,
1996
- updatedAt
1997
- };
1998
- };
1999
2190
  // src/vue/useVoiceWorkflowStatus.ts
2000
2191
  import { onUnmounted as onUnmounted6, ref as ref6, shallowRef as shallowRef6 } from "vue";
2001
2192
 
@@ -2117,5 +2308,6 @@ export {
2117
2308
  useVoiceController,
2118
2309
  useVoiceAppKitStatus,
2119
2310
  VoiceRoutingStatus,
2311
+ VoiceProviderStatus,
2120
2312
  VoiceOpsStatus
2121
2313
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.59",
3
+ "version": "0.0.22-beta.60",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",