@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.
@@ -167,6 +167,226 @@ var useVoiceAppKitStatus = (path = "/app-kit/status", options = {}) => {
167
167
  refresh: store.refresh
168
168
  };
169
169
  };
170
+
171
+ // src/client/opsStatusWidget.ts
172
+ var DEFAULT_TITLE = "Voice Ops Status";
173
+ var DEFAULT_DESCRIPTION = "Certified workflow, provider, and handoff readiness from the AbsoluteJS voice app kit.";
174
+ var SURFACE_LABELS = {
175
+ handoffs: "Handoffs",
176
+ providers: "Providers",
177
+ quality: "Quality",
178
+ sessions: "Sessions",
179
+ workflows: "Workflows"
180
+ };
181
+ var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
182
+ var readNumber = (value, key) => value && typeof value === "object" && (key in value) ? Number(value[key] ?? 0) : 0;
183
+ var surfaceDetail = (surface) => {
184
+ const total = readNumber(surface, "total");
185
+ const failed = readNumber(surface, "failed");
186
+ const degraded = readNumber(surface, "degraded");
187
+ const source = surface && typeof surface === "object" && "source" in surface && typeof surface.source === "string" ? ` from ${surface.source}` : "";
188
+ if (degraded > 0) {
189
+ return `${degraded} degraded of ${total}`;
190
+ }
191
+ if (failed > 0) {
192
+ return `${failed} failing of ${total}${source}`;
193
+ }
194
+ return total > 0 ? `${total} passing${source}` : `No failures${source}`;
195
+ };
196
+ var getVoiceOpsStatusLabel = (report, error) => {
197
+ if (error) {
198
+ return "Unavailable";
199
+ }
200
+ if (!report) {
201
+ return "Checking";
202
+ }
203
+ return report.status === "pass" ? "Passing" : "Needs attention";
204
+ };
205
+ var createVoiceOpsStatusViewModel = (snapshot, options = {}) => {
206
+ const report = snapshot.report;
207
+ const surfaces = Object.entries(report?.surfaces ?? {}).map(([id, surface]) => {
208
+ const failed = readNumber(surface, "failed") || readNumber(surface, "degraded");
209
+ const total = readNumber(surface, "total");
210
+ const status = surface && typeof surface === "object" && "status" in surface ? surface.status ?? "pass" : "pass";
211
+ return {
212
+ detail: surfaceDetail(surface),
213
+ failed,
214
+ id,
215
+ label: SURFACE_LABELS[id] ?? id,
216
+ status,
217
+ total
218
+ };
219
+ });
220
+ return {
221
+ description: options.description ?? DEFAULT_DESCRIPTION,
222
+ error: snapshot.error,
223
+ isLoading: snapshot.isLoading,
224
+ label: getVoiceOpsStatusLabel(report, snapshot.error),
225
+ links: options.includeLinks === false ? [] : report?.links ?? [],
226
+ passed: report?.passed ?? 0,
227
+ status: snapshot.error ? "error" : report ? report.status : snapshot.isLoading ? "loading" : "loading",
228
+ surfaces,
229
+ title: options.title ?? DEFAULT_TITLE,
230
+ total: report?.total ?? 0,
231
+ updatedAt: snapshot.updatedAt
232
+ };
233
+ };
234
+ var renderVoiceOpsStatusHTML = (snapshot, options = {}) => {
235
+ const model = createVoiceOpsStatusViewModel(snapshot, options);
236
+ const surfaces = model.surfaces.length ? model.surfaces.map((surface) => `<li class="absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${escapeHtml(surface.status)}">
237
+ <span>${escapeHtml(surface.label)}</span>
238
+ <strong>${escapeHtml(surface.detail)}</strong>
239
+ </li>`).join("") : '<li class="absolute-voice-ops-status__surface"><span>Status</span><strong>Waiting for first check</strong></li>';
240
+ const links = model.links.length ? `<nav class="absolute-voice-ops-status__links">${model.links.slice(0, 4).map((link) => `<a href="${escapeHtml(link.href)}">${escapeHtml(link.label)}</a>`).join("")}</nav>` : "";
241
+ return `<section class="absolute-voice-ops-status absolute-voice-ops-status--${escapeHtml(model.status)}">
242
+ <header class="absolute-voice-ops-status__header">
243
+ <span class="absolute-voice-ops-status__eyebrow">${escapeHtml(model.title)}</span>
244
+ <strong class="absolute-voice-ops-status__label">${escapeHtml(model.label)}</strong>
245
+ </header>
246
+ <p class="absolute-voice-ops-status__description">${escapeHtml(model.description)}</p>
247
+ <div class="absolute-voice-ops-status__summary">
248
+ <span>${model.passed} passing</span>
249
+ <span>${Math.max(model.total - model.passed, 0)} failing</span>
250
+ <span>${model.total} checks</span>
251
+ </div>
252
+ <ul class="absolute-voice-ops-status__surfaces">${surfaces}</ul>
253
+ ${model.error ? `<p class="absolute-voice-ops-status__error">${escapeHtml(model.error)}</p>` : ""}
254
+ ${links}
255
+ </section>`;
256
+ };
257
+ var getVoiceOpsStatusCSS = () => `.absolute-voice-ops-status{border:1px solid #d8d2c4;border-radius:20px;background:#fffaf0;color:#16130d;padding:18px;box-shadow:0 18px 40px rgba(47,37,18,.12);font-family:inherit}.absolute-voice-ops-status--fail,.absolute-voice-ops-status--error{border-color:#f2a7a7;background:#fff5f3}.absolute-voice-ops-status__header{align-items:start;display:flex;gap:12px;justify-content:space-between}.absolute-voice-ops-status__eyebrow{color:#73664f;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.absolute-voice-ops-status__label{font-size:28px;line-height:1}.absolute-voice-ops-status__description{color:#514733;margin:12px 0 0}.absolute-voice-ops-status__summary,.absolute-voice-ops-status__links{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.absolute-voice-ops-status__summary span,.absolute-voice-ops-status__links a{border:1px solid #e6ddca;border-radius:999px;color:inherit;padding:6px 10px;text-decoration:none}.absolute-voice-ops-status__surfaces{display:grid;gap:8px;list-style:none;margin:16px 0 0;padding:0}.absolute-voice-ops-status__surface{align-items:center;background:#fff;border:1px solid #eee4d2;border-radius:14px;display:flex;gap:12px;justify-content:space-between;padding:10px 12px}.absolute-voice-ops-status__surface--fail{border-color:#f2a7a7}.absolute-voice-ops-status__surface span{color:#655944}.absolute-voice-ops-status__error{color:#9f1239;font-weight:700}`;
258
+ var mountVoiceOpsStatus = (element, path = "/app-kit/status", options = {}) => {
259
+ const store = createVoiceAppKitStatusStore(path, options);
260
+ const render = () => {
261
+ element.innerHTML = renderVoiceOpsStatusHTML(store.getSnapshot(), options);
262
+ };
263
+ const unsubscribe = store.subscribe(render);
264
+ render();
265
+ store.refresh().catch(() => {});
266
+ return {
267
+ close: () => {
268
+ unsubscribe();
269
+ store.close();
270
+ },
271
+ refresh: store.refresh
272
+ };
273
+ };
274
+ var defineVoiceOpsStatusElement = (tagName = "absolute-voice-ops-status") => {
275
+ if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
276
+ return;
277
+ }
278
+ customElements.define(tagName, class AbsoluteVoiceOpsStatusElement extends HTMLElement {
279
+ mounted;
280
+ connectedCallback() {
281
+ const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
282
+ this.mounted = mountVoiceOpsStatus(this, this.getAttribute("path") ?? "/app-kit/status", {
283
+ description: this.getAttribute("description") ?? undefined,
284
+ includeLinks: this.getAttribute("include-links") !== "false",
285
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
286
+ title: this.getAttribute("title") ?? undefined
287
+ });
288
+ }
289
+ disconnectedCallback() {
290
+ this.mounted?.close();
291
+ this.mounted = undefined;
292
+ }
293
+ });
294
+ };
295
+
296
+ // src/react/VoiceOpsStatus.tsx
297
+ import { jsxDEV } from "react/jsx-dev-runtime";
298
+ var VoiceOpsStatus = ({
299
+ className,
300
+ path = "/app-kit/status",
301
+ ...options
302
+ }) => {
303
+ const snapshot = useVoiceAppKitStatus(path, options);
304
+ const model = createVoiceOpsStatusViewModel(snapshot, options);
305
+ return /* @__PURE__ */ jsxDEV("section", {
306
+ className: [
307
+ "absolute-voice-ops-status",
308
+ `absolute-voice-ops-status--${model.status}`,
309
+ className
310
+ ].filter(Boolean).join(" "),
311
+ children: [
312
+ /* @__PURE__ */ jsxDEV("header", {
313
+ className: "absolute-voice-ops-status__header",
314
+ children: [
315
+ /* @__PURE__ */ jsxDEV("span", {
316
+ className: "absolute-voice-ops-status__eyebrow",
317
+ children: model.title
318
+ }, undefined, false, undefined, this),
319
+ /* @__PURE__ */ jsxDEV("strong", {
320
+ className: "absolute-voice-ops-status__label",
321
+ children: model.label
322
+ }, undefined, false, undefined, this)
323
+ ]
324
+ }, undefined, true, undefined, this),
325
+ /* @__PURE__ */ jsxDEV("p", {
326
+ className: "absolute-voice-ops-status__description",
327
+ children: model.description
328
+ }, undefined, false, undefined, this),
329
+ /* @__PURE__ */ jsxDEV("div", {
330
+ className: "absolute-voice-ops-status__summary",
331
+ children: [
332
+ /* @__PURE__ */ jsxDEV("span", {
333
+ children: [
334
+ model.passed,
335
+ " passing"
336
+ ]
337
+ }, undefined, true, undefined, this),
338
+ /* @__PURE__ */ jsxDEV("span", {
339
+ children: [
340
+ Math.max(model.total - model.passed, 0),
341
+ " failing"
342
+ ]
343
+ }, undefined, true, undefined, this),
344
+ /* @__PURE__ */ jsxDEV("span", {
345
+ children: [
346
+ model.total,
347
+ " checks"
348
+ ]
349
+ }, undefined, true, undefined, this)
350
+ ]
351
+ }, undefined, true, undefined, this),
352
+ /* @__PURE__ */ jsxDEV("ul", {
353
+ className: "absolute-voice-ops-status__surfaces",
354
+ children: model.surfaces.length > 0 ? model.surfaces.map((surface) => /* @__PURE__ */ jsxDEV("li", {
355
+ className: `absolute-voice-ops-status__surface absolute-voice-ops-status__surface--${surface.status}`,
356
+ children: [
357
+ /* @__PURE__ */ jsxDEV("span", {
358
+ children: surface.label
359
+ }, undefined, false, undefined, this),
360
+ /* @__PURE__ */ jsxDEV("strong", {
361
+ children: surface.detail
362
+ }, undefined, false, undefined, this)
363
+ ]
364
+ }, surface.id, true, undefined, this)) : /* @__PURE__ */ jsxDEV("li", {
365
+ className: "absolute-voice-ops-status__surface",
366
+ children: [
367
+ /* @__PURE__ */ jsxDEV("span", {
368
+ children: "Status"
369
+ }, undefined, false, undefined, this),
370
+ /* @__PURE__ */ jsxDEV("strong", {
371
+ children: "Waiting for first check"
372
+ }, undefined, false, undefined, this)
373
+ ]
374
+ }, undefined, true, undefined, this)
375
+ }, undefined, false, undefined, this),
376
+ model.error ? /* @__PURE__ */ jsxDEV("p", {
377
+ className: "absolute-voice-ops-status__error",
378
+ children: model.error
379
+ }, undefined, false, undefined, this) : null,
380
+ model.links.length > 0 ? /* @__PURE__ */ jsxDEV("nav", {
381
+ className: "absolute-voice-ops-status__links",
382
+ children: model.links.slice(0, 4).map((link) => /* @__PURE__ */ jsxDEV("a", {
383
+ href: link.href,
384
+ children: link.label
385
+ }, `${link.label}:${link.href}`, false, undefined, this))
386
+ }, undefined, false, undefined, this) : null
387
+ ]
388
+ }, undefined, true, undefined, this);
389
+ };
170
390
  // src/react/useVoiceStream.tsx
171
391
  import { useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
172
392
 
@@ -1580,5 +1800,6 @@ export {
1580
1800
  useVoiceStream,
1581
1801
  useVoiceProviderStatus,
1582
1802
  useVoiceController,
1583
- useVoiceAppKitStatus
1803
+ useVoiceAppKitStatus,
1804
+ VoiceOpsStatus
1584
1805
  };
@@ -0,0 +1,9 @@
1
+ import { type VoiceOpsStatusWidgetOptions } from '../client/opsStatusWidget';
2
+ export declare const createVoiceOpsStatus: (path?: string, options?: VoiceOpsStatusWidgetOptions) => {
3
+ close: () => void;
4
+ getHTML: () => string;
5
+ getSnapshot: () => import("../client").VoiceAppKitStatusSnapshot;
6
+ getViewModel: () => import("../client").VoiceOpsStatusViewModel;
7
+ refresh: () => Promise<import("..").VoiceAppKitStatusReport | undefined>;
8
+ subscribe: (listener: () => void) => () => void;
9
+ };
@@ -1,4 +1,5 @@
1
1
  export { createVoiceAppKitStatus } from './createVoiceAppKitStatus';
2
+ export { createVoiceOpsStatus } from './createVoiceOpsStatus';
2
3
  export { createVoiceStream } from './createVoiceStream';
3
4
  export { createVoiceProviderStatus } from './createVoiceProviderStatus';
4
5
  export { createVoiceWorkflowStatus } from './createVoiceWorkflowStatus';
@@ -150,6 +150,143 @@ var createVoiceAppKitStatusStore = (path = "/app-kit/status", options = {}) => {
150
150
 
151
151
  // src/svelte/createVoiceAppKitStatus.ts
152
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
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
+ };
153
290
  // src/client/actions.ts
154
291
  var normalizeErrorMessage = (value) => {
155
292
  if (typeof value === "string" && value.trim()) {
@@ -1461,6 +1598,7 @@ export {
1461
1598
  createVoiceWorkflowStatus,
1462
1599
  createVoiceStream2 as createVoiceStream,
1463
1600
  createVoiceProviderStatus,
1601
+ createVoiceOpsStatus,
1464
1602
  createVoiceController,
1465
1603
  createVoiceAppKitStatus
1466
1604
  };
@@ -0,0 +1,30 @@
1
+ export declare const VoiceOpsStatus: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
2
+ description: StringConstructor;
3
+ includeLinks: {
4
+ default: boolean;
5
+ type: BooleanConstructor;
6
+ };
7
+ intervalMs: NumberConstructor;
8
+ path: {
9
+ default: string;
10
+ type: StringConstructor;
11
+ };
12
+ title: StringConstructor;
13
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
14
+ [key: string]: any;
15
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
16
+ description: StringConstructor;
17
+ includeLinks: {
18
+ default: boolean;
19
+ type: BooleanConstructor;
20
+ };
21
+ intervalMs: NumberConstructor;
22
+ path: {
23
+ default: string;
24
+ type: StringConstructor;
25
+ };
26
+ title: StringConstructor;
27
+ }>> & Readonly<{}>, {
28
+ path: string;
29
+ includeLinks: boolean;
30
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -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';
package/dist/vue/index.js CHANGED
@@ -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/vue/useVoiceAppKitStatus.ts
73
- import { onUnmounted, ref, shallowRef } from "vue";
72
+ // src/vue/VoiceOpsStatus.ts
73
+ import { defineComponent, h } from "vue";
74
74
 
75
75
  // src/client/appKitStatus.ts
76
76
  var fetchVoiceAppKitStatus = async (path = "/app-kit/status", options = {}) => {
@@ -151,7 +151,133 @@ 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
+
154
279
  // src/vue/useVoiceAppKitStatus.ts
280
+ import { onUnmounted, ref, shallowRef } from "vue";
155
281
  var useVoiceAppKitStatus = (path = "/app-kit/status", options = {}) => {
156
282
  const store = createVoiceAppKitStatusStore(path, options);
157
283
  const error = ref(null);
@@ -182,6 +308,72 @@ var useVoiceAppKitStatus = (path = "/app-kit/status", options = {}) => {
182
308
  updatedAt
183
309
  };
184
310
  };
311
+
312
+ // src/vue/VoiceOpsStatus.ts
313
+ var VoiceOpsStatus = defineComponent({
314
+ name: "VoiceOpsStatus",
315
+ props: {
316
+ description: String,
317
+ includeLinks: {
318
+ default: true,
319
+ type: Boolean
320
+ },
321
+ intervalMs: Number,
322
+ path: {
323
+ default: "/app-kit/status",
324
+ type: String
325
+ },
326
+ title: String
327
+ },
328
+ setup(props) {
329
+ const options = {
330
+ description: props.description,
331
+ includeLinks: props.includeLinks,
332
+ intervalMs: props.intervalMs,
333
+ title: props.title
334
+ };
335
+ const status = useVoiceAppKitStatus(props.path, options);
336
+ return () => {
337
+ const model = createVoiceOpsStatusViewModel({
338
+ error: status.error.value,
339
+ isLoading: status.isLoading.value,
340
+ report: status.report.value,
341
+ updatedAt: status.updatedAt.value
342
+ }, options);
343
+ return h("section", {
344
+ class: [
345
+ "absolute-voice-ops-status",
346
+ `absolute-voice-ops-status--${model.status}`
347
+ ]
348
+ }, [
349
+ h("header", { class: "absolute-voice-ops-status__header" }, [
350
+ h("span", { class: "absolute-voice-ops-status__eyebrow" }, model.title),
351
+ h("strong", { class: "absolute-voice-ops-status__label" }, model.label)
352
+ ]),
353
+ h("p", { class: "absolute-voice-ops-status__description" }, model.description),
354
+ h("div", { class: "absolute-voice-ops-status__summary" }, [
355
+ h("span", `${model.passed} passing`),
356
+ h("span", `${Math.max(model.total - model.passed, 0)} failing`),
357
+ h("span", `${model.total} checks`)
358
+ ]),
359
+ h("ul", { class: "absolute-voice-ops-status__surfaces" }, model.surfaces.length > 0 ? model.surfaces.map((surface) => h("li", {
360
+ class: [
361
+ "absolute-voice-ops-status__surface",
362
+ `absolute-voice-ops-status__surface--${surface.status}`
363
+ ],
364
+ key: surface.id
365
+ }, [h("span", surface.label), h("strong", surface.detail)])) : [
366
+ h("li", { class: "absolute-voice-ops-status__surface" }, [
367
+ h("span", "Status"),
368
+ h("strong", "Waiting for first check")
369
+ ])
370
+ ]),
371
+ model.error ? h("p", { class: "absolute-voice-ops-status__error" }, model.error) : null,
372
+ model.links.length > 0 ? h("nav", { class: "absolute-voice-ops-status__links" }, model.links.slice(0, 4).map((link) => h("a", { href: link.href, key: link.href }, link.label))) : null
373
+ ]);
374
+ };
375
+ }
376
+ });
185
377
  // src/vue/useVoiceStream.ts
186
378
  import { onUnmounted as onUnmounted2, ref as ref2, shallowRef as shallowRef2 } from "vue";
187
379
 
@@ -1659,5 +1851,6 @@ export {
1659
1851
  useVoiceStream,
1660
1852
  useVoiceProviderStatus,
1661
1853
  useVoiceController,
1662
- useVoiceAppKitStatus
1854
+ useVoiceAppKitStatus,
1855
+ VoiceOpsStatus
1663
1856
  };