@edvak99/voice-receptionist-widget 1.1.3 → 1.1.4

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.
Files changed (126) hide show
  1. package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-DT9UKvQO.mjs +307 -0
  2. package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-DT9UKvQO.mjs.map +1 -0
  3. package/fesm2022/{edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-Dn0GrZ6_.mjs → edvak99-voice-receptionist-widget-ai-receptionist-api.service-48cifVPF.mjs} +1275 -561
  4. package/fesm2022/edvak99-voice-receptionist-widget-ai-receptionist-api.service-48cifVPF.mjs.map +1 -0
  5. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-C5inEHJw.mjs +278 -0
  6. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-C5inEHJw.mjs.map +1 -0
  7. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-sYhM20CE.mjs +418 -0
  8. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-sYhM20CE.mjs.map +1 -0
  9. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DpwajEOE.mjs +194 -0
  10. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DpwajEOE.mjs.map +1 -0
  11. package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-B8_qp7Fe.mjs → edvak99-voice-receptionist-widget-call-recording-player.component-CgkRcFOl.mjs} +3 -2
  12. package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-B8_qp7Fe.mjs.map → edvak99-voice-receptionist-widget-call-recording-player.component-CgkRcFOl.mjs.map} +1 -1
  13. package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-Cj2XTeK4.mjs → edvak99-voice-receptionist-widget-call-review.service-CIMcjGKX.mjs} +2 -2
  14. package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-Cj2XTeK4.mjs.map → edvak99-voice-receptionist-widget-call-review.service-CIMcjGKX.mjs.map} +1 -1
  15. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-Deh3jPgo.mjs +21 -0
  16. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-Deh3jPgo.mjs.map +1 -0
  17. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-CxbO0eDS.mjs +440 -0
  18. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-CxbO0eDS.mjs.map +1 -0
  19. package/fesm2022/{edvak99-voice-receptionist-widget-eds-elapsed.pipe-BgLsnq6A.mjs → edvak99-voice-receptionist-widget-dashboard-socket.service-BX91r74O.mjs} +5 -21
  20. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-socket.service-BX91r74O.mjs.map +1 -0
  21. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-CWRiJGfv.mjs +38 -0
  22. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-CWRiJGfv.mjs.map +1 -0
  23. package/fesm2022/edvak99-voice-receptionist-widget-eds-button.component-Bg5T5BN8.mjs +27 -0
  24. package/fesm2022/edvak99-voice-receptionist-widget-eds-button.component-Bg5T5BN8.mjs.map +1 -0
  25. package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-BFCiJOT3.mjs +22 -0
  26. package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-BFCiJOT3.mjs.map +1 -0
  27. package/fesm2022/{edvak99-voice-receptionist-widget-live-calls-list.service-0kGWO6sh.mjs → edvak99-voice-receptionist-widget-eds-pulse-dot.component-BPY6xN7C.mjs} +15 -5
  28. package/fesm2022/edvak99-voice-receptionist-widget-eds-pulse-dot.component-BPY6xN7C.mjs.map +1 -0
  29. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-BsBQIS_f.mjs +203 -0
  30. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-BsBQIS_f.mjs.map +1 -0
  31. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-CaSDMxpV.mjs +81 -0
  32. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-CaSDMxpV.mjs.map +1 -0
  33. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-Cy3ND-c-.mjs +1070 -0
  34. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-Cy3ND-c-.mjs.map +1 -0
  35. package/fesm2022/edvak99-voice-receptionist-widget-modal.component-COfi7biN.mjs +48 -0
  36. package/fesm2022/edvak99-voice-receptionist-widget-modal.component-COfi7biN.mjs.map +1 -0
  37. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-Bdh4QfFe.mjs +127 -0
  38. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-Bdh4QfFe.mjs.map +1 -0
  39. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-fHVzpiVk.mjs +268 -0
  40. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-fHVzpiVk.mjs.map +1 -0
  41. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-Bok64X2q.mjs +968 -0
  42. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-Bok64X2q.mjs.map +1 -0
  43. package/fesm2022/{edvak99-voice-receptionist-widget-tasks-page.component-DXxjMVUv.mjs → edvak99-voice-receptionist-widget-tasks-page.component-Bb9lPcYi.mjs} +56 -5
  44. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-Bb9lPcYi.mjs.map +1 -0
  45. package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-C1Z6yC0q.mjs → edvak99-voice-receptionist-widget-wizard-page.component-BZVlWLWh.mjs} +4 -2
  46. package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-C1Z6yC0q.mjs.map → edvak99-voice-receptionist-widget-wizard-page.component-BZVlWLWh.mjs.map} +1 -1
  47. package/fesm2022/edvak99-voice-receptionist-widget.mjs +1 -1
  48. package/lib/components/call-conversation-panel/call-conversation-panel.component.d.ts +9 -6
  49. package/lib/components/call-tasks-panel/call-tasks-panel.component.d.ts +8 -8
  50. package/lib/components/knowledge-test-modal/knowledge-test-modal.component.d.ts +28 -0
  51. package/lib/components/live-transcript-panel/live-transcript-panel.component.d.ts +7 -4
  52. package/lib/components/outbound-dial-modal/outbound-dial-modal.component.d.ts +26 -0
  53. package/lib/layout/eds-module-nav.component.d.ts +0 -3
  54. package/lib/mocks/agent-activity.seed.d.ts +2 -0
  55. package/lib/models/agent-activity.types.d.ts +54 -0
  56. package/lib/models/ai-receptionist.types.d.ts +25 -0
  57. package/lib/models/api-dto.types.d.ts +18 -0
  58. package/lib/models/live-transcript.types.d.ts +2 -1
  59. package/lib/models/outbound-patient.types.d.ts +23 -0
  60. package/lib/models/outbound-purpose.types.d.ts +2 -0
  61. package/lib/models/staff-media.types.d.ts +36 -0
  62. package/lib/pages/activities-page/activities-page.component.d.ts +57 -0
  63. package/lib/pages/analytics-page/analytics-page.component.d.ts +38 -14
  64. package/lib/pages/call-history-detail-page/call-history-detail-page.component.d.ts +2 -3
  65. package/lib/pages/call-history-page/call-history-page.component.d.ts +9 -6
  66. package/lib/pages/dashboard-page/dashboard-page.component.d.ts +12 -8
  67. package/lib/pages/live-calls-page/live-calls-page.component.d.ts +14 -24
  68. package/lib/pages/live-monitor-page/live-monitor-page.component.d.ts +34 -1
  69. package/lib/pages/settings-page/settings-fields.config.d.ts +16 -0
  70. package/lib/pages/settings-page/settings-page.component.d.ts +48 -14
  71. package/lib/services/ai-receptionist-api.mock.d.ts +34 -0
  72. package/lib/services/ai-receptionist-api.service.d.ts +56 -1
  73. package/lib/services/live-transcript.service.d.ts +1 -0
  74. package/lib/services/staff-media.service.d.ts +52 -0
  75. package/lib/ui/eds-avatar.component.d.ts +5 -2
  76. package/lib/ui/eds-button.component.d.ts +1 -1
  77. package/lib/utils/call-history-display.util.d.ts +21 -0
  78. package/lib/utils/outbound-purpose.util.d.ts +19 -0
  79. package/lib/utils/transcript-timing.util.d.ts +1 -1
  80. package/lib/utils/welcome-message.util.d.ts +1 -1
  81. package/package.json +2 -1
  82. package/styles/eds.scss +95 -66
  83. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-Dsujn5az.mjs +0 -113
  84. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-Dsujn5az.mjs.map +0 -1
  85. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-xoLlHj8K.mjs +0 -67
  86. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-xoLlHj8K.mjs.map +0 -1
  87. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-2K9W77As.mjs +0 -316
  88. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-2K9W77As.mjs.map +0 -1
  89. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-B5AnEjoh.mjs +0 -174
  90. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-B5AnEjoh.mjs.map +0 -1
  91. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-BgsBCKgZ.mjs +0 -70
  92. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-BgsBCKgZ.mjs.map +0 -1
  93. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-BNdnbAVM.mjs +0 -248
  94. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-BNdnbAVM.mjs.map +0 -1
  95. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-CbphhP6o.mjs +0 -27
  96. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-CbphhP6o.mjs.map +0 -1
  97. package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-BgLsnq6A.mjs.map +0 -1
  98. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-Dn0GrZ6_.mjs.map +0 -1
  99. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-BngGx_Lc.mjs +0 -609
  100. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-BngGx_Lc.mjs.map +0 -1
  101. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-list.service-0kGWO6sh.mjs.map +0 -1
  102. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-DfLmK89M.mjs +0 -115
  103. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-DfLmK89M.mjs.map +0 -1
  104. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-DNpLFqBw.mjs +0 -574
  105. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-DNpLFqBw.mjs.map +0 -1
  106. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-BQECIuJu.mjs +0 -54
  107. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-BQECIuJu.mjs.map +0 -1
  108. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-CYSZ832p.mjs +0 -265
  109. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-CYSZ832p.mjs.map +0 -1
  110. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-B9nv6wtB.mjs +0 -509
  111. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-B9nv6wtB.mjs.map +0 -1
  112. package/fesm2022/edvak99-voice-receptionist-widget-task.util-DdtI0CR3.mjs +0 -52
  113. package/fesm2022/edvak99-voice-receptionist-widget-task.util-DdtI0CR3.mjs.map +0 -1
  114. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-DXxjMVUv.mjs.map +0 -1
  115. package/lib/knowledge/components/faq-empty-state/faq-empty-state.component.d.ts +0 -22
  116. package/lib/knowledge/components/faq-form/faq-form.component.d.ts +0 -27
  117. package/lib/knowledge/components/faq-search-tester/faq-search-tester.component.d.ts +0 -17
  118. package/lib/knowledge/mocks/practice-faq.seed.d.ts +0 -2
  119. package/lib/knowledge/models/practice-faq.model.d.ts +0 -50
  120. package/lib/knowledge/pages/knowledge-hub.page.d.ts +0 -54
  121. package/lib/knowledge/services/practice-faq-api.mock.d.ts +0 -16
  122. package/lib/knowledge/services/practice-faq-api.service.d.ts +0 -26
  123. package/lib/knowledge/utils/faq.util.d.ts +0 -3
  124. package/lib/layout/eds-topbar.component.d.ts +0 -29
  125. package/lib/pages/audit-page/audit-page.component.d.ts +0 -21
  126. package/lib/ui/eds-waveform.component.d.ts +0 -8
@@ -1,61 +1,8 @@
1
- import * as i0 from '@angular/core';
2
- import { InjectionToken, makeEnvironmentProviders, signal, Injectable, inject, ChangeDetectionStrategy, Component, input, computed, EventEmitter, HostListener, Output, Input } from '@angular/core';
3
- import { RouterLink, RouterLinkActive, Router, NavigationEnd, RouterOutlet } from '@angular/router';
4
- import { of, delay, throwError, map, catchError, forkJoin, switchMap, tap, finalize } from 'rxjs';
5
1
  import { HttpClient, HttpParams } from '@angular/common/http';
6
- import { filter } from 'rxjs/operators';
7
-
8
- const VOICE_RECEPTIONIST_CONFIG = new InjectionToken('VOICE_RECEPTIONIST_CONFIG');
9
- const VOICE_RECEPTIONIST_SESSION = new InjectionToken('VOICE_RECEPTIONIST_SESSION', { factory: () => null });
10
-
11
- function provideVoiceReceptionist(options) {
12
- const { session = null, ...config } = options;
13
- return makeEnvironmentProviders([
14
- { provide: VOICE_RECEPTIONIST_CONFIG, useValue: config },
15
- { provide: VOICE_RECEPTIONIST_SESSION, useValue: session },
16
- ]);
17
- }
18
-
19
- class ToastService {
20
- constructor() {
21
- this.nextId = 1;
22
- this.timers = new Map();
23
- this._toasts = signal([]);
24
- this.toasts = this._toasts.asReadonly();
25
- }
26
- show(text, variant = 'info', durationMs = 3500) {
27
- const id = this.nextId++;
28
- this._toasts.update((list) => [...list, { id, text, variant }]);
29
- const timer = setTimeout(() => this.dismiss(id), durationMs);
30
- this.timers.set(id, timer);
31
- }
32
- dismiss(id) {
33
- const timer = this.timers.get(id);
34
- if (timer) {
35
- clearTimeout(timer);
36
- this.timers.delete(id);
37
- }
38
- this._toasts.update((list) => list.filter((t) => t.id !== id));
39
- }
40
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
41
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ToastService, providedIn: 'root' }); }
42
- }
43
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ToastService, decorators: [{
44
- type: Injectable,
45
- args: [{ providedIn: 'root' }]
46
- }] });
47
-
48
- class ToastContainerComponent {
49
- constructor() {
50
- this.toastService = inject(ToastService);
51
- }
52
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ToastContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
53
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: ToastContainerComponent, isStandalone: true, selector: "app-toast-container", ngImport: i0, template: "<div class=\"toast-stack\" aria-live=\"polite\" aria-atomic=\"false\">\n @for (toast of toastService.toasts(); track toast.id) {\n <div class=\"toast\" [class]=\"'toast-' + toast.variant\" role=\"status\">\n <span class=\"toast-icon\" aria-hidden=\"true\">\n @switch (toast.variant) {\n @case ('success') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n @case ('error') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"13\" />\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\" />\n </svg>\n }\n @default {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\" />\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\" />\n </svg>\n }\n }\n </span>\n <span class=\"toast-text\">{{ toast.text }}</span>\n <button\n type=\"button\"\n class=\"toast-dismiss\"\n (click)=\"toastService.dismiss(toast.id)\"\n aria-label=\"Dismiss notification\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n </div>\n }\n</div>\n", styles: [":host{position:fixed;bottom:24px;right:24px;z-index:1100;pointer-events:none}.toast-stack{display:flex;flex-direction:column;gap:10px;max-width:380px}.toast{pointer-events:auto;display:flex;align-items:flex-start;gap:10px;padding:12px 14px;background:var(--eds-color-surface);border:1px solid var(--eds-color-border);border-radius:var(--eds-radius-md);box-shadow:0 6px 24px #141e321f;font-size:13px;color:var(--eds-color-text);animation:slide-in .16s ease}.toast-icon{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;flex-shrink:0}.toast-info .toast-icon{background:var(--eds-color-primary-soft);color:var(--eds-color-primary)}.toast-success .toast-icon{background:var(--eds-color-success-soft);color:var(--eds-color-success)}.toast-error .toast-icon{background:var(--eds-color-danger-soft);color:var(--eds-color-danger)}.toast-text{flex:1;line-height:1.45}.toast-dismiss{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:transparent;color:var(--eds-color-text-soft);border-radius:4px;transition:background .12s ease,color .12s ease}.toast-dismiss:hover{background:var(--eds-color-surface-alt);color:var(--eds-color-text)}@keyframes slide-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
54
- }
55
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ToastContainerComponent, decorators: [{
56
- type: Component,
57
- args: [{ selector: 'app-toast-container', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"toast-stack\" aria-live=\"polite\" aria-atomic=\"false\">\n @for (toast of toastService.toasts(); track toast.id) {\n <div class=\"toast\" [class]=\"'toast-' + toast.variant\" role=\"status\">\n <span class=\"toast-icon\" aria-hidden=\"true\">\n @switch (toast.variant) {\n @case ('success') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n @case ('error') {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"13\" />\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\" />\n </svg>\n }\n @default {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\" />\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\" />\n </svg>\n }\n }\n </span>\n <span class=\"toast-text\">{{ toast.text }}</span>\n <button\n type=\"button\"\n class=\"toast-dismiss\"\n (click)=\"toastService.dismiss(toast.id)\"\n aria-label=\"Dismiss notification\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n </div>\n }\n</div>\n", styles: [":host{position:fixed;bottom:24px;right:24px;z-index:1100;pointer-events:none}.toast-stack{display:flex;flex-direction:column;gap:10px;max-width:380px}.toast{pointer-events:auto;display:flex;align-items:flex-start;gap:10px;padding:12px 14px;background:var(--eds-color-surface);border:1px solid var(--eds-color-border);border-radius:var(--eds-radius-md);box-shadow:0 6px 24px #141e321f;font-size:13px;color:var(--eds-color-text);animation:slide-in .16s ease}.toast-icon{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;flex-shrink:0}.toast-info .toast-icon{background:var(--eds-color-primary-soft);color:var(--eds-color-primary)}.toast-success .toast-icon{background:var(--eds-color-success-soft);color:var(--eds-color-success)}.toast-error .toast-icon{background:var(--eds-color-danger-soft);color:var(--eds-color-danger)}.toast-text{flex:1;line-height:1.45}.toast-dismiss{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:transparent;color:var(--eds-color-text-soft);border-radius:4px;transition:background .12s ease,color .12s ease}.toast-dismiss:hover{background:var(--eds-color-surface-alt);color:var(--eds-color-text)}@keyframes slide-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}\n"] }]
58
- }] });
2
+ import * as i0 from '@angular/core';
3
+ import { signal, Injectable, inject } from '@angular/core';
4
+ import { of, delay, throwError, map, catchError, forkJoin, switchMap } from 'rxjs';
5
+ import { V as VOICE_RECEPTIONIST_CONFIG } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-BsBQIS_f.mjs';
59
6
 
60
7
  /** Build URL helpers from a config (or raw base URL). Safe outside injection context. */
61
8
  function createVoiceReceptionistPaths(configOrBase) {
@@ -87,6 +34,356 @@ function extractApiRows(body) {
87
34
  return [];
88
35
  }
89
36
 
37
+ function mapOutboundPatientHit(raw) {
38
+ if (!raw || typeof raw !== 'object')
39
+ return null;
40
+ const r = raw;
41
+ const patientId = String(r['patientId'] ?? r['patient_id'] ?? r['id'] ?? '').trim();
42
+ if (!patientId)
43
+ return null;
44
+ const firstName = String(r['firstName'] ?? r['first_name'] ?? '').trim();
45
+ const lastName = String(r['lastName'] ?? r['last_name'] ?? '').trim();
46
+ const full = String(r['fullName'] ?? r['full_name'] ?? r['name'] ?? '').trim() ||
47
+ [firstName, lastName].filter(Boolean).join(' ');
48
+ const phone = String(r['mobilePhone'] ?? r['phone'] ?? r['mobile_phone'] ?? '').trim() || null;
49
+ const dobRaw = String(r['dateOfBirth'] ?? r['dob'] ?? '').trim();
50
+ return {
51
+ patientId,
52
+ firstName,
53
+ lastName,
54
+ fullName: full || 'Unknown patient',
55
+ phone,
56
+ dob: dobRaw ? dobRaw.slice(0, 10) : null,
57
+ };
58
+ }
59
+ function mapOutboundAppointmentHit(raw) {
60
+ if (!raw || typeof raw !== 'object')
61
+ return null;
62
+ const r = raw;
63
+ const appointmentId = String(r['appointmentId'] ?? r['appointment_id'] ?? r['id'] ?? '').trim();
64
+ if (!appointmentId)
65
+ return null;
66
+ const providerObj = r['provider'] && typeof r['provider'] === 'object'
67
+ ? r['provider']
68
+ : null;
69
+ const locationObj = r['location'] && typeof r['location'] === 'object'
70
+ ? r['location']
71
+ : null;
72
+ const visitObj = r['visitType'] && typeof r['visitType'] === 'object'
73
+ ? r['visitType']
74
+ : null;
75
+ const providerName = String(r['providerName'] ??
76
+ r['provider_name'] ??
77
+ providerObj?.['fullName'] ??
78
+ [providerObj?.['firstName'], providerObj?.['lastName']].filter(Boolean).join(' ') ??
79
+ '').trim() || null;
80
+ const locationName = String(r['locationName'] ?? r['location_name'] ?? locationObj?.['locationName'] ?? '').trim() || null;
81
+ const visitTypeRaw = r['visitType'];
82
+ const visitType = (typeof visitTypeRaw === 'string' ? visitTypeRaw.trim() : '') ||
83
+ String(r['visitTypeName'] ??
84
+ r['visit_type'] ??
85
+ visitObj?.['visitTypeName'] ??
86
+ visitObj?.['name'] ??
87
+ '').trim() ||
88
+ null;
89
+ return {
90
+ appointmentId,
91
+ startTime: String(r['startTime'] ?? r['start_time'] ?? '').trim() || null,
92
+ endTime: String(r['endTime'] ?? r['end_time'] ?? '').trim() || null,
93
+ providerName,
94
+ locationName,
95
+ status: String(r['appointmentStatus'] ?? r['status'] ?? r['visitStatus'] ?? '').trim() || null,
96
+ visitType,
97
+ };
98
+ }
99
+ /** Best-effort E.164 for US/local numbers used in dialer. */
100
+ function toE164Phone(raw) {
101
+ const digits = String(raw ?? '').replace(/\D/g, '');
102
+ if (!digits)
103
+ return '';
104
+ if (String(raw ?? '').trim().startsWith('+') && /^\+[1-9]\d{6,14}$/.test(String(raw).trim())) {
105
+ return String(raw).trim();
106
+ }
107
+ if (digits.length === 10)
108
+ return `+1${digits}`;
109
+ if (digits.length === 11 && digits.startsWith('1'))
110
+ return `+${digits}`;
111
+ if (digits.length >= 8)
112
+ return `+${digits}`;
113
+ return String(raw ?? '').trim();
114
+ }
115
+
116
+ function mapStaffMediaJoinResult(raw) {
117
+ if (!raw || typeof raw !== 'object')
118
+ return null;
119
+ const r = raw;
120
+ const data = r['data'] && typeof r['data'] === 'object'
121
+ ? r['data']
122
+ : r;
123
+ const livekitUrl = String(data['livekitUrl'] ?? data['livekit_url'] ?? data['url'] ?? '').trim();
124
+ const clientToken = String(data['clientToken'] ?? data['client_token'] ?? data['token'] ?? '').trim();
125
+ const modeRaw = String(data['mode'] ?? 'takeover').toLowerCase();
126
+ const mode = modeRaw === 'listen' ? 'listen' : 'takeover';
127
+ if (!livekitUrl || !clientToken)
128
+ return null;
129
+ return {
130
+ ok: data['ok'] !== false,
131
+ callId: String(data['callId'] ?? data['call_id'] ?? ''),
132
+ mode,
133
+ provider: String(data['provider'] ?? 'livekit'),
134
+ livekitUrl,
135
+ clientToken,
136
+ tokenExpiresAt: strOrNull$1(data['tokenExpiresAt'] ?? data['token_expires_at']),
137
+ conversationId: strOrNull$1(data['conversationId'] ?? data['conversation_id']),
138
+ memberId: strOrNull$1(data['memberId'] ?? data['member_id']),
139
+ roomName: strOrNull$1(data['roomName'] ?? data['room_name']),
140
+ iceServers: Array.isArray(data['iceServers']) ? data['iceServers'] : undefined,
141
+ controlMode: strOrNull$1(data['controlMode'] ?? data['control_mode']),
142
+ aiPaused: typeof data['aiPaused'] === 'boolean'
143
+ ? data['aiPaused']
144
+ : typeof data['ai_paused'] === 'boolean'
145
+ ? data['ai_paused']
146
+ : mode === 'takeover',
147
+ aiHandedOff: typeof data['aiHandedOff'] === 'boolean'
148
+ ? data['aiHandedOff']
149
+ : mode === 'takeover',
150
+ assigned: strOrNull$1(data['assigned']),
151
+ };
152
+ }
153
+ function strOrNull$1(value) {
154
+ const s = String(value ?? '').trim();
155
+ return s || null;
156
+ }
157
+
158
+ function mapAgentActivity(raw, index = 0) {
159
+ if (!raw || typeof raw !== 'object') {
160
+ return {
161
+ id: `activity-${index}`,
162
+ type: 'unknown',
163
+ occurredAt: new Date().toISOString(),
164
+ title: 'Activity',
165
+ detail: '',
166
+ };
167
+ }
168
+ const r = raw;
169
+ const nestedPatient = asRecord(r['patient']);
170
+ const nestedProvider = asRecord(r['provider'] ?? r['practitioner'] ?? r['doctor']);
171
+ const type = String(r['type'] ?? r['activityType'] ?? 'unknown');
172
+ const patientName = strOrNull(r['patientName'] ?? r['patient_name']) ?? strOrNull(nestedPatient?.['name']);
173
+ const providerName = strOrNull(r['providerName'] ??
174
+ r['provider_name'] ??
175
+ r['practitionerName'] ??
176
+ r['practitioner_name'] ??
177
+ r['doctorName'] ??
178
+ r['doctor_name']) ?? strOrNull(nestedProvider?.['name']);
179
+ const detail = String(r['detail'] ?? r['description'] ?? '').trim();
180
+ const parsed = parseActivityDetail(detail);
181
+ const insurance = resolveInsurance(r, nestedPatient);
182
+ const visitType = strOrNull(r['visitType'] ?? r['visit_type'] ?? r['category'] ?? r['reason']) ??
183
+ parsed.visitType;
184
+ const startAt = strOrNull(r['startAt'] ?? r['start_at'] ?? r['appointmentStart'] ?? r['appointment_start']) ??
185
+ parsed.startAt;
186
+ const endAt = strOrNull(r['endAt'] ?? r['end_at'] ?? r['appointmentEnd'] ?? r['appointment_end']);
187
+ const practiceName = strOrNull(r['practiceName'] ?? r['practice_name'] ?? r['location'] ?? r['clinic']) ??
188
+ parsed.practiceName;
189
+ return {
190
+ id: String(r['id'] ?? r['activityId'] ?? `activity-${index}`),
191
+ type,
192
+ occurredAt: String(r['occurredAt'] ?? r['occurred_at'] ?? r['createdAt'] ?? ''),
193
+ title: String(r['title'] ?? r['summary'] ?? '').trim() || activityTypeLabel(type),
194
+ detail,
195
+ patientId: strOrNull(r['patientId'] ?? r['patient_id'] ?? nestedPatient?.['id']),
196
+ patientName,
197
+ appointmentId: strOrNull(r['appointmentId'] ?? r['appointment_id']),
198
+ taskId: strOrNull(r['taskId'] ?? r['task_id']),
199
+ callId: strOrNull(r['callId'] ?? r['call_id']),
200
+ startAt,
201
+ endAt,
202
+ durationMinutes: numOrNull(r['durationMinutes'] ?? r['duration_minutes'] ?? r['duration']),
203
+ visitType,
204
+ practiceName,
205
+ patientDob: strOrNull(r['patientDob'] ?? r['patient_dob'] ?? r['dob'] ?? nestedPatient?.['dob']),
206
+ patientAge: numOrNull(r['patientAge'] ?? r['patient_age'] ?? r['age'] ?? nestedPatient?.['age']),
207
+ patientGender: normalizeGender(r['patientGender'] ?? r['patient_gender'] ?? r['gender'] ?? nestedPatient?.['gender']),
208
+ patientInitials: strOrNull(r['patientInitials'] ??
209
+ r['patient_initials'] ??
210
+ r['initials'] ??
211
+ nestedPatient?.['initials']) ?? initialsFromName(patientName),
212
+ patientMrn: strOrNull(r['patientMrn'] ??
213
+ r['patient_mrn'] ??
214
+ r['mrn'] ??
215
+ r['MRN'] ??
216
+ nestedPatient?.['mrn'] ??
217
+ nestedPatient?.['MRN']),
218
+ hasInsurance: insurance.hasInsurance,
219
+ insuranceName: insurance.name,
220
+ providerId: strOrNull(r['providerId'] ?? r['provider_id'] ?? nestedProvider?.['id']),
221
+ providerName,
222
+ providerInitials: strOrNull(r['providerInitials'] ??
223
+ r['provider_initials'] ??
224
+ nestedProvider?.['initials']) ?? initialsFromName(providerName),
225
+ providerSpecialty: strOrNull(r['providerSpecialty'] ??
226
+ r['provider_specialty'] ??
227
+ r['specialty'] ??
228
+ nestedProvider?.['specialty'] ??
229
+ nestedProvider?.['speciality']),
230
+ priority: normalizePriority(r['priority'] ?? r['taskPriority'] ?? r['task_priority']),
231
+ };
232
+ }
233
+ const INSURED_WORDS = ['active', 'verified', 'covered', 'insured', 'yes', 'true', 'eligible'];
234
+ const UNINSURED_WORDS = [
235
+ 'none',
236
+ 'no',
237
+ 'false',
238
+ 'uninsured',
239
+ 'self_pay',
240
+ 'self-pay',
241
+ 'selfpay',
242
+ 'inactive',
243
+ 'expired',
244
+ 'missing',
245
+ ];
246
+ /**
247
+ * Coverage can arrive as a boolean, a status string, a payer name or a nested object.
248
+ * Unknown stays null so the UI can distinguish "no coverage" from "never told us".
249
+ */
250
+ function resolveInsurance(r, patient) {
251
+ const nested = asRecord(r['insurance'] ?? patient?.['insurance']);
252
+ const name = strOrNull(r['insuranceName'] ??
253
+ r['insurance_name'] ??
254
+ r['payerName'] ??
255
+ r['payer_name'] ??
256
+ r['payer'] ??
257
+ patient?.['insuranceName'] ??
258
+ patient?.['insurance_name'] ??
259
+ nested?.['name'] ??
260
+ nested?.['payer'] ??
261
+ nested?.['planName']) ?? (typeof r['insurance'] === 'string' ? strOrNull(r['insurance']) : null);
262
+ const flag = firstBoolean([
263
+ r['hasInsurance'],
264
+ r['has_insurance'],
265
+ r['insured'],
266
+ r['isInsured'],
267
+ patient?.['hasInsurance'],
268
+ patient?.['has_insurance'],
269
+ patient?.['insured'],
270
+ nested?.['active'],
271
+ nested?.['isActive'],
272
+ ]);
273
+ if (flag !== null)
274
+ return { hasInsurance: flag, name };
275
+ const status = String(r['insuranceStatus'] ??
276
+ r['insurance_status'] ??
277
+ patient?.['insuranceStatus'] ??
278
+ patient?.['insurance_status'] ??
279
+ nested?.['status'] ??
280
+ (typeof r['insurance'] === 'string' ? r['insurance'] : '') ??
281
+ '')
282
+ .trim()
283
+ .toLowerCase();
284
+ if (status) {
285
+ if (INSURED_WORDS.includes(status))
286
+ return { hasInsurance: true, name };
287
+ if (UNINSURED_WORDS.includes(status))
288
+ return { hasInsurance: false, name };
289
+ }
290
+ // A payer name on its own implies coverage.
291
+ return { hasInsurance: name ? true : null, name };
292
+ }
293
+ function firstBoolean(values) {
294
+ for (const value of values) {
295
+ if (typeof value === 'boolean')
296
+ return value;
297
+ }
298
+ return null;
299
+ }
300
+ /** Pull visit type / slot / practice out of a messy free-text detail blob. */
301
+ function parseActivityDetail(detail) {
302
+ if (!detail)
303
+ return { visitType: null, startAt: null, practiceName: null };
304
+ const visitMatch = /\(([^)]+)\)\s*$/.exec(detail);
305
+ const visitType = visitMatch?.[1]?.trim() || null;
306
+ const dateMatch = /\bfor\s+(\d{4}-\d{2}-\d{2})(?:\s+(\d{1,2}:\d{2}(?::\d{2})?))?/i.exec(detail) ??
307
+ /\b(\d{4}-\d{2}-\d{2})[ T](\d{1,2}:\d{2}(?::\d{2})?)/.exec(detail);
308
+ let startAt = null;
309
+ if (dateMatch) {
310
+ const date = dateMatch[1];
311
+ const time = dateMatch[2] ? dateMatch[2].padStart(5, '0') : '00:00';
312
+ startAt = `${date}T${time.length === 5 ? `${time}:00` : time}`;
313
+ }
314
+ const practiceMatch = /\bAT\s+([A-Z0-9][A-Z0-9 ,.'&-]{2,}?)(?:\s*\(|$)/.exec(detail);
315
+ const practiceName = practiceMatch?.[1]?.trim().replace(/,\s*$/, '') || null;
316
+ return { visitType, startAt, practiceName };
317
+ }
318
+ function activityTypeLabel(type) {
319
+ switch (type) {
320
+ case 'patient_created':
321
+ return 'Patient created';
322
+ case 'appointment_booked':
323
+ return 'Appointment booked';
324
+ case 'task_created':
325
+ return 'Task created';
326
+ default:
327
+ return type.replace(/_/g, ' ') || 'Activity';
328
+ }
329
+ }
330
+ function initialsFromName(name) {
331
+ if (!name?.trim())
332
+ return null;
333
+ const cleaned = name.replace(/^dr\.?\s+/i, '').trim();
334
+ const parts = cleaned.split(/\s+/).filter(Boolean);
335
+ if (!parts.length)
336
+ return null;
337
+ if (parts.length === 1)
338
+ return parts[0].slice(0, 2).toUpperCase();
339
+ return `${parts[0][0] ?? ''}${parts[parts.length - 1][0] ?? ''}`.toUpperCase();
340
+ }
341
+ function normalizeGender(value) {
342
+ const s = String(value ?? '').trim();
343
+ if (!s)
344
+ return null;
345
+ const key = s.toLowerCase();
346
+ if (key === 'm' || key === 'male')
347
+ return 'M';
348
+ if (key === 'f' || key === 'female')
349
+ return 'F';
350
+ if (key === 'o' || key === 'other' || key === 'non-binary' || key === 'nonbinary')
351
+ return 'O';
352
+ return s.length <= 3 ? s.toUpperCase() : s;
353
+ }
354
+ function normalizePriority(value) {
355
+ const key = String(value ?? '')
356
+ .trim()
357
+ .toLowerCase()
358
+ .replace(/[\s-]+/g, '_');
359
+ if (!key)
360
+ return null;
361
+ if (key === 'urgent' || key === 'critical')
362
+ return 'urgent';
363
+ if (key === 'high')
364
+ return 'high';
365
+ if (key === 'medium' || key === 'med' || key === 'normal')
366
+ return 'medium';
367
+ if (key === 'low')
368
+ return 'low';
369
+ return null;
370
+ }
371
+ function asRecord(value) {
372
+ return value && typeof value === 'object' && !Array.isArray(value)
373
+ ? value
374
+ : null;
375
+ }
376
+ function strOrNull(value) {
377
+ const s = String(value ?? '').trim();
378
+ return s || null;
379
+ }
380
+ function numOrNull(value) {
381
+ if (value == null || value === '')
382
+ return null;
383
+ const n = Number(value);
384
+ return Number.isFinite(n) ? n : null;
385
+ }
386
+
90
387
  const DEFAULT_PAGE_SIZE = 50;
91
388
  /** Parse `{ items, total, page, limit }` or a bare array (legacy). */
92
389
  function asPaginatedResult(body, fallbackPage = 1, fallbackLimit = DEFAULT_PAGE_SIZE) {
@@ -113,7 +410,7 @@ function totalPages(total, pageSize) {
113
410
  return Math.max(1, Math.ceil(total / pageSize));
114
411
  }
115
412
 
116
- const WELCOME_MESSAGE_MAX = 500;
413
+ const WELCOME_MESSAGE_MAX = 1000;
117
414
  function buildAutoWelcomeTemplate(practiceName, agentName) {
118
415
  const practice = practiceName.trim() || 'our office';
119
416
  const agent = agentName.trim() || 'your AI receptionist';
@@ -133,6 +430,112 @@ function computeEffectiveWelcomeMessage(settings) {
133
430
  return buildAutoWelcomeTemplate(String(general['practiceName'] ?? ''), String(general['agentName'] ?? ''));
134
431
  }
135
432
 
433
+ /** Call history table helpers — resolution badges + result chips. */
434
+ function historyResolution(input) {
435
+ if (input.risk === 'urgent' || input.risk === 'review')
436
+ return 'needs_attention';
437
+ const o = String(input.outcome ?? '').toLowerCase();
438
+ if (o.includes('fail') ||
439
+ o.includes('escalat') ||
440
+ o.includes('abandon') ||
441
+ o.includes('attention') ||
442
+ o.includes('voicemail') ||
443
+ o === 'transferred') {
444
+ return 'needs_attention';
445
+ }
446
+ return 'resolved';
447
+ }
448
+ function historyResolutionLabel(resolution) {
449
+ return resolution === 'resolved' ? 'RESOLVED' : 'NEEDS ATTENTION';
450
+ }
451
+ function resultChipIcon(label) {
452
+ const key = label.toLowerCase();
453
+ if (key.includes('appointment') || key.includes('book'))
454
+ return 'calendar_month';
455
+ if (key.includes('task'))
456
+ return 'check_circle';
457
+ if (key.includes('register') || key.includes('patient'))
458
+ return 'person_add';
459
+ if (key.includes('info'))
460
+ return 'info';
461
+ if (key.includes('transfer'))
462
+ return 'phone_forwarded';
463
+ if (key.includes('refill'))
464
+ return 'medication';
465
+ return 'label';
466
+ }
467
+ function callerMetaLine(input) {
468
+ if (isUnknownCaller(input.name, input.matched)) {
469
+ return String(input.phone ?? '').trim() || '—';
470
+ }
471
+ const age = input.age ?? ageFromDob(input.dob);
472
+ if (!input.dob && age == null && !input.gender) {
473
+ return String(input.phone ?? '').trim() || '—';
474
+ }
475
+ const parts = [];
476
+ if (input.dob) {
477
+ parts.push(`DOB: ${formatDobDisplay(input.dob)}`);
478
+ }
479
+ const ageGender = [];
480
+ if (age != null)
481
+ ageGender.push(`${age}Y`);
482
+ if (input.gender)
483
+ ageGender.push(String(input.gender).charAt(0).toUpperCase());
484
+ if (ageGender.length) {
485
+ parts.push(parts.length ? `• ${ageGender.join(' | ')}` : ageGender.join(' | '));
486
+ }
487
+ return parts.join(' ') || String(input.phone ?? '').trim() || '—';
488
+ }
489
+ function formatDobDisplay(dob) {
490
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(dob.trim());
491
+ if (m)
492
+ return `${m[2]}-${m[3]}-${m[1]}`;
493
+ return dob;
494
+ }
495
+ /** Age in whole years from YYYY-MM-DD (or partial ISO). */
496
+ function ageFromDob(dob, at = new Date()) {
497
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(dob ?? '').trim());
498
+ if (!m)
499
+ return null;
500
+ const y = Number(m[1]);
501
+ const mo = Number(m[2]);
502
+ const d = Number(m[3]);
503
+ if (!y || !mo || !d)
504
+ return null;
505
+ let age = at.getFullYear() - y;
506
+ const hadBirthday = at.getMonth() + 1 > mo || (at.getMonth() + 1 === mo && at.getDate() >= d);
507
+ if (!hadBirthday)
508
+ age -= 1;
509
+ return age >= 0 && age < 130 ? age : null;
510
+ }
511
+ function isUnknownCaller(name, matched) {
512
+ if (matched === false)
513
+ return true;
514
+ const n = String(name ?? '').trim();
515
+ return !n || /^unknown/i.test(n) || n === '?';
516
+ }
517
+ /** Derive result chips from explicit actions, else from outcome text. */
518
+ function historyResultChips(results, outcome) {
519
+ if (results?.length)
520
+ return results;
521
+ const o = outcome.trim();
522
+ if (!o)
523
+ return [];
524
+ const lower = o.toLowerCase();
525
+ if (lower.includes('appointment') || lower.includes('book'))
526
+ return ['Appointment Booked'];
527
+ if (lower.includes('task'))
528
+ return ['Task Created'];
529
+ if (lower.includes('register'))
530
+ return ['Patient Registered'];
531
+ if (lower.includes('info') || lower.includes('hours') || lower.includes('direction')) {
532
+ return ['Information Provided'];
533
+ }
534
+ if (lower === 'completed')
535
+ return ['Information Provided'];
536
+ return [o];
537
+ }
538
+
136
539
  /** V3 agent tool labels — mirrors GET /api/agent-tools/catalog */
137
540
  const AGENT_TOOL_LABELS = {
138
541
  find_doctors: 'Found Doctors',
@@ -239,6 +642,22 @@ function mapCallStatus(raw) {
239
642
  ];
240
643
  return allowed.includes(s) ? s : 'ai';
241
644
  }
645
+ function mapControlMode(raw, status, aiPaused) {
646
+ const mode = String(raw ?? '').toLowerCase().replace(/_/g, '-');
647
+ if (mode === 'ai' || mode === 'takeover' || mode === 'listen' || mode === 'paused') {
648
+ return mode;
649
+ }
650
+ if (aiPaused === true)
651
+ return 'paused';
652
+ const st = String(status ?? '').toLowerCase().replace(/_/g, '-');
653
+ if (st === 'takeover')
654
+ return 'takeover';
655
+ if (st === 'paused' || st === 'pause')
656
+ return 'paused';
657
+ if (st === 'listen' || st === 'listening')
658
+ return 'listen';
659
+ return 'ai';
660
+ }
242
661
  function mapSentiment(raw) {
243
662
  const s = String(raw ?? 'neutral').toLowerCase();
244
663
  if (s === 'calm' || s === 'neutral' || s === 'frustrated' || s === 'urgent')
@@ -283,10 +702,19 @@ function mapRecordingMeta(dto) {
283
702
  };
284
703
  }
285
704
  function mapLiveCallDto(dto) {
705
+ const dobRaw = String(dto.dob ?? dto.dateOfBirth ?? '').trim();
706
+ const dob = dobRaw ? dobRaw.slice(0, 10) : undefined;
707
+ const ageNum = typeof dto.age === 'number'
708
+ ? dto.age
709
+ : dto.age != null && String(dto.age).trim()
710
+ ? Number(dto.age)
711
+ : null;
712
+ const age = ageNum != null && Number.isFinite(ageNum) ? ageNum : ageFromDob(dob);
713
+ const gender = String(dto.gender ?? dto.sex ?? '').trim() || null;
286
714
  return {
287
715
  id: dto.id,
288
716
  name: dto.name,
289
- initials: dto.initials,
717
+ initials: dto.initials || (dto.matched ? '' : '?'),
290
718
  avatar: dto.avatar,
291
719
  phone: dto.phone,
292
720
  verification: mapVerification(dto.verification),
@@ -297,22 +725,48 @@ function mapLiveCallDto(dto) {
297
725
  sentiment: mapSentiment(dto.sentiment),
298
726
  confidence: normalizeConfidence(dto.confidence),
299
727
  goal: dto.goal,
300
- dob: dto.dob ?? undefined,
728
+ dob,
729
+ age,
730
+ gender,
301
731
  location: dto.location ?? '',
302
732
  provider: dto.provider,
303
733
  language: dto.language,
304
734
  matched: dto.matched,
305
735
  assigned: dto.assigned,
736
+ controlMode: mapControlMode(dto.controlMode, dto.status, dto.aiPaused),
306
737
  tools: (dto.tools ?? []).map(mapToolEvent),
307
738
  transcript: (dto.transcript ?? []).map(mapTranscriptLine),
739
+ aiPaused: dto.aiPaused === true || String(dto.controlMode ?? '').toLowerCase() === 'paused',
740
+ aiHandedOff: dto.aiHandedOff === true,
308
741
  };
309
742
  }
310
743
  function mapHistoryCallDto(dto) {
311
744
  const parsedStarted = Date.parse(dto.startedAt ?? dto.when);
745
+ const dobRaw = String(dto.dob ?? dto.dateOfBirth ?? '').trim();
746
+ const ageNum = typeof dto.age === 'number'
747
+ ? dto.age
748
+ : dto.age != null && String(dto.age).trim()
749
+ ? Number(dto.age)
750
+ : null;
751
+ const gender = String(dto.gender ?? dto.sex ?? '').trim() || null;
752
+ const handledRaw = String(dto.handledBy ?? dto.handler ?? '').trim().toLowerCase();
753
+ let handledBy;
754
+ if (handledRaw === 'staff' || handledRaw === 'human' || handledRaw === 'user') {
755
+ handledBy = 'staff';
756
+ }
757
+ else if (handledRaw === 'ai' || handledRaw === 'agent' || handledRaw === 'ava') {
758
+ handledBy = 'ai';
759
+ }
760
+ else if (String(dto.outcome ?? '').toLowerCase().includes('transfer')) {
761
+ handledBy = 'staff';
762
+ }
763
+ else {
764
+ handledBy = 'ai';
765
+ }
312
766
  return {
313
767
  id: dto.id,
314
768
  name: dto.name,
315
- initials: dto.initials,
769
+ initials: dto.initials || '?',
316
770
  phone: dto.phone,
317
771
  direction: dto.dir,
318
772
  outcome: dto.outcome,
@@ -324,6 +778,14 @@ function mapHistoryCallDto(dto) {
324
778
  location: dto.location,
325
779
  verification: mapVerification(dto.verify),
326
780
  review: dto.review === 'reviewed' ? 'reviewed' : 'needs',
781
+ handledBy,
782
+ dob: dobRaw ? dobRaw.slice(0, 10) : null,
783
+ age: ageNum != null && Number.isFinite(ageNum) ? ageNum : null,
784
+ gender,
785
+ results: Array.isArray(dto.actions)
786
+ ? dto.actions.map((a) => String(a).trim()).filter(Boolean)
787
+ : [],
788
+ risk: dto.risk ?? null,
327
789
  };
328
790
  }
329
791
  function mapHistoryDetailDto(call, detail, recordingUrl) {
@@ -518,6 +980,8 @@ function mapVolumeSeries(raw) {
518
980
  day: p.day,
519
981
  label: p.label,
520
982
  count: p.count,
983
+ inbound: p.inbound,
984
+ outbound: p.outbound,
521
985
  })),
522
986
  };
523
987
  }
@@ -595,22 +1059,77 @@ function mapSettingsResponse(body, safetyBody) {
595
1059
  status: c.ok ? 'ok' : 'warn',
596
1060
  }));
597
1061
  const mapped = {
598
- general: section('general'),
599
- phone: section('phoneAvailability'),
1062
+ general: normalizeGeneralSection(section('general')),
1063
+ phone: normalizePhoneSection(section('phoneAvailability')),
600
1064
  tasks: section('allowedTasks'),
601
1065
  scheduling: section('schedulingRules'),
602
1066
  refill: section('refillRules'),
603
1067
  verify: section('patientVerification'),
604
1068
  escalation: section('humanEscalation'),
605
1069
  outbound: section('outboundCalling'),
1070
+ knowledge: normalizeKnowledgeSection(section('knowledgeBase')),
606
1071
  safety,
607
- transcripts: section('transcriptsRecording'),
1072
+ transcripts: normalizeTranscriptsSection(section('transcriptsRecording')),
608
1073
  advanced: section('advanced'),
609
1074
  };
610
1075
  mapped.effectiveWelcomeMessage =
611
1076
  raw.effectiveWelcomeMessage ?? computeEffectiveWelcomeMessage(mapped);
1077
+ if (!mapped.knowledge) {
1078
+ mapped.knowledge = { whatToSay: '', whatNotToSay: '' };
1079
+ }
612
1080
  return mapped;
613
1081
  }
1082
+ function normalizeKnowledgeSection(raw) {
1083
+ return {
1084
+ ...raw,
1085
+ whatToSay: String(raw['whatToSay'] ?? raw['what_to_say'] ?? ''),
1086
+ whatNotToSay: String(raw['whatNotToSay'] ?? raw['what_not_to_say'] ?? ''),
1087
+ };
1088
+ }
1089
+ function normalizeGeneralSection(raw) {
1090
+ const language = String(raw['language'] ?? 'English');
1091
+ const languagesRaw = raw['languages'] ?? raw['languageList'] ?? language;
1092
+ const languages = Array.isArray(languagesRaw)
1093
+ ? languagesRaw.map(String).join(',')
1094
+ : String(languagesRaw || language);
1095
+ return {
1096
+ ...raw,
1097
+ languages,
1098
+ tone: String(raw['tone'] ?? 'professional'),
1099
+ };
1100
+ }
1101
+ function normalizePhoneSection(raw) {
1102
+ const locationIds = raw['locationIds'] ?? raw['location_ids'] ?? raw['locationsHandled'] ?? '';
1103
+ const locations = Array.isArray(locationIds) ? locationIds.map(String).join(',') : String(locationIds ?? '');
1104
+ const afterHoursBehavior = String(raw['afterHoursBehavior'] ??
1105
+ raw['after_hours_behavior'] ??
1106
+ (raw['afterHours'] === false ? 'voicemail' : 'ai_answers'));
1107
+ return {
1108
+ ...raw,
1109
+ answerMode: String(raw['answerMode'] ?? raw['answer_mode'] ?? 'always_first'),
1110
+ transferNumber: String(raw['transferNumber'] ?? raw['transfer_number'] ?? ''),
1111
+ ringTimeout: Number(raw['ringTimeout'] ?? raw['ring_timeout'] ?? 20) || 20,
1112
+ createFollowUpTask: Boolean(raw['createFollowUpTask'] ?? raw['create_follow_up_task'] ?? false),
1113
+ locationIds: locations,
1114
+ maxCallDurationMin: Number(raw['maxCallDurationMin'] ?? raw['max_call_duration_min'] ?? 5) || 5,
1115
+ silenceTimeoutSec: Number(raw['silenceTimeoutSec'] ?? raw['silence_timeout_sec'] ?? 30) || 30,
1116
+ afterHoursBehavior,
1117
+ };
1118
+ }
1119
+ function normalizeTranscriptsSection(raw) {
1120
+ const legacyRetain = Number(raw['retainDays'] ?? raw['retain_days'] ?? 30) || 30;
1121
+ return {
1122
+ ...raw,
1123
+ recordingRetainDays: Number(raw['recordingRetainDays'] ?? raw['recording_retain_days'] ?? legacyRetain) ||
1124
+ legacyRetain,
1125
+ transcriptRetainDays: Number(raw['transcriptRetainDays'] ?? raw['transcript_retain_days'] ?? legacyRetain) ||
1126
+ legacyRetain,
1127
+ disclosureWarningEnabled: Boolean(raw['disclosureWarningEnabled'] ?? raw['disclosure_warning_enabled'] ?? true),
1128
+ disclosureWarningText: String(raw['disclosureWarningText'] ??
1129
+ raw['disclosure_warning_text'] ??
1130
+ 'This call may be handled by an automated assistant and could be recorded for quality and training. You can ask to speak with a staff member at any time.'),
1131
+ };
1132
+ }
614
1133
  function humanizeSettingsKey(key) {
615
1134
  return key
616
1135
  .replace(/([A-Z])/g, ' $1')
@@ -629,6 +1148,7 @@ function mapSettingsToRequest(settings) {
629
1148
  patientVerification: settings.verify,
630
1149
  humanEscalation: settings.escalation,
631
1150
  outboundCalling: settings.outbound,
1151
+ knowledgeBase: settings.knowledge,
632
1152
  transcriptsRecording: settings.transcripts,
633
1153
  advanced: settings.advanced,
634
1154
  };
@@ -688,7 +1208,7 @@ function asAttentionList(body) {
688
1208
  return [];
689
1209
  }
690
1210
 
691
- const now$1 = Date.now();
1211
+ const now$2 = Date.now();
692
1212
  function item(partial) {
693
1213
  return {
694
1214
  callId: 'LC-4821',
@@ -699,7 +1219,7 @@ function item(partial) {
699
1219
  durationMs: 340,
700
1220
  toolName: null,
701
1221
  errorMessage: null,
702
- startedAt: new Date(now$1 - 120_000).toISOString(),
1222
+ startedAt: new Date(now$2 - 120_000).toISOString(),
703
1223
  requestPreview: '{"phone":"+15551234567"}',
704
1224
  responsePreview: '{"items":[{"id":"p1"}]}',
705
1225
  ...partial,
@@ -715,7 +1235,7 @@ const SEED_CALL_API_LOGS = {
715
1235
  urlPath: '/vonage/answer',
716
1236
  statusCode: 200,
717
1237
  durationMs: 120,
718
- startedAt: new Date(now$1 - 300_000).toISOString(),
1238
+ startedAt: new Date(now$2 - 300_000).toISOString(),
719
1239
  requestPreview: '{"callId":"..."}',
720
1240
  responsePreview: '{"status":"connected"}',
721
1241
  }),
@@ -728,7 +1248,7 @@ const SEED_CALL_API_LOGS = {
728
1248
  urlPath: '/api/v1/patients',
729
1249
  statusCode: 200,
730
1250
  durationMs: 420,
731
- startedAt: new Date(now$1 - 240_000).toISOString(),
1251
+ startedAt: new Date(now$2 - 240_000).toISOString(),
732
1252
  }),
733
1253
  item({
734
1254
  id: 'log-3',
@@ -738,7 +1258,7 @@ const SEED_CALL_API_LOGS = {
738
1258
  urlPath: '/v1/chat/completions',
739
1259
  statusCode: 200,
740
1260
  durationMs: 890,
741
- startedAt: new Date(now$1 - 180_000).toISOString(),
1261
+ startedAt: new Date(now$2 - 180_000).toISOString(),
742
1262
  requestPreview: '{"model":"gpt-4o-mini"}',
743
1263
  responsePreview: '{"choices":[...]}',
744
1264
  }),
@@ -751,7 +1271,7 @@ const SEED_CALL_API_LOGS = {
751
1271
  urlPath: '/api/appointments',
752
1272
  statusCode: 201,
753
1273
  durationMs: 560,
754
- startedAt: new Date(now$1 - 60_000).toISOString(),
1274
+ startedAt: new Date(now$2 - 60_000).toISOString(),
755
1275
  }),
756
1276
  ],
757
1277
  'HC-9001': [
@@ -765,7 +1285,7 @@ const SEED_CALL_API_LOGS = {
765
1285
  success: false,
766
1286
  durationMs: 2100,
767
1287
  errorMessage: 'Internal Server Error',
768
- startedAt: new Date(now$1 - 86_400_000).toISOString(),
1288
+ startedAt: new Date(now$2 - 86_400_000).toISOString(),
769
1289
  }),
770
1290
  ],
771
1291
  };
@@ -776,7 +1296,7 @@ const SEED_API_LOG_DETAILS = {
776
1296
  requestBody: { patientId: 'p1', slotId: 'slot-9', reason: 'Annual checkup' },
777
1297
  responseBody: { appointmentId: 'apt-42', status: 'confirmed' },
778
1298
  metadata: { practiceId: 'prac-1', locationId: 'loc-1' },
779
- completedAt: new Date(now$1 - 59_500).toISOString(),
1299
+ completedAt: new Date(now$2 - 59_500).toISOString(),
780
1300
  },
781
1301
  'log-f1': {
782
1302
  ...SEED_CALL_API_LOGS['HC-9001'][0],
@@ -784,7 +1304,7 @@ const SEED_API_LOG_DETAILS = {
784
1304
  requestBody: { phone: '+15559876543' },
785
1305
  responseBody: { error: 'Internal Server Error', message: 'Database timeout' },
786
1306
  metadata: { retryCount: 1 },
787
- completedAt: new Date(now$1 - 86_398_000).toISOString(),
1307
+ completedAt: new Date(now$2 - 86_398_000).toISOString(),
788
1308
  },
789
1309
  };
790
1310
  function allSeedLogs() {
@@ -808,7 +1328,7 @@ const SEED_API_LOG_STATS = {
808
1328
  success: false,
809
1329
  durationMs: 2100,
810
1330
  errorMessage: 'Internal Server Error',
811
- startedAt: new Date(now$1 - 3_600_000).toISOString(),
1331
+ startedAt: new Date(now$2 - 3_600_000).toISOString(),
812
1332
  }),
813
1333
  item({
814
1334
  id: 'log-fail-2',
@@ -819,7 +1339,7 @@ const SEED_API_LOG_STATS = {
819
1339
  success: false,
820
1340
  durationMs: 1500,
821
1341
  errorMessage: 'Service unavailable',
822
- startedAt: new Date(now$1 - 7_200_000).toISOString(),
1342
+ startedAt: new Date(now$2 - 7_200_000).toISOString(),
823
1343
  }),
824
1344
  item({
825
1345
  id: 'log-fail-3',
@@ -830,13 +1350,13 @@ const SEED_API_LOG_STATS = {
830
1350
  success: false,
831
1351
  durationMs: 980,
832
1352
  errorMessage: 'Gateway timeout',
833
- startedAt: new Date(now$1 - 10_800_000).toISOString(),
1353
+ startedAt: new Date(now$2 - 10_800_000).toISOString(),
834
1354
  }),
835
1355
  ],
836
1356
  };
837
1357
 
838
- const now = Date.now();
839
- const min = (m) => now - m * 60_000;
1358
+ const now$1 = Date.now();
1359
+ const min = (m) => now$1 - m * 60_000;
840
1360
  const baseTranscript = [
841
1361
  { who: 'system', text: 'Call connected · inbound PSTN' },
842
1362
  {
@@ -875,6 +1395,8 @@ const SEED_LIVE_CALLS = [
875
1395
  confidence: 92,
876
1396
  goal: 'Reschedule visit with Dr. Chen',
877
1397
  dob: '1985-03-14',
1398
+ age: 41,
1399
+ gender: 'F',
878
1400
  location: 'Riverside Main',
879
1401
  provider: 'Dr. Elena Chen',
880
1402
  language: 'English',
@@ -922,6 +1444,9 @@ const SEED_LIVE_CALLS = [
922
1444
  sentiment: 'frustrated',
923
1445
  confidence: 71,
924
1446
  goal: 'Refill lisinopril 10mg',
1447
+ dob: '1978-09-22',
1448
+ age: 47,
1449
+ gender: 'M',
925
1450
  location: 'North Clinic',
926
1451
  provider: 'Dr. Patel',
927
1452
  language: 'English',
@@ -1018,59 +1543,81 @@ const SEED_ATTENTION = [
1018
1543
  const SEED_HISTORY = [
1019
1544
  {
1020
1545
  id: 'HC-9001',
1021
- name: 'Linda Nguyen',
1022
- initials: 'LN',
1546
+ name: 'Sooraj Jagadeesh',
1547
+ initials: 'SJ',
1023
1548
  phone: '+1 (512) 555-0211',
1024
1549
  direction: 'inbound',
1025
1550
  outcome: 'Appointment booked',
1026
- reason: 'New patient intake',
1027
- duration: 342,
1028
- started: min(45),
1551
+ reason: 'Appointment Refill',
1552
+ duration: 252,
1553
+ durationLabel: '4:12',
1554
+ started: Date.parse('2026-06-12T14:20:00'),
1029
1555
  location: 'Riverside Main',
1030
1556
  verification: 'verified',
1031
1557
  review: 'reviewed',
1558
+ handledBy: 'ai',
1559
+ dob: '2002-06-30',
1560
+ age: 23,
1561
+ gender: 'M',
1562
+ results: ['Appointment Booked'],
1032
1563
  },
1033
1564
  {
1034
1565
  id: 'HC-9002',
1035
- name: 'Robert Kim',
1036
- initials: 'RK',
1037
- phone: '+1 (737) 555-0333',
1566
+ name: 'Unknown Caller',
1567
+ initials: '?',
1568
+ phone: '+1 (555) 010-2299',
1038
1569
  direction: 'outbound',
1039
- outcome: 'Voicemail',
1040
- reason: 'Follow-up reminder',
1041
- duration: 28,
1042
- started: min(90),
1570
+ outcome: 'Task created',
1571
+ reason: 'Appointment',
1572
+ duration: 165,
1573
+ durationLabel: '2:45',
1574
+ started: Date.parse('2026-06-12T11:05:00'),
1043
1575
  location: 'North Clinic',
1044
- verification: 'verified',
1576
+ verification: 'unknown',
1045
1577
  review: 'needs',
1578
+ handledBy: 'ai',
1579
+ results: ['Task Created'],
1580
+ risk: 'review',
1046
1581
  },
1047
1582
  {
1048
1583
  id: 'HC-9003',
1049
- name: 'Sarah Williams',
1050
- initials: 'SW',
1584
+ name: 'Dinesh Kumar',
1585
+ initials: 'DK',
1051
1586
  phone: '+1 (512) 555-0444',
1052
1587
  direction: 'inbound',
1053
- outcome: 'Transferred',
1054
- reason: 'Insurance prior auth',
1055
- duration: 198,
1056
- started: min(120),
1588
+ outcome: 'Completed',
1589
+ reason: 'Appointment',
1590
+ duration: 372,
1591
+ durationLabel: '6:12',
1592
+ started: Date.parse('2026-06-11T16:45:00'),
1057
1593
  location: 'Riverside Main',
1058
1594
  verification: 'verified',
1059
- review: 'needs',
1595
+ review: 'reviewed',
1596
+ handledBy: 'staff',
1597
+ dob: '1988-03-14',
1598
+ age: 38,
1599
+ gender: 'M',
1600
+ results: ['Patient Registered', 'Appointment Booked'],
1060
1601
  },
1061
1602
  {
1062
1603
  id: 'HC-9004',
1063
- name: 'Carlos Mendez',
1064
- initials: 'CM',
1604
+ name: 'Meera Banerjee',
1605
+ initials: 'MB',
1065
1606
  phone: '+1 (512) 555-0555',
1066
1607
  direction: 'inbound',
1067
- outcome: 'Task created',
1068
- reason: 'Lab results question',
1069
- duration: 156,
1070
- started: min(180),
1608
+ outcome: 'Completed',
1609
+ reason: 'Information',
1610
+ duration: 93,
1611
+ durationLabel: '1:33',
1612
+ started: Date.parse('2026-06-11T09:12:00'),
1071
1613
  location: 'South Clinic',
1072
1614
  verification: 'partial',
1073
- review: 'reviewed',
1615
+ review: 'needs',
1616
+ handledBy: 'ai',
1617
+ dob: '1995-11-02',
1618
+ age: 30,
1619
+ gender: 'F',
1620
+ results: ['Information Provided'],
1074
1621
  },
1075
1622
  {
1076
1623
  id: 'HC-9005',
@@ -1081,10 +1628,13 @@ const SEED_HISTORY = [
1081
1628
  outcome: 'Completed',
1082
1629
  reason: 'Hours & directions',
1083
1630
  duration: 89,
1084
- started: min(240),
1631
+ durationLabel: '1:29',
1632
+ started: Date.parse('2026-06-10T13:40:00'),
1085
1633
  location: 'North Clinic',
1086
1634
  verification: 'unknown',
1087
1635
  review: 'reviewed',
1636
+ handledBy: 'ai',
1637
+ results: ['Information Provided'],
1088
1638
  },
1089
1639
  ];
1090
1640
  const SEED_HISTORY_DETAILS = {
@@ -1218,57 +1768,95 @@ const SEED_AUDIT = [
1218
1768
  const SEED_ANALYTICS = {
1219
1769
  range: '30d',
1220
1770
  kpiCards: [
1221
- { label: 'Calls handled', value: '1,247', delta: '+12%', tone: 'pos', sub: 'last 30 days' },
1222
- { label: 'AI resolution rate', value: '62%', delta: '+4%', tone: 'pos', sub: 'without transfer' },
1223
- { label: 'Avg handle time', value: '3:06', delta: '−8s', tone: 'pos', sub: 'vs prior period' },
1224
- { label: 'Transfers', value: '14%', delta: '−2%', tone: 'neutral', sub: 'patient requested human' },
1771
+ {
1772
+ label: 'Estimated Impact This Month',
1773
+ value: '96 hours',
1774
+ delta: '',
1775
+ tone: 'pos',
1776
+ sub: 'of front-desk time handled by the AI ~2.4 full work weeks.',
1777
+ },
1778
+ {
1779
+ label: 'AI Autonomous Resolution',
1780
+ value: '89%',
1781
+ delta: '+12%',
1782
+ tone: 'pos',
1783
+ sub: 'without staff intervention',
1784
+ },
1785
+ {
1786
+ label: 'Appointments Booked',
1787
+ value: '34',
1788
+ delta: '-35%',
1789
+ tone: 'warn',
1790
+ sub: 'Created by AI',
1791
+ },
1792
+ {
1793
+ label: 'Tasks Created',
1794
+ value: '19',
1795
+ delta: '-35%',
1796
+ tone: 'warn',
1797
+ sub: 'Created by AI',
1798
+ },
1799
+ {
1800
+ label: 'Patients Registered',
1801
+ value: '75',
1802
+ delta: '-6%',
1803
+ tone: 'warn',
1804
+ sub: 'Created by AI',
1805
+ },
1806
+ {
1807
+ label: 'Calls Handled',
1808
+ value: '1,234',
1809
+ delta: '+25%',
1810
+ tone: 'pos',
1811
+ sub: 'Inbound 104 · Outbound 24',
1812
+ },
1225
1813
  ],
1226
1814
  volume: {
1227
1815
  timezone: 'America/Chicago',
1228
- startDate: '2025-05-22',
1816
+ startDate: '2025-05-01',
1229
1817
  endDate: '2025-05-28',
1230
1818
  points: [
1231
- { date: '2025-05-22', day: 'Mon', label: 'Mon May 22', count: 42 },
1232
- { date: '2025-05-23', day: 'Tue', label: 'Tue May 23', count: 58 },
1233
- { date: '2025-05-24', day: 'Wed', label: 'Wed May 24', count: 51 },
1234
- { date: '2025-05-25', day: 'Thu', label: 'Thu May 25', count: 63 },
1235
- { date: '2025-05-26', day: 'Fri', label: 'Fri May 26', count: 47 },
1236
- { date: '2025-05-27', day: 'Sat', label: 'Sat May 27', count: 22 },
1237
- { date: '2025-05-28', day: 'Sun', label: 'Sun May 28', count: 18 },
1819
+ { date: '2025-05-01', day: 'Week 1', label: 'Week 1', count: 210, inbound: 170, outbound: 40 },
1820
+ { date: '2025-05-08', day: 'Week 2', label: 'Week 2', count: 255, inbound: 200, outbound: 55 },
1821
+ { date: '2025-05-15', day: 'Week 3', label: 'Week 3', count: 180, inbound: 145, outbound: 35 },
1822
+ { date: '2025-05-22', day: 'Week 4', label: 'Week 4', count: 290, inbound: 230, outbound: 60 },
1238
1823
  ],
1239
1824
  },
1240
1825
  outcomes: [
1241
- { label: 'Completed by AI', value: 62, color: '#3681F4' },
1242
- { label: 'Transferred', value: 18, color: '#8E51FF' },
1243
- { label: 'Voicemail', value: 8, color: '#71717A' },
1244
- { label: 'Abandoned', value: 12, color: '#F03740' },
1826
+ { label: 'AI handled', value: 78, color: '#8E51FF' },
1827
+ { label: 'Human Assisted', value: 18, color: '#3681F4' },
1828
+ { label: 'Needs Attention', value: 4, color: '#F472B6' },
1245
1829
  ],
1246
1830
  reasons: [
1247
- { label: 'Scheduling', value: 38 },
1248
- { label: 'Refills', value: 22 },
1249
- { label: 'Billing', value: 15 },
1250
- { label: 'Directions', value: 10 },
1251
- { label: 'Other', value: 15 },
1831
+ { label: 'Appointments', value: 68 },
1832
+ { label: 'Medication/Refills', value: 24 },
1833
+ { label: 'Message', value: 20 },
1834
+ { label: 'Information', value: 23 },
1835
+ { label: 'Registration', value: 5 },
1836
+ { label: 'Tasks', value: 20 },
1837
+ { label: 'Others', value: 9 },
1252
1838
  ],
1253
1839
  transfers: [
1254
- { label: 'Billing', value: 34 },
1255
- { label: 'Clinical', value: 28 },
1256
- { label: 'Insurance', value: 18 },
1257
- { label: 'Emergency', value: 5 },
1840
+ { label: 'Out of Scope', value: 68 },
1841
+ { label: 'Multiple Requests', value: 24 },
1842
+ { label: 'Identity Unresolved', value: 20 },
1843
+ { label: 'Others', value: 23 },
1258
1844
  ],
1259
1845
  peakHours: {
1260
1846
  timezone: 'America/Chicago',
1261
1847
  points: Array.from({ length: 24 }, (_, hour) => ({
1262
1848
  hour,
1263
1849
  label: hour === 0 ? '12 AM' : hour < 12 ? `${hour} AM` : hour === 12 ? '12 PM' : `${hour - 12} PM`,
1264
- count: hour >= 8 && hour <= 17 ? 20 + Math.round(Math.sin((hour - 8) / 4) * 15) : 3 + (hour % 5),
1850
+ count: hour >= 8 && hour <= 20
1851
+ ? Math.round(30 + Math.sin((hour - 8) / 2.2) * 45 + (hour === 11 || hour === 16 ? 25 : 0))
1852
+ : 4 + (hour % 3),
1265
1853
  })),
1266
1854
  },
1267
1855
  kpis: {
1268
- totalCalls: 1247,
1856
+ totalCalls: 1234,
1269
1857
  avgDuration: 186,
1270
- bookingRate: 34,
1271
- transferRate: 14,
1858
+ bookingRate: 89,
1859
+ transferRate: 4,
1272
1860
  },
1273
1861
  };
1274
1862
  const SEED_SETTINGS = {
@@ -1276,26 +1864,50 @@ const SEED_SETTINGS = {
1276
1864
  practiceName: 'Riverside Family Medicine',
1277
1865
  timezone: 'America/Chicago',
1278
1866
  language: 'English',
1279
- agentName: 'Edvak AI',
1280
- welcomeMessage: 'Thank you for calling Riverside Family Medicine. This call may be recorded. How can I help you today?',
1867
+ languages: 'English,Spanish,Chinese,Hindi',
1868
+ agentName: 'Robin',
1869
+ tone: 'professional',
1870
+ welcomeMessage: 'Thanks for calling {{practiceName}}! This is {{agentName}}, the practice’s virtual assistant. How can I help you today?',
1281
1871
  ttsVoiceId: '7EzWGsX10sAS4c9m9cPf',
1282
1872
  },
1283
- phone: { mainNumber: '+1 (512) 555-0100', afterHours: true, ringTimeout: 30 },
1873
+ phone: {
1874
+ answerMode: 'always_first',
1875
+ mainNumber: '+1 (234) 092-0129',
1876
+ transferNumber: '+1 (234) 092-0129',
1877
+ ringTimeout: 20,
1878
+ createFollowUpTask: true,
1879
+ locationIds: 'loc-1,loc-2,loc-3',
1880
+ maxCallDurationMin: 5,
1881
+ silenceTimeoutSec: 30,
1882
+ afterHoursBehavior: 'ai_answers',
1883
+ afterHours: true,
1884
+ },
1284
1885
  tasks: { autoCreate: true, defaultAssignee: 'Front desk' },
1285
1886
  scheduling: { leadTimeHours: 4, allowSameDay: true },
1286
1887
  refill: { autoApproveDays: 7, requireStaffReview: true },
1287
1888
  verify: { requireDob: true, requireName: true, maxAttempts: 3 },
1288
1889
  escalation: { sentimentThreshold: 'frustrated', urgentKeywords: 'emergency, chest pain' },
1289
1890
  outbound: { enabled: true, callerId: '+1 (512) 555-0100' },
1891
+ knowledge: {
1892
+ whatToSay: 'Arrive 15 minutes early for new-patient visits. Bring a photo ID and insurance card. We accept most major plans and self-pay; ask front desk for current cash-pay rates. For medical emergencies, hang up and dial 911.',
1893
+ whatNotToSay: 'Do not give clinical advice, diagnose symptoms, or promise specific appointment times that are not available. Do not share other patients’ information. Do not quote prices that are not listed in practice guidance.',
1894
+ },
1290
1895
  safety: [
1291
1896
  { id: 's1', label: 'HIPAA logging enabled', status: 'ok' },
1292
1897
  { id: 's2', label: 'Recording consent script', status: 'ok' },
1293
1898
  { id: 's3', label: 'Emergency routing tested', status: 'warn' },
1294
1899
  { id: 's4', label: 'PHI redaction in transcripts', status: 'ok' },
1295
1900
  ],
1296
- transcripts: { retainDays: 90, redactPhi: true },
1901
+ transcripts: {
1902
+ recordingRetainDays: 30,
1903
+ transcriptRetainDays: 30,
1904
+ disclosureWarningEnabled: true,
1905
+ disclosureWarningText: 'This call may be handled by an automated assistant and could be recorded for quality and training. You can ask to speak with a staff member at any time.',
1906
+ retainDays: 30,
1907
+ redactPhi: true,
1908
+ },
1297
1909
  advanced: { modelVersion: 'edvak-v2', debugMode: false },
1298
- effectiveWelcomeMessage: 'Thank you for calling Riverside Family Medicine. This call may be recorded. How can I help you today?',
1910
+ effectiveWelcomeMessage: 'Thanks for calling Riverside Family Medicine! This is Robin, the practice’s virtual assistant. How can I help you today?',
1299
1911
  };
1300
1912
  const SEED_SETUP = {
1301
1913
  readiness: 92,
@@ -1423,6 +2035,210 @@ const SEED_OUTBOUND_PURPOSES = [
1423
2035
  },
1424
2036
  ];
1425
2037
 
2038
+ const hour = 60 * 60 * 1000;
2039
+ const now = Date.now();
2040
+ function todayAt(h, m = 0) {
2041
+ const d = new Date();
2042
+ d.setHours(h, m, 0, 0);
2043
+ return d.toISOString();
2044
+ }
2045
+ const SEED_ACTIVITIES = [
2046
+ {
2047
+ id: 'act-appt-1',
2048
+ type: 'appointment_booked',
2049
+ occurredAt: new Date(now - 0.4 * hour).toISOString(),
2050
+ title: 'Appointment booked',
2051
+ detail: '',
2052
+ patientId: 'pat-1001',
2053
+ patientName: 'Sooraj Jagadeesh',
2054
+ patientInitials: 'SJ',
2055
+ patientDob: '2002-06-30',
2056
+ patientAge: 23,
2057
+ patientGender: 'M',
2058
+ providerName: 'Dr. Anika Mehra',
2059
+ providerInitials: 'AM',
2060
+ providerSpecialty: 'Cardiologist',
2061
+ appointmentId: 'appt-9001',
2062
+ callId: 'LC-4801',
2063
+ startAt: todayAt(8, 0),
2064
+ endAt: todayAt(8, 15),
2065
+ durationMinutes: 15,
2066
+ visitType: 'Follow Up',
2067
+ hasInsurance: true,
2068
+ insuranceName: 'Aetna PPO',
2069
+ },
2070
+ {
2071
+ id: 'act-appt-1b',
2072
+ type: 'task_created',
2073
+ occurredAt: new Date(now - 0.35 * hour).toISOString(),
2074
+ title: 'Needs Scheduling Assistance',
2075
+ detail: '',
2076
+ patientId: 'pat-1001',
2077
+ patientName: 'Sooraj Jagadeesh',
2078
+ patientInitials: 'SJ',
2079
+ patientDob: '2002-06-30',
2080
+ patientAge: 23,
2081
+ patientGender: 'M',
2082
+ providerName: 'Front Desk',
2083
+ providerInitials: 'FD',
2084
+ providerSpecialty: 'Operations',
2085
+ taskId: 'task-4801',
2086
+ callId: 'LC-4801',
2087
+ startAt: todayAt(8, 5),
2088
+ visitType: 'Insurance verification',
2089
+ durationMinutes: 10,
2090
+ priority: 'medium',
2091
+ },
2092
+ {
2093
+ id: 'act-appt-2',
2094
+ type: 'appointment_booked',
2095
+ occurredAt: new Date(now - 1.1 * hour).toISOString(),
2096
+ title: 'Appointment booked',
2097
+ detail: '',
2098
+ patientId: 'pat-1005',
2099
+ patientName: 'Lisa Sullivan',
2100
+ patientInitials: 'LS',
2101
+ patientDob: '1998-03-12',
2102
+ patientAge: 28,
2103
+ patientGender: 'F',
2104
+ providerName: 'Dr. Marcus Webb',
2105
+ providerInitials: 'MW',
2106
+ providerSpecialty: 'Endocrinologist',
2107
+ appointmentId: 'appt-9003',
2108
+ callId: 'LC-4800',
2109
+ startAt: todayAt(8, 15),
2110
+ endAt: todayAt(8, 30),
2111
+ durationMinutes: 15,
2112
+ visitType: 'Lab Results Review',
2113
+ hasInsurance: false,
2114
+ },
2115
+ {
2116
+ id: 'act-appt-3',
2117
+ type: 'appointment_booked',
2118
+ occurredAt: new Date(now - 2.2 * hour).toISOString(),
2119
+ title: 'Appointment booked',
2120
+ detail: '',
2121
+ patientId: 'pat-1006',
2122
+ patientName: 'Aiden Rivera',
2123
+ patientInitials: 'AR',
2124
+ patientDob: '1995-11-04',
2125
+ patientAge: 30,
2126
+ patientGender: 'M',
2127
+ providerName: 'Dr. Priya Nair',
2128
+ providerInitials: 'PN',
2129
+ providerSpecialty: 'General Practitioner',
2130
+ appointmentId: 'appt-9004',
2131
+ callId: 'LC-4799',
2132
+ startAt: todayAt(8, 30),
2133
+ endAt: todayAt(8, 45),
2134
+ durationMinutes: 15,
2135
+ visitType: 'Routine Checkup',
2136
+ },
2137
+ {
2138
+ id: 'act-appt-4',
2139
+ type: 'appointment_booked',
2140
+ occurredAt: new Date(now - 8 * hour).toISOString(),
2141
+ title: 'Appointment booked',
2142
+ detail: '',
2143
+ patientId: 'pat-1003',
2144
+ patientName: 'Aisha Patel',
2145
+ patientInitials: 'AP',
2146
+ patientDob: '1988-01-19',
2147
+ patientAge: 38,
2148
+ patientGender: 'F',
2149
+ providerName: 'Dr. Jordan Lee',
2150
+ providerInitials: 'JL',
2151
+ providerSpecialty: 'Internal Medicine',
2152
+ appointmentId: 'appt-9002',
2153
+ callId: 'LC-4790',
2154
+ startAt: todayAt(14, 30),
2155
+ endAt: todayAt(15, 0),
2156
+ durationMinutes: 30,
2157
+ visitType: 'Annual Wellness',
2158
+ hasInsurance: true,
2159
+ insuranceName: 'BlueCross Blue Shield',
2160
+ },
2161
+ {
2162
+ id: 'act-1',
2163
+ type: 'patient_created',
2164
+ occurredAt: new Date(now - 0.5 * hour).toISOString(),
2165
+ title: 'Patient registered',
2166
+ detail: '',
2167
+ patientId: 'pat-1008',
2168
+ patientName: 'Maria Garcia',
2169
+ patientInitials: 'MG',
2170
+ patientDob: '1991-04-22',
2171
+ patientAge: 35,
2172
+ patientGender: 'F',
2173
+ callId: 'LC-4801',
2174
+ startAt: todayAt(9, 12),
2175
+ visitType: 'New registration',
2176
+ hasInsurance: true,
2177
+ insuranceName: 'UnitedHealthcare',
2178
+ },
2179
+ {
2180
+ id: 'act-5',
2181
+ type: 'patient_created',
2182
+ occurredAt: new Date(now - 26 * hour).toISOString(),
2183
+ title: 'Patient registered',
2184
+ detail: '',
2185
+ patientId: 'pat-1004',
2186
+ patientName: 'Sam Okonkwo',
2187
+ patientInitials: 'SO',
2188
+ patientDob: '1979-09-08',
2189
+ patientAge: 46,
2190
+ patientGender: 'M',
2191
+ callId: 'LC-4782',
2192
+ startAt: new Date(now - 26 * hour).toISOString(),
2193
+ visitType: 'Outbound registration',
2194
+ hasInsurance: false,
2195
+ },
2196
+ {
2197
+ id: 'act-3',
2198
+ type: 'task_created',
2199
+ occurredAt: new Date(now - 3 * hour).toISOString(),
2200
+ title: 'Needs Scheduling Assistance',
2201
+ detail: '',
2202
+ patientId: 'pat-1002',
2203
+ patientName: 'James Chen',
2204
+ patientInitials: 'JC',
2205
+ patientDob: '1985-12-01',
2206
+ patientAge: 40,
2207
+ patientGender: 'M',
2208
+ providerName: 'Front Desk',
2209
+ providerInitials: 'FD',
2210
+ providerSpecialty: 'Operations',
2211
+ taskId: 'task-301',
2212
+ callId: 'LC-4798',
2213
+ startAt: new Date(now - 3 * hour).toISOString(),
2214
+ visitType: 'Insurance verification',
2215
+ durationMinutes: 15,
2216
+ priority: 'medium',
2217
+ },
2218
+ {
2219
+ id: 'act-6',
2220
+ type: 'task_created',
2221
+ occurredAt: new Date(now - 5 * hour).toISOString(),
2222
+ title: 'Needs Scheduling Assistance',
2223
+ detail: '',
2224
+ patientId: 'pat-1007',
2225
+ patientName: 'Elena Foster',
2226
+ patientInitials: 'EF',
2227
+ patientDob: '1993-07-15',
2228
+ patientAge: 32,
2229
+ patientGender: 'F',
2230
+ providerName: 'Nurse Station',
2231
+ providerInitials: 'NS',
2232
+ providerSpecialty: 'Clinical support',
2233
+ taskId: 'task-302',
2234
+ callId: 'LC-4795',
2235
+ startAt: new Date(now - 5 * hour).toISOString(),
2236
+ visitType: 'Clinical callback',
2237
+ durationMinutes: 10,
2238
+ priority: 'medium',
2239
+ },
2240
+ ];
2241
+
1426
2242
  const MOCK_DELAY_MS = 200;
1427
2243
  class AiReceptionistApiMockService {
1428
2244
  constructor() {
@@ -1560,17 +2376,58 @@ class AiReceptionistApiMockService {
1560
2376
  });
1561
2377
  }
1562
2378
  postTakeover(id) {
1563
- this.patchLive(id, (c) => ({ ...c, status: 'takeover', assigned: 'You' }));
2379
+ return this.postMediaJoin(id, 'takeover').pipe(map(() => ({ data: { success: true } })));
2380
+ }
2381
+ postMediaJoin(id, mode) {
2382
+ this.patchLive(id, (c) => ({
2383
+ ...c,
2384
+ status: mode === 'takeover' ? 'takeover' : c.status,
2385
+ controlMode: mode,
2386
+ assigned: mode === 'takeover' ? 'You' : c.assigned,
2387
+ aiPaused: mode === 'takeover',
2388
+ }));
2389
+ // Mock tokens are placeholders — StaffMediaService is skipped when useMockApi is on.
2390
+ return this.mock({
2391
+ ok: true,
2392
+ callId: id,
2393
+ mode,
2394
+ provider: 'livekit',
2395
+ livekitUrl: 'wss://mock.livekit.local',
2396
+ clientToken: `mock-token-${mode}-${id}`,
2397
+ roomName: `call-${id}`,
2398
+ controlMode: mode,
2399
+ aiPaused: mode === 'takeover',
2400
+ assigned: mode === 'takeover' ? 'You' : null,
2401
+ });
2402
+ }
2403
+ postMediaLeave(id) {
2404
+ this.patchLive(id, (c) => ({
2405
+ ...c,
2406
+ status: 'ai',
2407
+ controlMode: 'ai',
2408
+ assigned: null,
2409
+ aiPaused: false,
2410
+ }));
1564
2411
  return this.mock({ success: true });
1565
2412
  }
1566
2413
  postResumeAi(id) {
1567
- this.patchLive(id, (c) => ({ ...c, status: 'ai', assigned: null, aiPaused: false }));
1568
- return this.mock({ success: true });
2414
+ return this.postMediaLeave(id);
1569
2415
  }
1570
2416
  postPause(id) {
1571
- this.patchLive(id, (c) => ({ ...c, aiPaused: true }));
2417
+ this.patchLive(id, (c) => ({ ...c, controlMode: 'paused', aiPaused: true }));
2418
+ return this.mock({ success: true });
2419
+ }
2420
+ postResume(id) {
2421
+ this.patchLive(id, (c) => ({
2422
+ ...c,
2423
+ controlMode: c.status === 'takeover' || c.assigned ? 'takeover' : 'ai',
2424
+ aiPaused: false,
2425
+ }));
1572
2426
  return this.mock({ success: true });
1573
2427
  }
2428
+ postListen(id) {
2429
+ return this.postMediaJoin(id, 'listen').pipe(map(() => ({ data: { success: true } })));
2430
+ }
1574
2431
  postTransfer(id) {
1575
2432
  this.patchLive(id, (c) => ({ ...c, status: 'transferring' }));
1576
2433
  return this.mock({ success: true });
@@ -1661,6 +2518,15 @@ class AiReceptionistApiMockService {
1661
2518
  ]);
1662
2519
  return of(new Blob([silent], { type: 'audio/wav' })).pipe(delay(MOCK_DELAY_MS));
1663
2520
  }
2521
+ testKnowledgeAnswer(params) {
2522
+ const answer = `Based on our guidance: for “${params.question.trim()}”, ` +
2523
+ `I'd share what's in our practice knowledge and avoid restricted topics.`;
2524
+ return this.previewVoice(params.voiceId ?? 'mock').pipe(map((audioBlob) => ({
2525
+ answer,
2526
+ voiceId: params.voiceId ?? 'mock',
2527
+ audioBlob,
2528
+ })));
2529
+ }
1664
2530
  withEffectiveWelcome(settings) {
1665
2531
  return {
1666
2532
  ...settings,
@@ -1679,6 +2545,29 @@ class AiReceptionistApiMockService {
1679
2545
  rows = rows.filter((e) => e.actor.toLowerCase().includes(actor.toLowerCase()));
1680
2546
  return this.paginate(rows, opts?.page, opts?.limit);
1681
2547
  }
2548
+ getActivities(filters) {
2549
+ let rows = structuredClone(SEED_ACTIVITIES);
2550
+ const types = (filters?.types ?? '')
2551
+ .split(',')
2552
+ .map((t) => t.trim())
2553
+ .filter(Boolean);
2554
+ if (types.length) {
2555
+ rows = rows.filter((a) => types.includes(a.type));
2556
+ }
2557
+ if (filters?.callId?.trim()) {
2558
+ const callId = filters.callId.trim().toLowerCase();
2559
+ rows = rows.filter((a) => (a.callId || '').toLowerCase() === callId);
2560
+ }
2561
+ if (filters?.search?.trim()) {
2562
+ const needle = filters.search.trim().toLowerCase();
2563
+ rows = rows.filter((a) => [a.title, a.detail, a.patientName, a.callId, a.type]
2564
+ .filter(Boolean)
2565
+ .join(' ')
2566
+ .toLowerCase()
2567
+ .includes(needle));
2568
+ }
2569
+ return this.paginate(rows, filters?.page, filters?.limit);
2570
+ }
1682
2571
  getSetup() {
1683
2572
  return this.mock(SEED_SETUP);
1684
2573
  }
@@ -1834,12 +2723,78 @@ class AiReceptionistApiMockService {
1834
2723
  return of({ success: true }).pipe(delay(MOCK_DELAY_MS));
1835
2724
  }
1836
2725
  placeOutboundCall(body) {
2726
+ const idHint = body.appointmentId
2727
+ ? ` (appt ${body.appointmentId})`
2728
+ : body.patientId
2729
+ ? ` (patient ${body.patientId})`
2730
+ : '';
1837
2731
  return of({
1838
2732
  success: true,
1839
2733
  callId: `LC-${Math.floor(4800 + Math.random() * 100)}`,
1840
- message: `Outbound call started to ${body.phone}`,
2734
+ message: `Outbound call started to ${body.phone}${idHint}`,
1841
2735
  }).pipe(delay(MOCK_DELAY_MS));
1842
2736
  }
2737
+ searchPatientsForOutbound(q, limit = 10) {
2738
+ const needle = q.trim().toLowerCase();
2739
+ const digits = q.replace(/\D/g, '');
2740
+ const hits = [
2741
+ {
2742
+ patientId: 'pat-1001',
2743
+ firstName: 'Maria',
2744
+ lastName: 'Garcia',
2745
+ fullName: 'Maria Garcia',
2746
+ phone: '+14155550100',
2747
+ dob: '1988-04-12',
2748
+ },
2749
+ {
2750
+ patientId: 'pat-1002',
2751
+ firstName: 'James',
2752
+ lastName: 'Chen',
2753
+ fullName: 'James Chen',
2754
+ phone: '+14155550122',
2755
+ dob: '1975-11-03',
2756
+ },
2757
+ {
2758
+ patientId: 'pat-1003',
2759
+ firstName: 'Aisha',
2760
+ lastName: 'Patel',
2761
+ fullName: 'Aisha Patel',
2762
+ phone: '+14155550987',
2763
+ dob: '1992-07-21',
2764
+ },
2765
+ ].filter((p) => {
2766
+ if (!needle)
2767
+ return false;
2768
+ const hay = `${p.fullName} ${p.phone} ${p.patientId}`.toLowerCase();
2769
+ return hay.includes(needle) || (!!digits && (p.phone ?? '').includes(digits));
2770
+ });
2771
+ return of(hits.slice(0, limit)).pipe(delay(MOCK_DELAY_MS));
2772
+ }
2773
+ listPatientAppointmentsForOutbound(patientId, limit = 20) {
2774
+ const now = Date.now();
2775
+ const day = 24 * 60 * 60 * 1000;
2776
+ const all = [
2777
+ {
2778
+ appointmentId: `appt-${patientId}-1`,
2779
+ startTime: new Date(now + 2 * day).toISOString(),
2780
+ endTime: new Date(now + 2 * day + 30 * 60 * 1000).toISOString(),
2781
+ providerName: 'Dr. Patel',
2782
+ locationName: 'Main Clinic',
2783
+ status: 'scheduled',
2784
+ visitType: 'Follow-up',
2785
+ },
2786
+ {
2787
+ appointmentId: `appt-${patientId}-2`,
2788
+ startTime: new Date(now + 9 * day).toISOString(),
2789
+ endTime: new Date(now + 9 * day + 45 * 60 * 1000).toISOString(),
2790
+ providerName: 'Dr. Nguyen',
2791
+ locationName: 'West Wing',
2792
+ status: 'confirmed',
2793
+ visitType: 'Annual wellness',
2794
+ },
2795
+ ];
2796
+ return of(all.slice(0, limit)).pipe(delay(MOCK_DELAY_MS));
2797
+ }
1843
2798
  patchLive(id, fn) {
1844
2799
  this.liveCalls.update((calls) => calls.map((c) => (c.id === id ? fn(structuredClone(c)) : c)));
1845
2800
  }
@@ -2025,10 +2980,49 @@ class AiReceptionistApiService {
2025
2980
  status: err.status,
2026
2981
  }))));
2027
2982
  }
2983
+ /**
2984
+ * Staff softphone join into LiveKit room `call-{callId}`.
2985
+ * POST /calls/:id/media/join { mode: listen|takeover }
2986
+ */
2987
+ postMediaJoin(id, mode) {
2988
+ if (this.useMock)
2989
+ return this.mock.postMediaJoin(id, mode);
2990
+ const enc = encodeURIComponent(id);
2991
+ return this.http
2992
+ .post(`${this.arBase}/calls/${enc}/media/join`, { mode })
2993
+ .pipe(map((body) => {
2994
+ const data = mapStaffMediaJoinResult(body);
2995
+ if (!data) {
2996
+ throw new Error('media/join response missing livekitUrl or clientToken');
2997
+ }
2998
+ return { data: { ...data, callId: data.callId || id, mode } };
2999
+ }), catchError((err) => throwError(() => new Error(this.apiErrorMessage(err, 'Failed to join call media')))));
3000
+ }
3001
+ /** Drop staff WebRTC only; does not hang up the patient. */
3002
+ postMediaLeave(id) {
3003
+ if (this.useMock)
3004
+ return this.mock.postMediaLeave(id);
3005
+ const enc = encodeURIComponent(id);
3006
+ return this.http
3007
+ .post(`${this.arBase}/calls/${enc}/media/leave`, {})
3008
+ .pipe(map(() => ({ data: { success: true } })));
3009
+ }
3010
+ /**
3011
+ * Take over — same join payload as media/join { mode: takeover }.
3012
+ * Prefer this or postMediaJoin('takeover'); both are supported by BE.
3013
+ */
2028
3014
  postTakeover(id) {
2029
3015
  if (this.useMock)
2030
- return this.mock.postTakeover(id);
2031
- return this.postCallAction(id, 'takeover');
3016
+ return this.mock.postMediaJoin(id, 'takeover');
3017
+ const enc = encodeURIComponent(id);
3018
+ return this.http.post(`${this.arBase}/calls/${enc}/takeover`, {}).pipe(map((body) => {
3019
+ const data = mapStaffMediaJoinResult(body);
3020
+ if (!data) {
3021
+ throw new Error('takeover response missing livekitUrl or clientToken');
3022
+ }
3023
+ const mode = 'takeover';
3024
+ return { data: { ...data, callId: data.callId || id, mode } };
3025
+ }), catchError((err) => throwError(() => new Error(this.apiErrorMessage(err, 'Failed to take over call')))));
2032
3026
  }
2033
3027
  postResumeAi(id) {
2034
3028
  if (this.useMock)
@@ -2040,6 +3034,25 @@ class AiReceptionistApiService {
2040
3034
  return this.mock.postPause(id);
2041
3035
  return this.postCallAction(id, 'pause');
2042
3036
  }
3037
+ postResume(id) {
3038
+ if (this.useMock)
3039
+ return this.mock.postResume(id);
3040
+ return this.postCallAction(id, 'resume');
3041
+ }
3042
+ /** Listen — same join payload as media/join { mode: listen }. */
3043
+ postListen(id) {
3044
+ if (this.useMock)
3045
+ return this.mock.postMediaJoin(id, 'listen');
3046
+ const enc = encodeURIComponent(id);
3047
+ return this.http.post(`${this.arBase}/calls/${enc}/listen`, {}).pipe(map((body) => {
3048
+ const data = mapStaffMediaJoinResult(body);
3049
+ if (!data) {
3050
+ throw new Error('listen response missing livekitUrl or clientToken');
3051
+ }
3052
+ const mode = 'listen';
3053
+ return { data: { ...data, callId: data.callId || id, mode } };
3054
+ }), catchError((err) => throwError(() => new Error(this.apiErrorMessage(err, 'Failed to listen to call')))));
3055
+ }
2043
3056
  postTransfer(id) {
2044
3057
  if (this.useMock)
2045
3058
  return this.mock.postTransfer(id);
@@ -2135,7 +3148,7 @@ class AiReceptionistApiService {
2135
3148
  return this.mock.putSettings(next);
2136
3149
  return this.http
2137
3150
  .put(`${this.arBase}/settings`, { settings: mapSettingsToRequest(next) })
2138
- .pipe(switchMap(() => this.getSettings()));
3151
+ .pipe(switchMap(() => this.getSettings()), catchError((err) => throwError(() => new Error(this.apiErrorMessage(err, 'Failed to save agent settings')))));
2139
3152
  }
2140
3153
  /** ElevenLabs spoken sample for Agent Settings voice picker. */
2141
3154
  previewVoice(voiceId) {
@@ -2145,6 +3158,34 @@ class AiReceptionistApiService {
2145
3158
  responseType: 'blob',
2146
3159
  });
2147
3160
  }
3161
+ /**
3162
+ * Knowledge Base test: draft an answer from whatToSay / whatNotToSay and
3163
+ * return spoken audio in the practice (or selected) voice.
3164
+ */
3165
+ testKnowledgeAnswer(params) {
3166
+ if (this.useMock) {
3167
+ return this.mock.testKnowledgeAnswer(params);
3168
+ }
3169
+ return this.http
3170
+ .post(`${this.arBase}/settings/test-knowledge`, params)
3171
+ .pipe(map((body) => {
3172
+ const answer = String(body?.answer ?? '').trim();
3173
+ const b64 = String(body?.audioBase64 ?? '').trim();
3174
+ if (!answer || !b64) {
3175
+ throw new Error('Test response missing answer or audio');
3176
+ }
3177
+ const contentType = String(body?.contentType ?? 'audio/mpeg');
3178
+ const binary = atob(b64);
3179
+ const bytes = new Uint8Array(binary.length);
3180
+ for (let i = 0; i < binary.length; i++)
3181
+ bytes[i] = binary.charCodeAt(i);
3182
+ return {
3183
+ answer,
3184
+ voiceId: String(body?.voiceId ?? params.voiceId ?? ''),
3185
+ audioBlob: new Blob([bytes], { type: contentType }),
3186
+ };
3187
+ }), catchError((err) => throwError(() => new Error(this.apiErrorMessage(err, 'Could not generate a test answer.')))));
3188
+ }
2148
3189
  getAudit(q, opts) {
2149
3190
  if (this.useMock)
2150
3191
  return this.mock.getAudit(q, undefined, opts);
@@ -2161,6 +3202,34 @@ class AiReceptionistApiService {
2161
3202
  };
2162
3203
  }));
2163
3204
  }
3205
+ /**
3206
+ * Agent activity feed: patients created, appointments booked, tasks from AI.
3207
+ * GET /api/ai-receptionist/activities
3208
+ */
3209
+ getActivities(filters) {
3210
+ if (this.useMock)
3211
+ return this.mock.getActivities(filters);
3212
+ const page = filters?.page ?? 1;
3213
+ const limit = filters?.limit ?? DEFAULT_PAGE_SIZE;
3214
+ let params = new HttpParams().set('page', String(page)).set('limit', String(limit));
3215
+ if (filters?.search?.trim())
3216
+ params = params.set('search', filters.search.trim());
3217
+ if (filters?.types?.trim())
3218
+ params = params.set('types', filters.types.trim());
3219
+ if (filters?.from?.trim())
3220
+ params = params.set('from', filters.from.trim());
3221
+ if (filters?.to?.trim())
3222
+ params = params.set('to', filters.to.trim());
3223
+ if (filters?.callId?.trim())
3224
+ params = params.set('callId', filters.callId.trim());
3225
+ return this.http.get(`${this.arBase}/activities`, { params }).pipe(map((body) => {
3226
+ const pageResult = asPaginatedResult(body, page, limit);
3227
+ return {
3228
+ ...pageResult,
3229
+ items: pageResult.items.map((row, i) => mapAgentActivity(row, i)),
3230
+ };
3231
+ }));
3232
+ }
2164
3233
  getSetup() {
2165
3234
  if (this.useMock)
2166
3235
  return this.mock.getSetup();
@@ -2277,6 +3346,62 @@ class AiReceptionistApiService {
2277
3346
  return throwError(() => ({ message: detail || 'Failed to place outbound call.' }));
2278
3347
  }));
2279
3348
  }
3349
+ /**
3350
+ * Patient typeahead for outbound dialer.
3351
+ * Tries `/api/v1/patients/search`, then `/api/v1/patients?q=`, then by-phone for digit queries.
3352
+ */
3353
+ searchPatientsForOutbound(q, limit = 10) {
3354
+ const query = q.trim();
3355
+ if (!query)
3356
+ return of([]);
3357
+ if (this.useMock)
3358
+ return this.mock.searchPatientsForOutbound(query, limit);
3359
+ const params = new HttpParams().set('q', query).set('limit', String(limit));
3360
+ const searchUrl = this.paths.v1Path('/patients/search');
3361
+ const listUrl = this.paths.v1Path('/patients');
3362
+ return this.http.get(searchUrl, { params }).pipe(map((body) => this.mapPatientHits(body)), catchError(() => this.http.get(listUrl, { params }).pipe(map((body) => this.mapPatientHits(body)), catchError(() => of([])))), switchMap((hits) => {
3363
+ if (hits.length > 0)
3364
+ return of(hits);
3365
+ const phone = toE164Phone(query);
3366
+ if (!phone && !/\d{7,}/.test(query))
3367
+ return of([]);
3368
+ return this.http
3369
+ .get(this.paths.v1Path('/patients/by-phone'), {
3370
+ params: new HttpParams().set('phone', phone || query.replace(/\D/g, '')),
3371
+ })
3372
+ .pipe(map((body) => this.mapPatientHits(body)), catchError(() => of([])));
3373
+ }));
3374
+ }
3375
+ /** Upcoming appointments for a patient (dialer picker). */
3376
+ listPatientAppointmentsForOutbound(patientId, limit = 20) {
3377
+ const id = patientId.trim();
3378
+ if (!id)
3379
+ return of([]);
3380
+ if (this.useMock)
3381
+ return this.mock.listPatientAppointmentsForOutbound(id, limit);
3382
+ const from = new Date();
3383
+ from.setHours(0, 0, 0, 0);
3384
+ const params = new HttpParams()
3385
+ .set('patientId', id)
3386
+ .set('patient_id', id)
3387
+ .set('from', from.toISOString())
3388
+ .set('limit', String(limit));
3389
+ const v1Url = this.paths.v1Path('/appointments');
3390
+ const apiUrl = this.paths.apiPath('/appointments');
3391
+ // Prefer /api/appointments (current host contract), then /api/v1/appointments.
3392
+ return this.http.get(apiUrl, { params }).pipe(map((body) => this.mapAppointmentHits(body)), catchError(() => this.http.get(v1Url, { params }).pipe(map((body) => this.mapAppointmentHits(body)), catchError(() => of([])))));
3393
+ }
3394
+ mapPatientHits(body) {
3395
+ return extractApiRows(body)
3396
+ .map(mapOutboundPatientHit)
3397
+ .filter((p) => !!p);
3398
+ }
3399
+ mapAppointmentHits(body) {
3400
+ return extractApiRows(body)
3401
+ .map(mapOutboundAppointmentHit)
3402
+ .filter((a) => !!a)
3403
+ .sort((a, b) => String(a.startTime ?? '').localeCompare(String(b.startTime ?? '')));
3404
+ }
2280
3405
  postCallAction(id, action) {
2281
3406
  const enc = encodeURIComponent(id);
2282
3407
  return this.http
@@ -2300,416 +3425,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
2300
3425
  args: [{ providedIn: 'root' }]
2301
3426
  }] });
2302
3427
 
2303
- /**
2304
- * Shared source of truth for Ava online/offline across topbar + module nav.
2305
- * Always re-fetches GET /status after mutations.
2306
- */
2307
- class AgentOnlineService {
2308
- constructor() {
2309
- this.api = inject(AiReceptionistApiService);
2310
- /** true = online, false = offline, null = unknown / not loaded */
2311
- this._online = signal(null);
2312
- this._loading = signal(false);
2313
- this._toggling = signal(false);
2314
- this._error = signal(null);
2315
- this._readiness = signal(null);
2316
- this.online = this._online.asReadonly();
2317
- this.loading = this._loading.asReadonly();
2318
- this.toggling = this._toggling.asReadonly();
2319
- this.error = this._error.asReadonly();
2320
- this.readiness = this._readiness.asReadonly();
2321
- }
2322
- refresh() {
2323
- this._loading.set(true);
2324
- this._error.set(null);
2325
- this.api.getStatus().subscribe({
2326
- next: ({ data }) => {
2327
- this._online.set(data.agentOn);
2328
- this._readiness.set(data.readiness);
2329
- this._loading.set(false);
2330
- },
2331
- error: (err) => {
2332
- this._online.set(null);
2333
- this._error.set(err.message ?? 'Could not load agent status.');
2334
- this._loading.set(false);
2335
- },
2336
- });
2337
- }
2338
- /**
2339
- * PUT /agent-online then re-fetch GET /status.
2340
- */
2341
- setOnline(enabled) {
2342
- this._toggling.set(true);
2343
- this._error.set(null);
2344
- return this.api.setAgentOnline(enabled).pipe(switchMap(() => this.api.getStatus().pipe(catchError((err) => {
2345
- this._error.set(err.message ?? 'Status refresh failed after toggle.');
2346
- return of(null);
2347
- }))), tap((result) => {
2348
- if (result) {
2349
- this._online.set(result.data.agentOn);
2350
- this._readiness.set(result.data.readiness);
2351
- }
2352
- else {
2353
- // Toggle likely succeeded; use requested value until refresh works.
2354
- this._online.set(enabled);
2355
- }
2356
- }), catchError((err) => {
2357
- this._error.set(err.message ?? 'Failed to update agent online status.');
2358
- return throwError(() => err);
2359
- }), finalize(() => this._toggling.set(false)));
2360
- }
2361
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AgentOnlineService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2362
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AgentOnlineService, providedIn: 'root' }); }
2363
- }
2364
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AgentOnlineService, decorators: [{
2365
- type: Injectable,
2366
- args: [{ providedIn: 'root' }]
2367
- }] });
2368
-
2369
- class EdsIconComponent {
2370
- constructor() {
2371
- this.name = input.required();
2372
- this.size = input(20);
2373
- this.fill = input(0);
2374
- this.weight = input(400);
2375
- this.variation = computed(() => `'FILL' ${this.fill()}, 'wght' ${this.weight()}, 'GRAD' 0, 'opsz' ${this.size()}`);
2376
- }
2377
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2378
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.17", type: EdsIconComponent, isStandalone: true, selector: "eds-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null }, weight: { classPropertyName: "weight", publicName: "weight", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `<span class="eds-icon" [style.font-size.px]="size()" [style.font-variation-settings]="variation()">{{ name() }}</span>`, isInline: true, styles: [":host{display:inline-flex;line-height:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2379
- }
2380
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsIconComponent, decorators: [{
2381
- type: Component,
2382
- args: [{ selector: 'eds-icon', standalone: true, template: `<span class="eds-icon" [style.font-size.px]="size()" [style.font-variation-settings]="variation()">{{ name() }}</span>`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:inline-flex;line-height:0}\n"] }]
2383
- }] });
2384
-
2385
- class EdsPulseDotComponent {
2386
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsPulseDotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2387
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: EdsPulseDotComponent, isStandalone: true, selector: "eds-pulse-dot", ngImport: i0, template: `<span class="eds-pulse-dot"><span class="eds-pulse-dot__core"></span></span>`, isInline: true, styles: [".eds-pulse-dot{position:relative;display:inline-flex;width:8px;height:8px}.eds-pulse-dot__core{width:8px;height:8px;border-radius:50%;background:var(--eds-green-500)}.eds-pulse-dot__core:after{content:\"\";position:absolute;inset:0;border-radius:50%;background:var(--eds-green-500);animation:eds-ping 1.4s cubic-bezier(0,0,.2,1) infinite}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2388
- }
2389
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsPulseDotComponent, decorators: [{
2390
- type: Component,
2391
- args: [{ selector: 'eds-pulse-dot', standalone: true, template: `<span class="eds-pulse-dot"><span class="eds-pulse-dot__core"></span></span>`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".eds-pulse-dot{position:relative;display:inline-flex;width:8px;height:8px}.eds-pulse-dot__core{width:8px;height:8px;border-radius:50%;background:var(--eds-green-500)}.eds-pulse-dot__core:after{content:\"\";position:absolute;inset:0;border-radius:50%;background:var(--eds-green-500);animation:eds-ping 1.4s cubic-bezier(0,0,.2,1) infinite}\n"] }]
2392
- }] });
2393
-
2394
- class EdsModuleNavComponent {
2395
- constructor() {
2396
- this.config = inject(VOICE_RECEPTIONIST_CONFIG);
2397
- this.session = inject(VOICE_RECEPTIONIST_SESSION);
2398
- this.agentOnline = inject(AgentOnlineService);
2399
- this.agentOn = this.agentOnline.online;
2400
- this.includePracticeNav = !!this.config.includePracticeNav;
2401
- this.receptionistNav = [
2402
- { id: 'dashboard', label: 'Dashboard', icon: 'space_dashboard', route: 'dashboard' },
2403
- { id: 'live', label: 'Live Calls', icon: 'graphic_eq', route: 'live', pulse: true },
2404
- { id: 'history', label: 'Call History', icon: 'history', route: 'history' },
2405
- { id: 'analytics', label: 'Analytics', icon: 'monitoring', route: 'analytics' },
2406
- { id: 'knowledge', label: 'Knowledge Base', icon: 'menu_book', route: 'knowledge' },
2407
- { id: 'settings', label: 'Agent Settings', icon: 'tune', route: 'settings' },
2408
- { id: 'audit', label: 'Audit Log', icon: 'verified', route: 'audit' },
2409
- ];
2410
- this.practiceNav = [
2411
- { id: 'patients', label: 'Patients', icon: 'groups', route: 'patients' },
2412
- { id: 'providers', label: 'Providers', icon: 'medical_services', route: 'providers' },
2413
- { id: 'appointments', label: 'Appointments', icon: 'calendar_month', route: 'appointments' },
2414
- { id: 'admin', label: 'Admin', icon: 'admin_panel_settings', route: 'admin', adminOnly: true },
2415
- ];
2416
- if (this.agentOnline.online() === null && !this.agentOnline.loading()) {
2417
- this.agentOnline.refresh();
2418
- }
2419
- }
2420
- showNavItem(item) {
2421
- return !item.adminOnly || !!this.session?.isAdmin();
2422
- }
2423
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsModuleNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2424
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: EdsModuleNavComponent, isStandalone: true, selector: "eds-module-nav", ngImport: i0, template: "<nav class=\"eds-modnav\" aria-label=\"AI Receptionist\">\n <div class=\"eds-modnav-header\">\n <span class=\"eds-modnav-icon eds-gradient-brand\">\n <eds-icon name=\"graphic_eq\" [size]=\"18\" [fill]=\"1\" />\n </span>\n <div class=\"eds-modnav-titles\">\n <span class=\"eds-modnav-name\">AI Receptionist</span>\n <span class=\"eds-modnav-status\">\n @if (agentOn() === true) {\n <eds-pulse-dot />\n Answering calls\n } @else if (agentOn() === false) {\n Offline \u2014 front desk\n } @else {\n Status unknown\n }\n </span>\n </div>\n </div>\n\n <div class=\"eds-modnav-links\">\n @for (item of receptionistNav; track item.id) {\n <a\n [routerLink]=\"item.route\"\n routerLinkActive=\"active\"\n [routerLinkActiveOptions]=\"{ exact: item.route === 'dashboard' }\"\n >\n <eds-icon [name]=\"item.icon\" [size]=\"18\" />\n <span class=\"eds-modnav-label\">{{ item.label }}</span>\n @if (item.pulse) {\n <eds-pulse-dot class=\"eds-modnav-pulse\" />\n }\n @if (item.badge) {\n <span class=\"eds-modnav-badge\">{{ item.badge }}</span>\n }\n </a>\n }\n </div>\n\n @if (includePracticeNav) {\n <p class=\"eds-modnav-section\">Practice</p>\n <div class=\"eds-modnav-links eds-modnav-links--practice\">\n @for (item of practiceNav; track item.id) {\n @if (showNavItem(item)) {\n <a [routerLink]=\"item.route\" routerLinkActive=\"active\">\n <eds-icon [name]=\"item.icon\" [size]=\"18\" />\n <span class=\"eds-modnav-label\">{{ item.label }}</span>\n </a>\n }\n }\n </div>\n }\n</nav>\n", styles: [":host{display:block;flex-shrink:0}.eds-modnav{width:var(--eds-nav-width);min-height:100vh;background:var(--eds-surface-l0);border-right:1px solid var(--eds-border-default);display:flex;flex-direction:column;padding:16px 12px;position:sticky;top:0;height:100vh;overflow-y:auto}.eds-modnav-header{display:flex;align-items:center;gap:10px;padding:4px 8px 16px;border-bottom:1px solid var(--eds-border-default);margin-bottom:12px}.eds-modnav-icon{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:var(--eds-radius-md);color:#fff}.eds-modnav-titles{display:flex;flex-direction:column;gap:2px;min-width:0}.eds-modnav-name{font-family:var(--eds-font-inter);font-size:14px;font-weight:600;color:var(--eds-text-primary)}.eds-modnav-status{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:var(--eds-green-500);font-weight:500}.eds-modnav-links{display:flex;flex-direction:column;gap:2px}.eds-modnav-links a{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:var(--eds-radius-md);text-decoration:none;color:var(--eds-text-secondary);font-size:13px;font-weight:500;position:relative}.eds-modnav-links a:hover{background:var(--eds-surface-l1);color:var(--eds-text-primary)}.eds-modnav-links a.active{background:#3681f41a;color:var(--eds-brand-600)}.eds-modnav-links--practice{margin-bottom:12px}.eds-modnav-section{margin:14px 10px 6px;font-family:var(--eds-font-inter);font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--eds-gray-500)}.eds-modnav-label{flex:1;min-width:0}.eds-modnav-pulse{margin-left:auto}.eds-modnav-badge{margin-left:auto;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:var(--eds-brand-600);color:#fff;font-size:10px;font-weight:700;display:inline-flex;align-items:center;justify-content:center}.eds-readiness-card{margin-top:auto;padding:12px;border-radius:var(--eds-radius-lg);border:1px solid var(--eds-border-default);background:var(--eds-surface-l1);text-decoration:none;color:inherit;transition:box-shadow .15s ease}.eds-readiness-card:hover{box-shadow:var(--eds-shadow-md)}.eds-readiness-top{display:flex;justify-content:space-between;margin-bottom:8px}.eds-readiness-bar{height:6px;background:var(--eds-gray-200);border-radius:999px;overflow:hidden}.eds-readiness-bar span{display:block;height:100%;background:linear-gradient(90deg,var(--eds-brand-500),var(--eds-violet-500));border-radius:999px}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }, { kind: "component", type: EdsPulseDotComponent, selector: "eds-pulse-dot" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2425
- }
2426
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsModuleNavComponent, decorators: [{
2427
- type: Component,
2428
- args: [{ selector: 'eds-module-nav', imports: [RouterLink, RouterLinkActive, EdsIconComponent, EdsPulseDotComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"eds-modnav\" aria-label=\"AI Receptionist\">\n <div class=\"eds-modnav-header\">\n <span class=\"eds-modnav-icon eds-gradient-brand\">\n <eds-icon name=\"graphic_eq\" [size]=\"18\" [fill]=\"1\" />\n </span>\n <div class=\"eds-modnav-titles\">\n <span class=\"eds-modnav-name\">AI Receptionist</span>\n <span class=\"eds-modnav-status\">\n @if (agentOn() === true) {\n <eds-pulse-dot />\n Answering calls\n } @else if (agentOn() === false) {\n Offline \u2014 front desk\n } @else {\n Status unknown\n }\n </span>\n </div>\n </div>\n\n <div class=\"eds-modnav-links\">\n @for (item of receptionistNav; track item.id) {\n <a\n [routerLink]=\"item.route\"\n routerLinkActive=\"active\"\n [routerLinkActiveOptions]=\"{ exact: item.route === 'dashboard' }\"\n >\n <eds-icon [name]=\"item.icon\" [size]=\"18\" />\n <span class=\"eds-modnav-label\">{{ item.label }}</span>\n @if (item.pulse) {\n <eds-pulse-dot class=\"eds-modnav-pulse\" />\n }\n @if (item.badge) {\n <span class=\"eds-modnav-badge\">{{ item.badge }}</span>\n }\n </a>\n }\n </div>\n\n @if (includePracticeNav) {\n <p class=\"eds-modnav-section\">Practice</p>\n <div class=\"eds-modnav-links eds-modnav-links--practice\">\n @for (item of practiceNav; track item.id) {\n @if (showNavItem(item)) {\n <a [routerLink]=\"item.route\" routerLinkActive=\"active\">\n <eds-icon [name]=\"item.icon\" [size]=\"18\" />\n <span class=\"eds-modnav-label\">{{ item.label }}</span>\n </a>\n }\n }\n </div>\n }\n</nav>\n", styles: [":host{display:block;flex-shrink:0}.eds-modnav{width:var(--eds-nav-width);min-height:100vh;background:var(--eds-surface-l0);border-right:1px solid var(--eds-border-default);display:flex;flex-direction:column;padding:16px 12px;position:sticky;top:0;height:100vh;overflow-y:auto}.eds-modnav-header{display:flex;align-items:center;gap:10px;padding:4px 8px 16px;border-bottom:1px solid var(--eds-border-default);margin-bottom:12px}.eds-modnav-icon{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:var(--eds-radius-md);color:#fff}.eds-modnav-titles{display:flex;flex-direction:column;gap:2px;min-width:0}.eds-modnav-name{font-family:var(--eds-font-inter);font-size:14px;font-weight:600;color:var(--eds-text-primary)}.eds-modnav-status{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:var(--eds-green-500);font-weight:500}.eds-modnav-links{display:flex;flex-direction:column;gap:2px}.eds-modnav-links a{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:var(--eds-radius-md);text-decoration:none;color:var(--eds-text-secondary);font-size:13px;font-weight:500;position:relative}.eds-modnav-links a:hover{background:var(--eds-surface-l1);color:var(--eds-text-primary)}.eds-modnav-links a.active{background:#3681f41a;color:var(--eds-brand-600)}.eds-modnav-links--practice{margin-bottom:12px}.eds-modnav-section{margin:14px 10px 6px;font-family:var(--eds-font-inter);font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--eds-gray-500)}.eds-modnav-label{flex:1;min-width:0}.eds-modnav-pulse{margin-left:auto}.eds-modnav-badge{margin-left:auto;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:var(--eds-brand-600);color:#fff;font-size:10px;font-weight:700;display:inline-flex;align-items:center;justify-content:center}.eds-readiness-card{margin-top:auto;padding:12px;border-radius:var(--eds-radius-lg);border:1px solid var(--eds-border-default);background:var(--eds-surface-l1);text-decoration:none;color:inherit;transition:box-shadow .15s ease}.eds-readiness-card:hover{box-shadow:var(--eds-shadow-md)}.eds-readiness-top{display:flex;justify-content:space-between;margin-bottom:8px}.eds-readiness-bar{height:6px;background:var(--eds-gray-200);border-radius:999px;overflow:hidden}.eds-readiness-bar span{display:block;height:100%;background:linear-gradient(90deg,var(--eds-brand-500),var(--eds-violet-500));border-radius:999px}\n"] }]
2429
- }], ctorParameters: () => [] });
2430
-
2431
- /**
2432
- * Lightweight, reusable modal shell.
2433
- *
2434
- * Renders a centered card on top of a dimmed backdrop. Closes on backdrop
2435
- * click, ESC key, or via the close button. Slot the modal body using
2436
- * <ng-content>.
2437
- */
2438
- class ModalComponent {
2439
- constructor() {
2440
- this.maxWidth = '640px';
2441
- this.closed = new EventEmitter();
2442
- }
2443
- onEscape() {
2444
- this.closed.emit();
2445
- }
2446
- onBackdropClick(event) {
2447
- if (event.target === event.currentTarget) {
2448
- this.closed.emit();
2449
- }
2450
- }
2451
- close() {
2452
- this.closed.emit();
2453
- }
2454
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2455
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: ModalComponent, isStandalone: true, selector: "app-modal", inputs: { title: "title", subtitle: "subtitle", maxWidth: "maxWidth" }, outputs: { closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "<div class=\"modal-backdrop\" (click)=\"onBackdropClick($event)\" role=\"presentation\">\n <div\n class=\"modal-card\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"title\"\n [style.max-width]=\"maxWidth\"\n >\n <header class=\"modal-header\">\n <div class=\"modal-titles\">\n <h2 class=\"modal-title\">{{ title }}</h2>\n @if (subtitle) {\n <p class=\"modal-subtitle\">{{ subtitle }}</p>\n }\n </div>\n <button type=\"button\" class=\"modal-close\" (click)=\"close()\" aria-label=\"Close dialog\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n </header>\n\n <div class=\"modal-body\">\n <ng-content />\n </div>\n </div>\n</div>\n", styles: [":host{display:contents}.modal-backdrop{position:fixed;inset:0;background:#141e326b;backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;padding:24px;z-index:1000;animation:fade-in .14s ease}.modal-card{width:100%;background:var(--eds-color-surface);border-radius:var(--eds-radius-lg);border:1px solid var(--eds-color-border);box-shadow:var(--eds-shadow-modal);max-height:calc(100vh - 48px);display:flex;flex-direction:column;overflow:hidden;animation:pop-in .14s ease}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:20px 24px 14px;border-bottom:1px solid var(--eds-color-border)}.modal-titles{display:flex;flex-direction:column;gap:4px;min-width:0}.modal-title{margin:0;font-size:16px;font-weight:600;letter-spacing:-.005em;color:var(--eds-color-text)}.modal-subtitle{margin:0;font-size:13px;color:var(--eds-color-text-muted)}.modal-close{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:var(--eds-radius-md);border:1px solid transparent;background:transparent;color:var(--eds-color-text-muted);transition:background .12s ease,color .12s ease}.modal-close:hover{background:var(--eds-color-surface-alt);color:var(--eds-color-text)}.modal-close:focus-visible{outline:2px solid var(--eds-color-primary);outline-offset:2px}.modal-body{padding:20px 24px 24px;overflow-y:auto}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes pop-in{0%{opacity:0;transform:translateY(6px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2456
- }
2457
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ModalComponent, decorators: [{
2458
- type: Component,
2459
- args: [{ selector: 'app-modal', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"modal-backdrop\" (click)=\"onBackdropClick($event)\" role=\"presentation\">\n <div\n class=\"modal-card\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"title\"\n [style.max-width]=\"maxWidth\"\n >\n <header class=\"modal-header\">\n <div class=\"modal-titles\">\n <h2 class=\"modal-title\">{{ title }}</h2>\n @if (subtitle) {\n <p class=\"modal-subtitle\">{{ subtitle }}</p>\n }\n </div>\n <button type=\"button\" class=\"modal-close\" (click)=\"close()\" aria-label=\"Close dialog\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n </header>\n\n <div class=\"modal-body\">\n <ng-content />\n </div>\n </div>\n</div>\n", styles: [":host{display:contents}.modal-backdrop{position:fixed;inset:0;background:#141e326b;backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;padding:24px;z-index:1000;animation:fade-in .14s ease}.modal-card{width:100%;background:var(--eds-color-surface);border-radius:var(--eds-radius-lg);border:1px solid var(--eds-color-border);box-shadow:var(--eds-shadow-modal);max-height:calc(100vh - 48px);display:flex;flex-direction:column;overflow:hidden;animation:pop-in .14s ease}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:20px 24px 14px;border-bottom:1px solid var(--eds-color-border)}.modal-titles{display:flex;flex-direction:column;gap:4px;min-width:0}.modal-title{margin:0;font-size:16px;font-weight:600;letter-spacing:-.005em;color:var(--eds-color-text)}.modal-subtitle{margin:0;font-size:13px;color:var(--eds-color-text-muted)}.modal-close{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:var(--eds-radius-md);border:1px solid transparent;background:transparent;color:var(--eds-color-text-muted);transition:background .12s ease,color .12s ease}.modal-close:hover{background:var(--eds-color-surface-alt);color:var(--eds-color-text)}.modal-close:focus-visible{outline:2px solid var(--eds-color-primary);outline-offset:2px}.modal-body{padding:20px 24px 24px;overflow-y:auto}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes pop-in{0%{opacity:0;transform:translateY(6px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}\n"] }]
2460
- }], propDecorators: { title: [{
2461
- type: Input,
2462
- args: [{ required: true }]
2463
- }], subtitle: [{
2464
- type: Input
2465
- }], maxWidth: [{
2466
- type: Input
2467
- }], closed: [{
2468
- type: Output
2469
- }], onEscape: [{
2470
- type: HostListener,
2471
- args: ['document:keydown.escape']
2472
- }] } });
2473
-
2474
- class EdsButtonComponent {
2475
- constructor() {
2476
- this.variant = input('secondary');
2477
- this.type = input('button');
2478
- this.disabled = input(false);
2479
- }
2480
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2481
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.17", type: EdsButtonComponent, isStandalone: true, selector: "eds-button", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2482
- <button [type]="type()" [disabled]="disabled()" [attr.data-variant]="variant()" class="eds-btn">
2483
- <ng-content />
2484
- </button>
2485
- `, isInline: true, styles: [".eds-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 14px;border-radius:var(--eds-radius-md);border:1px solid transparent;font-family:var(--eds-font-inter);font-size:13px;font-weight:500;cursor:pointer;transition:background .12s ease,border-color .12s ease,color .12s ease}.eds-btn:disabled{opacity:.5;cursor:not-allowed}.eds-btn[data-variant=primary]{background:var(--eds-brand-600);color:#fff}.eds-btn[data-variant=primary]:hover:not(:disabled){background:var(--eds-brand-500)}.eds-btn[data-variant=secondary]{background:var(--eds-surface-l0);color:var(--eds-text-primary);border-color:var(--eds-border-default)}.eds-btn[data-variant=secondary]:hover:not(:disabled){background:var(--eds-surface-l1)}.eds-btn[data-variant=ghost]{background:transparent;color:var(--eds-text-secondary)}.eds-btn[data-variant=ghost]:hover:not(:disabled){background:var(--eds-gray-100);color:var(--eds-text-primary)}.eds-btn[data-variant=danger]{background:#f037401a;color:var(--eds-red-500);border-color:#f0374040}.eds-btn[data-variant=positive]{background:#0abf521f;color:var(--eds-green-500);border-color:#0abf5240}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2486
- }
2487
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsButtonComponent, decorators: [{
2488
- type: Component,
2489
- args: [{ selector: 'eds-button', standalone: true, template: `
2490
- <button [type]="type()" [disabled]="disabled()" [attr.data-variant]="variant()" class="eds-btn">
2491
- <ng-content />
2492
- </button>
2493
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".eds-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 14px;border-radius:var(--eds-radius-md);border:1px solid transparent;font-family:var(--eds-font-inter);font-size:13px;font-weight:500;cursor:pointer;transition:background .12s ease,border-color .12s ease,color .12s ease}.eds-btn:disabled{opacity:.5;cursor:not-allowed}.eds-btn[data-variant=primary]{background:var(--eds-brand-600);color:#fff}.eds-btn[data-variant=primary]:hover:not(:disabled){background:var(--eds-brand-500)}.eds-btn[data-variant=secondary]{background:var(--eds-surface-l0);color:var(--eds-text-primary);border-color:var(--eds-border-default)}.eds-btn[data-variant=secondary]:hover:not(:disabled){background:var(--eds-surface-l1)}.eds-btn[data-variant=ghost]{background:transparent;color:var(--eds-text-secondary)}.eds-btn[data-variant=ghost]:hover:not(:disabled){background:var(--eds-gray-100);color:var(--eds-text-primary)}.eds-btn[data-variant=danger]{background:#f037401a;color:var(--eds-red-500);border-color:#f0374040}.eds-btn[data-variant=positive]{background:#0abf521f;color:var(--eds-green-500);border-color:#0abf5240}\n"] }]
2494
- }] });
2495
-
2496
- const PAGE_TITLES = {
2497
- dashboard: 'Dashboard',
2498
- live: 'Live Calls',
2499
- history: 'Call History',
2500
- tasks: 'Tasks & Follow-ups',
2501
- analytics: 'Analytics',
2502
- settings: 'Agent Settings',
2503
- knowledge: 'Knowledge Base',
2504
- audit: 'Audit Log',
2505
- wizard: 'Setup Wizard',
2506
- patients: 'Patients',
2507
- providers: 'Providers',
2508
- appointments: 'Appointments',
2509
- admin: 'Admin',
2510
- };
2511
- class EdsTopbarComponent {
2512
- constructor() {
2513
- this.router = inject(Router);
2514
- this.sessionAdapter = inject(VOICE_RECEPTIONIST_SESSION);
2515
- this.toast = inject(ToastService);
2516
- this.agentOnline = inject(AgentOnlineService);
2517
- this.pageTitle = signal('Dashboard');
2518
- this.confirmOffOpen = signal(false);
2519
- this.online = this.agentOnline.online;
2520
- this.loading = this.agentOnline.loading;
2521
- this.toggling = this.agentOnline.toggling;
2522
- this.statusError = this.agentOnline.error;
2523
- this.userEmail = computed(() => this.sessionAdapter?.email() ?? null);
2524
- this.showUser = computed(() => !!this.userEmail());
2525
- this.showLogout = computed(() => !!this.sessionAdapter);
2526
- this.userInitials = computed(() => {
2527
- const email = this.userEmail()?.trim() ?? '';
2528
- if (!email)
2529
- return '?';
2530
- const local = email.split('@')[0] ?? '';
2531
- const parts = local.split(/[._-]+/).filter(Boolean);
2532
- if (parts.length >= 2) {
2533
- return `${parts[0][0]}${parts[1][0]}`.toUpperCase();
2534
- }
2535
- return local.slice(0, 2).toUpperCase() || '?';
2536
- });
2537
- this.statusLabel = computed(() => {
2538
- if (this.loading() && this.online() === null)
2539
- return 'Checking status…';
2540
- if (this.online() === null)
2541
- return 'Status unknown';
2542
- return this.online()
2543
- ? 'AI receptionist is answering calls'
2544
- : 'AI is offline — calls go to front desk';
2545
- });
2546
- this.switchDisabled = computed(() => this.loading() || this.toggling() || this.online() === null);
2547
- this.agentOnline.refresh();
2548
- this.updateTitle(this.router.url);
2549
- this.router.events
2550
- .pipe(filter((e) => e instanceof NavigationEnd))
2551
- .subscribe((e) => this.updateTitle(e.urlAfterRedirects));
2552
- }
2553
- onToggleClick() {
2554
- if (this.switchDisabled())
2555
- return;
2556
- if (this.online()) {
2557
- this.confirmOffOpen.set(true);
2558
- return;
2559
- }
2560
- this.applyOnline(true);
2561
- }
2562
- cancelConfirmOff() {
2563
- this.confirmOffOpen.set(false);
2564
- }
2565
- confirmTurnOff() {
2566
- this.confirmOffOpen.set(false);
2567
- this.applyOnline(false);
2568
- }
2569
- applyOnline(enabled) {
2570
- this.agentOnline.setOnline(enabled).subscribe({
2571
- next: () => {
2572
- this.toast.show(enabled
2573
- ? 'AI receptionist is online — Ava will answer inbound calls.'
2574
- : 'AI receptionist is offline — calls go to the front desk.', 'success');
2575
- },
2576
- error: (err) => {
2577
- this.toast.show(err.message ?? 'Failed to update agent status.', 'error');
2578
- this.agentOnline.refresh();
2579
- },
2580
- });
2581
- }
2582
- logout() {
2583
- this.sessionAdapter?.logout();
2584
- }
2585
- updateTitle(url) {
2586
- const base = url.split('?')[0].split(';')[0];
2587
- const segments = base.split('/').filter(Boolean);
2588
- const last = segments[segments.length - 1] ?? '';
2589
- const prev = segments[segments.length - 2] ?? '';
2590
- if (prev === 'live' && last && last !== 'live') {
2591
- this.pageTitle.set('Live Monitor');
2592
- return;
2593
- }
2594
- if (prev === 'history' && last && last !== 'history') {
2595
- this.pageTitle.set('Call Detail');
2596
- return;
2597
- }
2598
- if (prev === 'settings' && last === 'outbound-purposes') {
2599
- this.pageTitle.set('Outbound Purposes');
2600
- return;
2601
- }
2602
- this.pageTitle.set(PAGE_TITLES[last] ?? 'AI Receptionist');
2603
- }
2604
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsTopbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2605
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: EdsTopbarComponent, isStandalone: true, selector: "eds-topbar", ngImport: i0, template: "<header class=\"eds-topbar\">\n <div class=\"eds-topbar-left\">\n <nav class=\"eds-breadcrumb eds-body-sm\" aria-label=\"Breadcrumb\">\n <span>AI Receptionist</span>\n <eds-icon name=\"chevron_right\" [size]=\"16\" />\n <span class=\"eds-breadcrumb-current\">{{ pageTitle() }}</span>\n </nav>\n </div>\n\n <div class=\"eds-topbar-right\">\n <div class=\"eds-agent-online\" [attr.data-state]=\"online() === null ? 'unknown' : online() ? 'on' : 'off'\">\n <button\n type=\"button\"\n class=\"eds-agent-toggle\"\n [disabled]=\"switchDisabled()\"\n [attr.aria-pressed]=\"online() === true\"\n [attr.aria-busy]=\"toggling()\"\n [title]=\"statusLabel()\"\n (click)=\"onToggleClick()\"\n >\n <span\n class=\"eds-agent-toggle__track\"\n [class.on]=\"online() === true\"\n [class.unknown]=\"online() === null\"\n >\n <span class=\"eds-agent-toggle__thumb\"></span>\n </span>\n <span class=\"eds-agent-toggle__copy\">\n <span class=\"eds-body-sm eds-agent-toggle__label\">\n @if (toggling()) {\n Updating\u2026\n } @else if (online() === true) {\n Agent online\n } @else if (online() === false) {\n Agent offline\n } @else {\n Status unknown\n }\n </span>\n <span class=\"eds-agent-toggle__hint\">{{ statusLabel() }}</span>\n </span>\n </button>\n @if (statusError() && online() === null) {\n <button type=\"button\" class=\"eds-agent-retry eds-body-sm\" (click)=\"agentOnline.refresh()\">\n Retry\n </button>\n }\n </div>\n\n @if (showUser()) {\n <div class=\"eds-topbar-user\" [title]=\"userEmail()!\">\n <span class=\"eds-topbar-user__avatar\">{{ userInitials() }}</span>\n <span class=\"eds-topbar-user__email eds-body-sm\">{{ userEmail() }}</span>\n </div>\n }\n\n @if (showLogout()) {\n <button type=\"button\" class=\"eds-topbar-logout\" (click)=\"logout()\" aria-label=\"Sign out\" title=\"Sign out\">\n <eds-icon name=\"logout\" [size]=\"18\" />\n <span class=\"eds-body-sm\">Log out</span>\n </button>\n }\n </div>\n</header>\n\n@if (confirmOffOpen()) {\n <app-modal\n title=\"Turn off AI receptionist?\"\n subtitle=\"Inbound callers will be transferred to the front desk instead of Ava.\"\n maxWidth=\"480px\"\n (closed)=\"cancelConfirmOff()\"\n >\n <p class=\"eds-agent-confirm__body\">\n While offline, Ava will not answer calls. Callers are routed to the human receptionist\n at the front desk.\n </p>\n <div class=\"eds-agent-confirm__actions\">\n <eds-button variant=\"secondary\" type=\"button\" (click)=\"cancelConfirmOff()\">Cancel</eds-button>\n <eds-button variant=\"danger\" type=\"button\" [disabled]=\"toggling()\" (click)=\"confirmTurnOff()\">\n Turn off\n </eds-button>\n </div>\n </app-modal>\n}\n", styles: [".eds-topbar{height:var(--eds-topbar-height);display:flex;align-items:center;gap:16px;padding:0 24px;background:var(--eds-surface-l0);border-bottom:1px solid var(--eds-border-default);position:sticky;top:0;z-index:10}.eds-topbar-left{flex-shrink:0}.eds-breadcrumb{display:inline-flex;align-items:center;gap:4px;color:var(--eds-text-secondary)}.eds-breadcrumb-current{color:var(--eds-text-primary);font-weight:600}.eds-topbar-right{display:flex;align-items:center;gap:12px;margin-left:auto}.eds-agent-online{display:inline-flex;align-items:center;gap:8px;max-width:min(360px,42vw)}.eds-agent-toggle{display:inline-flex;align-items:center;gap:10px;border:1px solid var(--eds-border-default);background:var(--eds-surface-l0);border-radius:var(--eds-radius-md);cursor:pointer;padding:6px 10px;text-align:left;font:inherit;color:inherit}.eds-agent-toggle:disabled{opacity:.65;cursor:not-allowed}.eds-agent-toggle:hover:not(:disabled){background:var(--eds-surface-l1)}.eds-agent-toggle__track{width:36px;height:20px;border-radius:999px;background:var(--eds-gray-300);position:relative;transition:background .15s ease;flex-shrink:0}.eds-agent-toggle__track.on{background:var(--eds-green-500)}.eds-agent-toggle__track.unknown{background:var(--eds-gray-400)}.eds-agent-toggle__thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .15s ease;box-shadow:var(--eds-shadow-sm)}.on .eds-agent-toggle__thumb{transform:translate(16px)}.eds-agent-toggle__copy{display:flex;flex-direction:column;gap:1px;min-width:0}.eds-agent-toggle__label{font-weight:600;color:var(--eds-text-primary);white-space:nowrap}.eds-agent-toggle__hint{font-size:11px;line-height:1.3;color:var(--eds-text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.eds-agent-retry{border:none;background:transparent;color:var(--eds-color-primary, #3681f4);cursor:pointer;padding:4px 6px;text-decoration:underline}.eds-agent-confirm__body{margin:0 0 20px;font-size:14px;line-height:1.5;color:var(--eds-text-secondary)}.eds-agent-confirm__body code{font-size:12px}.eds-agent-confirm__actions{display:flex;justify-content:flex-end;gap:10px}@media(max-width:1100px){.eds-agent-toggle__hint{display:none}}.eds-topbar-user{display:inline-flex;align-items:center;gap:8px;max-width:200px;padding:4px 8px;border-radius:var(--eds-radius-md)}.eds-topbar-user__avatar{width:28px;height:28px;border-radius:50%;background:var(--eds-gray-200);color:var(--eds-gray-700);font-size:11px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.eds-topbar-user__email{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--eds-text-secondary)}.eds-topbar-logout{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--eds-border-default);background:var(--eds-surface-l0);color:var(--eds-text-secondary);border-radius:var(--eds-radius-md);padding:6px 10px;cursor:pointer;font:inherit}.eds-topbar-logout:hover{background:var(--eds-surface-l1);color:var(--eds-text-primary);border-color:var(--eds-gray-300)}@media(max-width:1100px){.eds-topbar-user__email{display:none}}\n"], dependencies: [{ kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }, { kind: "component", type: ModalComponent, selector: "app-modal", inputs: ["title", "subtitle", "maxWidth"], outputs: ["closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2606
- }
2607
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsTopbarComponent, decorators: [{
2608
- type: Component,
2609
- args: [{ selector: 'eds-topbar', imports: [EdsIconComponent, EdsButtonComponent, ModalComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"eds-topbar\">\n <div class=\"eds-topbar-left\">\n <nav class=\"eds-breadcrumb eds-body-sm\" aria-label=\"Breadcrumb\">\n <span>AI Receptionist</span>\n <eds-icon name=\"chevron_right\" [size]=\"16\" />\n <span class=\"eds-breadcrumb-current\">{{ pageTitle() }}</span>\n </nav>\n </div>\n\n <div class=\"eds-topbar-right\">\n <div class=\"eds-agent-online\" [attr.data-state]=\"online() === null ? 'unknown' : online() ? 'on' : 'off'\">\n <button\n type=\"button\"\n class=\"eds-agent-toggle\"\n [disabled]=\"switchDisabled()\"\n [attr.aria-pressed]=\"online() === true\"\n [attr.aria-busy]=\"toggling()\"\n [title]=\"statusLabel()\"\n (click)=\"onToggleClick()\"\n >\n <span\n class=\"eds-agent-toggle__track\"\n [class.on]=\"online() === true\"\n [class.unknown]=\"online() === null\"\n >\n <span class=\"eds-agent-toggle__thumb\"></span>\n </span>\n <span class=\"eds-agent-toggle__copy\">\n <span class=\"eds-body-sm eds-agent-toggle__label\">\n @if (toggling()) {\n Updating\u2026\n } @else if (online() === true) {\n Agent online\n } @else if (online() === false) {\n Agent offline\n } @else {\n Status unknown\n }\n </span>\n <span class=\"eds-agent-toggle__hint\">{{ statusLabel() }}</span>\n </span>\n </button>\n @if (statusError() && online() === null) {\n <button type=\"button\" class=\"eds-agent-retry eds-body-sm\" (click)=\"agentOnline.refresh()\">\n Retry\n </button>\n }\n </div>\n\n @if (showUser()) {\n <div class=\"eds-topbar-user\" [title]=\"userEmail()!\">\n <span class=\"eds-topbar-user__avatar\">{{ userInitials() }}</span>\n <span class=\"eds-topbar-user__email eds-body-sm\">{{ userEmail() }}</span>\n </div>\n }\n\n @if (showLogout()) {\n <button type=\"button\" class=\"eds-topbar-logout\" (click)=\"logout()\" aria-label=\"Sign out\" title=\"Sign out\">\n <eds-icon name=\"logout\" [size]=\"18\" />\n <span class=\"eds-body-sm\">Log out</span>\n </button>\n }\n </div>\n</header>\n\n@if (confirmOffOpen()) {\n <app-modal\n title=\"Turn off AI receptionist?\"\n subtitle=\"Inbound callers will be transferred to the front desk instead of Ava.\"\n maxWidth=\"480px\"\n (closed)=\"cancelConfirmOff()\"\n >\n <p class=\"eds-agent-confirm__body\">\n While offline, Ava will not answer calls. Callers are routed to the human receptionist\n at the front desk.\n </p>\n <div class=\"eds-agent-confirm__actions\">\n <eds-button variant=\"secondary\" type=\"button\" (click)=\"cancelConfirmOff()\">Cancel</eds-button>\n <eds-button variant=\"danger\" type=\"button\" [disabled]=\"toggling()\" (click)=\"confirmTurnOff()\">\n Turn off\n </eds-button>\n </div>\n </app-modal>\n}\n", styles: [".eds-topbar{height:var(--eds-topbar-height);display:flex;align-items:center;gap:16px;padding:0 24px;background:var(--eds-surface-l0);border-bottom:1px solid var(--eds-border-default);position:sticky;top:0;z-index:10}.eds-topbar-left{flex-shrink:0}.eds-breadcrumb{display:inline-flex;align-items:center;gap:4px;color:var(--eds-text-secondary)}.eds-breadcrumb-current{color:var(--eds-text-primary);font-weight:600}.eds-topbar-right{display:flex;align-items:center;gap:12px;margin-left:auto}.eds-agent-online{display:inline-flex;align-items:center;gap:8px;max-width:min(360px,42vw)}.eds-agent-toggle{display:inline-flex;align-items:center;gap:10px;border:1px solid var(--eds-border-default);background:var(--eds-surface-l0);border-radius:var(--eds-radius-md);cursor:pointer;padding:6px 10px;text-align:left;font:inherit;color:inherit}.eds-agent-toggle:disabled{opacity:.65;cursor:not-allowed}.eds-agent-toggle:hover:not(:disabled){background:var(--eds-surface-l1)}.eds-agent-toggle__track{width:36px;height:20px;border-radius:999px;background:var(--eds-gray-300);position:relative;transition:background .15s ease;flex-shrink:0}.eds-agent-toggle__track.on{background:var(--eds-green-500)}.eds-agent-toggle__track.unknown{background:var(--eds-gray-400)}.eds-agent-toggle__thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .15s ease;box-shadow:var(--eds-shadow-sm)}.on .eds-agent-toggle__thumb{transform:translate(16px)}.eds-agent-toggle__copy{display:flex;flex-direction:column;gap:1px;min-width:0}.eds-agent-toggle__label{font-weight:600;color:var(--eds-text-primary);white-space:nowrap}.eds-agent-toggle__hint{font-size:11px;line-height:1.3;color:var(--eds-text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.eds-agent-retry{border:none;background:transparent;color:var(--eds-color-primary, #3681f4);cursor:pointer;padding:4px 6px;text-decoration:underline}.eds-agent-confirm__body{margin:0 0 20px;font-size:14px;line-height:1.5;color:var(--eds-text-secondary)}.eds-agent-confirm__body code{font-size:12px}.eds-agent-confirm__actions{display:flex;justify-content:flex-end;gap:10px}@media(max-width:1100px){.eds-agent-toggle__hint{display:none}}.eds-topbar-user{display:inline-flex;align-items:center;gap:8px;max-width:200px;padding:4px 8px;border-radius:var(--eds-radius-md)}.eds-topbar-user__avatar{width:28px;height:28px;border-radius:50%;background:var(--eds-gray-200);color:var(--eds-gray-700);font-size:11px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.eds-topbar-user__email{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--eds-text-secondary)}.eds-topbar-logout{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--eds-border-default);background:var(--eds-surface-l0);color:var(--eds-text-secondary);border-radius:var(--eds-radius-md);padding:6px 10px;cursor:pointer;font:inherit}.eds-topbar-logout:hover{background:var(--eds-surface-l1);color:var(--eds-text-primary);border-color:var(--eds-gray-300)}@media(max-width:1100px){.eds-topbar-user__email{display:none}}\n"] }]
2610
- }], ctorParameters: () => [] });
2611
-
2612
- class EdsAppShellComponent {
2613
- constructor() {
2614
- /** EHR embed: false (default). Standalone Voice Hub: set showModuleNav: true. */
2615
- this.showModuleNav = !!inject(VOICE_RECEPTIONIST_CONFIG).showModuleNav;
2616
- }
2617
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsAppShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2618
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: EdsAppShellComponent, isStandalone: true, selector: "eds-app-shell", ngImport: i0, template: "<div class=\"eds-layout\">\n @if (showModuleNav) {\n <eds-module-nav />\n }\n <div class=\"eds-main\">\n <eds-topbar />\n <div class=\"eds-content\">\n <router-outlet />\n </div>\n </div>\n <app-toast-container />\n</div>\n", styles: [":host{display:block}.eds-layout{display:flex;min-height:100vh;background:var(--eds-surface-l1)}.eds-main{flex:1;min-width:0;display:flex;flex-direction:column}.eds-content{flex:1;overflow:auto}\n"], dependencies: [{ kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: EdsModuleNavComponent, selector: "eds-module-nav" }, { kind: "component", type: EdsTopbarComponent, selector: "eds-topbar" }, { kind: "component", type: ToastContainerComponent, selector: "app-toast-container" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2619
- }
2620
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsAppShellComponent, decorators: [{
2621
- type: Component,
2622
- args: [{ selector: 'eds-app-shell', imports: [
2623
- RouterOutlet,
2624
- EdsModuleNavComponent,
2625
- EdsTopbarComponent,
2626
- ToastContainerComponent,
2627
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"eds-layout\">\n @if (showModuleNav) {\n <eds-module-nav />\n }\n <div class=\"eds-main\">\n <eds-topbar />\n <div class=\"eds-content\">\n <router-outlet />\n </div>\n </div>\n <app-toast-container />\n</div>\n", styles: [":host{display:block}.eds-layout{display:flex;min-height:100vh;background:var(--eds-surface-l1)}.eds-main{flex:1;min-width:0;display:flex;flex-direction:column}.eds-content{flex:1;overflow:auto}\n"] }]
2628
- }] });
2629
-
2630
- /** Feature child routes (under the embed shell). */
2631
- const VOICE_RECEPTIONIST_FEATURE_ROUTES = [
2632
- { path: '', pathMatch: 'full', redirectTo: 'dashboard' },
2633
- {
2634
- path: 'dashboard',
2635
- loadComponent: () => import('./edvak99-voice-receptionist-widget-dashboard-page.component-BNdnbAVM.mjs').then((m) => m.DashboardPageComponent),
2636
- },
2637
- {
2638
- path: 'live',
2639
- loadComponent: () => import('./edvak99-voice-receptionist-widget-live-calls-page.component-DfLmK89M.mjs').then((m) => m.LiveCallsPageComponent),
2640
- },
2641
- {
2642
- path: 'live/:id',
2643
- loadComponent: () => import('./edvak99-voice-receptionist-widget-live-monitor-page.component-DNpLFqBw.mjs').then((m) => m.LiveMonitorPageComponent),
2644
- },
2645
- {
2646
- path: 'history',
2647
- loadComponent: () => import('./edvak99-voice-receptionist-widget-call-history-page.component-B5AnEjoh.mjs').then((m) => m.CallHistoryPageComponent),
2648
- },
2649
- {
2650
- path: 'history/:id',
2651
- loadComponent: () => import('./edvak99-voice-receptionist-widget-call-history-detail-page.component-2K9W77As.mjs').then((m) => m.CallHistoryDetailPageComponent),
2652
- },
2653
- {
2654
- path: 'tasks',
2655
- loadComponent: () => import('./edvak99-voice-receptionist-widget-tasks-page.component-DXxjMVUv.mjs').then((m) => m.TasksPageComponent),
2656
- },
2657
- {
2658
- path: 'analytics',
2659
- loadComponent: () => import('./edvak99-voice-receptionist-widget-analytics-page.component-Dsujn5az.mjs').then((m) => m.AnalyticsPageComponent),
2660
- },
2661
- {
2662
- path: 'settings',
2663
- loadComponent: () => import('./edvak99-voice-receptionist-widget-settings-page.component-B9nv6wtB.mjs').then((m) => m.SettingsPageComponent),
2664
- },
2665
- {
2666
- path: 'settings/outbound-purposes',
2667
- loadComponent: () => import('./edvak99-voice-receptionist-widget-outbound-purposes-page.component-CYSZ832p.mjs').then((m) => m.OutboundPurposesPageComponent),
2668
- },
2669
- {
2670
- path: 'knowledge',
2671
- loadComponent: () => import('./edvak99-voice-receptionist-widget-knowledge-hub.page-BngGx_Lc.mjs').then((m) => m.KnowledgeHubPageComponent),
2672
- },
2673
- {
2674
- path: 'audit',
2675
- loadComponent: () => import('./edvak99-voice-receptionist-widget-audit-page.component-xoLlHj8K.mjs').then((m) => m.AuditPageComponent),
2676
- },
2677
- {
2678
- path: 'wizard',
2679
- loadComponent: () => import('./edvak99-voice-receptionist-widget-wizard-page.component-C1Z6yC0q.mjs').then((m) => m.WizardPageComponent),
2680
- },
2681
- ];
2682
- /**
2683
- * Lazy-loadable routes for EHR (and standalone hosts).
2684
- * Mount under a host path, e.g. `{ path: 'ai-receptionist', children: VOICE_RECEPTIONIST_ROUTES }`.
2685
- */
2686
- const VOICE_RECEPTIONIST_ROUTES = [
2687
- {
2688
- path: '',
2689
- component: EdsAppShellComponent,
2690
- children: VOICE_RECEPTIONIST_FEATURE_ROUTES,
2691
- },
2692
- ];
2693
-
2694
- class EdsBadgeComponent {
2695
- constructor() {
2696
- this.tone = input('gray');
2697
- }
2698
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2699
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.17", type: EdsBadgeComponent, isStandalone: true, selector: "eds-badge", inputs: { tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `<span class="eds-badge" [attr.data-tone]="tone()"><ng-content /></span>`, isInline: true, styles: [".eds-badge{display:inline-flex;align-items:center;gap:6px;padding:2px 8px;border-radius:var(--eds-radius-sm);font-family:var(--eds-font-inter);font-size:11px;font-weight:600;line-height:1.4;white-space:nowrap}.eds-badge[data-tone=brand]{background:#3681f41f;color:var(--eds-brand-600)}.eds-badge[data-tone=pos]{background:#0abf521f;color:var(--eds-green-500)}.eds-badge[data-tone=notice]{background:#ffb90026;color:#b45309}.eds-badge[data-tone=neg]{background:#f037401f;color:var(--eds-red-500)}.eds-badge[data-tone=violet]{background:#8e51ff1f;color:var(--eds-violet-500)}.eds-badge[data-tone=gray]{background:var(--eds-gray-100);color:var(--eds-gray-600)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2700
- }
2701
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsBadgeComponent, decorators: [{
2702
- type: Component,
2703
- args: [{ selector: 'eds-badge', standalone: true, template: `<span class="eds-badge" [attr.data-tone]="tone()"><ng-content /></span>`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".eds-badge{display:inline-flex;align-items:center;gap:6px;padding:2px 8px;border-radius:var(--eds-radius-sm);font-family:var(--eds-font-inter);font-size:11px;font-weight:600;line-height:1.4;white-space:nowrap}.eds-badge[data-tone=brand]{background:#3681f41f;color:var(--eds-brand-600)}.eds-badge[data-tone=pos]{background:#0abf521f;color:var(--eds-green-500)}.eds-badge[data-tone=notice]{background:#ffb90026;color:#b45309}.eds-badge[data-tone=neg]{background:#f037401f;color:var(--eds-red-500)}.eds-badge[data-tone=violet]{background:#8e51ff1f;color:var(--eds-violet-500)}.eds-badge[data-tone=gray]{background:var(--eds-gray-100);color:var(--eds-gray-600)}\n"] }]
2704
- }] });
2705
-
2706
- /*
2707
- * Public API Surface of @edvak99/voice-receptionist-widget
2708
- */
2709
-
2710
- /**
2711
- * Generated bundle index. Do not edit.
2712
- */
2713
-
2714
- export { AiReceptionistApiService as A, DEFAULT_PAGE_SIZE as D, EdsButtonComponent as E, ModalComponent as M, ToastService as T, VOICE_RECEPTIONIST_CONFIG as V, WELCOME_MESSAGE_MAX as W, EdsBadgeComponent as a, EdsIconComponent as b, EdsPulseDotComponent as c, resolveToolDisplayName as d, createVoiceReceptionistPaths as e, totalPages as f, buildAutoWelcomeTemplate as g, resolveWelcomePreview as h, DEFAULT_PLACEHOLDERS as i, asPaginatedResult as j, EdsAppShellComponent as k, VOICE_RECEPTIONIST_FEATURE_ROUTES as l, VOICE_RECEPTIONIST_ROUTES as m, VOICE_RECEPTIONIST_SESSION as n, provideVoiceReceptionist as p, resolveToolKey as r, toolEventTrackKey as t };
2715
- //# sourceMappingURL=edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-Dn0GrZ6_.mjs.map
3428
+ export { AiReceptionistApiService as A, DEFAULT_PAGE_SIZE as D, WELCOME_MESSAGE_MAX as W, historyResolutionLabel as a, historyResolution as b, callerMetaLine as c, ageFromDob as d, resolveToolKey as e, resolveToolDisplayName as f, toolEventTrackKey as g, historyResultChips as h, isUnknownCaller as i, createVoiceReceptionistPaths as j, totalPages as k, activityTypeLabel as l, initialsFromName as m, buildAutoWelcomeTemplate as n, resolveWelcomePreview as o, DEFAULT_PLACEHOLDERS as p, resultChipIcon as r, toE164Phone as t };
3429
+ //# sourceMappingURL=edvak99-voice-receptionist-widget-ai-receptionist-api.service-48cifVPF.mjs.map