@chat21/chat21-ionic 3.4.12 → 3.4.13-rc1

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
@@ -1,5 +1,8 @@
1
1
  # chat21-ionic ver 3.0
2
2
 
3
+ ### 3.4.13-rc.1
4
+ - added: copilot suggestions for agent responses
5
+
3
6
  ### 3.4.12 in PROD
4
7
 
5
8
  ### 3.4.12-rc.2
package/Dockerfile CHANGED
@@ -19,7 +19,7 @@ RUN ionic cordova build browser
19
19
 
20
20
  ### STAGE 2: Setup ###
21
21
 
22
- FROM nginx:1.14.1-alpine
22
+ FROM nginx:1.14.1-alpine as setup
23
23
 
24
24
  ## Copy our default nginx config
25
25
  COPY nginx.conf /etc/nginx/nginx.conf
package/config.xml CHANGED
@@ -106,7 +106,6 @@
106
106
  <platform name="browser">
107
107
  <preference name="ShowSplashScreen" value="false" />
108
108
  </platform>
109
- <engine name="browser" spec="^5.0.4" />
110
109
  <plugin name="cordova-android-support-gradle-release" spec="^3.0.1">
111
110
  <variable name="ANDROID_SUPPORT_VERSION" value="27.+" />
112
111
  </plugin>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chat21/chat21-ionic",
3
3
  "author": "Tiledesk SRL",
4
- "version": "3.4.12",
4
+ "version": "3.4.13-rc1",
5
5
  "license": "MIT License",
6
6
  "homepage": "https://tiledesk.com/",
7
7
  "repository": {
@@ -48,7 +48,6 @@
48
48
  "autolinker": "^3.14.2",
49
49
  "cordova": "^11.1.0",
50
50
  "cordova-android-support-gradle-release": "^3.0.1",
51
- "cordova-browser": "^5.0.4",
52
51
  "cordova-plugin-chooser": "^1.3.2",
53
52
  "cordova-plugin-ionic-keyboard": "^2.2.0",
54
53
  "cordova-plugin-ionic-webview": "^4.2.1",
@@ -86,6 +85,7 @@
86
85
  "@types/node": "^12.20.55",
87
86
  "codelyzer": "^6.0.0",
88
87
  "cordova-android": "^10.1.2",
88
+ "cordova-browser": "^6.0.0",
89
89
  "cordova-ios": "^6.3.0",
90
90
  "cordova-plugin-add-swift-support": "^2.0.2",
91
91
  "cordova-plugin-fcm-with-dependecy-updated": "^7.8.0",
@@ -147,9 +147,9 @@
147
147
  }
148
148
  },
149
149
  "platforms": [
150
- "browser",
151
150
  "android",
152
- "ios"
151
+ "ios",
152
+ "browser"
153
153
  ]
154
154
  }
155
155
  }
@@ -1,29 +1,41 @@
1
1
  <ion-grid>
2
2
  <div class="footer-options-container">
3
- <!-- CHAT-OPTION -->
4
- <div class="section-option" id="chat" tooltip="{{translationMap?.get('LABEL_CHAT')}}" placement="top">
5
- <ion-button fill="clear" (click)="onOpenSection('chat')" [class.active]="section==='chat'">
6
- <ion-icon name="chatbubbles"></ion-icon>
7
- {{translationMap.get('LABEL_CHAT')}}
8
- </ion-button>
9
- </div>
10
- <!-- EMAIL-OPTION -->
11
- <div *ngIf="emailSection" class="section-option" id="email" tooltip="{{translationMap?.get('EMAIL.LABEL_TOOLTIP')}}" placement="top">
12
- <ion-button fill="clear" [class.active]="section==='email'" (click)="onOpenSection('email')" [disabled]="channelType === 'direct' || channel == CHANNEL_TYPE.WHATSAPP || channel == CHANNEL_TYPE.MESSENGER">
13
- <ion-icon name="mail-open"></ion-icon>
14
- {{translationMap.get('LABEL_EMAIL')}}
15
- </ion-button>
3
+ <div>
4
+ <!-- CHAT-OPTION -->
5
+ <div class="section-option" id="chat" tooltip="{{translationMap?.get('LABEL_CHAT')}}" placement="top">
6
+ <ion-button fill="clear" (click)="onOpenSection('chat')" [class.active]="section==='chat'">
7
+ <ion-icon name="chatbubbles"></ion-icon>
8
+ {{translationMap.get('LABEL_CHAT')}}
9
+ </ion-button>
10
+ </div>
11
+ <!-- EMAIL-OPTION -->
12
+ <div *ngIf="emailSection" class="section-option" id="email" tooltip="{{translationMap?.get('EMAIL.LABEL_TOOLTIP')}}" placement="top">
13
+ <ion-button fill="clear" [class.active]="section==='email'" (click)="onOpenSection('email')" [disabled]="channelType === 'direct' || channel == CHANNEL_TYPE.WHATSAPP || channel == CHANNEL_TYPE.MESSENGER">
14
+ <ion-icon name="mail-open"></ion-icon>
15
+ {{translationMap.get('LABEL_EMAIL')}}
16
+ </ion-button>
17
+ </div>
18
+ <!-- WHATSAPP TEMPLATE -->
19
+ <div *ngIf="whatsappTemplatesSection" class="section-option" id="template" tooltip="{{translationMap?.get('WHATSAPP.LABEL_TOOLTIP')}}" placement="top">
20
+ <ion-button fill="clear" [class.active]="section==='templates'" (click)="onOpenSection('templates');onOpenTemplateModal()" [disabled]="channel !== CHANNEL_TYPE.WHATSAPP">
21
+ <ion-icon name="logo-whatsapp"></ion-icon>
22
+ {{translationMap?.get('WHATSAPP.LABEL_TEMPLATES')}}
23
+ </ion-button>
24
+ </div>
25
+
26
+ <div *ngIf="offlineMsgEmail && section==='chat' && channel !== CHANNEL_TYPE.WHATSAPP && messageString && (leadInfo?.presence['status']==='offline' && leadInfo?.hasEmail)" class="section-option offline-lead-tip" >
27
+ {{translationMap.get('EMAIL.EMAIL_OFFLINE_TIP')}}
28
+ </div>
16
29
  </div>
17
30
 
18
- <div *ngIf="whatsappTemplatesSection" class="section-option" id="template" tooltip="{{translationMap?.get('WHATSAPP.LABEL_TOOLTIP')}}" placement="top">
19
- <ion-button fill="clear" [class.active]="section==='templates'" (click)="onOpenSection('templates');onOpenTemplateModal()" [disabled]="channel !== CHANNEL_TYPE.WHATSAPP">
20
- <ion-icon name="logo-whatsapp"></ion-icon>
21
- {{translationMap?.get('WHATSAPP.LABEL_TEMPLATES')}}
22
- </ion-button>
23
- </div>
24
-
25
- <div *ngIf="offlineMsgEmail && section==='chat' && channel !== CHANNEL_TYPE.WHATSAPP && messageString && (leadInfo?.presence['status']==='offline' && leadInfo?.hasEmail)" class="section-option offline-lead-tip" >
26
- {{translationMap.get('EMAIL.EMAIL_OFFLINE_TIP')}}
31
+ <div>
32
+ <!-- COPILOT-OPTION -->
33
+ <div class="section-option" id="copilot" tooltip="{{translationMap?.get('COPILOT.ASK_AI')}}" placement="top">
34
+ <ion-button fill="clear" [class.active]="section ==='copilot'" (click)="onOpenSection('copilot')">
35
+ <ion-icon class="channel-icon" src="assets/images/icons/copilot.svg"></ion-icon>
36
+ {{translationMap?.get('COPILOT.ASK_AI')}}
37
+ </ion-button>
38
+ </div>
27
39
  </div>
28
40
 
29
41
  </div>
@@ -35,7 +47,7 @@
35
47
  </ion-col>
36
48
  </ion-row>
37
49
 
38
- <ion-row id="message-text-area" [style.display]="section==='chat' || section ==='templates'? 'flex': 'none'">
50
+ <ion-row id="message-text-area" [style.display]="section==='chat' || section ==='templates' || section ==='copilot'? 'flex': 'none'">
39
51
 
40
52
  <ion-col col-auto style="display: flex;">
41
53
 
@@ -5,6 +5,12 @@
5
5
  padding: 0px 16px 0px;
6
6
  border-bottom: 1px solid var(--border-color-base);
7
7
  border-top: 3px solid var(--basic-blue);
8
+ justify-content: space-between;
9
+
10
+ & > div{
11
+ display: flex;
12
+ align-items: center;
13
+ }
8
14
 
9
15
  .section-option{
10
16
  line-height: 16px;
@@ -26,6 +32,7 @@
26
32
  height: 25px;
27
33
  &.active{
28
34
  color: var(--basic-blue);
35
+ fill: var(--basic-blue);
29
36
  }
30
37
  ion-icon{
31
38
  margin-right: 4px;
@@ -35,7 +42,7 @@
35
42
  }
36
43
 
37
44
  .offline-lead-tip{
38
- font-size: 12px;
45
+ font-size: 9px;
39
46
  width: 100%;
40
47
  display: block;
41
48
  text-align: right;
@@ -22,6 +22,8 @@ import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance'
22
22
  import { EventsService } from 'src/app/services/events-service';
23
23
  import { isOnMobileDevice } from 'src/chat21-core/utils/utils';
24
24
  import { checkAcceptedFile } from 'src/chat21-core/utils/utils';
25
+ import { CopilotService } from 'src/app/services/copilot/copilot.service';
26
+ import { BRAND_BASE_INFO } from 'src/app/utils/utils-resources';
25
27
 
26
28
 
27
29
  @Component({
@@ -106,6 +108,9 @@ export class MessageTextAreaComponent implements OnInit, AfterViewInit, OnChange
106
108
  imageUrl: 'https://tiledesk.com/wp-content/uploads/2022/11/FAQ-Chatbot.png',
107
109
  }
108
110
  ];
111
+
112
+ BRAND_BASE_INFO = BRAND_BASE_INFO
113
+
109
114
  /**
110
115
  * Constructor
111
116
  * @param chooser
@@ -118,7 +123,6 @@ export class MessageTextAreaComponent implements OnInit, AfterViewInit, OnChange
118
123
  public modalController: ModalController,
119
124
  public uploadService: UploadService,
120
125
  public toastController: ToastController,
121
- private renderer: Renderer2,
122
126
  public eventsService: EventsService
123
127
  ) { }
124
128
 
@@ -0,0 +1,36 @@
1
+ <div>
2
+ <div class="canned-list" *ngIf="suggestions.length > 0">
3
+ <ion-item button="true" [ngClass]="{'is_active_item': i == arrowkeyLocation}" lines="none"
4
+ class="canned-item no-ripple border" id="{{'canned-item_'+ i }}"
5
+ *ngFor="let suggestion of suggestions; let i = index;"
6
+ (click)="onClickSuggestionFN(suggestion, $event)">
7
+ <div class="cannedContent">
8
+ <!-- <ion-input [class.readonly]="suggestion?.disabled" [readonly]="suggestion?.disabled" type="text" [(ngModel)]="suggestion.title" class="title" id="{{'titleCanned_'+suggestion._id}}" #title></ion-input> -->
9
+ <!-- <ion-input [class.readonly]="suggestion?.disabled" [readonly]="suggestion?.disabled" type="text" [(ngModel)]="suggestion.text" class="text"></ion-input> -->
10
+ <ion-label [class.readonly]="suggestion?.disabled" class="title" id="{{'titleCanned_'+suggestion._id}}" #title >{{suggestion?.title}}</ion-label>
11
+ <ion-label [class.readonly]="suggestion?.disabled" class="text">{{suggestion?.text}}</ion-label>
12
+ </div>
13
+ </ion-item>
14
+ <!-- <ion-item class="canned-item add-canned-response-wpr" button="true" lines="none" (click)="onClickAddCannedResponseFN()">
15
+ <ion-icon class="add-canned-response-icon" name="flash-outline"></ion-icon>
16
+ <span class="add-canned-response-add-icon">+</span>
17
+ <label class="add-canned-response-label" >{{translationMap?.get('AddNewCannedResponse')}}</label>
18
+ </ion-item> -->
19
+ </div>
20
+ <!-- LOADER -->
21
+ <div class="loader" *ngIf="suggestions.length === 0">
22
+ <div class="box">
23
+ <!-- <div class="container">
24
+ <span class="circle" [ngStyle]="{'background-color': stylesMap?.get('themeColor')}"></span>
25
+ <span class="circle" [ngStyle]="{'background-color': stylesMap?.get('themeColor')}"></span>
26
+ <span class="circle" [ngStyle]="{'background-color': stylesMap?.get('themeColor')}"></span>
27
+ <span class="circle" [ngStyle]="{'background-color': stylesMap?.get('themeColor')}"></span>
28
+ </div> -->
29
+ <div class="spinner" [ngStyle]="{'border-top-color': stylesMap?.get('themeColor')}"></div>
30
+ <div class="label">{{translationMap.get('LABEL_LOADING')}}</div>
31
+ </div>
32
+ </div>
33
+ <!-- <div *ngIf="!canShowCanned">
34
+
35
+ </div> -->
36
+ </div>
@@ -0,0 +1,205 @@
1
+ .canned-list {
2
+ // position: absolute;
3
+ // bottom: 0;
4
+ background-color: white !important;
5
+ width: 100%;
6
+ max-height: 310px;
7
+ overflow-y: auto;
8
+ // padding: 10px 0;
9
+ // margin: 0;
10
+ margin-bottom: 1px;
11
+ font-size: 14px;
12
+ line-height: 1.42857143;
13
+ color: #080f1a;
14
+ box-sizing: border-box;
15
+ -webkit-font-smoothing: antialiased;
16
+ // list-style: none;
17
+ z-index: 999999;
18
+
19
+ &::-webkit-scrollbar {
20
+ width: 6px;
21
+ height: 8px;
22
+ }
23
+
24
+ &::-webkit-scrollbar-track {
25
+ background: #f9f9f9;
26
+ }
27
+
28
+ &::-webkit-scrollbar-thumb {
29
+ background-color: #b9b9b9;
30
+ border-radius: 0px;
31
+ }
32
+
33
+ &::-webkit-scrollbar-thumb:hover {
34
+ background-color: #727272;
35
+ }
36
+
37
+ .cannedContent{
38
+ width: 100%;
39
+ }
40
+
41
+
42
+ ion-label{
43
+ white-space: pre-wrap;
44
+ word-wrap: break-word;
45
+
46
+ --padding-bottom: 0px;
47
+ --padding-top: 0px;
48
+ &.text{
49
+ font-style: italic;
50
+ margin-top: 2px;
51
+ }
52
+ &.title {
53
+ font-weight: 500;
54
+ }
55
+
56
+ &.readonly{
57
+ cursor: pointer;
58
+ pointer-events: none;
59
+ }
60
+ }
61
+ // .native-input[disabled] {
62
+ // opacity: 10 !important;
63
+ // cursor: pointer;
64
+ // }
65
+ // .native-textarea[disabled] {
66
+ // opacity: 10 !important;
67
+ // cursor: pointer;
68
+ // }
69
+ ion-icon {
70
+ zoom: 0.7;
71
+ color: var(--icon-color);
72
+
73
+ &:hover{
74
+ opacity: 0.8;
75
+ }
76
+ }
77
+ .no-ripple {
78
+ --ripple-color: transparent;
79
+ --background-activated: transparent;
80
+ --background: transparent;
81
+ }
82
+
83
+ .border{
84
+ border-bottom: 1px dashed rgb(211, 219, 229) !important;
85
+ margin: 0px 4px
86
+ }
87
+
88
+
89
+ }
90
+
91
+ .canned-item {
92
+ -webkit-tap-highlight-color: transparent;
93
+ font-family: Lato, sans-serif;
94
+ font-size: 14px;
95
+ line-height: 1.42857143;
96
+ list-style: none;
97
+ box-sizing: border-box;
98
+ -webkit-font-smoothing: antialiased;
99
+ // margin: 0 10px;
100
+ position: relative;
101
+ outline: none;
102
+ color: #434a54;
103
+ // padding: 10px;
104
+ padding: 5px;
105
+ width: auto;
106
+ cursor: pointer;
107
+ // .item-inner{
108
+ // border: none!important;
109
+ // }
110
+ }
111
+
112
+ ion-item {
113
+ --background-hover: var(--canned-hover-background) !important;
114
+ --color-hover: var(--canned-hover-color);
115
+ --background: transparent;
116
+ .nocannedTitle {
117
+ color: #f44336;
118
+ }
119
+ .no-canned-available-text {
120
+ color: #1877f2 !important;
121
+ }
122
+ .no-canned-available-text:hover {
123
+ text-decoration: underline;
124
+ }
125
+ }
126
+
127
+ .loader {
128
+ height: 310px;
129
+
130
+ .box{
131
+ top: 50%;
132
+ left: 50%;
133
+ transform: translate(-50%, -50%);
134
+ position: absolute;
135
+ }
136
+
137
+ .container{
138
+ height: 15px;
139
+ width: 105px;
140
+ display: flex;
141
+ position: relative;
142
+
143
+ .circle{
144
+ width: 15px;
145
+ height: 15px;
146
+ border-radius: 50%;
147
+ background-color: var(--basic-blue);
148
+ animation: move 500ms linear 0ms infinite;
149
+ margin-right: 30px;
150
+
151
+ &:first-child{
152
+ position: absolute;
153
+ top:0;
154
+ left:0;
155
+ animation: grow 500ms linear 0ms infinite;
156
+ }
157
+
158
+ &:last-child{
159
+ position: absolute;
160
+ top: 0;
161
+ right: 0;
162
+ margin-right: 0;
163
+ animation: grow 500ms linear 0s infinite reverse;
164
+ }
165
+ }
166
+
167
+ }
168
+
169
+ .spinner {
170
+ margin: auto;
171
+ border: 8px solid #EAF0F6;
172
+ border-radius: 50%;
173
+ border-top: 8px solid var(--basic-blue);
174
+ width: 50px;
175
+ height: 50px;
176
+ animation: spinner 1s linear infinite;
177
+ }
178
+
179
+ @keyframes spinner {
180
+ 0% { transform: rotate(0deg); }
181
+ 100% { transform: rotate(360deg); }
182
+ }
183
+
184
+ .label{
185
+ text-align: center;
186
+ margin-top: 10px;
187
+ animation: blinker 1s linear infinite;
188
+ }
189
+
190
+ @keyframes grow {
191
+ from {transform: scale(0,0); opacity: 0;}
192
+ to {transform: scale(1,1); opacity: 1;}
193
+ }
194
+
195
+ @keyframes move {
196
+ from {transform: translateX(0px)}
197
+ to {transform: translateX(45px)}
198
+ }
199
+
200
+ @keyframes blinker {
201
+ 50% {
202
+ opacity: 0;
203
+ }
204
+ }
205
+ }
@@ -0,0 +1,24 @@
1
+ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
+ import { IonicModule } from '@ionic/angular';
3
+
4
+ import { CopilotSuggestionsComponent } from './copilot-suggestions.component';
5
+
6
+ describe('CopilotSuggestionsComponent', () => {
7
+ let component: CopilotSuggestionsComponent;
8
+ let fixture: ComponentFixture<CopilotSuggestionsComponent>;
9
+
10
+ beforeEach(waitForAsync(() => {
11
+ TestBed.configureTestingModule({
12
+ declarations: [ CopilotSuggestionsComponent ],
13
+ imports: [IonicModule.forRoot()]
14
+ }).compileComponents();
15
+
16
+ fixture = TestBed.createComponent(CopilotSuggestionsComponent);
17
+ component = fixture.componentInstance;
18
+ fixture.detectChanges();
19
+ }));
20
+
21
+ it('should create', () => {
22
+ expect(component).toBeTruthy();
23
+ });
24
+ });
@@ -0,0 +1,163 @@
1
+ import { filter } from 'rxjs/operators';
2
+ import { Component, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, SimpleChange } from '@angular/core';
3
+ import { CopilotService } from 'src/app/services/copilot/copilot.service';
4
+ import { TiledeskService } from 'src/app/services/tiledesk/tiledesk.service';
5
+ import { UserModel } from 'src/chat21-core/models/user';
6
+ import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service';
7
+ import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
8
+ import { TiledeskAuthService } from 'src/chat21-core/providers/tiledesk/tiledesk-auth.service';
9
+ import { getProjectIdSelectedConversation } from 'src/chat21-core/utils/utils';
10
+
11
+ @Component({
12
+ selector: 'copilot-suggestions',
13
+ templateUrl: './copilot-suggestions.component.html',
14
+ styleUrls: ['./copilot-suggestions.component.scss'],
15
+ })
16
+ export class CopilotSuggestionsComponent implements OnInit {
17
+
18
+ // @Input() tagsCannedFilter: any = []
19
+ @Input() conversationWith: string;
20
+ @Input() conversationWithFullname: string;
21
+ @Input() currentString: string;
22
+ @Input() stylesMap: Map<string, string>;
23
+ @Input() translationMap: Map<string, string>;
24
+ @Output() onLoadedSuggestions = new EventEmitter<[any]>();
25
+ @Output() onClickSuggestion = new EventEmitter<any>();
26
+
27
+ public loggedUser: UserModel
28
+ public projectID: string;
29
+
30
+ public suggestions: any = []
31
+ public suggestionsCount: number
32
+
33
+ public arrowkeyLocation = -1
34
+
35
+
36
+ private logger: LoggerService = LoggerInstance.getInstance();
37
+ constructor(
38
+ public tiledeskAuthService: TiledeskAuthService,
39
+ public tiledeskService: TiledeskService,
40
+ public copilotService: CopilotService,
41
+ public el: ElementRef
42
+ ) { }
43
+
44
+ ngOnInit() {
45
+ this.loggedUser = this.tiledeskAuthService.getCurrentUser()
46
+ }
47
+
48
+ ngOnChanges(changes: SimpleChange){
49
+ this.logger.debug('[COPILOT] - loadTagsCanned strSearch ', this.currentString)
50
+ if(changes && changes['conversationWith'] && (changes['conversationWith'].previousValue !== changes['conversationWith'].currentValue)){
51
+ this.projectID = getProjectIdSelectedConversation(this.conversationWith)
52
+ }
53
+ this.loadSuggestions(this.conversationWith)
54
+ }
55
+
56
+
57
+ // ----------------------------------------------------------
58
+ // @ CANNED RESPONSES methods
59
+ // ----------------------------------------------------------
60
+ loadSuggestions(conversationWith) {
61
+ this.logger.log('[COPILOT] - loadTagsCanned conversationWith ', conversationWith)
62
+
63
+ if (this.projectID) {
64
+ this.logger.log('[COPILOT] - loadTagsCanned projectId ', this.projectID)
65
+ this.getAndShowSuggestions(this.projectID)
66
+ } else {
67
+ this.getProjectIdByConversationWith(this.conversationWith)
68
+ }
69
+ }
70
+
71
+ getProjectIdByConversationWith(conversationWith: string) {
72
+ const tiledeskToken = this.tiledeskAuthService.getTiledeskToken()
73
+
74
+ this.tiledeskService.getProjectIdByConvRecipient(tiledeskToken, conversationWith).subscribe((res) => {
75
+ this.logger.log('[COPILOT] - loadTagsCanned - GET PROJECTID BY CONV RECIPIENT RES', res)
76
+ if (res) {
77
+ const projectId = res.id_project
78
+ this.logger.log('[COPILOT] - loadTagsCanned - GET PROJECTID BY CONV RECIPIENT projectId ', projectId)
79
+ if (projectId) {
80
+ this.getAndShowSuggestions(projectId)
81
+ }
82
+ }
83
+ }, (error) => {
84
+ this.logger.error('[COPILOT] - loadTagsCanned - GET PROJECTID BY CONV RECIPIENT - ERROR ', error)
85
+ }, () => {
86
+ this.logger.log('[COPILOT] - loadTagsCanned - GET PROJECTID BY CONV RECIPIENT * COMPLETE *')
87
+ })
88
+ }
89
+
90
+
91
+ getAndShowSuggestions(projectId) {
92
+ const tiledeskToken = this.tiledeskAuthService.getTiledeskToken()
93
+ this.logger.log('[COPILOT] - loadTagsCanned tagsCanned.length', this.suggestions.length)
94
+ //if(this.tagsCanned.length <= 0 ){
95
+ this.suggestions = []
96
+ this.copilotService.getAll(tiledeskToken, projectId, this.conversationWith).subscribe((res) => {
97
+ this.logger.log('[COPILOT] - loadTagsCanned getCannedResponses RES', res)
98
+ this.suggestions = res.map(el => ({ ...el, disabled : true }))
99
+ this.suggestionsCount = res.length
100
+ this.logger.log('[COPILOT] - loadTagsCanned getCannedResponses tagsCannedCount', this.suggestions)
101
+ }, (error) => {
102
+ this.logger.error('[COPILOT] - loadTagsCanned getCannedResponses - ERROR ', error)
103
+ }, () => {
104
+ this.logger.log('[COPILOT] - loadTagsCanned getCannedResponses * COMPLETE *')
105
+ this.onLoadedSuggestions.emit(this.suggestions)
106
+ })
107
+ }
108
+
109
+
110
+ onClickSuggestionFN(suggestion, event){
111
+ if(!suggestion.disabled){
112
+ event.preventDefault();
113
+ event.stopPropagation();
114
+ } else if(this.suggestionsCount > 0){
115
+ this.onClickSuggestion.emit(suggestion)
116
+ } else {
117
+ this.logger.log('[CANNED] THERE IS NOT CANNED ', suggestion.text)
118
+ }
119
+ }
120
+
121
+ @HostListener('document:keydown', ['$event'])
122
+ handleKeyboardEvent(event: KeyboardEvent) {
123
+ // this.logger.log("CONVERSATION-DETAIL handleKeyboardEvent event.key ", event);
124
+
125
+ if (this.suggestions.length > 0) {
126
+ if (event.key === 'ArrowDown') {
127
+ this.arrowkeyLocation++
128
+ if (this.arrowkeyLocation === this.suggestions.length) {
129
+ this.arrowkeyLocation--
130
+ }
131
+ // this.replaceTagInMessage(this.tagsCannedFilter[this.arrowkeyLocation])
132
+ } else if (event.key === 'ArrowUp') {
133
+ if (this.arrowkeyLocation > 0) {
134
+ this.arrowkeyLocation--
135
+ } else if (this.arrowkeyLocation < 0) {
136
+ this.arrowkeyLocation++
137
+ }
138
+ // this.replaceTagInMessage(this.tagsCannedFilter[this.arrowkeyLocation])
139
+ }
140
+
141
+ // set the focus on current canned
142
+ setTimeout(() => {
143
+ this.el.nativeElement.querySelector('.canned-list').scrollTop = this.arrowkeyLocation * 59 // 59px is the height of the single element
144
+ // this.el.nativeElement.querySelector('#canned-item_'+this.arrowkeyLocation).scrollIntoView({ behavior: 'smooth' })
145
+ }, 0);
146
+
147
+ if (event.key === 'Enter') {
148
+ const canned_selected = this.suggestions[this.arrowkeyLocation]
149
+ this.logger.log('[CONVS-DETAIL] replaceTagInMessage canned_selected ',canned_selected)
150
+ if (canned_selected) {
151
+ this.arrowkeyLocation = -1
152
+ this.onClickSuggestion.emit(canned_selected)
153
+ // event.preventDefault();
154
+ // return false;
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+
161
+
162
+
163
+ }
@@ -43,6 +43,7 @@ import { ReturnReceiptComponent } from 'src/app/chatlib/conversation-detail/mess
43
43
  import { OptionsComponent } from 'src/app/chatlib/conversation-detail/message/options/options.component';
44
44
  import { UserTypingComponent } from 'src/chat21-core/utils/user-typing/user-typing.component';
45
45
  import { AvatarProfileComponent } from 'src/app/components/utils/avatar-profile/avatar-profile.component';
46
+ import { CopilotSuggestionsComponent } from 'src/app/components/copilot-suggestions/copilot-suggestions.component';
46
47
 
47
48
 
48
49
  @NgModule({
@@ -93,6 +94,7 @@ import { AvatarProfileComponent } from 'src/app/components/utils/avatar-profile/
93
94
  // --------- footer --------- //
94
95
  MessageTextAreaComponent,
95
96
  CannedResponseComponent,
97
+ CopilotSuggestionsComponent,
96
98
 
97
99
 
98
100
  TruncatePipe
@@ -61,7 +61,7 @@
61
61
  here is ignored
62
62
  </span>
63
63
 
64
- <div class="overlay" *ngIf="!HIDE_CANNED_RESPONSES"></div>
64
+ <div class="overlay" *ngIf="!HIDE_CANNED_RESPONSES || SHOW_COPILOT_SUGGESTIONS"></div>
65
65
 
66
66
  <ng-template #content_welcome>
67
67
  <!-- <div class="messageFirst">
@@ -180,6 +180,17 @@
180
180
  (onClickCanned)="replaceTagInMessage($event)"
181
181
  (onClickAddCannedResponse)="presentCreateCannedResponseModal()">
182
182
  </app-canned-response>
183
+
184
+ <copilot-suggestions *ngIf="SHOW_COPILOT_SUGGESTIONS"
185
+ id="copilot"
186
+ [conversationWith]="conversationWith"
187
+ [conversationWithFullname]="conversationWithFullname"
188
+ [currentString]="messageStr"
189
+ [stylesMap]="styleMap"
190
+ [translationMap]="translationsMap"
191
+ (onLoadedSuggestions)="onLoadedSuggestions($event)"
192
+ (onClickSuggestion)="replaceSuggestionInMessage($event)">
193
+ </copilot-suggestions>
183
194
 
184
195
 
185
196
  <!-- <div id="canned" *ngIf="tagsCannedFilter.length > 0 && HIDE_CANNED_RESPONSES === false">
@@ -152,7 +152,8 @@ ion-content {
152
152
  }
153
153
  }
154
154
 
155
- #canned {
155
+ #canned,
156
+ #copilot {
156
157
  z-index: 99999;
157
158
  width: 100%;
158
159
  // background: rgba(237,240,244,.9);
@@ -6,24 +6,15 @@ import {
6
6
  AfterViewInit,
7
7
  ViewChild,
8
8
  ElementRef,
9
- Directive,
10
9
  HostListener,
11
- ChangeDetectorRef,
12
- Renderer2,
13
- isDevMode
14
10
  } from '@angular/core'
15
11
  import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'
16
12
  import {
17
13
  ModalController,
18
14
  ToastController,
19
- PopoverController,
20
15
  Platform,
21
16
  ActionSheetController,
22
- NavController,
23
17
  IonContent,
24
- IonTextarea,
25
- IonButton,
26
- IonInput,
27
18
  } from '@ionic/angular'
28
19
 
29
20
  // models
@@ -56,7 +47,6 @@ import {
56
47
  MESSAGE_TYPE_INFO,
57
48
  MESSAGE_TYPE_MINE,
58
49
  MESSAGE_TYPE_OTHERS,
59
- URL_SOUND_LIST_CONVERSATION,
60
50
  TYPE_DIRECT,
61
51
  TYPE_MSG_EMAIL,
62
52
  TYPE_MSG_FORM,
@@ -88,12 +78,9 @@ import { takeUntil } from 'rxjs/operators'
88
78
  import { TiledeskService } from '../../services/tiledesk/tiledesk.service'
89
79
  import { NetworkService } from '../../services/network-service/network.service'
90
80
  import { EventsService } from '../../services/events-service'
91
- import { ScrollbarThemeDirective } from 'src/app/utils/scrollbar-theme.directive'
92
81
  import { WebsocketService } from 'src/app/services/websocket/websocket.service';
93
82
  import { Project } from 'src/chat21-core/models/projects';
94
- import { AppStorageService } from 'src/chat21-core/providers/abstract/app-storage.service';
95
83
  import { Globals } from 'src/app/utils/globals';
96
- import { TriggerEvents } from 'src/app/services/triggerEvents/triggerEvents';
97
84
 
98
85
  @Component({
99
86
  selector: 'app-conversation-detail',
@@ -149,6 +136,8 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
149
136
  public HIDE_CANNED_RESPONSES: boolean = true
150
137
  public canShowCanned: boolean = true
151
138
 
139
+ public SHOW_COPILOT_SUGGESTIONS: boolean = false;
140
+
152
141
  public window: any = window
153
142
  public styleMap: Map<string, string> = new Map()
154
143
 
@@ -695,7 +684,9 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
695
684
  "WHATSAPP.LABEL_TOOLTIP",
696
685
  "WHATSAPP.SELECT_MESSAGE_TEMPLATE",
697
686
  "WHATSAPP.ERROR_WHATSAPP_NOT_INSTALLED",
698
- "WHATSAPP.ERROR_WHATSAPP_GENERIC_ERROR"
687
+ "WHATSAPP.ERROR_WHATSAPP_GENERIC_ERROR",
688
+
689
+ "COPILOT.ASK_AI"
699
690
 
700
691
  ]
701
692
 
@@ -1676,6 +1667,51 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
1676
1667
  }
1677
1668
  }
1678
1669
 
1670
+
1671
+ /** COPILOT SUGGGESTIONS : start */
1672
+ onLoadedSuggestions(event){
1673
+ this.logger.log('[CONVS-DETAIL] onLoadedSuggestions --> ', event)
1674
+ if (event && event.length > 0) {
1675
+ // this.tagsCannedFilter = event
1676
+ // this.tagsCannedCount = event.length
1677
+ }
1678
+ }
1679
+
1680
+ replaceSuggestionInMessage(suggestion, event?) {
1681
+ const elTextArea = this.rowTextArea['el']
1682
+ const textArea = elTextArea.getElementsByTagName('ion-textarea')[0] as HTMLInputElement;
1683
+ // console.log('[CONVS-DETAIL] replaceTagInMessage textArea ', textArea)
1684
+ // console.log('[CONVS-DETAIL] replaceTagInMessage textArea value', textArea.value,)
1685
+
1686
+ // var lastChar = textArea.value.substr(-1); // Selects the last character
1687
+ // if (lastChar === '/') {
1688
+ // textArea.value = textArea.value.substring(0, textArea.value.length() - 1);
1689
+ // }
1690
+ // this.insertAtCursor(this.textArea, textArea.value)
1691
+
1692
+ this.logger.log('[CONVS-DETAIL] replaceTagInMessage canned text ', suggestion.text)
1693
+
1694
+ // replace text
1695
+ var strTEMP = textArea.value + ' ' + suggestion.text
1696
+ strTEMP = this.replacePlaceholderInCanned(strTEMP)
1697
+ this.logger.log('[CONVS-DETAIL] replaceTagInMessage strSearch ', strTEMP)
1698
+ // strTEMP = this.replacePlaceholderInCanned(strTEMP);
1699
+ // textArea.value = '';
1700
+ // that.messageString = strTEMP;
1701
+ textArea.value = strTEMP
1702
+ this.insertAtCursor(textArea, '')
1703
+ this.setCaretPosition(textArea)
1704
+ // setTimeout(() => {
1705
+ // // textArea.focus();
1706
+ // textArea.setFocus()
1707
+ // // this.resizeTextArea()
1708
+ // }, 200)
1709
+ this.SHOW_COPILOT_SUGGESTIONS = !this.SHOW_COPILOT_SUGGESTIONS
1710
+
1711
+ }
1712
+ /** COPILOT SUGGGESTIONS : end */
1713
+
1714
+
1679
1715
  setCaretPosition(ctrl) {
1680
1716
  ctrl.value.trim()
1681
1717
  ctrl.setFocus()
@@ -1868,6 +1904,9 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
1868
1904
  if (event === 'email' || event === 'templates') {
1869
1905
  this.getLeadDetail()
1870
1906
  }
1907
+ if(event === 'copilot'){
1908
+ this.SHOW_COPILOT_SUGGESTIONS = !this.SHOW_COPILOT_SUGGESTIONS
1909
+ }
1871
1910
  }
1872
1911
 
1873
1912
  // -------------- START SCROLL/RESIZE -------------- //
@@ -0,0 +1,16 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+
3
+ import { CopilotService } from './copilot.service';
4
+
5
+ describe('CopilotService', () => {
6
+ let service: CopilotService;
7
+
8
+ beforeEach(() => {
9
+ TestBed.configureTestingModule({});
10
+ service = TestBed.inject(CopilotService);
11
+ });
12
+
13
+ it('should be created', () => {
14
+ expect(service).toBeTruthy();
15
+ });
16
+ });
@@ -0,0 +1,44 @@
1
+ import { HttpClient, HttpHeaders } from '@angular/common/http';
2
+ import { Injectable } from '@angular/core';
3
+ import { AppConfigProvider } from '../app-config';
4
+ import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service';
5
+ import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
6
+ import { map, filter } from 'rxjs/operators';
7
+ import * as uuid from 'uuid';
8
+
9
+ @Injectable({
10
+ providedIn: 'root'
11
+ })
12
+ export class CopilotService {
13
+
14
+ private apiUrl: string;
15
+ private logger: LoggerService = LoggerInstance.getInstance();
16
+
17
+ constructor(
18
+ public http: HttpClient,
19
+ public appConfigProvider: AppConfigProvider
20
+ ) {
21
+ this.logger.log('[COPILOT-SERVICE] HELLO !');
22
+ this.apiUrl = appConfigProvider.getConfig().apiUrl;
23
+ this.logger.log('[COPILOT-SERVICE] apiUrl ', this.apiUrl);
24
+ }
25
+
26
+ public getAll(token: string, projectid: string, conversWith: string) {
27
+ const cannedResponsesURL = this.apiUrl + projectid + "/copilot?request_id=" + conversWith;
28
+ this.logger.log('[COPILOT-SERVICE] getAllSuggestions - URL ', cannedResponsesURL);
29
+
30
+ const httpOptions = {
31
+ headers: new HttpHeaders({
32
+ 'Content-Type': 'application/json',
33
+ Authorization: token
34
+ })
35
+ };
36
+
37
+ return this.http.get(cannedResponsesURL, httpOptions).pipe(map((res: [any]) => {
38
+ let suggestions = res.filter(el => el !== null).map(el => ({ ...el, _id: uuid.v4()} ));
39
+ this.logger.log('[COPILOT-SERVICE] getCannedResponses - RES ', suggestions);
40
+ return suggestions
41
+ }))
42
+ }
43
+
44
+ }
@@ -304,5 +304,8 @@
304
304
  "SEND_EMAIL_ERROR":"⚠️ ATTENTION ⚠️ Error occurred while sending email. Please retry again",
305
305
  "SUBJECT_OFFLINE_MESSAGE":"Tiledesk Offline message 💬",
306
306
  "SEND_EMAIL_SUCCESS_OFFLINE_MESSAGE":"Message also sent by email 📩"
307
+ },
308
+ "COPILOT": {
309
+ "ASK_AI":"Ask to AI"
307
310
  }
308
311
  }
@@ -304,5 +304,8 @@
304
304
  "SEND_EMAIL_ERROR":"⚠️ ATTENZIONE ⚠️Si è verificato un errore durante l'invio dell'email. Si prega di riprovare",
305
305
  "SUBJECT_OFFLINE_MESSAGE":"Messaggio offline di Tiledesk 💬",
306
306
  "SEND_EMAIL_SUCCESS_OFFLINE_MESSAGE":"Messaggio inviato anche via mail 📩"
307
- }
307
+ },
308
+ "COPILOT": {
309
+ "ASK_AI":"Chiedi all'AI"
310
+ }
308
311
  }
@@ -0,0 +1,3 @@
1
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 15 15" height="15" width="15" fill="currentColor">
2
+ <path d="M8.4,2.8l-.9-2.5-.9,2.5c-.6,1.7-2,3.1-3.7,3.7l-2.5.9,2.5.9c1.7.6,3.1,2,3.7,3.7l.9,2.5.9-2.5c.6-1.7,2-3.1,3.7-3.7l2.5-.9-2.5-.9c-1.7-.6-3.1-2-3.7-3.7Z"/>
3
+ </svg>