@chat21/chat21-web-widget 5.1.32-rc2 → 5.1.32-rc4

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.
package/CHANGELOG.md CHANGED
@@ -6,8 +6,18 @@
6
6
  ### **Copyrigth**:
7
7
  *Tiledesk SRL*
8
8
 
9
+ # 5.1.32-rc4
10
+ - **added**: Pulsante “chiudi stream” (`.close-stream-button`) — offset del contenuto e del foglio in fullscreen usando `--chat-footer-stream-button-height` solo mentre lo stream è in ascolto (`isStreamAudioActive`); variabili in `_variables.scss`.
11
+ - **added**: `VoiceService.discardCurrentRecordingSegment()` — in arrivo un messaggio da altro mittente durante lo stream, si scarta il segmento WebM corrente (nessun upload) senza chiudere mic/VAD; `interruptStreamDueToPeerMessage()` nel footer non spegne più `isStreamAudioActive`.
12
+ - **changed**: `#streamAudioAlert` — fascia superiore al footer con effetto vetro satinato (`backdrop-filter`, `color-mix` semi-trasparente).
13
+
14
+ # 5.1.32-rc3
15
+ - **changed**: `nginx.conf` (immagine Docker) — tipi MIME espliciti per `.mjs`, `.wasm`, `.onnx` e `default_type` a livello `http` (evita `text/plain` su moduli ONNX/VAD dietro deploy containerizzato).
16
+ - **chore**: rimossi script di deploy Amazon beta/prod deprecati dal repository.
17
+
9
18
  # 5.1.32-rc2
10
19
  - **bug fixed**: minor streaming icon UI fixed
20
+ - **changed**: Refactor UI pulsante stream audio nel conversation footer (layout / classi).
11
21
 
12
22
  # 5.1.32-rc1
13
23
  - **added**: Voice pipeline — VAD (`@ricky0123/vad-web`) with ONNX Runtime WASM served from `/assets/onnx` (`copy-onnx-wasm`), `VoiceService` with `audioSegment$` (WebM segments) and optional STT/TTS via unified OpenAI provider using `HttpClient`, transcript / error fields on segment payloads.
package/nginx.conf CHANGED
@@ -5,18 +5,38 @@ events {
5
5
  }
6
6
 
7
7
  http {
8
+ include /etc/nginx/mime.types;
9
+ default_type application/octet-stream;
10
+
8
11
  server {
9
12
  listen 80;
10
13
  server_name localhost;
11
14
 
12
15
  root /usr/share/nginx/html;
13
16
  index index.html index.htm;
14
- include /etc/nginx/mime.types;
15
17
 
16
18
  gzip on;
17
19
  gzip_min_length 1000;
18
20
  gzip_proxied expired no-cache no-store private auth;
19
- gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
21
+ gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript application/wasm;
22
+
23
+ # ONNX Runtime (.mjs), WASM e modelli VAD: il browser rifiuta moduli ES con Content-Type: text/plain
24
+ location ~* \.mjs$ {
25
+ root /usr/share/nginx/html;
26
+ default_type application/javascript;
27
+ charset utf-8;
28
+ add_header Cache-Control "public, max-age=31536000, immutable";
29
+ }
30
+ location ~* \.wasm$ {
31
+ root /usr/share/nginx/html;
32
+ default_type application/wasm;
33
+ add_header Cache-Control "public, max-age=31536000, immutable";
34
+ }
35
+ location ~* \.onnx$ {
36
+ root /usr/share/nginx/html;
37
+ default_type application/octet-stream;
38
+ add_header Cache-Control "public, max-age=31536000, immutable";
39
+ }
20
40
 
21
41
  location / {
22
42
  try_files $uri $uri/ /index.html;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chat21/chat21-web-widget",
3
3
  "author": "Tiledesk SRL",
4
- "version": "5.1.32-rc2",
4
+ "version": "5.1.32-rc4",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.tiledesk.com",
7
7
  "repository": {
@@ -4,7 +4,8 @@
4
4
  <div id="chat21-conversation-component"
5
5
  #afConversationComponent
6
6
  tabindex="1500"
7
- aria-modal="true">
7
+ aria-modal="true"
8
+ [class.chat21-conversation--close-stream-active]="closeStreamButtonActiveForSheetBottom()">
8
9
 
9
10
  <!-- HEADER -->
10
11
  <chat-conversation-header
@@ -242,6 +242,16 @@ dialog:-internal-dialog-in-top-layer{
242
242
  ::ng-deep .chat21-sheet-content{
243
243
  bottom: calc(var(--chat-footer-logo-height) + var(--chat-footer-height) + var(--chat-footer-close-button-height) + 34px)!important;
244
244
  }
245
+
246
+ /* Con `.close-stream-button` (stream in ascolto): spazio per alert stream sopra il footer */
247
+ #chat21-conversation-component.chat21-conversation--close-stream-active ::ng-deep .chat21-sheet-content {
248
+ bottom: calc(
249
+ var(--chat-footer-logo-height) +
250
+ var(--chat-footer-height) +
251
+ var(--chat-footer-stream-button-height) +
252
+ 34px
253
+ ) !important;
254
+ }
245
255
 
246
256
  }
247
257
 
@@ -827,6 +827,10 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
827
827
  this.showThinkingMessage = false;
828
828
  }
829
829
 
830
+ if (this.isStreamAudioActive && msg.sender !== this.senderId) {
831
+ this.conversationFooter?.interruptStreamDueToPeerMessage();
832
+ }
833
+
830
834
  that.newMessageAdded(msg);
831
835
  // Update badge based on the latest message received from the server.
832
836
  // We rely on `messages` being kept in-sync by the conversation handler.
@@ -1414,6 +1418,13 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1414
1418
  }
1415
1419
  // =========== END: event emitter function ====== //
1416
1420
 
1421
+ /**
1422
+ * True quando è visibile il pulsante chiudi stream (`.close-stream-button`, `isStreamAudioActive`).
1423
+ * Solo in quel caso il bottom del foglio include `--chat-footer-stream-button-height`.
1424
+ */
1425
+ closeStreamButtonActiveForSheetBottom(): boolean {
1426
+ return !!(this.g?.showAudioStreamFooterButton && this.isStreamAudioActive);
1427
+ }
1417
1428
 
1418
1429
  openInputFiles() {
1419
1430
  alert('ok');
@@ -270,6 +270,15 @@
270
270
  }// end c21-body-container
271
271
  }// end c21-body
272
272
 
273
+ /* Solo con pulsante chiudi stream (stream in ascolto): altezza extra come #streamAudioAlert */
274
+ :host-context(#chat21-conversation-component.chat21-conversation--close-stream-active) .c21-body .c21-body-container .c21-body-content .chat21-sheet-content {
275
+ bottom: calc(
276
+ var(--chat-footer-logo-height) +
277
+ var(--chat-footer-height) +
278
+ var(--chat-footer-stream-button-height)
279
+ );
280
+ }
281
+
273
282
  @keyframes thinking-dot {
274
283
  0%, 80%, 100% {
275
284
  opacity: 0.2;
@@ -393,19 +393,28 @@ textarea:active{
393
393
  #streamAudioAlert {
394
394
  bottom: 100%;
395
395
  width: 100%;
396
- min-height: 96px;
396
+ min-height: var(--chat-footer-stream-button-height);
397
397
  display: flex;
398
398
  align-items: center;
399
399
  justify-content: center;
400
- background-color: var(--content-background-color);
401
400
  position: absolute;
402
- padding: 10px 0;
401
+ padding: var(--chat-footer-stream-button-padding);
402
+ /* Satinato / vetro: più trasparenza, blur più marcato */
403
+ background-color: color-mix(in srgb, var(--content-background-color) 34%, transparent);
404
+ backdrop-filter: blur(20px) saturate(1.2);
405
+ -webkit-backdrop-filter: blur(20px) saturate(1.2);
406
+ box-shadow:
407
+ inset 0 1px 0 rgba(255, 255, 255, 0.28),
408
+ 0 1px 0 rgba(0, 0, 0, 0.05);
403
409
 
404
410
  &.hideTextReply {
405
411
  position: unset;
406
412
  min-height: auto;
407
413
  padding: 16px 0;
408
414
  box-shadow: none;
415
+ backdrop-filter: none;
416
+ -webkit-backdrop-filter: none;
417
+ background-color: var(--content-background-color);
409
418
  }
410
419
  }
411
420
 
@@ -181,14 +181,30 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
181
181
  await this.voiceService.startSession();
182
182
  }
183
183
 
184
- async stopVoice() {
184
+ async stopVoice(options?: { discardInProgressSegment?: boolean }) {
185
185
  this.voiceAudioSubscription?.unsubscribe();
186
186
  this.voiceAudioSubscription = undefined;
187
187
 
188
188
  this.voiceVolumeSubscription?.unsubscribe();
189
189
  this.voiceVolumeSubscription = undefined;
190
190
 
191
- await this.voiceService.stopSession();
191
+ await this.voiceService.stopSession(options);
192
+ }
193
+
194
+ /**
195
+ * Messaggio in arrivo da un altro mittente mentre lo stream è attivo: scarta solo il segmento
196
+ * registrato in quel momento (nessun upload); mic + VAD restano attivi, `isStreamAudioActive` true.
197
+ */
198
+ interruptStreamDueToPeerMessage(): void {
199
+ if (!this.isStreamAudioActive) {
200
+ return;
201
+ }
202
+ this.logger.log('[CONV-FOOTER] discard recording segment: incoming message from peer (stream stays on)');
203
+ try {
204
+ this.voiceService.discardCurrentRecordingSegment();
205
+ } catch (e) {
206
+ this.logger.error('[CONV-FOOTER] interruptStreamDueToPeerMessage', e);
207
+ }
192
208
  }
193
209
 
194
210
  updateWave(volume: number) {
@@ -100,9 +100,20 @@ export class VoiceService {
100
100
  this.startVolumeLoop();
101
101
  }
102
102
 
103
- async stopSession(): Promise<void> {
104
- if (this.mediaRecorder?.state === 'recording') {
105
- this.mediaRecorder.stop();
103
+ /**
104
+ * @param options.discardInProgressSegment — non inviare STT/upload per il segmento WebM corrente (es. interruzione da messaggio in arrivo).
105
+ */
106
+ async stopSession(options?: { discardInProgressSegment?: boolean }): Promise<void> {
107
+ const discard = options?.discardInProgressSegment === true;
108
+
109
+ if (this.mediaRecorder) {
110
+ if (discard) {
111
+ this.mediaRecorder.onstop = null;
112
+ this.mediaRecorder.ondataavailable = null;
113
+ }
114
+ if (this.mediaRecorder.state === 'recording') {
115
+ this.mediaRecorder.stop();
116
+ }
106
117
  }
107
118
 
108
119
  this.mediaRecorder = undefined;
@@ -134,6 +145,23 @@ export class VoiceService {
134
145
  this.onRecordingComplete = undefined;
135
146
  }
136
147
 
148
+ /**
149
+ * Scarta il segmento WebM in corso (nessun upload/STT) senza chiudere VAD, mic o sessione.
150
+ * Lo stream resta in ascolto per il prossimo `onSpeechStart`.
151
+ */
152
+ discardCurrentRecordingSegment(): void {
153
+ if (this.mediaRecorder) {
154
+ this.mediaRecorder.onstop = null;
155
+ this.mediaRecorder.ondataavailable = null;
156
+ if (this.mediaRecorder.state === 'recording') {
157
+ this.mediaRecorder.stop();
158
+ }
159
+ }
160
+ this.mediaRecorder = undefined;
161
+ this.audioChunks = [];
162
+ this.logger.log('[VoiceService] discarded in-progress segment; VAD session unchanged');
163
+ }
164
+
137
165
  /**
138
166
  * 🎧 AUDIO ANALYSER INIT
139
167
  */
@@ -161,7 +189,9 @@ export class VoiceService {
161
189
  return;
162
190
  }
163
191
 
164
- this.analyser.getByteFrequencyData(this.dataArray);
192
+ this.analyser.getByteFrequencyData(
193
+ this.dataArray as Parameters<AnalyserNode['getByteFrequencyData']>[0],
194
+ );
165
195
 
166
196
  let sum = 0;
167
197
  for (let i = 0; i < this.dataArray.length; i++) {
@@ -38,6 +38,8 @@
38
38
  --chat-footer-logo-height: 30px;
39
39
  --chat-footer-close-button-height: 30px;
40
40
  --chat-footer-border-radius: 16px;
41
+ --chat-footer-stream-button-height: 96px;
42
+ --chat-footer-stream-button-padding: 10px 0;
41
43
  --chat-footer-background-color: #f6f7fb;
42
44
  --chat-footer-color: #1a1a1a;
43
45
  --chat-footer-border-color-error: #aa0404;
@@ -1,41 +0,0 @@
1
- # npm version patch
2
- version=`node -e 'console.log(require("./package.json").version)'`
3
- echo "version $version"
4
-
5
- npm i
6
-
7
- cp src/environments/real_data/environment.prod.ts src/environments/environment.prod.ts
8
-
9
- # --build-optimizer=false if localstorage is disabled (webview) appears https://github.com/firebase/angularfire/issues/970
10
- ng build --configuration="prod" --aot=true
11
- ##--base-href='./v5/' --output-hashing none
12
-
13
- ### SET HASHING : START ###
14
- cp ./src/launch_template.js ./dist/browser/launch.js
15
- node ./src/build_launch.js
16
- ### SET HASHING : END ###
17
-
18
- #### FIREBASE #####
19
- # cd dist
20
- # # aws s3 sync . s3://tiledesk-widget/v5/latest/
21
- # aws s3 sync . s3://tiledesk-widget/v5/$version/ --cache-control max-age=300
22
- # aws s3 sync . s3://tiledesk-widget/v5/ --cache-control max-age=300
23
- # cd ..
24
-
25
- # #### MQTT #####
26
- cd dist/browser
27
- # aws s3 sync . s3://tiledesk-widget/v5/latest/
28
- aws s3 sync . s3://tiledesk-widget/v6/$version/ --cache-control max-age=86400 --exclude='launch.js' #8days
29
- aws s3 sync . s3://tiledesk-widget/v6/$version/ --cache-control "no-store,no-cache,private" --exclude='*' --include='launch.js'
30
- aws s3 sync . s3://tiledesk-widget/v6/ --cache-control max-age=86400 --exclude='launch.js' #8days
31
- aws s3 sync . s3://tiledesk-widget/v6/ --cache-control "no-store,no-cache,private" --exclude='*' --include='launch.js'
32
- cd ../..
33
-
34
- aws cloudfront create-invalidation --distribution-id E3EJDWEHY08CZZ --paths "/*"
35
-
36
- git restore src/environments/environment.prod.ts
37
-
38
- echo new version deployed $version on s3://tiledesk-widget/v6
39
- echo available on https://s3.eu-west-1.amazonaws.com/tiledesk-widget/v6/index.html
40
- echo https://widget.tiledesk.com/v6/index.html
41
- echo https://widget.tiledesk.com/v6/$version/index.html