@absolutejs/voice 0.0.22-beta.51 → 0.0.22-beta.53

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.
@@ -1,3 +1,4 @@
1
+ export { VoiceOpsStatusComponent } from './voice-ops-status.component';
1
2
  export { VoiceAppKitStatusService } from './voice-app-kit-status.service';
2
3
  export { VoiceStreamService } from './voice-stream.service';
3
4
  export { VoiceControllerService } from './voice-controller.service';
@@ -69,8 +69,8 @@ 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/angular/voice-app-kit-status.service.ts
73
- import { computed, Injectable, signal } from "@angular/core";
72
+ // src/angular/voice-ops-status.component.ts
73
+ import { Component, Input, signal } from "@angular/core";
74
74
 
75
75
  // src/client/appKitStatus.ts
76
76
  var fetchVoiceAppKitStatus = async (path = "/app-kit/status", options = {}) => {
@@ -151,7 +151,269 @@ var createVoiceAppKitStatusStore = (path = "/app-kit/status", options = {}) => {
151
151
  };
152
152
  };
153
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
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/angular/voice-ops-status.component.ts
280
+ var _dec = [
281
+ Component({
282
+ selector: "absolute-voice-ops-status",
283
+ standalone: true,
284
+ template: `
285
+ <section
286
+ class="absolute-voice-ops-status"
287
+ [class.absolute-voice-ops-status--pass]="model().status === 'pass'"
288
+ [class.absolute-voice-ops-status--fail]="model().status === 'fail'"
289
+ [class.absolute-voice-ops-status--loading]="model().status === 'loading'"
290
+ [class.absolute-voice-ops-status--error]="model().status === 'error'"
291
+ >
292
+ <header class="absolute-voice-ops-status__header">
293
+ <span class="absolute-voice-ops-status__eyebrow">{{
294
+ model().title
295
+ }}</span>
296
+ <strong class="absolute-voice-ops-status__label">{{
297
+ model().label
298
+ }}</strong>
299
+ </header>
300
+ <p class="absolute-voice-ops-status__description">
301
+ {{ model().description }}
302
+ </p>
303
+ <div class="absolute-voice-ops-status__summary">
304
+ <span>{{ model().passed }} passing</span>
305
+ <span>{{ model().total - model().passed }} failing</span>
306
+ <span>{{ model().total }} checks</span>
307
+ </div>
308
+ <ul class="absolute-voice-ops-status__surfaces">
309
+ @if (model().surfaces.length > 0) {
310
+ @for (surface of model().surfaces; track surface.id) {
311
+ <li
312
+ class="absolute-voice-ops-status__surface"
313
+ [class.absolute-voice-ops-status__surface--pass]="
314
+ surface.status === 'pass'
315
+ "
316
+ [class.absolute-voice-ops-status__surface--fail]="
317
+ surface.status === 'fail'
318
+ "
319
+ >
320
+ <span>{{ surface.label }}</span>
321
+ <strong>{{ surface.detail }}</strong>
322
+ </li>
323
+ }
324
+ } @else {
325
+ <li class="absolute-voice-ops-status__surface">
326
+ <span>Status</span>
327
+ <strong>Waiting for first check</strong>
328
+ </li>
329
+ }
330
+ </ul>
331
+ @if (model().error) {
332
+ <p class="absolute-voice-ops-status__error">{{ model().error }}</p>
333
+ }
334
+ @if (model().links.length > 0) {
335
+ <nav class="absolute-voice-ops-status__links">
336
+ @for (link of model().links.slice(0, 4); track link.href) {
337
+ <a [href]="link.href">{{ link.label }}</a>
338
+ }
339
+ </nav>
340
+ }
341
+ </section>
342
+ `
343
+ })
344
+ ];
345
+ var _dec2 = [
346
+ Input()
347
+ ];
348
+ var _dec3 = [
349
+ Input()
350
+ ];
351
+ var _dec4 = [
352
+ Input()
353
+ ];
354
+ var _dec5 = [
355
+ Input()
356
+ ];
357
+ var _dec6 = [
358
+ Input()
359
+ ];
360
+ var _init = __decoratorStart(undefined);
361
+
362
+ class VoiceOpsStatusComponent {
363
+ constructor() {
364
+ this.description = __runInitializers(_init, 8, this);
365
+ __runInitializers(_init, 11, this);
366
+ this.includeLinks = __runInitializers(_init, 12, this, true);
367
+ __runInitializers(_init, 15, this);
368
+ this.intervalMs = __runInitializers(_init, 16, this);
369
+ __runInitializers(_init, 19, this);
370
+ this.path = __runInitializers(_init, 20, this, "/app-kit/status");
371
+ __runInitializers(_init, 23, this);
372
+ this.title = __runInitializers(_init, 24, this);
373
+ __runInitializers(_init, 27, this);
374
+ }
375
+ cleanup = () => {};
376
+ store;
377
+ model = signal(createVoiceOpsStatusViewModel({
378
+ error: null,
379
+ isLoading: true
380
+ }));
381
+ ngOnInit() {
382
+ const options = this.options();
383
+ this.store = createVoiceAppKitStatusStore(this.path, options);
384
+ const sync = () => {
385
+ this.model.set(createVoiceOpsStatusViewModel(this.store.getSnapshot(), options));
386
+ };
387
+ this.cleanup = this.store.subscribe(sync);
388
+ sync();
389
+ if (typeof window !== "undefined") {
390
+ this.store.refresh().catch(() => {});
391
+ }
392
+ }
393
+ ngOnDestroy() {
394
+ this.cleanup();
395
+ this.store?.close();
396
+ }
397
+ options() {
398
+ return {
399
+ description: this.description,
400
+ includeLinks: this.includeLinks,
401
+ intervalMs: this.intervalMs,
402
+ title: this.title
403
+ };
404
+ }
405
+ }
406
+ __decorateElement(_init, 5, "description", _dec2, VoiceOpsStatusComponent);
407
+ __decorateElement(_init, 5, "includeLinks", _dec3, VoiceOpsStatusComponent);
408
+ __decorateElement(_init, 5, "intervalMs", _dec4, VoiceOpsStatusComponent);
409
+ __decorateElement(_init, 5, "path", _dec5, VoiceOpsStatusComponent);
410
+ __decorateElement(_init, 5, "title", _dec6, VoiceOpsStatusComponent);
411
+ VoiceOpsStatusComponent = __decorateElement(_init, 0, "VoiceOpsStatusComponent", _dec, VoiceOpsStatusComponent);
412
+ __runInitializers(_init, 1, VoiceOpsStatusComponent);
413
+ __decoratorMetadata(_init, VoiceOpsStatusComponent);
414
+ let _VoiceOpsStatusComponent = VoiceOpsStatusComponent;
154
415
  // src/angular/voice-app-kit-status.service.ts
416
+ import { computed, Injectable, signal as signal2 } from "@angular/core";
155
417
  var _dec = [
156
418
  Injectable({ providedIn: "root" })
157
419
  ];
@@ -160,10 +422,10 @@ var _init = __decoratorStart(undefined);
160
422
  class VoiceAppKitStatusService {
161
423
  connect(path = "/app-kit/status", options = {}) {
162
424
  const store = createVoiceAppKitStatusStore(path, options);
163
- const errorSignal = signal(null);
164
- const isLoadingSignal = signal(false);
165
- const reportSignal = signal(undefined);
166
- const updatedAtSignal = signal(undefined);
425
+ const errorSignal = signal2(null);
426
+ const isLoadingSignal = signal2(false);
427
+ const reportSignal = signal2(undefined);
428
+ const updatedAtSignal = signal2(undefined);
167
429
  const sync = () => {
168
430
  const snapshot = store.getSnapshot();
169
431
  errorSignal.set(snapshot.error);
@@ -194,7 +456,7 @@ __runInitializers(_init, 1, VoiceAppKitStatusService);
194
456
  __decoratorMetadata(_init, VoiceAppKitStatusService);
195
457
  let _VoiceAppKitStatusService = VoiceAppKitStatusService;
196
458
  // src/angular/voice-stream.service.ts
197
- import { computed as computed2, Injectable as Injectable2, signal as signal2 } from "@angular/core";
459
+ import { computed as computed2, Injectable as Injectable2, signal as signal3 } from "@angular/core";
198
460
 
199
461
  // src/client/actions.ts
200
462
  var normalizeErrorMessage = (value) => {
@@ -718,15 +980,15 @@ var _init = __decoratorStart(undefined);
718
980
  class VoiceStreamService {
719
981
  connect(path, options = {}) {
720
982
  const stream = createVoiceStream(path, options);
721
- const assistantAudioSignal = signal2([]);
722
- const assistantTextsSignal = signal2([]);
723
- const callSignal = signal2(null);
724
- const errorSignal = signal2(null);
725
- const isConnectedSignal = signal2(false);
726
- const partialSignal = signal2("");
727
- const sessionIdSignal = signal2(stream.sessionId);
728
- const statusSignal = signal2(stream.status);
729
- const turnsSignal = signal2([]);
983
+ const assistantAudioSignal = signal3([]);
984
+ const assistantTextsSignal = signal3([]);
985
+ const callSignal = signal3(null);
986
+ const errorSignal = signal3(null);
987
+ const isConnectedSignal = signal3(false);
988
+ const partialSignal = signal3("");
989
+ const sessionIdSignal = signal3(stream.sessionId);
990
+ const statusSignal = signal3(stream.status);
991
+ const turnsSignal = signal3([]);
730
992
  const sync = () => {
731
993
  assistantAudioSignal.set([...stream.assistantAudio]);
732
994
  assistantTextsSignal.set([...stream.assistantTexts]);
@@ -765,7 +1027,7 @@ __runInitializers(_init, 1, VoiceStreamService);
765
1027
  __decoratorMetadata(_init, VoiceStreamService);
766
1028
  let _VoiceStreamService = VoiceStreamService;
767
1029
  // src/angular/voice-controller.service.ts
768
- import { computed as computed3, Injectable as Injectable3, signal as signal3 } from "@angular/core";
1030
+ import { computed as computed3, Injectable as Injectable3, signal as signal4 } from "@angular/core";
769
1031
 
770
1032
  // src/client/htmx.ts
771
1033
  var DEFAULT_EVENT_NAME = "voice-refresh";
@@ -1406,16 +1668,16 @@ var _init = __decoratorStart(undefined);
1406
1668
  class VoiceControllerService {
1407
1669
  connect(path, options = {}) {
1408
1670
  const controller = createVoiceController(path, options);
1409
- const assistantAudioSignal = signal3([]);
1410
- const assistantTextsSignal = signal3([]);
1411
- const errorSignal = signal3(null);
1412
- const isConnectedSignal = signal3(false);
1413
- const isRecordingSignal = signal3(false);
1414
- const partialSignal = signal3("");
1415
- const recordingErrorSignal = signal3(null);
1416
- const sessionIdSignal = signal3(controller.sessionId);
1417
- const statusSignal = signal3(controller.status);
1418
- const turnsSignal = signal3([]);
1671
+ const assistantAudioSignal = signal4([]);
1672
+ const assistantTextsSignal = signal4([]);
1673
+ const errorSignal = signal4(null);
1674
+ const isConnectedSignal = signal4(false);
1675
+ const isRecordingSignal = signal4(false);
1676
+ const partialSignal = signal4("");
1677
+ const recordingErrorSignal = signal4(null);
1678
+ const sessionIdSignal = signal4(controller.sessionId);
1679
+ const statusSignal = signal4(controller.status);
1680
+ const turnsSignal = signal4([]);
1419
1681
  const sync = () => {
1420
1682
  assistantAudioSignal.set([...controller.assistantAudio]);
1421
1683
  assistantTextsSignal.set([...controller.assistantTexts]);
@@ -1459,7 +1721,7 @@ __runInitializers(_init, 1, VoiceControllerService);
1459
1721
  __decoratorMetadata(_init, VoiceControllerService);
1460
1722
  let _VoiceControllerService = VoiceControllerService;
1461
1723
  // src/angular/voice-provider-status.service.ts
1462
- import { computed as computed4, Injectable as Injectable4, signal as signal4 } from "@angular/core";
1724
+ import { computed as computed4, Injectable as Injectable4, signal as signal5 } from "@angular/core";
1463
1725
 
1464
1726
  // src/client/providerStatus.ts
1465
1727
  var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
@@ -1550,10 +1812,10 @@ var _init = __decoratorStart(undefined);
1550
1812
  class VoiceProviderStatusService {
1551
1813
  connect(path = "/api/provider-status", options = {}) {
1552
1814
  const store = createVoiceProviderStatusStore(path, options);
1553
- const errorSignal = signal4(null);
1554
- const isLoadingSignal = signal4(false);
1555
- const providersSignal = signal4([]);
1556
- const updatedAtSignal = signal4(undefined);
1815
+ const errorSignal = signal5(null);
1816
+ const isLoadingSignal = signal5(false);
1817
+ const providersSignal = signal5([]);
1818
+ const updatedAtSignal = signal5(undefined);
1557
1819
  const sync = () => {
1558
1820
  const snapshot = store.getSnapshot();
1559
1821
  errorSignal.set(snapshot.error);
@@ -1582,7 +1844,7 @@ __runInitializers(_init, 1, VoiceProviderStatusService);
1582
1844
  __decoratorMetadata(_init, VoiceProviderStatusService);
1583
1845
  let _VoiceProviderStatusService = VoiceProviderStatusService;
1584
1846
  // src/angular/voice-workflow-status.service.ts
1585
- import { computed as computed5, Injectable as Injectable5, signal as signal5 } from "@angular/core";
1847
+ import { computed as computed5, Injectable as Injectable5, signal as signal6 } from "@angular/core";
1586
1848
 
1587
1849
  // src/client/workflowStatus.ts
1588
1850
  var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
@@ -1672,10 +1934,10 @@ var _init = __decoratorStart(undefined);
1672
1934
  class VoiceWorkflowStatusService {
1673
1935
  connect(path = "/evals/scenarios/json", options = {}) {
1674
1936
  const store = createVoiceWorkflowStatusStore(path, options);
1675
- const errorSignal = signal5(null);
1676
- const isLoadingSignal = signal5(false);
1677
- const reportSignal = signal5(undefined);
1678
- const updatedAtSignal = signal5(undefined);
1937
+ const errorSignal = signal6(null);
1938
+ const isLoadingSignal = signal6(false);
1939
+ const reportSignal = signal6(undefined);
1940
+ const updatedAtSignal = signal6(undefined);
1679
1941
  const sync = () => {
1680
1942
  const snapshot = store.getSnapshot();
1681
1943
  errorSignal.set(snapshot.error);
@@ -1709,6 +1971,7 @@ export {
1709
1971
  VoiceWorkflowStatusService,
1710
1972
  VoiceStreamService,
1711
1973
  VoiceProviderStatusService,
1974
+ VoiceOpsStatusComponent,
1712
1975
  VoiceControllerService,
1713
1976
  VoiceAppKitStatusService
1714
1977
  };
@@ -0,0 +1,15 @@
1
+ import { OnDestroy, OnInit } from '@angular/core';
2
+ import { type VoiceOpsStatusViewModel } from '../client/opsStatusWidget';
3
+ export declare class VoiceOpsStatusComponent implements OnDestroy, OnInit {
4
+ description?: string;
5
+ includeLinks: boolean;
6
+ intervalMs?: number;
7
+ path: string;
8
+ title?: string;
9
+ private cleanup;
10
+ private store?;
11
+ model: import("@angular/core").WritableSignal<VoiceOpsStatusViewModel>;
12
+ ngOnInit(): void;
13
+ ngOnDestroy(): void;
14
+ private options;
15
+ }
@@ -6,8 +6,10 @@ export { bindVoiceBargeIn, createVoiceDuplexController } from './duplex';
6
6
  export { bindVoiceHTMX } from './htmx';
7
7
  export { createMicrophoneCapture } from './microphone';
8
8
  export { createVoiceAppKitStatusStore, fetchVoiceAppKitStatus } from './appKitStatus';
9
+ export { createVoiceOpsStatusViewModel, defineVoiceOpsStatusElement, getVoiceOpsStatusCSS, getVoiceOpsStatusLabel, mountVoiceOpsStatus, renderVoiceOpsStatusHTML } from './opsStatusWidget';
9
10
  export { createVoiceProviderStatusStore, fetchVoiceProviderStatus } from './providerStatus';
10
11
  export { createVoiceWorkflowStatusStore, fetchVoiceWorkflowStatus } from './workflowStatus';
11
12
  export type { VoiceAppKitStatusClientOptions, VoiceAppKitStatusSnapshot } from './appKitStatus';
13
+ export type { VoiceOpsStatusSurfaceView, VoiceOpsStatusViewModel, VoiceOpsStatusWidgetOptions } from './opsStatusWidget';
12
14
  export type { VoiceProviderStatusClientOptions, VoiceProviderStatusSnapshot } from './providerStatus';
13
15
  export type { VoiceWorkflowStatusClientOptions, VoiceWorkflowStatusSnapshot } from './workflowStatus';
@@ -1701,6 +1701,130 @@ var createVoiceAppKitStatusStore = (path = "/app-kit/status", options = {}) => {
1701
1701
  }
1702
1702
  };
1703
1703
  };
1704
+ // src/client/opsStatusWidget.ts
1705
+ var DEFAULT_TITLE = "Voice Ops Status";
1706
+ var DEFAULT_DESCRIPTION = "Certified workflow, provider, and handoff readiness from the AbsoluteJS voice app kit.";
1707
+ var SURFACE_LABELS = {
1708
+ handoffs: "Handoffs",
1709
+ providers: "Providers",
1710
+ quality: "Quality",
1711
+ sessions: "Sessions",
1712
+ workflows: "Workflows"
1713
+ };
1714
+ var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1715
+ var readNumber = (value, key) => value && typeof value === "object" && (key in value) ? Number(value[key] ?? 0) : 0;
1716
+ var surfaceDetail = (surface) => {
1717
+ const total = readNumber(surface, "total");
1718
+ const failed = readNumber(surface, "failed");
1719
+ const degraded = readNumber(surface, "degraded");
1720
+ const source = surface && typeof surface === "object" && "source" in surface && typeof surface.source === "string" ? ` from ${surface.source}` : "";
1721
+ if (degraded > 0) {
1722
+ return `${degraded} degraded of ${total}`;
1723
+ }
1724
+ if (failed > 0) {
1725
+ return `${failed} failing of ${total}${source}`;
1726
+ }
1727
+ return total > 0 ? `${total} passing${source}` : `No failures${source}`;
1728
+ };
1729
+ var getVoiceOpsStatusLabel = (report, error) => {
1730
+ if (error) {
1731
+ return "Unavailable";
1732
+ }
1733
+ if (!report) {
1734
+ return "Checking";
1735
+ }
1736
+ return report.status === "pass" ? "Passing" : "Needs attention";
1737
+ };
1738
+ var createVoiceOpsStatusViewModel = (snapshot, options = {}) => {
1739
+ const report = snapshot.report;
1740
+ const surfaces = Object.entries(report?.surfaces ?? {}).map(([id, surface]) => {
1741
+ const failed = readNumber(surface, "failed") || readNumber(surface, "degraded");
1742
+ const total = readNumber(surface, "total");
1743
+ const status = surface && typeof surface === "object" && "status" in surface ? surface.status ?? "pass" : "pass";
1744
+ return {
1745
+ detail: surfaceDetail(surface),
1746
+ failed,
1747
+ id,
1748
+ label: SURFACE_LABELS[id] ?? id,
1749
+ status,
1750
+ total
1751
+ };
1752
+ });
1753
+ return {
1754
+ description: options.description ?? DEFAULT_DESCRIPTION,
1755
+ error: snapshot.error,
1756
+ isLoading: snapshot.isLoading,
1757
+ label: getVoiceOpsStatusLabel(report, snapshot.error),
1758
+ links: options.includeLinks === false ? [] : report?.links ?? [],
1759
+ passed: report?.passed ?? 0,
1760
+ status: snapshot.error ? "error" : report ? report.status : snapshot.isLoading ? "loading" : "loading",
1761
+ surfaces,
1762
+ title: options.title ?? DEFAULT_TITLE,
1763
+ total: report?.total ?? 0,
1764
+ updatedAt: snapshot.updatedAt
1765
+ };
1766
+ };
1767
+ var renderVoiceOpsStatusHTML = (snapshot, options = {}) => {
1768
+ const model = createVoiceOpsStatusViewModel(snapshot, options);
1769
+ const surfaces = model.surfaces.length ? model.surfaces.map((surface) => `<li class="absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${escapeHtml(surface.status)}">
1770
+ <span>${escapeHtml(surface.label)}</span>
1771
+ <strong>${escapeHtml(surface.detail)}</strong>
1772
+ </li>`).join("") : '<li class="absolute-voice-ops-status__surface"><span>Status</span><strong>Waiting for first check</strong></li>';
1773
+ 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>` : "";
1774
+ return `<section class="absolute-voice-ops-status absolute-voice-ops-status--${escapeHtml(model.status)}">
1775
+ <header class="absolute-voice-ops-status__header">
1776
+ <span class="absolute-voice-ops-status__eyebrow">${escapeHtml(model.title)}</span>
1777
+ <strong class="absolute-voice-ops-status__label">${escapeHtml(model.label)}</strong>
1778
+ </header>
1779
+ <p class="absolute-voice-ops-status__description">${escapeHtml(model.description)}</p>
1780
+ <div class="absolute-voice-ops-status__summary">
1781
+ <span>${model.passed} passing</span>
1782
+ <span>${Math.max(model.total - model.passed, 0)} failing</span>
1783
+ <span>${model.total} checks</span>
1784
+ </div>
1785
+ <ul class="absolute-voice-ops-status__surfaces">${surfaces}</ul>
1786
+ ${model.error ? `<p class="absolute-voice-ops-status__error">${escapeHtml(model.error)}</p>` : ""}
1787
+ ${links}
1788
+ </section>`;
1789
+ };
1790
+ 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}`;
1791
+ var mountVoiceOpsStatus = (element, path = "/app-kit/status", options = {}) => {
1792
+ const store = createVoiceAppKitStatusStore(path, options);
1793
+ const render = () => {
1794
+ element.innerHTML = renderVoiceOpsStatusHTML(store.getSnapshot(), options);
1795
+ };
1796
+ const unsubscribe = store.subscribe(render);
1797
+ render();
1798
+ store.refresh().catch(() => {});
1799
+ return {
1800
+ close: () => {
1801
+ unsubscribe();
1802
+ store.close();
1803
+ },
1804
+ refresh: store.refresh
1805
+ };
1806
+ };
1807
+ var defineVoiceOpsStatusElement = (tagName = "absolute-voice-ops-status") => {
1808
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
1809
+ return;
1810
+ }
1811
+ customElements.define(tagName, class AbsoluteVoiceOpsStatusElement extends HTMLElement {
1812
+ mounted;
1813
+ connectedCallback() {
1814
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
1815
+ this.mounted = mountVoiceOpsStatus(this, this.getAttribute("path") ?? "/app-kit/status", {
1816
+ description: this.getAttribute("description") ?? undefined,
1817
+ includeLinks: this.getAttribute("include-links") !== "false",
1818
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
1819
+ title: this.getAttribute("title") ?? undefined
1820
+ });
1821
+ }
1822
+ disconnectedCallback() {
1823
+ this.mounted?.close();
1824
+ this.mounted = undefined;
1825
+ }
1826
+ });
1827
+ };
1704
1828
  // src/client/providerStatus.ts
1705
1829
  var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
1706
1830
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -1859,13 +1983,19 @@ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options =
1859
1983
  };
1860
1984
  };
1861
1985
  export {
1986
+ renderVoiceOpsStatusHTML,
1987
+ mountVoiceOpsStatus,
1988
+ getVoiceOpsStatusLabel,
1989
+ getVoiceOpsStatusCSS,
1862
1990
  fetchVoiceWorkflowStatus,
1863
1991
  fetchVoiceProviderStatus,
1864
1992
  fetchVoiceAppKitStatus,
1993
+ defineVoiceOpsStatusElement,
1865
1994
  decodeVoiceAudioChunk,
1866
1995
  createVoiceWorkflowStatusStore,
1867
1996
  createVoiceStream,
1868
1997
  createVoiceProviderStatusStore,
1998
+ createVoiceOpsStatusViewModel,
1869
1999
  createVoiceDuplexController,
1870
2000
  createVoiceController,
1871
2001
  createVoiceConnection,
@@ -0,0 +1,40 @@
1
+ import type { VoiceAppKitStatusReport } from '../appKit';
2
+ import { type VoiceAppKitStatusClientOptions, type VoiceAppKitStatusSnapshot } from './appKitStatus';
3
+ export type VoiceOpsStatusSurfaceView = {
4
+ detail: string;
5
+ failed: number;
6
+ id: string;
7
+ label: string;
8
+ status: 'pass' | 'fail';
9
+ total: number;
10
+ };
11
+ export type VoiceOpsStatusViewModel = {
12
+ description: string;
13
+ error: string | null;
14
+ isLoading: boolean;
15
+ label: string;
16
+ links: Array<{
17
+ href: string;
18
+ label: string;
19
+ }>;
20
+ passed: number;
21
+ status: 'pass' | 'fail' | 'loading' | 'error';
22
+ surfaces: VoiceOpsStatusSurfaceView[];
23
+ title: string;
24
+ total: number;
25
+ updatedAt?: number;
26
+ };
27
+ export type VoiceOpsStatusWidgetOptions = VoiceAppKitStatusClientOptions & {
28
+ description?: string;
29
+ includeLinks?: boolean;
30
+ title?: string;
31
+ };
32
+ export declare const getVoiceOpsStatusLabel: (report?: VoiceAppKitStatusReport | null, error?: string | null) => "Unavailable" | "Checking" | "Passing" | "Needs attention";
33
+ export declare const createVoiceOpsStatusViewModel: (snapshot: VoiceAppKitStatusSnapshot, options?: VoiceOpsStatusWidgetOptions) => VoiceOpsStatusViewModel;
34
+ export declare const renderVoiceOpsStatusHTML: (snapshot: VoiceAppKitStatusSnapshot, options?: VoiceOpsStatusWidgetOptions) => string;
35
+ export declare const getVoiceOpsStatusCSS: () => string;
36
+ export declare const mountVoiceOpsStatus: (element: Element, path?: string, options?: VoiceOpsStatusWidgetOptions) => {
37
+ close: () => void;
38
+ refresh: () => Promise<VoiceAppKitStatusReport | undefined>;
39
+ };
40
+ export declare const defineVoiceOpsStatusElement: (tagName?: string) => void;
@@ -0,0 +1,6 @@
1
+ import { type VoiceOpsStatusWidgetOptions } from '../client/opsStatusWidget';
2
+ export type VoiceOpsStatusProps = VoiceOpsStatusWidgetOptions & {
3
+ className?: string;
4
+ path?: string;
5
+ };
6
+ export declare const VoiceOpsStatus: ({ className, path, ...options }: VoiceOpsStatusProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,3 +1,4 @@
1
+ export { VoiceOpsStatus } from './VoiceOpsStatus';
1
2
  export { useVoiceAppKitStatus } from './useVoiceAppKitStatus';
2
3
  export { useVoiceStream } from './useVoiceStream';
3
4
  export { useVoiceController } from './useVoiceController';