@chat21/chat21-web-widget 5.1.32-rc1 → 5.1.32-rc13

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 (25) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/deploy_amazon_beta.sh +17 -7
  3. package/nginx.conf +22 -2
  4. package/package.json +1 -1
  5. package/src/app/app.module.ts +2 -0
  6. package/src/app/component/conversation-detail/conversation/conversation.component.html +2 -1
  7. package/src/app/component/conversation-detail/conversation/conversation.component.scss +10 -0
  8. package/src/app/component/conversation-detail/conversation/conversation.component.ts +13 -1
  9. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +7 -3
  10. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +18 -0
  11. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +24 -41
  12. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +37 -51
  13. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +37 -26
  14. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.html +43 -0
  15. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +79 -0
  16. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +192 -0
  17. package/src/app/component/message/audio-sync/audio-sync.component.html +0 -1
  18. package/src/app/component/message/audio-sync/audio-sync.component.scss +0 -1
  19. package/src/app/component/message/audio-sync/audio-sync.component.ts +378 -17
  20. package/src/app/providers/tts-audio-playback-coordinator.service.ts +93 -0
  21. package/src/app/providers/voice/voice.service.ts +117 -5
  22. package/src/app/sass/_variables.scss +2 -0
  23. package/src/launch.js +41 -32
  24. package/src/launch_template.js +41 -32
  25. package/deploy_amazon_prod.sh +0 -41
package/CHANGELOG.md CHANGED
@@ -6,6 +6,57 @@
6
6
  ### **Copyrigth**:
7
7
  *Tiledesk SRL*
8
8
 
9
+ # 5.1.32-rc13
10
+ - **changed**: stream audio footer UI — stream button expands into a “Terminate” pill with animated level bars driven by mic intensity; recorder icon hidden while streaming; textarea width adjusted while streaming
11
+ - **changed**: `StreamAudioSpectrum` — consolidated stream spectrum + stream button visuals into a single component with improved silence vs speaking handling and volume-driven bar heights
12
+ - **changed**: conversation layout while streaming — adjusted received bubble sizing (`fullSizeMessage`) and loading spinner spacing (`fullSize`) for full-width stream mode
13
+ - **added**: VAD speech state events (`speechStart$`, `speechEnd$`) to improve UI/state transitions around user speech
14
+
15
+
16
+ # 5.1.32-rc12
17
+ - **changed**: voice acquisition blocking during TTS response — pause VAD after user speech ends until the TTS response cycle completes; added safety timeout and `isAcquisitionBlocked$` to drive UI (e.g. greyed spectrum)
18
+ - **chore**: version bump to `5.1.32-rc12`
19
+
20
+ # 5.1.32-rc11
21
+ - **added**: global TTS stop — `TtsAudioPlaybackCoordinator.stopAll()` + `stopAllPlayback$` to abort current and queued TTS playback and reveal full message text
22
+ - **changed**: stop TTS playback when closing stream audio
23
+ - **chore**: version bump to `5.1.32-rc11`
24
+
25
+ # 5.1.32-rc10
26
+ - **added**: TTS playback state (`isTTSPlaying$`) to coordinate voice UI and suppress mic segment emission while the bot is speaking
27
+ - **changed**: stream spectrum theme color turns grey while TTS is playing
28
+
29
+
30
+ # 5.1.32-rc9
31
+ - **added**: mic-triggered TTS interruption — when VAD detects user speech, stop current TTS playback, clear the queue, and reveal the full message text
32
+ - **added**: global TTS stop API (`TtsAudioPlaybackCoordinator.stopAll()` + `stopAllPlayback$`) to stop current + queued TTS playback from UI/events (e.g. close stream)
33
+ - **changed**: `chat-audio-sync` TTS playback now streams audio via authenticated POST to `message.metadata.src`, sending `voiceSettings` + `text` and `streaming: true`
34
+ - **changed**: stream UI spectrum — removed circular orb and stretched the spectrum line to fill the `#streamAudioAlert` width with 10px side padding
35
+ - **changed**: conversation content layout while streaming — adjusted received bubble left margin and loading spinner margins for full-size mode
36
+
37
+
38
+ # 5.1.32-rc8
39
+ - **changed**: updated the dev environment defaults to align with the stage setup (remote config URL, API endpoints, logging level, storage prefix, and related settings)
40
+
41
+ # 5.1.32-rc7
42
+ - **added**: `StreamAudioSpectrum` component for audio visualization in the streaming footer UI
43
+ - **added**: TTS playback coordinator queue — ensures TTS messages play sequentially without interrupting the previous one
44
+ - **changed**: `chat-audio-sync` — updated TTS audio handling to support streaming playback and improved autoplay/animation timing
45
+ - **changed**: iframe loader (`launch.js`, `launch_template.js`) — streamlined loading logic and improved error handling, with fixes for localhost environments
46
+
47
+ # 5.1.32-rc4
48
+ - **added**: “Close stream” control (`.close-stream-button`) — content and sheet bottom offset in fullscreen using `--chat-footer-stream-button-height` only while the stream is listening (`isStreamAudioActive`); variables in `_variables.scss`.
49
+ - **added**: `VoiceService.discardCurrentRecordingSegment()` — when a message arrives from another sender during streaming, the current WebM segment is discarded (no upload) without stopping mic/VAD; `interruptStreamDueToPeerMessage()` in the footer no longer clears `isStreamAudioActive`.
50
+ - **changed**: `#streamAudioAlert` — band above the footer with a frosted-glass look (`backdrop-filter`, semi-transparent `color-mix`).
51
+
52
+ # 5.1.32-rc3
53
+ - **changed**: `nginx.conf` (Docker image) — explicit MIME types for `.mjs`, `.wasm`, `.onnx` and `default_type` at `http` level (avoids `text/plain` on ONNX/VAD modules behind containerized deploys).
54
+ - **chore**: removed deprecated Amazon beta/prod deploy scripts from the repository.
55
+
56
+ # 5.1.32-rc2
57
+ - **bug fixed**: minor streaming icon UI fixed
58
+ - **changed**: Refactor stream audio button UI in the conversation footer (layout / classes).
59
+
9
60
  # 5.1.32-rc1
10
61
  - **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.
11
62
  - **added**: Stream audio UI in conversation footer — toggle, real-time volume stream and animated waveform (`volume$`); mic session lifecycle wired to upload segments on speech end.
@@ -2,8 +2,16 @@
2
2
  version=`node -e 'console.log(require("./package.json").version)'`
3
3
  echo "version $version"
4
4
 
5
+ npm i
6
+
7
+ cp src/environments/real_data/environment.pre.ts src/environments/environment.pre.ts
8
+
5
9
  ng build --configuration="pre" --aot=true --base-href
6
10
 
11
+ ### SET HASHING : START ###
12
+ cp ./src/launch_template.js ./dist/browser/launch.js
13
+ node ./src/build_launch.js
14
+ ### SET HASHING : END ###
7
15
 
8
16
  # ########## --->>>> NATIVE-MQTT folder START <<<<<------ ########## #
9
17
 
@@ -23,15 +31,17 @@ ng build --configuration="pre" --aot=true --base-href
23
31
 
24
32
 
25
33
  # ########## --->>>> FIREBASE folder START <<<<<------ ########## #
26
- cd dist
27
- aws s3 sync . s3://tiledesk-widget-pre/v5/$version/ --cache-control max-age=300
28
- aws s3 sync . s3://tiledesk-widget-pre/v5/ --cache-control max-age=300
29
- cd ..
30
-
31
- #aws cloudfront create-invalidation --distribution-id E3EJDWEHY08CZZ --paths "/*"
32
- cd ..
34
+ cd dist/browser
35
+ aws s3 sync . s3://tiledesk-widget-pre/v5/$version/ --cache-control max-age=300 --exclude='launch.js' #7days
36
+ aws s3 sync . s3://tiledesk-widget-pre/v5/$version/ --cache-control "no-store,no-cache,private" --exclude='*' --include='launch.js'
37
+ aws s3 sync . s3://tiledesk-widget-pre/v5/ --cache-control max-age=300 --exclude='launch.js' #7days
38
+ aws s3 sync . s3://tiledesk-widget-pre/v5/ --cache-control "no-store,no-cache,private" --exclude='*' --include='launch.js'
39
+ cd ../..
33
40
 
34
41
  aws cloudfront create-invalidation --distribution-id E2V5O0YPR61V8P --paths "/*"
42
+
43
+ git restore src/environments/environment.pre.ts
44
+
35
45
  # echo new version deployed $NEW_VER/$NEW_BUILD/ on s3://tiledesk-widget-pre/v2
36
46
  echo new version deployed $version/ on s3://tiledesk-widget-pre/v5 and s3://tiledesk-widget-pre/v5/$version/
37
47
  echo available on https://s3.eu-west-1.amazonaws.com/tiledesk-widget-pre/v5/index.html
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-rc1",
4
+ "version": "5.1.32-rc13",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.tiledesk.com",
7
7
  "repository": {
@@ -16,6 +16,7 @@ import { ConversationFooterComponent } from './component/conversation-detail/con
16
16
  import { ConversationInternalFrameComponent } from './component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component';
17
17
  import { ConversationPreviewComponent } from './component/conversation-detail/conversation-preview/conversation-preview.component';
18
18
  import { ConversationAudioRecorderComponent } from './component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component';
19
+ import { StreamAudioSpectrumComponent } from './component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component';
19
20
  /** CONVERSATION-DETAIL COMPONENTS */
20
21
  import { BubbleMessageComponent } from './component/message/bubble-message/bubble-message.component';
21
22
  import { AvatarComponent } from './component/message/avatar/avatar.component';
@@ -294,6 +295,7 @@ export function uploadFactory(http: HttpClient, appConfig: AppConfigService, app
294
295
  ConversationPreviewComponent,
295
296
  ConversationInternalFrameComponent,
296
297
  ConversationAudioRecorderComponent,
298
+ StreamAudioSpectrumComponent,
297
299
  BubbleMessageComponent,
298
300
  AvatarComponent,
299
301
  FrameComponent,
@@ -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
 
@@ -251,7 +251,8 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
251
251
  'EMOJI_NOT_ELLOWED',
252
252
  'ATTACHMENT',
253
253
  'EMOJI',
254
- 'CLOSE_CHAT'
254
+ 'CLOSE_CHAT',
255
+ 'CLOSE'
255
256
  ];
256
257
 
257
258
  const keysContent = [
@@ -827,6 +828,10 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
827
828
  this.showThinkingMessage = false;
828
829
  }
829
830
 
831
+ if (this.isStreamAudioActive && msg.sender !== this.senderId) {
832
+ this.conversationFooter?.interruptStreamDueToPeerMessage();
833
+ }
834
+
830
835
  that.newMessageAdded(msg);
831
836
  // Update badge based on the latest message received from the server.
832
837
  // We rely on `messages` being kept in-sync by the conversation handler.
@@ -1414,6 +1419,13 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1414
1419
  }
1415
1420
  // =========== END: event emitter function ====== //
1416
1421
 
1422
+ /**
1423
+ * True quando è visibile il pulsante chiudi stream (`.close-stream-button`, `isStreamAudioActive`).
1424
+ * Solo in quel caso il bottom del foglio include `--chat-footer-stream-button-height`.
1425
+ */
1426
+ closeStreamButtonActiveForSheetBottom(): boolean {
1427
+ return !!(this.g?.showAudioStreamFooterButton && this.isStreamAudioActive);
1428
+ }
1417
1429
 
1418
1430
  openInputFiles() {
1419
1431
  alert('ok');
@@ -49,7 +49,7 @@
49
49
  <!-- message RECIPIENT:: -->
50
50
  <div role="messaggio" *ngIf="messageType(MESSAGE_TYPE_OTHERS, message)" class="msg_container base_receive">
51
51
 
52
- <chat-avatar-image *ngIf="!isSameSender(message?.sender, i)"
52
+ <chat-avatar-image *ngIf="!isSameSender(message?.sender, i) && !isStreamAudioActive"
53
53
  [ngClass]="{'slide-in-left': false}"
54
54
  [senderID]="message?.sender"
55
55
  [senderFullname]="message?.sender_fullname"
@@ -62,7 +62,8 @@
62
62
  [ngClass]="{'slide-in-left': false}"
63
63
  [class.no-background]="(isImage(message) || isFrame(message) || isCarousel(message)) && ((message?.text && message?.text.trim() === '') || !message?.text)"
64
64
  [class.emoticon]="isEmojii(message?.text)"
65
- [style.margin-left]="isSameSender(message?.sender, i)? 'calc(var(--avatar-width) + 10px)': null"
65
+ [class.fullSizeMessage]="isStreamAudioActive"
66
+ [style.margin-left]="isSameSender(message?.sender, i) ? 'calc(var(--avatar-width) + 10px)' : null"
66
67
  [ngStyle]="{'background': stylesMap.get('bubbleReceivedBackground'), 'color': stylesMap.get('bubbleReceivedTextColor'), 'width':isFrame(message) ?'100%' : null}"
67
68
  [isSameSender]="isSameSender(message?.sender, i)"
68
69
  [message]="message"
@@ -134,9 +135,10 @@
134
135
  [senderFullname]="nameUserTypingNow"
135
136
  [baseLocation]="baseLocation">
136
137
  </chat-avatar-image>
138
+
137
139
  <user-typing
138
- [ngClass]="{'userTypingNowExist': !idUserTypingNow}"
139
140
  [color]="stylesMap?.get('iconColor')"
141
+ [ngClass]="{'userTypingNowExist': !idUserTypingNow}"
140
142
  [translationMap]="translationMap"
141
143
  [idUserTypingNow]="idUserTypingNow"
142
144
  [nameUserTypingNow]="nameUserTypingNow">
@@ -145,7 +147,9 @@
145
147
 
146
148
  <div *ngIf="showThinkingMessage && lastServerSenderKind === 'bot'" class="msg_container base_receive thinking_receive">
147
149
  <user-typing class="loading thinking-dots"
150
+ [class.fullSize]="isStreamAudioActive"
148
151
  [color]="stylesMap?.get('iconColor')"
152
+ [class.fullSize]="isStreamAudioActive"
149
153
  [translationMap]="translationMap"
150
154
  [idUserTypingNow]="idUserTypingNow"
151
155
  [nameUserTypingNow]="nameUserTypingNow">
@@ -27,6 +27,10 @@
27
27
  margin: 25px 50px
28
28
  }
29
29
 
30
+ :host .loading.fullSize ::ng-deep > div.spinner{
31
+ margin: 50px 0px !important;
32
+ }
33
+
30
34
  // ============= CSS c21-body ================= //
31
35
  .c21-body {
32
36
  // -webkit-box-shadow: inset 0 10px 10px -10px rgba(0,0,0,0.4);
@@ -236,6 +240,11 @@
236
240
  height: fit-content;
237
241
  width: auto;
238
242
 
243
+ &.fullSizeMessage {
244
+ max-width: 100%;
245
+ margin: auto 0 auto 0 !important;
246
+ }
247
+
239
248
  }
240
249
 
241
250
 
@@ -270,6 +279,15 @@
270
279
  }// end c21-body-container
271
280
  }// end c21-body
272
281
 
282
+ /* Solo con pulsante chiudi stream (stream in ascolto): altezza extra come #streamAudioAlert */
283
+ :host-context(#chat21-conversation-component.chat21-conversation--close-stream-active) .c21-body .c21-body-container .c21-body-content .chat21-sheet-content {
284
+ bottom: calc(
285
+ var(--chat-footer-logo-height) +
286
+ var(--chat-footer-height) +
287
+ var(--chat-footer-stream-button-height)
288
+ );
289
+ }
290
+
273
291
  @keyframes thinking-dot {
274
292
  0%, 80%, 100% {
275
293
  opacity: 0.2;
@@ -13,26 +13,12 @@
13
13
  </div>
14
14
 
15
15
  <!-- STREAM AUDIO: cerchio con onde animate -->
16
- <div id="streamAudioAlert" *ngIf="!hideTextAreaContent && isStreamAudioActive" class="fade-in-bottom stream-audio-alert" [class.hideTextReply]="hideTextReply" role="status" [attr.aria-label]="translationMap?.get('STREAM_AUDIO_LISTENING') || 'Stream audio attivo'">
17
- <div class="stream-audio-alert__orb" [ngStyle]="{ color: stylesMap?.get('themeColor') }">
18
- <svg class="stream-audio-alert__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
19
- <circle cx="50" cy="50" r="46" fill="currentColor" opacity="0.14"/>
20
- <g class="stream-audio-alert__waves" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
21
- <g class="stream-audio-alert__wave-layer stream-audio-alert__wave-layer--1">
22
- <path [attr.d]="wavePath1"></path>
23
- </g>
24
-
25
- <g class="stream-audio-alert__wave-layer stream-audio-alert__wave-layer--2">
26
- <path [attr.d]="wavePath2"></path>
27
- </g>
28
-
29
- <g class="stream-audio-alert__wave-layer stream-audio-alert__wave-layer--3">
30
- <path [attr.d]="wavePath3"></path>
31
- </g>
32
- </g>
33
- </svg>
34
- </div>
35
- </div>
16
+ <!-- <div id="streamAudioAlert" *ngIf="!hideTextAreaContent && isStreamAudioActive" class="fade-in-bottom stream-audio-alert" [class.hideTextReply]="hideTextReply" role="status" [attr.aria-label]="translationMap?.get('STREAM_AUDIO_LISTENING') || 'Stream audio attivo'">
17
+ <chat-stream-audio-spectrum
18
+ [volume]="currentVolume"
19
+ [accentColor]="isBotSpeaking ? '#b0b0b0' : stylesMap?.get('themeColor')">
20
+ </chat-stream-audio-spectrum>
21
+ </div> -->
36
22
 
37
23
  </div>
38
24
 
@@ -85,7 +71,7 @@
85
71
 
86
72
 
87
73
 
88
- <div *ngIf="!isStopRec" class="visible-text-area" [class.hasError]="showAlertEmoji" [class.disabled] = "( isConversationArchived || hideTextReply)? true : null">
74
+ <div *ngIf="!isStopRec" class="visible-text-area" [class.stream-active]="isStreamAudioActive" [class.hasError]="showAlertEmoji" [class.disabled] = "( isConversationArchived || hideTextReply)? true : null">
89
75
  <!-- isFilePendingToUpload || -->
90
76
  <textarea
91
77
  [attr.disabled] = "(hideTextReply)? true : null"
@@ -117,7 +103,7 @@
117
103
  </div>
118
104
 
119
105
  <!-- ICON REC -->
120
- <div *ngIf="showAudioRecorderFooterButton && !textInputTextArea" tabindex="-1" class="chat21-audio-button" [class.active]="isStopRec" id="chat21-button-rec">
106
+ <div *ngIf="showAudioRecorderFooterButton && !textInputTextArea && !isStreamAudioActive" tabindex="-1" class="chat21-audio-button" [class.active]="isStopRec" id="chat21-button-rec">
121
107
  <chat-audio-recorder
122
108
  (startRecordingEvent)="onStartRecording()"
123
109
  (deleteRecordingEvent)="onDeleteRecording()"
@@ -126,8 +112,24 @@
126
112
  [stylesMap]="stylesMap">
127
113
  </chat-audio-recorder>
128
114
  </div>
115
+
116
+ <!-- ICON STREAM / CHIUDI STREAM (cerchio, icone bianche su iconColor) -->
117
+ <div *ngIf="showAudioStreamFooterButton" tabindex="-1" id="chat21-button-stream"
118
+ class="chat21-textarea-button chat21-stream-button"
119
+ [class.active]="isStreamAudioActive || (!textInputTextArea && !hideTextReply)"
120
+ [class.chat21-stream-button--expanded]="isStreamAudioActive"
121
+ (click)="onStreamPressed($event)" [attr.aria-label]="isStreamAudioActive ? (translationMap?.get('CLOSE') || 'Chiudi stream') : (translationMap?.get('STREAM_AUDIO') || 'Stream audio')"
122
+ [ngStyle]="{ 'background-color': stylesMap?.get('iconColor') || stylesMap?.get('themeColor') }">
123
+ <chat-stream-audio-spectrum
124
+ mode="button"
125
+ [active]="isStreamAudioActive"
126
+ [volume]="currentVolume"
127
+ [translationMap]="translationMap">
128
+ </chat-stream-audio-spectrum>
129
+ </div>
129
130
  </div>
130
131
 
132
+
131
133
  <div class="close-chat-container" *ngIf="closeChatInConversation">
132
134
  <button tabindex="1040" aflistconv #aflistconv class="c21-button-primary c21-close" (click)="onCloseChat($event)" [ngStyle]="{'background-color': stylesMap.get('themeColor'), 'border-color': stylesMap.get('themeColor'), 'color': stylesMap?.get('foregroundColor')}">
133
135
  <span class="v-align-center">
@@ -149,25 +151,6 @@
149
151
  </button>
150
152
  </div>
151
153
 
152
- <!-- ICON STREAM / CHIUDI STREAM (cerchio, icone bianche su iconColor) -->
153
- <div *ngIf="showAudioStreamFooterButton" tabindex="-1" id="chat21-button-stream"
154
- class="chat21-textarea-button chat21-stream-button" [class.active]="isStreamAudioActive || (!textInputTextArea && !hideTextReply)"
155
- (click)="onStreamPressed($event)" [attr.aria-label]="isStreamAudioActive ? (translationMap?.get('CLOSE') || 'Chiudi stream') : (translationMap?.get('STREAM_AUDIO') || 'Stream audio')"
156
- [ngStyle]="{ 'background-color': stylesMap?.get('iconColor') || stylesMap?.get('themeColor') }">
157
- <span class="v-align-center chat21-stream-button__icon" *ngIf="!isStreamAudioActive">
158
- <svg role="img" xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 30 30" fill="#ffffff" aria-hidden="true" preserveAspectRatio="xMidYMid meet">
159
- <path class="s0" d="m5.21 7.41c-1.21 0-2.21 0.99-2.21 2.21v8.14c0 1.21 0.99 2.21 2.21 2.21 1.22 0 2.21-0.99 2.21-2.21v-8.14c0-1.21-0.99-2.21-2.21-2.21z"/>
160
- <path class="s0" d="m11.64 3.01c-1.22 0-2.21 0.99-2.21 2.2v16.94c0 1.21 0.99 2.2 2.21 2.2 1.22 0 2.21-0.98 2.21-2.2v-16.94c0-1.21-0.99-2.21-2.21-2.21z"/>
161
- <path class="s0" d="m15.86 9.25v8.88c0 1.21 0.99 2.21 2.21 2.21 1.22 0 2.21-0.99 2.21-2.21v-8.88c0-1.22-0.99-2.21-2.21-2.21-1.22 0-2.21 0.99-2.21 2.21z"/>
162
- <path class="s0" d="m24.5 8.97c-1.22 0-2.21 0.99-2.21 2.21v5.02c0 1.22 0.99 2.21 2.21 2.21 1.22 0 2.21-0.99 2.21-2.21v-5.02c0-1.21-0.99-2.21-2.21-2.21z"/>
163
- </svg>
164
- </span>
165
- <span class="v-align-center chat21-stream-button__icon" *ngIf="isStreamAudioActive">
166
- <svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#ffffff" aria-hidden="true">
167
- <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
168
- </svg>
169
- </span>
170
- </div>
171
154
  </div>
172
155
 
173
156
 
@@ -9,6 +9,7 @@
9
9
  justify-content: space-between;
10
10
  gap: 8px;
11
11
  }
12
+
12
13
  .close-chat-container{
13
14
  display: flex;
14
15
  flex-direction: column;
@@ -42,6 +43,10 @@
42
43
  &.hasError{
43
44
  box-shadow: 0 0 0 1px var(--chat-footer-border-color-error) inset;
44
45
  }
46
+
47
+ &.stream-active {
48
+ width: 50%;
49
+ }
45
50
  }
46
51
 
47
52
  .chat21-textarea-button {
@@ -93,6 +98,10 @@
93
98
  box-sizing: border-box;
94
99
  flex-shrink: 0;
95
100
  color: #ffffff;
101
+ transition:
102
+ width 180ms ease,
103
+ border-radius 180ms ease,
104
+ padding 180ms ease;
96
105
 
97
106
  .chat21-stream-button__icon svg {
98
107
  width: 20px;
@@ -106,6 +115,22 @@
106
115
  background: rgba(255, 255, 255, 0.2) !important;
107
116
  border-radius: 50%;
108
117
  }
118
+
119
+ &.chat21-stream-button--expanded {
120
+ width: auto;
121
+ // min-width: 150px;
122
+ max-width: 100%;
123
+ border-radius: 999px;
124
+ padding: 0 14px;
125
+ gap: 12px;
126
+ justify-content: center;
127
+
128
+ // stop the circle-hover background from looking odd on pill
129
+ &.chat21-textarea-button span svg:hover {
130
+ background: transparent !important;
131
+ border-radius: 0;
132
+ }
133
+ }
109
134
  }
110
135
 
111
136
  textarea,
@@ -393,67 +418,28 @@ textarea:active{
393
418
  #streamAudioAlert {
394
419
  bottom: 100%;
395
420
  width: 100%;
396
- min-height: 96px;
421
+ min-height: var(--chat-footer-stream-button-height);
397
422
  display: flex;
398
423
  align-items: center;
399
424
  justify-content: center;
400
- background-color: var(--content-background-color);
401
425
  position: absolute;
402
- padding: 10px 0;
426
+ padding: var(--chat-footer-stream-button-padding);
427
+ /* Satinato / vetro: più trasparenza, blur più marcato */
428
+ background-color: color-mix(in srgb, var(--content-background-color) 34%, transparent);
429
+ backdrop-filter: blur(20px) saturate(1.2);
430
+ -webkit-backdrop-filter: blur(20px) saturate(1.2);
431
+ box-shadow:
432
+ inset 0 1px 0 rgba(255, 255, 255, 0.28),
433
+ 0 1px 0 rgba(0, 0, 0, 0.05);
403
434
 
404
435
  &.hideTextReply {
405
436
  position: unset;
406
437
  min-height: auto;
407
438
  padding: 16px 0;
408
439
  box-shadow: none;
409
- }
410
- }
411
-
412
- .stream-audio-alert__orb {
413
- display: flex;
414
- align-items: center;
415
- justify-content: center;
416
- width: 88px;
417
- height: 88px;
418
- border-radius: 50%;
419
- border: 2px solid currentColor;
420
- background: var(--content-background-color);
421
- box-shadow: inset 0 0 24px rgba(0, 0, 0, 0.04);
422
- }
423
-
424
- .stream-audio-alert__svg {
425
- width: 72px;
426
- height: 72px;
427
- display: block;
428
- }
429
-
430
- .stream-audio-alert__wave-layer {
431
- transform-origin: 50px 50px;
432
- transform-box: fill-box;
433
- animation: stream-wave-float 1.35s ease-in-out infinite;
434
- }
435
-
436
- .stream-audio-alert__wave-layer--1 {
437
- animation-delay: 0s;
438
- }
439
-
440
- .stream-audio-alert__wave-layer--2 {
441
- animation-delay: 0.18s;
442
- }
443
-
444
- .stream-audio-alert__wave-layer--3 {
445
- animation-delay: 0.36s;
446
- }
447
-
448
- @keyframes stream-wave-float {
449
- 0%,
450
- 100% {
451
- transform: translateY(0);
452
- opacity: 0.85;
453
- }
454
- 50% {
455
- transform: translateY(-6px);
456
- opacity: 1;
440
+ backdrop-filter: none;
441
+ -webkit-backdrop-filter: none;
442
+ background-color: var(--content-background-color);
457
443
  }
458
444
  }
459
445