@absolutejs/voice 0.0.22-beta.50 → 0.0.22-beta.52

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,248 @@ 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
+
258
+ // src/angular/voice-ops-status.component.ts
259
+ var _dec = [
260
+ Component({
261
+ selector: "absolute-voice-ops-status",
262
+ standalone: true,
263
+ template: `
264
+ <section
265
+ class="absolute-voice-ops-status"
266
+ [class.absolute-voice-ops-status--pass]="model().status === 'pass'"
267
+ [class.absolute-voice-ops-status--fail]="model().status === 'fail'"
268
+ [class.absolute-voice-ops-status--loading]="model().status === 'loading'"
269
+ [class.absolute-voice-ops-status--error]="model().status === 'error'"
270
+ >
271
+ <header class="absolute-voice-ops-status__header">
272
+ <span class="absolute-voice-ops-status__eyebrow">{{
273
+ model().title
274
+ }}</span>
275
+ <strong class="absolute-voice-ops-status__label">{{
276
+ model().label
277
+ }}</strong>
278
+ </header>
279
+ <p class="absolute-voice-ops-status__description">
280
+ {{ model().description }}
281
+ </p>
282
+ <div class="absolute-voice-ops-status__summary">
283
+ <span>{{ model().passed }} passing</span>
284
+ <span>{{ model().total - model().passed }} failing</span>
285
+ <span>{{ model().total }} checks</span>
286
+ </div>
287
+ <ul class="absolute-voice-ops-status__surfaces">
288
+ @if (model().surfaces.length > 0) {
289
+ @for (surface of model().surfaces; track surface.id) {
290
+ <li
291
+ class="absolute-voice-ops-status__surface"
292
+ [class.absolute-voice-ops-status__surface--pass]="
293
+ surface.status === 'pass'
294
+ "
295
+ [class.absolute-voice-ops-status__surface--fail]="
296
+ surface.status === 'fail'
297
+ "
298
+ >
299
+ <span>{{ surface.label }}</span>
300
+ <strong>{{ surface.detail }}</strong>
301
+ </li>
302
+ }
303
+ } @else {
304
+ <li class="absolute-voice-ops-status__surface">
305
+ <span>Status</span>
306
+ <strong>Waiting for first check</strong>
307
+ </li>
308
+ }
309
+ </ul>
310
+ @if (model().error) {
311
+ <p class="absolute-voice-ops-status__error">{{ model().error }}</p>
312
+ }
313
+ @if (model().links.length > 0) {
314
+ <nav class="absolute-voice-ops-status__links">
315
+ @for (link of model().links.slice(0, 4); track link.href) {
316
+ <a [href]="link.href">{{ link.label }}</a>
317
+ }
318
+ </nav>
319
+ }
320
+ </section>
321
+ `
322
+ })
323
+ ];
324
+ var _dec2 = [
325
+ Input()
326
+ ];
327
+ var _dec3 = [
328
+ Input()
329
+ ];
330
+ var _dec4 = [
331
+ Input()
332
+ ];
333
+ var _dec5 = [
334
+ Input()
335
+ ];
336
+ var _dec6 = [
337
+ Input()
338
+ ];
339
+ var _init = __decoratorStart(undefined);
340
+
341
+ class VoiceOpsStatusComponent {
342
+ constructor() {
343
+ this.description = __runInitializers(_init, 8, this);
344
+ __runInitializers(_init, 11, this);
345
+ this.includeLinks = __runInitializers(_init, 12, this, true);
346
+ __runInitializers(_init, 15, this);
347
+ this.intervalMs = __runInitializers(_init, 16, this);
348
+ __runInitializers(_init, 19, this);
349
+ this.path = __runInitializers(_init, 20, this, "/app-kit/status");
350
+ __runInitializers(_init, 23, this);
351
+ this.title = __runInitializers(_init, 24, this);
352
+ __runInitializers(_init, 27, this);
353
+ }
354
+ cleanup = () => {};
355
+ store;
356
+ model = signal(createVoiceOpsStatusViewModel({
357
+ error: null,
358
+ isLoading: true
359
+ }));
360
+ ngOnInit() {
361
+ const options = this.options();
362
+ this.store = createVoiceAppKitStatusStore(this.path, options);
363
+ const sync = () => {
364
+ this.model.set(createVoiceOpsStatusViewModel(this.store.getSnapshot(), options));
365
+ };
366
+ this.cleanup = this.store.subscribe(sync);
367
+ sync();
368
+ if (typeof window !== "undefined") {
369
+ this.store.refresh().catch(() => {});
370
+ }
371
+ }
372
+ ngOnDestroy() {
373
+ this.cleanup();
374
+ this.store?.close();
375
+ }
376
+ options() {
377
+ return {
378
+ description: this.description,
379
+ includeLinks: this.includeLinks,
380
+ intervalMs: this.intervalMs,
381
+ title: this.title
382
+ };
383
+ }
384
+ }
385
+ __decorateElement(_init, 5, "description", _dec2, VoiceOpsStatusComponent);
386
+ __decorateElement(_init, 5, "includeLinks", _dec3, VoiceOpsStatusComponent);
387
+ __decorateElement(_init, 5, "intervalMs", _dec4, VoiceOpsStatusComponent);
388
+ __decorateElement(_init, 5, "path", _dec5, VoiceOpsStatusComponent);
389
+ __decorateElement(_init, 5, "title", _dec6, VoiceOpsStatusComponent);
390
+ VoiceOpsStatusComponent = __decorateElement(_init, 0, "VoiceOpsStatusComponent", _dec, VoiceOpsStatusComponent);
391
+ __runInitializers(_init, 1, VoiceOpsStatusComponent);
392
+ __decoratorMetadata(_init, VoiceOpsStatusComponent);
393
+ let _VoiceOpsStatusComponent = VoiceOpsStatusComponent;
154
394
  // src/angular/voice-app-kit-status.service.ts
395
+ import { computed, Injectable, signal as signal2 } from "@angular/core";
155
396
  var _dec = [
156
397
  Injectable({ providedIn: "root" })
157
398
  ];
@@ -160,10 +401,10 @@ var _init = __decoratorStart(undefined);
160
401
  class VoiceAppKitStatusService {
161
402
  connect(path = "/app-kit/status", options = {}) {
162
403
  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);
404
+ const errorSignal = signal2(null);
405
+ const isLoadingSignal = signal2(false);
406
+ const reportSignal = signal2(undefined);
407
+ const updatedAtSignal = signal2(undefined);
167
408
  const sync = () => {
168
409
  const snapshot = store.getSnapshot();
169
410
  errorSignal.set(snapshot.error);
@@ -194,7 +435,7 @@ __runInitializers(_init, 1, VoiceAppKitStatusService);
194
435
  __decoratorMetadata(_init, VoiceAppKitStatusService);
195
436
  let _VoiceAppKitStatusService = VoiceAppKitStatusService;
196
437
  // src/angular/voice-stream.service.ts
197
- import { computed as computed2, Injectable as Injectable2, signal as signal2 } from "@angular/core";
438
+ import { computed as computed2, Injectable as Injectable2, signal as signal3 } from "@angular/core";
198
439
 
199
440
  // src/client/actions.ts
200
441
  var normalizeErrorMessage = (value) => {
@@ -718,15 +959,15 @@ var _init = __decoratorStart(undefined);
718
959
  class VoiceStreamService {
719
960
  connect(path, options = {}) {
720
961
  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([]);
962
+ const assistantAudioSignal = signal3([]);
963
+ const assistantTextsSignal = signal3([]);
964
+ const callSignal = signal3(null);
965
+ const errorSignal = signal3(null);
966
+ const isConnectedSignal = signal3(false);
967
+ const partialSignal = signal3("");
968
+ const sessionIdSignal = signal3(stream.sessionId);
969
+ const statusSignal = signal3(stream.status);
970
+ const turnsSignal = signal3([]);
730
971
  const sync = () => {
731
972
  assistantAudioSignal.set([...stream.assistantAudio]);
732
973
  assistantTextsSignal.set([...stream.assistantTexts]);
@@ -765,7 +1006,7 @@ __runInitializers(_init, 1, VoiceStreamService);
765
1006
  __decoratorMetadata(_init, VoiceStreamService);
766
1007
  let _VoiceStreamService = VoiceStreamService;
767
1008
  // src/angular/voice-controller.service.ts
768
- import { computed as computed3, Injectable as Injectable3, signal as signal3 } from "@angular/core";
1009
+ import { computed as computed3, Injectable as Injectable3, signal as signal4 } from "@angular/core";
769
1010
 
770
1011
  // src/client/htmx.ts
771
1012
  var DEFAULT_EVENT_NAME = "voice-refresh";
@@ -1406,16 +1647,16 @@ var _init = __decoratorStart(undefined);
1406
1647
  class VoiceControllerService {
1407
1648
  connect(path, options = {}) {
1408
1649
  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([]);
1650
+ const assistantAudioSignal = signal4([]);
1651
+ const assistantTextsSignal = signal4([]);
1652
+ const errorSignal = signal4(null);
1653
+ const isConnectedSignal = signal4(false);
1654
+ const isRecordingSignal = signal4(false);
1655
+ const partialSignal = signal4("");
1656
+ const recordingErrorSignal = signal4(null);
1657
+ const sessionIdSignal = signal4(controller.sessionId);
1658
+ const statusSignal = signal4(controller.status);
1659
+ const turnsSignal = signal4([]);
1419
1660
  const sync = () => {
1420
1661
  assistantAudioSignal.set([...controller.assistantAudio]);
1421
1662
  assistantTextsSignal.set([...controller.assistantTexts]);
@@ -1459,7 +1700,7 @@ __runInitializers(_init, 1, VoiceControllerService);
1459
1700
  __decoratorMetadata(_init, VoiceControllerService);
1460
1701
  let _VoiceControllerService = VoiceControllerService;
1461
1702
  // src/angular/voice-provider-status.service.ts
1462
- import { computed as computed4, Injectable as Injectable4, signal as signal4 } from "@angular/core";
1703
+ import { computed as computed4, Injectable as Injectable4, signal as signal5 } from "@angular/core";
1463
1704
 
1464
1705
  // src/client/providerStatus.ts
1465
1706
  var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
@@ -1550,10 +1791,10 @@ var _init = __decoratorStart(undefined);
1550
1791
  class VoiceProviderStatusService {
1551
1792
  connect(path = "/api/provider-status", options = {}) {
1552
1793
  const store = createVoiceProviderStatusStore(path, options);
1553
- const errorSignal = signal4(null);
1554
- const isLoadingSignal = signal4(false);
1555
- const providersSignal = signal4([]);
1556
- const updatedAtSignal = signal4(undefined);
1794
+ const errorSignal = signal5(null);
1795
+ const isLoadingSignal = signal5(false);
1796
+ const providersSignal = signal5([]);
1797
+ const updatedAtSignal = signal5(undefined);
1557
1798
  const sync = () => {
1558
1799
  const snapshot = store.getSnapshot();
1559
1800
  errorSignal.set(snapshot.error);
@@ -1582,7 +1823,7 @@ __runInitializers(_init, 1, VoiceProviderStatusService);
1582
1823
  __decoratorMetadata(_init, VoiceProviderStatusService);
1583
1824
  let _VoiceProviderStatusService = VoiceProviderStatusService;
1584
1825
  // src/angular/voice-workflow-status.service.ts
1585
- import { computed as computed5, Injectable as Injectable5, signal as signal5 } from "@angular/core";
1826
+ import { computed as computed5, Injectable as Injectable5, signal as signal6 } from "@angular/core";
1586
1827
 
1587
1828
  // src/client/workflowStatus.ts
1588
1829
  var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
@@ -1672,10 +1913,10 @@ var _init = __decoratorStart(undefined);
1672
1913
  class VoiceWorkflowStatusService {
1673
1914
  connect(path = "/evals/scenarios/json", options = {}) {
1674
1915
  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);
1916
+ const errorSignal = signal6(null);
1917
+ const isLoadingSignal = signal6(false);
1918
+ const reportSignal = signal6(undefined);
1919
+ const updatedAtSignal = signal6(undefined);
1679
1920
  const sync = () => {
1680
1921
  const snapshot = store.getSnapshot();
1681
1922
  errorSignal.set(snapshot.error);
@@ -1709,6 +1950,7 @@ export {
1709
1950
  VoiceWorkflowStatusService,
1710
1951
  VoiceStreamService,
1711
1952
  VoiceProviderStatusService,
1953
+ VoiceOpsStatusComponent,
1712
1954
  VoiceControllerService,
1713
1955
  VoiceAppKitStatusService
1714
1956
  };
@@ -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
+ }
package/dist/appKit.d.ts CHANGED
@@ -37,7 +37,15 @@ export type VoiceAppKitRoutesOptions<TProvider extends string = string> = {
37
37
  };
38
38
  export type VoiceAppKitStatus = 'pass' | 'fail';
39
39
  export type VoiceAppKitStatusOptions = {
40
+ include?: {
41
+ handoffs?: boolean;
42
+ providers?: boolean;
43
+ quality?: boolean;
44
+ sessions?: boolean;
45
+ workflows?: boolean;
46
+ };
40
47
  path?: string;
48
+ preferFixtureWorkflows?: boolean;
41
49
  };
42
50
  export type VoiceAppKitStatusReport = {
43
51
  checkedAt: number;
@@ -66,6 +74,7 @@ export type VoiceAppKitStatusReport = {
66
74
  };
67
75
  workflows?: {
68
76
  failed: number;
77
+ source: 'fixtures' | 'live';
69
78
  status: VoiceAppKitStatus;
70
79
  total: number;
71
80
  };
@@ -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, 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,109 @@ 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
+ };
1704
1807
  // src/client/providerStatus.ts
1705
1808
  var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
1706
1809
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -1859,6 +1962,10 @@ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options =
1859
1962
  };
1860
1963
  };
1861
1964
  export {
1965
+ renderVoiceOpsStatusHTML,
1966
+ mountVoiceOpsStatus,
1967
+ getVoiceOpsStatusLabel,
1968
+ getVoiceOpsStatusCSS,
1862
1969
  fetchVoiceWorkflowStatus,
1863
1970
  fetchVoiceProviderStatus,
1864
1971
  fetchVoiceAppKitStatus,
@@ -1866,6 +1973,7 @@ export {
1866
1973
  createVoiceWorkflowStatusStore,
1867
1974
  createVoiceStream,
1868
1975
  createVoiceProviderStatusStore,
1976
+ createVoiceOpsStatusViewModel,
1869
1977
  createVoiceDuplexController,
1870
1978
  createVoiceController,
1871
1979
  createVoiceConnection,
@@ -0,0 +1,39 @@
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
+ };