@chat21/chat21-web-widget 5.1.34-rc1 → 5.1.34

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 (193) hide show
  1. package/.github/workflows/docker-community-push-latest.yml +13 -23
  2. package/.github/workflows/docker-image-tag-community-tag-push.yml +12 -22
  3. package/CHANGELOG.md +8 -129
  4. package/Dockerfile +5 -4
  5. package/angular.json +3 -21
  6. package/docs/changelog/this-branch.md +0 -36
  7. package/env.sample +2 -3
  8. package/nginx.conf +2 -22
  9. package/package.json +3 -10
  10. package/src/app/app.component.html +2 -2
  11. package/src/app/app.component.scss +14 -25
  12. package/src/app/app.component.spec.ts +6 -21
  13. package/src/app/app.component.ts +9 -10
  14. package/src/app/app.module.ts +0 -13
  15. package/src/app/component/conversation-detail/conversation/conversation.component.html +11 -25
  16. package/src/app/component/conversation-detail/conversation/conversation.component.scss +2 -40
  17. package/src/app/component/conversation-detail/conversation/conversation.component.spec.ts +75 -644
  18. package/src/app/component/conversation-detail/conversation/conversation.component.ts +14 -100
  19. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +13 -25
  20. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts +5 -123
  21. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +0 -1
  22. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +10 -23
  23. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +1 -19
  24. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +149 -242
  25. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +5 -8
  26. package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.spec.ts +3 -53
  27. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +96 -200
  28. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +6 -211
  29. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +78 -452
  30. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +76 -291
  31. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.html +53 -113
  32. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.scss +4 -12
  33. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.spec.ts +29 -274
  34. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.html +9 -23
  35. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.spec.ts +8 -80
  36. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.html +23 -29
  37. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.spec.ts +16 -185
  38. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.ts +14 -34
  39. package/src/app/component/error-alert/error-alert.component.spec.ts +5 -65
  40. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.html +7 -16
  41. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.scss +0 -21
  42. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.spec.ts +7 -89
  43. package/src/app/component/form/form-builder/form-builder.component.html +1 -1
  44. package/src/app/component/form/form-builder/form-builder.component.spec.ts +21 -163
  45. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.html +4 -8
  46. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.scss +5 -10
  47. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.spec.ts +16 -90
  48. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.ts +0 -26
  49. package/src/app/component/form/inputs/form-label/form-label.component.spec.ts +11 -45
  50. package/src/app/component/form/inputs/form-radio-button/form-radio-button.component.spec.ts +6 -24
  51. package/src/app/component/form/inputs/form-select/form-select.component.spec.ts +5 -14
  52. package/src/app/component/form/inputs/form-text/form-text.component.html +12 -14
  53. package/src/app/component/form/inputs/form-text/form-text.component.scss +1 -11
  54. package/src/app/component/form/inputs/form-text/form-text.component.spec.ts +17 -113
  55. package/src/app/component/form/inputs/form-text/form-text.component.ts +3 -35
  56. package/src/app/component/form/inputs/form-textarea/form-textarea.component.html +11 -13
  57. package/src/app/component/form/inputs/form-textarea/form-textarea.component.scss +5 -6
  58. package/src/app/component/form/inputs/form-textarea/form-textarea.component.spec.ts +13 -149
  59. package/src/app/component/form/inputs/form-textarea/form-textarea.component.ts +0 -26
  60. package/src/app/component/form/prechat-form/prechat-form.component.html +11 -14
  61. package/src/app/component/form/prechat-form/prechat-form.component.spec.ts +10 -102
  62. package/src/app/component/form/prechat-form/prechat-form.component.ts +1 -8
  63. package/src/app/component/home/home.component.html +31 -38
  64. package/src/app/component/home/home.component.scss +2 -4
  65. package/src/app/component/home/home.component.spec.ts +11 -226
  66. package/src/app/component/home-conversations/home-conversations.component.html +26 -30
  67. package/src/app/component/home-conversations/home-conversations.component.scss +0 -3
  68. package/src/app/component/home-conversations/home-conversations.component.spec.ts +36 -212
  69. package/src/app/component/last-message/last-message.component.html +9 -15
  70. package/src/app/component/last-message/last-message.component.scss +2 -16
  71. package/src/app/component/last-message/last-message.component.spec.ts +23 -204
  72. package/src/app/component/last-message/last-message.component.ts +1 -4
  73. package/src/app/component/launcher-button/launcher-button.component.html +13 -8
  74. package/src/app/component/launcher-button/launcher-button.component.spec.ts +8 -104
  75. package/src/app/component/list-all-conversations/list-all-conversations.component.html +17 -12
  76. package/src/app/component/list-all-conversations/list-all-conversations.component.scss +0 -2
  77. package/src/app/component/list-conversations/list-conversations.component.html +22 -22
  78. package/src/app/component/menu-options/menu-options.component.html +20 -30
  79. package/src/app/component/menu-options/menu-options.component.spec.ts +9 -125
  80. package/src/app/component/message/audio/audio.component.html +15 -13
  81. package/src/app/component/message/audio/audio.component.spec.ts +5 -140
  82. package/src/app/component/message/audio/audio.component.ts +5 -1
  83. package/src/app/component/message/avatar/avatar.component.html +2 -2
  84. package/src/app/component/message/avatar/avatar.component.spec.ts +7 -99
  85. package/src/app/component/message/bubble-message/bubble-message.component.html +51 -38
  86. package/src/app/component/message/bubble-message/bubble-message.component.scss +1 -54
  87. package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +57 -154
  88. package/src/app/component/message/bubble-message/bubble-message.component.ts +11 -89
  89. package/src/app/component/message/buttons/action-button/action-button.component.html +4 -3
  90. package/src/app/component/message/buttons/action-button/action-button.component.spec.ts +5 -49
  91. package/src/app/component/message/buttons/link-button/link-button.component.scss +8 -5
  92. package/src/app/component/message/buttons/link-button/link-button.component.spec.ts +5 -50
  93. package/src/app/component/message/buttons/text-button/text-button.component.spec.ts +5 -44
  94. package/src/app/component/message/carousel/carousel.component.html +16 -29
  95. package/src/app/component/message/carousel/carousel.component.scss +8 -20
  96. package/src/app/component/message/carousel/carousel.component.spec.ts +3 -80
  97. package/src/app/component/message/carousel/carousel.component.ts +0 -16
  98. package/src/app/component/message/frame/frame.component.html +4 -9
  99. package/src/app/component/message/frame/frame.component.spec.ts +15 -34
  100. package/src/app/component/message/frame/frame.component.ts +2 -7
  101. package/src/app/component/message/html/html.component.html +1 -1
  102. package/src/app/component/message/html/html.component.scss +1 -1
  103. package/src/app/component/message/html/html.component.spec.ts +7 -24
  104. package/src/app/component/message/image/image.component.html +10 -12
  105. package/src/app/component/message/image/image.component.scss +0 -16
  106. package/src/app/component/message/image/image.component.spec.ts +15 -101
  107. package/src/app/component/message/image/image.component.ts +51 -90
  108. package/src/app/component/message/info-message/info-message.component.spec.ts +14 -26
  109. package/src/app/component/message/like-unlike/like-unlike.component.html +9 -7
  110. package/src/app/component/message/like-unlike/like-unlike.component.spec.ts +3 -31
  111. package/src/app/component/message/return-receipt/return-receipt.component.spec.ts +17 -38
  112. package/src/app/component/message/text/text.component.html +3 -3
  113. package/src/app/component/message/text/text.component.scss +86 -80
  114. package/src/app/component/message/text/text.component.spec.ts +13 -106
  115. package/src/app/component/message-attachment/message-attachment.component.spec.ts +13 -134
  116. package/src/app/component/selection-department/selection-department.component.html +23 -21
  117. package/src/app/component/selection-department/selection-department.component.spec.ts +14 -159
  118. package/src/app/component/selection-department/selection-department.component.ts +1 -8
  119. package/src/app/component/send-button/send-button.component.html +13 -5
  120. package/src/app/component/send-button/send-button.component.spec.ts +2 -2
  121. package/src/app/component/star-rating-widget/star-rating-widget.component.html +81 -51
  122. package/src/app/directives/tooltip.directive.spec.ts +4 -8
  123. package/src/app/modals/confirm-close/confirm-close.component.html +8 -20
  124. package/src/app/modals/confirm-close/confirm-close.component.scss +0 -3
  125. package/src/app/modals/confirm-close/confirm-close.component.spec.ts +4 -13
  126. package/src/app/modals/confirm-close/confirm-close.component.ts +1 -8
  127. package/src/app/pipe/html-entites-encode.pipe.spec.ts +2 -35
  128. package/src/app/pipe/marked.pipe.spec.ts +2 -38
  129. package/src/app/pipe/marked.pipe.ts +41 -51
  130. package/src/app/providers/app-config.service.ts +2 -4
  131. package/src/app/providers/brand.service.spec.ts +2 -23
  132. package/src/app/providers/brand.service.ts +1 -1
  133. package/src/app/providers/global-settings.service.spec.ts +14 -1009
  134. package/src/app/providers/global-settings.service.ts +2 -82
  135. package/src/app/providers/translator.service.ts +6 -26
  136. package/src/app/sass/_variables.scss +0 -3
  137. package/src/app/sass/animations.scss +1 -19
  138. package/src/app/utils/globals.ts +1 -21
  139. package/src/app/utils/utils-resources.ts +1 -1
  140. package/src/assets/i18n/en.json +99 -106
  141. package/src/assets/i18n/es.json +100 -107
  142. package/src/assets/i18n/fr.json +100 -107
  143. package/src/assets/i18n/it.json +98 -107
  144. package/src/assets/twp/index-dev.html +0 -18
  145. package/src/chat21-core/models/message.ts +1 -2
  146. package/src/chat21-core/providers/firebase/firebase-conversation-handler.ts +2 -3
  147. package/src/chat21-core/providers/mqtt/mqtt-conversation-handler.ts +0 -12
  148. package/src/chat21-core/providers/scripts/script.service.spec.ts +2 -12
  149. package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
  150. package/src/chat21-core/utils/utils-message.ts +0 -7
  151. package/src/chat21-core/utils/utils.ts +2 -5
  152. package/src/widget-config-template.json +1 -4
  153. package/src/widget-config.json +1 -4
  154. package/tsconfig.json +0 -5
  155. package/.angular-mcp-cache/package.json +0 -1
  156. package/.cursor/angular18-accessibility-auditor-skill.md +0 -442
  157. package/.cursor/mcp.json +0 -15
  158. package/.github/workflows/build.yml +0 -22
  159. package/.github/workflows/playwright.yml +0 -27
  160. package/mocks/voice-websocket-mock/server.cjs +0 -245
  161. package/playwright.config.ts +0 -41
  162. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.html +0 -46
  163. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +0 -83
  164. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +0 -192
  165. package/src/app/component/form/prechat-form-test-mock.ts +0 -35
  166. package/src/app/component/message/audio-sync/audio-sync.component.html +0 -18
  167. package/src/app/component/message/audio-sync/audio-sync.component.scss +0 -65
  168. package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +0 -103
  169. package/src/app/component/message/audio-sync/audio-sync.component.ts +0 -643
  170. package/src/app/providers/tts-audio-playback-coordinator.service.spec.ts +0 -117
  171. package/src/app/providers/tts-audio-playback-coordinator.service.ts +0 -109
  172. package/src/app/providers/voice/STT&TTS/openai-voice.config.ts +0 -12
  173. package/src/app/providers/voice/STT&TTS/openai-voice.provider.ts +0 -171
  174. package/src/app/providers/voice/STT&TTS/speech-provider.abstract.ts +0 -39
  175. package/src/app/providers/voice/audio.types.ts +0 -40
  176. package/src/app/providers/voice/vad.service.spec.ts +0 -28
  177. package/src/app/providers/voice/vad.service.ts +0 -70
  178. package/src/app/providers/voice/voice-streaming.service.spec.ts +0 -23
  179. package/src/app/providers/voice/voice-streaming.service.ts +0 -702
  180. package/src/app/providers/voice/voice-streaming.types.ts +0 -112
  181. package/src/app/providers/voice/voice.service.spec.ts +0 -227
  182. package/src/app/providers/voice/voice.service.ts +0 -973
  183. package/src/app/shims/onnxruntime-web-wasm.ts +0 -4
  184. package/src/assets/onnx/ort-wasm-simd-threaded.mjs +0 -59
  185. package/src/assets/onnx/ort-wasm-simd-threaded.wasm +0 -0
  186. package/src/assets/sounds/keyboard.mp3 +0 -0
  187. package/src/assets/twp/tiledesk_widget_files/widget-css-override-example.css +0 -14
  188. package/src/assets/vad/silero_vad_legacy.onnx +0 -0
  189. package/src/assets/vad/vad.worklet.bundle.min.js +0 -1
  190. package/src/chat21-core/providers/chat-manager.spec.ts +0 -72
  191. package/tests/widget-form-rich.spec.ts +0 -67
  192. package/tests/widget-index-dev-settings.spec.ts +0 -52
  193. package/tests/widget-twp-iframe.spec.ts +0 -39
@@ -1,245 +0,0 @@
1
- /**
2
- * Mock proxy voce WSS: salva l'audio in `temp/` e simula gli eventi del protocollo reale.
3
- *
4
- * Eventi JSON inviati al client (`msg.event`):
5
- * session_started | listening | transcript | thinking | speaking | done | error
6
- * Frame binari: piccolo WAV silenzio (TTS simulato), dopo `speaking`.
7
- *
8
- * Uso: `npm run voice-mock`
9
- * Env: VOICE_MOCK_PORT, VOICE_MOCK_PATH, VOICE_MOCK_SILENCE_MS (debounce fine parlato, default 700),
10
- * VOICE_MOCK_SEND_ERROR=1 (invia subito `error`)
11
- */
12
- /* eslint-disable no-console */
13
- const fs = require('fs');
14
- const path = require('path');
15
- const http = require('http');
16
- const { WebSocketServer } = require('ws');
17
-
18
- const PORT = parseInt(process.env.VOICE_MOCK_PORT || '4587', 10);
19
- const PATH = process.env.VOICE_MOCK_PATH || '/ws/voice';
20
- const SILENCE_MS = parseInt(process.env.VOICE_MOCK_SILENCE_MS || '700', 10);
21
- const SEND_ERROR = process.env.VOICE_MOCK_SEND_ERROR === '1' || process.env.VOICE_MOCK_SEND_ERROR === 'true';
22
-
23
- const here = __dirname;
24
- const outDir = path.join(here, 'temp');
25
- const DEFAULT_TOKEN = 'mock-token-ok';
26
- const DEFAULT_PROJECT = 'mock-project';
27
-
28
- if (!fs.existsSync(outDir)) {
29
- fs.mkdirSync(outDir, { recursive: true });
30
- }
31
-
32
- /** WAV PCM 16-bit mono, silenzio (decodificabile da Web Audio). */
33
- function buildSilenceWav(durationSec = 0.2) {
34
- const sampleRate = 8000;
35
- const bitsPerSample = 16;
36
- const channels = 1;
37
- const blockAlign = (channels * bitsPerSample) / 8;
38
- const byteRate = sampleRate * blockAlign;
39
- const numSamples = Math.floor(sampleRate * durationSec);
40
- const dataSize = numSamples * blockAlign;
41
- const buffer = Buffer.alloc(44 + dataSize);
42
- buffer.write('RIFF', 0);
43
- buffer.writeUInt32LE(36 + dataSize, 4);
44
- buffer.write('WAVE', 8);
45
- buffer.write('fmt ', 12);
46
- buffer.writeUInt32LE(16, 16);
47
- buffer.writeUInt16LE(1, 20);
48
- buffer.writeUInt16LE(channels, 22);
49
- buffer.writeUInt32LE(sampleRate, 24);
50
- buffer.writeUInt32LE(byteRate, 28);
51
- buffer.writeUInt16LE(blockAlign, 32);
52
- buffer.writeUInt16LE(bitsPerSample, 34);
53
- buffer.write('data', 36);
54
- buffer.writeUInt32LE(dataSize, 40);
55
- return buffer;
56
- }
57
-
58
- const TTS_MOCK_CHUNK = buildSilenceWav(0.25);
59
-
60
- function sendJson(ws, obj) {
61
- if (ws.readyState !== 1) {
62
- return;
63
- }
64
- try {
65
- ws.send(JSON.stringify(obj));
66
- } catch (e) {
67
- console.error('[voice-mock] sendJson failed', e.message);
68
- }
69
- }
70
-
71
- const server = http.createServer((_req, res) => {
72
- res.writeHead(200, { 'Content-Type': 'text/plain' });
73
- res.end(
74
- 'Tiledesk voice-websocket-mock. Events: session_started, listening, transcript, thinking, speaking, TTS binary, done, error.\n',
75
- );
76
- });
77
-
78
- const wss = new WebSocketServer({ noServer: true });
79
-
80
- server.on('upgrade', (req, socket, head) => {
81
- const host = (req.headers.host || 'localhost').split(':')[0];
82
- const u = new URL(req.url, 'http://' + host);
83
- if (u.pathname !== PATH) {
84
- socket.destroy();
85
- return;
86
- }
87
-
88
- wss.handleUpgrade(req, socket, head, (ws) => {
89
- wss.emit('connection', ws, req, u);
90
- });
91
- });
92
-
93
- wss.on('connection', (ws, _req, urlObj) => {
94
- const q = urlObj.searchParams;
95
- const token = q.get('token') || DEFAULT_TOKEN;
96
- const projectId = q.get('projectId') || DEFAULT_PROJECT;
97
- const qRequestId = q.get('requestId') || '';
98
- const mime = q.get('mimeType') || 'application/octet-stream';
99
- const ext = mime.indexOf('webm') >= 0 ? 'webm' : 'bin';
100
-
101
- const convId =
102
- qRequestId && qRequestId !== 'new'
103
- ? qRequestId
104
- : `mock-req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
105
-
106
- const safeTs = new Date().toISOString().replace(/[:.]/g, '-');
107
- const outFile = path.join(
108
- outDir,
109
- `voice-${safeTs}-p${String(projectId).replace(/[^a-zA-Z0-9._-]/g, '_')}-r${String(convId).replace(/[^a-zA-Z0-9._-]/g, '_')}.${ext}`,
110
- );
111
- const fileStream = fs.createWriteStream(outFile, { flags: 'a' });
112
- const tokenOk = String(token).length > 0;
113
-
114
- let chunkCount = 0;
115
- let silenceTimer = null;
116
- let finalPipelineStarted = false;
117
-
118
- function clearSilenceTimer() {
119
- if (silenceTimer) {
120
- clearTimeout(silenceTimer);
121
- silenceTimer = null;
122
- }
123
- }
124
-
125
- /** Dopo debounce senza nuovi chunk audio: chiude il turno come farebbe il proxy reale. */
126
- function runAfterUtteranceEnd() {
127
- if (finalPipelineStarted || ws.readyState !== 1) {
128
- return;
129
- }
130
- finalPipelineStarted = true;
131
- clearSilenceTimer();
132
-
133
- sendJson(ws, {
134
- event: 'transcript',
135
- text: '[mock] Trascrizione finale simulata.',
136
- isFinal: true,
137
- });
138
-
139
- setTimeout(() => {
140
- sendJson(ws, { event: 'thinking' });
141
- }, 150);
142
-
143
- setTimeout(() => {
144
- sendJson(ws, { event: 'speaking' });
145
- try {
146
- if (ws.readyState === 1) {
147
- ws.send(TTS_MOCK_CHUNK, { binary: true });
148
- }
149
- } catch (e) {
150
- console.error('[voice-mock] TTS binary send failed', e.message);
151
- }
152
- }, 450);
153
-
154
- setTimeout(() => {
155
- sendJson(ws, {
156
- event: 'done',
157
- url: 'https://example.com/mock-voice-session-complete',
158
- });
159
- }, 900);
160
- }
161
-
162
- function scheduleUtteranceEndCheck() {
163
- clearSilenceTimer();
164
- silenceTimer = setTimeout(runAfterUtteranceEnd, SILENCE_MS);
165
- }
166
-
167
- console.log('[voice-mock] client connected', {
168
- projectId,
169
- requestId: convId,
170
- mimeType: mime,
171
- outFile,
172
- token: tokenOk ? 'present' : 'missing',
173
- });
174
-
175
- if (!tokenOk) {
176
- sendJson(ws, { event: 'error', message: 'Missing or invalid token' });
177
- ws.close();
178
- return;
179
- }
180
-
181
- if (SEND_ERROR) {
182
- sendJson(ws, { event: 'error', message: 'VOICE_MOCK_SEND_ERROR is set' });
183
- setTimeout(() => ws.close(), 100);
184
- return;
185
- }
186
-
187
- sendJson(ws, { event: 'session_started', requestId: convId });
188
-
189
- setTimeout(() => {
190
- sendJson(ws, { event: 'listening' });
191
- }, 80);
192
-
193
- ws.on('message', (data, isBinary) => {
194
- if (isBinary || Buffer.isBuffer(data)) {
195
- const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
196
- fileStream.write(buf);
197
- chunkCount += 1;
198
-
199
- if (chunkCount === 1) {
200
- sendJson(ws, {
201
- event: 'transcript',
202
- text: '[mock] Utterance…',
203
- isFinal: false,
204
- });
205
- } else if (chunkCount === 4) {
206
- sendJson(ws, {
207
- event: 'transcript',
208
- text: '[mock] Utterance parziale aggiornata.',
209
- isFinal: false,
210
- });
211
- }
212
-
213
- finalPipelineStarted = false;
214
- scheduleUtteranceEndCheck();
215
- return;
216
- }
217
-
218
- const line = 'TEXT ' + (typeof data === 'string' ? data : data.toString()) + '\n';
219
- fileStream.write(Buffer.from(line, 'utf8'));
220
- });
221
-
222
- ws.on('close', () => {
223
- clearSilenceTimer();
224
- fileStream.end();
225
- const stat = fs.existsSync(outFile) ? fs.statSync(outFile) : null;
226
- console.log('[voice-mock] client closed, bytes on disk:', stat ? stat.size : 0, outFile);
227
- });
228
-
229
- ws.on('error', (e) => {
230
- console.error('[voice-mock] socket error', e);
231
- clearSilenceTimer();
232
- try {
233
- fileStream.end();
234
- } catch {
235
- // ignore
236
- }
237
- });
238
- });
239
-
240
- server.listen(PORT, '0.0.0.0', () => {
241
- console.log(
242
- '[voice-mock] listening on ws://127.0.0.1:' + PORT + PATH + ' — output dir: ' + outDir,
243
- );
244
- console.log('[voice-mock] silence debounce:', SILENCE_MS, 'ms (fire transcript→thinking→speaking→TTS wav→done)');
245
- });
@@ -1,41 +0,0 @@
1
- import { defineConfig, devices } from '@playwright/test';
2
-
3
- /**
4
- * See https://playwright.dev/docs/test-configuration.
5
- */
6
- export default defineConfig({
7
- testDir: './tests',
8
- timeout: 120_000,
9
- expect: { timeout: 30_000 },
10
- fullyParallel: true,
11
- forbidOnly: !!process.env.CI,
12
- retries: process.env.CI ? 2 : 0,
13
- workers: process.env.CI ? 1 : undefined,
14
- reporter: 'html',
15
- use: {
16
- baseURL: 'http://127.0.0.1:4203',
17
- trace: 'on-first-retry',
18
- },
19
-
20
- /**
21
- * Solo Chromium: i test TWP chiamano API Tiledesk reali; più browser in parallelo
22
- * aumentano flakiness. Per Firefox/WebKit decommentare i progetti sotto.
23
- */
24
- projects: [
25
- {
26
- name: 'chromium',
27
- use: { ...devices['Desktop Chrome'] },
28
- },
29
- // { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
30
- // { name: 'webkit', use: { ...devices['Desktop Safari'] } },
31
- ],
32
-
33
- webServer: {
34
- command: 'npx ng serve widget --port 4203 --host 127.0.0.1',
35
- url: 'http://127.0.0.1:4203/',
36
- reuseExistingServer: !process.env.CI,
37
- timeout: 300_000,
38
- stdout: 'pipe',
39
- stderr: 'pipe',
40
- },
41
- });
@@ -1,46 +0,0 @@
1
- <ng-container [ngSwitch]="mode">
2
- <!-- ALERT: spectrum line (fills streamAudioAlert width) -->
3
- <div *ngSwitchCase="'alert'" class="stream-audio-spectrum" [ngStyle]="accentColor ? { color: accentColor } : null">
4
- <svg class="stream-audio-spectrum__svg" viewBox="0 0 100 32" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
5
- <defs>
6
- <linearGradient [attr.id]="gradientId" x1="0" y1="16" x2="100" y2="16" gradientUnits="userSpaceOnUse">
7
- <stop offset="0%" stop-color="currentColor" stop-opacity="0.45"/>
8
- <stop offset="50%" stop-color="currentColor" stop-opacity="1"/>
9
- <stop offset="100%" stop-color="currentColor" stop-opacity="0.45"/>
10
- </linearGradient>
11
- </defs>
12
- <path class="stream-audio-spectrum__line"
13
- [attr.d]="spectrumLinePath"
14
- fill="none"
15
- [attr.stroke]="'url(#' + gradientId + ')'"
16
- stroke-width="2.4"
17
- stroke-linecap="round"
18
- stroke-linejoin="round"/>
19
- </svg>
20
- </div>
21
-
22
- <!-- BUTTON: inactive icon / expanded pill content -->
23
- <ng-container *ngSwitchCase="'button'">
24
- <span class="stream-audio-button__icon" *ngIf="!active" aria-hidden="true">
25
- <svg role="img" xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 23 23" aria-labelledby="altIconTitle">
26
- <rect x="0" y="7.5" height="6px" fill-opacity="1" width="2px" rx="0.75" ry="0.75"></rect>
27
- <rect x="4" y="5.5" height="10px" fill-opacity="1" width="2px" rx="0.75" ry="0.75"></rect>
28
- <rect x="8" y="2.5" height="16px" fill-opacity="1" width="2px" rx="0.75" ry="0.75"></rect>
29
- <rect x="12" y="5.5" height="10px" fill-opacity="1" width="2px" rx="0.75" ry="0.75"></rect>
30
- <rect x="16" y="2.5" height="16px" fill-opacity="1" width="2px" rx="0.75" ry="0.75"></rect>
31
- <rect x="20" y="7.5" height="6px" fill-opacity="1" width="2px" rx="0.75" ry="0.75"></rect>
32
- <title id="altIconTitle">{{ translationMap.get('STREAM_AUDIO') }}</title>
33
- </svg>
34
- </span>
35
-
36
- <span class="stream-audio-button__expanded" *ngIf="active">
37
- <span class="stream-audio-button__bars" aria-hidden="true">
38
- <span class="bar" [style.transform]="'scaleY(' + barScales[0] + ')'"></span>
39
- <span class="bar" [style.transform]="'scaleY(' + barScales[1] + ')'"></span>
40
- <span class="bar" [style.transform]="'scaleY(' + barScales[2] + ')'"></span>
41
- <span class="bar" [style.transform]="'scaleY(' + barScales[3] + ')'"></span>
42
- </span>
43
- <span class="stream-audio-button__label">{{ translationMap.get('CLOSE') }}</span>
44
- </span>
45
- </ng-container>
46
- </ng-container>
@@ -1,83 +0,0 @@
1
- :host {
2
- display: inline-flex;
3
- align-items: center;
4
- justify-content: center;
5
- width: 100%;
6
- }
7
-
8
- .stream-audio-spectrum {
9
- display: flex;
10
- align-items: center;
11
- justify-content: center;
12
- width: 100%;
13
- padding: 0 10px;
14
- box-sizing: border-box;
15
- }
16
-
17
- .stream-audio-spectrum__svg {
18
- width: 100%;
19
- height: 32px;
20
- display: block;
21
- }
22
-
23
- .stream-audio-spectrum__line {
24
- pointer-events: none;
25
- filter: drop-shadow(0 0 1px color-mix(in srgb, currentColor 35%, transparent));
26
- }
27
-
28
- /* ===========================
29
- * BUTTON (pill content)
30
- * =========================== */
31
- .stream-audio-button__icon {
32
- display: inline-flex;
33
- align-items: center;
34
- justify-content: center;
35
- width: 100%;
36
- }
37
-
38
- .stream-audio-button__icon svg {
39
- width: 20px;
40
- height: 20px;
41
- display: block;
42
- rect {
43
- fill: var(--icon-fill-color);
44
- }
45
- }
46
-
47
- .stream-audio-button__expanded {
48
- display: inline-flex;
49
- align-items: center;
50
- justify-content: center;
51
- gap: 12px;
52
- width: 100%;
53
- user-select: none;
54
- color: var(--icon-fill-color);
55
- }
56
-
57
- .stream-audio-button__label {
58
- font-size: 14px;
59
- line-height: 1;
60
- font-weight: 500;
61
- letter-spacing: 0.2px;
62
- white-space: nowrap;
63
- }
64
-
65
- .stream-audio-button__bars {
66
- display: inline-flex;
67
- align-items: center;
68
- justify-content: center;
69
- gap: 3px;
70
- width: 26px;
71
- height: 18px;
72
- transform-origin: center;
73
- margin: 0;
74
- line-height: 0;
75
- }
76
-
77
- .stream-audio-button__bars .bar {
78
- width: 3px;
79
- height: 100%;
80
- border-radius: 2px;
81
- background: var(--icon-fill-color);
82
- transform-origin: center;
83
- }
@@ -1,192 +0,0 @@
1
- import { Component, Input, OnChanges, OnDestroy, OnInit, Optional, SimpleChanges } from '@angular/core';
2
- import { Subscription } from 'rxjs';
3
- import { VoiceService } from 'src/app/providers/voice/voice.service';
4
-
5
- export type StreamAudioSpectrumMode = 'alert' | 'button';
6
-
7
- /**
8
- * Icona stream: cerchio con linea orizzontale tipo spettro, reattiva al volume del microfono.
9
- * Il parent (es. conversation-footer) aggiorna solo {@link volume} da VoiceService.
10
- */
11
- @Component({
12
- selector: 'chat-stream-audio-spectrum',
13
- templateUrl: './stream-audio-spectrum.component.html',
14
- styleUrl: './stream-audio-spectrum.component.scss',
15
- })
16
- export class StreamAudioSpectrumComponent implements OnInit, OnChanges {
17
- private static gradSeq = 0;
18
- readonly gradientId = `streamSpectrumGrad-${++StreamAudioSpectrumComponent.gradSeq}`;
19
-
20
- /** Volume normalizzato come emesso da VoiceService (stessa scala del footer). */
21
- @Input() volume = 0;
22
- /** Colore tema (stroke / gradient); opzionale. */
23
- @Input() accentColor?: string;
24
-
25
- /** UI variant. `alert` = spectrum line (in #streamAudioAlert). `button` = icon / pill with bars + label. */
26
- @Input() mode: StreamAudioSpectrumMode = 'alert';
27
- /** For `mode="button"`: whether the stream is active (expanded pill). */
28
- @Input() active = false;
29
- /** For `mode="button"`: VAD speech flag; if omitted, we fall back to a volume threshold heuristic. */
30
- @Input() isUserSpeaking?: boolean;
31
- /** For `mode="button"`: label on the pill. */
32
- @Input() translationMap: Map< string, string>;
33
-
34
- // ALERT (spectrum line)
35
- spectrumLinePath = 'M0,16 L100,16';
36
-
37
- // BUTTON (bars)
38
- barScales: [number, number, number, number] = [0.65, 0.65, 0.65, 0.65];
39
- private rafId: number | null = null;
40
- private lastSpeaking = false;
41
- private voiceSpeechStartSub?: Subscription;
42
- private voiceSpeechEndSub?: Subscription;
43
- private internalIsUserSpeaking = false;
44
-
45
- constructor(@Optional() private readonly voiceService: VoiceService | null) {}
46
-
47
- ngOnInit(): void {
48
- // Optional: use VAD speech events to improve idle/speaking detection.
49
- if (this.voiceService) {
50
- this.voiceSpeechStartSub = this.voiceService.speechStart$?.subscribe(() => {
51
- this.internalIsUserSpeaking = true;
52
- });
53
- this.voiceSpeechEndSub = this.voiceService.speechEnd$?.subscribe(() => {
54
- this.internalIsUserSpeaking = false;
55
- });
56
- }
57
- this.refreshAll();
58
- }
59
-
60
- ngOnChanges(changes: SimpleChanges): void {
61
- if (changes['volume'] || changes['mode'] || changes['active'] || changes['isUserSpeaking']) {
62
- this.refreshAll();
63
- }
64
- }
65
-
66
- ngOnDestroy(): void {
67
- this.stopRaf();
68
- this.voiceSpeechStartSub?.unsubscribe();
69
- this.voiceSpeechEndSub?.unsubscribe();
70
- }
71
-
72
- private refreshAll(): void {
73
- if (this.mode === 'alert') {
74
- this.refreshSpectrumPath();
75
- this.stopRaf();
76
- return;
77
- }
78
- this.refreshBars();
79
- }
80
-
81
- private refreshSpectrumPath(): void {
82
- const intensity = Math.min(this.volume / 80, 1);
83
- const t = Date.now() / 175;
84
- this.spectrumLinePath = this.buildSpectrumLinePath(intensity, t);
85
- }
86
-
87
- private buildSpectrumLinePath(intensity: number, t: number): string {
88
- const x0 = 0;
89
- const x1 = 100;
90
- const cy = 16;
91
- const segments = 100;
92
- const amp = 0.8 + intensity * 6.5;
93
- const parts: string[] = [];
94
- for (let i = 0; i <= segments; i++) {
95
- const p = i / segments;
96
- const x = x0 + p * (x1 - x0);
97
- const u = p * Math.PI * 6;
98
- const wobble =
99
- Math.sin(u + t) * 0.34 +
100
- Math.sin(u * 2.35 + t * 1.12) * 0.24 +
101
- Math.sin(u * 4.2 + t * 0.72) * 0.18 +
102
- Math.sin(u * 6.8 + t * 1.05) * 0.14 +
103
- Math.sin(u * 9.1 + t * 0.88) * 0.1;
104
- const y = cy + amp * wobble;
105
- const yClamped = Math.min(30, Math.max(2, y));
106
- parts.push(i === 0 ? `M${x.toFixed(2)},${yClamped.toFixed(2)}` : `L${x.toFixed(2)},${yClamped.toFixed(2)}`);
107
- }
108
- return parts.join('');
109
- }
110
-
111
- private refreshBars(): void {
112
- if (!this.active) {
113
- this.stopRaf();
114
- return;
115
- }
116
-
117
- const speaking = this.computeSpeaking();
118
- if (!speaking) {
119
- this.stopRaf();
120
- this.barScales = [0.65, 0.65, 0.65, 0.65];
121
- this.lastSpeaking = false;
122
- return;
123
- }
124
-
125
- // speaking: animate bars with volume-driven intensity
126
- if (!this.lastSpeaking) {
127
- this.lastSpeaking = true;
128
- }
129
- this.startRaf();
130
- }
131
-
132
- private computeSpeaking(): boolean {
133
- if (typeof this.isUserSpeaking === 'boolean') {
134
- return this.isUserSpeaking;
135
- }
136
- if (this.voiceService) {
137
- return this.internalIsUserSpeaking;
138
- }
139
- // Fallback heuristic: treat as speaking when volume crosses a low threshold.
140
- return (this.volume || 0) >= 4;
141
- }
142
-
143
- private startRaf(): void {
144
- if (this.rafId !== null) {
145
- return;
146
- }
147
- const tick = () => {
148
- if (!this.active) {
149
- this.stopRaf();
150
- return;
151
- }
152
- const speaking = this.computeSpeaking();
153
- if (!speaking) {
154
- this.stopRaf();
155
- this.barScales = [0.65, 0.65, 0.65, 0.65];
156
- return;
157
- }
158
-
159
- const intensity = Math.min((this.volume || 0) / 80, 1);
160
- const t = performance.now() / 220;
161
- const targets: [number, number, number, number] = [0.35, 0.35, 0.35, 0.35];
162
-
163
- for (let i = 0; i < 4; i++) {
164
- const phase = i * 0.9;
165
- const w1 = (Math.sin(t * 1.35 + phase) + 1) / 2;
166
- const w2 = (Math.sin(t * 2.05 + phase * 1.7) + 1) / 2;
167
- const mix = w1 * 0.62 + w2 * 0.38;
168
- const s = 0.25 + intensity * (0.25 + 0.95 * mix);
169
- targets[i as 0 | 1 | 2 | 3] = Math.max(0.35, Math.min(1.2, s));
170
- }
171
-
172
- // Smooth toward targets to avoid jitter on rapid volume changes.
173
- const lerp = (a: number, b: number, k: number) => a + (b - a) * k;
174
- this.barScales = [
175
- lerp(this.barScales[0], targets[0], 0.35),
176
- lerp(this.barScales[1], targets[1], 0.35),
177
- lerp(this.barScales[2], targets[2], 0.35),
178
- lerp(this.barScales[3], targets[3], 0.35),
179
- ];
180
-
181
- this.rafId = requestAnimationFrame(tick);
182
- };
183
- this.rafId = requestAnimationFrame(tick);
184
- }
185
-
186
- private stopRaf(): void {
187
- if (this.rafId !== null) {
188
- cancelAnimationFrame(this.rafId);
189
- this.rafId = null;
190
- }
191
- }
192
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Shared mock for pre-chat dynamic form (unit / integration-style tests).
3
- * Mirrors a typical `g.preChatFormJson` payload with i18n labels and email regex.
4
- */
5
- export const PRECHAT_FORM_JSON_MOCK = [
6
- {
7
- name: 'userFullname',
8
- type: 'text',
9
- mandatory: true,
10
- label: {
11
- en: 'User fullname',
12
- it: 'Nome utente',
13
- },
14
- },
15
- {
16
- name: 'userEmail',
17
- type: 'text',
18
- mandatory: true,
19
- regex:
20
- "/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$/",
21
- label: {
22
- en: 'Email',
23
- it: 'Indirizzo email',
24
- },
25
- errorLabel: {
26
- en: 'Invalid email address',
27
- it: 'Indirizzo email non valido',
28
- },
29
- },
30
- ];
31
-
32
- /** Deep clone so `setTranslations` / `buildFormGroup` can mutate safely in tests. */
33
- export function clonePrechatFormJsonMock(): any[] {
34
- return JSON.parse(JSON.stringify(PRECHAT_FORM_JSON_MOCK));
35
- }
@@ -1,18 +0,0 @@
1
- <div class="lyrics-container">
2
-
3
- <audio
4
- #audioPlayer
5
- (timeupdate)="onTimeUpdate()"
6
- style="display:none">
7
- </audio>
8
-
9
- <p class="lyrics message_innerhtml marked" #transcriptBox [style.color]="color">
10
- <span
11
- *ngFor="let w of words; let i = index; trackBy: trackByIndex"
12
- class="word"
13
- [ngClass]="w.state">
14
- {{ w.text }}
15
- </span>
16
- </p>
17
-
18
- </div>