@chat21/chat21-web-widget 5.0.84-rc.3 → 5.0.85

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 (33) hide show
  1. package/CHANGELOG.md +1 -8
  2. package/package.json +1 -1
  3. package/src/app/app.module.ts +2 -5
  4. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +3 -14
  5. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +10 -26
  6. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +49 -131
  7. package/src/app/component/message/audio/audio.component.html +20 -0
  8. package/src/app/component/message/audio/audio.component.scss +122 -0
  9. package/src/app/component/message/{audio-track/audio-track.component.spec.ts → audio/audio.component.spec.ts} +6 -6
  10. package/src/app/component/message/audio/audio.component.ts +122 -0
  11. package/src/app/component/message/bubble-message/bubble-message.component.html +15 -31
  12. package/src/app/component/message/bubble-message/bubble-message.component.scss +0 -7
  13. package/src/app/component/message/bubble-message/bubble-message.component.ts +0 -1
  14. package/src/app/utils/globals.ts +5 -5
  15. package/src/assets/i18n/en.json +1 -1
  16. package/src/assets/i18n/es.json +1 -1
  17. package/src/assets/i18n/fr.json +6 -6
  18. package/src/assets/i18n/it.json +1 -1
  19. package/src/assets/twp/blank.html +6 -3
  20. package/src/assets/twp/chatbot-panel.html +13 -5
  21. package/src/assets/twp/index-dev.html +15 -7
  22. package/src/assets/twp/index.html +13 -7
  23. package/src/assets/twp/tiledesk_widget_files/bootstrap.min.css +3 -4
  24. package/src/assets/twp/tiledesk_widget_files/bootstrap.min.js +4 -3
  25. package/src/assets/twp/tiledesk_widget_files/jquery.min.js +2 -2
  26. package/src/iframe-style.css +1 -1
  27. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +0 -29
  28. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.scss +0 -103
  29. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts +0 -23
  30. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +0 -96
  31. package/src/app/component/message/audio-track/audio-track.component.html +0 -32
  32. package/src/app/component/message/audio-track/audio-track.component.scss +0 -107
  33. package/src/app/component/message/audio-track/audio-track.component.ts +0 -147
@@ -1,103 +0,0 @@
1
-
2
- @font-face {
3
- font-family: 'Material Icons';
4
- src: url('https://fonts.googleapis.com/icon?family=Material+Icons');
5
- }
6
-
7
- .material-icons.mic_none::before {
8
- content: 'mic_none';
9
- }
10
-
11
- .audio-recorder {
12
- text-align: center;
13
- margin: 0px;
14
- display: inline-flex;
15
- align-items: center;
16
- justify-content: center;
17
- height: 100%;
18
- width: 100%;
19
- float: left;
20
- gap: 10px
21
- }
22
-
23
- chat-audio-track {
24
- display: flex;
25
- width: 70%;
26
- margin: 8px 0;
27
- pointer-events: auto;
28
- border-radius: var(--chat-footer-border-radius);
29
- background-color: var(--chat-footer-background-color);
30
- }
31
-
32
- button {
33
- margin: 0px;
34
- padding: 0px;
35
- font-size: 16px;
36
- border: none;
37
- background-color: transparent;
38
- color: var(--icon-fill-color);
39
- fill: var(--icon-fill-color);
40
- height: var(--chat-footer-height);
41
- }
42
-
43
- .mic-button {
44
- position: relative;
45
- background-color: transparent;
46
- border: none;
47
- outline: none;
48
- cursor: pointer;
49
- padding: 20px;
50
- border-radius: 50%;
51
- overflow: hidden;
52
- display: inline-flex;
53
- justify-content: center;
54
- align-items: center;
55
- }
56
-
57
- .mic-button svg {
58
- width: 24px;
59
- height: 24px;
60
- fill: var(--icon-fill-color);
61
- }
62
-
63
- .mic-button::before {
64
- content: '';
65
- position: absolute;
66
- top: auto;
67
- left: auto;
68
- width: 35px;
69
- height: 35px;
70
- background-color: var(--icon-fill-color);
71
- border-radius: 50%;
72
- transform: scale(0);
73
- transition: transform 0.3s ease;
74
- transform-origin: center;
75
- z-index: -1;
76
- }
77
-
78
- .mic-button:active::before {
79
- transform: scale(0.5);
80
- animation: pulse 1s infinite ease-in-out;
81
- }
82
-
83
- .mic-button:active {
84
- svg {
85
- color: #fff;
86
- fill: #fff;
87
- }
88
- }
89
-
90
- @keyframes pulse {
91
- 0% {
92
- transform: scale(1);
93
- opacity: 1;
94
- }
95
- 50% {
96
- transform: scale(0.9);
97
- opacity: 0.7;
98
- }
99
- 100% {
100
- transform: scale(1);
101
- opacity: 1;
102
- }
103
- }
@@ -1,23 +0,0 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
-
3
- import { ConversationAudioRecorderComponent } from './conversation-audio-recorder.component';
4
-
5
- describe('AudioRecorderComponent', () => {
6
- let component: ConversationAudioRecorderComponent;
7
- let fixture: ComponentFixture<ConversationAudioRecorderComponent>;
8
-
9
- beforeEach(async () => {
10
- await TestBed.configureTestingModule({
11
- declarations: [ ConversationAudioRecorderComponent ]
12
- })
13
- .compileComponents();
14
-
15
- fixture = TestBed.createComponent(ConversationAudioRecorderComponent);
16
- component = fixture.componentInstance;
17
- fixture.detectChanges();
18
- });
19
-
20
- it('should create', () => {
21
- expect(component).toBeTruthy();
22
- });
23
- });
@@ -1,96 +0,0 @@
1
- import { Component, EventEmitter, Input, Output } from '@angular/core';
2
- import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
3
-
4
- @Component({
5
- selector: 'chat-audio-recorder',
6
- templateUrl: './conversation-audio-recorder.component.html',
7
- styleUrls: ['./conversation-audio-recorder.component.scss']
8
- })
9
- export class ConversationAudioRecorderComponent {
10
-
11
- @Input() stylesMap: Map<string, string>;
12
- @Output() startRecordingEvent = new EventEmitter<void>();
13
- @Output() deleteRecordingEvent = new EventEmitter<void>();
14
- @Output() endRecordingEvent = new EventEmitter<Blob | null>();
15
- @Output() sendRecordingEvent = new EventEmitter<Blob | null>();
16
-
17
- mediaRecorder: MediaRecorder | null = null;
18
- audioChunks: Blob[] = [];
19
- audioBlob: Blob | null = null;
20
- audioUrl: SafeUrl | null = null;
21
-
22
- isRecording = false;
23
- rawAudioUrl: string | null = null;
24
- isPlaying: boolean = false;
25
- startTime: number;
26
-
27
-
28
-
29
- constructor(private sanitizer: DomSanitizer) {}
30
-
31
-
32
- startRecording() {
33
- // console.log('startRecording');
34
- this.startTime = Date.now();
35
- navigator.mediaDevices.getUserMedia({ audio: true })
36
- .then(stream => {
37
- this.mediaRecorder = new MediaRecorder(stream);
38
- this.mediaRecorder.start();
39
- this.isRecording = true;
40
- this.startRecordingEvent.emit();
41
- this.mediaRecorder.addEventListener('dataavailable', (event) => {
42
- this.audioChunks.push(event.data);
43
- });
44
-
45
- this.mediaRecorder.addEventListener('stop', () => {
46
- this.audioBlob = new Blob(this.audioChunks, { type: 'audio/mpeg' });
47
- this.rawAudioUrl = URL.createObjectURL(this.audioBlob);
48
- this.audioUrl = this.sanitizer.bypassSecurityTrustUrl(this.rawAudioUrl);
49
- this.audioChunks = [];
50
- this.endRecordingEvent.emit(this.audioBlob);
51
- });
52
- })
53
- .catch(error => {
54
- console.error('Errore nell’accesso al microfono:', error);
55
- });
56
- }
57
-
58
- stopRecording() {
59
- let endTime = Date.now();
60
- let time = endTime - this.startTime;
61
- if(time > 500){
62
- setTimeout(() => {
63
- if (this.mediaRecorder) {
64
- this.mediaRecorder.stop();
65
- this.isRecording = false;
66
- }
67
- }, 300);
68
-
69
- } else {
70
- this.audioUrl = null;
71
- this.isPlaying = false;
72
- this.rawAudioUrl = null;
73
- this.audioUrl = null;
74
- this.audioBlob = null;
75
- }
76
-
77
-
78
- }
79
-
80
- deleteRecording() {
81
- this.audioUrl = null;
82
- this.isPlaying = false;
83
- this.rawAudioUrl = null;
84
- this.audioUrl = null;
85
- this.audioBlob = null;
86
- this.deleteRecordingEvent.emit(null);
87
- }
88
-
89
- sendMessage(){
90
- if (this.audioUrl) {
91
- this.sendRecordingEvent.emit(this.audioBlob);
92
- this.audioUrl = null;
93
- }
94
- }
95
-
96
- }
@@ -1,32 +0,0 @@
1
-
2
- <!-- <audio *ngIf="metadata" controls>
3
- <source [src]="metadata.src" type="audio/mpeg">
4
- </audio> *ngIf="!metadata"-->
5
- <div class="audio-container">
6
-
7
- <div class="audio-track">
8
- <button *ngIf="!isPlaying" class="play-pause" (click)="playPauseAudio()">
9
- <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px">
10
- <path d="M320-200v-560l440 280-440 280Z"/>
11
- </svg>
12
- <!-- <i class="material-icons">play_arrow</i> -->
13
- </button>
14
- <button *ngIf="isPlaying" class="play-pause" (click)="playPauseAudio()">
15
- <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px">
16
- <path d="M560-200v-560h160v560H560Zm-320 0v-560h160v560H240Z"/>
17
- </svg>
18
- <!-- <i class="material-icons">pause</i> -->
19
- </button>
20
- <div class="duration" [style.color]="color" [style.font-size]="fontSize" >
21
- <span *ngIf="!isPlaying">{{ audioDuration ? formatTime(audioDuration) : '00:00' }}</span>
22
- <span *ngIf="isPlaying">{{ formatTime(currentTime) }}</span>
23
- </div>
24
-
25
- </div>
26
-
27
- <div class="audio-player-custom">
28
- <audio #audioElement [src]="audioUrl"></audio>
29
- <canvas #canvasElement class="waveformCanvas"></canvas>
30
- </div>
31
-
32
- </div>
@@ -1,107 +0,0 @@
1
-
2
- :host {
3
- --backgroundColor: #{var(--blue)};
4
- --textColor: #{var(--bck-msg-sent)};
5
- --hoverBackgroundColor: #{var(--bck-msg-sent)};
6
- --hoverTextColor: #{var(--blue)};
7
- --max-width: #{var(--button-in-msg-max-width)};
8
- }
9
-
10
- .audio-container{
11
- position: relative;
12
- display: inline-flex;
13
- width: 100%;
14
- padding: 0px 12px;
15
- margin: 6px 0px;
16
- }
17
-
18
- audio {
19
- width: 272px;
20
- height: 30px;
21
- margin: 0;
22
- padding: 10px;
23
- }
24
-
25
- .audio-recorder {
26
- text-align: center;
27
- margin: 0px;
28
- display: inline-flex;
29
- align-items: center;
30
- justify-content: center;
31
- height: 100%;
32
- width: 100%;
33
- float: left;
34
- }
35
-
36
- button {
37
- margin: 0px;
38
- padding: 0px;
39
- font-size: 16px;
40
- border: none;
41
- background-color: transparent;
42
- color: var(--icon-fill-color);
43
- fill: var(--icon-fill-color);
44
- }
45
-
46
- .waveformCanvas {
47
- width: 100%;
48
- height: 28px;
49
- z-index: 1;
50
- padding: 0px;
51
- margin: 0%;
52
- }
53
-
54
- .audio-track {
55
- // width: 247px;//272px;
56
- // height: 30px;
57
- position: relative;
58
- display: flex;
59
- align-items: center;
60
- // margin: 0 13px;
61
- margin: 0px;
62
- .play-pause {
63
- font-size: 20px;
64
- width: 30px;
65
- background-color: transparent;
66
- border-radius: 50%;
67
- height: 30px;
68
- margin: 0px;
69
- transition: background-color 0.5s ease;
70
-
71
- }
72
- .play-pause:hover {
73
- // background-color: #ddd;
74
- // background-color: rgb(82, 160, 252);
75
- background-color: #fff;
76
- svg{
77
- //fill:#fff;
78
- fill: rgb(82, 160, 252);
79
- }
80
- }
81
- .duration {
82
- padding: 0 3px;
83
- }
84
- }
85
-
86
- .audio-player-custom {
87
- // width: 200px;
88
- // height: 32px;
89
- // margin-left: 75px;
90
- // position: absolute;
91
- // overflow: hidden;
92
- // z-index: 1;
93
- // display: flex;
94
- // align-items: center;
95
-
96
- // width: calc(100% - 75px);
97
- // height: 32px;
98
- // margin-left: 65px;
99
- // position: absolute;
100
- overflow: hidden;
101
- z-index: 1;
102
- display: flex;
103
- align-items: center;
104
- width: 100%;
105
- }
106
-
107
-
@@ -1,147 +0,0 @@
1
- import { Component, ElementRef, AfterViewInit, Input, ViewChild } from '@angular/core';
2
- import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
3
- import { convertColorToRGBA } from 'src/chat21-core/utils/utils';
4
-
5
- @Component({
6
- selector: 'chat-audio-track',
7
- templateUrl: './audio-track.component.html',
8
- styleUrls: ['./audio-track.component.scss']
9
- })
10
- export class AudioTrackComponent implements AfterViewInit {
11
-
12
- @ViewChild('audioElement', { static: true }) audioElement!: ElementRef<HTMLAudioElement>;
13
- @ViewChild('canvasElement', { static: true }) waveformCanvas!: ElementRef<HTMLCanvasElement>;
14
-
15
- @Input() audioBlob: Blob | null = null;
16
- @Input() metadata: any | null = null;
17
- @Input() color: string;
18
- @Input() fontSize: string;
19
- @Input() stylesMap: Map<string, string>;
20
-
21
- audioUrl: SafeUrl | null = null;
22
- rawAudioUrl: string | null = null;
23
- audioContext!: AudioContext;
24
- audioBuffer!: AudioBuffer;
25
- audioDuration: number | null = null;
26
- currentTime: number = 0;
27
- isPlaying: boolean = false;
28
-
29
- constructor(private sanitizer: DomSanitizer) {}
30
-
31
- ngAfterViewInit() {
32
- console.log('stylesssss', this.stylesMap)
33
- if (this.audioBlob) {
34
- this.rawAudioUrl = URL.createObjectURL(this.audioBlob);
35
- this.audioUrl = this.sanitizer.bypassSecurityTrustUrl(this.rawAudioUrl);
36
- this.setupAudioContext();
37
- } else {
38
- this.rawAudioUrl = this.metadata.src;
39
- this.audioUrl = this.sanitizer.bypassSecurityTrustUrl(this.rawAudioUrl);
40
- this.setupAudioContext();
41
- }
42
- }
43
-
44
- async setupAudioContext() {
45
- this.audioContext = new AudioContext();
46
- if (this.rawAudioUrl) {
47
- const response = await fetch(this.rawAudioUrl);
48
- const audioData = await response.arrayBuffer();
49
- this.audioBuffer = await this.audioContext.decodeAudioData(audioData);
50
- this.getAudioDuration();
51
- this.drawWaveform(this.audioBuffer);
52
- }
53
- }
54
-
55
- drawWaveform(audioBuffer: AudioBuffer) {
56
- const canvas = this.waveformCanvas.nativeElement;
57
- const canvasCtx = canvas.getContext('2d');
58
- if (!canvasCtx) return;
59
- const width = canvas.width;
60
- const height = canvas.height;
61
- const rawData = audioBuffer.getChannelData(0);
62
-
63
- const samples = 60;
64
- const blockSize = Math.floor(rawData.length / samples);
65
- const waveform = new Float32Array(samples);
66
-
67
- for (let i = 0; i < samples; i++) {
68
- let sum = 0;
69
- for (let j = 0; j < blockSize; j++) {
70
- sum += Math.abs(rawData[i * blockSize + j]);
71
- }
72
- waveform[i] = sum / blockSize;
73
- }
74
-
75
- canvasCtx.clearRect(0, 0, width, height);
76
- const padding = 1;
77
- const barWidth = (width / samples) - padding * 2;
78
- const audio = this.audioElement.nativeElement;
79
- const playedPercent = audio.currentTime / this.audioDuration;
80
- // console.log('playedPercent: ', audio.currentTime, this.audioDuration);
81
-
82
- for (let i = 0; i < samples; i++) {
83
- var barHeight = waveform[i] * height * 4;
84
- if (barHeight < 4) barHeight = 4;
85
- const x = i * (barWidth + padding * 2) + padding;
86
-
87
- if (i / samples < playedPercent) {
88
- canvasCtx.fillStyle = this.color;
89
- } else {
90
- canvasCtx.fillStyle = convertColorToRGBA(this.color, 50);;
91
- }
92
- canvasCtx.fillRect(x, height / 2 - barHeight, barWidth, barHeight);
93
- canvasCtx.fillRect(x, height / 2, barWidth, barHeight);
94
- }
95
- }
96
-
97
- playPauseAudio() {
98
- const audio = this.audioElement.nativeElement;
99
- if (audio.paused) {
100
- this.isPlaying = true;
101
- this.updateWaveform();
102
- audio.play();
103
- this.audioContext.resume();
104
- } else {
105
- audio.pause();
106
- this.isPlaying = false;
107
- }
108
- audio.ontimeupdate = () => {
109
- this.currentTime = audio.currentTime;
110
- this.updateWaveform();
111
- };
112
- audio.onended = () => {
113
- this.isPlaying = false;
114
- };
115
- }
116
-
117
-
118
- updateWaveform() {
119
- this.drawWaveform(this.audioBuffer);
120
- if (this.isPlaying) {
121
- requestAnimationFrame(() => this.updateWaveform());
122
- }
123
- }
124
-
125
- formatTime(seconds: number): string {
126
- const minutes = Math.floor(seconds / 60);
127
- const sec = Math.floor(seconds % 60);
128
- return `${minutes}:${sec < 10 ? '0' + sec : sec}`;
129
- }
130
-
131
- getAudioDuration() {
132
- const audio = new Audio();
133
- audio.src = this.rawAudioUrl!;
134
- audio.addEventListener('loadedmetadata', () => {
135
- if (audio.duration === Infinity) {
136
- audio.currentTime = Number.MAX_SAFE_INTEGER;
137
- audio.ontimeupdate = () => {
138
- audio.ontimeupdate = null;
139
- audio.currentTime = 0;
140
- this.audioDuration = audio.duration;
141
- };
142
- } else {
143
- this.audioDuration = audio.duration;
144
- }
145
- });
146
- }
147
- }