@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.
@@ -10,10 +10,12 @@ export { createVoiceOpsStatusViewModel, defineVoiceOpsStatusElement, getVoiceOps
10
10
  export { createVoiceRoutingStatusStore, fetchVoiceRoutingStatus } from './routingStatus';
11
11
  export { createVoiceRoutingStatusViewModel, defineVoiceRoutingStatusElement, getVoiceRoutingStatusCSS, mountVoiceRoutingStatus, renderVoiceRoutingStatusHTML } from './routingStatusWidget';
12
12
  export { createVoiceProviderStatusStore, fetchVoiceProviderStatus } from './providerStatus';
13
+ export { createVoiceProviderStatusViewModel, defineVoiceProviderStatusElement, getVoiceProviderStatusCSS, mountVoiceProviderStatus, renderVoiceProviderStatusHTML } from './providerStatusWidget';
13
14
  export { createVoiceWorkflowStatusStore, fetchVoiceWorkflowStatus } from './workflowStatus';
14
15
  export type { VoiceAppKitStatusClientOptions, VoiceAppKitStatusSnapshot } from './appKitStatus';
15
16
  export type { VoiceOpsStatusSurfaceView, VoiceOpsStatusViewModel, VoiceOpsStatusWidgetOptions } from './opsStatusWidget';
16
17
  export type { VoiceRoutingStatusClientOptions, VoiceRoutingStatusSnapshot } from './routingStatus';
17
18
  export type { VoiceRoutingStatusViewModel, VoiceRoutingStatusWidgetOptions } from './routingStatusWidget';
18
19
  export type { VoiceProviderStatusClientOptions, VoiceProviderStatusSnapshot } from './providerStatus';
20
+ export type { VoiceProviderStatusCardView, VoiceProviderStatusViewModel, VoiceProviderStatusWidgetOptions } from './providerStatusWidget';
19
21
  export type { VoiceWorkflowStatusClientOptions, VoiceWorkflowStatusSnapshot } from './workflowStatus';
@@ -2073,6 +2073,123 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
2073
2073
  }
2074
2074
  };
2075
2075
  };
2076
+ // src/client/providerStatusWidget.ts
2077
+ var DEFAULT_TITLE3 = "Voice Providers";
2078
+ var DEFAULT_DESCRIPTION3 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
2079
+ var escapeHtml3 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2080
+ var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
2081
+ var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
2082
+ var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
2083
+ var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
2084
+ var getProviderDetail = (provider) => {
2085
+ if (provider.status === "suppressed") {
2086
+ return provider.lastError ? `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)} after ${provider.lastError}.` : `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)}.`;
2087
+ }
2088
+ if (provider.status === "recoverable") {
2089
+ return "Cooldown expired; ready for recovery traffic.";
2090
+ }
2091
+ if (provider.status === "rate-limited") {
2092
+ return "Rate limit detected; router should avoid this provider.";
2093
+ }
2094
+ if (provider.status === "degraded") {
2095
+ return provider.lastError ?? "Recent provider errors detected.";
2096
+ }
2097
+ if (provider.status === "healthy") {
2098
+ return provider.recommended ? "Healthy and currently recommended." : "Healthy and available for routing.";
2099
+ }
2100
+ return "No provider traffic observed yet.";
2101
+ };
2102
+ var isWarningStatus = (status) => status === "degraded" || status === "rate-limited" || status === "recoverable" || status === "suppressed";
2103
+ var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
2104
+ const providers = snapshot.providers.map((provider) => ({
2105
+ ...provider,
2106
+ detail: getProviderDetail(provider),
2107
+ label: `${formatProvider(provider.provider)}${provider.recommended ? " recommended" : ""}`,
2108
+ rows: [
2109
+ { label: "Runs", value: String(provider.runCount) },
2110
+ { label: "Avg latency", value: formatLatency(provider.averageElapsedMs) },
2111
+ { label: "Errors", value: String(provider.errorCount) },
2112
+ { label: "Timeouts", value: String(provider.timeoutCount) },
2113
+ { label: "Fallbacks", value: String(provider.fallbackCount) },
2114
+ {
2115
+ label: "Suppression",
2116
+ value: formatSuppression(provider.suppressionRemainingMs)
2117
+ }
2118
+ ]
2119
+ }));
2120
+ const warningCount = providers.filter((provider) => isWarningStatus(provider.status)).length;
2121
+ const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
2122
+ return {
2123
+ description: options.description ?? DEFAULT_DESCRIPTION3,
2124
+ error: snapshot.error,
2125
+ isLoading: snapshot.isLoading,
2126
+ label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
2127
+ providers,
2128
+ status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
2129
+ title: options.title ?? DEFAULT_TITLE3,
2130
+ updatedAt: snapshot.updatedAt
2131
+ };
2132
+ };
2133
+ var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
2134
+ const model = createVoiceProviderStatusViewModel(snapshot, options);
2135
+ 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--${escapeHtml3(provider.status)}">
2136
+ <header>
2137
+ <strong>${escapeHtml3(provider.label)}</strong>
2138
+ <span>${escapeHtml3(formatStatus(provider.status))}</span>
2139
+ </header>
2140
+ <p>${escapeHtml3(provider.detail)}</p>
2141
+ <dl>${provider.rows.map((row) => `<div>
2142
+ <dt>${escapeHtml3(row.label)}</dt>
2143
+ <dd>${escapeHtml3(row.value)}</dd>
2144
+ </div>`).join("")}</dl>
2145
+ </article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
2146
+ return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml3(model.status)}">
2147
+ <header class="absolute-voice-provider-status__header">
2148
+ <span class="absolute-voice-provider-status__eyebrow">${escapeHtml3(model.title)}</span>
2149
+ <strong class="absolute-voice-provider-status__label">${escapeHtml3(model.label)}</strong>
2150
+ </header>
2151
+ <p class="absolute-voice-provider-status__description">${escapeHtml3(model.description)}</p>
2152
+ ${providers}
2153
+ ${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml3(model.error)}</p>` : ""}
2154
+ </section>`;
2155
+ };
2156
+ 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}`;
2157
+ var mountVoiceProviderStatus = (element, path = "/api/provider-status", options = {}) => {
2158
+ const store = createVoiceProviderStatusStore(path, options);
2159
+ const render = () => {
2160
+ element.innerHTML = renderVoiceProviderStatusHTML(store.getSnapshot(), options);
2161
+ };
2162
+ const unsubscribe = store.subscribe(render);
2163
+ render();
2164
+ store.refresh().catch(() => {});
2165
+ return {
2166
+ close: () => {
2167
+ unsubscribe();
2168
+ store.close();
2169
+ },
2170
+ refresh: store.refresh
2171
+ };
2172
+ };
2173
+ var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-status") => {
2174
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
2175
+ return;
2176
+ }
2177
+ customElements.define(tagName, class AbsoluteVoiceProviderStatusElement extends HTMLElement {
2178
+ mounted;
2179
+ connectedCallback() {
2180
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
2181
+ this.mounted = mountVoiceProviderStatus(this, this.getAttribute("path") ?? "/api/provider-status", {
2182
+ description: this.getAttribute("description") ?? undefined,
2183
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
2184
+ title: this.getAttribute("title") ?? undefined
2185
+ });
2186
+ }
2187
+ disconnectedCallback() {
2188
+ this.mounted?.close();
2189
+ this.mounted = undefined;
2190
+ }
2191
+ });
2192
+ };
2076
2193
  // src/client/workflowStatus.ts
2077
2194
  var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
2078
2195
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -2153,10 +2270,13 @@ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options =
2153
2270
  };
2154
2271
  export {
2155
2272
  renderVoiceRoutingStatusHTML,
2273
+ renderVoiceProviderStatusHTML,
2156
2274
  renderVoiceOpsStatusHTML,
2157
2275
  mountVoiceRoutingStatus,
2276
+ mountVoiceProviderStatus,
2158
2277
  mountVoiceOpsStatus,
2159
2278
  getVoiceRoutingStatusCSS,
2279
+ getVoiceProviderStatusCSS,
2160
2280
  getVoiceOpsStatusLabel,
2161
2281
  getVoiceOpsStatusCSS,
2162
2282
  fetchVoiceWorkflowStatus,
@@ -2164,12 +2284,14 @@ export {
2164
2284
  fetchVoiceProviderStatus,
2165
2285
  fetchVoiceAppKitStatus,
2166
2286
  defineVoiceRoutingStatusElement,
2287
+ defineVoiceProviderStatusElement,
2167
2288
  defineVoiceOpsStatusElement,
2168
2289
  decodeVoiceAudioChunk,
2169
2290
  createVoiceWorkflowStatusStore,
2170
2291
  createVoiceStream,
2171
2292
  createVoiceRoutingStatusViewModel,
2172
2293
  createVoiceRoutingStatusStore,
2294
+ createVoiceProviderStatusViewModel,
2173
2295
  createVoiceProviderStatusStore,
2174
2296
  createVoiceOpsStatusViewModel,
2175
2297
  createVoiceDuplexController,
@@ -0,0 +1,32 @@
1
+ import type { VoiceProviderHealthSummary } from '../providerHealth';
2
+ import { type VoiceProviderStatusClientOptions, type VoiceProviderStatusSnapshot } from './providerStatus';
3
+ export type VoiceProviderStatusCardView<TProvider extends string = string> = VoiceProviderHealthSummary<TProvider> & {
4
+ detail: string;
5
+ label: string;
6
+ rows: Array<{
7
+ label: string;
8
+ value: string;
9
+ }>;
10
+ };
11
+ export type VoiceProviderStatusViewModel<TProvider extends string = string> = {
12
+ description: string;
13
+ error: string | null;
14
+ isLoading: boolean;
15
+ label: string;
16
+ providers: VoiceProviderStatusCardView<TProvider>[];
17
+ status: 'empty' | 'error' | 'loading' | 'ready' | 'warning';
18
+ title: string;
19
+ updatedAt?: number;
20
+ };
21
+ export type VoiceProviderStatusWidgetOptions = VoiceProviderStatusClientOptions & {
22
+ description?: string;
23
+ title?: string;
24
+ };
25
+ export declare const createVoiceProviderStatusViewModel: <TProvider extends string = string>(snapshot: VoiceProviderStatusSnapshot<TProvider>, options?: VoiceProviderStatusWidgetOptions) => VoiceProviderStatusViewModel<TProvider>;
26
+ export declare const renderVoiceProviderStatusHTML: <TProvider extends string = string>(snapshot: VoiceProviderStatusSnapshot<TProvider>, options?: VoiceProviderStatusWidgetOptions) => string;
27
+ export declare const getVoiceProviderStatusCSS: () => string;
28
+ export declare const mountVoiceProviderStatus: <TProvider extends string = string>(element: Element, path?: string, options?: VoiceProviderStatusWidgetOptions) => {
29
+ close: () => void;
30
+ refresh: () => Promise<VoiceProviderHealthSummary<TProvider>[]>;
31
+ };
32
+ export declare const defineVoiceProviderStatusElement: (tagName?: string) => void;
@@ -0,0 +1,6 @@
1
+ import { type VoiceProviderStatusWidgetOptions } from '../client/providerStatusWidget';
2
+ export type VoiceProviderStatusProps = VoiceProviderStatusWidgetOptions & {
3
+ className?: string;
4
+ path?: string;
5
+ };
6
+ export declare const VoiceProviderStatus: ({ className, path, ...options }: VoiceProviderStatusProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,4 +1,5 @@
1
1
  export { VoiceOpsStatus } from './VoiceOpsStatus';
2
+ export { VoiceProviderStatus } from './VoiceProviderStatus';
2
3
  export { VoiceRoutingStatus } from './VoiceRoutingStatus';
3
4
  export { useVoiceAppKitStatus } from './useVoiceAppKitStatus';
4
5
  export { useVoiceStream } from './useVoiceStream';