@absolutejs/voice 0.0.22-beta.65 → 0.0.22-beta.67
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/angular/index.d.ts +2 -0
- package/dist/angular/index.js +272 -26
- package/dist/angular/voice-provider-capabilities.service.d.ts +12 -0
- package/dist/angular/voice-turn-quality.service.d.ts +12 -0
- package/dist/client/index.d.ts +8 -0
- package/dist/client/index.js +397 -0
- package/dist/client/providerCapabilities.d.ts +19 -0
- package/dist/client/providerCapabilitiesWidget.d.ts +32 -0
- package/dist/client/turnQuality.d.ts +19 -0
- package/dist/client/turnQualityWidget.d.ts +32 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +123 -2
- package/dist/react/VoiceProviderCapabilities.d.ts +6 -0
- package/dist/react/VoiceTurnQuality.d.ts +6 -0
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.js +672 -83
- package/dist/react/useVoiceProviderCapabilities.d.ts +8 -0
- package/dist/react/useVoiceTurnQuality.d.ts +8 -0
- package/dist/svelte/createVoiceProviderCapabilities.d.ts +10 -0
- package/dist/svelte/createVoiceTurnQuality.d.ts +10 -0
- package/dist/svelte/index.d.ts +2 -0
- package/dist/svelte/index.js +440 -33
- package/dist/turnQuality.d.ts +94 -0
- package/dist/vue/VoiceProviderCapabilities.d.ts +51 -0
- package/dist/vue/VoiceTurnQuality.d.ts +51 -0
- package/dist/vue/index.d.ts +4 -0
- package/dist/vue/index.js +664 -84
- package/dist/vue/useVoiceProviderCapabilities.d.ts +9 -0
- package/dist/vue/useVoiceTurnQuality.d.ts +9 -0
- package/package.json +1 -1
package/dist/vue/index.js
CHANGED
|
@@ -664,9 +664,310 @@ var VoiceProviderSimulationControls = defineComponent2({
|
|
|
664
664
|
]);
|
|
665
665
|
}
|
|
666
666
|
});
|
|
667
|
-
// src/vue/
|
|
667
|
+
// src/vue/VoiceProviderCapabilities.ts
|
|
668
668
|
import { computed as computed2, defineComponent as defineComponent3, h as h3 } from "vue";
|
|
669
669
|
|
|
670
|
+
// src/client/providerCapabilities.ts
|
|
671
|
+
var fetchVoiceProviderCapabilities = async (path = "/api/provider-capabilities", options = {}) => {
|
|
672
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
673
|
+
const response = await fetchImpl(path);
|
|
674
|
+
if (!response.ok) {
|
|
675
|
+
throw new Error(`Voice provider capabilities failed: HTTP ${response.status}`);
|
|
676
|
+
}
|
|
677
|
+
return await response.json();
|
|
678
|
+
};
|
|
679
|
+
var createVoiceProviderCapabilitiesStore = (path = "/api/provider-capabilities", options = {}) => {
|
|
680
|
+
const listeners = new Set;
|
|
681
|
+
let closed = false;
|
|
682
|
+
let timer;
|
|
683
|
+
let snapshot = {
|
|
684
|
+
error: null,
|
|
685
|
+
isLoading: false
|
|
686
|
+
};
|
|
687
|
+
const emit = () => {
|
|
688
|
+
for (const listener of listeners) {
|
|
689
|
+
listener();
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
const refresh = async () => {
|
|
693
|
+
if (closed) {
|
|
694
|
+
return snapshot.report;
|
|
695
|
+
}
|
|
696
|
+
snapshot = {
|
|
697
|
+
...snapshot,
|
|
698
|
+
error: null,
|
|
699
|
+
isLoading: true
|
|
700
|
+
};
|
|
701
|
+
emit();
|
|
702
|
+
try {
|
|
703
|
+
const report = await fetchVoiceProviderCapabilities(path, options);
|
|
704
|
+
snapshot = {
|
|
705
|
+
error: null,
|
|
706
|
+
isLoading: false,
|
|
707
|
+
report,
|
|
708
|
+
updatedAt: Date.now()
|
|
709
|
+
};
|
|
710
|
+
emit();
|
|
711
|
+
return report;
|
|
712
|
+
} catch (error) {
|
|
713
|
+
snapshot = {
|
|
714
|
+
...snapshot,
|
|
715
|
+
error: error instanceof Error ? error.message : String(error),
|
|
716
|
+
isLoading: false
|
|
717
|
+
};
|
|
718
|
+
emit();
|
|
719
|
+
throw error;
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
const close = () => {
|
|
723
|
+
closed = true;
|
|
724
|
+
if (timer) {
|
|
725
|
+
clearInterval(timer);
|
|
726
|
+
timer = undefined;
|
|
727
|
+
}
|
|
728
|
+
listeners.clear();
|
|
729
|
+
};
|
|
730
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
731
|
+
timer = setInterval(() => {
|
|
732
|
+
refresh().catch(() => {});
|
|
733
|
+
}, options.intervalMs);
|
|
734
|
+
}
|
|
735
|
+
return {
|
|
736
|
+
close,
|
|
737
|
+
getServerSnapshot: () => snapshot,
|
|
738
|
+
getSnapshot: () => snapshot,
|
|
739
|
+
refresh,
|
|
740
|
+
subscribe: (listener) => {
|
|
741
|
+
listeners.add(listener);
|
|
742
|
+
return () => {
|
|
743
|
+
listeners.delete(listener);
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
// src/client/providerCapabilitiesWidget.ts
|
|
750
|
+
var DEFAULT_TITLE2 = "Provider Capabilities";
|
|
751
|
+
var DEFAULT_DESCRIPTION2 = "Configured, selected, and healthy voice providers for this deployment.";
|
|
752
|
+
var escapeHtml3 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
753
|
+
var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
754
|
+
var formatKind2 = (kind) => kind.toUpperCase();
|
|
755
|
+
var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
756
|
+
var getCapabilityDetail = (capability) => {
|
|
757
|
+
if (!capability.configured) {
|
|
758
|
+
return "Not configured in this deployment.";
|
|
759
|
+
}
|
|
760
|
+
if (capability.selected) {
|
|
761
|
+
return `Selected ${capability.kind.toUpperCase()} provider for new sessions.`;
|
|
762
|
+
}
|
|
763
|
+
if (capability.health?.status === "healthy") {
|
|
764
|
+
return "Configured and healthy fallback candidate.";
|
|
765
|
+
}
|
|
766
|
+
if (capability.health?.status === "idle") {
|
|
767
|
+
return "Configured; no traffic observed yet.";
|
|
768
|
+
}
|
|
769
|
+
if (capability.health?.lastError) {
|
|
770
|
+
return capability.health.lastError;
|
|
771
|
+
}
|
|
772
|
+
return "Configured and available.";
|
|
773
|
+
};
|
|
774
|
+
var isWarningStatus = (status) => status === "degraded" || status === "rate-limited" || status === "suppressed" || status === "unconfigured";
|
|
775
|
+
var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
|
|
776
|
+
const capabilities = (snapshot.report?.capabilities ?? []).map((capability) => ({
|
|
777
|
+
...capability,
|
|
778
|
+
detail: getCapabilityDetail(capability),
|
|
779
|
+
label: `${formatProvider(capability.provider)} ${formatKind2(capability.kind)}`,
|
|
780
|
+
rows: [
|
|
781
|
+
{ label: "Status", value: formatStatus(capability.status) },
|
|
782
|
+
{ label: "Selected", value: capability.selected ? "Yes" : "No" },
|
|
783
|
+
{ label: "Model", value: capability.model ?? "Default" },
|
|
784
|
+
{
|
|
785
|
+
label: "Features",
|
|
786
|
+
value: capability.features?.join(", ") || "Not specified"
|
|
787
|
+
},
|
|
788
|
+
{ label: "Runs", value: String(capability.health?.runCount ?? 0) },
|
|
789
|
+
{ label: "Errors", value: String(capability.health?.errorCount ?? 0) }
|
|
790
|
+
]
|
|
791
|
+
}));
|
|
792
|
+
const warningCount = capabilities.filter((capability) => isWarningStatus(capability.status)).length;
|
|
793
|
+
const selectedCount = snapshot.report?.selected ?? capabilities.filter((capability) => capability.selected).length;
|
|
794
|
+
return {
|
|
795
|
+
capabilities,
|
|
796
|
+
description: options.description ?? DEFAULT_DESCRIPTION2,
|
|
797
|
+
error: snapshot.error,
|
|
798
|
+
isLoading: snapshot.isLoading,
|
|
799
|
+
label: snapshot.error ? "Unavailable" : capabilities.length ? warningCount > 0 ? `${warningCount} needs attention` : `${selectedCount} selected` : snapshot.isLoading ? "Checking" : "No capabilities",
|
|
800
|
+
status: snapshot.error ? "error" : capabilities.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
801
|
+
title: options.title ?? DEFAULT_TITLE2,
|
|
802
|
+
updatedAt: snapshot.updatedAt
|
|
803
|
+
};
|
|
804
|
+
};
|
|
805
|
+
var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
|
|
806
|
+
const model = createVoiceProviderCapabilitiesViewModel(snapshot, options);
|
|
807
|
+
const capabilities = model.capabilities.length ? `<div class="absolute-voice-provider-capabilities__providers">${model.capabilities.map((capability) => `<article class="absolute-voice-provider-capabilities__provider absolute-voice-provider-capabilities__provider--${escapeHtml3(capability.status)}">
|
|
808
|
+
<header>
|
|
809
|
+
<strong>${escapeHtml3(capability.label)}</strong>
|
|
810
|
+
<span>${escapeHtml3(formatStatus(capability.status))}</span>
|
|
811
|
+
</header>
|
|
812
|
+
<p>${escapeHtml3(capability.detail)}</p>
|
|
813
|
+
<dl>${capability.rows.map((row) => `<div>
|
|
814
|
+
<dt>${escapeHtml3(row.label)}</dt>
|
|
815
|
+
<dd>${escapeHtml3(row.value)}</dd>
|
|
816
|
+
</div>`).join("")}</dl>
|
|
817
|
+
</article>`).join("")}</div>` : '<p class="absolute-voice-provider-capabilities__empty">Configure provider capabilities to see deployment coverage.</p>';
|
|
818
|
+
return `<section class="absolute-voice-provider-capabilities absolute-voice-provider-capabilities--${escapeHtml3(model.status)}">
|
|
819
|
+
<header class="absolute-voice-provider-capabilities__header">
|
|
820
|
+
<span class="absolute-voice-provider-capabilities__eyebrow">${escapeHtml3(model.title)}</span>
|
|
821
|
+
<strong class="absolute-voice-provider-capabilities__label">${escapeHtml3(model.label)}</strong>
|
|
822
|
+
</header>
|
|
823
|
+
<p class="absolute-voice-provider-capabilities__description">${escapeHtml3(model.description)}</p>
|
|
824
|
+
${capabilities}
|
|
825
|
+
${model.error ? `<p class="absolute-voice-provider-capabilities__error">${escapeHtml3(model.error)}</p>` : ""}
|
|
826
|
+
</section>`;
|
|
827
|
+
};
|
|
828
|
+
var getVoiceProviderCapabilitiesCSS = () => `.absolute-voice-provider-capabilities{border:1px solid #bfd7ea;border-radius:20px;background:#f6fbff;color:#08131f;padding:18px;box-shadow:0 18px 40px rgba(14,51,78,.12);font-family:inherit}.absolute-voice-provider-capabilities--error,.absolute-voice-provider-capabilities--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-provider-capabilities__header,.absolute-voice-provider-capabilities__provider header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-capabilities__eyebrow{color:#255f85;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-capabilities__label{font-size:24px;line-height:1}.absolute-voice-provider-capabilities__description,.absolute-voice-provider-capabilities__provider p,.absolute-voice-provider-capabilities__provider dt,.absolute-voice-provider-capabilities__empty{color:#405467}.absolute-voice-provider-capabilities__providers{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-capabilities__provider{background:#fff;border:1px solid #d7e7f3;border-radius:16px;padding:14px}.absolute-voice-provider-capabilities__provider--selected,.absolute-voice-provider-capabilities__provider--healthy{border-color:#86efac}.absolute-voice-provider-capabilities__provider--degraded,.absolute-voice-provider-capabilities__provider--rate-limited,.absolute-voice-provider-capabilities__provider--suppressed,.absolute-voice-provider-capabilities__provider--unconfigured{border-color:#f2a7a7}.absolute-voice-provider-capabilities__provider p{margin:10px 0}.absolute-voice-provider-capabilities__provider dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-capabilities__provider div{background:#f6fbff;border:1px solid #d7e7f3;border-radius:12px;padding:8px}.absolute-voice-provider-capabilities__provider dt{font-size:12px}.absolute-voice-provider-capabilities__provider dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-capabilities__empty{margin:14px 0 0}.absolute-voice-provider-capabilities__error{color:#9f1239;font-weight:700}`;
|
|
829
|
+
var mountVoiceProviderCapabilities = (element, path = "/api/provider-capabilities", options = {}) => {
|
|
830
|
+
const store = createVoiceProviderCapabilitiesStore(path, options);
|
|
831
|
+
const render = () => {
|
|
832
|
+
element.innerHTML = renderVoiceProviderCapabilitiesHTML(store.getSnapshot(), options);
|
|
833
|
+
};
|
|
834
|
+
const unsubscribe = store.subscribe(render);
|
|
835
|
+
render();
|
|
836
|
+
store.refresh().catch(() => {});
|
|
837
|
+
return {
|
|
838
|
+
close: () => {
|
|
839
|
+
unsubscribe();
|
|
840
|
+
store.close();
|
|
841
|
+
},
|
|
842
|
+
refresh: store.refresh
|
|
843
|
+
};
|
|
844
|
+
};
|
|
845
|
+
var defineVoiceProviderCapabilitiesElement = (tagName = "absolute-voice-provider-capabilities") => {
|
|
846
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
customElements.define(tagName, class AbsoluteVoiceProviderCapabilitiesElement extends HTMLElement {
|
|
850
|
+
mounted;
|
|
851
|
+
connectedCallback() {
|
|
852
|
+
const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
|
|
853
|
+
this.mounted = mountVoiceProviderCapabilities(this, this.getAttribute("path") ?? "/api/provider-capabilities", {
|
|
854
|
+
description: this.getAttribute("description") ?? undefined,
|
|
855
|
+
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
856
|
+
title: this.getAttribute("title") ?? undefined
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
disconnectedCallback() {
|
|
860
|
+
this.mounted?.close();
|
|
861
|
+
this.mounted = undefined;
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
// src/vue/useVoiceProviderCapabilities.ts
|
|
867
|
+
import { onUnmounted as onUnmounted3, shallowRef as shallowRef2 } from "vue";
|
|
868
|
+
var useVoiceProviderCapabilities = (path = "/api/provider-capabilities", options = {}) => {
|
|
869
|
+
const store = createVoiceProviderCapabilitiesStore(path, options);
|
|
870
|
+
const error = shallowRef2(null);
|
|
871
|
+
const isLoading = shallowRef2(false);
|
|
872
|
+
const report = shallowRef2();
|
|
873
|
+
const updatedAt = shallowRef2(undefined);
|
|
874
|
+
const sync = () => {
|
|
875
|
+
const snapshot = store.getSnapshot();
|
|
876
|
+
error.value = snapshot.error;
|
|
877
|
+
isLoading.value = snapshot.isLoading;
|
|
878
|
+
report.value = snapshot.report;
|
|
879
|
+
updatedAt.value = snapshot.updatedAt;
|
|
880
|
+
};
|
|
881
|
+
const unsubscribe = store.subscribe(sync);
|
|
882
|
+
sync();
|
|
883
|
+
store.refresh().catch(() => {});
|
|
884
|
+
onUnmounted3(() => {
|
|
885
|
+
unsubscribe();
|
|
886
|
+
store.close();
|
|
887
|
+
});
|
|
888
|
+
return {
|
|
889
|
+
error,
|
|
890
|
+
isLoading,
|
|
891
|
+
refresh: store.refresh,
|
|
892
|
+
report,
|
|
893
|
+
updatedAt
|
|
894
|
+
};
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
// src/vue/VoiceProviderCapabilities.ts
|
|
898
|
+
var VoiceProviderCapabilities = defineComponent3({
|
|
899
|
+
name: "VoiceProviderCapabilities",
|
|
900
|
+
props: {
|
|
901
|
+
class: {
|
|
902
|
+
default: "",
|
|
903
|
+
type: String
|
|
904
|
+
},
|
|
905
|
+
description: {
|
|
906
|
+
default: undefined,
|
|
907
|
+
type: String
|
|
908
|
+
},
|
|
909
|
+
intervalMs: {
|
|
910
|
+
default: 5000,
|
|
911
|
+
type: Number
|
|
912
|
+
},
|
|
913
|
+
path: {
|
|
914
|
+
default: "/api/provider-capabilities",
|
|
915
|
+
type: String
|
|
916
|
+
},
|
|
917
|
+
title: {
|
|
918
|
+
default: undefined,
|
|
919
|
+
type: String
|
|
920
|
+
}
|
|
921
|
+
},
|
|
922
|
+
setup(props) {
|
|
923
|
+
const options = {
|
|
924
|
+
description: props.description,
|
|
925
|
+
intervalMs: props.intervalMs,
|
|
926
|
+
title: props.title
|
|
927
|
+
};
|
|
928
|
+
const capabilities = useVoiceProviderCapabilities(props.path, options);
|
|
929
|
+
const model = computed2(() => createVoiceProviderCapabilitiesViewModel({
|
|
930
|
+
error: capabilities.error.value,
|
|
931
|
+
isLoading: capabilities.isLoading.value,
|
|
932
|
+
report: capabilities.report.value,
|
|
933
|
+
updatedAt: capabilities.updatedAt.value
|
|
934
|
+
}, options));
|
|
935
|
+
return () => h3("section", {
|
|
936
|
+
class: [
|
|
937
|
+
"absolute-voice-provider-capabilities",
|
|
938
|
+
`absolute-voice-provider-capabilities--${model.value.status}`,
|
|
939
|
+
props.class
|
|
940
|
+
]
|
|
941
|
+
}, [
|
|
942
|
+
h3("header", { class: "absolute-voice-provider-capabilities__header" }, [
|
|
943
|
+
h3("span", { class: "absolute-voice-provider-capabilities__eyebrow" }, model.value.title),
|
|
944
|
+
h3("strong", { class: "absolute-voice-provider-capabilities__label" }, model.value.label)
|
|
945
|
+
]),
|
|
946
|
+
h3("p", { class: "absolute-voice-provider-capabilities__description" }, model.value.description),
|
|
947
|
+
model.value.capabilities.length ? h3("div", { class: "absolute-voice-provider-capabilities__providers" }, model.value.capabilities.map((capability) => h3("article", {
|
|
948
|
+
class: [
|
|
949
|
+
"absolute-voice-provider-capabilities__provider",
|
|
950
|
+
`absolute-voice-provider-capabilities__provider--${capability.status}`
|
|
951
|
+
],
|
|
952
|
+
key: `${capability.kind}:${capability.provider}`
|
|
953
|
+
}, [
|
|
954
|
+
h3("header", [
|
|
955
|
+
h3("strong", capability.label),
|
|
956
|
+
h3("span", capability.status)
|
|
957
|
+
]),
|
|
958
|
+
h3("p", capability.detail),
|
|
959
|
+
h3("dl", capability.rows.map((row) => h3("div", { key: row.label }, [
|
|
960
|
+
h3("dt", row.label),
|
|
961
|
+
h3("dd", row.value)
|
|
962
|
+
])))
|
|
963
|
+
]))) : h3("p", { class: "absolute-voice-provider-capabilities__empty" }, "Configure provider capabilities to see deployment coverage."),
|
|
964
|
+
model.value.error ? h3("p", { class: "absolute-voice-provider-capabilities__error" }, model.value.error) : null
|
|
965
|
+
]);
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
// src/vue/VoiceProviderStatus.ts
|
|
969
|
+
import { computed as computed3, defineComponent as defineComponent4, h as h4 } from "vue";
|
|
970
|
+
|
|
670
971
|
// src/client/providerStatus.ts
|
|
671
972
|
var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
|
|
672
973
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
@@ -748,11 +1049,11 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
|
|
|
748
1049
|
};
|
|
749
1050
|
|
|
750
1051
|
// src/client/providerStatusWidget.ts
|
|
751
|
-
var
|
|
752
|
-
var
|
|
753
|
-
var
|
|
754
|
-
var
|
|
755
|
-
var
|
|
1052
|
+
var DEFAULT_TITLE3 = "Voice Providers";
|
|
1053
|
+
var DEFAULT_DESCRIPTION3 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
|
|
1054
|
+
var escapeHtml4 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1055
|
+
var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
1056
|
+
var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
756
1057
|
var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
|
|
757
1058
|
var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
|
|
758
1059
|
var getProviderDetail = (provider) => {
|
|
@@ -773,12 +1074,12 @@ var getProviderDetail = (provider) => {
|
|
|
773
1074
|
}
|
|
774
1075
|
return "No provider traffic observed yet.";
|
|
775
1076
|
};
|
|
776
|
-
var
|
|
1077
|
+
var isWarningStatus2 = (status) => status === "degraded" || status === "rate-limited" || status === "recoverable" || status === "suppressed";
|
|
777
1078
|
var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
|
|
778
1079
|
const providers = snapshot.providers.map((provider) => ({
|
|
779
1080
|
...provider,
|
|
780
1081
|
detail: getProviderDetail(provider),
|
|
781
|
-
label: `${
|
|
1082
|
+
label: `${formatProvider2(provider.provider)}${provider.recommended ? " recommended" : ""}`,
|
|
782
1083
|
rows: [
|
|
783
1084
|
{ label: "Runs", value: String(provider.runCount) },
|
|
784
1085
|
{ label: "Avg latency", value: formatLatency(provider.averageElapsedMs) },
|
|
@@ -791,40 +1092,40 @@ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
|
|
|
791
1092
|
}
|
|
792
1093
|
]
|
|
793
1094
|
}));
|
|
794
|
-
const warningCount = providers.filter((provider) =>
|
|
1095
|
+
const warningCount = providers.filter((provider) => isWarningStatus2(provider.status)).length;
|
|
795
1096
|
const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
|
|
796
1097
|
return {
|
|
797
|
-
description: options.description ??
|
|
1098
|
+
description: options.description ?? DEFAULT_DESCRIPTION3,
|
|
798
1099
|
error: snapshot.error,
|
|
799
1100
|
isLoading: snapshot.isLoading,
|
|
800
1101
|
label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
|
|
801
1102
|
providers,
|
|
802
1103
|
status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
803
|
-
title: options.title ??
|
|
1104
|
+
title: options.title ?? DEFAULT_TITLE3,
|
|
804
1105
|
updatedAt: snapshot.updatedAt
|
|
805
1106
|
};
|
|
806
1107
|
};
|
|
807
1108
|
var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
|
|
808
1109
|
const model = createVoiceProviderStatusViewModel(snapshot, options);
|
|
809
|
-
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--${
|
|
1110
|
+
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--${escapeHtml4(provider.status)}">
|
|
810
1111
|
<header>
|
|
811
|
-
<strong>${
|
|
812
|
-
<span>${
|
|
1112
|
+
<strong>${escapeHtml4(provider.label)}</strong>
|
|
1113
|
+
<span>${escapeHtml4(formatStatus2(provider.status))}</span>
|
|
813
1114
|
</header>
|
|
814
|
-
<p>${
|
|
1115
|
+
<p>${escapeHtml4(provider.detail)}</p>
|
|
815
1116
|
<dl>${provider.rows.map((row) => `<div>
|
|
816
|
-
<dt>${
|
|
817
|
-
<dd>${
|
|
1117
|
+
<dt>${escapeHtml4(row.label)}</dt>
|
|
1118
|
+
<dd>${escapeHtml4(row.value)}</dd>
|
|
818
1119
|
</div>`).join("")}</dl>
|
|
819
1120
|
</article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
|
|
820
|
-
return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${
|
|
1121
|
+
return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml4(model.status)}">
|
|
821
1122
|
<header class="absolute-voice-provider-status__header">
|
|
822
|
-
<span class="absolute-voice-provider-status__eyebrow">${
|
|
823
|
-
<strong class="absolute-voice-provider-status__label">${
|
|
1123
|
+
<span class="absolute-voice-provider-status__eyebrow">${escapeHtml4(model.title)}</span>
|
|
1124
|
+
<strong class="absolute-voice-provider-status__label">${escapeHtml4(model.label)}</strong>
|
|
824
1125
|
</header>
|
|
825
|
-
<p class="absolute-voice-provider-status__description">${
|
|
1126
|
+
<p class="absolute-voice-provider-status__description">${escapeHtml4(model.description)}</p>
|
|
826
1127
|
${providers}
|
|
827
|
-
${model.error ? `<p class="absolute-voice-provider-status__error">${
|
|
1128
|
+
${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml4(model.error)}</p>` : ""}
|
|
828
1129
|
</section>`;
|
|
829
1130
|
};
|
|
830
1131
|
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}`;
|
|
@@ -866,12 +1167,12 @@ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-statu
|
|
|
866
1167
|
};
|
|
867
1168
|
|
|
868
1169
|
// src/vue/useVoiceProviderStatus.ts
|
|
869
|
-
import { onUnmounted as
|
|
1170
|
+
import { onUnmounted as onUnmounted4, ref as ref3, shallowRef as shallowRef3 } from "vue";
|
|
870
1171
|
var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
|
|
871
1172
|
const store = createVoiceProviderStatusStore(path, options);
|
|
872
1173
|
const error = ref3(null);
|
|
873
1174
|
const isLoading = ref3(false);
|
|
874
|
-
const providers =
|
|
1175
|
+
const providers = shallowRef3([]);
|
|
875
1176
|
const updatedAt = ref3(undefined);
|
|
876
1177
|
const sync = () => {
|
|
877
1178
|
const snapshot = store.getSnapshot();
|
|
@@ -883,7 +1184,7 @@ var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
|
|
|
883
1184
|
const unsubscribe = store.subscribe(sync);
|
|
884
1185
|
sync();
|
|
885
1186
|
store.refresh().catch(() => {});
|
|
886
|
-
|
|
1187
|
+
onUnmounted4(() => {
|
|
887
1188
|
unsubscribe();
|
|
888
1189
|
store.close();
|
|
889
1190
|
});
|
|
@@ -897,7 +1198,7 @@ var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
|
|
|
897
1198
|
};
|
|
898
1199
|
|
|
899
1200
|
// src/vue/VoiceProviderStatus.ts
|
|
900
|
-
var VoiceProviderStatus =
|
|
1201
|
+
var VoiceProviderStatus = defineComponent4({
|
|
901
1202
|
name: "VoiceProviderStatus",
|
|
902
1203
|
props: {
|
|
903
1204
|
class: {
|
|
@@ -928,47 +1229,47 @@ var VoiceProviderStatus = defineComponent3({
|
|
|
928
1229
|
title: props.title
|
|
929
1230
|
};
|
|
930
1231
|
const status = useVoiceProviderStatus(props.path, options);
|
|
931
|
-
const model =
|
|
1232
|
+
const model = computed3(() => createVoiceProviderStatusViewModel({
|
|
932
1233
|
error: status.error.value,
|
|
933
1234
|
isLoading: status.isLoading.value,
|
|
934
1235
|
providers: status.providers.value,
|
|
935
1236
|
updatedAt: status.updatedAt.value
|
|
936
1237
|
}, options));
|
|
937
|
-
return () =>
|
|
1238
|
+
return () => h4("section", {
|
|
938
1239
|
class: [
|
|
939
1240
|
"absolute-voice-provider-status",
|
|
940
1241
|
`absolute-voice-provider-status--${model.value.status}`,
|
|
941
1242
|
props.class
|
|
942
1243
|
]
|
|
943
1244
|
}, [
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1245
|
+
h4("header", { class: "absolute-voice-provider-status__header" }, [
|
|
1246
|
+
h4("span", { class: "absolute-voice-provider-status__eyebrow" }, model.value.title),
|
|
1247
|
+
h4("strong", { class: "absolute-voice-provider-status__label" }, model.value.label)
|
|
947
1248
|
]),
|
|
948
|
-
|
|
949
|
-
model.value.providers.length ?
|
|
1249
|
+
h4("p", { class: "absolute-voice-provider-status__description" }, model.value.description),
|
|
1250
|
+
model.value.providers.length ? h4("div", { class: "absolute-voice-provider-status__providers" }, model.value.providers.map((provider) => h4("article", {
|
|
950
1251
|
class: [
|
|
951
1252
|
"absolute-voice-provider-status__provider",
|
|
952
1253
|
`absolute-voice-provider-status__provider--${provider.status}`
|
|
953
1254
|
],
|
|
954
1255
|
key: provider.provider
|
|
955
1256
|
}, [
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1257
|
+
h4("header", [
|
|
1258
|
+
h4("strong", provider.label),
|
|
1259
|
+
h4("span", provider.status)
|
|
959
1260
|
]),
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1261
|
+
h4("p", provider.detail),
|
|
1262
|
+
h4("dl", provider.rows.map((row) => h4("div", { key: row.label }, [
|
|
1263
|
+
h4("dt", row.label),
|
|
1264
|
+
h4("dd", row.value)
|
|
964
1265
|
])))
|
|
965
|
-
]))) :
|
|
966
|
-
model.value.error ?
|
|
1266
|
+
]))) : h4("p", { class: "absolute-voice-provider-status__empty" }, "Run voice traffic to see provider health."),
|
|
1267
|
+
model.value.error ? h4("p", { class: "absolute-voice-provider-status__error" }, model.value.error) : null
|
|
967
1268
|
]);
|
|
968
1269
|
}
|
|
969
1270
|
});
|
|
970
1271
|
// src/vue/VoiceRoutingStatus.ts
|
|
971
|
-
import { computed as
|
|
1272
|
+
import { computed as computed4, defineComponent as defineComponent5, h as h5 } from "vue";
|
|
972
1273
|
|
|
973
1274
|
// src/client/routingStatus.ts
|
|
974
1275
|
var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
|
|
@@ -1051,9 +1352,9 @@ var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {})
|
|
|
1051
1352
|
};
|
|
1052
1353
|
|
|
1053
1354
|
// src/client/routingStatusWidget.ts
|
|
1054
|
-
var
|
|
1055
|
-
var
|
|
1056
|
-
var
|
|
1355
|
+
var DEFAULT_TITLE4 = "Voice Routing";
|
|
1356
|
+
var DEFAULT_DESCRIPTION4 = "Latest provider routing decision from the self-hosted trace store.";
|
|
1357
|
+
var escapeHtml5 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1057
1358
|
var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
|
|
1058
1359
|
var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
|
|
1059
1360
|
const decision = snapshot.decision;
|
|
@@ -1077,30 +1378,30 @@ var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
|
|
|
1077
1378
|
] : [];
|
|
1078
1379
|
return {
|
|
1079
1380
|
decision,
|
|
1080
|
-
description: options.description ??
|
|
1381
|
+
description: options.description ?? DEFAULT_DESCRIPTION4,
|
|
1081
1382
|
error: snapshot.error,
|
|
1082
1383
|
isLoading: snapshot.isLoading,
|
|
1083
1384
|
label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
|
|
1084
1385
|
rows,
|
|
1085
1386
|
status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
1086
|
-
title: options.title ??
|
|
1387
|
+
title: options.title ?? DEFAULT_TITLE4,
|
|
1087
1388
|
updatedAt: snapshot.updatedAt
|
|
1088
1389
|
};
|
|
1089
1390
|
};
|
|
1090
1391
|
var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
|
|
1091
1392
|
const model = createVoiceRoutingStatusViewModel(snapshot, options);
|
|
1092
1393
|
const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
|
|
1093
|
-
<span>${
|
|
1094
|
-
<strong>${
|
|
1394
|
+
<span>${escapeHtml5(row.label)}</span>
|
|
1395
|
+
<strong>${escapeHtml5(row.value)}</strong>
|
|
1095
1396
|
</div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
|
|
1096
|
-
return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${
|
|
1397
|
+
return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml5(model.status)}">
|
|
1097
1398
|
<header class="absolute-voice-routing-status__header">
|
|
1098
|
-
<span class="absolute-voice-routing-status__eyebrow">${
|
|
1099
|
-
<strong class="absolute-voice-routing-status__label">${
|
|
1399
|
+
<span class="absolute-voice-routing-status__eyebrow">${escapeHtml5(model.title)}</span>
|
|
1400
|
+
<strong class="absolute-voice-routing-status__label">${escapeHtml5(model.label)}</strong>
|
|
1100
1401
|
</header>
|
|
1101
|
-
<p class="absolute-voice-routing-status__description">${
|
|
1402
|
+
<p class="absolute-voice-routing-status__description">${escapeHtml5(model.description)}</p>
|
|
1102
1403
|
${rows}
|
|
1103
|
-
${model.error ? `<p class="absolute-voice-routing-status__error">${
|
|
1404
|
+
${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml5(model.error)}</p>` : ""}
|
|
1104
1405
|
</section>`;
|
|
1105
1406
|
};
|
|
1106
1407
|
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}`;
|
|
@@ -1142,10 +1443,10 @@ var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status"
|
|
|
1142
1443
|
};
|
|
1143
1444
|
|
|
1144
1445
|
// src/vue/useVoiceRoutingStatus.ts
|
|
1145
|
-
import { onUnmounted as
|
|
1446
|
+
import { onUnmounted as onUnmounted5, ref as ref4, shallowRef as shallowRef4 } from "vue";
|
|
1146
1447
|
var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
|
|
1147
1448
|
const store = createVoiceRoutingStatusStore(path, options);
|
|
1148
|
-
const decision =
|
|
1449
|
+
const decision = shallowRef4(null);
|
|
1149
1450
|
const error = ref4(null);
|
|
1150
1451
|
const isLoading = ref4(false);
|
|
1151
1452
|
const updatedAt = ref4(undefined);
|
|
@@ -1159,7 +1460,7 @@ var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
|
|
|
1159
1460
|
const unsubscribe = store.subscribe(sync);
|
|
1160
1461
|
sync();
|
|
1161
1462
|
store.refresh().catch(() => {});
|
|
1162
|
-
|
|
1463
|
+
onUnmounted5(() => {
|
|
1163
1464
|
unsubscribe();
|
|
1164
1465
|
store.close();
|
|
1165
1466
|
});
|
|
@@ -1173,7 +1474,7 @@ var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
|
|
|
1173
1474
|
};
|
|
1174
1475
|
|
|
1175
1476
|
// src/vue/VoiceRoutingStatus.ts
|
|
1176
|
-
var VoiceRoutingStatus =
|
|
1477
|
+
var VoiceRoutingStatus = defineComponent5({
|
|
1177
1478
|
name: "VoiceRoutingStatus",
|
|
1178
1479
|
props: {
|
|
1179
1480
|
class: {
|
|
@@ -1204,34 +1505,309 @@ var VoiceRoutingStatus = defineComponent4({
|
|
|
1204
1505
|
title: props.title
|
|
1205
1506
|
};
|
|
1206
1507
|
const status = useVoiceRoutingStatus(props.path, options);
|
|
1207
|
-
const model =
|
|
1508
|
+
const model = computed4(() => createVoiceRoutingStatusViewModel({
|
|
1208
1509
|
decision: status.decision.value,
|
|
1209
1510
|
error: status.error.value,
|
|
1210
1511
|
isLoading: status.isLoading.value,
|
|
1211
1512
|
updatedAt: status.updatedAt.value
|
|
1212
1513
|
}, options));
|
|
1213
|
-
return () =>
|
|
1514
|
+
return () => h5("section", {
|
|
1214
1515
|
class: [
|
|
1215
1516
|
"absolute-voice-routing-status",
|
|
1216
1517
|
`absolute-voice-routing-status--${model.value.status}`,
|
|
1217
1518
|
props.class
|
|
1218
1519
|
]
|
|
1219
1520
|
}, [
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1521
|
+
h5("header", { class: "absolute-voice-routing-status__header" }, [
|
|
1522
|
+
h5("span", { class: "absolute-voice-routing-status__eyebrow" }, model.value.title),
|
|
1523
|
+
h5("strong", { class: "absolute-voice-routing-status__label" }, model.value.label)
|
|
1223
1524
|
]),
|
|
1224
|
-
|
|
1225
|
-
model.value.rows.length ?
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
]))) :
|
|
1229
|
-
model.value.error ?
|
|
1525
|
+
h5("p", { class: "absolute-voice-routing-status__description" }, model.value.description),
|
|
1526
|
+
model.value.rows.length ? h5("div", { class: "absolute-voice-routing-status__grid" }, model.value.rows.map((row) => h5("div", { key: row.label }, [
|
|
1527
|
+
h5("span", row.label),
|
|
1528
|
+
h5("strong", row.value)
|
|
1529
|
+
]))) : h5("p", { class: "absolute-voice-routing-status__empty" }, "Start a voice session to see the selected provider."),
|
|
1530
|
+
model.value.error ? h5("p", { class: "absolute-voice-routing-status__error" }, model.value.error) : null
|
|
1531
|
+
]);
|
|
1532
|
+
}
|
|
1533
|
+
});
|
|
1534
|
+
// src/vue/VoiceTurnQuality.ts
|
|
1535
|
+
import { computed as computed5, defineComponent as defineComponent6, h as h6 } from "vue";
|
|
1536
|
+
|
|
1537
|
+
// src/client/turnQuality.ts
|
|
1538
|
+
var fetchVoiceTurnQuality = async (path = "/api/turn-quality", options = {}) => {
|
|
1539
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1540
|
+
const response = await fetchImpl(path);
|
|
1541
|
+
if (!response.ok) {
|
|
1542
|
+
throw new Error(`Voice turn quality failed: HTTP ${response.status}`);
|
|
1543
|
+
}
|
|
1544
|
+
return await response.json();
|
|
1545
|
+
};
|
|
1546
|
+
var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) => {
|
|
1547
|
+
const listeners = new Set;
|
|
1548
|
+
let closed = false;
|
|
1549
|
+
let timer;
|
|
1550
|
+
let snapshot = {
|
|
1551
|
+
error: null,
|
|
1552
|
+
isLoading: false
|
|
1553
|
+
};
|
|
1554
|
+
const emit = () => {
|
|
1555
|
+
for (const listener of listeners) {
|
|
1556
|
+
listener();
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
const refresh = async () => {
|
|
1560
|
+
if (closed) {
|
|
1561
|
+
return snapshot.report;
|
|
1562
|
+
}
|
|
1563
|
+
snapshot = {
|
|
1564
|
+
...snapshot,
|
|
1565
|
+
error: null,
|
|
1566
|
+
isLoading: true
|
|
1567
|
+
};
|
|
1568
|
+
emit();
|
|
1569
|
+
try {
|
|
1570
|
+
const report = await fetchVoiceTurnQuality(path, options);
|
|
1571
|
+
snapshot = {
|
|
1572
|
+
error: null,
|
|
1573
|
+
isLoading: false,
|
|
1574
|
+
report,
|
|
1575
|
+
updatedAt: Date.now()
|
|
1576
|
+
};
|
|
1577
|
+
emit();
|
|
1578
|
+
return report;
|
|
1579
|
+
} catch (error) {
|
|
1580
|
+
snapshot = {
|
|
1581
|
+
...snapshot,
|
|
1582
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1583
|
+
isLoading: false
|
|
1584
|
+
};
|
|
1585
|
+
emit();
|
|
1586
|
+
throw error;
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
const close = () => {
|
|
1590
|
+
closed = true;
|
|
1591
|
+
if (timer) {
|
|
1592
|
+
clearInterval(timer);
|
|
1593
|
+
timer = undefined;
|
|
1594
|
+
}
|
|
1595
|
+
listeners.clear();
|
|
1596
|
+
};
|
|
1597
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
1598
|
+
timer = setInterval(() => {
|
|
1599
|
+
refresh().catch(() => {});
|
|
1600
|
+
}, options.intervalMs);
|
|
1601
|
+
}
|
|
1602
|
+
return {
|
|
1603
|
+
close,
|
|
1604
|
+
getServerSnapshot: () => snapshot,
|
|
1605
|
+
getSnapshot: () => snapshot,
|
|
1606
|
+
refresh,
|
|
1607
|
+
subscribe: (listener) => {
|
|
1608
|
+
listeners.add(listener);
|
|
1609
|
+
return () => {
|
|
1610
|
+
listeners.delete(listener);
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
};
|
|
1615
|
+
|
|
1616
|
+
// src/client/turnQualityWidget.ts
|
|
1617
|
+
var DEFAULT_TITLE5 = "Turn Quality";
|
|
1618
|
+
var DEFAULT_DESCRIPTION5 = "Per-turn STT confidence, fallback selection, corrections, and transcript coverage.";
|
|
1619
|
+
var escapeHtml6 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1620
|
+
var formatConfidence = (value) => typeof value === "number" ? `${Math.round(value * 100)}%` : "n/a";
|
|
1621
|
+
var formatMaybe = (value) => value === undefined || value === "" ? "n/a" : String(value);
|
|
1622
|
+
var getTurnDetail = (turn) => {
|
|
1623
|
+
if (turn.status === "fail") {
|
|
1624
|
+
return "Empty or unusable committed turn; inspect transcripts and adapter events.";
|
|
1625
|
+
}
|
|
1626
|
+
if (turn.fallbackUsed) {
|
|
1627
|
+
return `Fallback STT selected${turn.fallbackSelectionReason ? ` by ${turn.fallbackSelectionReason}` : ""}.`;
|
|
1628
|
+
}
|
|
1629
|
+
if (turn.correctionChanged) {
|
|
1630
|
+
return `Correction changed the turn${turn.correctionProvider ? ` via ${turn.correctionProvider}` : ""}.`;
|
|
1631
|
+
}
|
|
1632
|
+
if (turn.status === "warn") {
|
|
1633
|
+
return "Turn completed with quality warnings.";
|
|
1634
|
+
}
|
|
1635
|
+
if (turn.status === "unknown") {
|
|
1636
|
+
return "No quality diagnostics were recorded for this turn.";
|
|
1637
|
+
}
|
|
1638
|
+
return "Turn quality looks healthy.";
|
|
1639
|
+
};
|
|
1640
|
+
var createVoiceTurnQualityViewModel = (snapshot, options = {}) => {
|
|
1641
|
+
const turns = (snapshot.report?.turns ?? []).map((turn) => ({
|
|
1642
|
+
...turn,
|
|
1643
|
+
detail: getTurnDetail(turn),
|
|
1644
|
+
label: turn.text || "Empty turn",
|
|
1645
|
+
rows: [
|
|
1646
|
+
{ label: "Source", value: turn.source ?? "unknown" },
|
|
1647
|
+
{ label: "Confidence", value: formatConfidence(turn.averageConfidence) },
|
|
1648
|
+
{ label: "Fallback", value: turn.fallbackUsed ? "Yes" : "No" },
|
|
1649
|
+
{ label: "Correction", value: turn.correctionChanged ? "Changed" : "None" },
|
|
1650
|
+
{ label: "Transcripts", value: `${turn.selectedTranscriptCount} selected` },
|
|
1651
|
+
{ label: "Cost", value: formatMaybe(turn.costUnits) }
|
|
1652
|
+
]
|
|
1653
|
+
}));
|
|
1654
|
+
const warningCount = snapshot.report?.warnings ?? turns.filter((turn) => turn.status === "warn").length;
|
|
1655
|
+
const failedCount = snapshot.report?.failed ?? turns.filter((turn) => turn.status === "fail").length;
|
|
1656
|
+
return {
|
|
1657
|
+
description: options.description ?? DEFAULT_DESCRIPTION5,
|
|
1658
|
+
error: snapshot.error,
|
|
1659
|
+
isLoading: snapshot.isLoading,
|
|
1660
|
+
label: snapshot.error ? "Unavailable" : turns.length ? failedCount > 0 ? `${failedCount} failed` : warningCount > 0 ? `${warningCount} warnings` : `${turns.length} healthy` : snapshot.isLoading ? "Checking" : "No turns",
|
|
1661
|
+
status: snapshot.error ? "error" : turns.length ? failedCount > 0 || warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
1662
|
+
title: options.title ?? DEFAULT_TITLE5,
|
|
1663
|
+
turns,
|
|
1664
|
+
updatedAt: snapshot.updatedAt
|
|
1665
|
+
};
|
|
1666
|
+
};
|
|
1667
|
+
var renderVoiceTurnQualityHTML = (snapshot, options = {}) => {
|
|
1668
|
+
const model = createVoiceTurnQualityViewModel(snapshot, options);
|
|
1669
|
+
const turns = model.turns.length ? `<div class="absolute-voice-turn-quality__turns">${model.turns.map((turn) => `<article class="absolute-voice-turn-quality__turn absolute-voice-turn-quality__turn--${escapeHtml6(turn.status)}">
|
|
1670
|
+
<header>
|
|
1671
|
+
<strong>${escapeHtml6(turn.label)}</strong>
|
|
1672
|
+
<span>${escapeHtml6(turn.status)}</span>
|
|
1673
|
+
</header>
|
|
1674
|
+
<p>${escapeHtml6(turn.detail)}</p>
|
|
1675
|
+
<dl>${turn.rows.map((row) => `<div>
|
|
1676
|
+
<dt>${escapeHtml6(row.label)}</dt>
|
|
1677
|
+
<dd>${escapeHtml6(row.value)}</dd>
|
|
1678
|
+
</div>`).join("")}</dl>
|
|
1679
|
+
</article>`).join("")}</div>` : '<p class="absolute-voice-turn-quality__empty">Complete a voice turn to see STT quality diagnostics.</p>';
|
|
1680
|
+
return `<section class="absolute-voice-turn-quality absolute-voice-turn-quality--${escapeHtml6(model.status)}">
|
|
1681
|
+
<header class="absolute-voice-turn-quality__header">
|
|
1682
|
+
<span class="absolute-voice-turn-quality__eyebrow">${escapeHtml6(model.title)}</span>
|
|
1683
|
+
<strong class="absolute-voice-turn-quality__label">${escapeHtml6(model.label)}</strong>
|
|
1684
|
+
</header>
|
|
1685
|
+
<p class="absolute-voice-turn-quality__description">${escapeHtml6(model.description)}</p>
|
|
1686
|
+
${turns}
|
|
1687
|
+
${model.error ? `<p class="absolute-voice-turn-quality__error">${escapeHtml6(model.error)}</p>` : ""}
|
|
1688
|
+
</section>`;
|
|
1689
|
+
};
|
|
1690
|
+
var getVoiceTurnQualityCSS = () => `.absolute-voice-turn-quality{border:1px solid #e4d1a3;border-radius:20px;background:#fff9eb;color:#17120a;padding:18px;box-shadow:0 18px 40px rgba(73,48,14,.12);font-family:inherit}.absolute-voice-turn-quality--error,.absolute-voice-turn-quality--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-turn-quality__header,.absolute-voice-turn-quality__turn header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-turn-quality__eyebrow{color:#8a5a0a;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-turn-quality__label{font-size:24px;line-height:1}.absolute-voice-turn-quality__description,.absolute-voice-turn-quality__turn p,.absolute-voice-turn-quality__turn dt,.absolute-voice-turn-quality__empty{color:#5a4930}.absolute-voice-turn-quality__turns{display:grid;gap:12px;margin-top:14px}.absolute-voice-turn-quality__turn{background:#fff;border:1px solid #f0dfba;border-radius:16px;padding:14px}.absolute-voice-turn-quality__turn--pass{border-color:#86efac}.absolute-voice-turn-quality__turn--warn,.absolute-voice-turn-quality__turn--unknown{border-color:#fbbf24}.absolute-voice-turn-quality__turn--fail{border-color:#f2a7a7}.absolute-voice-turn-quality__turn p{margin:10px 0}.absolute-voice-turn-quality__turn dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-turn-quality__turn div{background:#fff9eb;border:1px solid #f0dfba;border-radius:12px;padding:8px}.absolute-voice-turn-quality__turn dt{font-size:12px}.absolute-voice-turn-quality__turn dd{font-weight:800;margin:4px 0 0}.absolute-voice-turn-quality__empty{margin:14px 0 0}.absolute-voice-turn-quality__error{color:#9f1239;font-weight:700}`;
|
|
1691
|
+
var mountVoiceTurnQuality = (element, path = "/api/turn-quality", options = {}) => {
|
|
1692
|
+
const store = createVoiceTurnQualityStore(path, options);
|
|
1693
|
+
const render = () => {
|
|
1694
|
+
element.innerHTML = renderVoiceTurnQualityHTML(store.getSnapshot(), options);
|
|
1695
|
+
};
|
|
1696
|
+
const unsubscribe = store.subscribe(render);
|
|
1697
|
+
render();
|
|
1698
|
+
store.refresh().catch(() => {});
|
|
1699
|
+
return {
|
|
1700
|
+
close: () => {
|
|
1701
|
+
unsubscribe();
|
|
1702
|
+
store.close();
|
|
1703
|
+
},
|
|
1704
|
+
refresh: store.refresh
|
|
1705
|
+
};
|
|
1706
|
+
};
|
|
1707
|
+
var defineVoiceTurnQualityElement = (tagName = "absolute-voice-turn-quality") => {
|
|
1708
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
customElements.define(tagName, class AbsoluteVoiceTurnQualityElement extends HTMLElement {
|
|
1712
|
+
mounted;
|
|
1713
|
+
connectedCallback() {
|
|
1714
|
+
const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
|
|
1715
|
+
this.mounted = mountVoiceTurnQuality(this, this.getAttribute("path") ?? "/api/turn-quality", {
|
|
1716
|
+
description: this.getAttribute("description") ?? undefined,
|
|
1717
|
+
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
1718
|
+
title: this.getAttribute("title") ?? undefined
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
disconnectedCallback() {
|
|
1722
|
+
this.mounted?.close();
|
|
1723
|
+
this.mounted = undefined;
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
1726
|
+
};
|
|
1727
|
+
|
|
1728
|
+
// src/vue/useVoiceTurnQuality.ts
|
|
1729
|
+
import { onUnmounted as onUnmounted6, shallowRef as shallowRef5 } from "vue";
|
|
1730
|
+
var useVoiceTurnQuality = (path = "/api/turn-quality", options = {}) => {
|
|
1731
|
+
const store = createVoiceTurnQualityStore(path, options);
|
|
1732
|
+
const error = shallowRef5(null);
|
|
1733
|
+
const isLoading = shallowRef5(false);
|
|
1734
|
+
const report = shallowRef5();
|
|
1735
|
+
const updatedAt = shallowRef5(undefined);
|
|
1736
|
+
const sync = () => {
|
|
1737
|
+
const snapshot = store.getSnapshot();
|
|
1738
|
+
error.value = snapshot.error;
|
|
1739
|
+
isLoading.value = snapshot.isLoading;
|
|
1740
|
+
report.value = snapshot.report;
|
|
1741
|
+
updatedAt.value = snapshot.updatedAt;
|
|
1742
|
+
};
|
|
1743
|
+
const unsubscribe = store.subscribe(sync);
|
|
1744
|
+
sync();
|
|
1745
|
+
store.refresh().catch(() => {});
|
|
1746
|
+
onUnmounted6(() => {
|
|
1747
|
+
unsubscribe();
|
|
1748
|
+
store.close();
|
|
1749
|
+
});
|
|
1750
|
+
return { error, isLoading, refresh: store.refresh, report, updatedAt };
|
|
1751
|
+
};
|
|
1752
|
+
|
|
1753
|
+
// src/vue/VoiceTurnQuality.ts
|
|
1754
|
+
var VoiceTurnQuality = defineComponent6({
|
|
1755
|
+
name: "VoiceTurnQuality",
|
|
1756
|
+
props: {
|
|
1757
|
+
class: { default: "", type: String },
|
|
1758
|
+
description: { default: undefined, type: String },
|
|
1759
|
+
intervalMs: { default: 5000, type: Number },
|
|
1760
|
+
path: { default: "/api/turn-quality", type: String },
|
|
1761
|
+
title: { default: undefined, type: String }
|
|
1762
|
+
},
|
|
1763
|
+
setup(props) {
|
|
1764
|
+
const options = {
|
|
1765
|
+
description: props.description,
|
|
1766
|
+
intervalMs: props.intervalMs,
|
|
1767
|
+
title: props.title
|
|
1768
|
+
};
|
|
1769
|
+
const quality = useVoiceTurnQuality(props.path, options);
|
|
1770
|
+
const model = computed5(() => createVoiceTurnQualityViewModel({
|
|
1771
|
+
error: quality.error.value,
|
|
1772
|
+
isLoading: quality.isLoading.value,
|
|
1773
|
+
report: quality.report.value,
|
|
1774
|
+
updatedAt: quality.updatedAt.value
|
|
1775
|
+
}, options));
|
|
1776
|
+
return () => h6("section", {
|
|
1777
|
+
class: [
|
|
1778
|
+
"absolute-voice-turn-quality",
|
|
1779
|
+
`absolute-voice-turn-quality--${model.value.status}`,
|
|
1780
|
+
props.class
|
|
1781
|
+
]
|
|
1782
|
+
}, [
|
|
1783
|
+
h6("header", { class: "absolute-voice-turn-quality__header" }, [
|
|
1784
|
+
h6("span", { class: "absolute-voice-turn-quality__eyebrow" }, model.value.title),
|
|
1785
|
+
h6("strong", { class: "absolute-voice-turn-quality__label" }, model.value.label)
|
|
1786
|
+
]),
|
|
1787
|
+
h6("p", { class: "absolute-voice-turn-quality__description" }, model.value.description),
|
|
1788
|
+
model.value.turns.length ? h6("div", { class: "absolute-voice-turn-quality__turns" }, model.value.turns.map((turn) => h6("article", {
|
|
1789
|
+
class: [
|
|
1790
|
+
"absolute-voice-turn-quality__turn",
|
|
1791
|
+
`absolute-voice-turn-quality__turn--${turn.status}`
|
|
1792
|
+
],
|
|
1793
|
+
key: `${turn.sessionId}:${turn.turnId}`
|
|
1794
|
+
}, [
|
|
1795
|
+
h6("header", [
|
|
1796
|
+
h6("strong", turn.label),
|
|
1797
|
+
h6("span", turn.status)
|
|
1798
|
+
]),
|
|
1799
|
+
h6("p", turn.detail),
|
|
1800
|
+
h6("dl", turn.rows.map((row) => h6("div", { key: row.label }, [
|
|
1801
|
+
h6("dt", row.label),
|
|
1802
|
+
h6("dd", row.value)
|
|
1803
|
+
])))
|
|
1804
|
+
]))) : h6("p", { class: "absolute-voice-turn-quality__empty" }, "Complete a voice turn to see STT quality diagnostics."),
|
|
1805
|
+
model.value.error ? h6("p", { class: "absolute-voice-turn-quality__error" }, model.value.error) : null
|
|
1230
1806
|
]);
|
|
1231
1807
|
}
|
|
1232
1808
|
});
|
|
1233
1809
|
// src/vue/useVoiceStream.ts
|
|
1234
|
-
import { onUnmounted as
|
|
1810
|
+
import { onUnmounted as onUnmounted7, ref as ref5, shallowRef as shallowRef6 } from "vue";
|
|
1235
1811
|
|
|
1236
1812
|
// src/client/actions.ts
|
|
1237
1813
|
var normalizeErrorMessage = (value) => {
|
|
@@ -1749,15 +2325,15 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
1749
2325
|
// src/vue/useVoiceStream.ts
|
|
1750
2326
|
var useVoiceStream = (path, options = {}) => {
|
|
1751
2327
|
const stream = createVoiceStream(path, options);
|
|
1752
|
-
const assistantAudio =
|
|
1753
|
-
const assistantTexts =
|
|
1754
|
-
const call =
|
|
2328
|
+
const assistantAudio = shallowRef6([]);
|
|
2329
|
+
const assistantTexts = shallowRef6([]);
|
|
2330
|
+
const call = shallowRef6(null);
|
|
1755
2331
|
const error = ref5(null);
|
|
1756
2332
|
const isConnected = ref5(false);
|
|
1757
2333
|
const partial = ref5("");
|
|
1758
2334
|
const sessionId = ref5(stream.sessionId);
|
|
1759
2335
|
const status = ref5(stream.status);
|
|
1760
|
-
const turns =
|
|
2336
|
+
const turns = shallowRef6([]);
|
|
1761
2337
|
const sync = () => {
|
|
1762
2338
|
assistantAudio.value = [...stream.assistantAudio];
|
|
1763
2339
|
assistantTexts.value = [...stream.assistantTexts];
|
|
@@ -1775,7 +2351,7 @@ var useVoiceStream = (path, options = {}) => {
|
|
|
1775
2351
|
unsubscribe();
|
|
1776
2352
|
stream.close();
|
|
1777
2353
|
};
|
|
1778
|
-
|
|
2354
|
+
onUnmounted7(destroy);
|
|
1779
2355
|
return {
|
|
1780
2356
|
assistantAudio,
|
|
1781
2357
|
assistantTexts,
|
|
@@ -1793,7 +2369,7 @@ var useVoiceStream = (path, options = {}) => {
|
|
|
1793
2369
|
};
|
|
1794
2370
|
};
|
|
1795
2371
|
// src/vue/useVoiceController.ts
|
|
1796
|
-
import { onUnmounted as
|
|
2372
|
+
import { onUnmounted as onUnmounted8, ref as ref6, shallowRef as shallowRef7 } from "vue";
|
|
1797
2373
|
|
|
1798
2374
|
// src/client/htmx.ts
|
|
1799
2375
|
var DEFAULT_EVENT_NAME = "voice-refresh";
|
|
@@ -2428,8 +3004,8 @@ var createVoiceController = (path, options = {}) => {
|
|
|
2428
3004
|
// src/vue/useVoiceController.ts
|
|
2429
3005
|
var useVoiceController = (path, options = {}) => {
|
|
2430
3006
|
const controller = createVoiceController(path, options);
|
|
2431
|
-
const assistantAudio =
|
|
2432
|
-
const assistantTexts =
|
|
3007
|
+
const assistantAudio = shallowRef7([]);
|
|
3008
|
+
const assistantTexts = shallowRef7([]);
|
|
2433
3009
|
const error = ref6(null);
|
|
2434
3010
|
const isConnected = ref6(false);
|
|
2435
3011
|
const isRecording = ref6(false);
|
|
@@ -2437,7 +3013,7 @@ var useVoiceController = (path, options = {}) => {
|
|
|
2437
3013
|
const recordingError = ref6(null);
|
|
2438
3014
|
const sessionId = ref6(controller.sessionId);
|
|
2439
3015
|
const status = ref6(controller.status);
|
|
2440
|
-
const turns =
|
|
3016
|
+
const turns = shallowRef7([]);
|
|
2441
3017
|
const sync = () => {
|
|
2442
3018
|
assistantAudio.value = [...controller.assistantAudio];
|
|
2443
3019
|
assistantTexts.value = [...controller.assistantTexts];
|
|
@@ -2456,7 +3032,7 @@ var useVoiceController = (path, options = {}) => {
|
|
|
2456
3032
|
unsubscribe();
|
|
2457
3033
|
controller.close();
|
|
2458
3034
|
};
|
|
2459
|
-
|
|
3035
|
+
onUnmounted8(destroy);
|
|
2460
3036
|
return {
|
|
2461
3037
|
assistantAudio,
|
|
2462
3038
|
assistantTexts,
|
|
@@ -2478,7 +3054,7 @@ var useVoiceController = (path, options = {}) => {
|
|
|
2478
3054
|
};
|
|
2479
3055
|
};
|
|
2480
3056
|
// src/vue/useVoiceWorkflowStatus.ts
|
|
2481
|
-
import { onUnmounted as
|
|
3057
|
+
import { onUnmounted as onUnmounted9, ref as ref7, shallowRef as shallowRef8 } from "vue";
|
|
2482
3058
|
|
|
2483
3059
|
// src/client/workflowStatus.ts
|
|
2484
3060
|
var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
|
|
@@ -2564,7 +3140,7 @@ var useVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => {
|
|
|
2564
3140
|
const store = createVoiceWorkflowStatusStore(path, options);
|
|
2565
3141
|
const error = ref7(null);
|
|
2566
3142
|
const isLoading = ref7(false);
|
|
2567
|
-
const report =
|
|
3143
|
+
const report = shallowRef8(undefined);
|
|
2568
3144
|
const updatedAt = ref7(undefined);
|
|
2569
3145
|
const sync = () => {
|
|
2570
3146
|
const snapshot = store.getSnapshot();
|
|
@@ -2578,7 +3154,7 @@ var useVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => {
|
|
|
2578
3154
|
if (typeof window !== "undefined") {
|
|
2579
3155
|
store.refresh().catch(() => {});
|
|
2580
3156
|
}
|
|
2581
|
-
|
|
3157
|
+
onUnmounted9(() => {
|
|
2582
3158
|
unsubscribe();
|
|
2583
3159
|
store.close();
|
|
2584
3160
|
});
|
|
@@ -2592,14 +3168,18 @@ var useVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => {
|
|
|
2592
3168
|
};
|
|
2593
3169
|
export {
|
|
2594
3170
|
useVoiceWorkflowStatus,
|
|
3171
|
+
useVoiceTurnQuality,
|
|
2595
3172
|
useVoiceStream,
|
|
2596
3173
|
useVoiceRoutingStatus,
|
|
2597
3174
|
useVoiceProviderStatus,
|
|
2598
3175
|
useVoiceProviderSimulationControls,
|
|
3176
|
+
useVoiceProviderCapabilities,
|
|
2599
3177
|
useVoiceController,
|
|
2600
3178
|
useVoiceAppKitStatus,
|
|
3179
|
+
VoiceTurnQuality,
|
|
2601
3180
|
VoiceRoutingStatus,
|
|
2602
3181
|
VoiceProviderStatus,
|
|
2603
3182
|
VoiceProviderSimulationControls,
|
|
3183
|
+
VoiceProviderCapabilities,
|
|
2604
3184
|
VoiceOpsStatus
|
|
2605
3185
|
};
|