@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/vue/index.js
CHANGED
|
@@ -69,8 +69,879 @@ 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/vue/
|
|
72
|
+
// src/vue/VoiceOpsStatus.ts
|
|
73
|
+
import { defineComponent, h } from "vue";
|
|
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/client/opsStatusWidget.ts
|
|
155
|
+
var DEFAULT_TITLE = "Voice Ops Status";
|
|
156
|
+
var DEFAULT_DESCRIPTION = "Certified workflow, provider, and handoff readiness from the AbsoluteJS voice app kit.";
|
|
157
|
+
var SURFACE_LABELS = {
|
|
158
|
+
handoffs: "Handoffs",
|
|
159
|
+
providers: "Providers",
|
|
160
|
+
quality: "Quality",
|
|
161
|
+
sessions: "Sessions",
|
|
162
|
+
workflows: "Workflows"
|
|
163
|
+
};
|
|
164
|
+
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
165
|
+
var readNumber = (value, key) => value && typeof value === "object" && (key in value) ? Number(value[key] ?? 0) : 0;
|
|
166
|
+
var surfaceDetail = (surface) => {
|
|
167
|
+
const total = readNumber(surface, "total");
|
|
168
|
+
const failed = readNumber(surface, "failed");
|
|
169
|
+
const degraded = readNumber(surface, "degraded");
|
|
170
|
+
const source = surface && typeof surface === "object" && "source" in surface && typeof surface.source === "string" ? ` from ${surface.source}` : "";
|
|
171
|
+
if (degraded > 0) {
|
|
172
|
+
return `${degraded} degraded of ${total}`;
|
|
173
|
+
}
|
|
174
|
+
if (failed > 0) {
|
|
175
|
+
return `${failed} failing of ${total}${source}`;
|
|
176
|
+
}
|
|
177
|
+
return total > 0 ? `${total} passing${source}` : `No failures${source}`;
|
|
178
|
+
};
|
|
179
|
+
var getVoiceOpsStatusLabel = (report, error) => {
|
|
180
|
+
if (error) {
|
|
181
|
+
return "Unavailable";
|
|
182
|
+
}
|
|
183
|
+
if (!report) {
|
|
184
|
+
return "Checking";
|
|
185
|
+
}
|
|
186
|
+
return report.status === "pass" ? "Passing" : "Needs attention";
|
|
187
|
+
};
|
|
188
|
+
var createVoiceOpsStatusViewModel = (snapshot, options = {}) => {
|
|
189
|
+
const report = snapshot.report;
|
|
190
|
+
const surfaces = Object.entries(report?.surfaces ?? {}).map(([id, surface]) => {
|
|
191
|
+
const failed = readNumber(surface, "failed") || readNumber(surface, "degraded");
|
|
192
|
+
const total = readNumber(surface, "total");
|
|
193
|
+
const status = surface && typeof surface === "object" && "status" in surface ? surface.status ?? "pass" : "pass";
|
|
194
|
+
return {
|
|
195
|
+
detail: surfaceDetail(surface),
|
|
196
|
+
failed,
|
|
197
|
+
id,
|
|
198
|
+
label: SURFACE_LABELS[id] ?? id,
|
|
199
|
+
status,
|
|
200
|
+
total
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
description: options.description ?? DEFAULT_DESCRIPTION,
|
|
205
|
+
error: snapshot.error,
|
|
206
|
+
isLoading: snapshot.isLoading,
|
|
207
|
+
label: getVoiceOpsStatusLabel(report, snapshot.error),
|
|
208
|
+
links: options.includeLinks === false ? [] : report?.links ?? [],
|
|
209
|
+
passed: report?.passed ?? 0,
|
|
210
|
+
status: snapshot.error ? "error" : report ? report.status : snapshot.isLoading ? "loading" : "loading",
|
|
211
|
+
surfaces,
|
|
212
|
+
title: options.title ?? DEFAULT_TITLE,
|
|
213
|
+
total: report?.total ?? 0,
|
|
214
|
+
updatedAt: snapshot.updatedAt
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
var renderVoiceOpsStatusHTML = (snapshot, options = {}) => {
|
|
218
|
+
const model = createVoiceOpsStatusViewModel(snapshot, options);
|
|
219
|
+
const surfaces = model.surfaces.length ? model.surfaces.map((surface) => `<li class="absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${escapeHtml(surface.status)}">
|
|
220
|
+
<span>${escapeHtml(surface.label)}</span>
|
|
221
|
+
<strong>${escapeHtml(surface.detail)}</strong>
|
|
222
|
+
</li>`).join("") : '<li class="absolute-voice-ops-status__surface"><span>Status</span><strong>Waiting for first check</strong></li>';
|
|
223
|
+
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>` : "";
|
|
224
|
+
return `<section class="absolute-voice-ops-status absolute-voice-ops-status--${escapeHtml(model.status)}">
|
|
225
|
+
<header class="absolute-voice-ops-status__header">
|
|
226
|
+
<span class="absolute-voice-ops-status__eyebrow">${escapeHtml(model.title)}</span>
|
|
227
|
+
<strong class="absolute-voice-ops-status__label">${escapeHtml(model.label)}</strong>
|
|
228
|
+
</header>
|
|
229
|
+
<p class="absolute-voice-ops-status__description">${escapeHtml(model.description)}</p>
|
|
230
|
+
<div class="absolute-voice-ops-status__summary">
|
|
231
|
+
<span>${model.passed} passing</span>
|
|
232
|
+
<span>${Math.max(model.total - model.passed, 0)} failing</span>
|
|
233
|
+
<span>${model.total} checks</span>
|
|
234
|
+
</div>
|
|
235
|
+
<ul class="absolute-voice-ops-status__surfaces">${surfaces}</ul>
|
|
236
|
+
${model.error ? `<p class="absolute-voice-ops-status__error">${escapeHtml(model.error)}</p>` : ""}
|
|
237
|
+
${links}
|
|
238
|
+
</section>`;
|
|
239
|
+
};
|
|
240
|
+
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}`;
|
|
241
|
+
var mountVoiceOpsStatus = (element, path = "/app-kit/status", options = {}) => {
|
|
242
|
+
const store = createVoiceAppKitStatusStore(path, options);
|
|
243
|
+
const render = () => {
|
|
244
|
+
element.innerHTML = renderVoiceOpsStatusHTML(store.getSnapshot(), options);
|
|
245
|
+
};
|
|
246
|
+
const unsubscribe = store.subscribe(render);
|
|
247
|
+
render();
|
|
248
|
+
store.refresh().catch(() => {});
|
|
249
|
+
return {
|
|
250
|
+
close: () => {
|
|
251
|
+
unsubscribe();
|
|
252
|
+
store.close();
|
|
253
|
+
},
|
|
254
|
+
refresh: store.refresh
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
var defineVoiceOpsStatusElement = (tagName = "absolute-voice-ops-status") => {
|
|
258
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
customElements.define(tagName, class AbsoluteVoiceOpsStatusElement extends HTMLElement {
|
|
262
|
+
mounted;
|
|
263
|
+
connectedCallback() {
|
|
264
|
+
const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
|
|
265
|
+
this.mounted = mountVoiceOpsStatus(this, this.getAttribute("path") ?? "/app-kit/status", {
|
|
266
|
+
description: this.getAttribute("description") ?? undefined,
|
|
267
|
+
includeLinks: this.getAttribute("include-links") !== "false",
|
|
268
|
+
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
269
|
+
title: this.getAttribute("title") ?? undefined
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
disconnectedCallback() {
|
|
273
|
+
this.mounted?.close();
|
|
274
|
+
this.mounted = undefined;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// src/vue/useVoiceAppKitStatus.ts
|
|
73
280
|
import { onUnmounted, ref, shallowRef } from "vue";
|
|
281
|
+
var useVoiceAppKitStatus = (path = "/app-kit/status", options = {}) => {
|
|
282
|
+
const store = createVoiceAppKitStatusStore(path, options);
|
|
283
|
+
const error = ref(null);
|
|
284
|
+
const isLoading = ref(false);
|
|
285
|
+
const report = shallowRef(undefined);
|
|
286
|
+
const updatedAt = ref(undefined);
|
|
287
|
+
const sync = () => {
|
|
288
|
+
const snapshot = store.getSnapshot();
|
|
289
|
+
error.value = snapshot.error;
|
|
290
|
+
isLoading.value = snapshot.isLoading;
|
|
291
|
+
report.value = snapshot.report;
|
|
292
|
+
updatedAt.value = snapshot.updatedAt;
|
|
293
|
+
};
|
|
294
|
+
const unsubscribe = store.subscribe(sync);
|
|
295
|
+
sync();
|
|
296
|
+
if (typeof window !== "undefined") {
|
|
297
|
+
store.refresh().catch(() => {});
|
|
298
|
+
}
|
|
299
|
+
onUnmounted(() => {
|
|
300
|
+
unsubscribe();
|
|
301
|
+
store.close();
|
|
302
|
+
});
|
|
303
|
+
return {
|
|
304
|
+
error,
|
|
305
|
+
isLoading,
|
|
306
|
+
refresh: store.refresh,
|
|
307
|
+
report,
|
|
308
|
+
updatedAt
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// src/vue/VoiceOpsStatus.ts
|
|
313
|
+
var VoiceOpsStatus = defineComponent({
|
|
314
|
+
name: "VoiceOpsStatus",
|
|
315
|
+
props: {
|
|
316
|
+
description: String,
|
|
317
|
+
includeLinks: {
|
|
318
|
+
default: true,
|
|
319
|
+
type: Boolean
|
|
320
|
+
},
|
|
321
|
+
intervalMs: Number,
|
|
322
|
+
path: {
|
|
323
|
+
default: "/app-kit/status",
|
|
324
|
+
type: String
|
|
325
|
+
},
|
|
326
|
+
title: String
|
|
327
|
+
},
|
|
328
|
+
setup(props) {
|
|
329
|
+
const options = {
|
|
330
|
+
description: props.description,
|
|
331
|
+
includeLinks: props.includeLinks,
|
|
332
|
+
intervalMs: props.intervalMs,
|
|
333
|
+
title: props.title
|
|
334
|
+
};
|
|
335
|
+
const status = useVoiceAppKitStatus(props.path, options);
|
|
336
|
+
return () => {
|
|
337
|
+
const model = createVoiceOpsStatusViewModel({
|
|
338
|
+
error: status.error.value,
|
|
339
|
+
isLoading: status.isLoading.value,
|
|
340
|
+
report: status.report.value,
|
|
341
|
+
updatedAt: status.updatedAt.value
|
|
342
|
+
}, options);
|
|
343
|
+
return h("section", {
|
|
344
|
+
class: [
|
|
345
|
+
"absolute-voice-ops-status",
|
|
346
|
+
`absolute-voice-ops-status--${model.status}`
|
|
347
|
+
]
|
|
348
|
+
}, [
|
|
349
|
+
h("header", { class: "absolute-voice-ops-status__header" }, [
|
|
350
|
+
h("span", { class: "absolute-voice-ops-status__eyebrow" }, model.title),
|
|
351
|
+
h("strong", { class: "absolute-voice-ops-status__label" }, model.label)
|
|
352
|
+
]),
|
|
353
|
+
h("p", { class: "absolute-voice-ops-status__description" }, model.description),
|
|
354
|
+
h("div", { class: "absolute-voice-ops-status__summary" }, [
|
|
355
|
+
h("span", `${model.passed} passing`),
|
|
356
|
+
h("span", `${Math.max(model.total - model.passed, 0)} failing`),
|
|
357
|
+
h("span", `${model.total} checks`)
|
|
358
|
+
]),
|
|
359
|
+
h("ul", { class: "absolute-voice-ops-status__surfaces" }, model.surfaces.length > 0 ? model.surfaces.map((surface) => h("li", {
|
|
360
|
+
class: [
|
|
361
|
+
"absolute-voice-ops-status__surface",
|
|
362
|
+
`absolute-voice-ops-status__surface--${surface.status}`
|
|
363
|
+
],
|
|
364
|
+
key: surface.id
|
|
365
|
+
}, [h("span", surface.label), h("strong", surface.detail)])) : [
|
|
366
|
+
h("li", { class: "absolute-voice-ops-status__surface" }, [
|
|
367
|
+
h("span", "Status"),
|
|
368
|
+
h("strong", "Waiting for first check")
|
|
369
|
+
])
|
|
370
|
+
]),
|
|
371
|
+
model.error ? h("p", { class: "absolute-voice-ops-status__error" }, model.error) : null,
|
|
372
|
+
model.links.length > 0 ? h("nav", { class: "absolute-voice-ops-status__links" }, model.links.slice(0, 4).map((link) => h("a", { href: link.href, key: link.href }, link.label))) : null
|
|
373
|
+
]);
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
// src/vue/VoiceProviderStatus.ts
|
|
378
|
+
import { computed, defineComponent as defineComponent2, h as h2 } from "vue";
|
|
379
|
+
|
|
380
|
+
// src/client/providerStatus.ts
|
|
381
|
+
var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
|
|
382
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
383
|
+
const response = await fetchImpl(path);
|
|
384
|
+
if (!response.ok) {
|
|
385
|
+
throw new Error(`Voice provider status failed: HTTP ${response.status}`);
|
|
386
|
+
}
|
|
387
|
+
return await response.json();
|
|
388
|
+
};
|
|
389
|
+
var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
|
|
390
|
+
const listeners = new Set;
|
|
391
|
+
let closed = false;
|
|
392
|
+
let timer;
|
|
393
|
+
let snapshot = {
|
|
394
|
+
error: null,
|
|
395
|
+
isLoading: false,
|
|
396
|
+
providers: []
|
|
397
|
+
};
|
|
398
|
+
const emit = () => {
|
|
399
|
+
for (const listener of listeners) {
|
|
400
|
+
listener();
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
const refresh = async () => {
|
|
404
|
+
if (closed) {
|
|
405
|
+
return snapshot.providers;
|
|
406
|
+
}
|
|
407
|
+
snapshot = {
|
|
408
|
+
...snapshot,
|
|
409
|
+
error: null,
|
|
410
|
+
isLoading: true
|
|
411
|
+
};
|
|
412
|
+
emit();
|
|
413
|
+
try {
|
|
414
|
+
const providers = await fetchVoiceProviderStatus(path, options);
|
|
415
|
+
snapshot = {
|
|
416
|
+
error: null,
|
|
417
|
+
isLoading: false,
|
|
418
|
+
providers,
|
|
419
|
+
updatedAt: Date.now()
|
|
420
|
+
};
|
|
421
|
+
emit();
|
|
422
|
+
return providers;
|
|
423
|
+
} catch (error) {
|
|
424
|
+
snapshot = {
|
|
425
|
+
...snapshot,
|
|
426
|
+
error: error instanceof Error ? error.message : String(error),
|
|
427
|
+
isLoading: false
|
|
428
|
+
};
|
|
429
|
+
emit();
|
|
430
|
+
throw error;
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
const close = () => {
|
|
434
|
+
closed = true;
|
|
435
|
+
if (timer) {
|
|
436
|
+
clearInterval(timer);
|
|
437
|
+
timer = undefined;
|
|
438
|
+
}
|
|
439
|
+
listeners.clear();
|
|
440
|
+
};
|
|
441
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
442
|
+
timer = setInterval(() => {
|
|
443
|
+
refresh().catch(() => {});
|
|
444
|
+
}, options.intervalMs);
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
close,
|
|
448
|
+
getServerSnapshot: () => snapshot,
|
|
449
|
+
getSnapshot: () => snapshot,
|
|
450
|
+
refresh,
|
|
451
|
+
subscribe: (listener) => {
|
|
452
|
+
listeners.add(listener);
|
|
453
|
+
return () => {
|
|
454
|
+
listeners.delete(listener);
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// src/client/providerStatusWidget.ts
|
|
461
|
+
var DEFAULT_TITLE2 = "Voice Providers";
|
|
462
|
+
var DEFAULT_DESCRIPTION2 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
|
|
463
|
+
var escapeHtml2 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
464
|
+
var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
465
|
+
var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
466
|
+
var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
|
|
467
|
+
var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
|
|
468
|
+
var getProviderDetail = (provider) => {
|
|
469
|
+
if (provider.status === "suppressed") {
|
|
470
|
+
return provider.lastError ? `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)} after ${provider.lastError}.` : `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)}.`;
|
|
471
|
+
}
|
|
472
|
+
if (provider.status === "recoverable") {
|
|
473
|
+
return "Cooldown expired; ready for recovery traffic.";
|
|
474
|
+
}
|
|
475
|
+
if (provider.status === "rate-limited") {
|
|
476
|
+
return "Rate limit detected; router should avoid this provider.";
|
|
477
|
+
}
|
|
478
|
+
if (provider.status === "degraded") {
|
|
479
|
+
return provider.lastError ?? "Recent provider errors detected.";
|
|
480
|
+
}
|
|
481
|
+
if (provider.status === "healthy") {
|
|
482
|
+
return provider.recommended ? "Healthy and currently recommended." : "Healthy and available for routing.";
|
|
483
|
+
}
|
|
484
|
+
return "No provider traffic observed yet.";
|
|
485
|
+
};
|
|
486
|
+
var isWarningStatus = (status) => status === "degraded" || status === "rate-limited" || status === "recoverable" || status === "suppressed";
|
|
487
|
+
var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
|
|
488
|
+
const providers = snapshot.providers.map((provider) => ({
|
|
489
|
+
...provider,
|
|
490
|
+
detail: getProviderDetail(provider),
|
|
491
|
+
label: `${formatProvider(provider.provider)}${provider.recommended ? " recommended" : ""}`,
|
|
492
|
+
rows: [
|
|
493
|
+
{ label: "Runs", value: String(provider.runCount) },
|
|
494
|
+
{ label: "Avg latency", value: formatLatency(provider.averageElapsedMs) },
|
|
495
|
+
{ label: "Errors", value: String(provider.errorCount) },
|
|
496
|
+
{ label: "Timeouts", value: String(provider.timeoutCount) },
|
|
497
|
+
{ label: "Fallbacks", value: String(provider.fallbackCount) },
|
|
498
|
+
{
|
|
499
|
+
label: "Suppression",
|
|
500
|
+
value: formatSuppression(provider.suppressionRemainingMs)
|
|
501
|
+
}
|
|
502
|
+
]
|
|
503
|
+
}));
|
|
504
|
+
const warningCount = providers.filter((provider) => isWarningStatus(provider.status)).length;
|
|
505
|
+
const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
|
|
506
|
+
return {
|
|
507
|
+
description: options.description ?? DEFAULT_DESCRIPTION2,
|
|
508
|
+
error: snapshot.error,
|
|
509
|
+
isLoading: snapshot.isLoading,
|
|
510
|
+
label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
|
|
511
|
+
providers,
|
|
512
|
+
status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
513
|
+
title: options.title ?? DEFAULT_TITLE2,
|
|
514
|
+
updatedAt: snapshot.updatedAt
|
|
515
|
+
};
|
|
516
|
+
};
|
|
517
|
+
var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
|
|
518
|
+
const model = createVoiceProviderStatusViewModel(snapshot, options);
|
|
519
|
+
const providers = model.providers.length ? `<div class="absolute-voice-provider-status__providers">${model.providers.map((provider) => `<article class="absolute-voice-provider-status__provider absolute-voice-provider-status__provider--${escapeHtml2(provider.status)}">
|
|
520
|
+
<header>
|
|
521
|
+
<strong>${escapeHtml2(provider.label)}</strong>
|
|
522
|
+
<span>${escapeHtml2(formatStatus(provider.status))}</span>
|
|
523
|
+
</header>
|
|
524
|
+
<p>${escapeHtml2(provider.detail)}</p>
|
|
525
|
+
<dl>${provider.rows.map((row) => `<div>
|
|
526
|
+
<dt>${escapeHtml2(row.label)}</dt>
|
|
527
|
+
<dd>${escapeHtml2(row.value)}</dd>
|
|
528
|
+
</div>`).join("")}</dl>
|
|
529
|
+
</article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
|
|
530
|
+
return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml2(model.status)}">
|
|
531
|
+
<header class="absolute-voice-provider-status__header">
|
|
532
|
+
<span class="absolute-voice-provider-status__eyebrow">${escapeHtml2(model.title)}</span>
|
|
533
|
+
<strong class="absolute-voice-provider-status__label">${escapeHtml2(model.label)}</strong>
|
|
534
|
+
</header>
|
|
535
|
+
<p class="absolute-voice-provider-status__description">${escapeHtml2(model.description)}</p>
|
|
536
|
+
${providers}
|
|
537
|
+
${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml2(model.error)}</p>` : ""}
|
|
538
|
+
</section>`;
|
|
539
|
+
};
|
|
540
|
+
var getVoiceProviderStatusCSS = () => `.absolute-voice-provider-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-provider-status--error,.absolute-voice-provider-status--warning{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-provider-status__header,.absolute-voice-provider-status__provider header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-provider-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-provider-status__label{font-size:24px;line-height:1}.absolute-voice-provider-status__description,.absolute-voice-provider-status__provider p,.absolute-voice-provider-status__provider dt,.absolute-voice-provider-status__empty{color:#514733}.absolute-voice-provider-status__providers{display:grid;gap:12px;margin-top:14px}.absolute-voice-provider-status__provider{background:#fff;border:1px solid #eee4d2;border-radius:16px;padding:14px}.absolute-voice-provider-status__provider--degraded,.absolute-voice-provider-status__provider--rate-limited,.absolute-voice-provider-status__provider--suppressed{border-color:#f2a7a7}.absolute-voice-provider-status__provider--recoverable{border-color:#fbbf24}.absolute-voice-provider-status__provider p{margin:10px 0}.absolute-voice-provider-status__provider dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}.absolute-voice-provider-status__provider div{background:#fffaf0;border:1px solid #eee4d2;border-radius:12px;padding:8px}.absolute-voice-provider-status__provider dt{font-size:12px}.absolute-voice-provider-status__provider dd{font-weight:800;margin:4px 0 0}.absolute-voice-provider-status__empty{margin:14px 0 0}.absolute-voice-provider-status__error{color:#9f1239;font-weight:700}`;
|
|
541
|
+
var mountVoiceProviderStatus = (element, path = "/api/provider-status", options = {}) => {
|
|
542
|
+
const store = createVoiceProviderStatusStore(path, options);
|
|
543
|
+
const render = () => {
|
|
544
|
+
element.innerHTML = renderVoiceProviderStatusHTML(store.getSnapshot(), options);
|
|
545
|
+
};
|
|
546
|
+
const unsubscribe = store.subscribe(render);
|
|
547
|
+
render();
|
|
548
|
+
store.refresh().catch(() => {});
|
|
549
|
+
return {
|
|
550
|
+
close: () => {
|
|
551
|
+
unsubscribe();
|
|
552
|
+
store.close();
|
|
553
|
+
},
|
|
554
|
+
refresh: store.refresh
|
|
555
|
+
};
|
|
556
|
+
};
|
|
557
|
+
var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-status") => {
|
|
558
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
customElements.define(tagName, class AbsoluteVoiceProviderStatusElement extends HTMLElement {
|
|
562
|
+
mounted;
|
|
563
|
+
connectedCallback() {
|
|
564
|
+
const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
|
|
565
|
+
this.mounted = mountVoiceProviderStatus(this, this.getAttribute("path") ?? "/api/provider-status", {
|
|
566
|
+
description: this.getAttribute("description") ?? undefined,
|
|
567
|
+
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
568
|
+
title: this.getAttribute("title") ?? undefined
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
disconnectedCallback() {
|
|
572
|
+
this.mounted?.close();
|
|
573
|
+
this.mounted = undefined;
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
// src/vue/useVoiceProviderStatus.ts
|
|
579
|
+
import { onUnmounted as onUnmounted2, ref as ref2, shallowRef as shallowRef2 } from "vue";
|
|
580
|
+
var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
|
|
581
|
+
const store = createVoiceProviderStatusStore(path, options);
|
|
582
|
+
const error = ref2(null);
|
|
583
|
+
const isLoading = ref2(false);
|
|
584
|
+
const providers = shallowRef2([]);
|
|
585
|
+
const updatedAt = ref2(undefined);
|
|
586
|
+
const sync = () => {
|
|
587
|
+
const snapshot = store.getSnapshot();
|
|
588
|
+
error.value = snapshot.error;
|
|
589
|
+
isLoading.value = snapshot.isLoading;
|
|
590
|
+
providers.value = [...snapshot.providers];
|
|
591
|
+
updatedAt.value = snapshot.updatedAt;
|
|
592
|
+
};
|
|
593
|
+
const unsubscribe = store.subscribe(sync);
|
|
594
|
+
sync();
|
|
595
|
+
store.refresh().catch(() => {});
|
|
596
|
+
onUnmounted2(() => {
|
|
597
|
+
unsubscribe();
|
|
598
|
+
store.close();
|
|
599
|
+
});
|
|
600
|
+
return {
|
|
601
|
+
error,
|
|
602
|
+
isLoading,
|
|
603
|
+
providers,
|
|
604
|
+
refresh: store.refresh,
|
|
605
|
+
updatedAt
|
|
606
|
+
};
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// src/vue/VoiceProviderStatus.ts
|
|
610
|
+
var VoiceProviderStatus = defineComponent2({
|
|
611
|
+
name: "VoiceProviderStatus",
|
|
612
|
+
props: {
|
|
613
|
+
class: {
|
|
614
|
+
default: "",
|
|
615
|
+
type: String
|
|
616
|
+
},
|
|
617
|
+
description: {
|
|
618
|
+
default: undefined,
|
|
619
|
+
type: String
|
|
620
|
+
},
|
|
621
|
+
intervalMs: {
|
|
622
|
+
default: 5000,
|
|
623
|
+
type: Number
|
|
624
|
+
},
|
|
625
|
+
path: {
|
|
626
|
+
default: "/api/provider-status",
|
|
627
|
+
type: String
|
|
628
|
+
},
|
|
629
|
+
title: {
|
|
630
|
+
default: undefined,
|
|
631
|
+
type: String
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
setup(props) {
|
|
635
|
+
const options = {
|
|
636
|
+
description: props.description,
|
|
637
|
+
intervalMs: props.intervalMs,
|
|
638
|
+
title: props.title
|
|
639
|
+
};
|
|
640
|
+
const status = useVoiceProviderStatus(props.path, options);
|
|
641
|
+
const model = computed(() => createVoiceProviderStatusViewModel({
|
|
642
|
+
error: status.error.value,
|
|
643
|
+
isLoading: status.isLoading.value,
|
|
644
|
+
providers: status.providers.value,
|
|
645
|
+
updatedAt: status.updatedAt.value
|
|
646
|
+
}, options));
|
|
647
|
+
return () => h2("section", {
|
|
648
|
+
class: [
|
|
649
|
+
"absolute-voice-provider-status",
|
|
650
|
+
`absolute-voice-provider-status--${model.value.status}`,
|
|
651
|
+
props.class
|
|
652
|
+
]
|
|
653
|
+
}, [
|
|
654
|
+
h2("header", { class: "absolute-voice-provider-status__header" }, [
|
|
655
|
+
h2("span", { class: "absolute-voice-provider-status__eyebrow" }, model.value.title),
|
|
656
|
+
h2("strong", { class: "absolute-voice-provider-status__label" }, model.value.label)
|
|
657
|
+
]),
|
|
658
|
+
h2("p", { class: "absolute-voice-provider-status__description" }, model.value.description),
|
|
659
|
+
model.value.providers.length ? h2("div", { class: "absolute-voice-provider-status__providers" }, model.value.providers.map((provider) => h2("article", {
|
|
660
|
+
class: [
|
|
661
|
+
"absolute-voice-provider-status__provider",
|
|
662
|
+
`absolute-voice-provider-status__provider--${provider.status}`
|
|
663
|
+
],
|
|
664
|
+
key: provider.provider
|
|
665
|
+
}, [
|
|
666
|
+
h2("header", [
|
|
667
|
+
h2("strong", provider.label),
|
|
668
|
+
h2("span", provider.status)
|
|
669
|
+
]),
|
|
670
|
+
h2("p", provider.detail),
|
|
671
|
+
h2("dl", provider.rows.map((row) => h2("div", { key: row.label }, [
|
|
672
|
+
h2("dt", row.label),
|
|
673
|
+
h2("dd", row.value)
|
|
674
|
+
])))
|
|
675
|
+
]))) : h2("p", { class: "absolute-voice-provider-status__empty" }, "Run voice traffic to see provider health."),
|
|
676
|
+
model.value.error ? h2("p", { class: "absolute-voice-provider-status__error" }, model.value.error) : null
|
|
677
|
+
]);
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
// src/vue/VoiceRoutingStatus.ts
|
|
681
|
+
import { computed as computed2, defineComponent as defineComponent3, h as h3 } from "vue";
|
|
682
|
+
|
|
683
|
+
// src/client/routingStatus.ts
|
|
684
|
+
var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
|
|
685
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
686
|
+
const response = await fetchImpl(path);
|
|
687
|
+
if (!response.ok) {
|
|
688
|
+
throw new Error(`Voice routing status failed: HTTP ${response.status}`);
|
|
689
|
+
}
|
|
690
|
+
return await response.json();
|
|
691
|
+
};
|
|
692
|
+
var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
|
|
693
|
+
const listeners = new Set;
|
|
694
|
+
let closed = false;
|
|
695
|
+
let timer;
|
|
696
|
+
let snapshot = {
|
|
697
|
+
decision: null,
|
|
698
|
+
error: null,
|
|
699
|
+
isLoading: false
|
|
700
|
+
};
|
|
701
|
+
const emit = () => {
|
|
702
|
+
for (const listener of listeners) {
|
|
703
|
+
listener();
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
const refresh = async () => {
|
|
707
|
+
if (closed) {
|
|
708
|
+
return snapshot.decision;
|
|
709
|
+
}
|
|
710
|
+
snapshot = {
|
|
711
|
+
...snapshot,
|
|
712
|
+
error: null,
|
|
713
|
+
isLoading: true
|
|
714
|
+
};
|
|
715
|
+
emit();
|
|
716
|
+
try {
|
|
717
|
+
const decision = await fetchVoiceRoutingStatus(path, options);
|
|
718
|
+
snapshot = {
|
|
719
|
+
decision,
|
|
720
|
+
error: null,
|
|
721
|
+
isLoading: false,
|
|
722
|
+
updatedAt: Date.now()
|
|
723
|
+
};
|
|
724
|
+
emit();
|
|
725
|
+
return decision;
|
|
726
|
+
} catch (error) {
|
|
727
|
+
snapshot = {
|
|
728
|
+
...snapshot,
|
|
729
|
+
error: error instanceof Error ? error.message : String(error),
|
|
730
|
+
isLoading: false
|
|
731
|
+
};
|
|
732
|
+
emit();
|
|
733
|
+
throw error;
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
const close = () => {
|
|
737
|
+
closed = true;
|
|
738
|
+
if (timer) {
|
|
739
|
+
clearInterval(timer);
|
|
740
|
+
timer = undefined;
|
|
741
|
+
}
|
|
742
|
+
listeners.clear();
|
|
743
|
+
};
|
|
744
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
745
|
+
timer = setInterval(() => {
|
|
746
|
+
refresh().catch(() => {});
|
|
747
|
+
}, options.intervalMs);
|
|
748
|
+
}
|
|
749
|
+
return {
|
|
750
|
+
close,
|
|
751
|
+
getServerSnapshot: () => snapshot,
|
|
752
|
+
getSnapshot: () => snapshot,
|
|
753
|
+
refresh,
|
|
754
|
+
subscribe: (listener) => {
|
|
755
|
+
listeners.add(listener);
|
|
756
|
+
return () => {
|
|
757
|
+
listeners.delete(listener);
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
// src/client/routingStatusWidget.ts
|
|
764
|
+
var DEFAULT_TITLE3 = "Voice Routing";
|
|
765
|
+
var DEFAULT_DESCRIPTION3 = "Latest provider routing decision from the self-hosted trace store.";
|
|
766
|
+
var escapeHtml3 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
767
|
+
var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
|
|
768
|
+
var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
|
|
769
|
+
const decision = snapshot.decision;
|
|
770
|
+
const rows = decision ? [
|
|
771
|
+
{ label: "Kind", value: decision.kind.toUpperCase() },
|
|
772
|
+
{ label: "Policy", value: formatValue(decision.routing, "Unknown") },
|
|
773
|
+
{ label: "Provider", value: formatValue(decision.provider, "Unknown") },
|
|
774
|
+
{
|
|
775
|
+
label: "Selected",
|
|
776
|
+
value: formatValue(decision.selectedProvider, "Unknown")
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
label: "Fallback",
|
|
780
|
+
value: formatValue(decision.fallbackProvider)
|
|
781
|
+
},
|
|
782
|
+
{ label: "Status", value: formatValue(decision.status, "unknown") },
|
|
783
|
+
{
|
|
784
|
+
label: "Latency budget",
|
|
785
|
+
value: typeof decision.latencyBudgetMs === "number" ? `${decision.latencyBudgetMs}ms` : "None"
|
|
786
|
+
}
|
|
787
|
+
] : [];
|
|
788
|
+
return {
|
|
789
|
+
decision,
|
|
790
|
+
description: options.description ?? DEFAULT_DESCRIPTION3,
|
|
791
|
+
error: snapshot.error,
|
|
792
|
+
isLoading: snapshot.isLoading,
|
|
793
|
+
label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
|
|
794
|
+
rows,
|
|
795
|
+
status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
796
|
+
title: options.title ?? DEFAULT_TITLE3,
|
|
797
|
+
updatedAt: snapshot.updatedAt
|
|
798
|
+
};
|
|
799
|
+
};
|
|
800
|
+
var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
|
|
801
|
+
const model = createVoiceRoutingStatusViewModel(snapshot, options);
|
|
802
|
+
const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
|
|
803
|
+
<span>${escapeHtml3(row.label)}</span>
|
|
804
|
+
<strong>${escapeHtml3(row.value)}</strong>
|
|
805
|
+
</div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
|
|
806
|
+
return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml3(model.status)}">
|
|
807
|
+
<header class="absolute-voice-routing-status__header">
|
|
808
|
+
<span class="absolute-voice-routing-status__eyebrow">${escapeHtml3(model.title)}</span>
|
|
809
|
+
<strong class="absolute-voice-routing-status__label">${escapeHtml3(model.label)}</strong>
|
|
810
|
+
</header>
|
|
811
|
+
<p class="absolute-voice-routing-status__description">${escapeHtml3(model.description)}</p>
|
|
812
|
+
${rows}
|
|
813
|
+
${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml3(model.error)}</p>` : ""}
|
|
814
|
+
</section>`;
|
|
815
|
+
};
|
|
816
|
+
var getVoiceRoutingStatusCSS = () => `.absolute-voice-routing-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-routing-status--error{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-routing-status__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-routing-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-routing-status__label{font-size:24px;line-height:1}.absolute-voice-routing-status__description{color:#514733;margin:12px 0 0}.absolute-voice-routing-status__grid{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin-top:14px}.absolute-voice-routing-status__grid div{background:#fff;border:1px solid #eee4d2;border-radius:14px;padding:10px 12px}.absolute-voice-routing-status__grid span{color:#655944;display:block;font-size:12px;margin-bottom:4px}.absolute-voice-routing-status__grid strong{overflow-wrap:anywhere}.absolute-voice-routing-status__empty{color:#655944;margin:14px 0 0}.absolute-voice-routing-status__error{color:#9f1239;font-weight:700}`;
|
|
817
|
+
var mountVoiceRoutingStatus = (element, path = "/api/routing/latest", options = {}) => {
|
|
818
|
+
const store = createVoiceRoutingStatusStore(path, options);
|
|
819
|
+
const render = () => {
|
|
820
|
+
element.innerHTML = renderVoiceRoutingStatusHTML(store.getSnapshot(), options);
|
|
821
|
+
};
|
|
822
|
+
const unsubscribe = store.subscribe(render);
|
|
823
|
+
render();
|
|
824
|
+
store.refresh().catch(() => {});
|
|
825
|
+
return {
|
|
826
|
+
close: () => {
|
|
827
|
+
unsubscribe();
|
|
828
|
+
store.close();
|
|
829
|
+
},
|
|
830
|
+
refresh: store.refresh
|
|
831
|
+
};
|
|
832
|
+
};
|
|
833
|
+
var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status") => {
|
|
834
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
customElements.define(tagName, class AbsoluteVoiceRoutingStatusElement extends HTMLElement {
|
|
838
|
+
mounted;
|
|
839
|
+
connectedCallback() {
|
|
840
|
+
const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
|
|
841
|
+
this.mounted = mountVoiceRoutingStatus(this, this.getAttribute("path") ?? "/api/routing/latest", {
|
|
842
|
+
description: this.getAttribute("description") ?? undefined,
|
|
843
|
+
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
844
|
+
title: this.getAttribute("title") ?? undefined
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
disconnectedCallback() {
|
|
848
|
+
this.mounted?.close();
|
|
849
|
+
this.mounted = undefined;
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
// src/vue/useVoiceRoutingStatus.ts
|
|
855
|
+
import { onUnmounted as onUnmounted3, ref as ref3, shallowRef as shallowRef3 } from "vue";
|
|
856
|
+
var useVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
|
|
857
|
+
const store = createVoiceRoutingStatusStore(path, options);
|
|
858
|
+
const decision = shallowRef3(null);
|
|
859
|
+
const error = ref3(null);
|
|
860
|
+
const isLoading = ref3(false);
|
|
861
|
+
const updatedAt = ref3(undefined);
|
|
862
|
+
const sync = () => {
|
|
863
|
+
const snapshot = store.getSnapshot();
|
|
864
|
+
decision.value = snapshot.decision;
|
|
865
|
+
error.value = snapshot.error;
|
|
866
|
+
isLoading.value = snapshot.isLoading;
|
|
867
|
+
updatedAt.value = snapshot.updatedAt;
|
|
868
|
+
};
|
|
869
|
+
const unsubscribe = store.subscribe(sync);
|
|
870
|
+
sync();
|
|
871
|
+
store.refresh().catch(() => {});
|
|
872
|
+
onUnmounted3(() => {
|
|
873
|
+
unsubscribe();
|
|
874
|
+
store.close();
|
|
875
|
+
});
|
|
876
|
+
return {
|
|
877
|
+
decision,
|
|
878
|
+
error,
|
|
879
|
+
isLoading,
|
|
880
|
+
refresh: store.refresh,
|
|
881
|
+
updatedAt
|
|
882
|
+
};
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
// src/vue/VoiceRoutingStatus.ts
|
|
886
|
+
var VoiceRoutingStatus = defineComponent3({
|
|
887
|
+
name: "VoiceRoutingStatus",
|
|
888
|
+
props: {
|
|
889
|
+
class: {
|
|
890
|
+
default: "",
|
|
891
|
+
type: String
|
|
892
|
+
},
|
|
893
|
+
description: {
|
|
894
|
+
default: undefined,
|
|
895
|
+
type: String
|
|
896
|
+
},
|
|
897
|
+
intervalMs: {
|
|
898
|
+
default: 5000,
|
|
899
|
+
type: Number
|
|
900
|
+
},
|
|
901
|
+
path: {
|
|
902
|
+
default: "/api/routing/latest",
|
|
903
|
+
type: String
|
|
904
|
+
},
|
|
905
|
+
title: {
|
|
906
|
+
default: undefined,
|
|
907
|
+
type: String
|
|
908
|
+
}
|
|
909
|
+
},
|
|
910
|
+
setup(props) {
|
|
911
|
+
const options = {
|
|
912
|
+
description: props.description,
|
|
913
|
+
intervalMs: props.intervalMs,
|
|
914
|
+
title: props.title
|
|
915
|
+
};
|
|
916
|
+
const status = useVoiceRoutingStatus(props.path, options);
|
|
917
|
+
const model = computed2(() => createVoiceRoutingStatusViewModel({
|
|
918
|
+
decision: status.decision.value,
|
|
919
|
+
error: status.error.value,
|
|
920
|
+
isLoading: status.isLoading.value,
|
|
921
|
+
updatedAt: status.updatedAt.value
|
|
922
|
+
}, options));
|
|
923
|
+
return () => h3("section", {
|
|
924
|
+
class: [
|
|
925
|
+
"absolute-voice-routing-status",
|
|
926
|
+
`absolute-voice-routing-status--${model.value.status}`,
|
|
927
|
+
props.class
|
|
928
|
+
]
|
|
929
|
+
}, [
|
|
930
|
+
h3("header", { class: "absolute-voice-routing-status__header" }, [
|
|
931
|
+
h3("span", { class: "absolute-voice-routing-status__eyebrow" }, model.value.title),
|
|
932
|
+
h3("strong", { class: "absolute-voice-routing-status__label" }, model.value.label)
|
|
933
|
+
]),
|
|
934
|
+
h3("p", { class: "absolute-voice-routing-status__description" }, model.value.description),
|
|
935
|
+
model.value.rows.length ? h3("div", { class: "absolute-voice-routing-status__grid" }, model.value.rows.map((row) => h3("div", { key: row.label }, [
|
|
936
|
+
h3("span", row.label),
|
|
937
|
+
h3("strong", row.value)
|
|
938
|
+
]))) : h3("p", { class: "absolute-voice-routing-status__empty" }, "Start a voice session to see the selected provider."),
|
|
939
|
+
model.value.error ? h3("p", { class: "absolute-voice-routing-status__error" }, model.value.error) : null
|
|
940
|
+
]);
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
// src/vue/useVoiceStream.ts
|
|
944
|
+
import { onUnmounted as onUnmounted4, ref as ref4, shallowRef as shallowRef4 } from "vue";
|
|
74
945
|
|
|
75
946
|
// src/client/actions.ts
|
|
76
947
|
var normalizeErrorMessage = (value) => {
|
|
@@ -120,6 +991,12 @@ var serverMessageToAction = (message) => {
|
|
|
120
991
|
sessionId: message.sessionId,
|
|
121
992
|
type: "complete"
|
|
122
993
|
};
|
|
994
|
+
case "call_lifecycle":
|
|
995
|
+
return {
|
|
996
|
+
event: message.event,
|
|
997
|
+
sessionId: message.sessionId,
|
|
998
|
+
type: "call_lifecycle"
|
|
999
|
+
};
|
|
123
1000
|
case "error":
|
|
124
1001
|
return {
|
|
125
1002
|
message: normalizeErrorMessage(message.message),
|
|
@@ -163,7 +1040,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
|
163
1040
|
var noop = () => {};
|
|
164
1041
|
var noopUnsubscribe = () => noop;
|
|
165
1042
|
var NOOP_CONNECTION = {
|
|
166
|
-
|
|
1043
|
+
callControl: noop,
|
|
167
1044
|
close: noop,
|
|
168
1045
|
endTurn: noop,
|
|
169
1046
|
getReadyState: () => WS_CLOSED,
|
|
@@ -171,6 +1048,7 @@ var NOOP_CONNECTION = {
|
|
|
171
1048
|
getSessionId: () => "",
|
|
172
1049
|
send: noop,
|
|
173
1050
|
sendAudio: noop,
|
|
1051
|
+
start: () => {},
|
|
174
1052
|
subscribe: noopUnsubscribe
|
|
175
1053
|
};
|
|
176
1054
|
var createSessionId = () => crypto.randomUUID();
|
|
@@ -192,6 +1070,7 @@ var isVoiceServerMessage = (value) => {
|
|
|
192
1070
|
switch (value.type) {
|
|
193
1071
|
case "audio":
|
|
194
1072
|
case "assistant":
|
|
1073
|
+
case "call_lifecycle":
|
|
195
1074
|
case "complete":
|
|
196
1075
|
case "error":
|
|
197
1076
|
case "final":
|
|
@@ -332,6 +1211,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
332
1211
|
const endTurn = () => {
|
|
333
1212
|
send({ type: "end_turn" });
|
|
334
1213
|
};
|
|
1214
|
+
const callControl = (message) => {
|
|
1215
|
+
send({
|
|
1216
|
+
...message,
|
|
1217
|
+
type: "call_control"
|
|
1218
|
+
});
|
|
1219
|
+
};
|
|
335
1220
|
const close = () => {
|
|
336
1221
|
clearTimers();
|
|
337
1222
|
if (state.ws) {
|
|
@@ -349,7 +1234,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
349
1234
|
};
|
|
350
1235
|
connect();
|
|
351
1236
|
return {
|
|
352
|
-
|
|
1237
|
+
callControl,
|
|
353
1238
|
close,
|
|
354
1239
|
endTurn,
|
|
355
1240
|
getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
|
|
@@ -357,6 +1242,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
357
1242
|
getSessionId: () => state.sessionId,
|
|
358
1243
|
send,
|
|
359
1244
|
sendAudio,
|
|
1245
|
+
start,
|
|
360
1246
|
subscribe
|
|
361
1247
|
};
|
|
362
1248
|
};
|
|
@@ -365,6 +1251,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
365
1251
|
var createInitialState = () => ({
|
|
366
1252
|
assistantAudio: [],
|
|
367
1253
|
assistantTexts: [],
|
|
1254
|
+
call: null,
|
|
368
1255
|
error: null,
|
|
369
1256
|
isConnected: false,
|
|
370
1257
|
scenarioId: null,
|
|
@@ -408,6 +1295,20 @@ var createVoiceStreamStore = () => {
|
|
|
408
1295
|
status: "completed"
|
|
409
1296
|
};
|
|
410
1297
|
break;
|
|
1298
|
+
case "call_lifecycle":
|
|
1299
|
+
state = {
|
|
1300
|
+
...state,
|
|
1301
|
+
call: {
|
|
1302
|
+
...state.call,
|
|
1303
|
+
disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
|
|
1304
|
+
endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
|
|
1305
|
+
events: [...state.call?.events ?? [], action.event],
|
|
1306
|
+
lastEventAt: action.event.at,
|
|
1307
|
+
startedAt: state.call?.startedAt ?? action.event.at
|
|
1308
|
+
},
|
|
1309
|
+
sessionId: action.sessionId
|
|
1310
|
+
};
|
|
1311
|
+
break;
|
|
411
1312
|
case "connected":
|
|
412
1313
|
state = {
|
|
413
1314
|
...state,
|
|
@@ -494,6 +1395,9 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
494
1395
|
}
|
|
495
1396
|
});
|
|
496
1397
|
return {
|
|
1398
|
+
callControl(message) {
|
|
1399
|
+
connection.callControl(message);
|
|
1400
|
+
},
|
|
497
1401
|
close() {
|
|
498
1402
|
unsubscribeConnection();
|
|
499
1403
|
connection.close();
|
|
@@ -537,6 +1441,9 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
537
1441
|
get assistantAudio() {
|
|
538
1442
|
return store.getSnapshot().assistantAudio;
|
|
539
1443
|
},
|
|
1444
|
+
get call() {
|
|
1445
|
+
return store.getSnapshot().call;
|
|
1446
|
+
},
|
|
540
1447
|
sendAudio(audio) {
|
|
541
1448
|
connection.sendAudio(audio);
|
|
542
1449
|
},
|
|
@@ -552,17 +1459,19 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
552
1459
|
// src/vue/useVoiceStream.ts
|
|
553
1460
|
var useVoiceStream = (path, options = {}) => {
|
|
554
1461
|
const stream = createVoiceStream(path, options);
|
|
555
|
-
const assistantAudio =
|
|
556
|
-
const assistantTexts =
|
|
557
|
-
const
|
|
558
|
-
const
|
|
559
|
-
const
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
const
|
|
1462
|
+
const assistantAudio = shallowRef4([]);
|
|
1463
|
+
const assistantTexts = shallowRef4([]);
|
|
1464
|
+
const call = shallowRef4(null);
|
|
1465
|
+
const error = ref4(null);
|
|
1466
|
+
const isConnected = ref4(false);
|
|
1467
|
+
const partial = ref4("");
|
|
1468
|
+
const sessionId = ref4(stream.sessionId);
|
|
1469
|
+
const status = ref4(stream.status);
|
|
1470
|
+
const turns = shallowRef4([]);
|
|
563
1471
|
const sync = () => {
|
|
564
1472
|
assistantAudio.value = [...stream.assistantAudio];
|
|
565
1473
|
assistantTexts.value = [...stream.assistantTexts];
|
|
1474
|
+
call.value = stream.call;
|
|
566
1475
|
error.value = stream.error;
|
|
567
1476
|
isConnected.value = stream.isConnected;
|
|
568
1477
|
partial.value = stream.partial;
|
|
@@ -576,10 +1485,12 @@ var useVoiceStream = (path, options = {}) => {
|
|
|
576
1485
|
unsubscribe();
|
|
577
1486
|
stream.close();
|
|
578
1487
|
};
|
|
579
|
-
|
|
1488
|
+
onUnmounted4(destroy);
|
|
580
1489
|
return {
|
|
581
1490
|
assistantAudio,
|
|
582
1491
|
assistantTexts,
|
|
1492
|
+
call,
|
|
1493
|
+
callControl: (message) => stream.callControl(message),
|
|
583
1494
|
close: () => destroy(),
|
|
584
1495
|
endTurn: () => stream.endTurn(),
|
|
585
1496
|
error,
|
|
@@ -592,7 +1503,7 @@ var useVoiceStream = (path, options = {}) => {
|
|
|
592
1503
|
};
|
|
593
1504
|
};
|
|
594
1505
|
// src/vue/useVoiceController.ts
|
|
595
|
-
import { onUnmounted as
|
|
1506
|
+
import { onUnmounted as onUnmounted5, ref as ref5, shallowRef as shallowRef5 } from "vue";
|
|
596
1507
|
|
|
597
1508
|
// src/client/htmx.ts
|
|
598
1509
|
var DEFAULT_EVENT_NAME = "voice-refresh";
|
|
@@ -1056,6 +1967,7 @@ var resolveVoiceRuntimePreset = (name = "default") => {
|
|
|
1056
1967
|
var createInitialState2 = (stream) => ({
|
|
1057
1968
|
assistantAudio: [...stream.assistantAudio],
|
|
1058
1969
|
assistantTexts: [...stream.assistantTexts],
|
|
1970
|
+
call: stream.call,
|
|
1059
1971
|
error: stream.error,
|
|
1060
1972
|
isConnected: stream.isConnected,
|
|
1061
1973
|
isRecording: false,
|
|
@@ -1085,6 +1997,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1085
1997
|
...state,
|
|
1086
1998
|
assistantAudio: [...stream.assistantAudio],
|
|
1087
1999
|
assistantTexts: [...stream.assistantTexts],
|
|
2000
|
+
call: stream.call,
|
|
1088
2001
|
error: stream.error,
|
|
1089
2002
|
isConnected: stream.isConnected,
|
|
1090
2003
|
partial: stream.partial,
|
|
@@ -1162,6 +2075,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1162
2075
|
bindHTMX(bindingOptions) {
|
|
1163
2076
|
return bindVoiceHTMX(stream, bindingOptions);
|
|
1164
2077
|
},
|
|
2078
|
+
callControl: (message) => stream.callControl(message),
|
|
1165
2079
|
close,
|
|
1166
2080
|
endTurn: () => stream.endTurn(),
|
|
1167
2081
|
get error() {
|
|
@@ -1214,6 +2128,9 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1214
2128
|
},
|
|
1215
2129
|
get assistantAudio() {
|
|
1216
2130
|
return state.assistantAudio;
|
|
2131
|
+
},
|
|
2132
|
+
get call() {
|
|
2133
|
+
return state.call;
|
|
1217
2134
|
}
|
|
1218
2135
|
};
|
|
1219
2136
|
};
|
|
@@ -1221,16 +2138,16 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1221
2138
|
// src/vue/useVoiceController.ts
|
|
1222
2139
|
var useVoiceController = (path, options = {}) => {
|
|
1223
2140
|
const controller = createVoiceController(path, options);
|
|
1224
|
-
const assistantAudio =
|
|
1225
|
-
const assistantTexts =
|
|
1226
|
-
const error =
|
|
1227
|
-
const isConnected =
|
|
1228
|
-
const isRecording =
|
|
1229
|
-
const partial =
|
|
1230
|
-
const recordingError =
|
|
1231
|
-
const sessionId =
|
|
1232
|
-
const status =
|
|
1233
|
-
const turns =
|
|
2141
|
+
const assistantAudio = shallowRef5([]);
|
|
2142
|
+
const assistantTexts = shallowRef5([]);
|
|
2143
|
+
const error = ref5(null);
|
|
2144
|
+
const isConnected = ref5(false);
|
|
2145
|
+
const isRecording = ref5(false);
|
|
2146
|
+
const partial = ref5("");
|
|
2147
|
+
const recordingError = ref5(null);
|
|
2148
|
+
const sessionId = ref5(controller.sessionId);
|
|
2149
|
+
const status = ref5(controller.status);
|
|
2150
|
+
const turns = shallowRef5([]);
|
|
1234
2151
|
const sync = () => {
|
|
1235
2152
|
assistantAudio.value = [...controller.assistantAudio];
|
|
1236
2153
|
assistantTexts.value = [...controller.assistantTexts];
|
|
@@ -1249,7 +2166,7 @@ var useVoiceController = (path, options = {}) => {
|
|
|
1249
2166
|
unsubscribe();
|
|
1250
2167
|
controller.close();
|
|
1251
2168
|
};
|
|
1252
|
-
|
|
2169
|
+
onUnmounted5(destroy);
|
|
1253
2170
|
return {
|
|
1254
2171
|
assistantAudio,
|
|
1255
2172
|
assistantTexts,
|
|
@@ -1270,7 +2187,127 @@ var useVoiceController = (path, options = {}) => {
|
|
|
1270
2187
|
turns
|
|
1271
2188
|
};
|
|
1272
2189
|
};
|
|
2190
|
+
// src/vue/useVoiceWorkflowStatus.ts
|
|
2191
|
+
import { onUnmounted as onUnmounted6, ref as ref6, shallowRef as shallowRef6 } from "vue";
|
|
2192
|
+
|
|
2193
|
+
// src/client/workflowStatus.ts
|
|
2194
|
+
var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
|
|
2195
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2196
|
+
const response = await fetchImpl(path);
|
|
2197
|
+
if (!response.ok) {
|
|
2198
|
+
throw new Error(`Voice workflow status failed: HTTP ${response.status}`);
|
|
2199
|
+
}
|
|
2200
|
+
return await response.json();
|
|
2201
|
+
};
|
|
2202
|
+
var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options = {}) => {
|
|
2203
|
+
const listeners = new Set;
|
|
2204
|
+
let closed = false;
|
|
2205
|
+
let timer;
|
|
2206
|
+
let snapshot = {
|
|
2207
|
+
error: null,
|
|
2208
|
+
isLoading: false
|
|
2209
|
+
};
|
|
2210
|
+
const emit = () => {
|
|
2211
|
+
for (const listener of listeners) {
|
|
2212
|
+
listener();
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
const refresh = async () => {
|
|
2216
|
+
if (closed) {
|
|
2217
|
+
return snapshot.report;
|
|
2218
|
+
}
|
|
2219
|
+
snapshot = {
|
|
2220
|
+
...snapshot,
|
|
2221
|
+
error: null,
|
|
2222
|
+
isLoading: true
|
|
2223
|
+
};
|
|
2224
|
+
emit();
|
|
2225
|
+
try {
|
|
2226
|
+
const report = await fetchVoiceWorkflowStatus(path, options);
|
|
2227
|
+
snapshot = {
|
|
2228
|
+
error: null,
|
|
2229
|
+
isLoading: false,
|
|
2230
|
+
report,
|
|
2231
|
+
updatedAt: Date.now()
|
|
2232
|
+
};
|
|
2233
|
+
emit();
|
|
2234
|
+
return report;
|
|
2235
|
+
} catch (error) {
|
|
2236
|
+
snapshot = {
|
|
2237
|
+
...snapshot,
|
|
2238
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2239
|
+
isLoading: false
|
|
2240
|
+
};
|
|
2241
|
+
emit();
|
|
2242
|
+
throw error;
|
|
2243
|
+
}
|
|
2244
|
+
};
|
|
2245
|
+
const close = () => {
|
|
2246
|
+
closed = true;
|
|
2247
|
+
if (timer) {
|
|
2248
|
+
clearInterval(timer);
|
|
2249
|
+
timer = undefined;
|
|
2250
|
+
}
|
|
2251
|
+
listeners.clear();
|
|
2252
|
+
};
|
|
2253
|
+
if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
|
|
2254
|
+
timer = setInterval(() => {
|
|
2255
|
+
refresh().catch(() => {});
|
|
2256
|
+
}, options.intervalMs);
|
|
2257
|
+
}
|
|
2258
|
+
return {
|
|
2259
|
+
close,
|
|
2260
|
+
getServerSnapshot: () => snapshot,
|
|
2261
|
+
getSnapshot: () => snapshot,
|
|
2262
|
+
refresh,
|
|
2263
|
+
subscribe: (listener) => {
|
|
2264
|
+
listeners.add(listener);
|
|
2265
|
+
return () => {
|
|
2266
|
+
listeners.delete(listener);
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
};
|
|
2270
|
+
};
|
|
2271
|
+
|
|
2272
|
+
// src/vue/useVoiceWorkflowStatus.ts
|
|
2273
|
+
var useVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => {
|
|
2274
|
+
const store = createVoiceWorkflowStatusStore(path, options);
|
|
2275
|
+
const error = ref6(null);
|
|
2276
|
+
const isLoading = ref6(false);
|
|
2277
|
+
const report = shallowRef6(undefined);
|
|
2278
|
+
const updatedAt = ref6(undefined);
|
|
2279
|
+
const sync = () => {
|
|
2280
|
+
const snapshot = store.getSnapshot();
|
|
2281
|
+
error.value = snapshot.error;
|
|
2282
|
+
isLoading.value = snapshot.isLoading;
|
|
2283
|
+
report.value = snapshot.report;
|
|
2284
|
+
updatedAt.value = snapshot.updatedAt;
|
|
2285
|
+
};
|
|
2286
|
+
const unsubscribe = store.subscribe(sync);
|
|
2287
|
+
sync();
|
|
2288
|
+
if (typeof window !== "undefined") {
|
|
2289
|
+
store.refresh().catch(() => {});
|
|
2290
|
+
}
|
|
2291
|
+
onUnmounted6(() => {
|
|
2292
|
+
unsubscribe();
|
|
2293
|
+
store.close();
|
|
2294
|
+
});
|
|
2295
|
+
return {
|
|
2296
|
+
error,
|
|
2297
|
+
isLoading,
|
|
2298
|
+
refresh: store.refresh,
|
|
2299
|
+
report,
|
|
2300
|
+
updatedAt
|
|
2301
|
+
};
|
|
2302
|
+
};
|
|
1273
2303
|
export {
|
|
2304
|
+
useVoiceWorkflowStatus,
|
|
1274
2305
|
useVoiceStream,
|
|
1275
|
-
|
|
2306
|
+
useVoiceRoutingStatus,
|
|
2307
|
+
useVoiceProviderStatus,
|
|
2308
|
+
useVoiceController,
|
|
2309
|
+
useVoiceAppKitStatus,
|
|
2310
|
+
VoiceRoutingStatus,
|
|
2311
|
+
VoiceProviderStatus,
|
|
2312
|
+
VoiceOpsStatus
|
|
1276
2313
|
};
|