@absolutejs/voice 0.0.22-beta.6 → 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/README.md +205 -0
- package/dist/angular/index.d.ts +4 -0
- package/dist/angular/index.js +587 -43
- package/dist/angular/voice-app-kit-status.service.d.ts +12 -0
- package/dist/angular/voice-ops-status.component.d.ts +15 -0
- package/dist/angular/voice-provider-status.service.d.ts +12 -0
- package/dist/angular/voice-routing-status.service.d.ts +11 -0
- package/dist/angular/voice-stream.service.d.ts +2 -0
- package/dist/angular/voice-workflow-status.service.d.ts +12 -0
- package/dist/appKit.d.ts +92 -0
- package/dist/assistantHealth.d.ts +81 -0
- package/dist/client/actions.d.ts +22 -0
- package/dist/client/appKitStatus.d.ts +19 -0
- package/dist/client/connection.d.ts +3 -0
- package/dist/client/htmxBootstrap.js +44 -2
- package/dist/client/index.d.ts +14 -0
- package/dist/client/index.js +713 -2
- package/dist/client/opsStatusWidget.d.ts +40 -0
- package/dist/client/providerStatus.d.ts +19 -0
- package/dist/client/providerStatusWidget.d.ts +32 -0
- package/dist/client/routingStatus.d.ts +19 -0
- package/dist/client/routingStatusWidget.d.ts +28 -0
- package/dist/client/workflowStatus.d.ts +19 -0
- package/dist/diagnosticsRoutes.d.ts +44 -0
- package/dist/evalRoutes.d.ts +213 -0
- package/dist/handoff.d.ts +54 -0
- package/dist/handoffHealth.d.ts +94 -0
- package/dist/index.d.ts +32 -4
- package/dist/index.js +4222 -133
- package/dist/modelAdapters.d.ts +75 -0
- package/dist/opsConsoleRoutes.d.ts +77 -0
- package/dist/opsWebhook.d.ts +126 -0
- package/dist/providerAdapters.d.ts +48 -0
- package/dist/providerHealth.d.ts +79 -0
- package/dist/qualityRoutes.d.ts +76 -0
- package/dist/queue.d.ts +52 -0
- package/dist/react/VoiceOpsStatus.d.ts +6 -0
- package/dist/react/VoiceProviderStatus.d.ts +6 -0
- package/dist/react/VoiceRoutingStatus.d.ts +6 -0
- package/dist/react/index.d.ts +7 -0
- package/dist/react/index.js +1024 -11
- package/dist/react/useVoiceAppKitStatus.d.ts +8 -0
- package/dist/react/useVoiceController.d.ts +2 -0
- package/dist/react/useVoiceProviderStatus.d.ts +8 -0
- package/dist/react/useVoiceRoutingStatus.d.ts +8 -0
- package/dist/react/useVoiceStream.d.ts +2 -0
- package/dist/react/useVoiceWorkflowStatus.d.ts +8 -0
- package/dist/resilienceRoutes.d.ts +117 -0
- package/dist/sessionReplay.d.ts +175 -0
- package/dist/svelte/createVoiceAppKitStatus.d.ts +8 -0
- package/dist/svelte/createVoiceOpsStatus.d.ts +9 -0
- package/dist/svelte/createVoiceProviderStatus.d.ts +10 -0
- package/dist/svelte/createVoiceRoutingStatus.d.ts +10 -0
- package/dist/svelte/createVoiceWorkflowStatus.d.ts +8 -0
- package/dist/svelte/index.d.ts +5 -0
- package/dist/svelte/index.js +736 -3
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1537 -7
- package/dist/testing/ioProviderSimulator.d.ts +41 -0
- package/dist/testing/providerSimulator.d.ts +44 -0
- package/dist/trace.d.ts +1 -1
- package/dist/types.d.ts +84 -2
- package/dist/vue/VoiceOpsStatus.d.ts +30 -0
- package/dist/vue/VoiceProviderStatus.d.ts +51 -0
- package/dist/vue/VoiceRoutingStatus.d.ts +51 -0
- package/dist/vue/index.d.ts +7 -0
- package/dist/vue/index.js +1062 -25
- package/dist/vue/useVoiceAppKitStatus.d.ts +9 -0
- package/dist/vue/useVoiceProviderStatus.d.ts +9 -0
- package/dist/vue/useVoiceRoutingStatus.d.ts +8 -0
- package/dist/vue/useVoiceStream.d.ts +2 -0
- package/dist/vue/useVoiceWorkflowStatus.d.ts +9 -0
- package/dist/workflowContract.d.ts +91 -0
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -69,9 +69,871 @@ var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
|
69
69
|
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
// src/react/
|
|
72
|
+
// src/react/useVoiceAppKitStatus.tsx
|
|
73
73
|
import { useEffect, useRef, useSyncExternalStore } from "react";
|
|
74
74
|
|
|
75
|
+
// src/client/appKitStatus.ts
|
|
76
|
+
var fetchVoiceAppKitStatus = async (path = "/app-kit/status", options = {}) => {
|
|
77
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
78
|
+
const response = await fetchImpl(path);
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
throw new Error(`Voice app kit status failed: HTTP ${response.status}`);
|
|
81
|
+
}
|
|
82
|
+
return await response.json();
|
|
83
|
+
};
|
|
84
|
+
var createVoiceAppKitStatusStore = (path = "/app-kit/status", options = {}) => {
|
|
85
|
+
const listeners = new Set;
|
|
86
|
+
let closed = false;
|
|
87
|
+
let timer;
|
|
88
|
+
let snapshot = {
|
|
89
|
+
error: null,
|
|
90
|
+
isLoading: false
|
|
91
|
+
};
|
|
92
|
+
const emit = () => {
|
|
93
|
+
for (const listener of listeners) {
|
|
94
|
+
listener();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const refresh = async () => {
|
|
98
|
+
if (closed) {
|
|
99
|
+
return snapshot.report;
|
|
100
|
+
}
|
|
101
|
+
snapshot = {
|
|
102
|
+
...snapshot,
|
|
103
|
+
error: null,
|
|
104
|
+
isLoading: true
|
|
105
|
+
};
|
|
106
|
+
emit();
|
|
107
|
+
try {
|
|
108
|
+
const report = await fetchVoiceAppKitStatus(path, options);
|
|
109
|
+
snapshot = {
|
|
110
|
+
error: null,
|
|
111
|
+
isLoading: false,
|
|
112
|
+
report,
|
|
113
|
+
updatedAt: Date.now()
|
|
114
|
+
};
|
|
115
|
+
emit();
|
|
116
|
+
return report;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
snapshot = {
|
|
119
|
+
...snapshot,
|
|
120
|
+
error: error instanceof Error ? error.message : String(error),
|
|
121
|
+
isLoading: false
|
|
122
|
+
};
|
|
123
|
+
emit();
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const close = () => {
|
|
128
|
+
closed = true;
|
|
129
|
+
if (timer) {
|
|
130
|
+
clearInterval(timer);
|
|
131
|
+
timer = undefined;
|
|
132
|
+
}
|
|
133
|
+
listeners.clear();
|
|
134
|
+
};
|
|
135
|
+
if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
|
|
136
|
+
timer = setInterval(() => {
|
|
137
|
+
refresh().catch(() => {});
|
|
138
|
+
}, options.intervalMs);
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
close,
|
|
142
|
+
getServerSnapshot: () => snapshot,
|
|
143
|
+
getSnapshot: () => snapshot,
|
|
144
|
+
refresh,
|
|
145
|
+
subscribe: (listener) => {
|
|
146
|
+
listeners.add(listener);
|
|
147
|
+
return () => {
|
|
148
|
+
listeners.delete(listener);
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// src/react/useVoiceAppKitStatus.tsx
|
|
155
|
+
var useVoiceAppKitStatus = (path = "/app-kit/status", options = {}) => {
|
|
156
|
+
const storeRef = useRef(null);
|
|
157
|
+
if (!storeRef.current) {
|
|
158
|
+
storeRef.current = createVoiceAppKitStatusStore(path, options);
|
|
159
|
+
}
|
|
160
|
+
const store = storeRef.current;
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
store.refresh().catch(() => {});
|
|
163
|
+
return () => store.close();
|
|
164
|
+
}, [store]);
|
|
165
|
+
return {
|
|
166
|
+
...useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot),
|
|
167
|
+
refresh: store.refresh
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// src/client/opsStatusWidget.ts
|
|
172
|
+
var DEFAULT_TITLE = "Voice Ops Status";
|
|
173
|
+
var DEFAULT_DESCRIPTION = "Certified workflow, provider, and handoff readiness from the AbsoluteJS voice app kit.";
|
|
174
|
+
var SURFACE_LABELS = {
|
|
175
|
+
handoffs: "Handoffs",
|
|
176
|
+
providers: "Providers",
|
|
177
|
+
quality: "Quality",
|
|
178
|
+
sessions: "Sessions",
|
|
179
|
+
workflows: "Workflows"
|
|
180
|
+
};
|
|
181
|
+
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
182
|
+
var readNumber = (value, key) => value && typeof value === "object" && (key in value) ? Number(value[key] ?? 0) : 0;
|
|
183
|
+
var surfaceDetail = (surface) => {
|
|
184
|
+
const total = readNumber(surface, "total");
|
|
185
|
+
const failed = readNumber(surface, "failed");
|
|
186
|
+
const degraded = readNumber(surface, "degraded");
|
|
187
|
+
const source = surface && typeof surface === "object" && "source" in surface && typeof surface.source === "string" ? ` from ${surface.source}` : "";
|
|
188
|
+
if (degraded > 0) {
|
|
189
|
+
return `${degraded} degraded of ${total}`;
|
|
190
|
+
}
|
|
191
|
+
if (failed > 0) {
|
|
192
|
+
return `${failed} failing of ${total}${source}`;
|
|
193
|
+
}
|
|
194
|
+
return total > 0 ? `${total} passing${source}` : `No failures${source}`;
|
|
195
|
+
};
|
|
196
|
+
var getVoiceOpsStatusLabel = (report, error) => {
|
|
197
|
+
if (error) {
|
|
198
|
+
return "Unavailable";
|
|
199
|
+
}
|
|
200
|
+
if (!report) {
|
|
201
|
+
return "Checking";
|
|
202
|
+
}
|
|
203
|
+
return report.status === "pass" ? "Passing" : "Needs attention";
|
|
204
|
+
};
|
|
205
|
+
var createVoiceOpsStatusViewModel = (snapshot, options = {}) => {
|
|
206
|
+
const report = snapshot.report;
|
|
207
|
+
const surfaces = Object.entries(report?.surfaces ?? {}).map(([id, surface]) => {
|
|
208
|
+
const failed = readNumber(surface, "failed") || readNumber(surface, "degraded");
|
|
209
|
+
const total = readNumber(surface, "total");
|
|
210
|
+
const status = surface && typeof surface === "object" && "status" in surface ? surface.status ?? "pass" : "pass";
|
|
211
|
+
return {
|
|
212
|
+
detail: surfaceDetail(surface),
|
|
213
|
+
failed,
|
|
214
|
+
id,
|
|
215
|
+
label: SURFACE_LABELS[id] ?? id,
|
|
216
|
+
status,
|
|
217
|
+
total
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
description: options.description ?? DEFAULT_DESCRIPTION,
|
|
222
|
+
error: snapshot.error,
|
|
223
|
+
isLoading: snapshot.isLoading,
|
|
224
|
+
label: getVoiceOpsStatusLabel(report, snapshot.error),
|
|
225
|
+
links: options.includeLinks === false ? [] : report?.links ?? [],
|
|
226
|
+
passed: report?.passed ?? 0,
|
|
227
|
+
status: snapshot.error ? "error" : report ? report.status : snapshot.isLoading ? "loading" : "loading",
|
|
228
|
+
surfaces,
|
|
229
|
+
title: options.title ?? DEFAULT_TITLE,
|
|
230
|
+
total: report?.total ?? 0,
|
|
231
|
+
updatedAt: snapshot.updatedAt
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
var renderVoiceOpsStatusHTML = (snapshot, options = {}) => {
|
|
235
|
+
const model = createVoiceOpsStatusViewModel(snapshot, options);
|
|
236
|
+
const surfaces = model.surfaces.length ? model.surfaces.map((surface) => `<li class="absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${escapeHtml(surface.status)}">
|
|
237
|
+
<span>${escapeHtml(surface.label)}</span>
|
|
238
|
+
<strong>${escapeHtml(surface.detail)}</strong>
|
|
239
|
+
</li>`).join("") : '<li class="absolute-voice-ops-status__surface"><span>Status</span><strong>Waiting for first check</strong></li>';
|
|
240
|
+
const links = model.links.length ? `<nav class="absolute-voice-ops-status__links">${model.links.slice(0, 4).map((link) => `<a href="${escapeHtml(link.href)}">${escapeHtml(link.label)}</a>`).join("")}</nav>` : "";
|
|
241
|
+
return `<section class="absolute-voice-ops-status absolute-voice-ops-status--${escapeHtml(model.status)}">
|
|
242
|
+
<header class="absolute-voice-ops-status__header">
|
|
243
|
+
<span class="absolute-voice-ops-status__eyebrow">${escapeHtml(model.title)}</span>
|
|
244
|
+
<strong class="absolute-voice-ops-status__label">${escapeHtml(model.label)}</strong>
|
|
245
|
+
</header>
|
|
246
|
+
<p class="absolute-voice-ops-status__description">${escapeHtml(model.description)}</p>
|
|
247
|
+
<div class="absolute-voice-ops-status__summary">
|
|
248
|
+
<span>${model.passed} passing</span>
|
|
249
|
+
<span>${Math.max(model.total - model.passed, 0)} failing</span>
|
|
250
|
+
<span>${model.total} checks</span>
|
|
251
|
+
</div>
|
|
252
|
+
<ul class="absolute-voice-ops-status__surfaces">${surfaces}</ul>
|
|
253
|
+
${model.error ? `<p class="absolute-voice-ops-status__error">${escapeHtml(model.error)}</p>` : ""}
|
|
254
|
+
${links}
|
|
255
|
+
</section>`;
|
|
256
|
+
};
|
|
257
|
+
var getVoiceOpsStatusCSS = () => `.absolute-voice-ops-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-ops-status--fail,.absolute-voice-ops-status--error{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-ops-status__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-ops-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-ops-status__label{font-size:28px;line-height:1}.absolute-voice-ops-status__description{color:#514733;margin:12px 0 0}.absolute-voice-ops-status__summary,.absolute-voice-ops-status__links{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.absolute-voice-ops-status__summary span,.absolute-voice-ops-status__links a{border:1px solid #e6ddca;border-radius:999px;color:inherit;padding:6px 10px;text-decoration:none}.absolute-voice-ops-status__surfaces{display:grid;gap:8px;list-style:none;margin:16px 0 0;padding:0}.absolute-voice-ops-status__surface{align-items:center;background:#fff;border:1px solid #eee4d2;border-radius:14px;display:flex;gap:12px;justify-content:space-between;padding:10px 12px}.absolute-voice-ops-status__surface--fail{border-color:#f2a7a7}.absolute-voice-ops-status__surface span{color:#655944}.absolute-voice-ops-status__error{color:#9f1239;font-weight:700}`;
|
|
258
|
+
var mountVoiceOpsStatus = (element, path = "/app-kit/status", options = {}) => {
|
|
259
|
+
const store = createVoiceAppKitStatusStore(path, options);
|
|
260
|
+
const render = () => {
|
|
261
|
+
element.innerHTML = renderVoiceOpsStatusHTML(store.getSnapshot(), options);
|
|
262
|
+
};
|
|
263
|
+
const unsubscribe = store.subscribe(render);
|
|
264
|
+
render();
|
|
265
|
+
store.refresh().catch(() => {});
|
|
266
|
+
return {
|
|
267
|
+
close: () => {
|
|
268
|
+
unsubscribe();
|
|
269
|
+
store.close();
|
|
270
|
+
},
|
|
271
|
+
refresh: store.refresh
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
var defineVoiceOpsStatusElement = (tagName = "absolute-voice-ops-status") => {
|
|
275
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
customElements.define(tagName, class AbsoluteVoiceOpsStatusElement extends HTMLElement {
|
|
279
|
+
mounted;
|
|
280
|
+
connectedCallback() {
|
|
281
|
+
const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
|
|
282
|
+
this.mounted = mountVoiceOpsStatus(this, this.getAttribute("path") ?? "/app-kit/status", {
|
|
283
|
+
description: this.getAttribute("description") ?? undefined,
|
|
284
|
+
includeLinks: this.getAttribute("include-links") !== "false",
|
|
285
|
+
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
286
|
+
title: this.getAttribute("title") ?? undefined
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
disconnectedCallback() {
|
|
290
|
+
this.mounted?.close();
|
|
291
|
+
this.mounted = undefined;
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// src/react/VoiceOpsStatus.tsx
|
|
297
|
+
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
298
|
+
var VoiceOpsStatus = ({
|
|
299
|
+
className,
|
|
300
|
+
path = "/app-kit/status",
|
|
301
|
+
...options
|
|
302
|
+
}) => {
|
|
303
|
+
const snapshot = useVoiceAppKitStatus(path, options);
|
|
304
|
+
const model = createVoiceOpsStatusViewModel(snapshot, options);
|
|
305
|
+
return /* @__PURE__ */ jsxDEV("section", {
|
|
306
|
+
className: [
|
|
307
|
+
"absolute-voice-ops-status",
|
|
308
|
+
`absolute-voice-ops-status--${model.status}`,
|
|
309
|
+
className
|
|
310
|
+
].filter(Boolean).join(" "),
|
|
311
|
+
children: [
|
|
312
|
+
/* @__PURE__ */ jsxDEV("header", {
|
|
313
|
+
className: "absolute-voice-ops-status__header",
|
|
314
|
+
children: [
|
|
315
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
316
|
+
className: "absolute-voice-ops-status__eyebrow",
|
|
317
|
+
children: model.title
|
|
318
|
+
}, undefined, false, undefined, this),
|
|
319
|
+
/* @__PURE__ */ jsxDEV("strong", {
|
|
320
|
+
className: "absolute-voice-ops-status__label",
|
|
321
|
+
children: model.label
|
|
322
|
+
}, undefined, false, undefined, this)
|
|
323
|
+
]
|
|
324
|
+
}, undefined, true, undefined, this),
|
|
325
|
+
/* @__PURE__ */ jsxDEV("p", {
|
|
326
|
+
className: "absolute-voice-ops-status__description",
|
|
327
|
+
children: model.description
|
|
328
|
+
}, undefined, false, undefined, this),
|
|
329
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
330
|
+
className: "absolute-voice-ops-status__summary",
|
|
331
|
+
children: [
|
|
332
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
333
|
+
children: [
|
|
334
|
+
model.passed,
|
|
335
|
+
" passing"
|
|
336
|
+
]
|
|
337
|
+
}, undefined, true, undefined, this),
|
|
338
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
339
|
+
children: [
|
|
340
|
+
Math.max(model.total - model.passed, 0),
|
|
341
|
+
" failing"
|
|
342
|
+
]
|
|
343
|
+
}, undefined, true, undefined, this),
|
|
344
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
345
|
+
children: [
|
|
346
|
+
model.total,
|
|
347
|
+
" checks"
|
|
348
|
+
]
|
|
349
|
+
}, undefined, true, undefined, this)
|
|
350
|
+
]
|
|
351
|
+
}, undefined, true, undefined, this),
|
|
352
|
+
/* @__PURE__ */ jsxDEV("ul", {
|
|
353
|
+
className: "absolute-voice-ops-status__surfaces",
|
|
354
|
+
children: model.surfaces.length > 0 ? model.surfaces.map((surface) => /* @__PURE__ */ jsxDEV("li", {
|
|
355
|
+
className: `absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${surface.status}`,
|
|
356
|
+
children: [
|
|
357
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
358
|
+
children: surface.label
|
|
359
|
+
}, undefined, false, undefined, this),
|
|
360
|
+
/* @__PURE__ */ jsxDEV("strong", {
|
|
361
|
+
children: surface.detail
|
|
362
|
+
}, undefined, false, undefined, this)
|
|
363
|
+
]
|
|
364
|
+
}, surface.id, true, undefined, this)) : /* @__PURE__ */ jsxDEV("li", {
|
|
365
|
+
className: "absolute-voice-ops-status__surface",
|
|
366
|
+
children: [
|
|
367
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
368
|
+
children: "Status"
|
|
369
|
+
}, undefined, false, undefined, this),
|
|
370
|
+
/* @__PURE__ */ jsxDEV("strong", {
|
|
371
|
+
children: "Waiting for first check"
|
|
372
|
+
}, undefined, false, undefined, this)
|
|
373
|
+
]
|
|
374
|
+
}, undefined, true, undefined, this)
|
|
375
|
+
}, undefined, false, undefined, this),
|
|
376
|
+
model.error ? /* @__PURE__ */ jsxDEV("p", {
|
|
377
|
+
className: "absolute-voice-ops-status__error",
|
|
378
|
+
children: model.error
|
|
379
|
+
}, undefined, false, undefined, this) : null,
|
|
380
|
+
model.links.length > 0 ? /* @__PURE__ */ jsxDEV("nav", {
|
|
381
|
+
className: "absolute-voice-ops-status__links",
|
|
382
|
+
children: model.links.slice(0, 4).map((link) => /* @__PURE__ */ jsxDEV("a", {
|
|
383
|
+
href: link.href,
|
|
384
|
+
children: link.label
|
|
385
|
+
}, `${link.label}:${link.href}`, false, undefined, this))
|
|
386
|
+
}, undefined, false, undefined, this) : null
|
|
387
|
+
]
|
|
388
|
+
}, undefined, true, undefined, this);
|
|
389
|
+
};
|
|
390
|
+
// src/react/useVoiceProviderStatus.tsx
|
|
391
|
+
import { useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
392
|
+
|
|
393
|
+
// src/client/providerStatus.ts
|
|
394
|
+
var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
|
|
395
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
396
|
+
const response = await fetchImpl(path);
|
|
397
|
+
if (!response.ok) {
|
|
398
|
+
throw new Error(`Voice provider status failed: HTTP ${response.status}`);
|
|
399
|
+
}
|
|
400
|
+
return await response.json();
|
|
401
|
+
};
|
|
402
|
+
var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
|
|
403
|
+
const listeners = new Set;
|
|
404
|
+
let closed = false;
|
|
405
|
+
let timer;
|
|
406
|
+
let snapshot = {
|
|
407
|
+
error: null,
|
|
408
|
+
isLoading: false,
|
|
409
|
+
providers: []
|
|
410
|
+
};
|
|
411
|
+
const emit = () => {
|
|
412
|
+
for (const listener of listeners) {
|
|
413
|
+
listener();
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
const refresh = async () => {
|
|
417
|
+
if (closed) {
|
|
418
|
+
return snapshot.providers;
|
|
419
|
+
}
|
|
420
|
+
snapshot = {
|
|
421
|
+
...snapshot,
|
|
422
|
+
error: null,
|
|
423
|
+
isLoading: true
|
|
424
|
+
};
|
|
425
|
+
emit();
|
|
426
|
+
try {
|
|
427
|
+
const providers = await fetchVoiceProviderStatus(path, options);
|
|
428
|
+
snapshot = {
|
|
429
|
+
error: null,
|
|
430
|
+
isLoading: false,
|
|
431
|
+
providers,
|
|
432
|
+
updatedAt: Date.now()
|
|
433
|
+
};
|
|
434
|
+
emit();
|
|
435
|
+
return providers;
|
|
436
|
+
} catch (error) {
|
|
437
|
+
snapshot = {
|
|
438
|
+
...snapshot,
|
|
439
|
+
error: error instanceof Error ? error.message : String(error),
|
|
440
|
+
isLoading: false
|
|
441
|
+
};
|
|
442
|
+
emit();
|
|
443
|
+
throw error;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
const close = () => {
|
|
447
|
+
closed = true;
|
|
448
|
+
if (timer) {
|
|
449
|
+
clearInterval(timer);
|
|
450
|
+
timer = undefined;
|
|
451
|
+
}
|
|
452
|
+
listeners.clear();
|
|
453
|
+
};
|
|
454
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
455
|
+
timer = setInterval(() => {
|
|
456
|
+
refresh().catch(() => {});
|
|
457
|
+
}, options.intervalMs);
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
close,
|
|
461
|
+
getServerSnapshot: () => snapshot,
|
|
462
|
+
getSnapshot: () => snapshot,
|
|
463
|
+
refresh,
|
|
464
|
+
subscribe: (listener) => {
|
|
465
|
+
listeners.add(listener);
|
|
466
|
+
return () => {
|
|
467
|
+
listeners.delete(listener);
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// src/react/useVoiceProviderStatus.tsx
|
|
474
|
+
var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
|
|
475
|
+
const storeRef = useRef2(null);
|
|
476
|
+
if (!storeRef.current) {
|
|
477
|
+
storeRef.current = createVoiceProviderStatusStore(path, options);
|
|
478
|
+
}
|
|
479
|
+
const store = storeRef.current;
|
|
480
|
+
useEffect2(() => {
|
|
481
|
+
store.refresh().catch(() => {});
|
|
482
|
+
return () => store.close();
|
|
483
|
+
}, [store]);
|
|
484
|
+
return {
|
|
485
|
+
...useSyncExternalStore2(store.subscribe, store.getSnapshot, store.getServerSnapshot),
|
|
486
|
+
refresh: store.refresh
|
|
487
|
+
};
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// src/client/providerStatusWidget.ts
|
|
491
|
+
var DEFAULT_TITLE2 = "Voice Providers";
|
|
492
|
+
var DEFAULT_DESCRIPTION2 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
|
|
493
|
+
var escapeHtml2 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
494
|
+
var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
495
|
+
var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
496
|
+
var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
|
|
497
|
+
var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
|
|
498
|
+
var getProviderDetail = (provider) => {
|
|
499
|
+
if (provider.status === "suppressed") {
|
|
500
|
+
return provider.lastError ? `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)} after ${provider.lastError}.` : `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)}.`;
|
|
501
|
+
}
|
|
502
|
+
if (provider.status === "recoverable") {
|
|
503
|
+
return "Cooldown expired; ready for recovery traffic.";
|
|
504
|
+
}
|
|
505
|
+
if (provider.status === "rate-limited") {
|
|
506
|
+
return "Rate limit detected; router should avoid this provider.";
|
|
507
|
+
}
|
|
508
|
+
if (provider.status === "degraded") {
|
|
509
|
+
return provider.lastError ?? "Recent provider errors detected.";
|
|
510
|
+
}
|
|
511
|
+
if (provider.status === "healthy") {
|
|
512
|
+
return provider.recommended ? "Healthy and currently recommended." : "Healthy and available for routing.";
|
|
513
|
+
}
|
|
514
|
+
return "No provider traffic observed yet.";
|
|
515
|
+
};
|
|
516
|
+
var isWarningStatus = (status) => status === "degraded" || status === "rate-limited" || status === "recoverable" || status === "suppressed";
|
|
517
|
+
var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
|
|
518
|
+
const providers = snapshot.providers.map((provider) => ({
|
|
519
|
+
...provider,
|
|
520
|
+
detail: getProviderDetail(provider),
|
|
521
|
+
label: `${formatProvider(provider.provider)}${provider.recommended ? " recommended" : ""}`,
|
|
522
|
+
rows: [
|
|
523
|
+
{ label: "Runs", value: String(provider.runCount) },
|
|
524
|
+
{ label: "Avg latency", value: formatLatency(provider.averageElapsedMs) },
|
|
525
|
+
{ label: "Errors", value: String(provider.errorCount) },
|
|
526
|
+
{ label: "Timeouts", value: String(provider.timeoutCount) },
|
|
527
|
+
{ label: "Fallbacks", value: String(provider.fallbackCount) },
|
|
528
|
+
{
|
|
529
|
+
label: "Suppression",
|
|
530
|
+
value: formatSuppression(provider.suppressionRemainingMs)
|
|
531
|
+
}
|
|
532
|
+
]
|
|
533
|
+
}));
|
|
534
|
+
const warningCount = providers.filter((provider) => isWarningStatus(provider.status)).length;
|
|
535
|
+
const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
|
|
536
|
+
return {
|
|
537
|
+
description: options.description ?? DEFAULT_DESCRIPTION2,
|
|
538
|
+
error: snapshot.error,
|
|
539
|
+
isLoading: snapshot.isLoading,
|
|
540
|
+
label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
|
|
541
|
+
providers,
|
|
542
|
+
status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
543
|
+
title: options.title ?? DEFAULT_TITLE2,
|
|
544
|
+
updatedAt: snapshot.updatedAt
|
|
545
|
+
};
|
|
546
|
+
};
|
|
547
|
+
var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
|
|
548
|
+
const model = createVoiceProviderStatusViewModel(snapshot, options);
|
|
549
|
+
const providers = model.providers.length ? `<div class="absolute-voice-provider-status__providers">${model.providers.map((provider) => `<article class="absolute-voice-provider-status__provider absolute-voice-provider-status__provider--${escapeHtml2(provider.status)}">
|
|
550
|
+
<header>
|
|
551
|
+
<strong>${escapeHtml2(provider.label)}</strong>
|
|
552
|
+
<span>${escapeHtml2(formatStatus(provider.status))}</span>
|
|
553
|
+
</header>
|
|
554
|
+
<p>${escapeHtml2(provider.detail)}</p>
|
|
555
|
+
<dl>${provider.rows.map((row) => `<div>
|
|
556
|
+
<dt>${escapeHtml2(row.label)}</dt>
|
|
557
|
+
<dd>${escapeHtml2(row.value)}</dd>
|
|
558
|
+
</div>`).join("")}</dl>
|
|
559
|
+
</article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
|
|
560
|
+
return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml2(model.status)}">
|
|
561
|
+
<header class="absolute-voice-provider-status__header">
|
|
562
|
+
<span class="absolute-voice-provider-status__eyebrow">${escapeHtml2(model.title)}</span>
|
|
563
|
+
<strong class="absolute-voice-provider-status__label">${escapeHtml2(model.label)}</strong>
|
|
564
|
+
</header>
|
|
565
|
+
<p class="absolute-voice-provider-status__description">${escapeHtml2(model.description)}</p>
|
|
566
|
+
${providers}
|
|
567
|
+
${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml2(model.error)}</p>` : ""}
|
|
568
|
+
</section>`;
|
|
569
|
+
};
|
|
570
|
+
var getVoiceProviderStatusCSS = () => `.absolute-voice-provider-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-provider-status--error,.absolute-voice-provider-status--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-provider-status__header,.absolute-voice-provider-status__provider header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-status__label{font-size:24px;line-height:1}.absolute-voice-provider-status__description,.absolute-voice-provider-status__provider p,.absolute-voice-provider-status__provider dt,.absolute-voice-provider-status__empty{color:#514733}.absolute-voice-provider-status__providers{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-status__provider{background:#fff;border:1px solid #eee4d2;border-radius:16px;padding:14px}.absolute-voice-provider-status__provider--degraded,.absolute-voice-provider-status__provider--rate-limited,.absolute-voice-provider-status__provider--suppressed{border-color:#f2a7a7}.absolute-voice-provider-status__provider--recoverable{border-color:#fbbf24}.absolute-voice-provider-status__provider p{margin:10px 0}.absolute-voice-provider-status__provider dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-status__provider div{background:#fffaf0;border:1px solid #eee4d2;border-radius:12px;padding:8px}.absolute-voice-provider-status__provider dt{font-size:12px}.absolute-voice-provider-status__provider dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-status__empty{margin:14px 0 0}.absolute-voice-provider-status__error{color:#9f1239;font-weight:700}`;
|
|
571
|
+
var mountVoiceProviderStatus = (element, path = "/api/provider-status", options = {}) => {
|
|
572
|
+
const store = createVoiceProviderStatusStore(path, options);
|
|
573
|
+
const render = () => {
|
|
574
|
+
element.innerHTML = renderVoiceProviderStatusHTML(store.getSnapshot(), options);
|
|
575
|
+
};
|
|
576
|
+
const unsubscribe = store.subscribe(render);
|
|
577
|
+
render();
|
|
578
|
+
store.refresh().catch(() => {});
|
|
579
|
+
return {
|
|
580
|
+
close: () => {
|
|
581
|
+
unsubscribe();
|
|
582
|
+
store.close();
|
|
583
|
+
},
|
|
584
|
+
refresh: store.refresh
|
|
585
|
+
};
|
|
586
|
+
};
|
|
587
|
+
var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-status") => {
|
|
588
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
customElements.define(tagName, class AbsoluteVoiceProviderStatusElement extends HTMLElement {
|
|
592
|
+
mounted;
|
|
593
|
+
connectedCallback() {
|
|
594
|
+
const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
|
|
595
|
+
this.mounted = mountVoiceProviderStatus(this, this.getAttribute("path") ?? "/api/provider-status", {
|
|
596
|
+
description: this.getAttribute("description") ?? undefined,
|
|
597
|
+
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
598
|
+
title: this.getAttribute("title") ?? undefined
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
disconnectedCallback() {
|
|
602
|
+
this.mounted?.close();
|
|
603
|
+
this.mounted = undefined;
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
// src/react/VoiceProviderStatus.tsx
|
|
609
|
+
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
610
|
+
var VoiceProviderStatus = ({
|
|
611
|
+
className,
|
|
612
|
+
path = "/api/provider-status",
|
|
613
|
+
...options
|
|
614
|
+
}) => {
|
|
615
|
+
const snapshot = useVoiceProviderStatus(path, options);
|
|
616
|
+
const model = createVoiceProviderStatusViewModel(snapshot, options);
|
|
617
|
+
return /* @__PURE__ */ jsxDEV2("section", {
|
|
618
|
+
className: [
|
|
619
|
+
"absolute-voice-provider-status",
|
|
620
|
+
`absolute-voice-provider-status--${model.status}`,
|
|
621
|
+
className
|
|
622
|
+
].filter(Boolean).join(" "),
|
|
623
|
+
children: [
|
|
624
|
+
/* @__PURE__ */ jsxDEV2("header", {
|
|
625
|
+
className: "absolute-voice-provider-status__header",
|
|
626
|
+
children: [
|
|
627
|
+
/* @__PURE__ */ jsxDEV2("span", {
|
|
628
|
+
className: "absolute-voice-provider-status__eyebrow",
|
|
629
|
+
children: model.title
|
|
630
|
+
}, undefined, false, undefined, this),
|
|
631
|
+
/* @__PURE__ */ jsxDEV2("strong", {
|
|
632
|
+
className: "absolute-voice-provider-status__label",
|
|
633
|
+
children: model.label
|
|
634
|
+
}, undefined, false, undefined, this)
|
|
635
|
+
]
|
|
636
|
+
}, undefined, true, undefined, this),
|
|
637
|
+
/* @__PURE__ */ jsxDEV2("p", {
|
|
638
|
+
className: "absolute-voice-provider-status__description",
|
|
639
|
+
children: model.description
|
|
640
|
+
}, undefined, false, undefined, this),
|
|
641
|
+
model.providers.length ? /* @__PURE__ */ jsxDEV2("div", {
|
|
642
|
+
className: "absolute-voice-provider-status__providers",
|
|
643
|
+
children: model.providers.map((provider) => /* @__PURE__ */ jsxDEV2("article", {
|
|
644
|
+
className: [
|
|
645
|
+
"absolute-voice-provider-status__provider",
|
|
646
|
+
`absolute-voice-provider-status__provider--${provider.status}`
|
|
647
|
+
].join(" "),
|
|
648
|
+
children: [
|
|
649
|
+
/* @__PURE__ */ jsxDEV2("header", {
|
|
650
|
+
children: [
|
|
651
|
+
/* @__PURE__ */ jsxDEV2("strong", {
|
|
652
|
+
children: provider.label
|
|
653
|
+
}, undefined, false, undefined, this),
|
|
654
|
+
/* @__PURE__ */ jsxDEV2("span", {
|
|
655
|
+
children: provider.status
|
|
656
|
+
}, undefined, false, undefined, this)
|
|
657
|
+
]
|
|
658
|
+
}, undefined, true, undefined, this),
|
|
659
|
+
/* @__PURE__ */ jsxDEV2("p", {
|
|
660
|
+
children: provider.detail
|
|
661
|
+
}, undefined, false, undefined, this),
|
|
662
|
+
/* @__PURE__ */ jsxDEV2("dl", {
|
|
663
|
+
children: provider.rows.map((row) => /* @__PURE__ */ jsxDEV2("div", {
|
|
664
|
+
children: [
|
|
665
|
+
/* @__PURE__ */ jsxDEV2("dt", {
|
|
666
|
+
children: row.label
|
|
667
|
+
}, undefined, false, undefined, this),
|
|
668
|
+
/* @__PURE__ */ jsxDEV2("dd", {
|
|
669
|
+
children: row.value
|
|
670
|
+
}, undefined, false, undefined, this)
|
|
671
|
+
]
|
|
672
|
+
}, row.label, true, undefined, this))
|
|
673
|
+
}, undefined, false, undefined, this)
|
|
674
|
+
]
|
|
675
|
+
}, provider.provider, true, undefined, this))
|
|
676
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2("p", {
|
|
677
|
+
className: "absolute-voice-provider-status__empty",
|
|
678
|
+
children: "Run voice traffic to see provider health."
|
|
679
|
+
}, undefined, false, undefined, this),
|
|
680
|
+
model.error ? /* @__PURE__ */ jsxDEV2("p", {
|
|
681
|
+
className: "absolute-voice-provider-status__error",
|
|
682
|
+
children: model.error
|
|
683
|
+
}, undefined, false, undefined, this) : null
|
|
684
|
+
]
|
|
685
|
+
}, undefined, true, undefined, this);
|
|
686
|
+
};
|
|
687
|
+
// src/react/useVoiceRoutingStatus.tsx
|
|
688
|
+
import { useEffect as useEffect3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
689
|
+
|
|
690
|
+
// src/client/routingStatus.ts
|
|
691
|
+
var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
|
|
692
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
693
|
+
const response = await fetchImpl(path);
|
|
694
|
+
if (!response.ok) {
|
|
695
|
+
throw new Error(`Voice routing status failed: HTTP ${response.status}`);
|
|
696
|
+
}
|
|
697
|
+
return await response.json();
|
|
698
|
+
};
|
|
699
|
+
var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
|
|
700
|
+
const listeners = new Set;
|
|
701
|
+
let closed = false;
|
|
702
|
+
let timer;
|
|
703
|
+
let snapshot = {
|
|
704
|
+
decision: null,
|
|
705
|
+
error: null,
|
|
706
|
+
isLoading: false
|
|
707
|
+
};
|
|
708
|
+
const emit = () => {
|
|
709
|
+
for (const listener of listeners) {
|
|
710
|
+
listener();
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
const refresh = async () => {
|
|
714
|
+
if (closed) {
|
|
715
|
+
return snapshot.decision;
|
|
716
|
+
}
|
|
717
|
+
snapshot = {
|
|
718
|
+
...snapshot,
|
|
719
|
+
error: null,
|
|
720
|
+
isLoading: true
|
|
721
|
+
};
|
|
722
|
+
emit();
|
|
723
|
+
try {
|
|
724
|
+
const decision = await fetchVoiceRoutingStatus(path, options);
|
|
725
|
+
snapshot = {
|
|
726
|
+
decision,
|
|
727
|
+
error: null,
|
|
728
|
+
isLoading: false,
|
|
729
|
+
updatedAt: Date.now()
|
|
730
|
+
};
|
|
731
|
+
emit();
|
|
732
|
+
return decision;
|
|
733
|
+
} catch (error) {
|
|
734
|
+
snapshot = {
|
|
735
|
+
...snapshot,
|
|
736
|
+
error: error instanceof Error ? error.message : String(error),
|
|
737
|
+
isLoading: false
|
|
738
|
+
};
|
|
739
|
+
emit();
|
|
740
|
+
throw error;
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
const close = () => {
|
|
744
|
+
closed = true;
|
|
745
|
+
if (timer) {
|
|
746
|
+
clearInterval(timer);
|
|
747
|
+
timer = undefined;
|
|
748
|
+
}
|
|
749
|
+
listeners.clear();
|
|
750
|
+
};
|
|
751
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
752
|
+
timer = setInterval(() => {
|
|
753
|
+
refresh().catch(() => {});
|
|
754
|
+
}, options.intervalMs);
|
|
755
|
+
}
|
|
756
|
+
return {
|
|
757
|
+
close,
|
|
758
|
+
getServerSnapshot: () => snapshot,
|
|
759
|
+
getSnapshot: () => snapshot,
|
|
760
|
+
refresh,
|
|
761
|
+
subscribe: (listener) => {
|
|
762
|
+
listeners.add(listener);
|
|
763
|
+
return () => {
|
|
764
|
+
listeners.delete(listener);
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
// src/react/useVoiceRoutingStatus.tsx
|
|
771
|
+
var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
|
|
772
|
+
const storeRef = useRef3(null);
|
|
773
|
+
if (!storeRef.current) {
|
|
774
|
+
storeRef.current = createVoiceRoutingStatusStore(path, options);
|
|
775
|
+
}
|
|
776
|
+
const store = storeRef.current;
|
|
777
|
+
useEffect3(() => {
|
|
778
|
+
store.refresh().catch(() => {});
|
|
779
|
+
return () => store.close();
|
|
780
|
+
}, [store]);
|
|
781
|
+
return {
|
|
782
|
+
...useSyncExternalStore3(store.subscribe, store.getSnapshot, store.getServerSnapshot),
|
|
783
|
+
refresh: store.refresh
|
|
784
|
+
};
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
// src/client/routingStatusWidget.ts
|
|
788
|
+
var DEFAULT_TITLE3 = "Voice Routing";
|
|
789
|
+
var DEFAULT_DESCRIPTION3 = "Latest provider routing decision from the self-hosted trace store.";
|
|
790
|
+
var escapeHtml3 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
791
|
+
var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
|
|
792
|
+
var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
|
|
793
|
+
const decision = snapshot.decision;
|
|
794
|
+
const rows = decision ? [
|
|
795
|
+
{ label: "Kind", value: decision.kind.toUpperCase() },
|
|
796
|
+
{ label: "Policy", value: formatValue(decision.routing, "Unknown") },
|
|
797
|
+
{ label: "Provider", value: formatValue(decision.provider, "Unknown") },
|
|
798
|
+
{
|
|
799
|
+
label: "Selected",
|
|
800
|
+
value: formatValue(decision.selectedProvider, "Unknown")
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
label: "Fallback",
|
|
804
|
+
value: formatValue(decision.fallbackProvider)
|
|
805
|
+
},
|
|
806
|
+
{ label: "Status", value: formatValue(decision.status, "unknown") },
|
|
807
|
+
{
|
|
808
|
+
label: "Latency budget",
|
|
809
|
+
value: typeof decision.latencyBudgetMs === "number" ? `${decision.latencyBudgetMs}ms` : "None"
|
|
810
|
+
}
|
|
811
|
+
] : [];
|
|
812
|
+
return {
|
|
813
|
+
decision,
|
|
814
|
+
description: options.description ?? DEFAULT_DESCRIPTION3,
|
|
815
|
+
error: snapshot.error,
|
|
816
|
+
isLoading: snapshot.isLoading,
|
|
817
|
+
label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
|
|
818
|
+
rows,
|
|
819
|
+
status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
820
|
+
title: options.title ?? DEFAULT_TITLE3,
|
|
821
|
+
updatedAt: snapshot.updatedAt
|
|
822
|
+
};
|
|
823
|
+
};
|
|
824
|
+
var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
|
|
825
|
+
const model = createVoiceRoutingStatusViewModel(snapshot, options);
|
|
826
|
+
const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
|
|
827
|
+
<span>${escapeHtml3(row.label)}</span>
|
|
828
|
+
<strong>${escapeHtml3(row.value)}</strong>
|
|
829
|
+
</div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
|
|
830
|
+
return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml3(model.status)}">
|
|
831
|
+
<header class="absolute-voice-routing-status__header">
|
|
832
|
+
<span class="absolute-voice-routing-status__eyebrow">${escapeHtml3(model.title)}</span>
|
|
833
|
+
<strong class="absolute-voice-routing-status__label">${escapeHtml3(model.label)}</strong>
|
|
834
|
+
</header>
|
|
835
|
+
<p class="absolute-voice-routing-status__description">${escapeHtml3(model.description)}</p>
|
|
836
|
+
${rows}
|
|
837
|
+
${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml3(model.error)}</p>` : ""}
|
|
838
|
+
</section>`;
|
|
839
|
+
};
|
|
840
|
+
var getVoiceRoutingStatusCSS = () => `.absolute-voice-routing-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-routing-status--error{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-routing-status__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-routing-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-routing-status__label{font-size:24px;line-height:1}.absolute-voice-routing-status__description{color:#514733;margin:12px 0 0}.absolute-voice-routing-status__grid{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin-top:14px}.absolute-voice-routing-status__grid div{background:#fff;border:1px solid #eee4d2;border-radius:14px;padding:10px 12px}.absolute-voice-routing-status__grid span{color:#655944;display:block;font-size:12px;margin-bottom:4px}.absolute-voice-routing-status__grid strong{overflow-wrap:anywhere}.absolute-voice-routing-status__empty{color:#655944;margin:14px 0 0}.absolute-voice-routing-status__error{color:#9f1239;font-weight:700}`;
|
|
841
|
+
var mountVoiceRoutingStatus = (element, path = "/api/routing/latest", options = {}) => {
|
|
842
|
+
const store = createVoiceRoutingStatusStore(path, options);
|
|
843
|
+
const render = () => {
|
|
844
|
+
element.innerHTML = renderVoiceRoutingStatusHTML(store.getSnapshot(), options);
|
|
845
|
+
};
|
|
846
|
+
const unsubscribe = store.subscribe(render);
|
|
847
|
+
render();
|
|
848
|
+
store.refresh().catch(() => {});
|
|
849
|
+
return {
|
|
850
|
+
close: () => {
|
|
851
|
+
unsubscribe();
|
|
852
|
+
store.close();
|
|
853
|
+
},
|
|
854
|
+
refresh: store.refresh
|
|
855
|
+
};
|
|
856
|
+
};
|
|
857
|
+
var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status") => {
|
|
858
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
customElements.define(tagName, class AbsoluteVoiceRoutingStatusElement extends HTMLElement {
|
|
862
|
+
mounted;
|
|
863
|
+
connectedCallback() {
|
|
864
|
+
const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
|
|
865
|
+
this.mounted = mountVoiceRoutingStatus(this, this.getAttribute("path") ?? "/api/routing/latest", {
|
|
866
|
+
description: this.getAttribute("description") ?? undefined,
|
|
867
|
+
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
868
|
+
title: this.getAttribute("title") ?? undefined
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
disconnectedCallback() {
|
|
872
|
+
this.mounted?.close();
|
|
873
|
+
this.mounted = undefined;
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
// src/react/VoiceRoutingStatus.tsx
|
|
879
|
+
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
880
|
+
var VoiceRoutingStatus = ({
|
|
881
|
+
className,
|
|
882
|
+
path = "/api/routing/latest",
|
|
883
|
+
...options
|
|
884
|
+
}) => {
|
|
885
|
+
const snapshot = useVoiceRoutingStatus(path, options);
|
|
886
|
+
const model = createVoiceRoutingStatusViewModel(snapshot, options);
|
|
887
|
+
return /* @__PURE__ */ jsxDEV3("section", {
|
|
888
|
+
className: [
|
|
889
|
+
"absolute-voice-routing-status",
|
|
890
|
+
`absolute-voice-routing-status--${model.status}`,
|
|
891
|
+
className
|
|
892
|
+
].filter(Boolean).join(" "),
|
|
893
|
+
children: [
|
|
894
|
+
/* @__PURE__ */ jsxDEV3("header", {
|
|
895
|
+
className: "absolute-voice-routing-status__header",
|
|
896
|
+
children: [
|
|
897
|
+
/* @__PURE__ */ jsxDEV3("span", {
|
|
898
|
+
className: "absolute-voice-routing-status__eyebrow",
|
|
899
|
+
children: model.title
|
|
900
|
+
}, undefined, false, undefined, this),
|
|
901
|
+
/* @__PURE__ */ jsxDEV3("strong", {
|
|
902
|
+
className: "absolute-voice-routing-status__label",
|
|
903
|
+
children: model.label
|
|
904
|
+
}, undefined, false, undefined, this)
|
|
905
|
+
]
|
|
906
|
+
}, undefined, true, undefined, this),
|
|
907
|
+
/* @__PURE__ */ jsxDEV3("p", {
|
|
908
|
+
className: "absolute-voice-routing-status__description",
|
|
909
|
+
children: model.description
|
|
910
|
+
}, undefined, false, undefined, this),
|
|
911
|
+
model.rows.length ? /* @__PURE__ */ jsxDEV3("div", {
|
|
912
|
+
className: "absolute-voice-routing-status__grid",
|
|
913
|
+
children: model.rows.map((row) => /* @__PURE__ */ jsxDEV3("div", {
|
|
914
|
+
children: [
|
|
915
|
+
/* @__PURE__ */ jsxDEV3("span", {
|
|
916
|
+
children: row.label
|
|
917
|
+
}, undefined, false, undefined, this),
|
|
918
|
+
/* @__PURE__ */ jsxDEV3("strong", {
|
|
919
|
+
children: row.value
|
|
920
|
+
}, undefined, false, undefined, this)
|
|
921
|
+
]
|
|
922
|
+
}, row.label, true, undefined, this))
|
|
923
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV3("p", {
|
|
924
|
+
className: "absolute-voice-routing-status__empty",
|
|
925
|
+
children: "Start a voice session to see the selected provider."
|
|
926
|
+
}, undefined, false, undefined, this),
|
|
927
|
+
model.error ? /* @__PURE__ */ jsxDEV3("p", {
|
|
928
|
+
className: "absolute-voice-routing-status__error",
|
|
929
|
+
children: model.error
|
|
930
|
+
}, undefined, false, undefined, this) : null
|
|
931
|
+
]
|
|
932
|
+
}, undefined, true, undefined, this);
|
|
933
|
+
};
|
|
934
|
+
// src/react/useVoiceStream.tsx
|
|
935
|
+
import { useEffect as useEffect4, useRef as useRef4, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
936
|
+
|
|
75
937
|
// src/client/actions.ts
|
|
76
938
|
var normalizeErrorMessage = (value) => {
|
|
77
939
|
if (typeof value === "string" && value.trim()) {
|
|
@@ -120,6 +982,12 @@ var serverMessageToAction = (message) => {
|
|
|
120
982
|
sessionId: message.sessionId,
|
|
121
983
|
type: "complete"
|
|
122
984
|
};
|
|
985
|
+
case "call_lifecycle":
|
|
986
|
+
return {
|
|
987
|
+
event: message.event,
|
|
988
|
+
sessionId: message.sessionId,
|
|
989
|
+
type: "call_lifecycle"
|
|
990
|
+
};
|
|
123
991
|
case "error":
|
|
124
992
|
return {
|
|
125
993
|
message: normalizeErrorMessage(message.message),
|
|
@@ -163,7 +1031,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
|
163
1031
|
var noop = () => {};
|
|
164
1032
|
var noopUnsubscribe = () => noop;
|
|
165
1033
|
var NOOP_CONNECTION = {
|
|
166
|
-
|
|
1034
|
+
callControl: noop,
|
|
167
1035
|
close: noop,
|
|
168
1036
|
endTurn: noop,
|
|
169
1037
|
getReadyState: () => WS_CLOSED,
|
|
@@ -171,6 +1039,7 @@ var NOOP_CONNECTION = {
|
|
|
171
1039
|
getSessionId: () => "",
|
|
172
1040
|
send: noop,
|
|
173
1041
|
sendAudio: noop,
|
|
1042
|
+
start: () => {},
|
|
174
1043
|
subscribe: noopUnsubscribe
|
|
175
1044
|
};
|
|
176
1045
|
var createSessionId = () => crypto.randomUUID();
|
|
@@ -192,6 +1061,7 @@ var isVoiceServerMessage = (value) => {
|
|
|
192
1061
|
switch (value.type) {
|
|
193
1062
|
case "audio":
|
|
194
1063
|
case "assistant":
|
|
1064
|
+
case "call_lifecycle":
|
|
195
1065
|
case "complete":
|
|
196
1066
|
case "error":
|
|
197
1067
|
case "final":
|
|
@@ -332,6 +1202,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
332
1202
|
const endTurn = () => {
|
|
333
1203
|
send({ type: "end_turn" });
|
|
334
1204
|
};
|
|
1205
|
+
const callControl = (message) => {
|
|
1206
|
+
send({
|
|
1207
|
+
...message,
|
|
1208
|
+
type: "call_control"
|
|
1209
|
+
});
|
|
1210
|
+
};
|
|
335
1211
|
const close = () => {
|
|
336
1212
|
clearTimers();
|
|
337
1213
|
if (state.ws) {
|
|
@@ -349,7 +1225,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
349
1225
|
};
|
|
350
1226
|
connect();
|
|
351
1227
|
return {
|
|
352
|
-
|
|
1228
|
+
callControl,
|
|
353
1229
|
close,
|
|
354
1230
|
endTurn,
|
|
355
1231
|
getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
|
|
@@ -357,6 +1233,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
357
1233
|
getSessionId: () => state.sessionId,
|
|
358
1234
|
send,
|
|
359
1235
|
sendAudio,
|
|
1236
|
+
start,
|
|
360
1237
|
subscribe
|
|
361
1238
|
};
|
|
362
1239
|
};
|
|
@@ -365,6 +1242,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
365
1242
|
var createInitialState = () => ({
|
|
366
1243
|
assistantAudio: [],
|
|
367
1244
|
assistantTexts: [],
|
|
1245
|
+
call: null,
|
|
368
1246
|
error: null,
|
|
369
1247
|
isConnected: false,
|
|
370
1248
|
scenarioId: null,
|
|
@@ -408,6 +1286,20 @@ var createVoiceStreamStore = () => {
|
|
|
408
1286
|
status: "completed"
|
|
409
1287
|
};
|
|
410
1288
|
break;
|
|
1289
|
+
case "call_lifecycle":
|
|
1290
|
+
state = {
|
|
1291
|
+
...state,
|
|
1292
|
+
call: {
|
|
1293
|
+
...state.call,
|
|
1294
|
+
disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
|
|
1295
|
+
endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
|
|
1296
|
+
events: [...state.call?.events ?? [], action.event],
|
|
1297
|
+
lastEventAt: action.event.at,
|
|
1298
|
+
startedAt: state.call?.startedAt ?? action.event.at
|
|
1299
|
+
},
|
|
1300
|
+
sessionId: action.sessionId
|
|
1301
|
+
};
|
|
1302
|
+
break;
|
|
411
1303
|
case "connected":
|
|
412
1304
|
state = {
|
|
413
1305
|
...state,
|
|
@@ -494,6 +1386,9 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
494
1386
|
}
|
|
495
1387
|
});
|
|
496
1388
|
return {
|
|
1389
|
+
callControl(message) {
|
|
1390
|
+
connection.callControl(message);
|
|
1391
|
+
},
|
|
497
1392
|
close() {
|
|
498
1393
|
unsubscribeConnection();
|
|
499
1394
|
connection.close();
|
|
@@ -537,6 +1432,9 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
537
1432
|
get assistantAudio() {
|
|
538
1433
|
return store.getSnapshot().assistantAudio;
|
|
539
1434
|
},
|
|
1435
|
+
get call() {
|
|
1436
|
+
return store.getSnapshot().call;
|
|
1437
|
+
},
|
|
540
1438
|
sendAudio(audio) {
|
|
541
1439
|
connection.sendAudio(audio);
|
|
542
1440
|
},
|
|
@@ -553,6 +1451,7 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
553
1451
|
var EMPTY_SNAPSHOT = {
|
|
554
1452
|
assistantAudio: [],
|
|
555
1453
|
assistantTexts: [],
|
|
1454
|
+
call: null,
|
|
556
1455
|
error: null,
|
|
557
1456
|
isConnected: false,
|
|
558
1457
|
partial: "",
|
|
@@ -561,22 +1460,23 @@ var EMPTY_SNAPSHOT = {
|
|
|
561
1460
|
turns: []
|
|
562
1461
|
};
|
|
563
1462
|
var useVoiceStream = (path, options = {}) => {
|
|
564
|
-
const streamRef =
|
|
1463
|
+
const streamRef = useRef4(null);
|
|
565
1464
|
if (!streamRef.current) {
|
|
566
1465
|
streamRef.current = createVoiceStream(path, options);
|
|
567
1466
|
}
|
|
568
1467
|
const stream = streamRef.current;
|
|
569
|
-
|
|
570
|
-
const snapshot =
|
|
1468
|
+
useEffect4(() => () => stream.close(), [stream]);
|
|
1469
|
+
const snapshot = useSyncExternalStore4(stream.subscribe, stream.getSnapshot, stream.getServerSnapshot) ?? EMPTY_SNAPSHOT;
|
|
571
1470
|
return {
|
|
572
1471
|
...snapshot,
|
|
1472
|
+
callControl: (message) => stream.callControl(message),
|
|
573
1473
|
close: () => stream.close(),
|
|
574
1474
|
endTurn: () => stream.endTurn(),
|
|
575
1475
|
sendAudio: (audio) => stream.sendAudio(audio)
|
|
576
1476
|
};
|
|
577
1477
|
};
|
|
578
1478
|
// src/react/useVoiceController.tsx
|
|
579
|
-
import { useEffect as
|
|
1479
|
+
import { useEffect as useEffect5, useRef as useRef5, useSyncExternalStore as useSyncExternalStore5 } from "react";
|
|
580
1480
|
|
|
581
1481
|
// src/client/htmx.ts
|
|
582
1482
|
var DEFAULT_EVENT_NAME = "voice-refresh";
|
|
@@ -1040,6 +1940,7 @@ var resolveVoiceRuntimePreset = (name = "default") => {
|
|
|
1040
1940
|
var createInitialState2 = (stream) => ({
|
|
1041
1941
|
assistantAudio: [...stream.assistantAudio],
|
|
1042
1942
|
assistantTexts: [...stream.assistantTexts],
|
|
1943
|
+
call: stream.call,
|
|
1043
1944
|
error: stream.error,
|
|
1044
1945
|
isConnected: stream.isConnected,
|
|
1045
1946
|
isRecording: false,
|
|
@@ -1069,6 +1970,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1069
1970
|
...state,
|
|
1070
1971
|
assistantAudio: [...stream.assistantAudio],
|
|
1071
1972
|
assistantTexts: [...stream.assistantTexts],
|
|
1973
|
+
call: stream.call,
|
|
1072
1974
|
error: stream.error,
|
|
1073
1975
|
isConnected: stream.isConnected,
|
|
1074
1976
|
partial: stream.partial,
|
|
@@ -1146,6 +2048,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1146
2048
|
bindHTMX(bindingOptions) {
|
|
1147
2049
|
return bindVoiceHTMX(stream, bindingOptions);
|
|
1148
2050
|
},
|
|
2051
|
+
callControl: (message) => stream.callControl(message),
|
|
1149
2052
|
close,
|
|
1150
2053
|
endTurn: () => stream.endTurn(),
|
|
1151
2054
|
get error() {
|
|
@@ -1198,6 +2101,9 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1198
2101
|
},
|
|
1199
2102
|
get assistantAudio() {
|
|
1200
2103
|
return state.assistantAudio;
|
|
2104
|
+
},
|
|
2105
|
+
get call() {
|
|
2106
|
+
return state.call;
|
|
1201
2107
|
}
|
|
1202
2108
|
};
|
|
1203
2109
|
};
|
|
@@ -1206,6 +2112,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1206
2112
|
var EMPTY_SNAPSHOT2 = {
|
|
1207
2113
|
assistantAudio: [],
|
|
1208
2114
|
assistantTexts: [],
|
|
2115
|
+
call: null,
|
|
1209
2116
|
error: null,
|
|
1210
2117
|
isConnected: false,
|
|
1211
2118
|
isRecording: false,
|
|
@@ -1216,16 +2123,17 @@ var EMPTY_SNAPSHOT2 = {
|
|
|
1216
2123
|
turns: []
|
|
1217
2124
|
};
|
|
1218
2125
|
var useVoiceController = (path, options = {}) => {
|
|
1219
|
-
const controllerRef =
|
|
2126
|
+
const controllerRef = useRef5(null);
|
|
1220
2127
|
if (!controllerRef.current) {
|
|
1221
2128
|
controllerRef.current = createVoiceController(path, options);
|
|
1222
2129
|
}
|
|
1223
2130
|
const controller = controllerRef.current;
|
|
1224
|
-
|
|
1225
|
-
const snapshot =
|
|
2131
|
+
useEffect5(() => () => controller.close(), [controller]);
|
|
2132
|
+
const snapshot = useSyncExternalStore5(controller.subscribe, controller.getSnapshot, controller.getServerSnapshot) ?? EMPTY_SNAPSHOT2;
|
|
1226
2133
|
return {
|
|
1227
2134
|
...snapshot,
|
|
1228
2135
|
bindHTMX: controller.bindHTMX,
|
|
2136
|
+
callControl: (message) => controller.callControl(message),
|
|
1229
2137
|
close: () => controller.close(),
|
|
1230
2138
|
endTurn: () => controller.endTurn(),
|
|
1231
2139
|
sendAudio: (audio) => controller.sendAudio(audio),
|
|
@@ -1234,7 +2142,112 @@ var useVoiceController = (path, options = {}) => {
|
|
|
1234
2142
|
toggleRecording: () => controller.toggleRecording()
|
|
1235
2143
|
};
|
|
1236
2144
|
};
|
|
2145
|
+
// src/react/useVoiceWorkflowStatus.tsx
|
|
2146
|
+
import { useEffect as useEffect6, useRef as useRef6, useSyncExternalStore as useSyncExternalStore6 } from "react";
|
|
2147
|
+
|
|
2148
|
+
// src/client/workflowStatus.ts
|
|
2149
|
+
var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
|
|
2150
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2151
|
+
const response = await fetchImpl(path);
|
|
2152
|
+
if (!response.ok) {
|
|
2153
|
+
throw new Error(`Voice workflow status failed: HTTP ${response.status}`);
|
|
2154
|
+
}
|
|
2155
|
+
return await response.json();
|
|
2156
|
+
};
|
|
2157
|
+
var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options = {}) => {
|
|
2158
|
+
const listeners = new Set;
|
|
2159
|
+
let closed = false;
|
|
2160
|
+
let timer;
|
|
2161
|
+
let snapshot = {
|
|
2162
|
+
error: null,
|
|
2163
|
+
isLoading: false
|
|
2164
|
+
};
|
|
2165
|
+
const emit = () => {
|
|
2166
|
+
for (const listener of listeners) {
|
|
2167
|
+
listener();
|
|
2168
|
+
}
|
|
2169
|
+
};
|
|
2170
|
+
const refresh = async () => {
|
|
2171
|
+
if (closed) {
|
|
2172
|
+
return snapshot.report;
|
|
2173
|
+
}
|
|
2174
|
+
snapshot = {
|
|
2175
|
+
...snapshot,
|
|
2176
|
+
error: null,
|
|
2177
|
+
isLoading: true
|
|
2178
|
+
};
|
|
2179
|
+
emit();
|
|
2180
|
+
try {
|
|
2181
|
+
const report = await fetchVoiceWorkflowStatus(path, options);
|
|
2182
|
+
snapshot = {
|
|
2183
|
+
error: null,
|
|
2184
|
+
isLoading: false,
|
|
2185
|
+
report,
|
|
2186
|
+
updatedAt: Date.now()
|
|
2187
|
+
};
|
|
2188
|
+
emit();
|
|
2189
|
+
return report;
|
|
2190
|
+
} catch (error) {
|
|
2191
|
+
snapshot = {
|
|
2192
|
+
...snapshot,
|
|
2193
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2194
|
+
isLoading: false
|
|
2195
|
+
};
|
|
2196
|
+
emit();
|
|
2197
|
+
throw error;
|
|
2198
|
+
}
|
|
2199
|
+
};
|
|
2200
|
+
const close = () => {
|
|
2201
|
+
closed = true;
|
|
2202
|
+
if (timer) {
|
|
2203
|
+
clearInterval(timer);
|
|
2204
|
+
timer = undefined;
|
|
2205
|
+
}
|
|
2206
|
+
listeners.clear();
|
|
2207
|
+
};
|
|
2208
|
+
if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
|
|
2209
|
+
timer = setInterval(() => {
|
|
2210
|
+
refresh().catch(() => {});
|
|
2211
|
+
}, options.intervalMs);
|
|
2212
|
+
}
|
|
2213
|
+
return {
|
|
2214
|
+
close,
|
|
2215
|
+
getServerSnapshot: () => snapshot,
|
|
2216
|
+
getSnapshot: () => snapshot,
|
|
2217
|
+
refresh,
|
|
2218
|
+
subscribe: (listener) => {
|
|
2219
|
+
listeners.add(listener);
|
|
2220
|
+
return () => {
|
|
2221
|
+
listeners.delete(listener);
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
};
|
|
2225
|
+
};
|
|
2226
|
+
|
|
2227
|
+
// src/react/useVoiceWorkflowStatus.tsx
|
|
2228
|
+
var useVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => {
|
|
2229
|
+
const storeRef = useRef6(null);
|
|
2230
|
+
if (!storeRef.current) {
|
|
2231
|
+
storeRef.current = createVoiceWorkflowStatusStore(path, options);
|
|
2232
|
+
}
|
|
2233
|
+
const store = storeRef.current;
|
|
2234
|
+
useEffect6(() => {
|
|
2235
|
+
store.refresh().catch(() => {});
|
|
2236
|
+
return () => store.close();
|
|
2237
|
+
}, [store]);
|
|
2238
|
+
return {
|
|
2239
|
+
...useSyncExternalStore6(store.subscribe, store.getSnapshot, store.getServerSnapshot),
|
|
2240
|
+
refresh: store.refresh
|
|
2241
|
+
};
|
|
2242
|
+
};
|
|
1237
2243
|
export {
|
|
2244
|
+
useVoiceWorkflowStatus,
|
|
1238
2245
|
useVoiceStream,
|
|
1239
|
-
|
|
2246
|
+
useVoiceRoutingStatus,
|
|
2247
|
+
useVoiceProviderStatus,
|
|
2248
|
+
useVoiceController,
|
|
2249
|
+
useVoiceAppKitStatus,
|
|
2250
|
+
VoiceRoutingStatus,
|
|
2251
|
+
VoiceProviderStatus,
|
|
2252
|
+
VoiceOpsStatus
|
|
1240
2253
|
};
|