@edvak99/voice-receptionist-widget 1.1.2 → 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 (132) hide show
  1. package/README.md +3 -0
  2. package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-DT9UKvQO.mjs +307 -0
  3. package/fesm2022/edvak99-voice-receptionist-widget-activities-page.component-DT9UKvQO.mjs.map +1 -0
  4. package/fesm2022/{edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-CMhknpGf.mjs → edvak99-voice-receptionist-widget-ai-receptionist-api.service-48cifVPF.mjs} +1351 -579
  5. package/fesm2022/edvak99-voice-receptionist-widget-ai-receptionist-api.service-48cifVPF.mjs.map +1 -0
  6. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-C5inEHJw.mjs +278 -0
  7. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-C5inEHJw.mjs.map +1 -0
  8. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-sYhM20CE.mjs +418 -0
  9. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-sYhM20CE.mjs.map +1 -0
  10. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DpwajEOE.mjs +194 -0
  11. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-DpwajEOE.mjs.map +1 -0
  12. package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-AN6GcSDq.mjs → edvak99-voice-receptionist-widget-call-recording-player.component-CgkRcFOl.mjs} +5 -4
  13. package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-AN6GcSDq.mjs.map → edvak99-voice-receptionist-widget-call-recording-player.component-CgkRcFOl.mjs.map} +1 -1
  14. package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-Cy_Bf7AO.mjs → edvak99-voice-receptionist-widget-call-review.service-CIMcjGKX.mjs} +2 -2
  15. package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-Cy_Bf7AO.mjs.map → edvak99-voice-receptionist-widget-call-review.service-CIMcjGKX.mjs.map} +1 -1
  16. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-Deh3jPgo.mjs +21 -0
  17. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-Deh3jPgo.mjs.map +1 -0
  18. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-CxbO0eDS.mjs +440 -0
  19. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-CxbO0eDS.mjs.map +1 -0
  20. package/fesm2022/{edvak99-voice-receptionist-widget-eds-elapsed.pipe-ouRlPpwT.mjs → edvak99-voice-receptionist-widget-dashboard-socket.service-BX91r74O.mjs} +5 -21
  21. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-socket.service-BX91r74O.mjs.map +1 -0
  22. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-CWRiJGfv.mjs +38 -0
  23. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-CWRiJGfv.mjs.map +1 -0
  24. package/fesm2022/edvak99-voice-receptionist-widget-eds-button.component-Bg5T5BN8.mjs +27 -0
  25. package/fesm2022/edvak99-voice-receptionist-widget-eds-button.component-Bg5T5BN8.mjs.map +1 -0
  26. package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-BFCiJOT3.mjs +22 -0
  27. package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-BFCiJOT3.mjs.map +1 -0
  28. package/fesm2022/{edvak99-voice-receptionist-widget-live-calls-list.service-CDtFRW8w.mjs → edvak99-voice-receptionist-widget-eds-pulse-dot.component-BPY6xN7C.mjs} +15 -5
  29. package/fesm2022/edvak99-voice-receptionist-widget-eds-pulse-dot.component-BPY6xN7C.mjs.map +1 -0
  30. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-BsBQIS_f.mjs +203 -0
  31. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-BsBQIS_f.mjs.map +1 -0
  32. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-CaSDMxpV.mjs +81 -0
  33. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-CaSDMxpV.mjs.map +1 -0
  34. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-Cy3ND-c-.mjs +1070 -0
  35. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-Cy3ND-c-.mjs.map +1 -0
  36. package/fesm2022/edvak99-voice-receptionist-widget-modal.component-COfi7biN.mjs +48 -0
  37. package/fesm2022/edvak99-voice-receptionist-widget-modal.component-COfi7biN.mjs.map +1 -0
  38. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-Bdh4QfFe.mjs +127 -0
  39. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-Bdh4QfFe.mjs.map +1 -0
  40. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-fHVzpiVk.mjs +268 -0
  41. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-fHVzpiVk.mjs.map +1 -0
  42. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-Bok64X2q.mjs +968 -0
  43. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-Bok64X2q.mjs.map +1 -0
  44. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-Bb9lPcYi.mjs +317 -0
  45. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-Bb9lPcYi.mjs.map +1 -0
  46. package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-CJLa5pia.mjs → edvak99-voice-receptionist-widget-wizard-page.component-BZVlWLWh.mjs} +6 -4
  47. package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-CJLa5pia.mjs.map → edvak99-voice-receptionist-widget-wizard-page.component-BZVlWLWh.mjs.map} +1 -1
  48. package/fesm2022/edvak99-voice-receptionist-widget.mjs +1 -1
  49. package/lib/components/call-conversation-panel/call-conversation-panel.component.d.ts +9 -6
  50. package/lib/components/call-tasks-panel/call-tasks-panel.component.d.ts +8 -8
  51. package/lib/components/knowledge-test-modal/knowledge-test-modal.component.d.ts +28 -0
  52. package/lib/components/live-transcript-panel/live-transcript-panel.component.d.ts +7 -4
  53. package/lib/components/outbound-dial-modal/outbound-dial-modal.component.d.ts +26 -0
  54. package/lib/layout/eds-module-nav.component.d.ts +0 -3
  55. package/lib/mocks/agent-activity.seed.d.ts +2 -0
  56. package/lib/models/agent-activity.types.d.ts +54 -0
  57. package/lib/models/ai-receptionist.types.d.ts +26 -0
  58. package/lib/models/api-dto.types.d.ts +18 -0
  59. package/lib/models/live-transcript.types.d.ts +2 -1
  60. package/lib/models/outbound-patient.types.d.ts +23 -0
  61. package/lib/models/outbound-purpose.types.d.ts +2 -0
  62. package/lib/models/pagination.d.ts +11 -0
  63. package/lib/models/staff-media.types.d.ts +36 -0
  64. package/lib/pages/activities-page/activities-page.component.d.ts +57 -0
  65. package/lib/pages/analytics-page/analytics-page.component.d.ts +38 -14
  66. package/lib/pages/call-history-detail-page/call-history-detail-page.component.d.ts +2 -3
  67. package/lib/pages/call-history-page/call-history-page.component.d.ts +16 -9
  68. package/lib/pages/dashboard-page/dashboard-page.component.d.ts +12 -8
  69. package/lib/pages/live-calls-page/live-calls-page.component.d.ts +14 -24
  70. package/lib/pages/live-monitor-page/live-monitor-page.component.d.ts +34 -1
  71. package/lib/pages/settings-page/settings-fields.config.d.ts +16 -0
  72. package/lib/pages/settings-page/settings-page.component.d.ts +48 -14
  73. package/lib/pages/tasks-page/tasks-page.component.d.ts +6 -0
  74. package/lib/services/ai-receptionist-api.mock.d.ts +44 -9
  75. package/lib/services/ai-receptionist-api.service.d.ts +65 -10
  76. package/lib/services/live-transcript.service.d.ts +1 -0
  77. package/lib/services/staff-media.service.d.ts +52 -0
  78. package/lib/ui/eds-avatar.component.d.ts +5 -2
  79. package/lib/ui/eds-button.component.d.ts +1 -1
  80. package/lib/utils/call-history-display.util.d.ts +21 -0
  81. package/lib/utils/outbound-purpose.util.d.ts +19 -0
  82. package/lib/utils/transcript-timing.util.d.ts +1 -1
  83. package/lib/utils/welcome-message.util.d.ts +1 -1
  84. package/package.json +2 -1
  85. package/styles/_index.scss +5 -40
  86. package/styles/chat-ui.scss +18 -18
  87. package/styles/eds.scss +188 -119
  88. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CW5OKmik.mjs +0 -113
  89. package/fesm2022/edvak99-voice-receptionist-widget-analytics-page.component-CW5OKmik.mjs.map +0 -1
  90. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DL1GfQyF.mjs +0 -35
  91. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DL1GfQyF.mjs.map +0 -1
  92. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-CTwfUHUf.mjs +0 -316
  93. package/fesm2022/edvak99-voice-receptionist-widget-call-history-detail-page.component-CTwfUHUf.mjs.map +0 -1
  94. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-Cj4wxd4e.mjs +0 -138
  95. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-Cj4wxd4e.mjs.map +0 -1
  96. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs +0 -70
  97. package/fesm2022/edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs.map +0 -1
  98. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-DzdIt2A7.mjs +0 -248
  99. package/fesm2022/edvak99-voice-receptionist-widget-dashboard-page.component-DzdIt2A7.mjs.map +0 -1
  100. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs +0 -27
  101. package/fesm2022/edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs.map +0 -1
  102. package/fesm2022/edvak99-voice-receptionist-widget-eds-elapsed.pipe-ouRlPpwT.mjs.map +0 -1
  103. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-CMhknpGf.mjs.map +0 -1
  104. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-cZqBuull.mjs +0 -575
  105. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-cZqBuull.mjs.map +0 -1
  106. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-list.service-CDtFRW8w.mjs.map +0 -1
  107. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-DrYzXJQW.mjs +0 -115
  108. package/fesm2022/edvak99-voice-receptionist-widget-live-calls-page.component-DrYzXJQW.mjs.map +0 -1
  109. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-D7dlk0y2.mjs +0 -574
  110. package/fesm2022/edvak99-voice-receptionist-widget-live-monitor-page.component-D7dlk0y2.mjs.map +0 -1
  111. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-BQECIuJu.mjs +0 -54
  112. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purpose.util-BQECIuJu.mjs.map +0 -1
  113. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-CJ8NEjcj.mjs +0 -265
  114. package/fesm2022/edvak99-voice-receptionist-widget-outbound-purposes-page.component-CJ8NEjcj.mjs.map +0 -1
  115. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-Bo3XNK3b.mjs +0 -509
  116. package/fesm2022/edvak99-voice-receptionist-widget-settings-page.component-Bo3XNK3b.mjs.map +0 -1
  117. package/fesm2022/edvak99-voice-receptionist-widget-task.util-DdtI0CR3.mjs +0 -52
  118. package/fesm2022/edvak99-voice-receptionist-widget-task.util-DdtI0CR3.mjs.map +0 -1
  119. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-CBwl15NP.mjs +0 -237
  120. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-CBwl15NP.mjs.map +0 -1
  121. package/lib/knowledge/components/faq-empty-state/faq-empty-state.component.d.ts +0 -22
  122. package/lib/knowledge/components/faq-form/faq-form.component.d.ts +0 -27
  123. package/lib/knowledge/components/faq-search-tester/faq-search-tester.component.d.ts +0 -17
  124. package/lib/knowledge/mocks/practice-faq.seed.d.ts +0 -2
  125. package/lib/knowledge/models/practice-faq.model.d.ts +0 -47
  126. package/lib/knowledge/pages/knowledge-hub.page.d.ts +0 -47
  127. package/lib/knowledge/services/practice-faq-api.mock.d.ts +0 -16
  128. package/lib/knowledge/services/practice-faq-api.service.d.ts +0 -22
  129. package/lib/knowledge/utils/faq.util.d.ts +0 -3
  130. package/lib/layout/eds-topbar.component.d.ts +0 -29
  131. package/lib/pages/audit-page/audit-page.component.d.ts +0 -14
  132. package/lib/ui/eds-waveform.component.d.ts +0 -8
@@ -1,509 +0,0 @@
1
- import * as i0 from '@angular/core';
2
- import { inject, signal, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
- import * as i1 from '@angular/forms';
4
- import { FormsModule } from '@angular/forms';
5
- import { RouterLink } from '@angular/router';
6
- import { A as AiReceptionistApiService, T as ToastService, W as WELCOME_MESSAGE_MAX, f as buildAutoWelcomeTemplate, g as resolveWelcomePreview, a as EdsBadgeComponent, E as EdsButtonComponent, b as EdsIconComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-CMhknpGf.mjs';
7
-
8
- /** Curated ElevenLabs voices — keep in sync with backend `tts-voice-presets.ts`. */
9
- const DEFAULT_TTS_VOICE_ID = '7EzWGsX10sAS4c9m9cPf';
10
- const TTS_VOICE_PRESETS = [
11
- {
12
- id: '7EzWGsX10sAS4c9m9cPf',
13
- label: 'Jack John – Conversational and Upbeat',
14
- },
15
- {
16
- id: 'NUjosfEayZAdRcDmcHM8',
17
- label: 'Madison – Cool, Calm & Conversational',
18
- },
19
- {
20
- id: 'gE0owC0H9C8SzfDyIUtB',
21
- label: 'Ivanna – Sassy, Condescending and Clear',
22
- },
23
- {
24
- id: 'Uc7anshoV8mdBhDnEZEX',
25
- label: 'Cecily – Clear and Warm',
26
- },
27
- ];
28
-
29
- const SETTINGS_SECTION_META = {
30
- general: {
31
- title: 'General',
32
- description: 'Practice identity and how the AI introduces itself on calls.',
33
- },
34
- phone: {
35
- title: 'Phone',
36
- description: 'Inbound routing, after-hours behavior, and ring timing.',
37
- },
38
- tasks: {
39
- title: 'Tasks & follow-ups',
40
- description: 'When the AI should create staff tasks after a call.',
41
- },
42
- scheduling: {
43
- title: 'Scheduling',
44
- description: 'Rules for booking and rescheduling appointments.',
45
- },
46
- refill: {
47
- title: 'Medication refills',
48
- description: 'Refill approval windows and staff review requirements.',
49
- },
50
- verify: {
51
- title: 'Patient verification',
52
- description: 'Identity checks before accessing chart or scheduling data.',
53
- },
54
- escalation: {
55
- title: 'Escalation',
56
- description: 'When to offer a human or transfer to reception.',
57
- },
58
- outbound: {
59
- title: 'Outbound calling',
60
- description: 'Outbound dialer defaults. Purpose templates are managed separately.',
61
- },
62
- transcripts: {
63
- title: 'Transcripts & recording',
64
- description: 'Retention and PHI handling for call transcripts.',
65
- },
66
- advanced: {
67
- title: 'Advanced',
68
- description: 'Model and debug options — read-only. Contact Edvak support to change.',
69
- },
70
- };
71
- /** Entire sections where every field is read-only (including unknown API fields). */
72
- const READ_ONLY_SECTIONS = new Set(['advanced', 'verify']);
73
- /** Settings tabs that stay visible but cannot be selected/edited. */
74
- const DISABLED_SETTINGS_TABS = new Set(['verify']);
75
- /** Individual fields locked for compliance, telephony, or core agent identity. */
76
- const READ_ONLY_FIELD_KEYS = [
77
- { section: 'general', key: 'practiceName', reason: 'Core practice identity' },
78
- { section: 'general', key: 'agentName', reason: 'AI agent identity' },
79
- { section: 'general', key: 'timezone', reason: 'Practice timezone' },
80
- { section: 'general', key: 'language', reason: 'Default language' },
81
- { section: 'phone', key: 'mainNumber', reason: 'Inbound phone routing' },
82
- { section: 'outbound', key: 'callerId', reason: 'Outbound caller ID' },
83
- { section: 'transcripts', key: 'retainDays', reason: 'Recording retention policy' },
84
- { section: 'transcripts', key: 'redactPhi', reason: 'PHI redaction policy' },
85
- { section: 'advanced', key: 'modelVersion', reason: 'Voice AI model profile' },
86
- { section: 'advanced', key: 'debugMode', reason: 'Debug logging' },
87
- ];
88
- const READ_ONLY_LOOKUP = new Set(READ_ONLY_FIELD_KEYS.map(({ section, key }) => `${section}.${key}`));
89
- function isFieldReadOnly(section, key) {
90
- if (READ_ONLY_SECTIONS.has(section))
91
- return true;
92
- return READ_ONLY_LOOKUP.has(`${section}.${key}`);
93
- }
94
- function readOnlyFieldReason(section, key) {
95
- return READ_ONLY_FIELD_KEYS.find((f) => f.section === section && f.key === key)?.reason;
96
- }
97
- const SETTINGS_FIELDS = {
98
- general: [
99
- {
100
- key: 'practiceName',
101
- label: 'Practice name',
102
- description: 'Spoken on greetings and confirmations.',
103
- type: 'text',
104
- },
105
- {
106
- key: 'agentName',
107
- label: 'AI agent name',
108
- description: 'How the assistant introduces itself (e.g. “Ava”).',
109
- type: 'text',
110
- },
111
- {
112
- key: 'timezone',
113
- label: 'Timezone',
114
- description: 'Used for hours, scheduling, and call timestamps.',
115
- type: 'text',
116
- },
117
- {
118
- key: 'language',
119
- label: 'Default language',
120
- description: 'Primary language for the AI receptionist.',
121
- type: 'text',
122
- },
123
- {
124
- key: 'ttsVoiceId',
125
- label: 'Voice',
126
- description: 'Spoken voice used on phone calls for this practice.',
127
- type: 'select',
128
- options: TTS_VOICE_PRESETS.map((p) => ({ value: p.id, label: p.label })),
129
- },
130
- ],
131
- phone: [
132
- {
133
- key: 'mainNumber',
134
- label: 'Main phone number',
135
- description: 'Primary inbound number patients call.',
136
- type: 'text',
137
- },
138
- {
139
- key: 'afterHours',
140
- label: 'After-hours mode',
141
- description: 'Use after-hours greeting and routing outside business hours.',
142
- type: 'boolean',
143
- },
144
- {
145
- key: 'ringTimeout',
146
- label: 'Ring timeout (seconds)',
147
- description: 'How long to ring before the AI answers.',
148
- type: 'number',
149
- },
150
- ],
151
- tasks: [
152
- {
153
- key: 'autoCreate',
154
- label: 'Auto-create tasks',
155
- description: 'Create a staff task when the AI cannot fully resolve a request.',
156
- type: 'boolean',
157
- },
158
- {
159
- key: 'defaultAssignee',
160
- label: 'Default assignee',
161
- description: 'Team or role new tasks are routed to.',
162
- type: 'text',
163
- },
164
- ],
165
- scheduling: [
166
- {
167
- key: 'leadTimeHours',
168
- label: 'Minimum lead time (hours)',
169
- description: 'Earliest bookable slot from now.',
170
- type: 'number',
171
- },
172
- {
173
- key: 'allowSameDay',
174
- label: 'Allow same-day booking',
175
- description: 'Let patients book appointments later today.',
176
- type: 'boolean',
177
- },
178
- ],
179
- refill: [
180
- {
181
- key: 'autoApproveDays',
182
- label: 'Auto-approve within (days)',
183
- description: 'Refills due within this window may be auto-approved.',
184
- type: 'number',
185
- },
186
- {
187
- key: 'requireStaffReview',
188
- label: 'Require staff review',
189
- description: 'All refill requests need staff approval before sending to pharmacy.',
190
- type: 'boolean',
191
- },
192
- ],
193
- verify: [
194
- {
195
- key: 'requireDob',
196
- label: 'Require date of birth',
197
- description: 'Ask for DOB before lookup or scheduling.',
198
- type: 'boolean',
199
- },
200
- {
201
- key: 'requireName',
202
- label: 'Require full name',
203
- description: 'Ask for patient name during verification.',
204
- type: 'boolean',
205
- },
206
- {
207
- key: 'maxAttempts',
208
- label: 'Max verification attempts',
209
- description: 'After this many failures, offer transfer to staff.',
210
- type: 'number',
211
- },
212
- ],
213
- escalation: [
214
- {
215
- key: 'sentimentThreshold',
216
- label: 'Sentiment threshold',
217
- description: 'Escalate when caller sentiment reaches this level (e.g. frustrated).',
218
- type: 'text',
219
- },
220
- {
221
- key: 'urgentKeywords',
222
- label: 'Urgent keywords',
223
- description: 'Comma-separated phrases that trigger immediate escalation.',
224
- type: 'text',
225
- },
226
- ],
227
- outbound: [
228
- {
229
- key: 'enabled',
230
- label: 'Outbound calls enabled',
231
- description: 'Allow staff to place outbound calls from the dashboard.',
232
- type: 'boolean',
233
- },
234
- {
235
- key: 'callerId',
236
- label: 'Outbound caller ID',
237
- description: 'Number shown to patients on outbound calls.',
238
- type: 'text',
239
- },
240
- ],
241
- transcripts: [
242
- {
243
- key: 'retainDays',
244
- label: 'Retention (days)',
245
- description: 'How long transcripts and recordings are kept.',
246
- type: 'number',
247
- },
248
- {
249
- key: 'redactPhi',
250
- label: 'Redact PHI in transcripts',
251
- description: 'Mask sensitive identifiers in stored transcripts.',
252
- type: 'boolean',
253
- },
254
- ],
255
- advanced: [
256
- {
257
- key: 'modelVersion',
258
- label: 'Model version',
259
- description: 'Voice AI model profile (set by Edvak support).',
260
- type: 'text',
261
- },
262
- {
263
- key: 'debugMode',
264
- label: 'Debug mode',
265
- description: 'Verbose logging for troubleshooting — not for production.',
266
- type: 'boolean',
267
- },
268
- ],
269
- };
270
- /** Fallback label for API fields not listed in config. */
271
- function humanizeFieldKey(key) {
272
- return key
273
- .replace(/([A-Z])/g, ' $1')
274
- .replace(/_/g, ' ')
275
- .trim()
276
- .replace(/^\w/, (c) => c.toUpperCase());
277
- }
278
- function resolveFieldMeta(section, key, value) {
279
- const known = SETTINGS_FIELDS[section]?.find((f) => f.key === key);
280
- if (known)
281
- return known;
282
- return {
283
- key,
284
- label: humanizeFieldKey(key),
285
- description: '',
286
- type: typeof value === 'boolean' ? 'boolean' : typeof value === 'number' ? 'number' : 'text',
287
- };
288
- }
289
-
290
- const GENERAL_IDENTITY_KEYS = new Set(['practiceName', 'agentName']);
291
- const GENERAL_OTHER_KEYS = new Set(['timezone', 'language', 'ttsVoiceId']);
292
- class SettingsPageComponent {
293
- constructor() {
294
- this.api = inject(AiReceptionistApiService);
295
- this.toast = inject(ToastService);
296
- this.originalSettings = null;
297
- this.isFieldReadOnly = isFieldReadOnly;
298
- this.readOnlySections = READ_ONLY_SECTIONS;
299
- this.disabledTabs = DISABLED_SETTINGS_TABS;
300
- this.settings = signal(null);
301
- this.tab = signal('general');
302
- this.loading = signal(true);
303
- this.saving = signal(false);
304
- this.saved = signal(false);
305
- this.welcomeMax = WELCOME_MESSAGE_MAX;
306
- this.previewingVoice = signal(false);
307
- this.voicePreviewAudio = null;
308
- this.tabs = [
309
- { id: 'general', label: 'General', icon: 'business' },
310
- { id: 'phone', label: 'Phone', icon: 'call' },
311
- { id: 'tasks', label: 'Tasks', icon: 'task_alt' },
312
- { id: 'scheduling', label: 'Scheduling', icon: 'calendar_month' },
313
- { id: 'refill', label: 'Refills', icon: 'medication' },
314
- { id: 'verify', label: 'Verification', icon: 'verified_user' },
315
- { id: 'escalation', label: 'Escalation', icon: 'warning' },
316
- { id: 'outbound', label: 'Outbound', icon: 'call_made' },
317
- { id: 'transcripts', label: 'Transcripts', icon: 'transcribe' },
318
- { id: 'advanced', label: 'Advanced', icon: 'tune' },
319
- { id: 'safety', label: 'Safety', icon: 'health_and_safety' },
320
- ];
321
- this.sectionMeta = computed(() => {
322
- const id = this.tab();
323
- if (id === 'safety') {
324
- return { title: 'Safety checklist', description: '' };
325
- }
326
- return SETTINGS_SECTION_META[id];
327
- });
328
- this.fieldsForTab = computed(() => {
329
- const s = this.settings();
330
- const id = this.tab();
331
- if (!s || id === 'safety')
332
- return [];
333
- const section = s[id];
334
- const configured = SETTINGS_FIELDS[id] ?? [];
335
- const configuredKeys = new Set(configured.map((f) => f.key));
336
- const extras = Object.keys(section)
337
- .filter((key) => key !== 'welcomeMessage' && !configuredKeys.has(key))
338
- .map((key) => resolveFieldMeta(id, key, section[key]));
339
- return [...configured, ...extras];
340
- });
341
- this.generalIdentityFields = computed(() => this.fieldsForTab().filter((f) => GENERAL_IDENTITY_KEYS.has(f.key)));
342
- this.generalOtherFields = computed(() => this.fieldsForTab().filter((f) => GENERAL_OTHER_KEYS.has(f.key)));
343
- this.welcomeMessage = computed(() => String(this.settings()?.general['welcomeMessage'] ?? ''));
344
- this.welcomePlaceholder = computed(() => {
345
- const s = this.settings();
346
- if (!s)
347
- return '';
348
- return buildAutoWelcomeTemplate(String(s.general['practiceName'] ?? ''), String(s.general['agentName'] ?? ''));
349
- });
350
- this.welcomePreview = computed(() => {
351
- const s = this.settings();
352
- if (!s)
353
- return '';
354
- return resolveWelcomePreview(String(s.general['welcomeMessage'] ?? ''), String(s.general['practiceName'] ?? ''), String(s.general['agentName'] ?? ''));
355
- });
356
- this.usingDefaultWelcome = computed(() => !this.welcomeMessage().trim());
357
- this.showRecordingConsentHint = computed(() => {
358
- const t = this.settings()?.transcripts;
359
- if (!t)
360
- return false;
361
- const retain = Number(t['retainDays'] ?? 0);
362
- return retain > 0 || t['redactPhi'] === true;
363
- });
364
- }
365
- ngOnInit() {
366
- this.api.getSettings().subscribe({
367
- next: ({ data }) => {
368
- this.originalSettings = structuredClone(data);
369
- this.settings.set(structuredClone(data));
370
- this.loading.set(false);
371
- },
372
- error: () => {
373
- this.loading.set(false);
374
- this.toast.show('Failed to load agent settings', 'error');
375
- },
376
- });
377
- }
378
- fieldIsReadOnly(section, key) {
379
- if (section === 'safety' || section === 'effectiveWelcomeMessage')
380
- return true;
381
- return isFieldReadOnly(section, key);
382
- }
383
- sectionIsReadOnly(section) {
384
- if (section === 'safety' || section === 'effectiveWelcomeMessage')
385
- return true;
386
- return READ_ONLY_SECTIONS.has(section);
387
- }
388
- isTabDisabled(id) {
389
- return id !== 'safety' && id !== 'effectiveWelcomeMessage' && DISABLED_SETTINGS_TABS.has(id);
390
- }
391
- setTab(id) {
392
- if (this.isTabDisabled(id))
393
- return;
394
- this.tab.set(id);
395
- }
396
- updateField(section, key, value) {
397
- const current = this.settings();
398
- if (!current || section === 'safety' || isFieldReadOnly(section, key))
399
- return;
400
- const sectionData = { ...current[section] };
401
- sectionData[key] = value;
402
- this.settings.set({ ...current, [section]: sectionData });
403
- }
404
- updateWelcomeMessage(value) {
405
- this.updateField('general', 'welcomeMessage', value.slice(0, WELCOME_MESSAGE_MAX));
406
- }
407
- resetWelcomeMessage() {
408
- this.updateField('general', 'welcomeMessage', '');
409
- }
410
- previewSelectedVoice() {
411
- const voiceId = String(this.fieldValue('general', 'ttsVoiceId') ?? '').trim();
412
- if (!voiceId || this.previewingVoice())
413
- return;
414
- this.previewingVoice.set(true);
415
- this.api.previewVoice(voiceId).subscribe({
416
- next: (blob) => {
417
- this.previewingVoice.set(false);
418
- if (this.voicePreviewAudio) {
419
- this.voicePreviewAudio.pause();
420
- URL.revokeObjectURL(this.voicePreviewAudio.src);
421
- this.voicePreviewAudio = null;
422
- }
423
- const url = URL.createObjectURL(blob);
424
- const audio = new Audio(url);
425
- this.voicePreviewAudio = audio;
426
- audio.onended = () => {
427
- URL.revokeObjectURL(url);
428
- if (this.voicePreviewAudio === audio)
429
- this.voicePreviewAudio = null;
430
- };
431
- void audio.play().catch(() => {
432
- this.toast.show('Could not play voice preview in this browser.', 'error');
433
- URL.revokeObjectURL(url);
434
- });
435
- },
436
- error: () => {
437
- this.previewingVoice.set(false);
438
- this.toast.show('Could not generate voice preview. Try again.', 'error');
439
- },
440
- });
441
- }
442
- fieldValue(section, key) {
443
- const s = this.settings();
444
- if (!s || section === 'safety')
445
- return '';
446
- return s[section][key];
447
- }
448
- save() {
449
- const s = this.settings();
450
- const orig = this.originalSettings;
451
- if (!s || !orig)
452
- return;
453
- const payload = structuredClone(s);
454
- const wm = String(payload.general['welcomeMessage'] ?? '').trim();
455
- payload.general['welcomeMessage'] = wm;
456
- this.restoreReadOnlyFields(payload, orig);
457
- this.saving.set(true);
458
- this.api.putSettings(payload).subscribe({
459
- next: ({ data }) => {
460
- this.originalSettings = structuredClone(data);
461
- this.settings.set(structuredClone(data));
462
- this.saving.set(false);
463
- this.saved.set(true);
464
- this.toast.show('Agent settings saved', 'success');
465
- setTimeout(() => this.saved.set(false), 2000);
466
- },
467
- error: () => {
468
- this.saving.set(false);
469
- this.toast.show('Failed to save agent settings', 'error');
470
- },
471
- });
472
- }
473
- restoreReadOnlyFields(payload, orig) {
474
- const editableSections = [
475
- 'general',
476
- 'phone',
477
- 'tasks',
478
- 'scheduling',
479
- 'refill',
480
- 'verify',
481
- 'escalation',
482
- 'outbound',
483
- 'transcripts',
484
- 'advanced',
485
- ];
486
- for (const sectionKey of editableSections) {
487
- if (READ_ONLY_SECTIONS.has(sectionKey)) {
488
- payload[sectionKey] = structuredClone(orig[sectionKey]);
489
- continue;
490
- }
491
- const payloadSection = payload[sectionKey];
492
- const origSection = orig[sectionKey];
493
- for (const key of Object.keys(payloadSection)) {
494
- if (isFieldReadOnly(sectionKey, key)) {
495
- payloadSection[key] = origSection[key];
496
- }
497
- }
498
- }
499
- }
500
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SettingsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
501
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: SettingsPageComponent, isStandalone: true, selector: "eds-settings-page", ngImport: i0, template: "<div class=\"eds-page settings-page\">\n <header class=\"settings-page__header\">\n <div>\n <h1 class=\"eds-h1\">Agent settings</h1>\n <p class=\"settings-page__subtitle\">Configure AI receptionist behavior and safety</p>\n </div>\n <div class=\"settings-page__save-row\">\n @if (saved()) {\n <span class=\"settings-page__saved\">Saved</span>\n }\n <eds-button variant=\"primary\" [disabled]=\"saving()\" (click)=\"save()\">\n <eds-icon name=\"save\" [size]=\"18\" />\n {{ saving() ? 'Saving\u2026' : 'Save changes' }}\n </eds-button>\n </div>\n </header>\n\n @if (loading()) {\n <p class=\"settings-page__loading\">Loading settings\u2026</p>\n } @else if (settings()) {\n <div class=\"settings-page__layout\">\n <nav class=\"eds-card settings-page__sidebar\" aria-label=\"Settings sections\">\n @for (t of tabs; track t.id) {\n <button\n type=\"button\"\n class=\"settings-page__nav-item\"\n [class.settings-page__nav-item--active]=\"tab() === t.id\"\n [class.settings-page__nav-item--disabled]=\"isTabDisabled(t.id)\"\n [disabled]=\"isTabDisabled(t.id)\"\n [attr.title]=\"isTabDisabled(t.id) ? 'This section is currently disabled' : null\"\n (click)=\"setTab(t.id)\"\n >\n <eds-icon [name]=\"t.icon\" [size]=\"18\" />\n {{ t.label }}\n @if (isTabDisabled(t.id)) {\n <span class=\"settings-page__nav-disabled-badge\">Disabled</span>\n }\n </button>\n }\n </nav>\n\n <article class=\"eds-card settings-page__panel\">\n @if (tab() === 'safety') {\n <header class=\"settings-page__panel-head\">\n <h2 class=\"settings-page__panel-title\">Safety checklist</h2>\n <p class=\"settings-page__panel-desc\">\n Read-only compliance status. Contact Edvak support to change safety configuration.\n </p>\n </header>\n <ul class=\"settings-page__safety\">\n @for (item of settings()!.safety; track item.id) {\n <li>\n <eds-icon\n [name]=\"item.status === 'ok' ? 'check_circle' : 'error'\"\n [size]=\"22\"\n />\n <span class=\"settings-page__safety-label\">{{ item.label }}</span>\n <eds-badge [tone]=\"item.status === 'ok' ? 'pos' : 'notice'\">\n {{ item.status === 'ok' ? 'Pass' : 'Needs attention' }}\n </eds-badge>\n </li>\n }\n </ul>\n } @else {\n <header class=\"settings-page__panel-head\">\n <h2 class=\"settings-page__panel-title\">{{ sectionMeta().title }}</h2>\n <p class=\"settings-page__panel-desc\">{{ sectionMeta().description }}</p>\n </header>\n\n @if (sectionIsReadOnly(tab())) {\n <p class=\"settings-page__readonly-banner\">\n <eds-icon name=\"lock\" [size]=\"18\" />\n These settings are managed by Edvak. Contact support to request changes.\n </p>\n }\n\n @if (tab() === 'general') {\n <a routerLink=\"../knowledge\" class=\"settings-page__quick-link\">\n <eds-icon name=\"menu_book\" [size]=\"22\" />\n <div>\n <span class=\"settings-page__quick-link-title\">Practice Knowledge Base</span>\n <span class=\"settings-page__quick-link-desc\">FAQ answers your AI uses on phone calls</span>\n </div>\n <eds-icon name=\"chevron_right\" [size]=\"20\" />\n </a>\n\n <div class=\"settings-page__fields settings-page__fields--general\">\n @for (field of generalIdentityFields(); track field.key) {\n <label\n class=\"settings-page__field\"\n [class.settings-page__field--readonly]=\"isFieldReadOnly('general', field.key)\"\n >\n <span class=\"settings-page__field-label\">\n {{ field.label }}\n @if (isFieldReadOnly('general', field.key)) {\n <span class=\"settings-page__readonly-badge\">Read-only</span>\n }\n </span>\n @if (field.description) {\n <span class=\"settings-page__field-desc\">{{ field.description }}</span>\n }\n <input\n type=\"text\"\n class=\"settings-page__input\"\n [class.settings-page__input--readonly]=\"isFieldReadOnly('general', field.key)\"\n [disabled]=\"isFieldReadOnly('general', field.key)\"\n [ngModel]=\"fieldValue('general', field.key)\"\n (ngModelChange)=\"updateField('general', field.key, $event)\"\n />\n </label>\n }\n\n <section class=\"settings-page__welcome\">\n <label class=\"settings-page__field settings-page__field--full\">\n <span class=\"settings-page__field-label\">Welcome message</span>\n <span class=\"settings-page__field-desc\">\n Spoken automatically when a caller connects. Leave blank to use the default template.\n </span>\n <textarea\n class=\"settings-page__textarea\"\n rows=\"4\"\n [maxlength]=\"welcomeMax\"\n [placeholder]=\"welcomePlaceholder()\"\n [ngModel]=\"welcomeMessage()\"\n (ngModelChange)=\"updateWelcomeMessage($event)\"\n ></textarea>\n <span class=\"settings-page__char-count\">\n {{ welcomeMessage().length }}/{{ welcomeMax }}\n </span>\n @if (showRecordingConsentHint()) {\n <p class=\"settings-page__recording-hint\">\n Call recording is enabled \u2014 consider mentioning that the call may be recorded or monitored.\n </p>\n }\n </label>\n\n <div class=\"settings-page__welcome-preview eds-card\">\n <div class=\"settings-page__welcome-preview-head\">\n <eds-icon name=\"record_voice_over\" [size]=\"20\" />\n <span class=\"settings-page__field-label\">Live preview</span>\n @if (usingDefaultWelcome()) {\n <eds-badge tone=\"gray\">Default template</eds-badge>\n }\n </div>\n <p class=\"settings-page__welcome-preview-text\">\u201C{{ welcomePreview() }}\u201D</p>\n </div>\n\n @if (!usingDefaultWelcome()) {\n <eds-button variant=\"secondary\" type=\"button\" (click)=\"resetWelcomeMessage()\">\n Reset to default\n </eds-button>\n }\n </section>\n\n @for (field of generalOtherFields(); track field.key) {\n <label\n class=\"settings-page__field\"\n [class.settings-page__field--readonly]=\"isFieldReadOnly('general', field.key)\"\n >\n <span class=\"settings-page__field-label\">\n {{ field.label }}\n @if (isFieldReadOnly('general', field.key)) {\n <span class=\"settings-page__readonly-badge\">Read-only</span>\n }\n </span>\n @if (field.description) {\n <span class=\"settings-page__field-desc\">{{ field.description }}</span>\n }\n @if (field.type === 'select' && field.options?.length) {\n <select\n class=\"settings-page__input settings-page__select\"\n [class.settings-page__input--readonly]=\"isFieldReadOnly('general', field.key)\"\n [disabled]=\"isFieldReadOnly('general', field.key)\"\n [ngModel]=\"fieldValue('general', field.key)\"\n (ngModelChange)=\"updateField('general', field.key, $event)\"\n >\n @for (opt of field.options; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n @if (field.key === 'ttsVoiceId') {\n <div class=\"settings-page__voice-preview-row\">\n <eds-button\n variant=\"secondary\"\n type=\"button\"\n [disabled]=\"previewingVoice()\"\n (click)=\"previewSelectedVoice()\"\n >\n {{ previewingVoice() ? 'Generating\u2026' : 'Preview voice' }}\n </eds-button>\n <span class=\"settings-page__field-desc\">\n Plays a short sample in the browser \u2014 save to use on phone calls.\n </span>\n </div>\n }\n } @else {\n <input\n type=\"text\"\n class=\"settings-page__input\"\n [class.settings-page__input--readonly]=\"isFieldReadOnly('general', field.key)\"\n [disabled]=\"isFieldReadOnly('general', field.key)\"\n [ngModel]=\"fieldValue('general', field.key)\"\n (ngModelChange)=\"updateField('general', field.key, $event)\"\n />\n }\n </label>\n }\n </div>\n } @else {\n @if (tab() === 'outbound') {\n <a [routerLink]=\"['../settings/outbound-purposes']\" class=\"settings-page__quick-link\">\n <eds-icon name=\"call_made\" [size]=\"22\" />\n <div>\n <span class=\"settings-page__quick-link-title\">Outbound Call Purposes</span>\n <span class=\"settings-page__quick-link-desc\">Templates, required fields, and opening scripts</span>\n </div>\n <eds-icon name=\"chevron_right\" [size]=\"20\" />\n </a>\n }\n\n <div class=\"settings-page__fields\">\n @for (field of fieldsForTab(); track field.key) {\n @if (field.type === 'boolean') {\n <label\n class=\"settings-page__toggle-row\"\n [class.settings-page__toggle-row--readonly]=\"fieldIsReadOnly(tab(), field.key)\"\n >\n <div class=\"settings-page__toggle-copy\">\n <span class=\"settings-page__field-label\">\n {{ field.label }}\n @if (fieldIsReadOnly(tab(), field.key)) {\n <span class=\"settings-page__readonly-badge\">Read-only</span>\n }\n </span>\n @if (field.description) {\n <span class=\"settings-page__field-desc\">{{ field.description }}</span>\n }\n </div>\n <input\n type=\"checkbox\"\n class=\"settings-page__checkbox\"\n [disabled]=\"fieldIsReadOnly(tab(), field.key)\"\n [ngModel]=\"fieldValue(tab(), field.key)\"\n (ngModelChange)=\"updateField(tab(), field.key, $event)\"\n />\n </label>\n } @else {\n <label\n class=\"settings-page__field\"\n [class.settings-page__field--readonly]=\"fieldIsReadOnly(tab(), field.key)\"\n >\n <span class=\"settings-page__field-label\">\n {{ field.label }}\n @if (fieldIsReadOnly(tab(), field.key)) {\n <span class=\"settings-page__readonly-badge\">Read-only</span>\n }\n </span>\n @if (field.description) {\n <span class=\"settings-page__field-desc\">{{ field.description }}</span>\n }\n @if (field.type === 'number') {\n <input\n type=\"number\"\n class=\"settings-page__input\"\n [class.settings-page__input--readonly]=\"fieldIsReadOnly(tab(), field.key)\"\n [disabled]=\"fieldIsReadOnly(tab(), field.key)\"\n [ngModel]=\"fieldValue(tab(), field.key)\"\n (ngModelChange)=\"updateField(tab(), field.key, +$event)\"\n />\n } @else if (field.type === 'select' && field.options?.length) {\n <select\n class=\"settings-page__input settings-page__select\"\n [class.settings-page__input--readonly]=\"fieldIsReadOnly(tab(), field.key)\"\n [disabled]=\"fieldIsReadOnly(tab(), field.key)\"\n [ngModel]=\"fieldValue(tab(), field.key)\"\n (ngModelChange)=\"updateField(tab(), field.key, $event)\"\n >\n @for (opt of field.options; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n } @else {\n <input\n type=\"text\"\n class=\"settings-page__input\"\n [class.settings-page__input--readonly]=\"fieldIsReadOnly(tab(), field.key)\"\n [disabled]=\"fieldIsReadOnly(tab(), field.key)\"\n [ngModel]=\"fieldValue(tab(), field.key)\"\n (ngModelChange)=\"updateField(tab(), field.key, $event)\"\n />\n }\n </label>\n }\n }\n </div>\n }\n }\n </article>\n </div>\n }\n</div>\n", styles: [".settings-page__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:24px}.settings-page__subtitle,.settings-page__loading{margin:6px 0 0;font-size:15px;line-height:1.5;color:var(--color-text-muted, #64748b)}.settings-page__save-row{display:flex;align-items:center;gap:12px;flex-shrink:0}.settings-page__saved{color:var(--color-success, #22c55e);font-weight:600;font-size:14px}.settings-page__layout{display:grid;grid-template-columns:240px minmax(0,1fr);gap:20px;align-items:start}.settings-page__sidebar{padding:8px;display:flex;flex-direction:column;gap:2px;position:sticky;top:16px;max-height:calc(100vh - 120px);overflow-y:auto}.settings-page__nav-item{display:flex;align-items:center;gap:10px;padding:11px 14px;border:none;background:none;border-radius:var(--radius-md, 8px);font-size:14px;font-weight:500;text-align:left;cursor:pointer;color:var(--color-text-muted, #64748b);width:100%;line-height:1.3}.settings-page__nav-item:hover{background:var(--surface-l1, #f9fafb);color:var(--color-text, #0f172a)}.settings-page__nav-item--active{background:var(--color-primary-light, #eaf2ff);color:var(--color-primary, #3681f4);font-weight:600}.settings-page__nav-item--disabled,.settings-page__nav-item:disabled{opacity:.45;cursor:not-allowed;color:var(--color-text-muted, #94a3b8)}.settings-page__nav-item--disabled:hover,.settings-page__nav-item:disabled:hover{background:none;color:var(--color-text-muted, #94a3b8)}.settings-page__nav-disabled-badge{margin-left:auto;font-size:11px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--color-text-muted, #94a3b8)}.settings-page__panel{padding:28px 32px;min-height:420px}.settings-page__panel-head{margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--color-border, #e2e8f0)}.settings-page__panel-title{margin:0 0 8px;font-size:22px;font-weight:600;color:var(--color-text, #0f172a);letter-spacing:-.01em}.settings-page__panel-desc{margin:0;font-size:15px;line-height:1.55;color:var(--color-text-muted, #64748b);max-width:640px}.settings-page__quick-link{display:flex;align-items:center;gap:14px;padding:16px 18px;margin-bottom:24px;text-decoration:none;color:inherit;border:1px solid var(--color-border, #e2e8f0);border-radius:var(--radius-md, 8px);background:var(--color-surface-alt, #f8fafc);transition:border-color .15s ease,box-shadow .15s ease}.settings-page__quick-link div{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.settings-page__quick-link:hover{border-color:var(--color-primary, #3681f4);box-shadow:var(--shadow-sm)}.settings-page__quick-link-title{font-size:15px;font-weight:600;color:var(--color-text, #0f172a)}.settings-page__quick-link-desc{font-size:14px;line-height:1.45;color:var(--color-text-muted, #64748b)}.settings-page__fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px 28px}.settings-page__field{display:flex;flex-direction:column;gap:6px}.settings-page__field-label{display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:14px;font-weight:600;color:var(--color-text, #0f172a);line-height:1.35}.settings-page__readonly-badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--color-text-muted, #64748b);background:var(--color-surface-alt, #f1f5f9);border:1px solid var(--color-border, #e2e8f0)}.settings-page__readonly-banner{display:flex;align-items:center;gap:10px;margin:0 0 20px;padding:12px 14px;border-radius:var(--radius-md, 8px);font-size:14px;line-height:1.45;color:var(--color-text-muted, #64748b);background:var(--color-surface-alt, #f8fafc);border:1px solid var(--color-border, #e2e8f0)}.settings-page__field--readonly .settings-page__field-desc{color:var(--color-text-soft, #94a3b8)}.settings-page__input--readonly,.settings-page__input:disabled{color:var(--color-text-muted, #64748b);background:var(--color-surface-alt, #f1f5f9);border-color:var(--color-border, #e2e8f0);cursor:not-allowed;opacity:1}.settings-page__toggle-row--readonly{cursor:default;opacity:.92}.settings-page__toggle-row--readonly .settings-page__checkbox:disabled{cursor:not-allowed}.settings-page__field-desc{font-size:13px;line-height:1.45;color:var(--color-text-muted, #64748b);margin-bottom:2px}.settings-page__field--full{grid-column:1/-1}.settings-page__textarea{width:100%;padding:10px 12px;border:1px solid var(--color-border-strong, #cbd5e1);border-radius:var(--radius-md, 8px);font-size:15px;line-height:1.5;color:var(--color-text, #0f172a);font-family:inherit;resize:vertical;min-height:96px}.settings-page__textarea:focus{outline:2px solid rgba(54,129,244,.35);outline-offset:1px;border-color:var(--color-primary, #3681f4)}.settings-page__char-count{font-size:12px;color:var(--color-text-muted, #64748b);text-align:right}.settings-page__recording-hint{margin:4px 0 0;font-size:13px;line-height:1.45;color:var(--color-warning, #f59e0b)}.settings-page__welcome{grid-column:1/-1;display:flex;flex-direction:column;gap:14px;padding:18px;border:1px solid var(--color-border, #e2e8f0);border-radius:var(--radius-md, 8px);background:var(--color-surface-alt, #f8fafc)}.settings-page__welcome-preview{padding:14px 16px;background:#fff}.settings-page__welcome-preview-head{display:flex;align-items:center;gap:8px;margin-bottom:10px}.settings-page__welcome-preview-text{margin:0;font-size:15px;line-height:1.55;color:var(--color-text, #0f172a);font-style:italic}.settings-page__fields--general{margin-top:0}.settings-page__input{width:100%;padding:10px 12px;border:1px solid var(--color-border-strong, #cbd5e1);border-radius:var(--radius-md, 8px);font-size:15px;line-height:1.4;color:var(--color-text, #0f172a);background:#fff}.settings-page__input:focus{outline:2px solid rgba(54,129,244,.35);outline-offset:1px;border-color:var(--color-primary, #3681f4)}.settings-page__input::placeholder{color:var(--color-text-soft, #94a3b8)}.settings-page__select{appearance:auto;cursor:pointer}.settings-page__voice-preview-row{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin-top:10px}.settings-page__toggle-row{grid-column:1/-1;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 18px;border:1px solid var(--color-border, #e2e8f0);border-radius:var(--radius-md, 8px);background:var(--color-surface-alt, #f8fafc);cursor:pointer}.settings-page__toggle-copy{display:flex;flex-direction:column;gap:4px;min-width:0}.settings-page__checkbox{width:18px;height:18px;flex-shrink:0;accent-color:var(--color-primary, #3681f4);cursor:pointer}.settings-page__safety{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:12px}.settings-page__safety li{display:flex;align-items:center;gap:12px;padding:16px 18px;background:var(--color-surface-alt, #f8fafc);border:1px solid var(--color-border, #e2e8f0);border-radius:var(--radius-md, 8px)}.settings-page__safety-label{flex:1;font-size:15px;font-weight:500;color:var(--color-text, #0f172a);line-height:1.4}@media(max-width:960px){.settings-page__layout{grid-template-columns:1fr}.settings-page__sidebar{position:static;max-height:none;flex-direction:row;flex-wrap:wrap}.settings-page__nav-item{width:auto;flex:1 1 auto}.settings-page__fields{grid-template-columns:1fr}.settings-page__panel{padding:20px}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: EdsBadgeComponent, selector: "eds-badge", inputs: ["tone"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }, { kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
502
- }
503
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SettingsPageComponent, decorators: [{
504
- type: Component,
505
- args: [{ selector: 'eds-settings-page', imports: [FormsModule, RouterLink, EdsBadgeComponent, EdsButtonComponent, EdsIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"eds-page settings-page\">\n <header class=\"settings-page__header\">\n <div>\n <h1 class=\"eds-h1\">Agent settings</h1>\n <p class=\"settings-page__subtitle\">Configure AI receptionist behavior and safety</p>\n </div>\n <div class=\"settings-page__save-row\">\n @if (saved()) {\n <span class=\"settings-page__saved\">Saved</span>\n }\n <eds-button variant=\"primary\" [disabled]=\"saving()\" (click)=\"save()\">\n <eds-icon name=\"save\" [size]=\"18\" />\n {{ saving() ? 'Saving\u2026' : 'Save changes' }}\n </eds-button>\n </div>\n </header>\n\n @if (loading()) {\n <p class=\"settings-page__loading\">Loading settings\u2026</p>\n } @else if (settings()) {\n <div class=\"settings-page__layout\">\n <nav class=\"eds-card settings-page__sidebar\" aria-label=\"Settings sections\">\n @for (t of tabs; track t.id) {\n <button\n type=\"button\"\n class=\"settings-page__nav-item\"\n [class.settings-page__nav-item--active]=\"tab() === t.id\"\n [class.settings-page__nav-item--disabled]=\"isTabDisabled(t.id)\"\n [disabled]=\"isTabDisabled(t.id)\"\n [attr.title]=\"isTabDisabled(t.id) ? 'This section is currently disabled' : null\"\n (click)=\"setTab(t.id)\"\n >\n <eds-icon [name]=\"t.icon\" [size]=\"18\" />\n {{ t.label }}\n @if (isTabDisabled(t.id)) {\n <span class=\"settings-page__nav-disabled-badge\">Disabled</span>\n }\n </button>\n }\n </nav>\n\n <article class=\"eds-card settings-page__panel\">\n @if (tab() === 'safety') {\n <header class=\"settings-page__panel-head\">\n <h2 class=\"settings-page__panel-title\">Safety checklist</h2>\n <p class=\"settings-page__panel-desc\">\n Read-only compliance status. Contact Edvak support to change safety configuration.\n </p>\n </header>\n <ul class=\"settings-page__safety\">\n @for (item of settings()!.safety; track item.id) {\n <li>\n <eds-icon\n [name]=\"item.status === 'ok' ? 'check_circle' : 'error'\"\n [size]=\"22\"\n />\n <span class=\"settings-page__safety-label\">{{ item.label }}</span>\n <eds-badge [tone]=\"item.status === 'ok' ? 'pos' : 'notice'\">\n {{ item.status === 'ok' ? 'Pass' : 'Needs attention' }}\n </eds-badge>\n </li>\n }\n </ul>\n } @else {\n <header class=\"settings-page__panel-head\">\n <h2 class=\"settings-page__panel-title\">{{ sectionMeta().title }}</h2>\n <p class=\"settings-page__panel-desc\">{{ sectionMeta().description }}</p>\n </header>\n\n @if (sectionIsReadOnly(tab())) {\n <p class=\"settings-page__readonly-banner\">\n <eds-icon name=\"lock\" [size]=\"18\" />\n These settings are managed by Edvak. Contact support to request changes.\n </p>\n }\n\n @if (tab() === 'general') {\n <a routerLink=\"../knowledge\" class=\"settings-page__quick-link\">\n <eds-icon name=\"menu_book\" [size]=\"22\" />\n <div>\n <span class=\"settings-page__quick-link-title\">Practice Knowledge Base</span>\n <span class=\"settings-page__quick-link-desc\">FAQ answers your AI uses on phone calls</span>\n </div>\n <eds-icon name=\"chevron_right\" [size]=\"20\" />\n </a>\n\n <div class=\"settings-page__fields settings-page__fields--general\">\n @for (field of generalIdentityFields(); track field.key) {\n <label\n class=\"settings-page__field\"\n [class.settings-page__field--readonly]=\"isFieldReadOnly('general', field.key)\"\n >\n <span class=\"settings-page__field-label\">\n {{ field.label }}\n @if (isFieldReadOnly('general', field.key)) {\n <span class=\"settings-page__readonly-badge\">Read-only</span>\n }\n </span>\n @if (field.description) {\n <span class=\"settings-page__field-desc\">{{ field.description }}</span>\n }\n <input\n type=\"text\"\n class=\"settings-page__input\"\n [class.settings-page__input--readonly]=\"isFieldReadOnly('general', field.key)\"\n [disabled]=\"isFieldReadOnly('general', field.key)\"\n [ngModel]=\"fieldValue('general', field.key)\"\n (ngModelChange)=\"updateField('general', field.key, $event)\"\n />\n </label>\n }\n\n <section class=\"settings-page__welcome\">\n <label class=\"settings-page__field settings-page__field--full\">\n <span class=\"settings-page__field-label\">Welcome message</span>\n <span class=\"settings-page__field-desc\">\n Spoken automatically when a caller connects. Leave blank to use the default template.\n </span>\n <textarea\n class=\"settings-page__textarea\"\n rows=\"4\"\n [maxlength]=\"welcomeMax\"\n [placeholder]=\"welcomePlaceholder()\"\n [ngModel]=\"welcomeMessage()\"\n (ngModelChange)=\"updateWelcomeMessage($event)\"\n ></textarea>\n <span class=\"settings-page__char-count\">\n {{ welcomeMessage().length }}/{{ welcomeMax }}\n </span>\n @if (showRecordingConsentHint()) {\n <p class=\"settings-page__recording-hint\">\n Call recording is enabled \u2014 consider mentioning that the call may be recorded or monitored.\n </p>\n }\n </label>\n\n <div class=\"settings-page__welcome-preview eds-card\">\n <div class=\"settings-page__welcome-preview-head\">\n <eds-icon name=\"record_voice_over\" [size]=\"20\" />\n <span class=\"settings-page__field-label\">Live preview</span>\n @if (usingDefaultWelcome()) {\n <eds-badge tone=\"gray\">Default template</eds-badge>\n }\n </div>\n <p class=\"settings-page__welcome-preview-text\">\u201C{{ welcomePreview() }}\u201D</p>\n </div>\n\n @if (!usingDefaultWelcome()) {\n <eds-button variant=\"secondary\" type=\"button\" (click)=\"resetWelcomeMessage()\">\n Reset to default\n </eds-button>\n }\n </section>\n\n @for (field of generalOtherFields(); track field.key) {\n <label\n class=\"settings-page__field\"\n [class.settings-page__field--readonly]=\"isFieldReadOnly('general', field.key)\"\n >\n <span class=\"settings-page__field-label\">\n {{ field.label }}\n @if (isFieldReadOnly('general', field.key)) {\n <span class=\"settings-page__readonly-badge\">Read-only</span>\n }\n </span>\n @if (field.description) {\n <span class=\"settings-page__field-desc\">{{ field.description }}</span>\n }\n @if (field.type === 'select' && field.options?.length) {\n <select\n class=\"settings-page__input settings-page__select\"\n [class.settings-page__input--readonly]=\"isFieldReadOnly('general', field.key)\"\n [disabled]=\"isFieldReadOnly('general', field.key)\"\n [ngModel]=\"fieldValue('general', field.key)\"\n (ngModelChange)=\"updateField('general', field.key, $event)\"\n >\n @for (opt of field.options; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n @if (field.key === 'ttsVoiceId') {\n <div class=\"settings-page__voice-preview-row\">\n <eds-button\n variant=\"secondary\"\n type=\"button\"\n [disabled]=\"previewingVoice()\"\n (click)=\"previewSelectedVoice()\"\n >\n {{ previewingVoice() ? 'Generating\u2026' : 'Preview voice' }}\n </eds-button>\n <span class=\"settings-page__field-desc\">\n Plays a short sample in the browser \u2014 save to use on phone calls.\n </span>\n </div>\n }\n } @else {\n <input\n type=\"text\"\n class=\"settings-page__input\"\n [class.settings-page__input--readonly]=\"isFieldReadOnly('general', field.key)\"\n [disabled]=\"isFieldReadOnly('general', field.key)\"\n [ngModel]=\"fieldValue('general', field.key)\"\n (ngModelChange)=\"updateField('general', field.key, $event)\"\n />\n }\n </label>\n }\n </div>\n } @else {\n @if (tab() === 'outbound') {\n <a [routerLink]=\"['../settings/outbound-purposes']\" class=\"settings-page__quick-link\">\n <eds-icon name=\"call_made\" [size]=\"22\" />\n <div>\n <span class=\"settings-page__quick-link-title\">Outbound Call Purposes</span>\n <span class=\"settings-page__quick-link-desc\">Templates, required fields, and opening scripts</span>\n </div>\n <eds-icon name=\"chevron_right\" [size]=\"20\" />\n </a>\n }\n\n <div class=\"settings-page__fields\">\n @for (field of fieldsForTab(); track field.key) {\n @if (field.type === 'boolean') {\n <label\n class=\"settings-page__toggle-row\"\n [class.settings-page__toggle-row--readonly]=\"fieldIsReadOnly(tab(), field.key)\"\n >\n <div class=\"settings-page__toggle-copy\">\n <span class=\"settings-page__field-label\">\n {{ field.label }}\n @if (fieldIsReadOnly(tab(), field.key)) {\n <span class=\"settings-page__readonly-badge\">Read-only</span>\n }\n </span>\n @if (field.description) {\n <span class=\"settings-page__field-desc\">{{ field.description }}</span>\n }\n </div>\n <input\n type=\"checkbox\"\n class=\"settings-page__checkbox\"\n [disabled]=\"fieldIsReadOnly(tab(), field.key)\"\n [ngModel]=\"fieldValue(tab(), field.key)\"\n (ngModelChange)=\"updateField(tab(), field.key, $event)\"\n />\n </label>\n } @else {\n <label\n class=\"settings-page__field\"\n [class.settings-page__field--readonly]=\"fieldIsReadOnly(tab(), field.key)\"\n >\n <span class=\"settings-page__field-label\">\n {{ field.label }}\n @if (fieldIsReadOnly(tab(), field.key)) {\n <span class=\"settings-page__readonly-badge\">Read-only</span>\n }\n </span>\n @if (field.description) {\n <span class=\"settings-page__field-desc\">{{ field.description }}</span>\n }\n @if (field.type === 'number') {\n <input\n type=\"number\"\n class=\"settings-page__input\"\n [class.settings-page__input--readonly]=\"fieldIsReadOnly(tab(), field.key)\"\n [disabled]=\"fieldIsReadOnly(tab(), field.key)\"\n [ngModel]=\"fieldValue(tab(), field.key)\"\n (ngModelChange)=\"updateField(tab(), field.key, +$event)\"\n />\n } @else if (field.type === 'select' && field.options?.length) {\n <select\n class=\"settings-page__input settings-page__select\"\n [class.settings-page__input--readonly]=\"fieldIsReadOnly(tab(), field.key)\"\n [disabled]=\"fieldIsReadOnly(tab(), field.key)\"\n [ngModel]=\"fieldValue(tab(), field.key)\"\n (ngModelChange)=\"updateField(tab(), field.key, $event)\"\n >\n @for (opt of field.options; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n } @else {\n <input\n type=\"text\"\n class=\"settings-page__input\"\n [class.settings-page__input--readonly]=\"fieldIsReadOnly(tab(), field.key)\"\n [disabled]=\"fieldIsReadOnly(tab(), field.key)\"\n [ngModel]=\"fieldValue(tab(), field.key)\"\n (ngModelChange)=\"updateField(tab(), field.key, $event)\"\n />\n }\n </label>\n }\n }\n </div>\n }\n }\n </article>\n </div>\n }\n</div>\n", styles: [".settings-page__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:24px}.settings-page__subtitle,.settings-page__loading{margin:6px 0 0;font-size:15px;line-height:1.5;color:var(--color-text-muted, #64748b)}.settings-page__save-row{display:flex;align-items:center;gap:12px;flex-shrink:0}.settings-page__saved{color:var(--color-success, #22c55e);font-weight:600;font-size:14px}.settings-page__layout{display:grid;grid-template-columns:240px minmax(0,1fr);gap:20px;align-items:start}.settings-page__sidebar{padding:8px;display:flex;flex-direction:column;gap:2px;position:sticky;top:16px;max-height:calc(100vh - 120px);overflow-y:auto}.settings-page__nav-item{display:flex;align-items:center;gap:10px;padding:11px 14px;border:none;background:none;border-radius:var(--radius-md, 8px);font-size:14px;font-weight:500;text-align:left;cursor:pointer;color:var(--color-text-muted, #64748b);width:100%;line-height:1.3}.settings-page__nav-item:hover{background:var(--surface-l1, #f9fafb);color:var(--color-text, #0f172a)}.settings-page__nav-item--active{background:var(--color-primary-light, #eaf2ff);color:var(--color-primary, #3681f4);font-weight:600}.settings-page__nav-item--disabled,.settings-page__nav-item:disabled{opacity:.45;cursor:not-allowed;color:var(--color-text-muted, #94a3b8)}.settings-page__nav-item--disabled:hover,.settings-page__nav-item:disabled:hover{background:none;color:var(--color-text-muted, #94a3b8)}.settings-page__nav-disabled-badge{margin-left:auto;font-size:11px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--color-text-muted, #94a3b8)}.settings-page__panel{padding:28px 32px;min-height:420px}.settings-page__panel-head{margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--color-border, #e2e8f0)}.settings-page__panel-title{margin:0 0 8px;font-size:22px;font-weight:600;color:var(--color-text, #0f172a);letter-spacing:-.01em}.settings-page__panel-desc{margin:0;font-size:15px;line-height:1.55;color:var(--color-text-muted, #64748b);max-width:640px}.settings-page__quick-link{display:flex;align-items:center;gap:14px;padding:16px 18px;margin-bottom:24px;text-decoration:none;color:inherit;border:1px solid var(--color-border, #e2e8f0);border-radius:var(--radius-md, 8px);background:var(--color-surface-alt, #f8fafc);transition:border-color .15s ease,box-shadow .15s ease}.settings-page__quick-link div{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.settings-page__quick-link:hover{border-color:var(--color-primary, #3681f4);box-shadow:var(--shadow-sm)}.settings-page__quick-link-title{font-size:15px;font-weight:600;color:var(--color-text, #0f172a)}.settings-page__quick-link-desc{font-size:14px;line-height:1.45;color:var(--color-text-muted, #64748b)}.settings-page__fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px 28px}.settings-page__field{display:flex;flex-direction:column;gap:6px}.settings-page__field-label{display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:14px;font-weight:600;color:var(--color-text, #0f172a);line-height:1.35}.settings-page__readonly-badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--color-text-muted, #64748b);background:var(--color-surface-alt, #f1f5f9);border:1px solid var(--color-border, #e2e8f0)}.settings-page__readonly-banner{display:flex;align-items:center;gap:10px;margin:0 0 20px;padding:12px 14px;border-radius:var(--radius-md, 8px);font-size:14px;line-height:1.45;color:var(--color-text-muted, #64748b);background:var(--color-surface-alt, #f8fafc);border:1px solid var(--color-border, #e2e8f0)}.settings-page__field--readonly .settings-page__field-desc{color:var(--color-text-soft, #94a3b8)}.settings-page__input--readonly,.settings-page__input:disabled{color:var(--color-text-muted, #64748b);background:var(--color-surface-alt, #f1f5f9);border-color:var(--color-border, #e2e8f0);cursor:not-allowed;opacity:1}.settings-page__toggle-row--readonly{cursor:default;opacity:.92}.settings-page__toggle-row--readonly .settings-page__checkbox:disabled{cursor:not-allowed}.settings-page__field-desc{font-size:13px;line-height:1.45;color:var(--color-text-muted, #64748b);margin-bottom:2px}.settings-page__field--full{grid-column:1/-1}.settings-page__textarea{width:100%;padding:10px 12px;border:1px solid var(--color-border-strong, #cbd5e1);border-radius:var(--radius-md, 8px);font-size:15px;line-height:1.5;color:var(--color-text, #0f172a);font-family:inherit;resize:vertical;min-height:96px}.settings-page__textarea:focus{outline:2px solid rgba(54,129,244,.35);outline-offset:1px;border-color:var(--color-primary, #3681f4)}.settings-page__char-count{font-size:12px;color:var(--color-text-muted, #64748b);text-align:right}.settings-page__recording-hint{margin:4px 0 0;font-size:13px;line-height:1.45;color:var(--color-warning, #f59e0b)}.settings-page__welcome{grid-column:1/-1;display:flex;flex-direction:column;gap:14px;padding:18px;border:1px solid var(--color-border, #e2e8f0);border-radius:var(--radius-md, 8px);background:var(--color-surface-alt, #f8fafc)}.settings-page__welcome-preview{padding:14px 16px;background:#fff}.settings-page__welcome-preview-head{display:flex;align-items:center;gap:8px;margin-bottom:10px}.settings-page__welcome-preview-text{margin:0;font-size:15px;line-height:1.55;color:var(--color-text, #0f172a);font-style:italic}.settings-page__fields--general{margin-top:0}.settings-page__input{width:100%;padding:10px 12px;border:1px solid var(--color-border-strong, #cbd5e1);border-radius:var(--radius-md, 8px);font-size:15px;line-height:1.4;color:var(--color-text, #0f172a);background:#fff}.settings-page__input:focus{outline:2px solid rgba(54,129,244,.35);outline-offset:1px;border-color:var(--color-primary, #3681f4)}.settings-page__input::placeholder{color:var(--color-text-soft, #94a3b8)}.settings-page__select{appearance:auto;cursor:pointer}.settings-page__voice-preview-row{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin-top:10px}.settings-page__toggle-row{grid-column:1/-1;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 18px;border:1px solid var(--color-border, #e2e8f0);border-radius:var(--radius-md, 8px);background:var(--color-surface-alt, #f8fafc);cursor:pointer}.settings-page__toggle-copy{display:flex;flex-direction:column;gap:4px;min-width:0}.settings-page__checkbox{width:18px;height:18px;flex-shrink:0;accent-color:var(--color-primary, #3681f4);cursor:pointer}.settings-page__safety{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:12px}.settings-page__safety li{display:flex;align-items:center;gap:12px;padding:16px 18px;background:var(--color-surface-alt, #f8fafc);border:1px solid var(--color-border, #e2e8f0);border-radius:var(--radius-md, 8px)}.settings-page__safety-label{flex:1;font-size:15px;font-weight:500;color:var(--color-text, #0f172a);line-height:1.4}@media(max-width:960px){.settings-page__layout{grid-template-columns:1fr}.settings-page__sidebar{position:static;max-height:none;flex-direction:row;flex-wrap:wrap}.settings-page__nav-item{width:auto;flex:1 1 auto}.settings-page__fields{grid-template-columns:1fr}.settings-page__panel{padding:20px}}\n"] }]
506
- }] });
507
-
508
- export { SettingsPageComponent };
509
- //# sourceMappingURL=edvak99-voice-receptionist-widget-settings-page.component-Bo3XNK3b.mjs.map