@edvak99/voice-receptionist-widget 1.0.0 → 1.1.0

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 (124) hide show
  1. package/README.md +56 -43
  2. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CoVjr5Y3.mjs +113 -0
  3. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CoVjr5Y3.mjs.map +1 -0
  4. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-ou9oxnJp.mjs +35 -0
  5. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-ou9oxnJp.mjs.map +1 -0
  6. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-BV7KOhd9.mjs +316 -0
  7. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-BV7KOhd9.mjs.map +1 -0
  8. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-CWWma-6r.mjs +138 -0
  9. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-CWWma-6r.mjs.map +1 -0
  10. package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-B2IuwsVH.mjs +110 -0
  11. package/fesm2022/edvak99-voice-receptionist-widget-call-recording-player.component-B2IuwsVH.mjs.map +1 -0
  12. package/fesm2022/edvak99-voice-receptionist-widget-call-review.service-D-GutOar.mjs +29 -0
  13. package/fesm2022/edvak99-voice-receptionist-widget-call-review.service-D-GutOar.mjs.map +1 -0
  14. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs +70 -0
  15. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs.map +1 -0
  16. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-B93giErR.mjs +248 -0
  17. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-B93giErR.mjs.map +1 -0
  18. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs +27 -0
  19. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs.map +1 -0
  20. package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-BrMNsxB5.mjs +136 -0
  21. package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-BrMNsxB5.mjs.map +1 -0
  22. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DClMfxeY.mjs +2666 -0
  23. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DClMfxeY.mjs.map +1 -0
  24. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-BNsM-o3z.mjs +575 -0
  25. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-BNsM-o3z.mjs.map +1 -0
  26. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-list.service-C2UZJ5wF.mjs +120 -0
  27. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-list.service-C2UZJ5wF.mjs.map +1 -0
  28. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-DQTVJjsP.mjs +115 -0
  29. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-DQTVJjsP.mjs.map +1 -0
  30. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-C3iTtVdn.mjs +574 -0
  31. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-C3iTtVdn.mjs.map +1 -0
  32. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-BQECIuJu.mjs +54 -0
  33. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-BQECIuJu.mjs.map +1 -0
  34. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-C10N-x5g.mjs +265 -0
  35. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-C10N-x5g.mjs.map +1 -0
  36. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-BBE_H2jb.mjs +509 -0
  37. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-BBE_H2jb.mjs.map +1 -0
  38. package/fesm2022/edvak99-voice-receptionist-widget-task.util-DdtI0CR3.mjs +52 -0
  39. package/fesm2022/edvak99-voice-receptionist-widget-task.util-DdtI0CR3.mjs.map +1 -0
  40. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-COnYcaMT.mjs +237 -0
  41. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-COnYcaMT.mjs.map +1 -0
  42. package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-Box7X-Cq.mjs +45 -0
  43. package/fesm2022/edvak99-voice-receptionist-widget-wizard-page.component-Box7X-Cq.mjs.map +1 -0
  44. package/fesm2022/edvak99-voice-receptionist-widget.mjs +1 -41
  45. package/fesm2022/edvak99-voice-receptionist-widget.mjs.map +1 -1
  46. package/lib/api-paths.d.ts +11 -0
  47. package/lib/components/call-conversation-panel/call-conversation-panel.component.d.ts +45 -0
  48. package/lib/components/call-recording-player/call-recording-player.component.d.ts +29 -0
  49. package/lib/components/call-tasks-panel/call-tasks-panel.component.d.ts +19 -0
  50. package/lib/components/live-transcript-panel/live-transcript-panel.component.d.ts +36 -0
  51. package/lib/components/outbound-dial-modal/outbound-dial-modal.component.d.ts +37 -0
  52. package/lib/components/outbound-purpose-form-modal/outbound-purpose-form-modal.component.d.ts +38 -0
  53. package/lib/components/task-detail-drawer/task-detail-drawer.component.d.ts +20 -0
  54. package/lib/config.d.ts +23 -0
  55. package/lib/extract-api-rows.d.ts +2 -0
  56. package/lib/knowledge/components/faq-empty-state/faq-empty-state.component.d.ts +22 -0
  57. package/lib/knowledge/components/faq-form/faq-form.component.d.ts +27 -0
  58. package/lib/knowledge/components/faq-search-tester/faq-search-tester.component.d.ts +17 -0
  59. package/lib/knowledge/mocks/practice-faq.seed.d.ts +2 -0
  60. package/lib/knowledge/models/practice-faq.model.d.ts +47 -0
  61. package/lib/knowledge/pages/knowledge-hub.page.d.ts +47 -0
  62. package/lib/knowledge/services/practice-faq-api.mock.d.ts +16 -0
  63. package/lib/knowledge/services/practice-faq-api.service.d.ts +22 -0
  64. package/lib/knowledge/utils/faq.util.d.ts +3 -0
  65. package/lib/layout/eds-app-rail.component.d.ts +6 -0
  66. package/lib/layout/eds-app-shell.component.d.ts +5 -0
  67. package/lib/layout/eds-module-nav.component.d.ts +24 -0
  68. package/lib/layout/eds-topbar.component.d.ts +29 -0
  69. package/lib/mocks/api-log.seed.d.ts +5 -0
  70. package/lib/mocks/outbound-purpose.seed.d.ts +3 -0
  71. package/lib/mocks/seed-data.d.ts +38 -0
  72. package/lib/models/ai-receptionist.types.d.ts +257 -0
  73. package/lib/models/api-dto.types.d.ts +215 -0
  74. package/lib/models/api-log.types.d.ts +53 -0
  75. package/lib/models/live-transcript.types.d.ts +26 -0
  76. package/lib/models/outbound-purpose.types.d.ts +50 -0
  77. package/lib/pages/analytics-page/analytics-page.component.d.ts +44 -0
  78. package/lib/pages/audit-page/audit-page.component.d.ts +14 -0
  79. package/lib/pages/call-history-detail-page/call-history-detail-page.component.d.ts +25 -0
  80. package/lib/pages/call-history-page/call-history-page.component.d.ts +42 -0
  81. package/lib/pages/dashboard-page/dashboard-page.component.d.ts +60 -0
  82. package/lib/pages/live-calls-page/live-calls-page.component.d.ts +37 -0
  83. package/lib/pages/live-monitor-page/live-monitor-page.component.d.ts +43 -0
  84. package/lib/pages/outbound-purposes-page/outbound-purposes-page.component.d.ts +24 -0
  85. package/lib/pages/settings-page/settings-fields.config.d.ts +35 -0
  86. package/lib/pages/settings-page/settings-page.component.d.ts +50 -0
  87. package/lib/pages/settings-page/tts-voice-presets.d.ts +7 -0
  88. package/lib/pages/tasks-page/tasks-page.component.d.ts +35 -0
  89. package/lib/pages/wizard-page/wizard-page.component.d.ts +17 -0
  90. package/lib/pipes/eds-elapsed.pipe.d.ts +7 -0
  91. package/lib/provide-voice-receptionist.d.ts +6 -0
  92. package/lib/routes.d.ts +8 -0
  93. package/lib/services/agent-online.service.d.ts +29 -0
  94. package/lib/services/ai-receptionist-api.mappers.d.ts +45 -0
  95. package/lib/services/ai-receptionist-api.mock.d.ts +150 -0
  96. package/lib/services/ai-receptionist-api.service.d.ts +156 -0
  97. package/lib/services/call-review.service.d.ts +12 -0
  98. package/lib/services/dashboard-socket.service.d.ts +26 -0
  99. package/lib/services/live-calls-list.service.d.ts +29 -0
  100. package/lib/services/live-transcript.service.d.ts +39 -0
  101. package/lib/shared/copy-button.component.d.ts +14 -0
  102. package/lib/shared/modal.component.d.ts +20 -0
  103. package/lib/shared/toast-container.component.d.ts +7 -0
  104. package/lib/shared/toast.service.d.ts +16 -0
  105. package/lib/ui/eds-avatar.component.d.ts +8 -0
  106. package/lib/ui/eds-badge.component.d.ts +7 -0
  107. package/lib/ui/eds-button.component.d.ts +9 -0
  108. package/lib/ui/eds-icon.component.d.ts +10 -0
  109. package/lib/ui/eds-pulse-dot.component.d.ts +5 -0
  110. package/lib/ui/eds-waveform.component.d.ts +8 -0
  111. package/lib/utils/call-outcome.util.d.ts +8 -0
  112. package/lib/utils/call-review.util.d.ts +8 -0
  113. package/lib/utils/outbound-purpose.util.d.ts +14 -0
  114. package/lib/utils/task.util.d.ts +10 -0
  115. package/lib/utils/tool-labels.util.d.ts +14 -0
  116. package/lib/utils/transcript-timing.util.d.ts +17 -0
  117. package/lib/utils/welcome-message.util.d.ts +5 -0
  118. package/package.json +12 -5
  119. package/public-api.d.ts +6 -2
  120. package/styles/_index.scss +55 -0
  121. package/styles/chat-ui.scss +133 -0
  122. package/styles/eds.scss +328 -0
  123. package/lib/voice-receptionist-widget.component.d.ts +0 -5
  124. package/lib/voice-receptionist-widget.service.d.ts +0 -6
@@ -0,0 +1,574 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, signal, Injectable, input, ChangeDetectionStrategy, Component, output, effect, ViewChild, DestroyRef } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { ActivatedRoute, RouterLink } from '@angular/router';
5
+ import * as i1 from '@angular/forms';
6
+ import { FormsModule } from '@angular/forms';
7
+ import { r as resolveToolKey, d as resolveToolDisplayName, V as VOICE_RECEPTIONIST_CONFIG, b as EdsIconComponent, A as AiReceptionistApiService, t as toolEventTrackKey } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-DClMfxeY.mjs';
8
+ import { D as DashboardSocketService, E as EdsElapsedPipe } from './edvak99-voice-receptionist-widget-eds-elapsed.pipe-BrMNsxB5.mjs';
9
+ import { C as CallRecordingPlayerComponent } from './edvak99-voice-receptionist-widget-call-recording-player.component-B2IuwsVH.mjs';
10
+ import { E as EdsAvatarComponent } from './edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs';
11
+
12
+ function mapChunkToLine(chunk) {
13
+ const at = Date.parse(chunk.timestamp);
14
+ return {
15
+ who: chunk.role === 'assistant' ? 'ai' : 'patient',
16
+ text: chunk.text,
17
+ at: Number.isFinite(at) ? at : Date.now(),
18
+ };
19
+ }
20
+ function formatEventTime(iso) {
21
+ return new Date(iso).toLocaleTimeString('en-US', {
22
+ hour: 'numeric',
23
+ minute: '2-digit',
24
+ second: '2-digit',
25
+ hour12: false,
26
+ });
27
+ }
28
+ function mapCallEventToTool(event) {
29
+ const p = event.payload;
30
+ const state = p['state'];
31
+ const rawName = String(p['name'] ?? event.eventType.replace(/^tool_/, ''));
32
+ const tech = String(p['tech'] ?? event.eventType);
33
+ const key = resolveToolKey(p['key'] != null ? String(p['key']) : undefined, rawName, tech);
34
+ return {
35
+ t: formatEventTime(event.occurredAt),
36
+ key,
37
+ name: resolveToolDisplayName(key, rawName),
38
+ tech,
39
+ result: String(p['result'] ?? ''),
40
+ state: state === 'ok' || state === 'warn' || state === 'error' || state === 'active'
41
+ ? state
42
+ : 'ok',
43
+ };
44
+ }
45
+ function mapLifecycleEventToTool(event) {
46
+ const p = event.payload;
47
+ return {
48
+ t: formatEventTime(event.occurredAt),
49
+ name: event.eventType.replace(/_/g, ' '),
50
+ tech: event.eventType,
51
+ result: String(p['detail'] ?? p['message'] ?? p['result'] ?? ''),
52
+ state: 'ok',
53
+ };
54
+ }
55
+ function isDuplicateTranscriptLine(lines, line) {
56
+ return lines.some((l) => l.who === line.who && l.text.trim() === line.text.trim());
57
+ }
58
+
59
+ /**
60
+ * Live call stream over shared Socket.io (`/dashboard` namespace).
61
+ * REST seeds transcript/tools once; socket delivers `new_chunk` + `call_event`.
62
+ */
63
+ class LiveTranscriptService {
64
+ constructor() {
65
+ this.socket = inject(DashboardSocketService);
66
+ this.config = inject(VOICE_RECEPTIONIST_CONFIG);
67
+ this.lines = signal([]);
68
+ this.patientInterim = signal(null);
69
+ this.aiInterim = signal(null);
70
+ this.tools = signal([]);
71
+ this.connected = this.socket.connected;
72
+ this.error = signal(null);
73
+ this.callEnded = signal(false);
74
+ this.summaryRequested = signal(false);
75
+ this.activeCallId = null;
76
+ this.watching = false;
77
+ this.mockTimer = null;
78
+ this.mockIndex = 0;
79
+ this.chunkHandler = (chunk) => this.handleChunk(chunk);
80
+ this.eventHandler = (event) => this.handleCallEvent(event);
81
+ this.onSocketConnect = () => {
82
+ if (this.activeCallId) {
83
+ this.socket.emit('watch_call', { callId: this.activeCallId });
84
+ }
85
+ };
86
+ }
87
+ seed(transcript, toolEvents) {
88
+ this.lines.set(transcript.map((line) => ({ ...line })));
89
+ this.tools.set(toolEvents.map((tool) => ({ ...tool })));
90
+ }
91
+ watch(callId, live = true) {
92
+ const id = callId.trim();
93
+ if (!id || !live)
94
+ return;
95
+ const switchingCall = this.activeCallId !== id;
96
+ if (switchingCall) {
97
+ this.unsubscribeSocketEvents();
98
+ this.clearInterim();
99
+ this.callEnded.set(false);
100
+ this.summaryRequested.set(false);
101
+ this.activeCallId = id;
102
+ }
103
+ else if (this.watching) {
104
+ return;
105
+ }
106
+ if (this.config.useMockApi) {
107
+ this.watching = true;
108
+ this.startMockStream(id);
109
+ return;
110
+ }
111
+ if (!this.watching) {
112
+ this.socket.acquire();
113
+ this.socket.on('new_chunk', this.chunkHandler);
114
+ this.socket.on('call_event', this.eventHandler);
115
+ this.socket.onConnect(this.onSocketConnect);
116
+ this.watching = true;
117
+ }
118
+ if (this.socket.connected()) {
119
+ this.socket.emit('watch_call', { callId: id });
120
+ }
121
+ }
122
+ stop() {
123
+ this.activeCallId = null;
124
+ this.unsubscribeSocketEvents();
125
+ this.resetStreamState();
126
+ }
127
+ markCompleted() {
128
+ this.callEnded.set(true);
129
+ this.unsubscribeSocketEvents();
130
+ this.clearInterim();
131
+ }
132
+ unsubscribeSocketEvents() {
133
+ this.stopMockStream();
134
+ if (!this.watching)
135
+ return;
136
+ this.socket.off('new_chunk', this.chunkHandler);
137
+ this.socket.off('call_event', this.eventHandler);
138
+ this.socket.offConnect(this.onSocketConnect);
139
+ this.socket.release();
140
+ this.watching = false;
141
+ }
142
+ handleChunk(chunk) {
143
+ if (!chunk?.text?.trim())
144
+ return;
145
+ if (chunk.role === 'user') {
146
+ if (chunk.isFinal) {
147
+ this.patientInterim.set(null);
148
+ this.appendLineIfNew(mapChunkToLine(chunk));
149
+ }
150
+ else {
151
+ this.patientInterim.set(chunk.text);
152
+ }
153
+ return;
154
+ }
155
+ if (chunk.isFinal) {
156
+ this.aiInterim.set(null);
157
+ this.appendLineIfNew(mapChunkToLine(chunk));
158
+ }
159
+ else {
160
+ this.aiInterim.set(chunk.text);
161
+ }
162
+ }
163
+ handleCallEvent(event) {
164
+ if (!event?.eventType)
165
+ return;
166
+ if (event.eventType.startsWith('tool_')) {
167
+ this.tools.update((prev) => [...prev, mapCallEventToTool(event)]);
168
+ return;
169
+ }
170
+ if (event.eventType === 'call_completed') {
171
+ this.markCompleted();
172
+ return;
173
+ }
174
+ if (event.eventType === 'summary_generated') {
175
+ this.summaryRequested.set(true);
176
+ return;
177
+ }
178
+ this.tools.update((prev) => [...prev, mapLifecycleEventToTool(event)]);
179
+ }
180
+ appendLineIfNew(line) {
181
+ this.lines.update((prev) => {
182
+ if (isDuplicateTranscriptLine(prev, line))
183
+ return prev;
184
+ return [...prev, line];
185
+ });
186
+ }
187
+ startMockStream(callId) {
188
+ this.stopMockStream();
189
+ this.mockIndex = 0;
190
+ const mockChunks = [
191
+ {
192
+ role: 'user',
193
+ text: 'Hi, I need to schedule an appointment.',
194
+ isFinal: false,
195
+ timestamp: new Date().toISOString(),
196
+ },
197
+ {
198
+ role: 'user',
199
+ text: 'Hi, I need to schedule an appointment.',
200
+ isFinal: true,
201
+ timestamp: new Date().toISOString(),
202
+ },
203
+ {
204
+ role: 'assistant',
205
+ text: 'Of course — I can help with that. Which provider would you prefer?',
206
+ isFinal: true,
207
+ timestamp: new Date().toISOString(),
208
+ },
209
+ ];
210
+ const mockEvents = [
211
+ {
212
+ eventType: 'tool_check_doctor_availability',
213
+ occurredAt: new Date().toISOString(),
214
+ payload: {
215
+ key: 'check_doctor_availability',
216
+ name: 'Checked Doctor Availability',
217
+ tech: 'tool_check_doctor_availability',
218
+ result: 'Found 3 available time slots',
219
+ state: 'ok',
220
+ },
221
+ },
222
+ ];
223
+ this.mockTimer = setInterval(() => {
224
+ if (this.activeCallId !== callId) {
225
+ this.stopMockStream();
226
+ return;
227
+ }
228
+ if (this.mockIndex < mockChunks.length) {
229
+ this.handleChunk(mockChunks[this.mockIndex]);
230
+ this.mockIndex++;
231
+ return;
232
+ }
233
+ if (this.mockIndex === mockChunks.length) {
234
+ this.handleCallEvent(mockEvents[0]);
235
+ this.mockIndex++;
236
+ }
237
+ }, 1500);
238
+ }
239
+ stopMockStream() {
240
+ if (this.mockTimer) {
241
+ clearInterval(this.mockTimer);
242
+ this.mockTimer = null;
243
+ }
244
+ }
245
+ clearInterim() {
246
+ this.patientInterim.set(null);
247
+ this.aiInterim.set(null);
248
+ }
249
+ resetStreamState() {
250
+ this.lines.set([]);
251
+ this.tools.set([]);
252
+ this.clearInterim();
253
+ this.error.set(null);
254
+ this.callEnded.set(false);
255
+ this.summaryRequested.set(false);
256
+ }
257
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LiveTranscriptService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
258
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LiveTranscriptService, providedIn: 'root' }); }
259
+ }
260
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LiveTranscriptService, decorators: [{
261
+ type: Injectable,
262
+ args: [{ providedIn: 'root' }]
263
+ }] });
264
+
265
+ class EdsWaveformComponent {
266
+ constructor() {
267
+ this.active = input(true);
268
+ this.tone = input('brand');
269
+ this.bars = Array.from({ length: 28 }, (_, i) => i);
270
+ }
271
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsWaveformComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
272
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: EdsWaveformComponent, isStandalone: true, selector: "eds-waveform", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
273
+ <div
274
+ class="eds-waveform"
275
+ [class.eds-waveform--inactive]="!active()"
276
+ [attr.data-tone]="tone()"
277
+ aria-hidden="true"
278
+ >
279
+ @for (i of bars; track i) {
280
+ <span class="eds-waveform__bar" [style.animation-delay.ms]="i * 70"></span>
281
+ }
282
+ </div>
283
+ `, isInline: true, styles: [".eds-waveform{display:flex;align-items:flex-end;gap:3px;height:20px}.eds-waveform__bar{width:3px;height:100%;border-radius:3px;transform-origin:bottom;animation:eds-wave 1.1s ease-in-out infinite}.eds-waveform[data-tone=brand] .eds-waveform__bar{background:var(--brand-400, #5aa3f6)}.eds-waveform[data-tone=violet] .eds-waveform__bar{background:var(--violet-400, #a684ff)}.eds-waveform--inactive .eds-waveform__bar{height:4px;opacity:.3;animation:none;align-self:flex-end}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
284
+ }
285
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EdsWaveformComponent, decorators: [{
286
+ type: Component,
287
+ args: [{ selector: 'eds-waveform', standalone: true, template: `
288
+ <div
289
+ class="eds-waveform"
290
+ [class.eds-waveform--inactive]="!active()"
291
+ [attr.data-tone]="tone()"
292
+ aria-hidden="true"
293
+ >
294
+ @for (i of bars; track i) {
295
+ <span class="eds-waveform__bar" [style.animation-delay.ms]="i * 70"></span>
296
+ }
297
+ </div>
298
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".eds-waveform{display:flex;align-items:flex-end;gap:3px;height:20px}.eds-waveform__bar{width:3px;height:100%;border-radius:3px;transform-origin:bottom;animation:eds-wave 1.1s ease-in-out infinite}.eds-waveform[data-tone=brand] .eds-waveform__bar{background:var(--brand-400, #5aa3f6)}.eds-waveform[data-tone=violet] .eds-waveform__bar{background:var(--violet-400, #a684ff)}.eds-waveform--inactive .eds-waveform__bar{height:4px;opacity:.3;animation:none;align-self:flex-end}\n"] }]
299
+ }] });
300
+
301
+ class LiveTranscriptPanelComponent {
302
+ constructor() {
303
+ this.live = input(true);
304
+ this.paused = input(false);
305
+ this.humanTakeover = input(false);
306
+ this.showStaffInput = input(false);
307
+ this.staffMessage = input('');
308
+ this.staffMessageChange = output();
309
+ this.sendMessage = output();
310
+ this.stream = inject(LiveTranscriptService);
311
+ this.lines = this.stream.lines;
312
+ this.patientInterim = this.stream.patientInterim;
313
+ this.aiInterim = this.stream.aiInterim;
314
+ this.connected = this.stream.connected;
315
+ this.error = this.stream.error;
316
+ this.shouldScroll = false;
317
+ effect(() => {
318
+ this.lines();
319
+ this.patientInterim();
320
+ this.aiInterim();
321
+ this.requestScroll();
322
+ });
323
+ }
324
+ ngAfterViewChecked() {
325
+ if (this.shouldScroll && this.scrollHost) {
326
+ const el = this.scrollHost.nativeElement;
327
+ el.scrollTop = el.scrollHeight;
328
+ this.shouldScroll = false;
329
+ }
330
+ }
331
+ waveformActive() {
332
+ return this.live() && this.connected() && !this.paused();
333
+ }
334
+ waveformTone() {
335
+ return this.humanTakeover() ? 'violet' : 'brand';
336
+ }
337
+ recLabel() {
338
+ if (this.paused())
339
+ return 'PAUSED';
340
+ if (this.live() && this.connected())
341
+ return 'REC';
342
+ if (this.error())
343
+ return 'ERROR';
344
+ return this.live() ? 'CONNECT' : 'LOG';
345
+ }
346
+ roleLabel(who) {
347
+ switch (who) {
348
+ case 'ai':
349
+ return 'Ava · AI';
350
+ case 'patient':
351
+ return 'Patient';
352
+ case 'staff':
353
+ return 'Staff';
354
+ default:
355
+ return 'System';
356
+ }
357
+ }
358
+ speakerIcon(who) {
359
+ switch (who) {
360
+ case 'ai':
361
+ return 'graphic_eq';
362
+ case 'staff':
363
+ return 'support_agent';
364
+ case 'patient':
365
+ return 'person';
366
+ default:
367
+ return 'info';
368
+ }
369
+ }
370
+ systemIcon(flag) {
371
+ if (flag === 'verify')
372
+ return 'verified_user';
373
+ if (flag === 'fail' || flag === 'escalate')
374
+ return 'bolt';
375
+ return 'info';
376
+ }
377
+ systemClass(flag) {
378
+ if (flag === 'verify')
379
+ return 'live-transcript-panel__system--verify';
380
+ if (flag === 'fail' || flag === 'escalate')
381
+ return 'live-transcript-panel__system--warn';
382
+ return 'live-transcript-panel__system--info';
383
+ }
384
+ msgClass(who) {
385
+ return `live-transcript-panel__msg--${who}`;
386
+ }
387
+ avatarClass(who) {
388
+ return `live-transcript-panel__avatar--${who}`;
389
+ }
390
+ onStaffInput(value) {
391
+ this.staffMessageChange.emit(value);
392
+ }
393
+ onSend() {
394
+ this.sendMessage.emit();
395
+ }
396
+ requestScroll() {
397
+ this.shouldScroll = true;
398
+ }
399
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LiveTranscriptPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
400
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: LiveTranscriptPanelComponent, isStandalone: true, selector: "eds-live-transcript-panel", inputs: { live: { classPropertyName: "live", publicName: "live", isSignal: true, isRequired: false, transformFunction: null }, paused: { classPropertyName: "paused", publicName: "paused", isSignal: true, isRequired: false, transformFunction: null }, humanTakeover: { classPropertyName: "humanTakeover", publicName: "humanTakeover", isSignal: true, isRequired: false, transformFunction: null }, showStaffInput: { classPropertyName: "showStaffInput", publicName: "showStaffInput", isSignal: true, isRequired: false, transformFunction: null }, staffMessage: { classPropertyName: "staffMessage", publicName: "staffMessage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { staffMessageChange: "staffMessageChange", sendMessage: "sendMessage" }, viewQueries: [{ propertyName: "scrollHost", first: true, predicate: ["scrollHost"], descendants: true }], ngImport: i0, template: "<section class=\"live-transcript-panel\" aria-labelledby=\"live-transcript-title\">\n <header class=\"live-transcript-panel__header\">\n <h2 id=\"live-transcript-title\" class=\"live-transcript-panel__title\">Live transcript</h2>\n <div class=\"live-transcript-panel__rec\">\n <eds-waveform [active]=\"waveformActive()\" [tone]=\"waveformTone()\" />\n <span class=\"live-transcript-panel__rec-label\">{{ recLabel() }}</span>\n </div>\n </header>\n\n <div #scrollHost class=\"live-transcript-panel__scroll\" role=\"log\" aria-live=\"polite\">\n @for (line of lines(); track $index) {\n @if (line.who === 'system') {\n <div class=\"live-transcript-panel__system eds-animate-fade-up\" [class]=\"systemClass(line.flag)\">\n <eds-icon [name]=\"systemIcon(line.flag)\" [size]=\"14\" />\n <span>{{ line.text }}</span>\n </div>\n } @else {\n <article class=\"live-transcript-panel__msg eds-animate-fade-up\" [class]=\"msgClass(line.who)\">\n <div class=\"live-transcript-panel__avatar\" [class]=\"avatarClass(line.who)\">\n <eds-icon [name]=\"speakerIcon(line.who)\" [size]=\"16\" />\n </div>\n <div class=\"live-transcript-panel__content\">\n <span class=\"live-transcript-panel__label\">{{ roleLabel(line.who) }}</span>\n <div class=\"live-transcript-panel__bubble\">\n <p>{{ line.text }}</p>\n </div>\n </div>\n </article>\n }\n }\n\n @if (patientInterim(); as interim) {\n <article class=\"live-transcript-panel__msg live-transcript-panel__msg--patient eds-animate-fade-up\">\n <div class=\"live-transcript-panel__avatar live-transcript-panel__avatar--patient\">\n <eds-icon name=\"person\" [size]=\"16\" />\n </div>\n <div class=\"live-transcript-panel__content\">\n <span class=\"live-transcript-panel__label\">Patient \u00B7 listening</span>\n <div class=\"live-transcript-panel__bubble live-transcript-panel__bubble--interim\">\n <p>{{ interim }}</p>\n </div>\n </div>\n </article>\n }\n\n @if (aiInterim(); as interim) {\n <article class=\"live-transcript-panel__msg live-transcript-panel__msg--ai eds-animate-fade-up\">\n <div class=\"live-transcript-panel__avatar live-transcript-panel__avatar--ai\">\n <eds-icon name=\"graphic_eq\" [size]=\"16\" />\n </div>\n <div class=\"live-transcript-panel__content\">\n <span class=\"live-transcript-panel__label\">Ava \u00B7 AI</span>\n <div class=\"live-transcript-panel__bubble live-transcript-panel__bubble--interim\">\n <p>{{ interim }}<span class=\"live-transcript-panel__caret\" aria-hidden=\"true\"></span></p>\n </div>\n </div>\n </article>\n }\n\n @if (lines().length === 0 && !patientInterim() && !aiInterim()) {\n <p class=\"live-transcript-panel__empty\">Waiting for conversation\u2026</p>\n }\n </div>\n\n @if (showStaffInput()) {\n <footer class=\"live-transcript-panel__footer\">\n <input\n type=\"text\"\n class=\"live-transcript-panel__input\"\n placeholder=\"Type to speak as staff\u2026\"\n [ngModel]=\"staffMessage()\"\n (ngModelChange)=\"onStaffInput($event)\"\n (keyup.enter)=\"onSend()\"\n />\n <button type=\"button\" class=\"live-transcript-panel__send\" (click)=\"onSend()\">\n <eds-icon name=\"send\" [size]=\"18\" />\n Send\n </button>\n </footer>\n }\n</section>\n", styles: [".live-transcript-panel{display:flex;flex-direction:column;min-height:0;height:calc(100vh - 234px);min-height:460px;background:var(--slate-900);border:1px solid var(--slate-800);border-radius:var(--radius-lg)}.live-transcript-panel__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-bottom:1px solid var(--slate-800);flex-shrink:0}.live-transcript-panel__title{margin:0;font:600 11px/1 var(--font-mono);letter-spacing:.08em;text-transform:uppercase;color:var(--slate-400)}.live-transcript-panel__rec{display:flex;align-items:center;gap:10px}.live-transcript-panel__rec-label{font:600 11px/1 var(--font-mono);letter-spacing:.06em;color:var(--slate-500)}.live-transcript-panel__scroll{flex:1;overflow:auto;padding:18px;display:flex;flex-direction:column;gap:14px}.live-transcript-panel__empty{margin:0;text-align:center;padding:32px 12px;font:400 13px/1.4 var(--font-body);color:var(--slate-500)}.live-transcript-panel__system{align-self:center;display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:var(--radius-pill);border:1px solid var(--slate-800);font:500 12px/1 var(--font-mono);max-width:90%;text-align:center}.live-transcript-panel__system--verify{color:var(--green-400);background:#0abf521a;border-color:#0abf524d}.live-transcript-panel__system--warn{color:var(--amber-300);background:#fe9a001a;border-color:#fe9a004d}.live-transcript-panel__system--info{color:var(--slate-300);background:var(--slate-900);border-color:var(--slate-800)}.live-transcript-panel__msg{display:flex;gap:10px;max-width:560px}.live-transcript-panel__msg--ai,.live-transcript-panel__msg--staff{margin-left:auto;flex-direction:row-reverse}.live-transcript-panel__msg--ai .live-transcript-panel__content,.live-transcript-panel__msg--staff .live-transcript-panel__content{align-items:flex-end}.live-transcript-panel__msg--ai .live-transcript-panel__bubble,.live-transcript-panel__msg--staff .live-transcript-panel__bubble{border-radius:12px 4px 12px 12px}.live-transcript-panel__msg--typing .live-transcript-panel__bubble{padding:12px 16px}.live-transcript-panel__avatar{width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff}.live-transcript-panel__avatar--ai{background:linear-gradient(135deg,var(--brand-500),var(--violet-500))}.live-transcript-panel__avatar--staff{background:#8e51ff2e;color:var(--violet-400);border:1px solid rgba(142,81,255,.35)}.live-transcript-panel__avatar--patient{background:var(--slate-800);color:var(--slate-400)}.live-transcript-panel__content{display:flex;flex-direction:column;gap:4px;min-width:0}.live-transcript-panel__label{font:600 11px/1 var(--font-mono);letter-spacing:.04em;color:var(--slate-500)}.live-transcript-panel__msg--ai .live-transcript-panel__label{color:var(--brand-400)}.live-transcript-panel__msg--staff .live-transcript-panel__label{color:var(--purple-300)}.live-transcript-panel__bubble{padding:9px 13px;border-radius:4px 12px 12px;background:#3681f41f;border:1px solid rgba(54,129,244,.28)}.live-transcript-panel__bubble p{margin:0;font:400 13px/19px var(--font-sans);color:var(--gray-100)}.live-transcript-panel__bubble--interim{opacity:.85;font-style:italic}.live-transcript-panel__msg--patient .live-transcript-panel__bubble{background:var(--slate-900);border-color:var(--slate-800)}.live-transcript-panel__msg--staff .live-transcript-panel__bubble{background:#8e51ff24;border-color:#8e51ff4d}.live-transcript-panel__msg--staff .live-transcript-panel__bubble p{color:var(--purple-300)}.live-transcript-panel__caret{display:inline-block;width:7px;height:14px;margin-left:2px;vertical-align:text-bottom;background:var(--brand-400);animation:eds-caret 1s step-end infinite}.live-transcript-panel__dots{display:inline-flex;align-items:center;gap:5px;height:14px}.live-transcript-panel__dots span{width:6px;height:6px;border-radius:50%;background:var(--brand-400);animation:eds-blink 1.4s ease-in-out infinite}.live-transcript-panel__dots span:nth-child(2){animation-delay:.2s}.live-transcript-panel__dots span:nth-child(3){animation-delay:.4s}.live-transcript-panel__footer{display:flex;gap:8px;padding:12px;border-top:1px solid var(--slate-800);flex-shrink:0}.live-transcript-panel__input{flex:1;height:38px;padding:0 14px;border-radius:var(--radius-md);border:1px solid var(--slate-700);background:var(--slate-950);color:var(--gray-100);font:400 13px/1 var(--font-sans)}.live-transcript-panel__input::placeholder{color:var(--slate-500)}.live-transcript-panel__input:focus{outline:none;border-color:var(--violet-400)}.live-transcript-panel__send{display:inline-flex;align-items:center;gap:6px;height:38px;padding:0 14px;border:none;border-radius:var(--radius-md);background:#8e51ff38;color:var(--purple-300);font:600 13px/1 var(--font-sans);cursor:pointer}.live-transcript-panel__send:hover{background:var(--violet-500);color:#fff}@media(max-width:900px){.live-transcript-panel{height:auto;min-height:360px;max-height:60vh}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }, { kind: "component", type: EdsWaveformComponent, selector: "eds-waveform", inputs: ["active", "tone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
401
+ }
402
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LiveTranscriptPanelComponent, decorators: [{
403
+ type: Component,
404
+ args: [{ selector: 'eds-live-transcript-panel', imports: [FormsModule, EdsIconComponent, EdsWaveformComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"live-transcript-panel\" aria-labelledby=\"live-transcript-title\">\n <header class=\"live-transcript-panel__header\">\n <h2 id=\"live-transcript-title\" class=\"live-transcript-panel__title\">Live transcript</h2>\n <div class=\"live-transcript-panel__rec\">\n <eds-waveform [active]=\"waveformActive()\" [tone]=\"waveformTone()\" />\n <span class=\"live-transcript-panel__rec-label\">{{ recLabel() }}</span>\n </div>\n </header>\n\n <div #scrollHost class=\"live-transcript-panel__scroll\" role=\"log\" aria-live=\"polite\">\n @for (line of lines(); track $index) {\n @if (line.who === 'system') {\n <div class=\"live-transcript-panel__system eds-animate-fade-up\" [class]=\"systemClass(line.flag)\">\n <eds-icon [name]=\"systemIcon(line.flag)\" [size]=\"14\" />\n <span>{{ line.text }}</span>\n </div>\n } @else {\n <article class=\"live-transcript-panel__msg eds-animate-fade-up\" [class]=\"msgClass(line.who)\">\n <div class=\"live-transcript-panel__avatar\" [class]=\"avatarClass(line.who)\">\n <eds-icon [name]=\"speakerIcon(line.who)\" [size]=\"16\" />\n </div>\n <div class=\"live-transcript-panel__content\">\n <span class=\"live-transcript-panel__label\">{{ roleLabel(line.who) }}</span>\n <div class=\"live-transcript-panel__bubble\">\n <p>{{ line.text }}</p>\n </div>\n </div>\n </article>\n }\n }\n\n @if (patientInterim(); as interim) {\n <article class=\"live-transcript-panel__msg live-transcript-panel__msg--patient eds-animate-fade-up\">\n <div class=\"live-transcript-panel__avatar live-transcript-panel__avatar--patient\">\n <eds-icon name=\"person\" [size]=\"16\" />\n </div>\n <div class=\"live-transcript-panel__content\">\n <span class=\"live-transcript-panel__label\">Patient \u00B7 listening</span>\n <div class=\"live-transcript-panel__bubble live-transcript-panel__bubble--interim\">\n <p>{{ interim }}</p>\n </div>\n </div>\n </article>\n }\n\n @if (aiInterim(); as interim) {\n <article class=\"live-transcript-panel__msg live-transcript-panel__msg--ai eds-animate-fade-up\">\n <div class=\"live-transcript-panel__avatar live-transcript-panel__avatar--ai\">\n <eds-icon name=\"graphic_eq\" [size]=\"16\" />\n </div>\n <div class=\"live-transcript-panel__content\">\n <span class=\"live-transcript-panel__label\">Ava \u00B7 AI</span>\n <div class=\"live-transcript-panel__bubble live-transcript-panel__bubble--interim\">\n <p>{{ interim }}<span class=\"live-transcript-panel__caret\" aria-hidden=\"true\"></span></p>\n </div>\n </div>\n </article>\n }\n\n @if (lines().length === 0 && !patientInterim() && !aiInterim()) {\n <p class=\"live-transcript-panel__empty\">Waiting for conversation\u2026</p>\n }\n </div>\n\n @if (showStaffInput()) {\n <footer class=\"live-transcript-panel__footer\">\n <input\n type=\"text\"\n class=\"live-transcript-panel__input\"\n placeholder=\"Type to speak as staff\u2026\"\n [ngModel]=\"staffMessage()\"\n (ngModelChange)=\"onStaffInput($event)\"\n (keyup.enter)=\"onSend()\"\n />\n <button type=\"button\" class=\"live-transcript-panel__send\" (click)=\"onSend()\">\n <eds-icon name=\"send\" [size]=\"18\" />\n Send\n </button>\n </footer>\n }\n</section>\n", styles: [".live-transcript-panel{display:flex;flex-direction:column;min-height:0;height:calc(100vh - 234px);min-height:460px;background:var(--slate-900);border:1px solid var(--slate-800);border-radius:var(--radius-lg)}.live-transcript-panel__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-bottom:1px solid var(--slate-800);flex-shrink:0}.live-transcript-panel__title{margin:0;font:600 11px/1 var(--font-mono);letter-spacing:.08em;text-transform:uppercase;color:var(--slate-400)}.live-transcript-panel__rec{display:flex;align-items:center;gap:10px}.live-transcript-panel__rec-label{font:600 11px/1 var(--font-mono);letter-spacing:.06em;color:var(--slate-500)}.live-transcript-panel__scroll{flex:1;overflow:auto;padding:18px;display:flex;flex-direction:column;gap:14px}.live-transcript-panel__empty{margin:0;text-align:center;padding:32px 12px;font:400 13px/1.4 var(--font-body);color:var(--slate-500)}.live-transcript-panel__system{align-self:center;display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:var(--radius-pill);border:1px solid var(--slate-800);font:500 12px/1 var(--font-mono);max-width:90%;text-align:center}.live-transcript-panel__system--verify{color:var(--green-400);background:#0abf521a;border-color:#0abf524d}.live-transcript-panel__system--warn{color:var(--amber-300);background:#fe9a001a;border-color:#fe9a004d}.live-transcript-panel__system--info{color:var(--slate-300);background:var(--slate-900);border-color:var(--slate-800)}.live-transcript-panel__msg{display:flex;gap:10px;max-width:560px}.live-transcript-panel__msg--ai,.live-transcript-panel__msg--staff{margin-left:auto;flex-direction:row-reverse}.live-transcript-panel__msg--ai .live-transcript-panel__content,.live-transcript-panel__msg--staff .live-transcript-panel__content{align-items:flex-end}.live-transcript-panel__msg--ai .live-transcript-panel__bubble,.live-transcript-panel__msg--staff .live-transcript-panel__bubble{border-radius:12px 4px 12px 12px}.live-transcript-panel__msg--typing .live-transcript-panel__bubble{padding:12px 16px}.live-transcript-panel__avatar{width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff}.live-transcript-panel__avatar--ai{background:linear-gradient(135deg,var(--brand-500),var(--violet-500))}.live-transcript-panel__avatar--staff{background:#8e51ff2e;color:var(--violet-400);border:1px solid rgba(142,81,255,.35)}.live-transcript-panel__avatar--patient{background:var(--slate-800);color:var(--slate-400)}.live-transcript-panel__content{display:flex;flex-direction:column;gap:4px;min-width:0}.live-transcript-panel__label{font:600 11px/1 var(--font-mono);letter-spacing:.04em;color:var(--slate-500)}.live-transcript-panel__msg--ai .live-transcript-panel__label{color:var(--brand-400)}.live-transcript-panel__msg--staff .live-transcript-panel__label{color:var(--purple-300)}.live-transcript-panel__bubble{padding:9px 13px;border-radius:4px 12px 12px;background:#3681f41f;border:1px solid rgba(54,129,244,.28)}.live-transcript-panel__bubble p{margin:0;font:400 13px/19px var(--font-sans);color:var(--gray-100)}.live-transcript-panel__bubble--interim{opacity:.85;font-style:italic}.live-transcript-panel__msg--patient .live-transcript-panel__bubble{background:var(--slate-900);border-color:var(--slate-800)}.live-transcript-panel__msg--staff .live-transcript-panel__bubble{background:#8e51ff24;border-color:#8e51ff4d}.live-transcript-panel__msg--staff .live-transcript-panel__bubble p{color:var(--purple-300)}.live-transcript-panel__caret{display:inline-block;width:7px;height:14px;margin-left:2px;vertical-align:text-bottom;background:var(--brand-400);animation:eds-caret 1s step-end infinite}.live-transcript-panel__dots{display:inline-flex;align-items:center;gap:5px;height:14px}.live-transcript-panel__dots span{width:6px;height:6px;border-radius:50%;background:var(--brand-400);animation:eds-blink 1.4s ease-in-out infinite}.live-transcript-panel__dots span:nth-child(2){animation-delay:.2s}.live-transcript-panel__dots span:nth-child(3){animation-delay:.4s}.live-transcript-panel__footer{display:flex;gap:8px;padding:12px;border-top:1px solid var(--slate-800);flex-shrink:0}.live-transcript-panel__input{flex:1;height:38px;padding:0 14px;border-radius:var(--radius-md);border:1px solid var(--slate-700);background:var(--slate-950);color:var(--gray-100);font:400 13px/1 var(--font-sans)}.live-transcript-panel__input::placeholder{color:var(--slate-500)}.live-transcript-panel__input:focus{outline:none;border-color:var(--violet-400)}.live-transcript-panel__send{display:inline-flex;align-items:center;gap:6px;height:38px;padding:0 14px;border:none;border-radius:var(--radius-md);background:#8e51ff38;color:var(--purple-300);font:600 13px/1 var(--font-sans);cursor:pointer}.live-transcript-panel__send:hover{background:var(--violet-500);color:#fff}@media(max-width:900px){.live-transcript-panel{height:auto;min-height:360px;max-height:60vh}}\n"] }]
405
+ }], ctorParameters: () => [], propDecorators: { scrollHost: [{
406
+ type: ViewChild,
407
+ args: ['scrollHost']
408
+ }] } });
409
+
410
+ class LiveMonitorPageComponent {
411
+ constructor() {
412
+ this.api = inject(AiReceptionistApiService);
413
+ this.route = inject(ActivatedRoute);
414
+ this.stream = inject(LiveTranscriptService);
415
+ this.destroyRef = inject(DestroyRef);
416
+ this.call = signal(null);
417
+ this.staffMessage = signal('');
418
+ this.loading = signal(true);
419
+ this.callId = signal('');
420
+ this.summary = signal(null);
421
+ this.summaryLoading = signal(false);
422
+ this.liveTools = this.stream.tools;
423
+ this.callEnded = this.stream.callEnded;
424
+ this.summaryLoaded = false;
425
+ this.toolTrackKey = toolEventTrackKey;
426
+ effect(() => {
427
+ if (this.stream.callEnded()) {
428
+ this.loadCallMetadataOnce();
429
+ }
430
+ });
431
+ effect(() => {
432
+ if (this.stream.summaryRequested() && !this.summaryLoaded) {
433
+ this.loadPostCallSummary();
434
+ }
435
+ });
436
+ }
437
+ ngOnInit() {
438
+ this.route.paramMap.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => {
439
+ const id = params.get('id') ?? '';
440
+ this.callId.set(id);
441
+ this.loading.set(true);
442
+ this.summary.set(null);
443
+ this.summaryLoaded = false;
444
+ this.stream.stop();
445
+ this.loadCallMetadata();
446
+ });
447
+ }
448
+ ngOnDestroy() {
449
+ this.stream.stop();
450
+ }
451
+ isTakeover() {
452
+ const c = this.call();
453
+ return c?.status === 'takeover' || !!c?.assigned;
454
+ }
455
+ isPaused() {
456
+ return !!this.call()?.aiPaused;
457
+ }
458
+ isLive() {
459
+ return !!this.call() && !this.callEnded();
460
+ }
461
+ confidenceLevel(confidence) {
462
+ if (confidence >= 70)
463
+ return 'high';
464
+ if (confidence < 50)
465
+ return 'low';
466
+ return 'mid';
467
+ }
468
+ sentimentPercent(sentiment) {
469
+ const map = {
470
+ calm: 85,
471
+ neutral: 60,
472
+ frustrated: 35,
473
+ urgent: 15,
474
+ };
475
+ return map[sentiment];
476
+ }
477
+ toolStateClass(state) {
478
+ return `live-monitor-page__tool--${state}`;
479
+ }
480
+ takeover() {
481
+ this.api.postTakeover(this.callId()).subscribe(() => this.refreshMetadata());
482
+ }
483
+ resumeAi() {
484
+ this.api.postResumeAi(this.callId()).subscribe(() => this.refreshMetadata());
485
+ }
486
+ transfer() {
487
+ this.api.postTransfer(this.callId()).subscribe(() => this.refreshMetadata());
488
+ }
489
+ endCall() {
490
+ const id = this.callId();
491
+ this.api.postEnd(id).subscribe(() => {
492
+ this.stream.markCompleted();
493
+ this.loadCallMetadataOnce();
494
+ this.loadPostCallSummary();
495
+ });
496
+ }
497
+ sendMessage() {
498
+ const text = this.staffMessage().trim();
499
+ if (!text)
500
+ return;
501
+ this.api.postMessage(this.callId(), text).subscribe(() => {
502
+ this.staffMessage.set('');
503
+ this.refreshMetadata();
504
+ });
505
+ }
506
+ /** Initial load — seeds transcript/tools from REST, then starts socket. */
507
+ loadCallMetadata() {
508
+ const id = this.callId();
509
+ if (!id)
510
+ return;
511
+ this.api.getLiveCall(id).subscribe(({ data }) => {
512
+ this.call.set(data);
513
+ this.loading.set(false);
514
+ if (data) {
515
+ this.stream.seed(data.transcript, data.tools);
516
+ if (!this.callEnded()) {
517
+ this.stream.watch(id, true);
518
+ }
519
+ }
520
+ });
521
+ }
522
+ /** Metadata-only refresh after staff actions — does not overwrite live transcript/tools. */
523
+ refreshMetadata() {
524
+ const id = this.callId();
525
+ if (!id)
526
+ return;
527
+ this.api.getLiveCall(id).subscribe(({ data }) => {
528
+ if (data)
529
+ this.call.set(data);
530
+ });
531
+ }
532
+ /** One-time metadata refresh after call ends (no socket). */
533
+ loadCallMetadataOnce() {
534
+ const id = this.callId();
535
+ if (!id)
536
+ return;
537
+ this.api.getLiveCall(id).subscribe(({ data }) => {
538
+ if (data) {
539
+ this.call.set(data);
540
+ }
541
+ });
542
+ }
543
+ loadPostCallSummary() {
544
+ const id = this.callId();
545
+ if (!id || this.summaryLoaded)
546
+ return;
547
+ this.summaryLoading.set(true);
548
+ this.api.getCallSummary(id).subscribe(({ data }) => {
549
+ this.summaryLoading.set(false);
550
+ this.summaryLoaded = true;
551
+ if (!data)
552
+ return;
553
+ this.summary.set(data.summary);
554
+ this.stream.seed(data.transcript, data.tools);
555
+ });
556
+ }
557
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LiveMonitorPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
558
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: LiveMonitorPageComponent, isStandalone: true, selector: "eds-live-monitor-page", ngImport: i0, template: "<div class=\"eds-page eds-page--dark live-monitor-page\">\n @if (loading()) {\n <p class=\"live-monitor-page__loading\">Loading call\u2026</p>\n } @else {\n @if (call(); as c) {\n <header class=\"live-monitor-page__header\">\n <a routerLink=\"../live\" class=\"live-monitor-page__back\" aria-label=\"Back to live calls\">\n <eds-icon name=\"arrow_back\" [size]=\"19\" />\n </a>\n\n <div class=\"live-monitor-page__header-main\">\n <eds-avatar\n [initials]=\"c.initials\"\n tone=\"brand\"\n [gradient]=\"c.matched\"\n [class.live-monitor-page__avatar--ring]=\"c.matched\"\n />\n\n <div class=\"live-monitor-page__caller\">\n <div class=\"live-monitor-page__name-row\">\n <h1 class=\"live-monitor-page__name\">{{ c.name }}</h1>\n <span class=\"live-monitor-page__patient-tag\" [attr.data-kind]=\"c.matched ? 'registered' : 'new'\">\n <eds-icon [name]=\"c.matched ? 'person_check' : 'person_add'\" [size]=\"14\" />\n {{ c.matched ? 'Registered' : 'New' }}\n </span>\n </div>\n <div class=\"live-monitor-page__meta-row\">\n <eds-icon [name]=\"c.direction === 'inbound' ? 'call_received' : 'call_made'\" [size]=\"14\" />\n <span>{{ c.phone }}</span>\n <span class=\"live-monitor-page__sep\">\u00B7</span>\n <span>{{ c.reason }}</span>\n </div>\n </div>\n\n <div class=\"live-monitor-page__header-right\">\n <span class=\"live-monitor-page__duration\">\n <span class=\"live-monitor-page__duration-dot\" aria-hidden=\"true\"></span>\n {{ c.started | edsElapsed }}\n </span>\n </div>\n </div>\n </header>\n\n @if (callEnded()) {\n <div class=\"live-monitor-page__completed-banner\" role=\"status\">\n <eds-icon name=\"check_circle\" [size]=\"18\" />\n Call ended\n </div>\n }\n\n <div class=\"live-monitor-page__grid\">\n <div class=\"live-monitor-page__main\">\n <eds-live-transcript-panel\n [live]=\"isLive()\"\n [paused]=\"isPaused()\"\n [humanTakeover]=\"isTakeover()\"\n [showStaffInput]=\"isTakeover() && !callEnded()\"\n [staffMessage]=\"staffMessage()\"\n (staffMessageChange)=\"staffMessage.set($event)\"\n (sendMessage)=\"sendMessage()\"\n />\n\n @if (callEnded()) {\n <section class=\"live-monitor-page__post-call\">\n <div class=\"live-monitor-page__summary\">\n <h2 class=\"live-monitor-page__section-title\">Call summary</h2>\n @if (summaryLoading()) {\n <p class=\"live-monitor-page__muted\">Generating summary\u2026</p>\n } @else if (summary()) {\n <p>{{ summary() }}</p>\n } @else {\n <p class=\"live-monitor-page__muted\">Summary will appear when processing completes.</p>\n }\n </div>\n <eds-call-recording-player [callId]=\"callId()\" />\n </section>\n }\n </div>\n\n <aside class=\"live-monitor-page__sidebar\">\n @if (!callEnded()) {\n <div class=\"live-monitor-page__orb-wrap\" [attr.data-mode]=\"c.status === 'transferring' ? 'transfer' : 'ai'\">\n <span class=\"live-monitor-page__ring live-monitor-page__ring--1\"></span>\n <span class=\"live-monitor-page__ring live-monitor-page__ring--2\"></span>\n <span class=\"live-monitor-page__orb\">\n <eds-icon name=\"smart_toy\" [size]=\"28\" />\n </span>\n <span class=\"live-monitor-page__confidence\" [attr.data-level]=\"confidenceLevel(c.confidence)\">\n AI confidence {{ c.confidence }}%\n </span>\n </div>\n\n <section class=\"live-monitor-page__sidebar-block\">\n <h3 class=\"live-monitor-page__section-title\">Patient sentiment</h3>\n <div class=\"live-monitor-page__sentiment-bar\">\n <span\n class=\"live-monitor-page__sentiment-fill\"\n [style.width.%]=\"sentimentPercent(c.sentiment)\"\n [attr.data-sentiment]=\"c.sentiment\"\n ></span>\n </div>\n <span class=\"live-monitor-page__sentiment-label\">{{ c.sentiment }}</span>\n </section>\n }\n\n <section class=\"live-monitor-page__sidebar-block\">\n <h3 class=\"live-monitor-page__section-title\">Tools used</h3>\n <ul class=\"live-monitor-page__tools\">\n @for (tool of liveTools(); track toolTrackKey(tool, $index)) {\n <li [class]=\"toolStateClass(tool.state)\">\n <span class=\"live-monitor-page__tool-time\">{{ tool.t }}</span>\n <div class=\"live-monitor-page__tool-body\">\n <span class=\"live-monitor-page__tool-name\">{{ tool.name }}</span>\n <span class=\"live-monitor-page__tool-result\">{{ tool.result }}</span>\n </div>\n @if (tool.state === 'active') {\n <eds-icon name=\"sync\" [size]=\"16\" class=\"live-monitor-page__tool-spin\" />\n }\n </li>\n } @empty {\n <li class=\"live-monitor-page__tools-empty\">No tool activity yet.</li>\n }\n </ul>\n </section>\n\n <section class=\"live-monitor-page__sidebar-block\">\n <h3 class=\"live-monitor-page__section-title\">Goal</h3>\n <p class=\"live-monitor-page__goal\">{{ c.goal }}</p>\n </section>\n </aside>\n </div>\n\n @if (!callEnded()) {\n <footer class=\"live-monitor-page__control-bar\">\n <div class=\"live-monitor-page__control-group\">\n @if (isTakeover()) {\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--violet\" (click)=\"resumeAi()\">\n <eds-icon name=\"smart_toy\" [size]=\"18\" /> Resume AI\n </button>\n } @else {\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--primary\" (click)=\"takeover()\">\n <eds-icon name=\"pan_tool_alt\" [size]=\"18\" /> Take over\n </button>\n }\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--neutral\" disabled title=\"Coming soon\">\n <eds-icon name=\"hearing\" [size]=\"18\" /> Listen\n </button>\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--neutral\" (click)=\"transfer()\">\n <eds-icon name=\"phone_forwarded\" [size]=\"18\" /> Transfer\n </button>\n </div>\n\n <span class=\"live-monitor-page__divider\" aria-hidden=\"true\"></span>\n\n <div class=\"live-monitor-page__control-group\">\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--neutral\" disabled title=\"Coming soon\">\n <eds-icon name=\"person_search\" [size]=\"18\" /> Open chart\n </button>\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--neutral\" disabled title=\"Coming soon\">\n <eds-icon name=\"add_task\" [size]=\"18\" /> Create task\n </button>\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--neutral\" disabled title=\"Coming soon\">\n <eds-icon name=\"edit_note\" [size]=\"18\" /> Add note\n </button>\n </div>\n\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--danger\" (click)=\"endCall()\">\n <eds-icon name=\"call_end\" [size]=\"18\" /> End call\n </button>\n </footer>\n }\n } @else {\n <p class=\"live-monitor-page__loading\">Call ended or not found.</p>\n }\n }\n</div>\n", styles: [".live-monitor-page{display:flex;flex-direction:column;min-height:calc(100vh - var(--eds-topbar-height));padding:var(--space-5);color:var(--gray-200)}.live-monitor-page__loading{font:400 13px/1.4 var(--font-body);color:var(--slate-400)}.live-monitor-page__header{display:flex;align-items:flex-start;gap:12px;margin-bottom:var(--space-4)}.live-monitor-page__back{width:36px;height:36px;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--radius-md);background:var(--slate-900);border:1px solid var(--slate-800);color:var(--gray-300);text-decoration:none;flex-shrink:0}.live-monitor-page__back:hover{background:var(--slate-800)}.live-monitor-page__header-main{flex:1;display:flex;align-items:center;gap:14px;min-width:0;flex-wrap:wrap}.live-monitor-page__avatar--ring{box-shadow:0 0 0 2px var(--brand-500)}.live-monitor-page__caller{flex:1;min-width:200px}.live-monitor-page__name-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.live-monitor-page__name{margin:0;font:700 18px/22px var(--font-sans);color:var(--gray-100)}.live-monitor-page__patient-tag{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:var(--radius-pill);font:500 11px/1 var(--font-mono);border:1px solid transparent}.live-monitor-page__patient-tag[data-kind=registered]{color:var(--green-400);background:#0abf5224;border-color:#0abf5247}.live-monitor-page__patient-tag[data-kind=new]{color:var(--brand-300);background:#3681f424;border-color:#3681f44d}.live-monitor-page__meta-row{display:flex;align-items:center;gap:6px;margin-top:4px;font:500 12px/1 var(--font-mono);color:var(--slate-400);flex-wrap:wrap}.live-monitor-page__sep{color:var(--slate-600)}.live-monitor-page__header-right{display:flex;flex-direction:column;align-items:flex-end;gap:4px;margin-left:auto}.live-monitor-page__duration{display:inline-flex;align-items:center;gap:8px;font:700 22px/1 var(--font-mono);color:var(--gray-100)}.live-monitor-page__duration-dot{width:9px;height:9px;border-radius:50%;background:var(--green-400);box-shadow:0 0 0 3px #0abf5224}.live-monitor-page__completed-banner{display:flex;align-items:center;gap:8px;padding:10px 14px;margin-bottom:var(--space-4);border-radius:var(--radius-lg);background:#0abf521f;color:var(--green-400);font:400 13px/1.4 var(--font-body)}.live-monitor-page__grid{display:grid;grid-template-columns:1fr 340px;gap:var(--space-4);flex:1;min-height:0}.live-monitor-page__main{display:flex;flex-direction:column;gap:var(--space-4);min-height:0}.live-monitor-page__main-tabs{display:flex;gap:4px;padding:4px;border-radius:var(--radius-md);background:var(--slate-900);border:1px solid var(--slate-800);width:fit-content}.live-monitor-page__main-tab{border:none;background:transparent;color:var(--slate-400);font:600 12px/1 var(--font-body);padding:8px 14px;border-radius:var(--radius-sm);cursor:pointer}.live-monitor-page__main-tab--active{background:var(--slate-800);color:var(--gray-100)}.live-monitor-page__api-logs{flex:1;min-height:280px;padding:16px;background:var(--slate-900);border:1px solid var(--slate-800)}.live-monitor-page__post-call{display:flex;flex-direction:column;gap:var(--space-4)}.live-monitor-page__summary{padding:16px;border-radius:var(--radius-lg);background:var(--slate-900);border:1px solid var(--slate-800);font:400 13px/1.5 var(--font-body);color:var(--slate-300)}.live-monitor-page__muted{color:var(--slate-500);margin:0}.live-monitor-page__sidebar{display:flex;flex-direction:column;gap:var(--space-4);min-height:0}.live-monitor-page__sidebar-block{padding:16px;border-radius:var(--radius-lg);background:var(--slate-900);border:1px solid var(--slate-800)}.live-monitor-page__section-title{margin:0 0 12px;font:600 11px/1 var(--font-mono);letter-spacing:.08em;text-transform:uppercase;color:var(--slate-400)}.live-monitor-page__orb-wrap{display:flex;flex-direction:column;align-items:center;gap:10px;padding:20px 16px;border-radius:var(--radius-lg);background:var(--slate-900);border:1px solid var(--slate-800);position:relative}.live-monitor-page__orb{width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;background:linear-gradient(135deg,var(--brand-500),var(--violet-500));animation:eds-breathe 3s ease-in-out infinite;position:relative;z-index:1}.live-monitor-page__ring{position:absolute;width:64px;height:64px;top:20px;border-radius:50%;border:2px solid var(--brand-400);animation:eds-ring 2s ease-out infinite}.live-monitor-page__ring--2{animation-delay:1s}.live-monitor-page__orb-wrap[data-mode=transfer] .live-monitor-page__orb{background:linear-gradient(135deg,var(--amber-400),var(--violet-500))}.live-monitor-page__confidence{font:500 12px/1 var(--font-mono);color:var(--slate-400)}.live-monitor-page__confidence[data-level=high]{color:var(--green-400)}.live-monitor-page__confidence[data-level=low]{color:var(--amber-400)}.live-monitor-page__sentiment-bar{height:6px;background:var(--slate-800);border-radius:3px;overflow:hidden;margin-bottom:8px}.live-monitor-page__sentiment-fill{display:block;height:100%;border-radius:3px;background:var(--green-400);transition:width .3s ease}.live-monitor-page__sentiment-fill[data-sentiment=frustrated],.live-monitor-page__sentiment-fill[data-sentiment=urgent]{background:var(--red-400)}.live-monitor-page__sentiment-fill[data-sentiment=neutral]{background:var(--amber-400)}.live-monitor-page__sentiment-label{font:500 12px/1 var(--font-mono);color:var(--slate-400);text-transform:capitalize}.live-monitor-page__tools{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px;max-height:280px;overflow:auto}.live-monitor-page__tools li{display:grid;grid-template-columns:auto 1fr auto;gap:10px;align-items:start;padding:10px;border-radius:var(--radius-md);background:var(--slate-950);border-left:3px solid var(--slate-700)}.live-monitor-page__tool-time{font:500 11px/1 var(--font-mono);color:var(--slate-500);white-space:nowrap}.live-monitor-page__tool-body{display:flex;flex-direction:column;gap:2px;min-width:0}.live-monitor-page__tool-name{font:600 13px/1.2 var(--font-sans);color:var(--gray-100)}.live-monitor-page__tool-result{font:400 12px/1.4 var(--font-body);color:var(--slate-400)}.live-monitor-page__tool-tech{font:500 10px/1 var(--font-mono);color:var(--slate-500)}.live-monitor-page__tool-spin{animation:eds-spin 1s linear infinite;color:var(--brand-400)}.live-monitor-page__tool--ok{border-left-color:var(--green-400)}.live-monitor-page__tool--warn{border-left-color:var(--amber-400)}.live-monitor-page__tool--error{border-left-color:var(--red-400)}.live-monitor-page__tool--active{border-left-color:var(--brand-400)}.live-monitor-page__tools-empty{border-left:none!important;background:transparent!important;padding:8px 0!important;font:400 13px/1.4 var(--font-body);color:var(--slate-500)}.live-monitor-page__goal{margin:0;font:400 13px/1.5 var(--font-body);color:var(--slate-300)}.live-monitor-page__control-bar{position:sticky;bottom:0;display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:var(--space-4);padding:12px 16px;border-radius:var(--radius-lg);background:var(--slate-900);border:1px solid var(--slate-800);box-shadow:0 -8px 24px #03071780}.live-monitor-page__control-group{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.live-monitor-page__divider{width:1px;height:24px;background:var(--slate-800);flex-shrink:0}.live-monitor-page__ctrl{display:inline-flex;align-items:center;gap:6px;height:38px;padding:0 14px;border:none;border-radius:var(--radius-md);font:600 13px/1 var(--font-sans);cursor:pointer;white-space:nowrap}.live-monitor-page__ctrl:disabled{opacity:.45;cursor:not-allowed}.live-monitor-page__ctrl--neutral{background:var(--slate-800);color:var(--gray-200)}.live-monitor-page__ctrl--neutral:hover:not(:disabled){background:var(--slate-700)}.live-monitor-page__ctrl--primary{background:var(--brand-600);color:#fff}.live-monitor-page__ctrl--primary:hover:not(:disabled){background:var(--brand-500)}.live-monitor-page__ctrl--violet{background:#8e51ff2e;color:var(--purple-300)}.live-monitor-page__ctrl--violet:hover:not(:disabled){background:var(--violet-500);color:#fff}.live-monitor-page__ctrl--danger{margin-left:auto;background:var(--red-500);color:#fff}.live-monitor-page__ctrl--danger:hover:not(:disabled){background:var(--red-400)}@media(max-width:960px){.live-monitor-page__grid{grid-template-columns:1fr}.live-monitor-page__header-right{align-items:flex-start;width:100%;margin-left:0}.live-monitor-page__ctrl--danger{margin-left:0;width:100%;justify-content:center}}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: EdsAvatarComponent, selector: "eds-avatar", inputs: ["initials", "tone", "gradient"] }, { kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }, { kind: "pipe", type: EdsElapsedPipe, name: "edsElapsed" }, { kind: "component", type: LiveTranscriptPanelComponent, selector: "eds-live-transcript-panel", inputs: ["live", "paused", "humanTakeover", "showStaffInput", "staffMessage"], outputs: ["staffMessageChange", "sendMessage"] }, { kind: "component", type: CallRecordingPlayerComponent, selector: "eds-call-recording-player", inputs: ["callId"], outputs: ["currentTimeChange", "durationChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
559
+ }
560
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: LiveMonitorPageComponent, decorators: [{
561
+ type: Component,
562
+ args: [{ selector: 'eds-live-monitor-page', imports: [
563
+ RouterLink,
564
+ FormsModule,
565
+ EdsAvatarComponent,
566
+ EdsIconComponent,
567
+ EdsElapsedPipe,
568
+ LiveTranscriptPanelComponent,
569
+ CallRecordingPlayerComponent,
570
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"eds-page eds-page--dark live-monitor-page\">\n @if (loading()) {\n <p class=\"live-monitor-page__loading\">Loading call\u2026</p>\n } @else {\n @if (call(); as c) {\n <header class=\"live-monitor-page__header\">\n <a routerLink=\"../live\" class=\"live-monitor-page__back\" aria-label=\"Back to live calls\">\n <eds-icon name=\"arrow_back\" [size]=\"19\" />\n </a>\n\n <div class=\"live-monitor-page__header-main\">\n <eds-avatar\n [initials]=\"c.initials\"\n tone=\"brand\"\n [gradient]=\"c.matched\"\n [class.live-monitor-page__avatar--ring]=\"c.matched\"\n />\n\n <div class=\"live-monitor-page__caller\">\n <div class=\"live-monitor-page__name-row\">\n <h1 class=\"live-monitor-page__name\">{{ c.name }}</h1>\n <span class=\"live-monitor-page__patient-tag\" [attr.data-kind]=\"c.matched ? 'registered' : 'new'\">\n <eds-icon [name]=\"c.matched ? 'person_check' : 'person_add'\" [size]=\"14\" />\n {{ c.matched ? 'Registered' : 'New' }}\n </span>\n </div>\n <div class=\"live-monitor-page__meta-row\">\n <eds-icon [name]=\"c.direction === 'inbound' ? 'call_received' : 'call_made'\" [size]=\"14\" />\n <span>{{ c.phone }}</span>\n <span class=\"live-monitor-page__sep\">\u00B7</span>\n <span>{{ c.reason }}</span>\n </div>\n </div>\n\n <div class=\"live-monitor-page__header-right\">\n <span class=\"live-monitor-page__duration\">\n <span class=\"live-monitor-page__duration-dot\" aria-hidden=\"true\"></span>\n {{ c.started | edsElapsed }}\n </span>\n </div>\n </div>\n </header>\n\n @if (callEnded()) {\n <div class=\"live-monitor-page__completed-banner\" role=\"status\">\n <eds-icon name=\"check_circle\" [size]=\"18\" />\n Call ended\n </div>\n }\n\n <div class=\"live-monitor-page__grid\">\n <div class=\"live-monitor-page__main\">\n <eds-live-transcript-panel\n [live]=\"isLive()\"\n [paused]=\"isPaused()\"\n [humanTakeover]=\"isTakeover()\"\n [showStaffInput]=\"isTakeover() && !callEnded()\"\n [staffMessage]=\"staffMessage()\"\n (staffMessageChange)=\"staffMessage.set($event)\"\n (sendMessage)=\"sendMessage()\"\n />\n\n @if (callEnded()) {\n <section class=\"live-monitor-page__post-call\">\n <div class=\"live-monitor-page__summary\">\n <h2 class=\"live-monitor-page__section-title\">Call summary</h2>\n @if (summaryLoading()) {\n <p class=\"live-monitor-page__muted\">Generating summary\u2026</p>\n } @else if (summary()) {\n <p>{{ summary() }}</p>\n } @else {\n <p class=\"live-monitor-page__muted\">Summary will appear when processing completes.</p>\n }\n </div>\n <eds-call-recording-player [callId]=\"callId()\" />\n </section>\n }\n </div>\n\n <aside class=\"live-monitor-page__sidebar\">\n @if (!callEnded()) {\n <div class=\"live-monitor-page__orb-wrap\" [attr.data-mode]=\"c.status === 'transferring' ? 'transfer' : 'ai'\">\n <span class=\"live-monitor-page__ring live-monitor-page__ring--1\"></span>\n <span class=\"live-monitor-page__ring live-monitor-page__ring--2\"></span>\n <span class=\"live-monitor-page__orb\">\n <eds-icon name=\"smart_toy\" [size]=\"28\" />\n </span>\n <span class=\"live-monitor-page__confidence\" [attr.data-level]=\"confidenceLevel(c.confidence)\">\n AI confidence {{ c.confidence }}%\n </span>\n </div>\n\n <section class=\"live-monitor-page__sidebar-block\">\n <h3 class=\"live-monitor-page__section-title\">Patient sentiment</h3>\n <div class=\"live-monitor-page__sentiment-bar\">\n <span\n class=\"live-monitor-page__sentiment-fill\"\n [style.width.%]=\"sentimentPercent(c.sentiment)\"\n [attr.data-sentiment]=\"c.sentiment\"\n ></span>\n </div>\n <span class=\"live-monitor-page__sentiment-label\">{{ c.sentiment }}</span>\n </section>\n }\n\n <section class=\"live-monitor-page__sidebar-block\">\n <h3 class=\"live-monitor-page__section-title\">Tools used</h3>\n <ul class=\"live-monitor-page__tools\">\n @for (tool of liveTools(); track toolTrackKey(tool, $index)) {\n <li [class]=\"toolStateClass(tool.state)\">\n <span class=\"live-monitor-page__tool-time\">{{ tool.t }}</span>\n <div class=\"live-monitor-page__tool-body\">\n <span class=\"live-monitor-page__tool-name\">{{ tool.name }}</span>\n <span class=\"live-monitor-page__tool-result\">{{ tool.result }}</span>\n </div>\n @if (tool.state === 'active') {\n <eds-icon name=\"sync\" [size]=\"16\" class=\"live-monitor-page__tool-spin\" />\n }\n </li>\n } @empty {\n <li class=\"live-monitor-page__tools-empty\">No tool activity yet.</li>\n }\n </ul>\n </section>\n\n <section class=\"live-monitor-page__sidebar-block\">\n <h3 class=\"live-monitor-page__section-title\">Goal</h3>\n <p class=\"live-monitor-page__goal\">{{ c.goal }}</p>\n </section>\n </aside>\n </div>\n\n @if (!callEnded()) {\n <footer class=\"live-monitor-page__control-bar\">\n <div class=\"live-monitor-page__control-group\">\n @if (isTakeover()) {\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--violet\" (click)=\"resumeAi()\">\n <eds-icon name=\"smart_toy\" [size]=\"18\" /> Resume AI\n </button>\n } @else {\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--primary\" (click)=\"takeover()\">\n <eds-icon name=\"pan_tool_alt\" [size]=\"18\" /> Take over\n </button>\n }\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--neutral\" disabled title=\"Coming soon\">\n <eds-icon name=\"hearing\" [size]=\"18\" /> Listen\n </button>\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--neutral\" (click)=\"transfer()\">\n <eds-icon name=\"phone_forwarded\" [size]=\"18\" /> Transfer\n </button>\n </div>\n\n <span class=\"live-monitor-page__divider\" aria-hidden=\"true\"></span>\n\n <div class=\"live-monitor-page__control-group\">\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--neutral\" disabled title=\"Coming soon\">\n <eds-icon name=\"person_search\" [size]=\"18\" /> Open chart\n </button>\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--neutral\" disabled title=\"Coming soon\">\n <eds-icon name=\"add_task\" [size]=\"18\" /> Create task\n </button>\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--neutral\" disabled title=\"Coming soon\">\n <eds-icon name=\"edit_note\" [size]=\"18\" /> Add note\n </button>\n </div>\n\n <button type=\"button\" class=\"live-monitor-page__ctrl live-monitor-page__ctrl--danger\" (click)=\"endCall()\">\n <eds-icon name=\"call_end\" [size]=\"18\" /> End call\n </button>\n </footer>\n }\n } @else {\n <p class=\"live-monitor-page__loading\">Call ended or not found.</p>\n }\n }\n</div>\n", styles: [".live-monitor-page{display:flex;flex-direction:column;min-height:calc(100vh - var(--eds-topbar-height));padding:var(--space-5);color:var(--gray-200)}.live-monitor-page__loading{font:400 13px/1.4 var(--font-body);color:var(--slate-400)}.live-monitor-page__header{display:flex;align-items:flex-start;gap:12px;margin-bottom:var(--space-4)}.live-monitor-page__back{width:36px;height:36px;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--radius-md);background:var(--slate-900);border:1px solid var(--slate-800);color:var(--gray-300);text-decoration:none;flex-shrink:0}.live-monitor-page__back:hover{background:var(--slate-800)}.live-monitor-page__header-main{flex:1;display:flex;align-items:center;gap:14px;min-width:0;flex-wrap:wrap}.live-monitor-page__avatar--ring{box-shadow:0 0 0 2px var(--brand-500)}.live-monitor-page__caller{flex:1;min-width:200px}.live-monitor-page__name-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.live-monitor-page__name{margin:0;font:700 18px/22px var(--font-sans);color:var(--gray-100)}.live-monitor-page__patient-tag{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:var(--radius-pill);font:500 11px/1 var(--font-mono);border:1px solid transparent}.live-monitor-page__patient-tag[data-kind=registered]{color:var(--green-400);background:#0abf5224;border-color:#0abf5247}.live-monitor-page__patient-tag[data-kind=new]{color:var(--brand-300);background:#3681f424;border-color:#3681f44d}.live-monitor-page__meta-row{display:flex;align-items:center;gap:6px;margin-top:4px;font:500 12px/1 var(--font-mono);color:var(--slate-400);flex-wrap:wrap}.live-monitor-page__sep{color:var(--slate-600)}.live-monitor-page__header-right{display:flex;flex-direction:column;align-items:flex-end;gap:4px;margin-left:auto}.live-monitor-page__duration{display:inline-flex;align-items:center;gap:8px;font:700 22px/1 var(--font-mono);color:var(--gray-100)}.live-monitor-page__duration-dot{width:9px;height:9px;border-radius:50%;background:var(--green-400);box-shadow:0 0 0 3px #0abf5224}.live-monitor-page__completed-banner{display:flex;align-items:center;gap:8px;padding:10px 14px;margin-bottom:var(--space-4);border-radius:var(--radius-lg);background:#0abf521f;color:var(--green-400);font:400 13px/1.4 var(--font-body)}.live-monitor-page__grid{display:grid;grid-template-columns:1fr 340px;gap:var(--space-4);flex:1;min-height:0}.live-monitor-page__main{display:flex;flex-direction:column;gap:var(--space-4);min-height:0}.live-monitor-page__main-tabs{display:flex;gap:4px;padding:4px;border-radius:var(--radius-md);background:var(--slate-900);border:1px solid var(--slate-800);width:fit-content}.live-monitor-page__main-tab{border:none;background:transparent;color:var(--slate-400);font:600 12px/1 var(--font-body);padding:8px 14px;border-radius:var(--radius-sm);cursor:pointer}.live-monitor-page__main-tab--active{background:var(--slate-800);color:var(--gray-100)}.live-monitor-page__api-logs{flex:1;min-height:280px;padding:16px;background:var(--slate-900);border:1px solid var(--slate-800)}.live-monitor-page__post-call{display:flex;flex-direction:column;gap:var(--space-4)}.live-monitor-page__summary{padding:16px;border-radius:var(--radius-lg);background:var(--slate-900);border:1px solid var(--slate-800);font:400 13px/1.5 var(--font-body);color:var(--slate-300)}.live-monitor-page__muted{color:var(--slate-500);margin:0}.live-monitor-page__sidebar{display:flex;flex-direction:column;gap:var(--space-4);min-height:0}.live-monitor-page__sidebar-block{padding:16px;border-radius:var(--radius-lg);background:var(--slate-900);border:1px solid var(--slate-800)}.live-monitor-page__section-title{margin:0 0 12px;font:600 11px/1 var(--font-mono);letter-spacing:.08em;text-transform:uppercase;color:var(--slate-400)}.live-monitor-page__orb-wrap{display:flex;flex-direction:column;align-items:center;gap:10px;padding:20px 16px;border-radius:var(--radius-lg);background:var(--slate-900);border:1px solid var(--slate-800);position:relative}.live-monitor-page__orb{width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;background:linear-gradient(135deg,var(--brand-500),var(--violet-500));animation:eds-breathe 3s ease-in-out infinite;position:relative;z-index:1}.live-monitor-page__ring{position:absolute;width:64px;height:64px;top:20px;border-radius:50%;border:2px solid var(--brand-400);animation:eds-ring 2s ease-out infinite}.live-monitor-page__ring--2{animation-delay:1s}.live-monitor-page__orb-wrap[data-mode=transfer] .live-monitor-page__orb{background:linear-gradient(135deg,var(--amber-400),var(--violet-500))}.live-monitor-page__confidence{font:500 12px/1 var(--font-mono);color:var(--slate-400)}.live-monitor-page__confidence[data-level=high]{color:var(--green-400)}.live-monitor-page__confidence[data-level=low]{color:var(--amber-400)}.live-monitor-page__sentiment-bar{height:6px;background:var(--slate-800);border-radius:3px;overflow:hidden;margin-bottom:8px}.live-monitor-page__sentiment-fill{display:block;height:100%;border-radius:3px;background:var(--green-400);transition:width .3s ease}.live-monitor-page__sentiment-fill[data-sentiment=frustrated],.live-monitor-page__sentiment-fill[data-sentiment=urgent]{background:var(--red-400)}.live-monitor-page__sentiment-fill[data-sentiment=neutral]{background:var(--amber-400)}.live-monitor-page__sentiment-label{font:500 12px/1 var(--font-mono);color:var(--slate-400);text-transform:capitalize}.live-monitor-page__tools{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px;max-height:280px;overflow:auto}.live-monitor-page__tools li{display:grid;grid-template-columns:auto 1fr auto;gap:10px;align-items:start;padding:10px;border-radius:var(--radius-md);background:var(--slate-950);border-left:3px solid var(--slate-700)}.live-monitor-page__tool-time{font:500 11px/1 var(--font-mono);color:var(--slate-500);white-space:nowrap}.live-monitor-page__tool-body{display:flex;flex-direction:column;gap:2px;min-width:0}.live-monitor-page__tool-name{font:600 13px/1.2 var(--font-sans);color:var(--gray-100)}.live-monitor-page__tool-result{font:400 12px/1.4 var(--font-body);color:var(--slate-400)}.live-monitor-page__tool-tech{font:500 10px/1 var(--font-mono);color:var(--slate-500)}.live-monitor-page__tool-spin{animation:eds-spin 1s linear infinite;color:var(--brand-400)}.live-monitor-page__tool--ok{border-left-color:var(--green-400)}.live-monitor-page__tool--warn{border-left-color:var(--amber-400)}.live-monitor-page__tool--error{border-left-color:var(--red-400)}.live-monitor-page__tool--active{border-left-color:var(--brand-400)}.live-monitor-page__tools-empty{border-left:none!important;background:transparent!important;padding:8px 0!important;font:400 13px/1.4 var(--font-body);color:var(--slate-500)}.live-monitor-page__goal{margin:0;font:400 13px/1.5 var(--font-body);color:var(--slate-300)}.live-monitor-page__control-bar{position:sticky;bottom:0;display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:var(--space-4);padding:12px 16px;border-radius:var(--radius-lg);background:var(--slate-900);border:1px solid var(--slate-800);box-shadow:0 -8px 24px #03071780}.live-monitor-page__control-group{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.live-monitor-page__divider{width:1px;height:24px;background:var(--slate-800);flex-shrink:0}.live-monitor-page__ctrl{display:inline-flex;align-items:center;gap:6px;height:38px;padding:0 14px;border:none;border-radius:var(--radius-md);font:600 13px/1 var(--font-sans);cursor:pointer;white-space:nowrap}.live-monitor-page__ctrl:disabled{opacity:.45;cursor:not-allowed}.live-monitor-page__ctrl--neutral{background:var(--slate-800);color:var(--gray-200)}.live-monitor-page__ctrl--neutral:hover:not(:disabled){background:var(--slate-700)}.live-monitor-page__ctrl--primary{background:var(--brand-600);color:#fff}.live-monitor-page__ctrl--primary:hover:not(:disabled){background:var(--brand-500)}.live-monitor-page__ctrl--violet{background:#8e51ff2e;color:var(--purple-300)}.live-monitor-page__ctrl--violet:hover:not(:disabled){background:var(--violet-500);color:#fff}.live-monitor-page__ctrl--danger{margin-left:auto;background:var(--red-500);color:#fff}.live-monitor-page__ctrl--danger:hover:not(:disabled){background:var(--red-400)}@media(max-width:960px){.live-monitor-page__grid{grid-template-columns:1fr}.live-monitor-page__header-right{align-items:flex-start;width:100%;margin-left:0}.live-monitor-page__ctrl--danger{margin-left:0;width:100%;justify-content:center}}\n"] }]
571
+ }], ctorParameters: () => [] });
572
+
573
+ export { LiveMonitorPageComponent };
574
+ //# sourceMappingURL=edvak99-voice-receptionist-widget-live-monitor-page.component-C3iTtVdn.mjs.map