@absolutejs/voice 0.0.22-beta.6 → 0.0.22-beta.61
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 +18 -0
- package/dist/client/index.js +893 -2
- package/dist/client/opsStatusWidget.d.ts +40 -0
- package/dist/client/providerSimulationControls.d.ts +33 -0
- package/dist/client/providerSimulationControlsWidget.d.ts +20 -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/VoiceProviderSimulationControls.d.ts +5 -0
- package/dist/react/VoiceProviderStatus.d.ts +6 -0
- package/dist/react/VoiceRoutingStatus.d.ts +6 -0
- package/dist/react/index.d.ts +9 -0
- package/dist/react/index.js +1295 -11
- package/dist/react/useVoiceAppKitStatus.d.ts +8 -0
- package/dist/react/useVoiceController.d.ts +2 -0
- package/dist/react/useVoiceProviderSimulationControls.d.ts +10 -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/createVoiceProviderSimulationControls.d.ts +11 -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 +6 -0
- package/dist/svelte/index.js +923 -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/VoiceProviderSimulationControls.d.ts +88 -0
- package/dist/vue/VoiceProviderStatus.d.ts +51 -0
- package/dist/vue/VoiceRoutingStatus.d.ts +51 -0
- package/dist/vue/index.d.ts +9 -0
- package/dist/vue/index.js +1354 -25
- package/dist/vue/useVoiceAppKitStatus.d.ts +9 -0
- package/dist/vue/useVoiceProviderSimulationControls.d.ts +24 -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/svelte/index.js
CHANGED
|
@@ -69,6 +69,410 @@ 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/client/appKitStatus.ts
|
|
73
|
+
var fetchVoiceAppKitStatus = async (path = "/app-kit/status", options = {}) => {
|
|
74
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
75
|
+
const response = await fetchImpl(path);
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
throw new Error(`Voice app kit status failed: HTTP ${response.status}`);
|
|
78
|
+
}
|
|
79
|
+
return await response.json();
|
|
80
|
+
};
|
|
81
|
+
var createVoiceAppKitStatusStore = (path = "/app-kit/status", options = {}) => {
|
|
82
|
+
const listeners = new Set;
|
|
83
|
+
let closed = false;
|
|
84
|
+
let timer;
|
|
85
|
+
let snapshot = {
|
|
86
|
+
error: null,
|
|
87
|
+
isLoading: false
|
|
88
|
+
};
|
|
89
|
+
const emit = () => {
|
|
90
|
+
for (const listener of listeners) {
|
|
91
|
+
listener();
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const refresh = async () => {
|
|
95
|
+
if (closed) {
|
|
96
|
+
return snapshot.report;
|
|
97
|
+
}
|
|
98
|
+
snapshot = {
|
|
99
|
+
...snapshot,
|
|
100
|
+
error: null,
|
|
101
|
+
isLoading: true
|
|
102
|
+
};
|
|
103
|
+
emit();
|
|
104
|
+
try {
|
|
105
|
+
const report = await fetchVoiceAppKitStatus(path, options);
|
|
106
|
+
snapshot = {
|
|
107
|
+
error: null,
|
|
108
|
+
isLoading: false,
|
|
109
|
+
report,
|
|
110
|
+
updatedAt: Date.now()
|
|
111
|
+
};
|
|
112
|
+
emit();
|
|
113
|
+
return report;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
snapshot = {
|
|
116
|
+
...snapshot,
|
|
117
|
+
error: error instanceof Error ? error.message : String(error),
|
|
118
|
+
isLoading: false
|
|
119
|
+
};
|
|
120
|
+
emit();
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const close = () => {
|
|
125
|
+
closed = true;
|
|
126
|
+
if (timer) {
|
|
127
|
+
clearInterval(timer);
|
|
128
|
+
timer = undefined;
|
|
129
|
+
}
|
|
130
|
+
listeners.clear();
|
|
131
|
+
};
|
|
132
|
+
if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
|
|
133
|
+
timer = setInterval(() => {
|
|
134
|
+
refresh().catch(() => {});
|
|
135
|
+
}, options.intervalMs);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
close,
|
|
139
|
+
getServerSnapshot: () => snapshot,
|
|
140
|
+
getSnapshot: () => snapshot,
|
|
141
|
+
refresh,
|
|
142
|
+
subscribe: (listener) => {
|
|
143
|
+
listeners.add(listener);
|
|
144
|
+
return () => {
|
|
145
|
+
listeners.delete(listener);
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// src/svelte/createVoiceAppKitStatus.ts
|
|
152
|
+
var createVoiceAppKitStatus = (path = "/app-kit/status", options = {}) => createVoiceAppKitStatusStore(path, options);
|
|
153
|
+
// src/client/opsStatusWidget.ts
|
|
154
|
+
var DEFAULT_TITLE = "Voice Ops Status";
|
|
155
|
+
var DEFAULT_DESCRIPTION = "Certified workflow, provider, and handoff readiness from the AbsoluteJS voice app kit.";
|
|
156
|
+
var SURFACE_LABELS = {
|
|
157
|
+
handoffs: "Handoffs",
|
|
158
|
+
providers: "Providers",
|
|
159
|
+
quality: "Quality",
|
|
160
|
+
sessions: "Sessions",
|
|
161
|
+
workflows: "Workflows"
|
|
162
|
+
};
|
|
163
|
+
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
164
|
+
var readNumber = (value, key) => value && typeof value === "object" && (key in value) ? Number(value[key] ?? 0) : 0;
|
|
165
|
+
var surfaceDetail = (surface) => {
|
|
166
|
+
const total = readNumber(surface, "total");
|
|
167
|
+
const failed = readNumber(surface, "failed");
|
|
168
|
+
const degraded = readNumber(surface, "degraded");
|
|
169
|
+
const source = surface && typeof surface === "object" && "source" in surface && typeof surface.source === "string" ? ` from ${surface.source}` : "";
|
|
170
|
+
if (degraded > 0) {
|
|
171
|
+
return `${degraded} degraded of ${total}`;
|
|
172
|
+
}
|
|
173
|
+
if (failed > 0) {
|
|
174
|
+
return `${failed} failing of ${total}${source}`;
|
|
175
|
+
}
|
|
176
|
+
return total > 0 ? `${total} passing${source}` : `No failures${source}`;
|
|
177
|
+
};
|
|
178
|
+
var getVoiceOpsStatusLabel = (report, error) => {
|
|
179
|
+
if (error) {
|
|
180
|
+
return "Unavailable";
|
|
181
|
+
}
|
|
182
|
+
if (!report) {
|
|
183
|
+
return "Checking";
|
|
184
|
+
}
|
|
185
|
+
return report.status === "pass" ? "Passing" : "Needs attention";
|
|
186
|
+
};
|
|
187
|
+
var createVoiceOpsStatusViewModel = (snapshot, options = {}) => {
|
|
188
|
+
const report = snapshot.report;
|
|
189
|
+
const surfaces = Object.entries(report?.surfaces ?? {}).map(([id, surface]) => {
|
|
190
|
+
const failed = readNumber(surface, "failed") || readNumber(surface, "degraded");
|
|
191
|
+
const total = readNumber(surface, "total");
|
|
192
|
+
const status = surface && typeof surface === "object" && "status" in surface ? surface.status ?? "pass" : "pass";
|
|
193
|
+
return {
|
|
194
|
+
detail: surfaceDetail(surface),
|
|
195
|
+
failed,
|
|
196
|
+
id,
|
|
197
|
+
label: SURFACE_LABELS[id] ?? id,
|
|
198
|
+
status,
|
|
199
|
+
total
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
return {
|
|
203
|
+
description: options.description ?? DEFAULT_DESCRIPTION,
|
|
204
|
+
error: snapshot.error,
|
|
205
|
+
isLoading: snapshot.isLoading,
|
|
206
|
+
label: getVoiceOpsStatusLabel(report, snapshot.error),
|
|
207
|
+
links: options.includeLinks === false ? [] : report?.links ?? [],
|
|
208
|
+
passed: report?.passed ?? 0,
|
|
209
|
+
status: snapshot.error ? "error" : report ? report.status : snapshot.isLoading ? "loading" : "loading",
|
|
210
|
+
surfaces,
|
|
211
|
+
title: options.title ?? DEFAULT_TITLE,
|
|
212
|
+
total: report?.total ?? 0,
|
|
213
|
+
updatedAt: snapshot.updatedAt
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
var renderVoiceOpsStatusHTML = (snapshot, options = {}) => {
|
|
217
|
+
const model = createVoiceOpsStatusViewModel(snapshot, options);
|
|
218
|
+
const surfaces = model.surfaces.length ? model.surfaces.map((surface) => `<li class="absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${escapeHtml(surface.status)}">
|
|
219
|
+
<span>${escapeHtml(surface.label)}</span>
|
|
220
|
+
<strong>${escapeHtml(surface.detail)}</strong>
|
|
221
|
+
</li>`).join("") : '<li class="absolute-voice-ops-status__surface"><span>Status</span><strong>Waiting for first check</strong></li>';
|
|
222
|
+
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>` : "";
|
|
223
|
+
return `<section class="absolute-voice-ops-status absolute-voice-ops-status--${escapeHtml(model.status)}">
|
|
224
|
+
<header class="absolute-voice-ops-status__header">
|
|
225
|
+
<span class="absolute-voice-ops-status__eyebrow">${escapeHtml(model.title)}</span>
|
|
226
|
+
<strong class="absolute-voice-ops-status__label">${escapeHtml(model.label)}</strong>
|
|
227
|
+
</header>
|
|
228
|
+
<p class="absolute-voice-ops-status__description">${escapeHtml(model.description)}</p>
|
|
229
|
+
<div class="absolute-voice-ops-status__summary">
|
|
230
|
+
<span>${model.passed} passing</span>
|
|
231
|
+
<span>${Math.max(model.total - model.passed, 0)} failing</span>
|
|
232
|
+
<span>${model.total} checks</span>
|
|
233
|
+
</div>
|
|
234
|
+
<ul class="absolute-voice-ops-status__surfaces">${surfaces}</ul>
|
|
235
|
+
${model.error ? `<p class="absolute-voice-ops-status__error">${escapeHtml(model.error)}</p>` : ""}
|
|
236
|
+
${links}
|
|
237
|
+
</section>`;
|
|
238
|
+
};
|
|
239
|
+
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}`;
|
|
240
|
+
var mountVoiceOpsStatus = (element, path = "/app-kit/status", options = {}) => {
|
|
241
|
+
const store = createVoiceAppKitStatusStore(path, options);
|
|
242
|
+
const render = () => {
|
|
243
|
+
element.innerHTML = renderVoiceOpsStatusHTML(store.getSnapshot(), options);
|
|
244
|
+
};
|
|
245
|
+
const unsubscribe = store.subscribe(render);
|
|
246
|
+
render();
|
|
247
|
+
store.refresh().catch(() => {});
|
|
248
|
+
return {
|
|
249
|
+
close: () => {
|
|
250
|
+
unsubscribe();
|
|
251
|
+
store.close();
|
|
252
|
+
},
|
|
253
|
+
refresh: store.refresh
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
var defineVoiceOpsStatusElement = (tagName = "absolute-voice-ops-status") => {
|
|
257
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
customElements.define(tagName, class AbsoluteVoiceOpsStatusElement extends HTMLElement {
|
|
261
|
+
mounted;
|
|
262
|
+
connectedCallback() {
|
|
263
|
+
const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
|
|
264
|
+
this.mounted = mountVoiceOpsStatus(this, this.getAttribute("path") ?? "/app-kit/status", {
|
|
265
|
+
description: this.getAttribute("description") ?? undefined,
|
|
266
|
+
includeLinks: this.getAttribute("include-links") !== "false",
|
|
267
|
+
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
268
|
+
title: this.getAttribute("title") ?? undefined
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
disconnectedCallback() {
|
|
272
|
+
this.mounted?.close();
|
|
273
|
+
this.mounted = undefined;
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// src/svelte/createVoiceOpsStatus.ts
|
|
279
|
+
var createVoiceOpsStatus = (path = "/app-kit/status", options = {}) => {
|
|
280
|
+
const store = createVoiceAppKitStatusStore(path, options);
|
|
281
|
+
return {
|
|
282
|
+
close: store.close,
|
|
283
|
+
getHTML: () => renderVoiceOpsStatusHTML(store.getSnapshot(), options),
|
|
284
|
+
getSnapshot: store.getSnapshot,
|
|
285
|
+
getViewModel: () => createVoiceOpsStatusViewModel(store.getSnapshot(), options),
|
|
286
|
+
refresh: store.refresh,
|
|
287
|
+
subscribe: store.subscribe
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
// src/client/providerSimulationControls.ts
|
|
291
|
+
var postSimulation = async (pathPrefix, mode, provider, fetchImpl) => {
|
|
292
|
+
const response = await fetchImpl(`${pathPrefix}/${mode}?provider=${encodeURIComponent(provider)}`, { method: "POST" });
|
|
293
|
+
const body = await response.json().catch(() => null);
|
|
294
|
+
if (!response.ok) {
|
|
295
|
+
const message = body && typeof body === "object" && "error" in body ? String(body.error) : `Voice provider simulation failed: HTTP ${response.status}`;
|
|
296
|
+
throw new Error(message);
|
|
297
|
+
}
|
|
298
|
+
return body;
|
|
299
|
+
};
|
|
300
|
+
var createVoiceProviderSimulationControlsStore = (options) => {
|
|
301
|
+
const listeners = new Set;
|
|
302
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
303
|
+
const pathPrefix = options.pathPrefix ?? `/api/${options.kind ?? "stt"}-simulate`;
|
|
304
|
+
let closed = false;
|
|
305
|
+
let snapshot = {
|
|
306
|
+
error: null,
|
|
307
|
+
isRunning: false,
|
|
308
|
+
lastResult: null,
|
|
309
|
+
mode: null,
|
|
310
|
+
provider: null
|
|
311
|
+
};
|
|
312
|
+
const emit = () => {
|
|
313
|
+
for (const listener of listeners) {
|
|
314
|
+
listener();
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
const run = async (provider, mode) => {
|
|
318
|
+
if (closed) {
|
|
319
|
+
return snapshot.lastResult;
|
|
320
|
+
}
|
|
321
|
+
snapshot = {
|
|
322
|
+
...snapshot,
|
|
323
|
+
error: null,
|
|
324
|
+
isRunning: true,
|
|
325
|
+
mode,
|
|
326
|
+
provider
|
|
327
|
+
};
|
|
328
|
+
emit();
|
|
329
|
+
try {
|
|
330
|
+
const result = await postSimulation(pathPrefix, mode, provider, fetchImpl);
|
|
331
|
+
snapshot = {
|
|
332
|
+
error: null,
|
|
333
|
+
isRunning: false,
|
|
334
|
+
lastResult: result,
|
|
335
|
+
mode,
|
|
336
|
+
provider,
|
|
337
|
+
updatedAt: Date.now()
|
|
338
|
+
};
|
|
339
|
+
emit();
|
|
340
|
+
return result;
|
|
341
|
+
} catch (error) {
|
|
342
|
+
snapshot = {
|
|
343
|
+
...snapshot,
|
|
344
|
+
error: error instanceof Error ? error.message : String(error),
|
|
345
|
+
isRunning: false
|
|
346
|
+
};
|
|
347
|
+
emit();
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
const close = () => {
|
|
352
|
+
closed = true;
|
|
353
|
+
listeners.clear();
|
|
354
|
+
};
|
|
355
|
+
return {
|
|
356
|
+
close,
|
|
357
|
+
getServerSnapshot: () => snapshot,
|
|
358
|
+
getSnapshot: () => snapshot,
|
|
359
|
+
run,
|
|
360
|
+
subscribe: (listener) => {
|
|
361
|
+
listeners.add(listener);
|
|
362
|
+
return () => {
|
|
363
|
+
listeners.delete(listener);
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// src/client/providerSimulationControlsWidget.ts
|
|
370
|
+
var escapeHtml2 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
371
|
+
var formatKind = (kind) => (kind ?? "stt").toUpperCase();
|
|
372
|
+
var createVoiceProviderSimulationControlsViewModel = (snapshot, options) => {
|
|
373
|
+
const configuredProviders = options.providers.filter((provider) => provider.configured !== false);
|
|
374
|
+
const fallbackReady = !options.fallbackRequiredProvider || configuredProviders.some((entry) => entry.provider === options.fallbackRequiredProvider);
|
|
375
|
+
const failureProviders = (options.failureProviders ? options.failureProviders.map((provider) => ({ provider })) : configuredProviders).filter((provider) => configuredProviders.some((entry) => entry.provider === provider.provider));
|
|
376
|
+
return {
|
|
377
|
+
canSimulateFailure: configuredProviders.length > 0 && fallbackReady,
|
|
378
|
+
description: options.failureMessage ?? `Simulate ${formatKind(options.kind)} provider failure and recovery without changing credentials.`,
|
|
379
|
+
error: snapshot.error,
|
|
380
|
+
failureProviders,
|
|
381
|
+
isRunning: snapshot.isRunning,
|
|
382
|
+
label: snapshot.isRunning ? `Running ${snapshot.mode ?? "simulation"}` : snapshot.lastResult ? `${snapshot.lastResult.provider} ${snapshot.lastResult.mode} simulated` : configuredProviders.length ? `${configuredProviders.length} configured` : "No configured providers",
|
|
383
|
+
providers: configuredProviders,
|
|
384
|
+
resultText: snapshot.lastResult ? JSON.stringify(snapshot.lastResult, null, 2) : null,
|
|
385
|
+
title: options.title ?? `${formatKind(options.kind)} Failure Simulation`
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
var renderVoiceProviderSimulationControlsHTML = (snapshot, options) => {
|
|
389
|
+
const model = createVoiceProviderSimulationControlsViewModel(snapshot, options);
|
|
390
|
+
const failureButtons = model.failureProviders.map((provider) => `<button type="button" data-voice-provider-fail="${escapeHtml2(provider.provider)}"${!model.canSimulateFailure || snapshot.isRunning ? " disabled" : ""}>Simulate ${escapeHtml2(provider.provider)} ${escapeHtml2(formatKind(options.kind))} failure</button>`).join("");
|
|
391
|
+
const recoveryButtons = model.providers.map((provider) => `<button type="button" data-voice-provider-recover="${escapeHtml2(provider.provider)}"${snapshot.isRunning ? " disabled" : ""}>Mark ${escapeHtml2(provider.provider)} recovered</button>`).join("");
|
|
392
|
+
return `<section class="absolute-voice-provider-simulation absolute-voice-provider-simulation--${snapshot.error ? "error" : snapshot.isRunning ? "running" : "ready"}">
|
|
393
|
+
<header class="absolute-voice-provider-simulation__header">
|
|
394
|
+
<span class="absolute-voice-provider-simulation__eyebrow">${escapeHtml2(model.title)}</span>
|
|
395
|
+
<strong class="absolute-voice-provider-simulation__label">${escapeHtml2(model.label)}</strong>
|
|
396
|
+
</header>
|
|
397
|
+
<p class="absolute-voice-provider-simulation__description">${escapeHtml2(model.description)}</p>
|
|
398
|
+
${model.canSimulateFailure ? "" : `<p class="absolute-voice-provider-simulation__empty">${escapeHtml2(options.fallbackRequiredMessage ?? "Configure fallback providers before simulating failure.")}</p>`}
|
|
399
|
+
<div class="absolute-voice-provider-simulation__actions">${failureButtons}${recoveryButtons}</div>
|
|
400
|
+
${snapshot.error ? `<p class="absolute-voice-provider-simulation__error">${escapeHtml2(snapshot.error)}</p>` : ""}
|
|
401
|
+
${model.resultText ? `<pre class="absolute-voice-provider-simulation__result">${escapeHtml2(model.resultText)}</pre>` : ""}
|
|
402
|
+
</section>`;
|
|
403
|
+
};
|
|
404
|
+
var bindVoiceProviderSimulationControls = (element, store) => {
|
|
405
|
+
const onClick = (event) => {
|
|
406
|
+
const target = event.target;
|
|
407
|
+
if (!(target instanceof HTMLElement)) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const failProvider = target.getAttribute("data-voice-provider-fail");
|
|
411
|
+
const recoverProvider = target.getAttribute("data-voice-provider-recover");
|
|
412
|
+
if (failProvider) {
|
|
413
|
+
store.run(failProvider, "failure").catch(() => {});
|
|
414
|
+
}
|
|
415
|
+
if (recoverProvider) {
|
|
416
|
+
store.run(recoverProvider, "recovery").catch(() => {});
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
element.addEventListener("click", onClick);
|
|
420
|
+
return () => element.removeEventListener("click", onClick);
|
|
421
|
+
};
|
|
422
|
+
var mountVoiceProviderSimulationControls = (element, options) => {
|
|
423
|
+
const store = createVoiceProviderSimulationControlsStore(options);
|
|
424
|
+
const render = () => {
|
|
425
|
+
element.innerHTML = renderVoiceProviderSimulationControlsHTML(store.getSnapshot(), options);
|
|
426
|
+
};
|
|
427
|
+
const unsubscribeStore = store.subscribe(render);
|
|
428
|
+
const unsubscribeDom = bindVoiceProviderSimulationControls(element, store);
|
|
429
|
+
render();
|
|
430
|
+
return {
|
|
431
|
+
close: () => {
|
|
432
|
+
unsubscribeDom();
|
|
433
|
+
unsubscribeStore();
|
|
434
|
+
store.close();
|
|
435
|
+
},
|
|
436
|
+
run: store.run
|
|
437
|
+
};
|
|
438
|
+
};
|
|
439
|
+
var defineVoiceProviderSimulationControlsElement = (tagName = "absolute-voice-provider-simulation") => {
|
|
440
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
customElements.define(tagName, class AbsoluteVoiceProviderSimulationElement extends HTMLElement {
|
|
444
|
+
mounted;
|
|
445
|
+
connectedCallback() {
|
|
446
|
+
const providers = (this.getAttribute("providers") ?? "").split(",").map((provider) => provider.trim()).filter(Boolean).map((provider) => ({ provider }));
|
|
447
|
+
const failureProviders = (this.getAttribute("failure-providers") ?? "").split(",").map((provider) => provider.trim()).filter(Boolean);
|
|
448
|
+
this.mounted = mountVoiceProviderSimulationControls(this, {
|
|
449
|
+
failureProviders: failureProviders.length ? failureProviders : undefined,
|
|
450
|
+
fallbackRequiredMessage: this.getAttribute("fallback-required-message") ?? undefined,
|
|
451
|
+
fallbackRequiredProvider: this.getAttribute("fallback-required-provider") ?? undefined,
|
|
452
|
+
failureMessage: this.getAttribute("failure-message") ?? undefined,
|
|
453
|
+
kind: this.getAttribute("kind") ?? "stt",
|
|
454
|
+
pathPrefix: this.getAttribute("path-prefix") ?? undefined,
|
|
455
|
+
providers,
|
|
456
|
+
title: this.getAttribute("title") ?? undefined
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
disconnectedCallback() {
|
|
460
|
+
this.mounted?.close();
|
|
461
|
+
this.mounted = undefined;
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// src/svelte/createVoiceProviderSimulationControls.ts
|
|
467
|
+
var createVoiceProviderSimulationControls = (options) => {
|
|
468
|
+
const store = createVoiceProviderSimulationControlsStore(options);
|
|
469
|
+
return {
|
|
470
|
+
...store,
|
|
471
|
+
bind: (element) => bindVoiceProviderSimulationControls(element, store),
|
|
472
|
+
getHTML: () => renderVoiceProviderSimulationControlsHTML(store.getSnapshot(), options),
|
|
473
|
+
getViewModel: () => createVoiceProviderSimulationControlsViewModel(store.getSnapshot(), options)
|
|
474
|
+
};
|
|
475
|
+
};
|
|
72
476
|
// src/client/actions.ts
|
|
73
477
|
var normalizeErrorMessage = (value) => {
|
|
74
478
|
if (typeof value === "string" && value.trim()) {
|
|
@@ -117,6 +521,12 @@ var serverMessageToAction = (message) => {
|
|
|
117
521
|
sessionId: message.sessionId,
|
|
118
522
|
type: "complete"
|
|
119
523
|
};
|
|
524
|
+
case "call_lifecycle":
|
|
525
|
+
return {
|
|
526
|
+
event: message.event,
|
|
527
|
+
sessionId: message.sessionId,
|
|
528
|
+
type: "call_lifecycle"
|
|
529
|
+
};
|
|
120
530
|
case "error":
|
|
121
531
|
return {
|
|
122
532
|
message: normalizeErrorMessage(message.message),
|
|
@@ -160,7 +570,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
|
160
570
|
var noop = () => {};
|
|
161
571
|
var noopUnsubscribe = () => noop;
|
|
162
572
|
var NOOP_CONNECTION = {
|
|
163
|
-
|
|
573
|
+
callControl: noop,
|
|
164
574
|
close: noop,
|
|
165
575
|
endTurn: noop,
|
|
166
576
|
getReadyState: () => WS_CLOSED,
|
|
@@ -168,6 +578,7 @@ var NOOP_CONNECTION = {
|
|
|
168
578
|
getSessionId: () => "",
|
|
169
579
|
send: noop,
|
|
170
580
|
sendAudio: noop,
|
|
581
|
+
start: () => {},
|
|
171
582
|
subscribe: noopUnsubscribe
|
|
172
583
|
};
|
|
173
584
|
var createSessionId = () => crypto.randomUUID();
|
|
@@ -189,6 +600,7 @@ var isVoiceServerMessage = (value) => {
|
|
|
189
600
|
switch (value.type) {
|
|
190
601
|
case "audio":
|
|
191
602
|
case "assistant":
|
|
603
|
+
case "call_lifecycle":
|
|
192
604
|
case "complete":
|
|
193
605
|
case "error":
|
|
194
606
|
case "final":
|
|
@@ -329,6 +741,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
329
741
|
const endTurn = () => {
|
|
330
742
|
send({ type: "end_turn" });
|
|
331
743
|
};
|
|
744
|
+
const callControl = (message) => {
|
|
745
|
+
send({
|
|
746
|
+
...message,
|
|
747
|
+
type: "call_control"
|
|
748
|
+
});
|
|
749
|
+
};
|
|
332
750
|
const close = () => {
|
|
333
751
|
clearTimers();
|
|
334
752
|
if (state.ws) {
|
|
@@ -346,7 +764,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
346
764
|
};
|
|
347
765
|
connect();
|
|
348
766
|
return {
|
|
349
|
-
|
|
767
|
+
callControl,
|
|
350
768
|
close,
|
|
351
769
|
endTurn,
|
|
352
770
|
getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
|
|
@@ -354,6 +772,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
354
772
|
getSessionId: () => state.sessionId,
|
|
355
773
|
send,
|
|
356
774
|
sendAudio,
|
|
775
|
+
start,
|
|
357
776
|
subscribe
|
|
358
777
|
};
|
|
359
778
|
};
|
|
@@ -362,6 +781,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
362
781
|
var createInitialState = () => ({
|
|
363
782
|
assistantAudio: [],
|
|
364
783
|
assistantTexts: [],
|
|
784
|
+
call: null,
|
|
365
785
|
error: null,
|
|
366
786
|
isConnected: false,
|
|
367
787
|
scenarioId: null,
|
|
@@ -405,6 +825,20 @@ var createVoiceStreamStore = () => {
|
|
|
405
825
|
status: "completed"
|
|
406
826
|
};
|
|
407
827
|
break;
|
|
828
|
+
case "call_lifecycle":
|
|
829
|
+
state = {
|
|
830
|
+
...state,
|
|
831
|
+
call: {
|
|
832
|
+
...state.call,
|
|
833
|
+
disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
|
|
834
|
+
endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
|
|
835
|
+
events: [...state.call?.events ?? [], action.event],
|
|
836
|
+
lastEventAt: action.event.at,
|
|
837
|
+
startedAt: state.call?.startedAt ?? action.event.at
|
|
838
|
+
},
|
|
839
|
+
sessionId: action.sessionId
|
|
840
|
+
};
|
|
841
|
+
break;
|
|
408
842
|
case "connected":
|
|
409
843
|
state = {
|
|
410
844
|
...state,
|
|
@@ -491,6 +925,9 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
491
925
|
}
|
|
492
926
|
});
|
|
493
927
|
return {
|
|
928
|
+
callControl(message) {
|
|
929
|
+
connection.callControl(message);
|
|
930
|
+
},
|
|
494
931
|
close() {
|
|
495
932
|
unsubscribeConnection();
|
|
496
933
|
connection.close();
|
|
@@ -534,6 +971,9 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
534
971
|
get assistantAudio() {
|
|
535
972
|
return store.getSnapshot().assistantAudio;
|
|
536
973
|
},
|
|
974
|
+
get call() {
|
|
975
|
+
return store.getSnapshot().call;
|
|
976
|
+
},
|
|
537
977
|
sendAudio(audio) {
|
|
538
978
|
connection.sendAudio(audio);
|
|
539
979
|
},
|
|
@@ -548,6 +988,474 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
548
988
|
|
|
549
989
|
// src/svelte/createVoiceStream.ts
|
|
550
990
|
var createVoiceStream2 = (path, options = {}) => createVoiceStream(path, options);
|
|
991
|
+
// src/client/providerStatus.ts
|
|
992
|
+
var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
|
|
993
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
994
|
+
const response = await fetchImpl(path);
|
|
995
|
+
if (!response.ok) {
|
|
996
|
+
throw new Error(`Voice provider status failed: HTTP ${response.status}`);
|
|
997
|
+
}
|
|
998
|
+
return await response.json();
|
|
999
|
+
};
|
|
1000
|
+
var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
|
|
1001
|
+
const listeners = new Set;
|
|
1002
|
+
let closed = false;
|
|
1003
|
+
let timer;
|
|
1004
|
+
let snapshot = {
|
|
1005
|
+
error: null,
|
|
1006
|
+
isLoading: false,
|
|
1007
|
+
providers: []
|
|
1008
|
+
};
|
|
1009
|
+
const emit = () => {
|
|
1010
|
+
for (const listener of listeners) {
|
|
1011
|
+
listener();
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
const refresh = async () => {
|
|
1015
|
+
if (closed) {
|
|
1016
|
+
return snapshot.providers;
|
|
1017
|
+
}
|
|
1018
|
+
snapshot = {
|
|
1019
|
+
...snapshot,
|
|
1020
|
+
error: null,
|
|
1021
|
+
isLoading: true
|
|
1022
|
+
};
|
|
1023
|
+
emit();
|
|
1024
|
+
try {
|
|
1025
|
+
const providers = await fetchVoiceProviderStatus(path, options);
|
|
1026
|
+
snapshot = {
|
|
1027
|
+
error: null,
|
|
1028
|
+
isLoading: false,
|
|
1029
|
+
providers,
|
|
1030
|
+
updatedAt: Date.now()
|
|
1031
|
+
};
|
|
1032
|
+
emit();
|
|
1033
|
+
return providers;
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
snapshot = {
|
|
1036
|
+
...snapshot,
|
|
1037
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1038
|
+
isLoading: false
|
|
1039
|
+
};
|
|
1040
|
+
emit();
|
|
1041
|
+
throw error;
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
const close = () => {
|
|
1045
|
+
closed = true;
|
|
1046
|
+
if (timer) {
|
|
1047
|
+
clearInterval(timer);
|
|
1048
|
+
timer = undefined;
|
|
1049
|
+
}
|
|
1050
|
+
listeners.clear();
|
|
1051
|
+
};
|
|
1052
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
1053
|
+
timer = setInterval(() => {
|
|
1054
|
+
refresh().catch(() => {});
|
|
1055
|
+
}, options.intervalMs);
|
|
1056
|
+
}
|
|
1057
|
+
return {
|
|
1058
|
+
close,
|
|
1059
|
+
getServerSnapshot: () => snapshot,
|
|
1060
|
+
getSnapshot: () => snapshot,
|
|
1061
|
+
refresh,
|
|
1062
|
+
subscribe: (listener) => {
|
|
1063
|
+
listeners.add(listener);
|
|
1064
|
+
return () => {
|
|
1065
|
+
listeners.delete(listener);
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
// src/client/providerStatusWidget.ts
|
|
1072
|
+
var DEFAULT_TITLE2 = "Voice Providers";
|
|
1073
|
+
var DEFAULT_DESCRIPTION2 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
|
|
1074
|
+
var escapeHtml3 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1075
|
+
var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
1076
|
+
var formatStatus = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
1077
|
+
var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
|
|
1078
|
+
var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
|
|
1079
|
+
var getProviderDetail = (provider) => {
|
|
1080
|
+
if (provider.status === "suppressed") {
|
|
1081
|
+
return provider.lastError ? `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)} after ${provider.lastError}.` : `Suppressed for ${formatSuppression(provider.suppressionRemainingMs)}.`;
|
|
1082
|
+
}
|
|
1083
|
+
if (provider.status === "recoverable") {
|
|
1084
|
+
return "Cooldown expired; ready for recovery traffic.";
|
|
1085
|
+
}
|
|
1086
|
+
if (provider.status === "rate-limited") {
|
|
1087
|
+
return "Rate limit detected; router should avoid this provider.";
|
|
1088
|
+
}
|
|
1089
|
+
if (provider.status === "degraded") {
|
|
1090
|
+
return provider.lastError ?? "Recent provider errors detected.";
|
|
1091
|
+
}
|
|
1092
|
+
if (provider.status === "healthy") {
|
|
1093
|
+
return provider.recommended ? "Healthy and currently recommended." : "Healthy and available for routing.";
|
|
1094
|
+
}
|
|
1095
|
+
return "No provider traffic observed yet.";
|
|
1096
|
+
};
|
|
1097
|
+
var isWarningStatus = (status) => status === "degraded" || status === "rate-limited" || status === "recoverable" || status === "suppressed";
|
|
1098
|
+
var createVoiceProviderStatusViewModel = (snapshot, options = {}) => {
|
|
1099
|
+
const providers = snapshot.providers.map((provider) => ({
|
|
1100
|
+
...provider,
|
|
1101
|
+
detail: getProviderDetail(provider),
|
|
1102
|
+
label: `${formatProvider(provider.provider)}${provider.recommended ? " recommended" : ""}`,
|
|
1103
|
+
rows: [
|
|
1104
|
+
{ label: "Runs", value: String(provider.runCount) },
|
|
1105
|
+
{ label: "Avg latency", value: formatLatency(provider.averageElapsedMs) },
|
|
1106
|
+
{ label: "Errors", value: String(provider.errorCount) },
|
|
1107
|
+
{ label: "Timeouts", value: String(provider.timeoutCount) },
|
|
1108
|
+
{ label: "Fallbacks", value: String(provider.fallbackCount) },
|
|
1109
|
+
{
|
|
1110
|
+
label: "Suppression",
|
|
1111
|
+
value: formatSuppression(provider.suppressionRemainingMs)
|
|
1112
|
+
}
|
|
1113
|
+
]
|
|
1114
|
+
}));
|
|
1115
|
+
const warningCount = providers.filter((provider) => isWarningStatus(provider.status)).length;
|
|
1116
|
+
const healthyCount = providers.filter((provider) => provider.status === "healthy").length;
|
|
1117
|
+
return {
|
|
1118
|
+
description: options.description ?? DEFAULT_DESCRIPTION2,
|
|
1119
|
+
error: snapshot.error,
|
|
1120
|
+
isLoading: snapshot.isLoading,
|
|
1121
|
+
label: snapshot.error ? "Unavailable" : providers.length ? warningCount > 0 ? `${warningCount} needs attention` : `${healthyCount} healthy` : snapshot.isLoading ? "Checking" : "No provider traffic",
|
|
1122
|
+
providers,
|
|
1123
|
+
status: snapshot.error ? "error" : providers.length ? warningCount > 0 ? "warning" : "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
1124
|
+
title: options.title ?? DEFAULT_TITLE2,
|
|
1125
|
+
updatedAt: snapshot.updatedAt
|
|
1126
|
+
};
|
|
1127
|
+
};
|
|
1128
|
+
var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
|
|
1129
|
+
const model = createVoiceProviderStatusViewModel(snapshot, options);
|
|
1130
|
+
const providers = model.providers.length ? `<div class="absolute-voice-provider-status__providers">${model.providers.map((provider) => `<article class="absolute-voice-provider-status__provider absolute-voice-provider-status__provider--${escapeHtml3(provider.status)}">
|
|
1131
|
+
<header>
|
|
1132
|
+
<strong>${escapeHtml3(provider.label)}</strong>
|
|
1133
|
+
<span>${escapeHtml3(formatStatus(provider.status))}</span>
|
|
1134
|
+
</header>
|
|
1135
|
+
<p>${escapeHtml3(provider.detail)}</p>
|
|
1136
|
+
<dl>${provider.rows.map((row) => `<div>
|
|
1137
|
+
<dt>${escapeHtml3(row.label)}</dt>
|
|
1138
|
+
<dd>${escapeHtml3(row.value)}</dd>
|
|
1139
|
+
</div>`).join("")}</dl>
|
|
1140
|
+
</article>`).join("")}</div>` : '<p class="absolute-voice-provider-status__empty">Run voice traffic to see provider health.</p>';
|
|
1141
|
+
return `<section class="absolute-voice-provider-status absolute-voice-provider-status--${escapeHtml3(model.status)}">
|
|
1142
|
+
<header class="absolute-voice-provider-status__header">
|
|
1143
|
+
<span class="absolute-voice-provider-status__eyebrow">${escapeHtml3(model.title)}</span>
|
|
1144
|
+
<strong class="absolute-voice-provider-status__label">${escapeHtml3(model.label)}</strong>
|
|
1145
|
+
</header>
|
|
1146
|
+
<p class="absolute-voice-provider-status__description">${escapeHtml3(model.description)}</p>
|
|
1147
|
+
${providers}
|
|
1148
|
+
${model.error ? `<p class="absolute-voice-provider-status__error">${escapeHtml3(model.error)}</p>` : ""}
|
|
1149
|
+
</section>`;
|
|
1150
|
+
};
|
|
1151
|
+
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}`;
|
|
1152
|
+
var mountVoiceProviderStatus = (element, path = "/api/provider-status", options = {}) => {
|
|
1153
|
+
const store = createVoiceProviderStatusStore(path, options);
|
|
1154
|
+
const render = () => {
|
|
1155
|
+
element.innerHTML = renderVoiceProviderStatusHTML(store.getSnapshot(), options);
|
|
1156
|
+
};
|
|
1157
|
+
const unsubscribe = store.subscribe(render);
|
|
1158
|
+
render();
|
|
1159
|
+
store.refresh().catch(() => {});
|
|
1160
|
+
return {
|
|
1161
|
+
close: () => {
|
|
1162
|
+
unsubscribe();
|
|
1163
|
+
store.close();
|
|
1164
|
+
},
|
|
1165
|
+
refresh: store.refresh
|
|
1166
|
+
};
|
|
1167
|
+
};
|
|
1168
|
+
var defineVoiceProviderStatusElement = (tagName = "absolute-voice-provider-status") => {
|
|
1169
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
customElements.define(tagName, class AbsoluteVoiceProviderStatusElement extends HTMLElement {
|
|
1173
|
+
mounted;
|
|
1174
|
+
connectedCallback() {
|
|
1175
|
+
const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
|
|
1176
|
+
this.mounted = mountVoiceProviderStatus(this, this.getAttribute("path") ?? "/api/provider-status", {
|
|
1177
|
+
description: this.getAttribute("description") ?? undefined,
|
|
1178
|
+
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
1179
|
+
title: this.getAttribute("title") ?? undefined
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
disconnectedCallback() {
|
|
1183
|
+
this.mounted?.close();
|
|
1184
|
+
this.mounted = undefined;
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
// src/svelte/createVoiceProviderStatus.ts
|
|
1190
|
+
var createVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
|
|
1191
|
+
const store = createVoiceProviderStatusStore(path, options);
|
|
1192
|
+
return {
|
|
1193
|
+
...store,
|
|
1194
|
+
getHTML: () => renderVoiceProviderStatusHTML(store.getSnapshot(), options),
|
|
1195
|
+
getViewModel: () => createVoiceProviderStatusViewModel(store.getSnapshot(), options)
|
|
1196
|
+
};
|
|
1197
|
+
};
|
|
1198
|
+
// src/client/routingStatus.ts
|
|
1199
|
+
var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
|
|
1200
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1201
|
+
const response = await fetchImpl(path);
|
|
1202
|
+
if (!response.ok) {
|
|
1203
|
+
throw new Error(`Voice routing status failed: HTTP ${response.status}`);
|
|
1204
|
+
}
|
|
1205
|
+
return await response.json();
|
|
1206
|
+
};
|
|
1207
|
+
var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
|
|
1208
|
+
const listeners = new Set;
|
|
1209
|
+
let closed = false;
|
|
1210
|
+
let timer;
|
|
1211
|
+
let snapshot = {
|
|
1212
|
+
decision: null,
|
|
1213
|
+
error: null,
|
|
1214
|
+
isLoading: false
|
|
1215
|
+
};
|
|
1216
|
+
const emit = () => {
|
|
1217
|
+
for (const listener of listeners) {
|
|
1218
|
+
listener();
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
const refresh = async () => {
|
|
1222
|
+
if (closed) {
|
|
1223
|
+
return snapshot.decision;
|
|
1224
|
+
}
|
|
1225
|
+
snapshot = {
|
|
1226
|
+
...snapshot,
|
|
1227
|
+
error: null,
|
|
1228
|
+
isLoading: true
|
|
1229
|
+
};
|
|
1230
|
+
emit();
|
|
1231
|
+
try {
|
|
1232
|
+
const decision = await fetchVoiceRoutingStatus(path, options);
|
|
1233
|
+
snapshot = {
|
|
1234
|
+
decision,
|
|
1235
|
+
error: null,
|
|
1236
|
+
isLoading: false,
|
|
1237
|
+
updatedAt: Date.now()
|
|
1238
|
+
};
|
|
1239
|
+
emit();
|
|
1240
|
+
return decision;
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
snapshot = {
|
|
1243
|
+
...snapshot,
|
|
1244
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1245
|
+
isLoading: false
|
|
1246
|
+
};
|
|
1247
|
+
emit();
|
|
1248
|
+
throw error;
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
const close = () => {
|
|
1252
|
+
closed = true;
|
|
1253
|
+
if (timer) {
|
|
1254
|
+
clearInterval(timer);
|
|
1255
|
+
timer = undefined;
|
|
1256
|
+
}
|
|
1257
|
+
listeners.clear();
|
|
1258
|
+
};
|
|
1259
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
1260
|
+
timer = setInterval(() => {
|
|
1261
|
+
refresh().catch(() => {});
|
|
1262
|
+
}, options.intervalMs);
|
|
1263
|
+
}
|
|
1264
|
+
return {
|
|
1265
|
+
close,
|
|
1266
|
+
getServerSnapshot: () => snapshot,
|
|
1267
|
+
getSnapshot: () => snapshot,
|
|
1268
|
+
refresh,
|
|
1269
|
+
subscribe: (listener) => {
|
|
1270
|
+
listeners.add(listener);
|
|
1271
|
+
return () => {
|
|
1272
|
+
listeners.delete(listener);
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
// src/client/routingStatusWidget.ts
|
|
1279
|
+
var DEFAULT_TITLE3 = "Voice Routing";
|
|
1280
|
+
var DEFAULT_DESCRIPTION3 = "Latest provider routing decision from the self-hosted trace store.";
|
|
1281
|
+
var escapeHtml4 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1282
|
+
var formatValue = (value, fallback = "None") => typeof value === "string" && value.trim() ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : fallback;
|
|
1283
|
+
var createVoiceRoutingStatusViewModel = (snapshot, options = {}) => {
|
|
1284
|
+
const decision = snapshot.decision;
|
|
1285
|
+
const rows = decision ? [
|
|
1286
|
+
{ label: "Kind", value: decision.kind.toUpperCase() },
|
|
1287
|
+
{ label: "Policy", value: formatValue(decision.routing, "Unknown") },
|
|
1288
|
+
{ label: "Provider", value: formatValue(decision.provider, "Unknown") },
|
|
1289
|
+
{
|
|
1290
|
+
label: "Selected",
|
|
1291
|
+
value: formatValue(decision.selectedProvider, "Unknown")
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
label: "Fallback",
|
|
1295
|
+
value: formatValue(decision.fallbackProvider)
|
|
1296
|
+
},
|
|
1297
|
+
{ label: "Status", value: formatValue(decision.status, "unknown") },
|
|
1298
|
+
{
|
|
1299
|
+
label: "Latency budget",
|
|
1300
|
+
value: typeof decision.latencyBudgetMs === "number" ? `${decision.latencyBudgetMs}ms` : "None"
|
|
1301
|
+
}
|
|
1302
|
+
] : [];
|
|
1303
|
+
return {
|
|
1304
|
+
decision,
|
|
1305
|
+
description: options.description ?? DEFAULT_DESCRIPTION3,
|
|
1306
|
+
error: snapshot.error,
|
|
1307
|
+
isLoading: snapshot.isLoading,
|
|
1308
|
+
label: snapshot.error ? "Unavailable" : decision ? `${formatValue(decision.kind).toUpperCase()} ${formatValue(decision.status, "unknown")}` : snapshot.isLoading ? "Checking" : "No routing yet",
|
|
1309
|
+
rows,
|
|
1310
|
+
status: snapshot.error ? "error" : decision ? "ready" : snapshot.isLoading ? "loading" : "empty",
|
|
1311
|
+
title: options.title ?? DEFAULT_TITLE3,
|
|
1312
|
+
updatedAt: snapshot.updatedAt
|
|
1313
|
+
};
|
|
1314
|
+
};
|
|
1315
|
+
var renderVoiceRoutingStatusHTML = (snapshot, options = {}) => {
|
|
1316
|
+
const model = createVoiceRoutingStatusViewModel(snapshot, options);
|
|
1317
|
+
const rows = model.rows.length ? `<div class="absolute-voice-routing-status__grid">${model.rows.map((row) => `<div>
|
|
1318
|
+
<span>${escapeHtml4(row.label)}</span>
|
|
1319
|
+
<strong>${escapeHtml4(row.value)}</strong>
|
|
1320
|
+
</div>`).join("")}</div>` : '<p class="absolute-voice-routing-status__empty">Start a voice session to see the selected provider.</p>';
|
|
1321
|
+
return `<section class="absolute-voice-routing-status absolute-voice-routing-status--${escapeHtml4(model.status)}">
|
|
1322
|
+
<header class="absolute-voice-routing-status__header">
|
|
1323
|
+
<span class="absolute-voice-routing-status__eyebrow">${escapeHtml4(model.title)}</span>
|
|
1324
|
+
<strong class="absolute-voice-routing-status__label">${escapeHtml4(model.label)}</strong>
|
|
1325
|
+
</header>
|
|
1326
|
+
<p class="absolute-voice-routing-status__description">${escapeHtml4(model.description)}</p>
|
|
1327
|
+
${rows}
|
|
1328
|
+
${model.error ? `<p class="absolute-voice-routing-status__error">${escapeHtml4(model.error)}</p>` : ""}
|
|
1329
|
+
</section>`;
|
|
1330
|
+
};
|
|
1331
|
+
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}`;
|
|
1332
|
+
var mountVoiceRoutingStatus = (element, path = "/api/routing/latest", options = {}) => {
|
|
1333
|
+
const store = createVoiceRoutingStatusStore(path, options);
|
|
1334
|
+
const render = () => {
|
|
1335
|
+
element.innerHTML = renderVoiceRoutingStatusHTML(store.getSnapshot(), options);
|
|
1336
|
+
};
|
|
1337
|
+
const unsubscribe = store.subscribe(render);
|
|
1338
|
+
render();
|
|
1339
|
+
store.refresh().catch(() => {});
|
|
1340
|
+
return {
|
|
1341
|
+
close: () => {
|
|
1342
|
+
unsubscribe();
|
|
1343
|
+
store.close();
|
|
1344
|
+
},
|
|
1345
|
+
refresh: store.refresh
|
|
1346
|
+
};
|
|
1347
|
+
};
|
|
1348
|
+
var defineVoiceRoutingStatusElement = (tagName = "absolute-voice-routing-status") => {
|
|
1349
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
customElements.define(tagName, class AbsoluteVoiceRoutingStatusElement extends HTMLElement {
|
|
1353
|
+
mounted;
|
|
1354
|
+
connectedCallback() {
|
|
1355
|
+
const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
|
|
1356
|
+
this.mounted = mountVoiceRoutingStatus(this, this.getAttribute("path") ?? "/api/routing/latest", {
|
|
1357
|
+
description: this.getAttribute("description") ?? undefined,
|
|
1358
|
+
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
1359
|
+
title: this.getAttribute("title") ?? undefined
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
disconnectedCallback() {
|
|
1363
|
+
this.mounted?.close();
|
|
1364
|
+
this.mounted = undefined;
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
};
|
|
1368
|
+
|
|
1369
|
+
// src/svelte/createVoiceRoutingStatus.ts
|
|
1370
|
+
var createVoiceRoutingStatus = (path = "/api/routing/latest", options = {}) => {
|
|
1371
|
+
const store = createVoiceRoutingStatusStore(path, options);
|
|
1372
|
+
return {
|
|
1373
|
+
...store,
|
|
1374
|
+
getHTML: () => renderVoiceRoutingStatusHTML(store.getSnapshot(), options),
|
|
1375
|
+
getViewModel: () => createVoiceRoutingStatusViewModel(store.getSnapshot(), options)
|
|
1376
|
+
};
|
|
1377
|
+
};
|
|
1378
|
+
// src/client/workflowStatus.ts
|
|
1379
|
+
var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
|
|
1380
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1381
|
+
const response = await fetchImpl(path);
|
|
1382
|
+
if (!response.ok) {
|
|
1383
|
+
throw new Error(`Voice workflow status failed: HTTP ${response.status}`);
|
|
1384
|
+
}
|
|
1385
|
+
return await response.json();
|
|
1386
|
+
};
|
|
1387
|
+
var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options = {}) => {
|
|
1388
|
+
const listeners = new Set;
|
|
1389
|
+
let closed = false;
|
|
1390
|
+
let timer;
|
|
1391
|
+
let snapshot = {
|
|
1392
|
+
error: null,
|
|
1393
|
+
isLoading: false
|
|
1394
|
+
};
|
|
1395
|
+
const emit = () => {
|
|
1396
|
+
for (const listener of listeners) {
|
|
1397
|
+
listener();
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
const refresh = async () => {
|
|
1401
|
+
if (closed) {
|
|
1402
|
+
return snapshot.report;
|
|
1403
|
+
}
|
|
1404
|
+
snapshot = {
|
|
1405
|
+
...snapshot,
|
|
1406
|
+
error: null,
|
|
1407
|
+
isLoading: true
|
|
1408
|
+
};
|
|
1409
|
+
emit();
|
|
1410
|
+
try {
|
|
1411
|
+
const report = await fetchVoiceWorkflowStatus(path, options);
|
|
1412
|
+
snapshot = {
|
|
1413
|
+
error: null,
|
|
1414
|
+
isLoading: false,
|
|
1415
|
+
report,
|
|
1416
|
+
updatedAt: Date.now()
|
|
1417
|
+
};
|
|
1418
|
+
emit();
|
|
1419
|
+
return report;
|
|
1420
|
+
} catch (error) {
|
|
1421
|
+
snapshot = {
|
|
1422
|
+
...snapshot,
|
|
1423
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1424
|
+
isLoading: false
|
|
1425
|
+
};
|
|
1426
|
+
emit();
|
|
1427
|
+
throw error;
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
const close = () => {
|
|
1431
|
+
closed = true;
|
|
1432
|
+
if (timer) {
|
|
1433
|
+
clearInterval(timer);
|
|
1434
|
+
timer = undefined;
|
|
1435
|
+
}
|
|
1436
|
+
listeners.clear();
|
|
1437
|
+
};
|
|
1438
|
+
if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
|
|
1439
|
+
timer = setInterval(() => {
|
|
1440
|
+
refresh().catch(() => {});
|
|
1441
|
+
}, options.intervalMs);
|
|
1442
|
+
}
|
|
1443
|
+
return {
|
|
1444
|
+
close,
|
|
1445
|
+
getServerSnapshot: () => snapshot,
|
|
1446
|
+
getSnapshot: () => snapshot,
|
|
1447
|
+
refresh,
|
|
1448
|
+
subscribe: (listener) => {
|
|
1449
|
+
listeners.add(listener);
|
|
1450
|
+
return () => {
|
|
1451
|
+
listeners.delete(listener);
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
};
|
|
1456
|
+
|
|
1457
|
+
// src/svelte/createVoiceWorkflowStatus.ts
|
|
1458
|
+
var createVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => createVoiceWorkflowStatusStore(path, options);
|
|
551
1459
|
// src/client/htmx.ts
|
|
552
1460
|
var DEFAULT_EVENT_NAME = "voice-refresh";
|
|
553
1461
|
var DEFAULT_QUERY_PARAM = "sessionId";
|
|
@@ -1010,6 +1918,7 @@ var resolveVoiceRuntimePreset = (name = "default") => {
|
|
|
1010
1918
|
var createInitialState2 = (stream) => ({
|
|
1011
1919
|
assistantAudio: [...stream.assistantAudio],
|
|
1012
1920
|
assistantTexts: [...stream.assistantTexts],
|
|
1921
|
+
call: stream.call,
|
|
1013
1922
|
error: stream.error,
|
|
1014
1923
|
isConnected: stream.isConnected,
|
|
1015
1924
|
isRecording: false,
|
|
@@ -1039,6 +1948,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1039
1948
|
...state,
|
|
1040
1949
|
assistantAudio: [...stream.assistantAudio],
|
|
1041
1950
|
assistantTexts: [...stream.assistantTexts],
|
|
1951
|
+
call: stream.call,
|
|
1042
1952
|
error: stream.error,
|
|
1043
1953
|
isConnected: stream.isConnected,
|
|
1044
1954
|
partial: stream.partial,
|
|
@@ -1116,6 +2026,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1116
2026
|
bindHTMX(bindingOptions) {
|
|
1117
2027
|
return bindVoiceHTMX(stream, bindingOptions);
|
|
1118
2028
|
},
|
|
2029
|
+
callControl: (message) => stream.callControl(message),
|
|
1119
2030
|
close,
|
|
1120
2031
|
endTurn: () => stream.endTurn(),
|
|
1121
2032
|
get error() {
|
|
@@ -1168,10 +2079,19 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1168
2079
|
},
|
|
1169
2080
|
get assistantAudio() {
|
|
1170
2081
|
return state.assistantAudio;
|
|
2082
|
+
},
|
|
2083
|
+
get call() {
|
|
2084
|
+
return state.call;
|
|
1171
2085
|
}
|
|
1172
2086
|
};
|
|
1173
2087
|
};
|
|
1174
2088
|
export {
|
|
2089
|
+
createVoiceWorkflowStatus,
|
|
1175
2090
|
createVoiceStream2 as createVoiceStream,
|
|
1176
|
-
|
|
2091
|
+
createVoiceRoutingStatus,
|
|
2092
|
+
createVoiceProviderStatus,
|
|
2093
|
+
createVoiceProviderSimulationControls,
|
|
2094
|
+
createVoiceOpsStatus,
|
|
2095
|
+
createVoiceController,
|
|
2096
|
+
createVoiceAppKitStatus
|
|
1177
2097
|
};
|