@sinequa/assistant 3.1.1
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/chat/chat-message/chat-message.component.d.ts +157 -0
- package/chat/chat-reference/chat-reference.component.d.ts +118 -0
- package/chat/chat-settings-v3/chat-settings-v3.component.d.ts +41 -0
- package/chat/chat.component.d.ts +1112 -0
- package/chat/chat.service.d.ts +1046 -0
- package/chat/format-icon/format-icon.component.d.ts +10 -0
- package/chat/format-icon/icons.d.ts +5 -0
- package/chat/index.d.ts +5 -0
- package/chat/initials-avatar/initials-avatar.component.d.ts +35 -0
- package/chat/instance-manager.service.d.ts +28 -0
- package/chat/messages/de.d.ts +4 -0
- package/chat/messages/en.d.ts +4 -0
- package/chat/messages/fr.d.ts +4 -0
- package/chat/messages/index.d.ts +4 -0
- package/chat/public-api.d.ts +11 -0
- package/chat/rest-chat.service.d.ts +28 -0
- package/chat/saved-chats/saved-chats.component.d.ts +37 -0
- package/chat/styles/assistant.scss +61 -0
- package/chat/styles/references.scss +23 -0
- package/chat/types.d.ts +1241 -0
- package/chat/websocket-chat.service.d.ts +97 -0
- package/esm2020/chat/chat-message/chat-message.component.mjs +181 -0
- package/esm2020/chat/chat-reference/chat-reference.component.mjs +40 -0
- package/esm2020/chat/chat-settings-v3/chat-settings-v3.component.mjs +103 -0
- package/esm2020/chat/chat.component.mjs +369 -0
- package/esm2020/chat/chat.service.mjs +185 -0
- package/esm2020/chat/format-icon/format-icon.component.mjs +23 -0
- package/esm2020/chat/format-icon/icons.mjs +138 -0
- package/esm2020/chat/initials-avatar/initials-avatar.component.mjs +60 -0
- package/esm2020/chat/instance-manager.service.mjs +46 -0
- package/esm2020/chat/messages/de.mjs +4 -0
- package/esm2020/chat/messages/en.mjs +4 -0
- package/esm2020/chat/messages/fr.mjs +4 -0
- package/esm2020/chat/messages/index.mjs +9 -0
- package/esm2020/chat/public-api.mjs +12 -0
- package/esm2020/chat/rest-chat.service.mjs +164 -0
- package/esm2020/chat/saved-chats/saved-chats.component.mjs +132 -0
- package/esm2020/chat/sinequa-assistant-chat.mjs +5 -0
- package/esm2020/chat/types.mjs +85 -0
- package/esm2020/chat/websocket-chat.service.mjs +430 -0
- package/esm2020/public-api.mjs +3 -0
- package/esm2020/sinequa-assistant.mjs +5 -0
- package/fesm2015/sinequa-assistant-chat.mjs +1925 -0
- package/fesm2015/sinequa-assistant-chat.mjs.map +1 -0
- package/fesm2015/sinequa-assistant.mjs +9 -0
- package/fesm2015/sinequa-assistant.mjs.map +1 -0
- package/fesm2020/sinequa-assistant-chat.mjs +1911 -0
- package/fesm2020/sinequa-assistant-chat.mjs.map +1 -0
- package/fesm2020/sinequa-assistant.mjs +9 -0
- package/fesm2020/sinequa-assistant.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/package.json +46 -0
- package/public-api.d.ts +1 -0
|
@@ -0,0 +1,1911 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Injectable, EventEmitter, inject, Component, Input, Output, ViewEncapsulation, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild, ContentChild } from '@angular/core';
|
|
3
|
+
import { Subscription, filter, tap, switchMap, BehaviorSubject, Subject, forkJoin, map, catchError, throwError, shareReplay, fromEvent, merge, takeUntil, finalize } from 'rxjs';
|
|
4
|
+
import * as i3 from '@sinequa/core/web-services';
|
|
5
|
+
import { PrincipalWebService, UserSettingsWebService, AuditWebService, SignalRWebService, JsonMethodPluginService } from '@sinequa/core/web-services';
|
|
6
|
+
import * as i1 from '@angular/common';
|
|
7
|
+
import { CommonModule } from '@angular/common';
|
|
8
|
+
import * as i2 from '@angular/forms';
|
|
9
|
+
import { FormsModule } from '@angular/forms';
|
|
10
|
+
import { LoginService, AuthenticationService } from '@sinequa/core/login';
|
|
11
|
+
import { Action } from '@sinequa/components/action';
|
|
12
|
+
import { AbstractFacet } from '@sinequa/components/facet';
|
|
13
|
+
import * as i1$1 from '@sinequa/components/search';
|
|
14
|
+
import { SearchService } from '@sinequa/components/search';
|
|
15
|
+
import { UserPreferences } from '@sinequa/components/user-settings';
|
|
16
|
+
import { NotificationsService } from '@sinequa/core/notification';
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
import { AppService } from '@sinequa/core/app-utils';
|
|
19
|
+
import { IntlService } from '@sinequa/core/intl';
|
|
20
|
+
import get from 'lodash/get';
|
|
21
|
+
import { Utils } from '@sinequa/core/base';
|
|
22
|
+
import { HttpTransportType, LogLevel } from '@microsoft/signalr';
|
|
23
|
+
import { unified } from 'unified';
|
|
24
|
+
import remarkParse from 'remark-parse';
|
|
25
|
+
import { visit, CONTINUE, EXIT } from 'unist-util-visit';
|
|
26
|
+
import * as i2$1 from '@sinequa/components/utils';
|
|
27
|
+
import { UtilsModule } from '@sinequa/components/utils';
|
|
28
|
+
import remarkGfm from 'remark-gfm';
|
|
29
|
+
import * as i5 from '@sinequa/components/collapse';
|
|
30
|
+
import { CollapseModule } from '@sinequa/components/collapse';
|
|
31
|
+
import * as i6 from 'ngx-remark';
|
|
32
|
+
import { RemarkModule } from 'ngx-remark';
|
|
33
|
+
import SafeColor from 'safecolor';
|
|
34
|
+
import { ModalService, ModalButton, ModalModule } from '@sinequa/core/modal';
|
|
35
|
+
import { parseISO, isToday, isYesterday, isThisWeek, differenceInDays, endOfYesterday, isThisMonth, differenceInMonths, isThisQuarter, isThisYear, differenceInYears, format } from 'date-fns';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A service to create and manage instances of ChatService dynamically based on the provided component references and the implementation type (http or websocket)
|
|
39
|
+
* All chat-related components should share the same instance of this InstanceManagerService, which in turn provides the appropriate instance of ChatService
|
|
40
|
+
*/
|
|
41
|
+
class InstanceManagerService {
|
|
42
|
+
constructor() {
|
|
43
|
+
this._serviceInstances = new Map();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Store the instance of ChatService in the map
|
|
47
|
+
* @param key key differentiator between components used to store their corresponding ChatService instance
|
|
48
|
+
* @param service The ChatService instance
|
|
49
|
+
*/
|
|
50
|
+
storeInstance(key, service) {
|
|
51
|
+
this._serviceInstances.set(key, service);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* @param key key differentiator between components based on which the corresponding ChatService instance is fetched
|
|
55
|
+
* @returns The instance of the service corresponding to the instance of the component
|
|
56
|
+
*/
|
|
57
|
+
getInstance(key) {
|
|
58
|
+
if (!this.checkInstance(key)) {
|
|
59
|
+
throw new Error(`No chat instance found for the given key : '${key}'`);
|
|
60
|
+
}
|
|
61
|
+
return this._serviceInstances.get(key);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
*
|
|
65
|
+
* @param key key differentiator between components based on which the check for an existent ChatService instance is performed
|
|
66
|
+
* @returns True if a ChatService instance has been already instantiated for the given key. Otherwise, false.
|
|
67
|
+
*/
|
|
68
|
+
checkInstance(key) {
|
|
69
|
+
return this._serviceInstances.has(key);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
InstanceManagerService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: InstanceManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
73
|
+
InstanceManagerService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: InstanceManagerService, providedIn: 'root' });
|
|
74
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: InstanceManagerService, decorators: [{
|
|
75
|
+
type: Injectable,
|
|
76
|
+
args: [{
|
|
77
|
+
providedIn: 'root',
|
|
78
|
+
}]
|
|
79
|
+
}] });
|
|
80
|
+
|
|
81
|
+
class ChatSettingsV3Component {
|
|
82
|
+
constructor() {
|
|
83
|
+
this._update = new EventEmitter();
|
|
84
|
+
this._cancel = new EventEmitter();
|
|
85
|
+
this.subscription = new Subscription();
|
|
86
|
+
this.isAdmin = false;
|
|
87
|
+
this.loginService = inject(LoginService);
|
|
88
|
+
this.instanceManagerService = inject(InstanceManagerService);
|
|
89
|
+
this.principalService = inject(PrincipalWebService);
|
|
90
|
+
}
|
|
91
|
+
ngOnInit() {
|
|
92
|
+
this.subscription.add(this.loginService.events.pipe(filter(e => e.type === 'login-complete'), tap(_ => this.instantiateChatService()), switchMap(() => this.chatService.initConfig$), filter(initConfig => !!initConfig)).subscribe(_ => {
|
|
93
|
+
this.isAdmin = this.principalService.principal.isAdministrator;
|
|
94
|
+
// Init config with a copy of the original chat config, so that it won't be modified by the user until he clicks on save
|
|
95
|
+
this.config = JSON.parse(JSON.stringify(this.chatService.chatConfig$.value));
|
|
96
|
+
this.selectedModel = this.chatService.getModel(this.config.serviceSettings.service_id, this.config.serviceSettings.model_id);
|
|
97
|
+
this.updateFunctionsCheckboxes();
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
ngOnDestroy() {
|
|
101
|
+
this.subscription.unsubscribe();
|
|
102
|
+
}
|
|
103
|
+
get hasPrompts() {
|
|
104
|
+
return this.isAdmin
|
|
105
|
+
|| !!this.config.uiSettings.systemPrompt
|
|
106
|
+
|| !!this.config.uiSettings.userPrompt;
|
|
107
|
+
}
|
|
108
|
+
get hasAdvancedParameters() {
|
|
109
|
+
return this.isAdmin
|
|
110
|
+
|| !!this.config.uiSettings.temperature
|
|
111
|
+
|| !!this.config.uiSettings.top_p
|
|
112
|
+
|| !!this.config.uiSettings.maxTokens;
|
|
113
|
+
}
|
|
114
|
+
get hasModel() {
|
|
115
|
+
return this.isAdmin
|
|
116
|
+
|| !!this.config.uiSettings.servicesModels
|
|
117
|
+
|| !!this.config.uiSettings.functions
|
|
118
|
+
|| !!this.config.uiSettings.debug
|
|
119
|
+
|| !!this.config.uiSettings.temperature
|
|
120
|
+
|| !!this.config.uiSettings.top_p
|
|
121
|
+
|| !!this.config.uiSettings.maxTokens;
|
|
122
|
+
}
|
|
123
|
+
instantiateChatService() {
|
|
124
|
+
this.chatService = this.instanceManagerService.getInstance(this.instanceId);
|
|
125
|
+
}
|
|
126
|
+
onChatModelChange(selectedModel) {
|
|
127
|
+
// Update properties based on the selected model
|
|
128
|
+
this.config.serviceSettings.service_id = selectedModel.serviceId;
|
|
129
|
+
this.config.serviceSettings.model_id = selectedModel.modelId;
|
|
130
|
+
}
|
|
131
|
+
toggleFunctionsSelection(name) {
|
|
132
|
+
if (this.config.functions.includes(name)) {
|
|
133
|
+
this.config.functions = this.config.functions.filter(item => item !== name);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
this.config.functions.push(name);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
updateFunctionsCheckboxes() {
|
|
140
|
+
// Update the checkboxes based on config.functions
|
|
141
|
+
(this.chatService.functions || []).forEach(item => {
|
|
142
|
+
item['selected'] = this.config.functions.includes(item.functionName);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Save the new chat config in the chat service and the user preferences
|
|
147
|
+
*/
|
|
148
|
+
save() {
|
|
149
|
+
this.chatService.updateChatConfig(this.config);
|
|
150
|
+
this._update.emit();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Cancel the current changes
|
|
154
|
+
*/
|
|
155
|
+
cancel() {
|
|
156
|
+
this._cancel.emit();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
ChatSettingsV3Component.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatSettingsV3Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
160
|
+
ChatSettingsV3Component.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: ChatSettingsV3Component, isStandalone: true, selector: "sq-chat-settings-v3", inputs: { instanceId: "instanceId" }, outputs: { _update: "update", _cancel: "cancel" }, ngImport: i0, template: "<div class=\"sq-chat-settings\">\n <div class=\"settings-panel card-body small\" *ngIf=\"config\">\n\n <h5 *ngIf=\"hasModel\">Model</h5>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.servicesModels\">\n <label for=\"gllmModel\" class=\"form-label\">Model</label>\n <select class=\"form-select\" id=\"gllmModel\" [(ngModel)]=\"selectedModel\" (ngModelChange)=\"onChatModelChange($event)\">\n <option *ngFor=\"let model of chatService.models\" [ngValue]=\"model\">{{model.displayName}}</option>\n </select>\n </div>\n \n <div class=\"mb-4\" *ngIf=\"isAdmin || config.uiSettings.functions\">\n <label for=\"gllmFunctions\" class=\"form-label\">Functions</label>\n <div id=\"gllmFunctions\" *ngFor=\"let func of chatService.functions\" class=\"multi-option form-check form-switch\">\n <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" [id]=\"func.functionName\" [(ngModel)]=\"func.selected\"\n (ngModelChange)=\"toggleFunctionsSelection(func.functionName)\">\n <label class=\"form-check-label\" [for]=\"func.functionName\" [title]=\"func.description\">{{ func.functionName }}</label>\n </div>\n </div>\n \n <div class=\"form-check form-switch mb-2\" *ngIf=\"isAdmin || config.uiSettings.debug\">\n <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" id=\"debug\" [(ngModel)]=\"config.debug\">\n <label class=\"form-check-label\" for=\"debug\">Debug</label>\n </div>\n \n <details *ngIf=\"hasAdvancedParameters\">\n <summary>Advanced parameters</summary>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.temperature\">\n <label for=\"temperature\" class=\"form-label\">Temperature: {{config.serviceSettings.temperature}}</label>\n <input type=\"range\" class=\"form-range form-range-sm\" min=\"0\" max=\"2\" step=\"0.1\" id=\"temperature\"\n [(ngModel)]=\"config.serviceSettings.temperature\">\n </div>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.top_p\">\n <label for=\"top-p\" class=\"form-label\">Top P: {{config.serviceSettings.top_p}}</label>\n <input type=\"range\" class=\"form-range form-range-sm\" min=\"0\" max=\"1\" step=\"0.05\" id=\"top-p\"\n [(ngModel)]=\"config.serviceSettings.top_p\">\n </div>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.maxTokens\">\n <label for=\"max-tokens\" class=\"form-label\">Max generated tokens per answer:\n {{config.serviceSettings.maxTokens}}</label>\n <input type=\"range\" class=\"form-range form-range-sm\" min=\"1\" max=\"2048\" step=\"1\" id=\"max-tokens\"\n [(ngModel)]=\"config.serviceSettings.maxTokens\">\n </div>\n </details>\n \n <hr>\n \n <h5 *ngIf=\"hasPrompts\">Prompts</h5>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.systemPrompt\">\n <label for=\"initialSystemPrompt\" class=\"form-label\">System prompt (hidden)</label>\n <textarea class=\"form-control\" id=\"initialSystemPrompt\" [(ngModel)]=\"config.uiSettings.systemPrompt\"></textarea>\n </div>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.userPrompt\">\n <label for=\"initialUserPrompt\" class=\"form-label\">Initial user prompt</label>\n <textarea class=\"form-control\" id=\"initialUserPrompt\" [(ngModel)]=\"config.uiSettings.userPrompt\"></textarea>\n </div>\n \n </div>\n \n <div class=\"buttons-panel d-flex justify-content-end\">\n <button class=\"btn btn-light\" (click)=\"cancel()\">Cancel</button>\n <button class=\"btn btn-primary\" *ngIf=\"config\" (click)=\"save()\">Save</button>\n </div>\n \n</div>", styles: [":host{display:block;width:var(--ast-chat-settings-width, 100%);max-width:100%;height:100%;margin-left:auto;margin-right:auto;padding-top:var(--ast-chat-settings-padding-top, 0);padding-bottom:var(--ast-chat-settings-padding-bottom, 0)}.sq-chat-settings{display:flex;flex-direction:column;height:100%}.sq-chat-settings .settings-panel{flex-grow:1;overflow:auto}.sq-chat-settings .buttons-panel{padding-top:.5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
|
|
161
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatSettingsV3Component, decorators: [{
|
|
162
|
+
type: Component,
|
|
163
|
+
args: [{ selector: 'sq-chat-settings-v3', standalone: true, imports: [CommonModule, FormsModule], template: "<div class=\"sq-chat-settings\">\n <div class=\"settings-panel card-body small\" *ngIf=\"config\">\n\n <h5 *ngIf=\"hasModel\">Model</h5>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.servicesModels\">\n <label for=\"gllmModel\" class=\"form-label\">Model</label>\n <select class=\"form-select\" id=\"gllmModel\" [(ngModel)]=\"selectedModel\" (ngModelChange)=\"onChatModelChange($event)\">\n <option *ngFor=\"let model of chatService.models\" [ngValue]=\"model\">{{model.displayName}}</option>\n </select>\n </div>\n \n <div class=\"mb-4\" *ngIf=\"isAdmin || config.uiSettings.functions\">\n <label for=\"gllmFunctions\" class=\"form-label\">Functions</label>\n <div id=\"gllmFunctions\" *ngFor=\"let func of chatService.functions\" class=\"multi-option form-check form-switch\">\n <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" [id]=\"func.functionName\" [(ngModel)]=\"func.selected\"\n (ngModelChange)=\"toggleFunctionsSelection(func.functionName)\">\n <label class=\"form-check-label\" [for]=\"func.functionName\" [title]=\"func.description\">{{ func.functionName }}</label>\n </div>\n </div>\n \n <div class=\"form-check form-switch mb-2\" *ngIf=\"isAdmin || config.uiSettings.debug\">\n <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" id=\"debug\" [(ngModel)]=\"config.debug\">\n <label class=\"form-check-label\" for=\"debug\">Debug</label>\n </div>\n \n <details *ngIf=\"hasAdvancedParameters\">\n <summary>Advanced parameters</summary>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.temperature\">\n <label for=\"temperature\" class=\"form-label\">Temperature: {{config.serviceSettings.temperature}}</label>\n <input type=\"range\" class=\"form-range form-range-sm\" min=\"0\" max=\"2\" step=\"0.1\" id=\"temperature\"\n [(ngModel)]=\"config.serviceSettings.temperature\">\n </div>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.top_p\">\n <label for=\"top-p\" class=\"form-label\">Top P: {{config.serviceSettings.top_p}}</label>\n <input type=\"range\" class=\"form-range form-range-sm\" min=\"0\" max=\"1\" step=\"0.05\" id=\"top-p\"\n [(ngModel)]=\"config.serviceSettings.top_p\">\n </div>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.maxTokens\">\n <label for=\"max-tokens\" class=\"form-label\">Max generated tokens per answer:\n {{config.serviceSettings.maxTokens}}</label>\n <input type=\"range\" class=\"form-range form-range-sm\" min=\"1\" max=\"2048\" step=\"1\" id=\"max-tokens\"\n [(ngModel)]=\"config.serviceSettings.maxTokens\">\n </div>\n </details>\n \n <hr>\n \n <h5 *ngIf=\"hasPrompts\">Prompts</h5>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.systemPrompt\">\n <label for=\"initialSystemPrompt\" class=\"form-label\">System prompt (hidden)</label>\n <textarea class=\"form-control\" id=\"initialSystemPrompt\" [(ngModel)]=\"config.uiSettings.systemPrompt\"></textarea>\n </div>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.userPrompt\">\n <label for=\"initialUserPrompt\" class=\"form-label\">Initial user prompt</label>\n <textarea class=\"form-control\" id=\"initialUserPrompt\" [(ngModel)]=\"config.uiSettings.userPrompt\"></textarea>\n </div>\n \n </div>\n \n <div class=\"buttons-panel d-flex justify-content-end\">\n <button class=\"btn btn-light\" (click)=\"cancel()\">Cancel</button>\n <button class=\"btn btn-primary\" *ngIf=\"config\" (click)=\"save()\">Save</button>\n </div>\n \n</div>", styles: [":host{display:block;width:var(--ast-chat-settings-width, 100%);max-width:100%;height:100%;margin-left:auto;margin-right:auto;padding-top:var(--ast-chat-settings-padding-top, 0);padding-bottom:var(--ast-chat-settings-padding-bottom, 0)}.sq-chat-settings{display:flex;flex-direction:column;height:100%}.sq-chat-settings .settings-panel{flex-grow:1;overflow:auto}.sq-chat-settings .buttons-panel{padding-top:.5rem}\n"] }]
|
|
164
|
+
}], propDecorators: { instanceId: [{
|
|
165
|
+
type: Input
|
|
166
|
+
}], _update: [{
|
|
167
|
+
type: Output,
|
|
168
|
+
args: ["update"]
|
|
169
|
+
}], _cancel: [{
|
|
170
|
+
type: Output,
|
|
171
|
+
args: ["cancel"]
|
|
172
|
+
}] } });
|
|
173
|
+
|
|
174
|
+
// Define the Zod representation for the globalSettings object
|
|
175
|
+
const globalSettingsSchema = z.object({
|
|
176
|
+
restEndpoint: z.string().optional(),
|
|
177
|
+
websocketEndpoint: z.string().optional(),
|
|
178
|
+
signalRTransport: z.enum(["WebSockets", "ServerSentEvents", "LongPolling", "None"]),
|
|
179
|
+
signalRLogLevel: z.enum(["Critical", "Debug", "Error", "Information", "None", "Trace", "Warning"])
|
|
180
|
+
}).refine(data => (!!data.restEndpoint || !!data.websocketEndpoint), {
|
|
181
|
+
message: "Based on the provided input() protocol ('REST' or 'WEBSOCKET') to the Chat Component, either 'restEndpoint' or 'websocketEndpoint' property should be provided in the 'globalSettings' of the assistant instance.",
|
|
182
|
+
});
|
|
183
|
+
// Define the Zod representation for the serviceSettings object
|
|
184
|
+
const serviceSettingsSchema = z.object({
|
|
185
|
+
service_id: z.string(),
|
|
186
|
+
model_id: z.string(),
|
|
187
|
+
temperature: z.number(),
|
|
188
|
+
top_p: z.number(),
|
|
189
|
+
maxTokens: z.number(),
|
|
190
|
+
results_per_prompt: z.number(),
|
|
191
|
+
presence_penalty: z.number(),
|
|
192
|
+
frequency_penalty: z.number(),
|
|
193
|
+
});
|
|
194
|
+
// Define the Zod representation for the TextChunksOptions interface
|
|
195
|
+
const textChunksOptionsSchema = z.object({
|
|
196
|
+
extendMode: z.enum(['None', 'Sentence', 'Chars']).optional(),
|
|
197
|
+
extendScope: z.number().optional(),
|
|
198
|
+
});
|
|
199
|
+
// Define the Zod representation for the contextOptions object
|
|
200
|
+
const contextOptionsSchema = z.object({
|
|
201
|
+
docColumns: z.array(z.string()),
|
|
202
|
+
textChunkFillGaps: z.number(),
|
|
203
|
+
html: z.boolean(),
|
|
204
|
+
topPassagesOptions: z.object({
|
|
205
|
+
topPassages: z.number(),
|
|
206
|
+
topPassagesMinScore: z.number(),
|
|
207
|
+
textChunkOptions: textChunksOptionsSchema,
|
|
208
|
+
}),
|
|
209
|
+
matchingPassagesOptions: z.object({
|
|
210
|
+
matchingPassagesPerDoc: z.number(),
|
|
211
|
+
fromTopDocuments: z.number(),
|
|
212
|
+
matchingPassagesMinScore: z.number(),
|
|
213
|
+
textChunkOptions: textChunksOptionsSchema,
|
|
214
|
+
}),
|
|
215
|
+
relevantExtractsOptions: z.object({
|
|
216
|
+
topRelevantExtractsPerDoc: z.number(),
|
|
217
|
+
fromTopDocuments: z.number(),
|
|
218
|
+
textChunkOptions: textChunksOptionsSchema,
|
|
219
|
+
}),
|
|
220
|
+
htmlOptions: z.object({
|
|
221
|
+
images: z.boolean(),
|
|
222
|
+
links: z.boolean(),
|
|
223
|
+
tables: z.boolean(),
|
|
224
|
+
extendTables: z.boolean(),
|
|
225
|
+
}),
|
|
226
|
+
});
|
|
227
|
+
// Define the Zod representation for the contextSettings object
|
|
228
|
+
const contextSettingsSchema = z.object({
|
|
229
|
+
app: z.string().optional(),
|
|
230
|
+
query: z.object({}).optional(),
|
|
231
|
+
contextOptions: contextOptionsSchema,
|
|
232
|
+
});
|
|
233
|
+
// Define the Zod representation for the uiSettings object
|
|
234
|
+
const uiSettingsSchema = z.object({
|
|
235
|
+
display: z.boolean(),
|
|
236
|
+
servicesModels: z.boolean(),
|
|
237
|
+
functions: z.boolean(),
|
|
238
|
+
temperature: z.boolean(),
|
|
239
|
+
top_p: z.boolean(),
|
|
240
|
+
maxTokens: z.boolean(),
|
|
241
|
+
debug: z.boolean(),
|
|
242
|
+
displaySystemPrompt: z.boolean(),
|
|
243
|
+
systemPrompt: z.string(),
|
|
244
|
+
displayUserPrompt: z.boolean(),
|
|
245
|
+
userPrompt: z.string(),
|
|
246
|
+
});
|
|
247
|
+
// Define the Zod representation for the entire ChatConfig object
|
|
248
|
+
const chatConfigSchema = z.object({
|
|
249
|
+
globalSettings: globalSettingsSchema,
|
|
250
|
+
serviceSettings: serviceSettingsSchema,
|
|
251
|
+
contextSettings: contextSettingsSchema,
|
|
252
|
+
uiSettings: uiSettingsSchema,
|
|
253
|
+
functions: z.array(z.string()),
|
|
254
|
+
debug: z.boolean(),
|
|
255
|
+
saveChats: z.boolean()
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
class ChatService {
|
|
259
|
+
constructor() {
|
|
260
|
+
/** Emit true once the initialization of the chat process is done*/
|
|
261
|
+
this.initProcess$ = new BehaviorSubject(false);
|
|
262
|
+
/** Emit true once the initialization of the chat config is done*/
|
|
263
|
+
this.initConfig$ = new BehaviorSubject(false);
|
|
264
|
+
/** Global configuration of the chat */
|
|
265
|
+
this.chatConfig$ = new BehaviorSubject(undefined);
|
|
266
|
+
/** Streaming status of the chat endpoint */
|
|
267
|
+
this.streaming$ = new BehaviorSubject(false);
|
|
268
|
+
/** List of saved chats */
|
|
269
|
+
this.savedChats$ = new BehaviorSubject([]);
|
|
270
|
+
/** Emit the saved chat to load */
|
|
271
|
+
this.loadSavedChat$ = new BehaviorSubject(undefined);
|
|
272
|
+
this.searchService = inject(SearchService);
|
|
273
|
+
this.userSettingsService = inject(UserSettingsWebService);
|
|
274
|
+
this.notificationsService = inject(NotificationsService);
|
|
275
|
+
this.auditService = inject(AuditWebService);
|
|
276
|
+
this.prefs = inject(UserPreferences);
|
|
277
|
+
this.loginService = inject(LoginService);
|
|
278
|
+
this.appService = inject(AppService);
|
|
279
|
+
this.intlService = inject(IntlService);
|
|
280
|
+
}
|
|
281
|
+
get assistants() {
|
|
282
|
+
if (!this.userSettingsService.userSettings)
|
|
283
|
+
this.userSettingsService.userSettings = {};
|
|
284
|
+
if (!this.userSettingsService.userSettings["assistants"])
|
|
285
|
+
this.userSettingsService.userSettings["assistants"] = {};
|
|
286
|
+
return this.userSettingsService.userSettings["assistants"];
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Get the instance ID of the chat service
|
|
290
|
+
* @returns The instance ID of the chat service
|
|
291
|
+
*/
|
|
292
|
+
get chatInstanceId() {
|
|
293
|
+
return this._chatInstanceId;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Persist the instance ID of the chat service
|
|
297
|
+
* @param instanceId The instance ID of the chat service
|
|
298
|
+
*/
|
|
299
|
+
setChatInstanceId(instanceId) {
|
|
300
|
+
this._chatInstanceId = instanceId;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Get the ID of the current chat discussion which is used to save/get/delete it
|
|
304
|
+
* @returns The ID of the current chat discussion
|
|
305
|
+
*/
|
|
306
|
+
get savedChatId() {
|
|
307
|
+
return this._savedChatId;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Persist the ID of the current chat discussion which is used to save/get/delete it
|
|
311
|
+
* @param savedChatId The ID of the current chat discussion which is used to save/get/delete it
|
|
312
|
+
*/
|
|
313
|
+
setSavedChatId(savedChatId) {
|
|
314
|
+
this._savedChatId = savedChatId;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Initialize the chat config by merging the default config with the user preferences and the app config
|
|
318
|
+
* override desc order: user preferences > app config > default config
|
|
319
|
+
*/
|
|
320
|
+
initChatConfig() {
|
|
321
|
+
const key = this.chatInstanceId;
|
|
322
|
+
const userSettingsConfig = this.assistants[key] || {};
|
|
323
|
+
const defaultChatConfig = this.appService.app?.data?.assistants?.[key];
|
|
324
|
+
// Validate the object against the schema
|
|
325
|
+
try {
|
|
326
|
+
chatConfigSchema.parse(defaultChatConfig);
|
|
327
|
+
// Merge configs with override desc order: user preferences > app config > default config
|
|
328
|
+
const chatConfig = {
|
|
329
|
+
...defaultChatConfig,
|
|
330
|
+
...userSettingsConfig
|
|
331
|
+
};
|
|
332
|
+
// Update the chat config and store it in the user preferences
|
|
333
|
+
this.updateChatConfig(chatConfig, false);
|
|
334
|
+
this.initConfig$.next(true);
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
this.notificationsService.error(`Missing valid configuration for the assistant instance '${key}'`);
|
|
338
|
+
throw new Error(`Missing valid configuration for the assistant instance '${key}' . \n ${JSON.stringify(error.issues, null, 2)}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Update the chat config and store it in the user preferences
|
|
343
|
+
* @param config The updated chat config
|
|
344
|
+
* @param notify Whether to notify the user about the update
|
|
345
|
+
* @param successCallback The callback to execute if the update is successful
|
|
346
|
+
* @param errorCallback The callback to execute if the update fails
|
|
347
|
+
*/
|
|
348
|
+
updateChatConfig(config, notify = true, successCallback, errorCallback) {
|
|
349
|
+
this.chatConfig$.next(config);
|
|
350
|
+
const assistants = Object.assign({}, this.assistants);
|
|
351
|
+
assistants[this.chatInstanceId] = config;
|
|
352
|
+
this.userSettingsService.patch({ assistants }).subscribe(next => {
|
|
353
|
+
if (notify) {
|
|
354
|
+
successCallback ? successCallback() : this.notificationsService.success(`The configuration of the assistant instance '${this.chatInstanceId}' has been successfully updated`);
|
|
355
|
+
}
|
|
356
|
+
}, error => {
|
|
357
|
+
if (notify) {
|
|
358
|
+
errorCallback ? errorCallback() : this.notificationsService.error(`The update of the assistant instance '${this.chatInstanceId}' configuration failed`);
|
|
359
|
+
}
|
|
360
|
+
console.error("Could not patch assistants!", error);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Get the model description for the given (serviceId + modelId)
|
|
365
|
+
* If a model is not found, an error message is returned
|
|
366
|
+
* @param serviceId The serviceId of the model
|
|
367
|
+
* @param modelId The modelId of the model
|
|
368
|
+
* @returns The model description
|
|
369
|
+
*/
|
|
370
|
+
getModel(serviceId, modelId) {
|
|
371
|
+
let model = this.models?.find(m => m.serviceId === serviceId && m.modelId === modelId);
|
|
372
|
+
// Handle obsolete config
|
|
373
|
+
if (!model) {
|
|
374
|
+
this.notificationsService.error(`FATAL ERROR : The model (serviceId = '${serviceId}', modelId = '${modelId}') is no longer available. Please contact an admin for further information.`);
|
|
375
|
+
throw new Error(`FATAL ERROR : The model (serviceId = '${serviceId}', modelId = '${modelId}') is no longer available`);
|
|
376
|
+
}
|
|
377
|
+
return model;
|
|
378
|
+
}
|
|
379
|
+
// Todo: pending implementation
|
|
380
|
+
notifyAudit(messagesHistory, model) {
|
|
381
|
+
// let numberOfUserMessages = 0;
|
|
382
|
+
// let numberOfAttachments = 0;
|
|
383
|
+
// let numberOfAssistantMessages = 0;
|
|
384
|
+
// for(let m of messagesHistory) {
|
|
385
|
+
// if(m.$attachment) numberOfAttachments++;
|
|
386
|
+
// else if(m.role === 'user') numberOfUserMessages++;
|
|
387
|
+
// else if (m.role === 'assistant') numberOfAssistantMessages++;
|
|
388
|
+
// }
|
|
389
|
+
// this.auditService.notify({
|
|
390
|
+
// type: 'Chat_Messages',
|
|
391
|
+
// detail: {
|
|
392
|
+
// // message: messagesHistory.map(m => m.role.toUpperCase() + ': '+ (m.$attachment? `attachment ${m.$attachment?.$record.title}` : m.content)).join('\n\n'),
|
|
393
|
+
// message: messagesHistory.map(m => m.role.toUpperCase() + ': '+ m.content).join('\n\n'),
|
|
394
|
+
// // numberOfUserMessages,
|
|
395
|
+
// // numberOfAttachments,
|
|
396
|
+
// // numberOfAssistantMessages,
|
|
397
|
+
// model
|
|
398
|
+
// }
|
|
399
|
+
// });
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Format a date string in UTC to a local date string
|
|
403
|
+
* @param value Date string in UTC to format
|
|
404
|
+
* @returns A formatted local date string
|
|
405
|
+
*/
|
|
406
|
+
formatDateTime(value) {
|
|
407
|
+
return this.intlService["formatTime"](value, { day: "numeric", month: "short", year: "numeric" });
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Takes a text prompt that may contain placeholders for variables
|
|
411
|
+
* and replaces these placeholders if it finds a match in the given
|
|
412
|
+
* context object.
|
|
413
|
+
*/
|
|
414
|
+
static formatPrompt(prompt, context) {
|
|
415
|
+
return prompt.replace(/{{(.*?)}}/g, (match, expr) => get(context, expr) ?? match);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* @returns A Globally Unique Identifier
|
|
419
|
+
*/
|
|
420
|
+
static generateGUID() {
|
|
421
|
+
return Utils.guid();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
ChatService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
425
|
+
ChatService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatService });
|
|
426
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatService, decorators: [{
|
|
427
|
+
type: Injectable
|
|
428
|
+
}] });
|
|
429
|
+
|
|
430
|
+
class WebSocketChatService extends ChatService {
|
|
431
|
+
constructor() {
|
|
432
|
+
super();
|
|
433
|
+
this.connectionBuilt$ = new Subject(); // Emit when the connection is built
|
|
434
|
+
this.connectionStarted$ = new Subject(); // Emit when the connection is started
|
|
435
|
+
this.messageHandlers = new Map();
|
|
436
|
+
this.actionMap = new Map();
|
|
437
|
+
this.content = "";
|
|
438
|
+
this.attachments = [];
|
|
439
|
+
this.signalRService = inject(SignalRWebService);
|
|
440
|
+
this.authenticationService = inject(AuthenticationService);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Initialize the chat process after the login is complete.
|
|
444
|
+
* It includes building and starting a connection, executing parallel requests for models and functions, and handling errors during the process.
|
|
445
|
+
*
|
|
446
|
+
* @returns An Observable<boolean> indicating the success of the initialization process.
|
|
447
|
+
*/
|
|
448
|
+
init() {
|
|
449
|
+
return this.loginService.events.pipe(filter((e) => e.type === 'login-complete'), tap(() => this.getRequestsUrl()),
|
|
450
|
+
// Build the connection and handle the completion with the connectionBuilt$ subject
|
|
451
|
+
switchMap(() => this.buildConnection()), tap(() => {
|
|
452
|
+
this.initMessageHandlers();
|
|
453
|
+
this.connectionBuilt$.next();
|
|
454
|
+
}),
|
|
455
|
+
// Start the connection and handle the completion with the connectionStarted$ subject
|
|
456
|
+
switchMap(() => this.startConnection()),
|
|
457
|
+
// Execute parallel requests for models and functions
|
|
458
|
+
switchMap(() => {
|
|
459
|
+
this.connectionStarted$.next();
|
|
460
|
+
return forkJoin([
|
|
461
|
+
this.listModels(),
|
|
462
|
+
this.listFunctions()
|
|
463
|
+
]);
|
|
464
|
+
}),
|
|
465
|
+
// Map the results of parallel requests to a boolean indicating success
|
|
466
|
+
map(([models, functions]) => {
|
|
467
|
+
this.initProcess$.next(true);
|
|
468
|
+
return !!models && !!functions;
|
|
469
|
+
}),
|
|
470
|
+
// Any errors during the process are caught, logged, and re-thrown to propagate the error further
|
|
471
|
+
catchError((error) => {
|
|
472
|
+
console.error('Error occurred:', error);
|
|
473
|
+
return throwError(() => error);
|
|
474
|
+
}),
|
|
475
|
+
// Cache and replay the emitted value for subsequent subscribers
|
|
476
|
+
shareReplay(1));
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Define the assistant endpoint to use for the websocket requests
|
|
480
|
+
* It can be overridden by the app config
|
|
481
|
+
*/
|
|
482
|
+
getRequestsUrl() {
|
|
483
|
+
if (this.chatConfig$.value.globalSettings.websocketEndpoint) {
|
|
484
|
+
this.REQUEST_URL = this.chatConfig$.value.globalSettings.websocketEndpoint;
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
throw new Error(`The property 'websocketEndpoint' must be provided when attempting to use 'WebSocket' in assistant instance`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
listModels() {
|
|
491
|
+
const modelsSubject = new Subject();
|
|
492
|
+
this.connection.on('ListModels', (res) => {
|
|
493
|
+
this.models = res.models?.filter(model => !!model.enable);
|
|
494
|
+
modelsSubject.next(this.models);
|
|
495
|
+
modelsSubject.complete();
|
|
496
|
+
});
|
|
497
|
+
// Send the request to get the list of models
|
|
498
|
+
this.connection.invoke('ListModels', { debug: this.chatConfig$.value.debug })
|
|
499
|
+
.catch(error => {
|
|
500
|
+
console.error('Error invoking ListModels:', error);
|
|
501
|
+
modelsSubject.complete();
|
|
502
|
+
return Promise.resolve(); // Return a resolved promise to handle the error and prevent unhandled promise rejection
|
|
503
|
+
});
|
|
504
|
+
return modelsSubject.asObservable();
|
|
505
|
+
}
|
|
506
|
+
listFunctions() {
|
|
507
|
+
const functionsSubject = new Subject();
|
|
508
|
+
this.connection.on('ListFunctions', (res) => {
|
|
509
|
+
this.functions = res.functions;
|
|
510
|
+
functionsSubject.next(this.functions);
|
|
511
|
+
functionsSubject.complete();
|
|
512
|
+
});
|
|
513
|
+
// Send the request to get the list of functions
|
|
514
|
+
this.connection.invoke('ListFunctions', { debug: this.chatConfig$.value.debug })
|
|
515
|
+
.catch(error => {
|
|
516
|
+
console.error('Error invoking ListFunctions:', error);
|
|
517
|
+
functionsSubject.complete();
|
|
518
|
+
return Promise.resolve(); // Return a resolved promise to handle the error and prevent unhandled promise rejection
|
|
519
|
+
});
|
|
520
|
+
return functionsSubject.asObservable();
|
|
521
|
+
}
|
|
522
|
+
fetch(messages, query = this.searchService.query) {
|
|
523
|
+
// Start streaming by invoking the Chat method
|
|
524
|
+
this.streaming$.next(true);
|
|
525
|
+
// Prepare the payload to send to the Chat method
|
|
526
|
+
const data = {
|
|
527
|
+
history: messages,
|
|
528
|
+
functions: this.chatConfig$.value.functions,
|
|
529
|
+
debug: this.chatConfig$.value.debug,
|
|
530
|
+
serviceSettings: this.chatConfig$.value.serviceSettings,
|
|
531
|
+
contextSettings: {
|
|
532
|
+
...this.chatConfig$.value.contextSettings,
|
|
533
|
+
app: this.appService.appName,
|
|
534
|
+
query
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
if (this.chatConfig$.value.saveChats) {
|
|
538
|
+
data.instanceId = this.chatInstanceId;
|
|
539
|
+
data.savedChatId = this.savedChatId;
|
|
540
|
+
}
|
|
541
|
+
let response = { role: "assistant", content: "", additionalProperties: { display: true } }; // here display: true is needed in order to be able to show the progress
|
|
542
|
+
// Create a Subject to signal completion
|
|
543
|
+
const completion$ = new Subject();
|
|
544
|
+
// Create observables for each non-global handler in the messageHandlers map (default and eventual custom ones) once it is triggered by the hub connection
|
|
545
|
+
const observables = Array
|
|
546
|
+
.from(this.messageHandlers.entries())
|
|
547
|
+
.filter(([eventName, eventHandler]) => !eventHandler.isGlobalHandler)
|
|
548
|
+
.map(([eventName, eventHandler]) => {
|
|
549
|
+
return fromEvent(this.connection, eventName).pipe(map((event) => eventHandler.handler(event)) // Execute the corresponding handler
|
|
550
|
+
);
|
|
551
|
+
});
|
|
552
|
+
// Then merge them into a single observable in order to simulate the streaming behavior
|
|
553
|
+
const combined$ = merge(...observables).pipe(takeUntil(completion$) // Complete the observable when completion$ emits
|
|
554
|
+
);
|
|
555
|
+
// Invoke the Chat method
|
|
556
|
+
this.connection.invoke('Chat', data)
|
|
557
|
+
.then(() => this.notifyAudit(this.chatHistory, this.chatConfig$.value.serviceSettings.service_id)) // When the server indicates it has successfully finished invoking the method, notify the audit service with the recent chat history
|
|
558
|
+
.catch(error => {
|
|
559
|
+
console.error('Error invoking Chat:', error);
|
|
560
|
+
return Promise.resolve(); // Return a resolved promise to handle the error and prevent unhandled promise rejection
|
|
561
|
+
})
|
|
562
|
+
.finally(() => {
|
|
563
|
+
this.streaming$.next(false); // Complete streaming regardless of success or error
|
|
564
|
+
this.actionMap.clear(); // Clear the actionMap
|
|
565
|
+
this.content = ""; // Clear the content
|
|
566
|
+
this.attachments = []; // Clear the attachments
|
|
567
|
+
this.executionTime = ""; // Clear the executionTime
|
|
568
|
+
completion$.next(); // Emit a signal to complete the observables
|
|
569
|
+
completion$.complete(); // Complete the subject
|
|
570
|
+
});
|
|
571
|
+
// Return the merged observables
|
|
572
|
+
return combined$.pipe(map(() => {
|
|
573
|
+
// Define $progress from the actionMap
|
|
574
|
+
const actions = Array.from(this.actionMap.values());
|
|
575
|
+
const $progress = actions.length > 0
|
|
576
|
+
? actions.map((a) => ({
|
|
577
|
+
title: a.displayName ?? "",
|
|
578
|
+
content: a.displayValue ?? "",
|
|
579
|
+
done: a.executionTime !== undefined,
|
|
580
|
+
time: a.executionTime,
|
|
581
|
+
}))
|
|
582
|
+
: undefined;
|
|
583
|
+
// Define the attachment used in the context of the response message
|
|
584
|
+
const $attachment = this.attachments;
|
|
585
|
+
// As soon as the first content or $progress is defined, the assistant is considered as streaming
|
|
586
|
+
if (!!this.content || $progress || $attachment.length > 0) {
|
|
587
|
+
response = { ...response, content: this.content, additionalProperties: { ...response.additionalProperties, $progress, $attachment } };
|
|
588
|
+
}
|
|
589
|
+
// Return the result
|
|
590
|
+
return { history: [...messages, response], executionTime: this.executionTime };
|
|
591
|
+
}));
|
|
592
|
+
}
|
|
593
|
+
listSavedChat() {
|
|
594
|
+
const data = {
|
|
595
|
+
instanceId: this.chatInstanceId,
|
|
596
|
+
debug: this.chatConfig$.value.debug
|
|
597
|
+
};
|
|
598
|
+
this.connection.on('SavedChatList', (res) => {
|
|
599
|
+
this.savedChats$.next(res.savedChats); // emits the result to the savedChats$ subject
|
|
600
|
+
});
|
|
601
|
+
// Invoke the method SavedChatList
|
|
602
|
+
this.connection.invoke('SavedChatList', data)
|
|
603
|
+
.catch(error => {
|
|
604
|
+
console.error('Error invoking SavedChatList:', error);
|
|
605
|
+
return Promise.resolve();
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
getSavedChat(id) {
|
|
609
|
+
const savedChatSubject = new Subject();
|
|
610
|
+
const data = {
|
|
611
|
+
instanceId: this.chatInstanceId,
|
|
612
|
+
savedChatId: id,
|
|
613
|
+
debug: this.chatConfig$.value.debug
|
|
614
|
+
};
|
|
615
|
+
this.connection.on('SavedChatGet', (res) => {
|
|
616
|
+
savedChatSubject.next(res.savedChat);
|
|
617
|
+
savedChatSubject.complete();
|
|
618
|
+
});
|
|
619
|
+
// Invoke the method SavedChatGet
|
|
620
|
+
this.connection.invoke('SavedChatGet', data)
|
|
621
|
+
.catch(error => {
|
|
622
|
+
console.error('Error invoking SavedChatGet:', error);
|
|
623
|
+
savedChatSubject.complete();
|
|
624
|
+
return Promise.resolve();
|
|
625
|
+
});
|
|
626
|
+
return savedChatSubject.asObservable();
|
|
627
|
+
}
|
|
628
|
+
deleteSavedChat(ids) {
|
|
629
|
+
const deleteSavedChatSubject = new Subject();
|
|
630
|
+
const data = {
|
|
631
|
+
instanceId: this.chatInstanceId,
|
|
632
|
+
SavedChatIds: ids,
|
|
633
|
+
debug: this.chatConfig$.value.debug
|
|
634
|
+
};
|
|
635
|
+
this.connection.on('SavedChatDelete', (res) => {
|
|
636
|
+
deleteSavedChatSubject.next(res.deleteCount);
|
|
637
|
+
deleteSavedChatSubject.complete();
|
|
638
|
+
});
|
|
639
|
+
// Invoke the method SavedChatDelete
|
|
640
|
+
this.connection.invoke('SavedChatDelete', data)
|
|
641
|
+
.catch(error => {
|
|
642
|
+
console.error('Error invoking SavedChatDelete:', error);
|
|
643
|
+
deleteSavedChatSubject.complete();
|
|
644
|
+
return Promise.resolve();
|
|
645
|
+
});
|
|
646
|
+
return deleteSavedChatSubject.asObservable();
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Initialize out-of-the-box handlers
|
|
650
|
+
* It is a placeholder for non-streaming scenarios, where you invoke a specific hub method, and the server responds with a single message or a result
|
|
651
|
+
*/
|
|
652
|
+
initMessageHandlers() {
|
|
653
|
+
this.addMessageHandler("Debug", { handler: (debug) => console.log(debug),
|
|
654
|
+
isGlobalHandler: true });
|
|
655
|
+
this.addMessageHandler("ActionStart", { handler: (action) => this.actionMap.set(action.guid, action),
|
|
656
|
+
isGlobalHandler: false });
|
|
657
|
+
this.addMessageHandler("ActionResult", {
|
|
658
|
+
handler: (action) => this.actionMap.set(action.guid, { ...this.actionMap.get(action.guid), ...action }),
|
|
659
|
+
isGlobalHandler: false
|
|
660
|
+
});
|
|
661
|
+
this.addMessageHandler("ActionStop", {
|
|
662
|
+
handler: (action) => this.actionMap.set(action.guid, { ...this.actionMap.get(action.guid), ...action }),
|
|
663
|
+
isGlobalHandler: false
|
|
664
|
+
});
|
|
665
|
+
this.addMessageHandler("ContextMessage", {
|
|
666
|
+
handler: (message) => this.attachments.push(message.metadata),
|
|
667
|
+
isGlobalHandler: false
|
|
668
|
+
});
|
|
669
|
+
this.addMessageHandler("Message", {
|
|
670
|
+
handler: (message) => this.content += message ?? "",
|
|
671
|
+
isGlobalHandler: false
|
|
672
|
+
});
|
|
673
|
+
this.addMessageHandler("History", {
|
|
674
|
+
handler: (history) => {
|
|
675
|
+
this.chatHistory = history.history;
|
|
676
|
+
this.executionTime = history.executionTime;
|
|
677
|
+
},
|
|
678
|
+
isGlobalHandler: false
|
|
679
|
+
});
|
|
680
|
+
this.addMessageHandler("Error", {
|
|
681
|
+
handler: (error) => {
|
|
682
|
+
console.error(error);
|
|
683
|
+
this.notificationsService.error(error);
|
|
684
|
+
},
|
|
685
|
+
isGlobalHandler: true
|
|
686
|
+
});
|
|
687
|
+
this.addMessageHandler("Quota", {
|
|
688
|
+
handler: (message) => {
|
|
689
|
+
if (message.quota.maxQuotaReached) {
|
|
690
|
+
const msg = `Sorry, you have exceeded the allowed quota. Please retry starting from ${this.formatDateTime(message.quota.nextResetUTC)}.`;
|
|
691
|
+
console.error(msg);
|
|
692
|
+
this.notificationsService.error(msg);
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
isGlobalHandler: true
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Override and register the entire messageHandlers map by merging the provided map with the default one
|
|
700
|
+
* @param messageHandlers
|
|
701
|
+
*/
|
|
702
|
+
overrideMessageHandlers(messageHandlers) {
|
|
703
|
+
// Clear the already registered global chat handlers before merging the new ones
|
|
704
|
+
this.messageHandlers.forEach((eventHandler, eventName) => {
|
|
705
|
+
if (eventHandler.isGlobalHandler) {
|
|
706
|
+
this.unsubscribeMessageHandler(eventName);
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
// Merge the new event handlers with the existing ones
|
|
710
|
+
this.messageHandlers = new Map([...this.messageHandlers, ...messageHandlers]);
|
|
711
|
+
// Register the global chat handlers among the merged map
|
|
712
|
+
this.messageHandlers.forEach((eventHandler, eventName) => {
|
|
713
|
+
if (eventHandler.isGlobalHandler) {
|
|
714
|
+
this.registerMessageHandler(eventName, eventHandler);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Add a listener for a specific event.
|
|
720
|
+
* If a listener for this same event already exists, it will be overridden.
|
|
721
|
+
* If the listener has "isChatGlobalHandler" set to true, it will be registered to the hub connection.
|
|
722
|
+
* @param eventName Name of the event to register a listener for
|
|
723
|
+
* @param eventHandler The handler to be called when the event is received
|
|
724
|
+
*/
|
|
725
|
+
addMessageHandler(eventName, eventHandler) {
|
|
726
|
+
this.messageHandlers.set(eventName, eventHandler);
|
|
727
|
+
if (eventHandler.isGlobalHandler) {
|
|
728
|
+
this.registerMessageHandler(eventName, eventHandler);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Dynamically register a listener for a specific event.
|
|
733
|
+
* If a listener for this event already exists, it will be overridden.
|
|
734
|
+
* @param eventName Name of the event to register a listener for
|
|
735
|
+
* @param eventHandler The handler to be called when the event is received
|
|
736
|
+
*/
|
|
737
|
+
registerMessageHandler(eventName, eventHandler) {
|
|
738
|
+
if (!this.connection) {
|
|
739
|
+
console.log("No connection found to register the listener" + eventName);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
this.connection.on(eventName, (data) => {
|
|
743
|
+
eventHandler.handler(data);
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Remove a listener for a specific event from the messageHandlers map and unsubscribe from receiving messages for this event from the SignalR hub.
|
|
748
|
+
* @param eventName Name of the event to remove the listener for
|
|
749
|
+
*/
|
|
750
|
+
removeMessageHandler(eventName) {
|
|
751
|
+
this.messageHandlers.delete(eventName);
|
|
752
|
+
this.unsubscribeMessageHandler(eventName);
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Unsubscribe from receiving messages for a specific event from the SignalR hub.
|
|
756
|
+
* ALL its related listeners will be removed from hub connection
|
|
757
|
+
* This is needed to prevent accumulating old listeners when overriding the entire messageHandlers map
|
|
758
|
+
* @param eventName Name of the event
|
|
759
|
+
*/
|
|
760
|
+
unsubscribeMessageHandler(eventName) {
|
|
761
|
+
this.connection.off(eventName);
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Build a connection to the signalR websocket and register default listeners to the methods defined in the server hub class
|
|
765
|
+
* @param options The options for the connection. It overrides the default options
|
|
766
|
+
* @param logLevel Define the log level displayed in the console
|
|
767
|
+
* @returns Promise that resolves when the connection is built
|
|
768
|
+
*/
|
|
769
|
+
buildConnection(options) {
|
|
770
|
+
return new Promise((resolve, reject) => {
|
|
771
|
+
if (!this.REQUEST_URL) {
|
|
772
|
+
reject(new Error("No endpoint provided to connect the websocket to"));
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
const logLevel = this.getLogLevel();
|
|
776
|
+
this.connection = this.signalRService.buildConnection(this.REQUEST_URL, { ...this.defaultOptions, ...options }, logLevel);
|
|
777
|
+
resolve();
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Start the connection
|
|
782
|
+
* @returns Promise that resolves when the connection is started
|
|
783
|
+
*/
|
|
784
|
+
startConnection() {
|
|
785
|
+
return this.signalRService.startConnection(this.connection);
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Stop the connection
|
|
789
|
+
* @returns Promise that resolves when the connection is stopped
|
|
790
|
+
*/
|
|
791
|
+
stopConnection() {
|
|
792
|
+
return this.signalRService.stopConnection(this.connection);
|
|
793
|
+
}
|
|
794
|
+
getTransports() {
|
|
795
|
+
switch (this.chatConfig$.value?.globalSettings.signalRTransport) {
|
|
796
|
+
case "WebSockets":
|
|
797
|
+
return HttpTransportType.WebSockets;
|
|
798
|
+
case "ServerSentEvents":
|
|
799
|
+
return HttpTransportType.ServerSentEvents;
|
|
800
|
+
case "LongPolling":
|
|
801
|
+
return HttpTransportType.LongPolling;
|
|
802
|
+
default:
|
|
803
|
+
return HttpTransportType.None;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
getLogLevel() {
|
|
807
|
+
switch (this.chatConfig$.value?.globalSettings.signalRLogLevel) {
|
|
808
|
+
case "Critical":
|
|
809
|
+
return LogLevel.Critical; // Log level for diagnostic messages that indicate a failure that will terminate the entire application.
|
|
810
|
+
case "Debug":
|
|
811
|
+
return LogLevel.Debug; // Log level for low severity diagnostic messages.
|
|
812
|
+
case "Error":
|
|
813
|
+
return LogLevel.Error; // Log level for diagnostic messages that indicate a failure in the current operation.
|
|
814
|
+
case "Information":
|
|
815
|
+
return LogLevel.Information; // Log level for informational diagnostic messages.
|
|
816
|
+
case "None":
|
|
817
|
+
return LogLevel.None; // The highest possible log level. Used when configuring logging to indicate that no log messages should be emitted.
|
|
818
|
+
case "Trace":
|
|
819
|
+
return LogLevel.Trace; // Log level for very low severity diagnostic messages.
|
|
820
|
+
case "Warning":
|
|
821
|
+
return LogLevel.Warning; // Log level for diagnostic messages that indicate a non-fatal problem.
|
|
822
|
+
default:
|
|
823
|
+
return LogLevel.None; // The highest possible log level. Used when configuring logging to indicate that no log messages should be emitted.
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
get defaultOptions() {
|
|
827
|
+
let headers = {
|
|
828
|
+
"sinequa-force-camel-case": "true",
|
|
829
|
+
"x-language": this.intlService.currentLocale.name,
|
|
830
|
+
"ui-language": this.intlService.currentLocale.name,
|
|
831
|
+
};
|
|
832
|
+
if (this.authenticationService.processedCredentials) {
|
|
833
|
+
headers = { ...headers, "sinequa-csrf-token": this.authenticationService.processedCredentials.data.csrfToken };
|
|
834
|
+
}
|
|
835
|
+
;
|
|
836
|
+
// For the first GET request sent by signalR to start a WebSocket protocol,
|
|
837
|
+
// as far as we know, signalR only lets us tweak the request with this access token factory
|
|
838
|
+
// so we pass along the Sinequa CSRF token to pass the CSRF check..
|
|
839
|
+
return {
|
|
840
|
+
transport: this.getTransports(),
|
|
841
|
+
withCredentials: true,
|
|
842
|
+
headers,
|
|
843
|
+
accessTokenFactory: () => this.authenticationService.processedCredentials?.data?.csrfToken || ""
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
WebSocketChatService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: WebSocketChatService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
848
|
+
WebSocketChatService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: WebSocketChatService });
|
|
849
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: WebSocketChatService, decorators: [{
|
|
850
|
+
type: Injectable
|
|
851
|
+
}], ctorParameters: function () { return []; } });
|
|
852
|
+
|
|
853
|
+
class InitialsAvatarComponent {
|
|
854
|
+
constructor() {
|
|
855
|
+
this.fullName = '';
|
|
856
|
+
this.size = 1.5; // in rem
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Gives initials of a name and a safe color background to use,
|
|
860
|
+
* assuming text color will be white
|
|
861
|
+
* @param fullName full name to evaluate intials for
|
|
862
|
+
* @param split string to use has splitter for `fullName`
|
|
863
|
+
* @returns an `object` containing initials and color
|
|
864
|
+
*/
|
|
865
|
+
getInitialsAndColorFromFullName(fullName, split = ' ') {
|
|
866
|
+
return { initials: this.getInitialsFromFullName(fullName, split), color: this.getColorFromName(fullName) };
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Gives initials of a name, ie:
|
|
870
|
+
* ```
|
|
871
|
+
* getInitialForFullName('John Snow') => 'JS'
|
|
872
|
+
* getInitialForFullName('Geralt of Rivia', ' of ') => 'GR'
|
|
873
|
+
* ```
|
|
874
|
+
* @param fullName full name to evaluate intial for
|
|
875
|
+
* @param split string to use has splitter
|
|
876
|
+
* @returns string containg the first letter of splitted name
|
|
877
|
+
*/
|
|
878
|
+
getInitialsFromFullName(fullName, split = ' ') {
|
|
879
|
+
if (!fullName)
|
|
880
|
+
return '';
|
|
881
|
+
const names = fullName.split(split);
|
|
882
|
+
return names[0][0] + (names[1]?.[0] ?? '');
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Gets a random color using text as seed
|
|
886
|
+
* @param text string to use for color generation
|
|
887
|
+
* @returns string formatted like `rgb(xxx, xxx, xxx)`
|
|
888
|
+
*/
|
|
889
|
+
getColorFromName(text) {
|
|
890
|
+
const safeColor = new SafeColor({
|
|
891
|
+
color: [255, 255, 255],
|
|
892
|
+
contrast: 4.5
|
|
893
|
+
});
|
|
894
|
+
return safeColor.random(text);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
InitialsAvatarComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: InitialsAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
898
|
+
InitialsAvatarComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: InitialsAvatarComponent, isStandalone: true, selector: "sq-initials-avatar", inputs: { fullName: "fullName", size: "size" }, ngImport: i0, template: "<span class=\"sq-initials-avatar\" *ngIf=\"getInitialsAndColorFromFullName(fullName) as meta\"\n [ngStyle]=\"{ 'background-color': meta.color }\" [style.height.rem]=\"size\" [style.width.rem]=\"size\"\n [style.line-height.rem]=\"size\" [style.font-size.rem]=\"size/2\">\n {{ meta.initials | uppercase }}\n</span>", styles: [".sq-initials-avatar{display:block;border-radius:50%;text-align:center;color:#fff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
|
|
899
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: InitialsAvatarComponent, decorators: [{
|
|
900
|
+
type: Component,
|
|
901
|
+
args: [{ selector: 'sq-initials-avatar', standalone: true, imports: [CommonModule], template: "<span class=\"sq-initials-avatar\" *ngIf=\"getInitialsAndColorFromFullName(fullName) as meta\"\n [ngStyle]=\"{ 'background-color': meta.color }\" [style.height.rem]=\"size\" [style.width.rem]=\"size\"\n [style.line-height.rem]=\"size\" [style.font-size.rem]=\"size/2\">\n {{ meta.initials | uppercase }}\n</span>", styles: [".sq-initials-avatar{display:block;border-radius:50%;text-align:center;color:#fff}\n"] }]
|
|
902
|
+
}], propDecorators: { fullName: [{
|
|
903
|
+
type: Input
|
|
904
|
+
}], size: [{
|
|
905
|
+
type: Input
|
|
906
|
+
}] } });
|
|
907
|
+
|
|
908
|
+
const defaultFormatIcons = {
|
|
909
|
+
"extractslocations": { icon: "far fa-file-alt" },
|
|
910
|
+
"matchlocations": { icon: "far fa-flag" },
|
|
911
|
+
"geo": { icon: "fas fa-map-marker-alt" },
|
|
912
|
+
"person": { icon: "fas fa-user" },
|
|
913
|
+
"company": { icon: "fas fa-building" },
|
|
914
|
+
"title": { icon: "fas fa-tag" },
|
|
915
|
+
"modified": { icon: "far fa-calendar-alt" },
|
|
916
|
+
"size": { icon: "fas fa-weight-hanging" },
|
|
917
|
+
"treepath": { icon: "fas fa-folder-open" },
|
|
918
|
+
"filename": { icon: "far fa-file-alt" },
|
|
919
|
+
"authors": { icon: "fas fa-user-edit" },
|
|
920
|
+
"accesslists": { icon: "fas fa-lock" },
|
|
921
|
+
"doctype": { icon: "far fa-file" },
|
|
922
|
+
"documentlanguages": { icon: "fas fa-globe-americas" },
|
|
923
|
+
"globalrelevance": { icon: "far fa-star" },
|
|
924
|
+
"indexationtime": { icon: "fas fa-search" },
|
|
925
|
+
"concepts": { icon: "far fa-comment-dots" },
|
|
926
|
+
"keywords": { icon: "fas fa-tags" },
|
|
927
|
+
"matchingpartnames": { icon: "fas fa-align-left" },
|
|
928
|
+
"msgfrom": { icon: "fas fa-envelope" },
|
|
929
|
+
"msgto": { icon: "fas fa-envelope-open-text" },
|
|
930
|
+
"file": { icon: "far fa-file" },
|
|
931
|
+
"htm": { icon: "fas fa-globe-europe", color: '#4545bf' },
|
|
932
|
+
"html": { icon: "fas fa-globe-europe", color: '#4545bf' },
|
|
933
|
+
"xhtm": { icon: "fas fa-globe-europe", color: '#4545bf' },
|
|
934
|
+
"xhtml": { icon: "fas fa-globe-europe", color: '#4545bf' },
|
|
935
|
+
"mht": { icon: "fas fa-globe-europe", color: '#4545bf' },
|
|
936
|
+
"doc": { icon: "far fa-file-word", color: '#3f3fca' },
|
|
937
|
+
"docx": { icon: "far fa-file-word", color: '#3f3fca' },
|
|
938
|
+
"docm": { icon: "far fa-file-word", color: '#3f3fca' },
|
|
939
|
+
"dot": { icon: "far fa-file-word", color: '#3f3fca' },
|
|
940
|
+
"dotx": { icon: "far fa-file-word", color: '#3f3fca' },
|
|
941
|
+
"dotm": { icon: "far fa-file-word", color: '#3f3fca' },
|
|
942
|
+
"rtf": { icon: "far fa-file-word", color: '#3f3fca' },
|
|
943
|
+
"odt": { icon: "far fa-file-word", color: 'grey' },
|
|
944
|
+
"ott": { icon: "far fa-file-word", color: 'grey' },
|
|
945
|
+
"gdoc": { icon: "far fa-file-word", color: 'blue' },
|
|
946
|
+
"xls": { icon: "far fa-file-excel", color: 'green' },
|
|
947
|
+
"xlsx": { icon: "far fa-file-excel", color: 'green' },
|
|
948
|
+
"xlt": { icon: "far fa-file-excel", color: 'green' },
|
|
949
|
+
"xltx": { icon: "far fa-file-excel", color: 'green' },
|
|
950
|
+
"xlsm": { icon: "far fa-file-excel", color: 'green' },
|
|
951
|
+
"xltm": { icon: "far fa-file-excel", color: 'green' },
|
|
952
|
+
"gsheet": { icon: "far fa-file-excel", color: 'darkgreen' },
|
|
953
|
+
"ods": { icon: "far fa-file-excel", color: 'lightgreen' },
|
|
954
|
+
"ots": { icon: "far fa-file-excel", color: 'lightgreen' },
|
|
955
|
+
"ppt": { icon: "far fa-file-powerpoint", color: '#e64b30' },
|
|
956
|
+
"pptx": { icon: "far fa-file-powerpoint", color: '#e64b30' },
|
|
957
|
+
"pptm": { icon: "far fa-file-powerpoint", color: '#e64b30' },
|
|
958
|
+
"pptm2": { icon: "far fa-file-powerpoint", color: '#e64b30' },
|
|
959
|
+
"pps": { icon: "far fa-file-powerpoint", color: '#e64b30' },
|
|
960
|
+
"ppsx": { icon: "far fa-file-powerpoint", color: '#e64b30' },
|
|
961
|
+
"ppsm": { icon: "far fa-file-powerpoint", color: '#e64b30' },
|
|
962
|
+
"pot": { icon: "far fa-file-powerpoint", color: '#e64b30' },
|
|
963
|
+
"potx": { icon: "far fa-file-powerpoint", color: '#e64b30' },
|
|
964
|
+
"potm": { icon: "far fa-file-powerpoint", color: '#e64b30' },
|
|
965
|
+
"odp": { icon: "far fa-file-powerpoint", color: 'red' },
|
|
966
|
+
"otp": { icon: "far fa-file-powerpoint", color: 'red' },
|
|
967
|
+
"gslides": { icon: "far fa-file-powerpoint", color: 'orange' },
|
|
968
|
+
"pdf": { icon: "far fa-file-pdf", color: '#ec2e2e' },
|
|
969
|
+
"jpg": { icon: "far fa-file-image", color: '#4545bf' },
|
|
970
|
+
"jpeg": { icon: "far fa-file-image", color: '#4545bf' },
|
|
971
|
+
"bmp": { icon: "far fa-file-image", color: '#4545bf' },
|
|
972
|
+
"tiff": { icon: "far fa-file-image", color: '#4545bf' },
|
|
973
|
+
"tif": { icon: "far fa-file-image", color: '#4545bf' },
|
|
974
|
+
"gif": { icon: "far fa-file-image", color: '#4545bf' },
|
|
975
|
+
"png": { icon: "far fa-file-image", color: '#4545bf' },
|
|
976
|
+
"mp4": { icon: "far fa-file-video", color: '#4545bf' },
|
|
977
|
+
"flv": { icon: "far fa-file-video", color: '#4545bf' },
|
|
978
|
+
"swf": { icon: "far fa-file-video", color: '#4545bf' },
|
|
979
|
+
"mts": { icon: "far fa-file-video", color: '#4545bf' },
|
|
980
|
+
"divx": { icon: "far fa-file-video", color: '#4545bf' },
|
|
981
|
+
"wmv": { icon: "far fa-file-video", color: '#4545bf' },
|
|
982
|
+
"avi": { icon: "far fa-file-video", color: '#4545bf' },
|
|
983
|
+
"mov": { icon: "far fa-file-video", color: '#4545bf' },
|
|
984
|
+
"mpg": { icon: "far fa-file-video", color: '#4545bf' },
|
|
985
|
+
"mpeg": { icon: "far fa-file-video", color: '#4545bf' },
|
|
986
|
+
"asf": { icon: "far fa-file-video", color: '#4545bf' },
|
|
987
|
+
"rm": { icon: "far fa-file-video", color: '#4545bf' },
|
|
988
|
+
"mp3": { icon: "far fa-file-audio", color: 'lightblue' },
|
|
989
|
+
"wav": { icon: "far fa-file-audio", color: 'lightblue' },
|
|
990
|
+
"ogg": { icon: "far fa-file-audio", color: 'lightblue' },
|
|
991
|
+
"wma": { icon: "far fa-file-audio", color: 'lightblue' },
|
|
992
|
+
"aac": { icon: "far fa-file-audio", color: 'lightblue' },
|
|
993
|
+
"m3u": { icon: "far fa-file-audio", color: 'lightblue' },
|
|
994
|
+
"txt": { icon: "far fa-file-alt", color: '#202020' },
|
|
995
|
+
"text": { icon: "far fa-file-alt", color: '#202020' },
|
|
996
|
+
"xml": { icon: "far fa-file-code", color: '#4545bf' },
|
|
997
|
+
"cs": { icon: "far fa-file-code", color: '#4545bf' },
|
|
998
|
+
"java": { icon: "far fa-file-code", color: '#4545bf' },
|
|
999
|
+
"cpp": { icon: "far fa-file-code", color: '#4545bf' },
|
|
1000
|
+
"c": { icon: "far fa-file-code", color: '#4545bf' },
|
|
1001
|
+
"h": { icon: "far fa-file-code", color: '#4545bf' },
|
|
1002
|
+
"hpp": { icon: "far fa-file-code", color: '#4545bf' },
|
|
1003
|
+
"js": { icon: "far fa-file-code", color: '#4545bf' },
|
|
1004
|
+
"ts": { icon: "far fa-file-code", color: '#4545bf' },
|
|
1005
|
+
"zip": { icon: "far fa-file-archive", color: 'yellow' },
|
|
1006
|
+
"7zip": { icon: "far fa-file-archive", color: 'yellow' },
|
|
1007
|
+
"7z": { icon: "far fa-file-archive", color: 'yellow' },
|
|
1008
|
+
"rar": { icon: "far fa-file-archive", color: 'yellow' },
|
|
1009
|
+
"gz": { icon: "far fa-file-archive", color: 'yellow' },
|
|
1010
|
+
"notes": { icon: "fas fa-file-invoice", color: 'orange' },
|
|
1011
|
+
"quickr": { icon: "fas fa-file-invoice", color: 'orange' },
|
|
1012
|
+
"email": { icon: "far fa-envelope", color: 'black' },
|
|
1013
|
+
"mail": { icon: "far fa-envelope", color: 'black' },
|
|
1014
|
+
"msg": { icon: "far fa-envelope", color: 'black' },
|
|
1015
|
+
"mdb": { icon: "far fa-database", color: 'purple' },
|
|
1016
|
+
"odb": { icon: "far fa-database", color: 'darkred' },
|
|
1017
|
+
"otb": { icon: "far fa-database", color: 'darkred' },
|
|
1018
|
+
"xsn": { icon: "fas fa-file-excel", color: 'purple' },
|
|
1019
|
+
"gform": { icon: "fas fa-file-excel", color: 'purple' },
|
|
1020
|
+
"one": { icon: "far fa-book", color: 'purple' },
|
|
1021
|
+
"odf": { icon: "fas fa-file-medical-alt", color: 'grey' },
|
|
1022
|
+
"otf": { icon: "fas fa-file-medical-alt", color: 'grey' },
|
|
1023
|
+
"vsdx": { icon: "far fa-object-group", color: 'purple' },
|
|
1024
|
+
"vsx": { icon: "far fa-object-group", color: 'purple' },
|
|
1025
|
+
"vtx": { icon: "far fa-object-group", color: 'purple' },
|
|
1026
|
+
"vdx": { icon: "far fa-object-group", color: 'purple' },
|
|
1027
|
+
"vssx": { icon: "far fa-object-group", color: 'purple' },
|
|
1028
|
+
"vstx": { icon: "far fa-object-group", color: 'purple' },
|
|
1029
|
+
"vsdm": { icon: "far fa-object-group", color: 'purple' },
|
|
1030
|
+
"vssm": { icon: "far fa-object-group", color: 'purple' },
|
|
1031
|
+
"vstm": { icon: "far fa-object-group", color: 'purple' },
|
|
1032
|
+
"vdw": { icon: "far fa-object-group", color: 'purple' },
|
|
1033
|
+
"vsd": { icon: "far fa-object-group", color: 'purple' },
|
|
1034
|
+
"vss": { icon: "far fa-object-group", color: 'purple' },
|
|
1035
|
+
"vst": { icon: "far fa-object-group", color: 'purple' },
|
|
1036
|
+
"odg": { icon: "far fa-object-group", color: 'orange' },
|
|
1037
|
+
"otg": { icon: "far fa-object-group", color: 'orange' },
|
|
1038
|
+
"gdraw": { icon: "far fa-object-group", color: 'red' },
|
|
1039
|
+
"pub": { icon: "far fa-object-group", color: 'darkgreen' },
|
|
1040
|
+
"ldap": { icon: "far fa-users", color: 'brown' },
|
|
1041
|
+
"ad": { icon: "far fa-users", color: 'brown' },
|
|
1042
|
+
"mmp": { icon: "fas fa-file-medical", color: 'grey' },
|
|
1043
|
+
"mppx": { icon: "fas fa-file-medical", color: 'grey' },
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
class FormatIconComponent {
|
|
1047
|
+
constructor() {
|
|
1048
|
+
this._formatIcons = defaultFormatIcons;
|
|
1049
|
+
}
|
|
1050
|
+
ngOnChanges() {
|
|
1051
|
+
const icon = this.extension ? this._formatIcons[this.extension] : undefined;
|
|
1052
|
+
this.icon = icon?.icon || this._formatIcons.file.icon;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
FormatIconComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: FormatIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1056
|
+
FormatIconComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: FormatIconComponent, isStandalone: true, selector: "sq-format-icon", inputs: { extension: "extension" }, usesOnChanges: true, ngImport: i0, template: "<span *ngIf=\"icon\" class=\"{{icon}}\"></span>", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
1057
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: FormatIconComponent, decorators: [{
|
|
1058
|
+
type: Component,
|
|
1059
|
+
args: [{ selector: 'sq-format-icon', encapsulation: ViewEncapsulation.None, standalone: true, imports: [CommonModule], template: "<span *ngIf=\"icon\" class=\"{{icon}}\"></span>" }]
|
|
1060
|
+
}], propDecorators: { extension: [{
|
|
1061
|
+
type: Input
|
|
1062
|
+
}] } });
|
|
1063
|
+
|
|
1064
|
+
class ChatReferenceComponent {
|
|
1065
|
+
constructor() {
|
|
1066
|
+
this.openDocument = new EventEmitter();
|
|
1067
|
+
this.openPreview = new EventEmitter();
|
|
1068
|
+
}
|
|
1069
|
+
get parts() {
|
|
1070
|
+
if (!this.attachment)
|
|
1071
|
+
return [];
|
|
1072
|
+
return this.attachment.parts.filter(part => (!this.partId || part.partId === this.partId) && !!part.text);
|
|
1073
|
+
}
|
|
1074
|
+
expandAttachment() {
|
|
1075
|
+
if (this.partId)
|
|
1076
|
+
return;
|
|
1077
|
+
this.attachment['$expanded'] = !this.attachment['$expanded'];
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
ChatReferenceComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatReferenceComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1081
|
+
ChatReferenceComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: ChatReferenceComponent, isStandalone: true, selector: "sq-chat-reference", inputs: { reference: "reference", attachment: "attachment", partId: "partId" }, outputs: { openDocument: "openDocument", openPreview: "openPreview" }, ngImport: i0, template: "<div [class.reference-tooltip]=\"!!partId\">\n <div class=\"reference-data\" [class.expanded]=\"attachment['$expanded'] || !!partId\">\n <span class=\"reference me-1\">{{reference}}</span>\n <sq-format-icon [extension]=\"attachment.record.fileext\"></sq-format-icon>\n <a [id]=\"'attachment-'+attachment.recordId\" (click)=\"expandAttachment()\">\n {{attachment.record.title}}\n </a>\n <i class=\"fas fa-eye\" (click)=\"openPreview.emit(attachment)\" [sqTooltip]=\"!partId ? 'Preview document' : ''\"></i>\n <i class=\"fas fa-arrow-up-right-from-square\" *ngIf=\"attachment.record.url1 || attachment.record.originalUrl\"\n (click)=\"openDocument.emit(attachment.record)\" [sqTooltip]=\"!partId ? 'Open document' : ''\"></i>\n </div>\n <div class=\"reference-passages\" *ngIf=\"!!partId || (attachment['$expanded']) && parts.length\">\n <div class=\"reference-passage\" *ngFor=\"let part of parts\">\n <span class=\"reference me-1\">{{reference}}.{{part.partId}}</span>\n <span [innerHTML]=\"part.text\"></span>\n </div>\n </div>\n</div>", styles: [".ast-primary{color:var(--ast-primary-color, #005DA7);background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover{background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover:hover{color:var(--ast-primary-color, #005DA7)}.ast-secondary{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-secondary-bg, #FFF8F1)}.ast-btn{border:0;text-align:left;padding-top:.5rem;padding-bottom:.5rem;display:flex;align-items:center}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference,:host ::ng-deep .attachment .reference{position:relative;bottom:var(--ast-reference-bottom, .3em);font-weight:var(--ast-reference-font-weight, bold);padding:var(--ast-reference-padding, 0 .2em);margin:var(--ast-reference-margin, 0 .1em);border-radius:var(--ast-reference-border-radius, .2em);background-color:var(--ast-reference-background-color, lightblue);color:var(--ast-reference-color, black)}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference{font-size:var(--ast-reference-message-font-size, .7em)}:host ::ng-deep .attachment .reference{font-size:var(--ast-reference-attachment-font-size, 13px)}:host{display:block}:host.expanded,:host:hover{background-color:#fff}.reference-data{display:flex;flex-direction:row;align-items:baseline;padding:var(--ast-size-1, .25rem)}.reference-data a{color:var(--ast-secondary-color, #FF732E);flex-grow:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:var(--font-weight-bold, 500);cursor:pointer}.reference-data i{padding:var(--ast-size-1, .25rem);margin-left:var(--ast-size-1, .25rem);cursor:pointer;color:#000}.reference-data i.active{color:#fff;background-color:var(--ast-secondary-color, #FF732E)}.reference-data:not(.expanded) i{opacity:0}.reference-data:not(.expanded):hover i{opacity:1}.reference-passages{white-space:normal;font-style:italic;font-weight:400;padding:1rem 0;color:#000}.reference-passages .reference-passage{display:flex;align-items:baseline;padding-left:2.5rem;padding-right:1rem;word-wrap:break-word}.reference-passages .reference-passage+.reference-passage{padding-top:1rem}.reference-passages .reference-passage .reference{margin-right:var(--ast-size-2, .5rem)}sq-format-icon{margin-left:var(--ast-size-1, .25rem);margin-right:var(--ast-size-2, .5rem);color:var(--ast-secondary-color, #FF732E)}.reference-tooltip{max-width:600px!important;box-shadow:0 .5rem 1rem #00000026;padding:.5rem;font-size:.875rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: UtilsModule }, { kind: "directive", type: i2$1.TooltipDirective, selector: "[sqTooltip]", inputs: ["sqTooltip", "sqTooltipData", "sqTooltipTemplate", "placement", "fallbackPlacements", "delay", "hoverableTooltip", "tooltipClass"] }, { kind: "component", type: FormatIconComponent, selector: "sq-format-icon", inputs: ["extension"] }] });
|
|
1082
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatReferenceComponent, decorators: [{
|
|
1083
|
+
type: Component,
|
|
1084
|
+
args: [{ selector: 'sq-chat-reference', standalone: true, imports: [CommonModule, UtilsModule, FormatIconComponent], template: "<div [class.reference-tooltip]=\"!!partId\">\n <div class=\"reference-data\" [class.expanded]=\"attachment['$expanded'] || !!partId\">\n <span class=\"reference me-1\">{{reference}}</span>\n <sq-format-icon [extension]=\"attachment.record.fileext\"></sq-format-icon>\n <a [id]=\"'attachment-'+attachment.recordId\" (click)=\"expandAttachment()\">\n {{attachment.record.title}}\n </a>\n <i class=\"fas fa-eye\" (click)=\"openPreview.emit(attachment)\" [sqTooltip]=\"!partId ? 'Preview document' : ''\"></i>\n <i class=\"fas fa-arrow-up-right-from-square\" *ngIf=\"attachment.record.url1 || attachment.record.originalUrl\"\n (click)=\"openDocument.emit(attachment.record)\" [sqTooltip]=\"!partId ? 'Open document' : ''\"></i>\n </div>\n <div class=\"reference-passages\" *ngIf=\"!!partId || (attachment['$expanded']) && parts.length\">\n <div class=\"reference-passage\" *ngFor=\"let part of parts\">\n <span class=\"reference me-1\">{{reference}}.{{part.partId}}</span>\n <span [innerHTML]=\"part.text\"></span>\n </div>\n </div>\n</div>", styles: [".ast-primary{color:var(--ast-primary-color, #005DA7);background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover{background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover:hover{color:var(--ast-primary-color, #005DA7)}.ast-secondary{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-secondary-bg, #FFF8F1)}.ast-btn{border:0;text-align:left;padding-top:.5rem;padding-bottom:.5rem;display:flex;align-items:center}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference,:host ::ng-deep .attachment .reference{position:relative;bottom:var(--ast-reference-bottom, .3em);font-weight:var(--ast-reference-font-weight, bold);padding:var(--ast-reference-padding, 0 .2em);margin:var(--ast-reference-margin, 0 .1em);border-radius:var(--ast-reference-border-radius, .2em);background-color:var(--ast-reference-background-color, lightblue);color:var(--ast-reference-color, black)}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference{font-size:var(--ast-reference-message-font-size, .7em)}:host ::ng-deep .attachment .reference{font-size:var(--ast-reference-attachment-font-size, 13px)}:host{display:block}:host.expanded,:host:hover{background-color:#fff}.reference-data{display:flex;flex-direction:row;align-items:baseline;padding:var(--ast-size-1, .25rem)}.reference-data a{color:var(--ast-secondary-color, #FF732E);flex-grow:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:var(--font-weight-bold, 500);cursor:pointer}.reference-data i{padding:var(--ast-size-1, .25rem);margin-left:var(--ast-size-1, .25rem);cursor:pointer;color:#000}.reference-data i.active{color:#fff;background-color:var(--ast-secondary-color, #FF732E)}.reference-data:not(.expanded) i{opacity:0}.reference-data:not(.expanded):hover i{opacity:1}.reference-passages{white-space:normal;font-style:italic;font-weight:400;padding:1rem 0;color:#000}.reference-passages .reference-passage{display:flex;align-items:baseline;padding-left:2.5rem;padding-right:1rem;word-wrap:break-word}.reference-passages .reference-passage+.reference-passage{padding-top:1rem}.reference-passages .reference-passage .reference{margin-right:var(--ast-size-2, .5rem)}sq-format-icon{margin-left:var(--ast-size-1, .25rem);margin-right:var(--ast-size-2, .5rem);color:var(--ast-secondary-color, #FF732E)}.reference-tooltip{max-width:600px!important;box-shadow:0 .5rem 1rem #00000026;padding:.5rem;font-size:.875rem}\n"] }]
|
|
1085
|
+
}], propDecorators: { reference: [{
|
|
1086
|
+
type: Input
|
|
1087
|
+
}], attachment: [{
|
|
1088
|
+
type: Input
|
|
1089
|
+
}], partId: [{
|
|
1090
|
+
type: Input
|
|
1091
|
+
}], openDocument: [{
|
|
1092
|
+
type: Output
|
|
1093
|
+
}], openPreview: [{
|
|
1094
|
+
type: Output
|
|
1095
|
+
}] } });
|
|
1096
|
+
|
|
1097
|
+
class ChatMessageComponent {
|
|
1098
|
+
constructor(searchService, ui, principalService, cdr, el) {
|
|
1099
|
+
this.searchService = searchService;
|
|
1100
|
+
this.ui = ui;
|
|
1101
|
+
this.principalService = principalService;
|
|
1102
|
+
this.cdr = cdr;
|
|
1103
|
+
this.el = el;
|
|
1104
|
+
this.canEdit = false;
|
|
1105
|
+
this.canRegenerate = false;
|
|
1106
|
+
this.canCopy = false;
|
|
1107
|
+
this.referenceClicked = new EventEmitter();
|
|
1108
|
+
this.edit = new EventEmitter();
|
|
1109
|
+
this.regenerate = new EventEmitter();
|
|
1110
|
+
this.openPreview = new EventEmitter();
|
|
1111
|
+
this.references = [];
|
|
1112
|
+
this.referenceMap = new Map();
|
|
1113
|
+
this.showReferences = true;
|
|
1114
|
+
/**
|
|
1115
|
+
* This Unified plugin looks a text nodes and replaces any reference in the
|
|
1116
|
+
* form [1], [2.3], etc. with custom nodes of type "chat-reference".
|
|
1117
|
+
*/
|
|
1118
|
+
this.referencePlugin = (tree) => {
|
|
1119
|
+
const references = new Set();
|
|
1120
|
+
// Visit all text nodes
|
|
1121
|
+
visit(tree, "text", (node, index, parent) => {
|
|
1122
|
+
let text = node.value;
|
|
1123
|
+
text = this.reformatReferences(text);
|
|
1124
|
+
const matches = this.getReferenceMatches(text);
|
|
1125
|
+
// Quit if no references were found
|
|
1126
|
+
if (matches.length === 0) {
|
|
1127
|
+
return CONTINUE;
|
|
1128
|
+
}
|
|
1129
|
+
const nodes = [];
|
|
1130
|
+
for (let match of matches) {
|
|
1131
|
+
const refId = match[1].trim();
|
|
1132
|
+
const [ref] = refId.split(".");
|
|
1133
|
+
// We find a valid reference in the text
|
|
1134
|
+
if (!isNaN(+ref)) {
|
|
1135
|
+
references.add(+ref); // Add it to the set of used references
|
|
1136
|
+
// If needed, insert a text node before the reference
|
|
1137
|
+
const current = nodes.at(-1) ?? { end: 0 };
|
|
1138
|
+
if (match.index > current.end) {
|
|
1139
|
+
nodes.push({ type: "text", value: text.substring(current.end, match.index), end: match.index });
|
|
1140
|
+
}
|
|
1141
|
+
// Add a custom reference node
|
|
1142
|
+
nodes.push({ type: "chat-reference", refId, end: match.index + match[0].length });
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
// Quit if no references were found
|
|
1146
|
+
if (nodes.length === 0) {
|
|
1147
|
+
return CONTINUE;
|
|
1148
|
+
}
|
|
1149
|
+
if (nodes.at(-1).end < text.length) {
|
|
1150
|
+
nodes.push({ type: "text", value: text.substring(nodes.at(-1).end, text.length), end: text.length });
|
|
1151
|
+
}
|
|
1152
|
+
// Delete the current text node from the parent and replace it with the new nodes
|
|
1153
|
+
parent.children.splice(index, 1, ...nodes);
|
|
1154
|
+
return index + nodes.length; // Visit the next node after the inserted ones
|
|
1155
|
+
});
|
|
1156
|
+
if (references.size > 0) {
|
|
1157
|
+
this.references = Array.from(references.values())
|
|
1158
|
+
.sort((a, b) => a - b)
|
|
1159
|
+
.map(r => '' + r);
|
|
1160
|
+
this.cdr.detectChanges();
|
|
1161
|
+
}
|
|
1162
|
+
return tree;
|
|
1163
|
+
};
|
|
1164
|
+
this.placeholderPlugin = (tree) => {
|
|
1165
|
+
visit(tree, "text", (node, index, parent) => {
|
|
1166
|
+
parent.children.push({ type: "streaming-placeholder" });
|
|
1167
|
+
return EXIT;
|
|
1168
|
+
}, true);
|
|
1169
|
+
return tree;
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
get name() {
|
|
1173
|
+
return !this.principalService.principal ? ''
|
|
1174
|
+
: this.principalService.principal['fullName'] || this.principalService.principal.name;
|
|
1175
|
+
}
|
|
1176
|
+
ngOnChanges(changes) {
|
|
1177
|
+
if (changes.streaming) {
|
|
1178
|
+
this.collapseProgress = !this.streaming;
|
|
1179
|
+
}
|
|
1180
|
+
if (changes.message && this.message.role === "assistant") {
|
|
1181
|
+
this.references = [];
|
|
1182
|
+
this.referenceMap.clear();
|
|
1183
|
+
for (let m of this.conversation) {
|
|
1184
|
+
if (m.additionalProperties.$attachment) {
|
|
1185
|
+
for (const attachment of m.additionalProperties.$attachment) {
|
|
1186
|
+
this.referenceMap.set('' + attachment.contextId, { ...attachment, $partId: attachment.parts?.[0].partId });
|
|
1187
|
+
for (let i = 0; i < attachment.parts.length; i++) {
|
|
1188
|
+
const refId = `${attachment.contextId}.${attachment.parts[i].partId}`;
|
|
1189
|
+
this.referenceMap.set(refId, { ...attachment, $partId: attachment.parts[i].partId });
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
this.processor = unified()
|
|
1195
|
+
.use(remarkParse)
|
|
1196
|
+
.use(remarkGfm)
|
|
1197
|
+
.use(() => this.referencePlugin);
|
|
1198
|
+
if (this.streaming) {
|
|
1199
|
+
this.processor = this.processor.use(() => this.placeholderPlugin);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
ngAfterViewInit() {
|
|
1204
|
+
Prism?.highlightAllUnder?.(this.el.nativeElement);
|
|
1205
|
+
}
|
|
1206
|
+
openDocument(record) {
|
|
1207
|
+
const url = record.url1 || record.originalUrl;
|
|
1208
|
+
if (url) {
|
|
1209
|
+
// Open the URL in a new tab
|
|
1210
|
+
window.open(url, '_blank');
|
|
1211
|
+
}
|
|
1212
|
+
this.referenceClicked.emit(record);
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Reformat [ids: 12.2, 42.5] to [12.2][42.5]
|
|
1216
|
+
*/
|
|
1217
|
+
reformatReferences(content) {
|
|
1218
|
+
return content.replace(/\[(?:ids?:?\s*)?(?:documents?:?\s*)?(\s*(?:,?\s*\d+(?:\.\d+)?(?:\.part)?\s*)+)\]/g, (str, match) => `[${match.replace(/\.part/g, "").split(',').join("] [")}]`);
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Match all references in a given message
|
|
1222
|
+
*/
|
|
1223
|
+
getReferenceMatches(content) {
|
|
1224
|
+
return Array.from(content.matchAll(/\[(\s*\d+(?:\.\d+)?\s*)\]/g));
|
|
1225
|
+
}
|
|
1226
|
+
copyToClipboard(text) {
|
|
1227
|
+
this.ui.copyToClipboard(text);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
ChatMessageComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatMessageComponent, deps: [{ token: i1$1.SearchService }, { token: i2$1.UIService }, { token: i3.PrincipalWebService }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1231
|
+
ChatMessageComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: ChatMessageComponent, isStandalone: true, selector: "sq-chat-message", inputs: { message: "message", conversation: "conversation", assistantIcon: "assistantIcon", streaming: "streaming", canEdit: "canEdit", canRegenerate: "canRegenerate", canCopy: "canCopy" }, outputs: { referenceClicked: "referenceClicked", edit: "edit", regenerate: "regenerate", openPreview: "openPreview" }, usesOnChanges: true, ngImport: i0, template: "<!-- Message icon -->\n<span class=\"message-icon\" [title]=\"message.role\">\n <i [ngClass]=\"assistantIcon\" [style.--sq-size.px]=\"24\" *ngIf=\"message.role === 'assistant'\"></i>\n <sq-initials-avatar [fullName]=\"name\" *ngIf=\"message.role !== 'assistant'\"></sq-initials-avatar>\n</span>\n\n<!-- Message body -->\n<div class=\"flex-grow-1\" style=\"min-width: 0;\" [ngClass]=\"'message-'+message.role\">\n\n <div *ngIf=\"message.additionalProperties.$progress as progress\" class=\"small ms-3 mb-2\">\n <a href=\"#\" (click)=\"collapseProgress = !collapseProgress; false\" class=\"text-muted\">\n View progress\n <i class=\"fas fa-chevron-{{collapseProgress? 'right' : 'down'}}\"></i>\n </a>\n <sq-collapse [collapsed]=\"collapseProgress\">\n <ng-template>\n <ul class=\"list-unstyled\">\n <li *ngFor=\"let step of progress\" [title]=\"step.time ?? ''\">\n <i class=\"fas fa-fw fa-check text-success\" *ngIf=\"step.done\"></i>\n <i class=\"fas fa-fw fa-spin fa-circle-notch\" *ngIf=\"!step.done\"></i>\n <span class=\"ms-2 fw-bold\">{{step.title}}</span>\n <span *ngIf=\"step.content\">: {{step.content}}</span>\n </li>\n </ul>\n </ng-template>\n </sq-collapse>\n </div>\n\n <div class=\"message-content\" *ngIf=\"message.content\">\n\n <remark *ngIf=\"message.role === 'assistant'\" [markdown]=\"message.content\" [processor]=\"processor\">\n\n <ng-template remarkTemplate=\"chat-reference\" let-ref>\n\n <a *ngIf=\"referenceMap.get(ref.refId) as attachment; else staticRefTpl\"\n (click)=\"openDocument(attachment.record)\"\n class=\"reference\"\n [sqTooltip]=\"attachment\"\n [sqTooltipTemplate]=\"tooltipTpl\"\n [hoverableTooltip]=\"true\"\n >{{ref.refId}}</a>\n\n <ng-template #staticRefTpl>\n <span class=\"reference\">{{ref.refId}}</span>\n </ng-template>\n\n </ng-template>\n\n <ng-template remarkTemplate=\"streaming-placeholder\">\n <span class=\"placeholder-glow\" *ngIf=\"streaming\">\n <span class=\"placeholder ms-1\"></span>\n </span>\n </ng-template>\n\n <ng-template remarkTemplate=\"code\" let-node>\n <div class=\"card mb-2\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <span>{{node.lang}}</span>\n <button class=\"btn btn-light btn-sm\" (click)=\"copyToClipboard(node.value)\"><i class=\"far fa-fw fa-clipboard\"></i> Copy code</button>\n </div>\n <pre class=\"language-{{node.lang}} my-0 rounded-0 rounded-bottom\"><code class=\"language-{{node.lang}}\">{{node.value}}</code></pre>\n </div>\n </ng-template>\n\n </remark>\n\n <p *ngIf=\"message.role === 'user'\">{{message.content}}</p>\n\n <!-- List of reference, if any -->\n <div *ngIf=\"references?.length\" class=\"references\">\n <span class=\"references-title\">References</span> <i class=\"fas references-collapse\" [class.fa-chevron-down]=\"showReferences\" [class.fa-chevron-right]=\"!showReferences\" (click)=\"showReferences=!showReferences\"></i>\n <sq-collapse [collapsed]=\"!showReferences\">\n <ng-template>\n <ul>\n <ng-container *ngFor=\"let reference of references\">\n <li *ngIf=\"referenceMap.get(reference) as attachment\" class=\"text-truncate\">\n <sq-chat-reference [class.expanded]=\"attachment.$expanded\" [attachment]=\"attachment\" [reference]=\"reference\"\n (openPreview)=\"openPreview.emit($event)\" (openDocument)=\"openDocument($event)\"></sq-chat-reference>\n </li>\n </ng-container>\n </ul>\n </ng-template>\n </sq-collapse>\n </div>\n \n </div>\n\n <!-- Edit / Regenerate floating actions -->\n <div class=\"sq-chat-message-action\">\n <button class=\"btn btn-sm\" *ngIf=\"canEdit\" sqTooltip=\"Edit message\"\n (click)=\"edit.emit(message)\">\n <i class=\"fas fa-edit\"></i>\n </button>\n <button class=\"btn btn-sm\" *ngIf=\"canCopy\" sqTooltip=\"Copy to clipboard\"\n (click)=\"copyToClipboard(message.content)\">\n <i class=\"far fa-clipboard\"></i>\n </button>\n <button class=\"btn btn-sm\" *ngIf=\"canRegenerate\" sqTooltip=\"Regenerate message\"\n (click)=\"regenerate.emit(message)\">\n <i class=\"fas fa-sync-alt\"></i>\n </button>\n </div>\n\n <ng-template #tooltipTpl let-ref>\n <sq-chat-reference class=\"expanded\"\n [attachment]=\"ref\"\n [reference]=\"ref.contextId\"\n [partId]=\"ref.$partId\"\n (openPreview)=\"openPreview.emit($event)\"\n (openDocument)=\"openDocument($event)\">\n </sq-chat-reference>\n </ng-template>\n\n</div>\n", styles: [".ast-primary{color:var(--ast-primary-color, #005DA7);background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover{background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover:hover{color:var(--ast-primary-color, #005DA7)}.ast-secondary{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-secondary-bg, #FFF8F1)}.ast-btn{border:0;text-align:left;padding-top:.5rem;padding-bottom:.5rem;display:flex;align-items:center}:host{display:flex}:host:not(:hover) .sq-chat-message-action{display:none}.message-content{padding:var(--ast-message-padding, var(--ast-size-3, .75rem));border-radius:var(--ast-message-border-radius, var(--ast-size-4, 1rem));display:inline-block;max-width:100%}.message-content .references{margin-top:var(--ast-size-5, 1.25rem)}.message-content .references ul{border-left:.2rem solid var(--ast-secondary-color, #FF732E);padding-left:var(--ast-size-5, 1.25rem);margin-top:var(--ast-size-2, .5rem)}.message-content .references .references-title{font-weight:var(--font-weight-bold, 500)}.message-content .references .references-collapse{cursor:pointer;margin-left:var(--ast-size-2, .5rem)}.message-content ::ng-deep p:last-child{margin-bottom:0}.message-content ::ng-deep .placeholder-glow .placeholder{animation-duration:.4s;width:12px;height:var(--ast-size-4, 1rem);vertical-align:text-bottom}.message-content ::ng-deep img{max-width:100%}.message-content ::ng-deep table{display:block;border:1px solid #ccc;border-collapse:collapse;margin:0;padding:0;min-width:100%;overflow-x:auto;table-layout:fixed}.message-content ::ng-deep table tr{background-color:#f8f8f8;border:1px solid #ddd;padding:.35em}.message-content ::ng-deep table th,.message-content ::ng-deep table td{padding:.625em;text-align:center}.message-content ::ng-deep table th{font-size:.85em;letter-spacing:.1em;text-transform:uppercase}.message-assistant .message-content{background:var(--ast-secondary-bg, #FFF8F1)}.message-user .message-content{background:var(--ast-primary-bg, #f2f8fe);font-weight:var(--ast-user-font-weight, var(--font-weight-bold, 500))}.sq-chat-message-action{position:absolute;right:calc(0rem - var(--ast-size-2, .5rem));top:var(--ast-size-2, .5rem);display:flex;flex-direction:column}.message-icon{margin-top:var(--ast-size-3, .75rem);margin-right:var(--ast-size-4, 1rem)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: UtilsModule }, { kind: "directive", type: i2$1.TooltipDirective, selector: "[sqTooltip]", inputs: ["sqTooltip", "sqTooltipData", "sqTooltipTemplate", "placement", "fallbackPlacements", "delay", "hoverableTooltip", "tooltipClass"] }, { kind: "ngmodule", type: CollapseModule }, { kind: "component", type: i5.Collapse, selector: "sq-collapse", inputs: ["collapsed"] }, { kind: "ngmodule", type: RemarkModule }, { kind: "component", type: i6.RemarkComponent, selector: "remark", inputs: ["markdown", "processor", "debug"] }, { kind: "directive", type: i6.RemarkTemplateDirective, selector: "[remarkTemplate]", inputs: ["remarkTemplate"] }, { kind: "component", type: InitialsAvatarComponent, selector: "sq-initials-avatar", inputs: ["fullName", "size"] }, { kind: "component", type: ChatReferenceComponent, selector: "sq-chat-reference", inputs: ["reference", "attachment", "partId"], outputs: ["openDocument", "openPreview"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1232
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatMessageComponent, decorators: [{
|
|
1233
|
+
type: Component,
|
|
1234
|
+
args: [{ selector: "sq-chat-message", changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, UtilsModule, CollapseModule, RemarkModule,
|
|
1235
|
+
InitialsAvatarComponent, ChatReferenceComponent], template: "<!-- Message icon -->\n<span class=\"message-icon\" [title]=\"message.role\">\n <i [ngClass]=\"assistantIcon\" [style.--sq-size.px]=\"24\" *ngIf=\"message.role === 'assistant'\"></i>\n <sq-initials-avatar [fullName]=\"name\" *ngIf=\"message.role !== 'assistant'\"></sq-initials-avatar>\n</span>\n\n<!-- Message body -->\n<div class=\"flex-grow-1\" style=\"min-width: 0;\" [ngClass]=\"'message-'+message.role\">\n\n <div *ngIf=\"message.additionalProperties.$progress as progress\" class=\"small ms-3 mb-2\">\n <a href=\"#\" (click)=\"collapseProgress = !collapseProgress; false\" class=\"text-muted\">\n View progress\n <i class=\"fas fa-chevron-{{collapseProgress? 'right' : 'down'}}\"></i>\n </a>\n <sq-collapse [collapsed]=\"collapseProgress\">\n <ng-template>\n <ul class=\"list-unstyled\">\n <li *ngFor=\"let step of progress\" [title]=\"step.time ?? ''\">\n <i class=\"fas fa-fw fa-check text-success\" *ngIf=\"step.done\"></i>\n <i class=\"fas fa-fw fa-spin fa-circle-notch\" *ngIf=\"!step.done\"></i>\n <span class=\"ms-2 fw-bold\">{{step.title}}</span>\n <span *ngIf=\"step.content\">: {{step.content}}</span>\n </li>\n </ul>\n </ng-template>\n </sq-collapse>\n </div>\n\n <div class=\"message-content\" *ngIf=\"message.content\">\n\n <remark *ngIf=\"message.role === 'assistant'\" [markdown]=\"message.content\" [processor]=\"processor\">\n\n <ng-template remarkTemplate=\"chat-reference\" let-ref>\n\n <a *ngIf=\"referenceMap.get(ref.refId) as attachment; else staticRefTpl\"\n (click)=\"openDocument(attachment.record)\"\n class=\"reference\"\n [sqTooltip]=\"attachment\"\n [sqTooltipTemplate]=\"tooltipTpl\"\n [hoverableTooltip]=\"true\"\n >{{ref.refId}}</a>\n\n <ng-template #staticRefTpl>\n <span class=\"reference\">{{ref.refId}}</span>\n </ng-template>\n\n </ng-template>\n\n <ng-template remarkTemplate=\"streaming-placeholder\">\n <span class=\"placeholder-glow\" *ngIf=\"streaming\">\n <span class=\"placeholder ms-1\"></span>\n </span>\n </ng-template>\n\n <ng-template remarkTemplate=\"code\" let-node>\n <div class=\"card mb-2\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <span>{{node.lang}}</span>\n <button class=\"btn btn-light btn-sm\" (click)=\"copyToClipboard(node.value)\"><i class=\"far fa-fw fa-clipboard\"></i> Copy code</button>\n </div>\n <pre class=\"language-{{node.lang}} my-0 rounded-0 rounded-bottom\"><code class=\"language-{{node.lang}}\">{{node.value}}</code></pre>\n </div>\n </ng-template>\n\n </remark>\n\n <p *ngIf=\"message.role === 'user'\">{{message.content}}</p>\n\n <!-- List of reference, if any -->\n <div *ngIf=\"references?.length\" class=\"references\">\n <span class=\"references-title\">References</span> <i class=\"fas references-collapse\" [class.fa-chevron-down]=\"showReferences\" [class.fa-chevron-right]=\"!showReferences\" (click)=\"showReferences=!showReferences\"></i>\n <sq-collapse [collapsed]=\"!showReferences\">\n <ng-template>\n <ul>\n <ng-container *ngFor=\"let reference of references\">\n <li *ngIf=\"referenceMap.get(reference) as attachment\" class=\"text-truncate\">\n <sq-chat-reference [class.expanded]=\"attachment.$expanded\" [attachment]=\"attachment\" [reference]=\"reference\"\n (openPreview)=\"openPreview.emit($event)\" (openDocument)=\"openDocument($event)\"></sq-chat-reference>\n </li>\n </ng-container>\n </ul>\n </ng-template>\n </sq-collapse>\n </div>\n \n </div>\n\n <!-- Edit / Regenerate floating actions -->\n <div class=\"sq-chat-message-action\">\n <button class=\"btn btn-sm\" *ngIf=\"canEdit\" sqTooltip=\"Edit message\"\n (click)=\"edit.emit(message)\">\n <i class=\"fas fa-edit\"></i>\n </button>\n <button class=\"btn btn-sm\" *ngIf=\"canCopy\" sqTooltip=\"Copy to clipboard\"\n (click)=\"copyToClipboard(message.content)\">\n <i class=\"far fa-clipboard\"></i>\n </button>\n <button class=\"btn btn-sm\" *ngIf=\"canRegenerate\" sqTooltip=\"Regenerate message\"\n (click)=\"regenerate.emit(message)\">\n <i class=\"fas fa-sync-alt\"></i>\n </button>\n </div>\n\n <ng-template #tooltipTpl let-ref>\n <sq-chat-reference class=\"expanded\"\n [attachment]=\"ref\"\n [reference]=\"ref.contextId\"\n [partId]=\"ref.$partId\"\n (openPreview)=\"openPreview.emit($event)\"\n (openDocument)=\"openDocument($event)\">\n </sq-chat-reference>\n </ng-template>\n\n</div>\n", styles: [".ast-primary{color:var(--ast-primary-color, #005DA7);background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover{background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover:hover{color:var(--ast-primary-color, #005DA7)}.ast-secondary{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-secondary-bg, #FFF8F1)}.ast-btn{border:0;text-align:left;padding-top:.5rem;padding-bottom:.5rem;display:flex;align-items:center}:host{display:flex}:host:not(:hover) .sq-chat-message-action{display:none}.message-content{padding:var(--ast-message-padding, var(--ast-size-3, .75rem));border-radius:var(--ast-message-border-radius, var(--ast-size-4, 1rem));display:inline-block;max-width:100%}.message-content .references{margin-top:var(--ast-size-5, 1.25rem)}.message-content .references ul{border-left:.2rem solid var(--ast-secondary-color, #FF732E);padding-left:var(--ast-size-5, 1.25rem);margin-top:var(--ast-size-2, .5rem)}.message-content .references .references-title{font-weight:var(--font-weight-bold, 500)}.message-content .references .references-collapse{cursor:pointer;margin-left:var(--ast-size-2, .5rem)}.message-content ::ng-deep p:last-child{margin-bottom:0}.message-content ::ng-deep .placeholder-glow .placeholder{animation-duration:.4s;width:12px;height:var(--ast-size-4, 1rem);vertical-align:text-bottom}.message-content ::ng-deep img{max-width:100%}.message-content ::ng-deep table{display:block;border:1px solid #ccc;border-collapse:collapse;margin:0;padding:0;min-width:100%;overflow-x:auto;table-layout:fixed}.message-content ::ng-deep table tr{background-color:#f8f8f8;border:1px solid #ddd;padding:.35em}.message-content ::ng-deep table th,.message-content ::ng-deep table td{padding:.625em;text-align:center}.message-content ::ng-deep table th{font-size:.85em;letter-spacing:.1em;text-transform:uppercase}.message-assistant .message-content{background:var(--ast-secondary-bg, #FFF8F1)}.message-user .message-content{background:var(--ast-primary-bg, #f2f8fe);font-weight:var(--ast-user-font-weight, var(--font-weight-bold, 500))}.sq-chat-message-action{position:absolute;right:calc(0rem - var(--ast-size-2, .5rem));top:var(--ast-size-2, .5rem);display:flex;flex-direction:column}.message-icon{margin-top:var(--ast-size-3, .75rem);margin-right:var(--ast-size-4, 1rem)}\n"] }]
|
|
1236
|
+
}], ctorParameters: function () { return [{ type: i1$1.SearchService }, { type: i2$1.UIService }, { type: i3.PrincipalWebService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }]; }, propDecorators: { message: [{
|
|
1237
|
+
type: Input
|
|
1238
|
+
}], conversation: [{
|
|
1239
|
+
type: Input
|
|
1240
|
+
}], assistantIcon: [{
|
|
1241
|
+
type: Input
|
|
1242
|
+
}], streaming: [{
|
|
1243
|
+
type: Input
|
|
1244
|
+
}], canEdit: [{
|
|
1245
|
+
type: Input
|
|
1246
|
+
}], canRegenerate: [{
|
|
1247
|
+
type: Input
|
|
1248
|
+
}], canCopy: [{
|
|
1249
|
+
type: Input
|
|
1250
|
+
}], referenceClicked: [{
|
|
1251
|
+
type: Output
|
|
1252
|
+
}], edit: [{
|
|
1253
|
+
type: Output
|
|
1254
|
+
}], regenerate: [{
|
|
1255
|
+
type: Output
|
|
1256
|
+
}], openPreview: [{
|
|
1257
|
+
type: Output
|
|
1258
|
+
}] } });
|
|
1259
|
+
|
|
1260
|
+
class RestChatService extends ChatService {
|
|
1261
|
+
constructor() {
|
|
1262
|
+
super();
|
|
1263
|
+
this.jsonMethodWebService = inject(JsonMethodPluginService);
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Initialize the chat process after the login is complete.
|
|
1267
|
+
* It listens for the 'login-complete' event, initializes necessary URL, and performs parallel requests for models, functions and quota data.
|
|
1268
|
+
* @returns An Observable<boolean> indicating the success of the initialization process.
|
|
1269
|
+
*/
|
|
1270
|
+
init() {
|
|
1271
|
+
return this.loginService.events.pipe(filter((e) => e.type === 'login-complete'), tap(() => this.getRequestsUrl()),
|
|
1272
|
+
// Execute parallel requests for models and functions
|
|
1273
|
+
switchMap(() => forkJoin([
|
|
1274
|
+
this.listModels(),
|
|
1275
|
+
this.listFunctions()
|
|
1276
|
+
])),
|
|
1277
|
+
// Map the results of parallel requests to a boolean indicating success
|
|
1278
|
+
map(([models, functions]) => !!models && !!functions),
|
|
1279
|
+
// Any errors during the process are caught, logged, and re-thrown to propagate the error further
|
|
1280
|
+
catchError((error) => {
|
|
1281
|
+
console.error('Error occurred:', error);
|
|
1282
|
+
return throwError(() => error);
|
|
1283
|
+
}),
|
|
1284
|
+
// cache and replay the emitted value for subsequent subscribers, ensuring the initialization logic is only executed once even if there are multiple subscribers
|
|
1285
|
+
shareReplay(1));
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Define the GLLM plugin to use for the http requests
|
|
1289
|
+
* It can be overridden by the app config
|
|
1290
|
+
*/
|
|
1291
|
+
getRequestsUrl() {
|
|
1292
|
+
if (this.chatConfig$.value.globalSettings.restEndpoint) {
|
|
1293
|
+
this.REQUEST_URL = this.chatConfig$.value.globalSettings.restEndpoint;
|
|
1294
|
+
}
|
|
1295
|
+
else {
|
|
1296
|
+
throw new Error(`The property 'restEndpoint' must be provided when attempting to use 'REST' in assistant instance`);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
listModels() {
|
|
1300
|
+
const data = {
|
|
1301
|
+
action: "listmodels",
|
|
1302
|
+
debug: this.chatConfig$.value.debug
|
|
1303
|
+
};
|
|
1304
|
+
return this.jsonMethodWebService.get(this.REQUEST_URL, data).pipe(map(res => res.models), tap(models => this.models = models?.filter(model => !!model.enable)), catchError((error) => {
|
|
1305
|
+
console.error('Error invoking listmodels:', error);
|
|
1306
|
+
return throwError(() => error);
|
|
1307
|
+
}));
|
|
1308
|
+
}
|
|
1309
|
+
listFunctions() {
|
|
1310
|
+
const data = {
|
|
1311
|
+
action: "listfunctions",
|
|
1312
|
+
debug: this.chatConfig$.value.debug
|
|
1313
|
+
};
|
|
1314
|
+
return this.jsonMethodWebService.get(this.REQUEST_URL, data).pipe(map(res => res.functions), tap(functions => this.functions = functions), catchError((error) => {
|
|
1315
|
+
console.error('Error invoking listfunctions:', error);
|
|
1316
|
+
return throwError(() => error);
|
|
1317
|
+
}));
|
|
1318
|
+
}
|
|
1319
|
+
fetch(messages, query = this.searchService.query) {
|
|
1320
|
+
// Start streaming by invoking the Chat method
|
|
1321
|
+
this.streaming$.next(true);
|
|
1322
|
+
// Prepare the payload to send to the Chat method
|
|
1323
|
+
const data = {
|
|
1324
|
+
action: "chat",
|
|
1325
|
+
history: messages,
|
|
1326
|
+
functions: this.chatConfig$.value.functions,
|
|
1327
|
+
debug: this.chatConfig$.value.debug,
|
|
1328
|
+
serviceSettings: this.chatConfig$.value.serviceSettings,
|
|
1329
|
+
contextSettings: {
|
|
1330
|
+
...this.chatConfig$.value.contextSettings,
|
|
1331
|
+
app: this.appService.appName,
|
|
1332
|
+
query
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
if (this.chatConfig$.value.saveChats) {
|
|
1336
|
+
data.instanceId = this.chatInstanceId;
|
|
1337
|
+
data.savedChatId = this.savedChatId;
|
|
1338
|
+
}
|
|
1339
|
+
// Request the Chat endpoint
|
|
1340
|
+
return this.jsonMethodWebService.post(this.REQUEST_URL, data).pipe(tap((res) => {
|
|
1341
|
+
if (res.quota.maxQuotaReached) {
|
|
1342
|
+
const msg = `Sorry, you have exceeded the allowed quota. Please retry starting from ${this.formatDateTime(res.quota.nextResetUTC)}.`;
|
|
1343
|
+
this.notificationsService.error(msg);
|
|
1344
|
+
throw new Error(msg);
|
|
1345
|
+
}
|
|
1346
|
+
}), map((res) => {
|
|
1347
|
+
// Define $progress from the actions property of the response
|
|
1348
|
+
let $progress;
|
|
1349
|
+
if (res.actions?.length > 0) {
|
|
1350
|
+
const actions = Object.values(res.actions.reduce((acc, item) => {
|
|
1351
|
+
acc[item.guid] = { ...(acc[item.guid] || {}), ...item };
|
|
1352
|
+
return acc;
|
|
1353
|
+
}, {}));
|
|
1354
|
+
$progress = actions.map((a) => ({
|
|
1355
|
+
title: a.displayName ?? "",
|
|
1356
|
+
content: a.displayValue ?? "",
|
|
1357
|
+
done: a.executionTime !== undefined,
|
|
1358
|
+
time: a.executionTime,
|
|
1359
|
+
}));
|
|
1360
|
+
}
|
|
1361
|
+
// Define the response message
|
|
1362
|
+
const response = { ...res.history.at(-1), additionalProperties: { display: true } };
|
|
1363
|
+
if ($progress) {
|
|
1364
|
+
response.additionalProperties.$progress = $progress;
|
|
1365
|
+
}
|
|
1366
|
+
if (res.context) {
|
|
1367
|
+
response.additionalProperties.$attachment = res.context.map((ctx) => ctx.additionalProperties);
|
|
1368
|
+
}
|
|
1369
|
+
// Update the chat history with the history property of the res
|
|
1370
|
+
this.chatHistory = res.history;
|
|
1371
|
+
// Return the result
|
|
1372
|
+
return { history: [...messages, response], executionTime: res.executionTime };
|
|
1373
|
+
}), tap(() => this.notifyAudit(this.chatHistory, this.chatConfig$.value.serviceSettings.service_id)), finalize(() => this.streaming$.next(false)));
|
|
1374
|
+
}
|
|
1375
|
+
listSavedChat() {
|
|
1376
|
+
const data = {
|
|
1377
|
+
action: "SavedChatList",
|
|
1378
|
+
instanceId: this.chatInstanceId,
|
|
1379
|
+
debug: this.chatConfig$.value.debug
|
|
1380
|
+
};
|
|
1381
|
+
this.jsonMethodWebService.get(this.REQUEST_URL, data).subscribe(res => this.savedChats$.next(res.savedChats), error => {
|
|
1382
|
+
console.error('Error occurred while calling the SavedChatList API:', error);
|
|
1383
|
+
this.notificationsService.error('Error occurred while calling the SavedChatList API');
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
getSavedChat(id) {
|
|
1387
|
+
const data = {
|
|
1388
|
+
action: "SavedChatGet",
|
|
1389
|
+
instanceId: this.chatInstanceId,
|
|
1390
|
+
savedChatId: id,
|
|
1391
|
+
debug: this.chatConfig$.value.debug
|
|
1392
|
+
};
|
|
1393
|
+
return this.jsonMethodWebService.get(this.REQUEST_URL, data).pipe(map(res => res.savedChat), catchError((error) => {
|
|
1394
|
+
console.error('Error occurred while calling the SavedChatGet API:', error);
|
|
1395
|
+
this.notificationsService.error('Error occurred while calling the SavedChatGet API');
|
|
1396
|
+
return throwError(() => error);
|
|
1397
|
+
}));
|
|
1398
|
+
}
|
|
1399
|
+
deleteSavedChat(ids) {
|
|
1400
|
+
const data = {
|
|
1401
|
+
action: "SavedChatDelete",
|
|
1402
|
+
instanceId: this.chatInstanceId,
|
|
1403
|
+
savedChatIds: ids,
|
|
1404
|
+
debug: this.chatConfig$.value.debug
|
|
1405
|
+
};
|
|
1406
|
+
return this.jsonMethodWebService.post(this.REQUEST_URL, data).pipe(map(res => res.deletedCount), catchError((error) => {
|
|
1407
|
+
console.error('Error occurred while calling the SavedChatDelete API:', error);
|
|
1408
|
+
this.notificationsService.error('Error occurred while calling the SavedChatDelete API ');
|
|
1409
|
+
return throwError(() => error);
|
|
1410
|
+
}));
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
RestChatService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: RestChatService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1414
|
+
RestChatService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: RestChatService });
|
|
1415
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: RestChatService, decorators: [{
|
|
1416
|
+
type: Injectable
|
|
1417
|
+
}], ctorParameters: function () { return []; } });
|
|
1418
|
+
|
|
1419
|
+
class ChatComponent extends AbstractFacet {
|
|
1420
|
+
constructor() {
|
|
1421
|
+
super();
|
|
1422
|
+
/** Define the protocol to be used for this chat instance*/
|
|
1423
|
+
this.protocol = "WEBSOCKET";
|
|
1424
|
+
/** Map of listeners overriding default registered ones*/
|
|
1425
|
+
this.messageHandlers = new Map();
|
|
1426
|
+
/** When the assistant answer a user question, automatically scroll down to the bottom of the discussion */
|
|
1427
|
+
this.automaticScrollToLastResponse = false;
|
|
1428
|
+
this.enableChat = true;
|
|
1429
|
+
this.showCredits = true;
|
|
1430
|
+
this.customAssistantIcon = '';
|
|
1431
|
+
this.data = new EventEmitter();
|
|
1432
|
+
this.referenceClicked = new EventEmitter();
|
|
1433
|
+
this.openPreview = new EventEmitter();
|
|
1434
|
+
this.loading$ = new EventEmitter(false);
|
|
1435
|
+
this.error = new EventEmitter();
|
|
1436
|
+
this._config = new EventEmitter();
|
|
1437
|
+
this.messages$ = new BehaviorSubject(undefined);
|
|
1438
|
+
this.question = '';
|
|
1439
|
+
this._actions = [];
|
|
1440
|
+
this.sub = new Subscription();
|
|
1441
|
+
this.changes$ = new BehaviorSubject(undefined);
|
|
1442
|
+
this.handleFirstChanges = false;
|
|
1443
|
+
this.isAtBottom = true;
|
|
1444
|
+
this.initializationError = false;
|
|
1445
|
+
this.loginService = inject(LoginService);
|
|
1446
|
+
this.websocketService = inject(WebSocketChatService);
|
|
1447
|
+
this.restService = inject(RestChatService);
|
|
1448
|
+
this.instanceManagerService = inject(InstanceManagerService);
|
|
1449
|
+
this.searchService = inject(SearchService);
|
|
1450
|
+
this.principalService = inject(PrincipalWebService);
|
|
1451
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
1452
|
+
this._actions.push(new Action({
|
|
1453
|
+
icon: 'fas fa-sync',
|
|
1454
|
+
title: 'Reset chat',
|
|
1455
|
+
action: () => this.loadDefaultChat()
|
|
1456
|
+
}));
|
|
1457
|
+
}
|
|
1458
|
+
ngOnInit() {
|
|
1459
|
+
this.sub.add(this.loginService.events.pipe(filter(e => e.type === 'login-complete'), tap(_ => this.instantiateChatService()), map(_ => this.chatService.initChatConfig()), switchMap(() => this.chatService.initConfig$), filter(initConfig => !!initConfig), switchMap(_ => this.chatService.init()), filter(success => !!success), tap(_ => this.onLoadChat()), switchMap(_ => this.chatService.chatConfig$), tap(config => {
|
|
1460
|
+
this.config = config;
|
|
1461
|
+
this._config.emit(config);
|
|
1462
|
+
try {
|
|
1463
|
+
this.updateModelDescription();
|
|
1464
|
+
if (!this.handleFirstChanges) {
|
|
1465
|
+
this.handleChanges();
|
|
1466
|
+
this.addScrollListener();
|
|
1467
|
+
this.handleFirstChanges = true;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
catch (error) {
|
|
1471
|
+
this.initializationError = true;
|
|
1472
|
+
throw error;
|
|
1473
|
+
}
|
|
1474
|
+
})).subscribe());
|
|
1475
|
+
}
|
|
1476
|
+
ngOnChanges(changes) {
|
|
1477
|
+
this.changes$.next(changes);
|
|
1478
|
+
if (this.config) {
|
|
1479
|
+
this.handleChanges();
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
ngOnDestroy() {
|
|
1483
|
+
this.sub.unsubscribe();
|
|
1484
|
+
this.dataSubscription?.unsubscribe();
|
|
1485
|
+
}
|
|
1486
|
+
instantiateChatService() {
|
|
1487
|
+
switch (this.protocol) {
|
|
1488
|
+
case 'REST':
|
|
1489
|
+
this.chatService = this.restService;
|
|
1490
|
+
break;
|
|
1491
|
+
case 'WEBSOCKET':
|
|
1492
|
+
this.chatService = this.websocketService;
|
|
1493
|
+
break;
|
|
1494
|
+
default:
|
|
1495
|
+
throw new Error(`Could not found a ChatService implementation corresponding to the provided protocol: '${this.protocol}'`);
|
|
1496
|
+
}
|
|
1497
|
+
this.chatService.setChatInstanceId(this.instanceId);
|
|
1498
|
+
this.instanceManagerService.storeInstance(this.instanceId, this.chatService);
|
|
1499
|
+
}
|
|
1500
|
+
get actions() { return this._actions; }
|
|
1501
|
+
handleChanges() {
|
|
1502
|
+
const changes = this.changes$.value;
|
|
1503
|
+
if (changes?.messageHandlers && this.messageHandlers && this.chatService instanceof WebSocketChatService) {
|
|
1504
|
+
this.chatService.overrideMessageHandlers(this.messageHandlers);
|
|
1505
|
+
}
|
|
1506
|
+
if (!this.messages$.value || changes?.chat) {
|
|
1507
|
+
// Load the chat
|
|
1508
|
+
this.chat ? this.openChat(this.chat.messages) : this.loadDefaultChat();
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
addScrollListener() {
|
|
1512
|
+
this.sub.add(merge(this.loading$, this.messages$, this.chatService.streaming$, fromEvent(this.messageList.nativeElement, 'scroll')).subscribe(() => {
|
|
1513
|
+
this.isAtBottom = this.toggleScrollButtonVisibility();
|
|
1514
|
+
this.cdr.detectChanges();
|
|
1515
|
+
}));
|
|
1516
|
+
}
|
|
1517
|
+
updateModelDescription() {
|
|
1518
|
+
this.modelDescription = this.chatService.getModel(this.config.serviceSettings.service_id, this.config.serviceSettings.model_id);
|
|
1519
|
+
this.assistantIcon = !!this.customAssistantIcon ? this.customAssistantIcon : 'sq-sinequa';
|
|
1520
|
+
switch (this.modelDescription?.provider) {
|
|
1521
|
+
case 'Google':
|
|
1522
|
+
this.privacyUrl = '';
|
|
1523
|
+
break;
|
|
1524
|
+
case 'AzureOpenAI':
|
|
1525
|
+
this.privacyUrl = 'https://learn.microsoft.com/en-us/legal/cognitive-services/openai/data-privacy';
|
|
1526
|
+
break;
|
|
1527
|
+
case 'OpenAI':
|
|
1528
|
+
this.privacyUrl = 'https://openai.com/enterprise-privacy';
|
|
1529
|
+
break;
|
|
1530
|
+
case 'Cohere':
|
|
1531
|
+
this.privacyUrl = 'https://cohere.com/security';
|
|
1532
|
+
break;
|
|
1533
|
+
}
|
|
1534
|
+
this.cdr.detectChanges();
|
|
1535
|
+
}
|
|
1536
|
+
submitQuestion() {
|
|
1537
|
+
if (this.question.trim() && this.messages$.value && this.chatService.chatHistory) {
|
|
1538
|
+
if (this.messageToEdit !== undefined) {
|
|
1539
|
+
// Update the messages in the UI
|
|
1540
|
+
this.messages$.next(this.messages$.value.slice(0, this.messageToEdit));
|
|
1541
|
+
// Update the raw messages in the chat history which is the clean version used to make the next request
|
|
1542
|
+
this.chatService.chatHistory = this.chatService.chatHistory.slice(0, this.messageToEdit);
|
|
1543
|
+
this.messageToEdit = undefined;
|
|
1544
|
+
}
|
|
1545
|
+
// Re-attach the $progress and $attachment of the last response to the last assistant's response in the chat history
|
|
1546
|
+
this.chatService.chatHistory.at(-1).additionalProperties.$progress = this.messages$.value.at(-1).additionalProperties.$progress;
|
|
1547
|
+
this.chatService.chatHistory.at(-1).additionalProperties.$attachment = this.messages$.value.at(-1).additionalProperties.$attachment;
|
|
1548
|
+
// Fetch the answer
|
|
1549
|
+
this.fetchAnswer(this.question.trim(), this.chatService.chatHistory);
|
|
1550
|
+
// Clear the input value in the UI
|
|
1551
|
+
this.questionInput.nativeElement.value = '';
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
fetchAnswer(question, conversation) {
|
|
1555
|
+
const userMsg = { role: 'user', content: question, additionalProperties: { display: true } };
|
|
1556
|
+
const messages = [...conversation, userMsg];
|
|
1557
|
+
this.messages$.next(messages);
|
|
1558
|
+
this.fetch(messages);
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Given a list of messages, fetch the server for a continuation and updates
|
|
1562
|
+
* the list of messages accordingly.
|
|
1563
|
+
* @param messages
|
|
1564
|
+
*/
|
|
1565
|
+
fetch(messages) {
|
|
1566
|
+
this.cdr.detectChanges();
|
|
1567
|
+
this.loading$.next(true);
|
|
1568
|
+
this.dataSubscription?.unsubscribe();
|
|
1569
|
+
this.dataSubscription = this.chatService.fetch(messages, this.query)
|
|
1570
|
+
.subscribe({
|
|
1571
|
+
next: res => this.updateData(res.history),
|
|
1572
|
+
error: err => {
|
|
1573
|
+
this.terminateFetch();
|
|
1574
|
+
console.error(err);
|
|
1575
|
+
this.error.emit(err);
|
|
1576
|
+
},
|
|
1577
|
+
complete: () => {
|
|
1578
|
+
this.terminateFetch();
|
|
1579
|
+
}
|
|
1580
|
+
});
|
|
1581
|
+
if (this.automaticScrollToLastResponse) {
|
|
1582
|
+
this.scrollDown();
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Update the UI with the new messages
|
|
1587
|
+
* @param messages
|
|
1588
|
+
*/
|
|
1589
|
+
updateData(messages) {
|
|
1590
|
+
this.messages$.next(messages);
|
|
1591
|
+
this.data.emit(messages);
|
|
1592
|
+
this.loading$.next(false);
|
|
1593
|
+
this.question = '';
|
|
1594
|
+
if (this.automaticScrollToLastResponse) {
|
|
1595
|
+
this.scrollDown();
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
toggleScrollButtonVisibility() {
|
|
1599
|
+
if (this.messageList?.nativeElement) {
|
|
1600
|
+
return Math.round(this.messageList?.nativeElement.scrollHeight - this.messageList?.nativeElement.scrollTop - 1) <= this.messageList?.nativeElement.clientHeight;
|
|
1601
|
+
}
|
|
1602
|
+
return true;
|
|
1603
|
+
}
|
|
1604
|
+
scrollDown() {
|
|
1605
|
+
setTimeout(() => {
|
|
1606
|
+
if (this.messageList?.nativeElement) {
|
|
1607
|
+
this.messageList.nativeElement.scrollTop = this.messageList.nativeElement.scrollHeight;
|
|
1608
|
+
this.cdr.detectChanges();
|
|
1609
|
+
}
|
|
1610
|
+
}, 10);
|
|
1611
|
+
}
|
|
1612
|
+
newChat() {
|
|
1613
|
+
this.chatService.listSavedChat(); // Refresh the list of saved chats
|
|
1614
|
+
this.loadDefaultChat(); // Start a new chat
|
|
1615
|
+
}
|
|
1616
|
+
loadDefaultChat() {
|
|
1617
|
+
this.openChat([
|
|
1618
|
+
{ role: 'system', content: this.config.uiSettings.systemPrompt, additionalProperties: { display: false } },
|
|
1619
|
+
{ role: 'user', content: ChatService.formatPrompt(this.config.uiSettings.userPrompt, { principal: this.principalService.principal }), additionalProperties: { display: true } },
|
|
1620
|
+
]);
|
|
1621
|
+
}
|
|
1622
|
+
openChat(messages, chatId) {
|
|
1623
|
+
if (!messages || !Array.isArray(messages)) {
|
|
1624
|
+
console.error('Error occurs while trying to load the chat discussion. Invalid messages received :', messages);
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
this.chatService.setSavedChatId(chatId || ChatService.generateGUID());
|
|
1628
|
+
this.resetChat();
|
|
1629
|
+
this.messages$.next(messages);
|
|
1630
|
+
this.chatService.chatHistory = messages;
|
|
1631
|
+
const lastMessage = messages.at(-1);
|
|
1632
|
+
if (lastMessage && lastMessage.role === 'user') {
|
|
1633
|
+
this.fetch(messages); // If the last message if from a user, an answer from the assistant is expected
|
|
1634
|
+
}
|
|
1635
|
+
else {
|
|
1636
|
+
this.updateData(messages); // If the last message if from the assistant, we can load the conversation right away
|
|
1637
|
+
this.terminateFetch();
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
resetChat() {
|
|
1641
|
+
if (this.messages$.value) {
|
|
1642
|
+
this.messages$.next(undefined); // Reset chat
|
|
1643
|
+
}
|
|
1644
|
+
this.chatService.chatHistory = undefined; // Reset chat history
|
|
1645
|
+
this.question = '';
|
|
1646
|
+
this.terminateFetch();
|
|
1647
|
+
}
|
|
1648
|
+
onLoadChat() {
|
|
1649
|
+
this.loading$.next(true);
|
|
1650
|
+
this.sub.add(this.chatService.loadSavedChat$
|
|
1651
|
+
.pipe(filter(savedChat => !!savedChat), switchMap(savedChat => this.chatService.getSavedChat(savedChat.id)), filter(savedChatHistory => !!savedChatHistory), tap(savedChatHistory => this.openChat(savedChatHistory.History, savedChatHistory.id)), finalize(() => this.chatService.listSavedChat()) // Refresh the list of saved chats
|
|
1652
|
+
).subscribe());
|
|
1653
|
+
}
|
|
1654
|
+
terminateFetch() {
|
|
1655
|
+
this.dataSubscription?.unsubscribe();
|
|
1656
|
+
this.dataSubscription = undefined;
|
|
1657
|
+
this.loading$.next(false);
|
|
1658
|
+
setTimeout(() => {
|
|
1659
|
+
this.questionInput?.nativeElement.focus();
|
|
1660
|
+
});
|
|
1661
|
+
this.cdr.detectChanges();
|
|
1662
|
+
}
|
|
1663
|
+
editMessage(index) {
|
|
1664
|
+
this.messageToEdit = index;
|
|
1665
|
+
this.question = this.chatService.chatHistory[index].content;
|
|
1666
|
+
this.questionInput?.nativeElement.focus();
|
|
1667
|
+
}
|
|
1668
|
+
regenerateMessage(index) {
|
|
1669
|
+
// Define the chat history based on which the assistant will generate a new answer
|
|
1670
|
+
const slicedMessages = this.chatService.chatHistory.slice(0, index);
|
|
1671
|
+
// Accordingly update the messages in the UI
|
|
1672
|
+
this.messages$.next(slicedMessages);
|
|
1673
|
+
// Fetch the answer
|
|
1674
|
+
this.fetch(slicedMessages);
|
|
1675
|
+
}
|
|
1676
|
+
onKeyUp(event) {
|
|
1677
|
+
switch (event.key) {
|
|
1678
|
+
case 'ArrowUp':
|
|
1679
|
+
this.navigateMessage(-1);
|
|
1680
|
+
break;
|
|
1681
|
+
case 'ArrowDown':
|
|
1682
|
+
this.navigateMessage(1);
|
|
1683
|
+
break;
|
|
1684
|
+
case 'Enter':
|
|
1685
|
+
this.submitQuestion();
|
|
1686
|
+
break;
|
|
1687
|
+
default:
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
// Handle Shift + Enter
|
|
1691
|
+
if (event.shiftKey && event.key === 'Enter') {
|
|
1692
|
+
this.submitQuestion();
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
navigateMessage(direction) {
|
|
1696
|
+
if (!this.chatService.chatHistory || this.chatService.chatHistory.length < 1) {
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
const userMessages = this.chatService.chatHistory.filter(m => m.role === 'user');
|
|
1700
|
+
if (userMessages.length < 1) {
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
this.currentMessageIndex = (this.currentMessageIndex ?? userMessages.length) + direction;
|
|
1704
|
+
if (this.currentMessageIndex < 0) {
|
|
1705
|
+
// If the user presses up arrow on the first message, stay at the first message
|
|
1706
|
+
this.currentMessageIndex = 0;
|
|
1707
|
+
}
|
|
1708
|
+
else if (this.currentMessageIndex >= userMessages.length) {
|
|
1709
|
+
// If the user presses down arrow on the last previous message, clear the input
|
|
1710
|
+
this.currentMessageIndex = undefined;
|
|
1711
|
+
this.question = '';
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
this.question = userMessages[this.currentMessageIndex].content;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
ChatComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1718
|
+
ChatComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: ChatComponent, isStandalone: true, selector: "sq-chat-v3", inputs: { instanceId: "instanceId", query: "query", protocol: "protocol", messageHandlers: "messageHandlers", automaticScrollToLastResponse: "automaticScrollToLastResponse", chat: "chat", enableChat: "enableChat", showCredits: "showCredits", customAssistantIcon: "customAssistantIcon" }, outputs: { data: "data", referenceClicked: "referenceClicked", openPreview: "openPreview", loading$: "loading", error: "error", _config: "config" }, providers: [
|
|
1719
|
+
RestChatService,
|
|
1720
|
+
WebSocketChatService
|
|
1721
|
+
], queries: [{ propertyName: "loadingTpl", first: true, predicate: ["loadingTpl"], descendants: true }], viewQueries: [{ propertyName: "messageList", first: true, predicate: ["messageList"], descendants: true }, { propertyName: "questionInput", first: true, predicate: ["questionInput"], descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "\n<ng-container *ngIf=\"!initializationError\">\n <div *ngIf=\"messages$ | async as messages; else loadingTpl || loadingTplDefault\" class=\"h-100 d-flex flex-column\">\n\n <ul class=\"list-group list-group-flush overflow-auto pb-5\" #messageList>\n <ng-container *ngFor=\"let message of messages; let index = index; let last = last\">\n <!-- Regular messages -->\n <li class=\"list-group-item\" *ngIf=\"message.additionalProperties.display\"\n [class.opacity-50]=\"messageToEdit && messageToEdit < index + 1\">\n <sq-chat-message\n [class.sq-user-message]=\"message.role !== 'assistant'\"\n [message]=\"message\"\n [conversation]=\"messages\"\n [assistantIcon]=\"customAssistantIcon || assistantIcon\"\n [streaming]=\"last && (chatService.streaming$ | async)\"\n [canEdit]=\"(loading$ | async) === false && (chatService.streaming$ | async) === false && messageToEdit === undefined && message.role !== 'assistant'\"\n [canRegenerate]=\"(loading$ | async) === false && (chatService.streaming$ | async) === false && messageToEdit === undefined && message.role === 'assistant' && last\"\n [canCopy]=\"!(last && (chatService.streaming$ | async)) && messageToEdit === undefined && message.role === 'assistant'\"\n (edit)=\"editMessage(index)\" (regenerate)=\"regenerateMessage(index)\"\n (referenceClicked)=\"referenceClicked.emit($event)\"\n (openPreview)=\"openPreview.emit($event)\">\n </sq-chat-message>\n </li>\n </ng-container>\n\n <li class=\"list-group-item\" *ngIf=\"(loading$ | async) === true\">\n <ng-container *ngTemplateOutlet=\"loadingTpl || loadingTplDefault\"></ng-container>\n </li>\n </ul>\n\n <div class=\"user-input mt-auto\" *ngIf=\"enableChat\">\n <div class=\"py-2\">\n <ng-container *ngTemplateOutlet=\"inputTpl\"></ng-container>\n <div class=\"text-end small text-muted px-3\" *ngIf=\"showCredits\">\n powered by {{modelDescription?.displayName}} <ng-container *ngIf=\"privacyUrl\"> - <a [href]=\"privacyUrl\" target=\"_blank\">privacy notice</a></ng-container>\n </div>\n </div>\n </div>\n </div>\n</ng-container>\n\n<!-- NG TEMPLATES-->\n\n<ng-template #loadingTplDefault>\n <div class=\"spinner-grow text-success d-block mx-auto my-5\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n</ng-template>\n\n<ng-template #inputTpl>\n <div class=\"px-3 py-1\">\n <div class=\"ast-input-container\">\n <i class=\"fas fa-search\"></i>\n <input #questionInput\n type=\"text\" class=\"form-control\"\n placeholder=\"Ask something\" autofocus\n [(ngModel)]=\"question\"\n (keyup)=\"onKeyUp($event)\"\n [disabled]=\"(loading$ | async) || (chatService.streaming$ | async)\">\n <button\n *ngIf=\"!(chatService.streaming$ | async) && !(loading$ | async)\"\n type=\"button\"\n class=\"btn btn-light ms-2\"\n title=\"Send message\"\n (click)=\"submitQuestion()\">\n <i class=\"fas fa-paper-plane\"></i>\n </button>\n <!--<button\n *ngIf=\"(chatService.streaming$ | async)\"\n type=\"button\"\n class=\"btn btn-light ms-2\"\n title=\"Stop generating\"\n (click)=\"terminateFetch()\">\n <i class=\"fas fa-stop\"></i>\n </button>-->\n <button\n *ngIf=\"messageToEdit\"\n type=\"button\"\n class=\"btn btn-light ms-2\"\n title=\"Cancel edition\"\n (click)=\"messageToEdit = undefined; question = ''\">\n <i class=\"fas fa-undo-alt\"></i>\n </button>\n <div class=\"sq-floating-scroll\" *ngIf=\"!isAtBottom\">\n <button class=\"btn shadow\" (click)=\"scrollDown()\">\n <i class=\"fas fa-angle-double-down\"></i>\n </button>\n </div>\n </div>\n </div>\n</ng-template>\n", styles: [".ast-primary{color:var(--ast-primary-color, #005DA7);background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover{background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover:hover{color:var(--ast-primary-color, #005DA7)}.ast-secondary{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-secondary-bg, #FFF8F1)}.ast-btn{border:0;text-align:left;padding-top:.5rem;padding-bottom:.5rem;display:flex;align-items:center}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference,:host ::ng-deep .attachment .reference{position:relative;bottom:var(--ast-reference-bottom, .3em);font-weight:var(--ast-reference-font-weight, bold);padding:var(--ast-reference-padding, 0 .2em);margin:var(--ast-reference-margin, 0 .1em);border-radius:var(--ast-reference-border-radius, .2em);background-color:var(--ast-reference-background-color, lightblue);color:var(--ast-reference-color, black)}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference{font-size:var(--ast-reference-message-font-size, .7em)}:host ::ng-deep .attachment .reference{font-size:var(--ast-reference-attachment-font-size, 13px)}:host{font-size:.875rem}:host>div>.user-input>div:not(.progress),:host>div>ul>li{width:var(--ast-chat-container-width, 100%);max-width:100%;margin-left:auto;margin-right:auto}:host>div>ul{padding-top:var(--ast-chat-padding-top, 0);padding-bottom:var(--ast-chat-padding-bottom, 0)}li.attachment>p{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;-webkit-line-clamp:3}li.attachment.expanded>p{display:block}.progress{--bs-progress-height: 3px}.progress.disabled{--bs-progress-height: 20px;--bs-progress-bar-bg: var(--bs-danger)}.user-input{z-index:1}.user-input ul.list-group{max-height:30vh}.form-control:disabled{background-color:#ededed}a.disabled{cursor:default;opacity:.5}.no-max-height{max-height:initial!important}.sq-floating-scroll{position:absolute;right:50%;bottom:75px;text-align:center}.sq-floating-scroll .btn{background-color:#fff}.sq-floating-scroll .btn:hover{background-color:#fff;opacity:.9}.ast-input-container{display:flex;align-items:center;background-color:var(--ast-input-bg, #F8F8F8);border-radius:var(--ast-size-3, .75rem)}.ast-input-container>i{padding-left:var(--ast-size-3, .75rem);color:#212529bf}.ast-input-container input{padding-left:var(--ast-size-3, .75rem);padding-right:var(--ast-size-3, .75rem)}.ast-input-container input,.ast-input-container button,.ast-input-container button:hover{background-color:transparent;border:0}.ast-input-container button:not(:hover){color:#212529bf}sq-chat-message.sq-user-message{float:var(--ast-user-message-float, none)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ChatMessageComponent, selector: "sq-chat-message", inputs: ["message", "conversation", "assistantIcon", "streaming", "canEdit", "canRegenerate", "canCopy"], outputs: ["referenceClicked", "edit", "regenerate", "openPreview"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1722
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatComponent, decorators: [{
|
|
1723
|
+
type: Component,
|
|
1724
|
+
args: [{ selector: 'sq-chat-v3', providers: [
|
|
1725
|
+
RestChatService,
|
|
1726
|
+
WebSocketChatService
|
|
1727
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, FormsModule, ChatMessageComponent], template: "\n<ng-container *ngIf=\"!initializationError\">\n <div *ngIf=\"messages$ | async as messages; else loadingTpl || loadingTplDefault\" class=\"h-100 d-flex flex-column\">\n\n <ul class=\"list-group list-group-flush overflow-auto pb-5\" #messageList>\n <ng-container *ngFor=\"let message of messages; let index = index; let last = last\">\n <!-- Regular messages -->\n <li class=\"list-group-item\" *ngIf=\"message.additionalProperties.display\"\n [class.opacity-50]=\"messageToEdit && messageToEdit < index + 1\">\n <sq-chat-message\n [class.sq-user-message]=\"message.role !== 'assistant'\"\n [message]=\"message\"\n [conversation]=\"messages\"\n [assistantIcon]=\"customAssistantIcon || assistantIcon\"\n [streaming]=\"last && (chatService.streaming$ | async)\"\n [canEdit]=\"(loading$ | async) === false && (chatService.streaming$ | async) === false && messageToEdit === undefined && message.role !== 'assistant'\"\n [canRegenerate]=\"(loading$ | async) === false && (chatService.streaming$ | async) === false && messageToEdit === undefined && message.role === 'assistant' && last\"\n [canCopy]=\"!(last && (chatService.streaming$ | async)) && messageToEdit === undefined && message.role === 'assistant'\"\n (edit)=\"editMessage(index)\" (regenerate)=\"regenerateMessage(index)\"\n (referenceClicked)=\"referenceClicked.emit($event)\"\n (openPreview)=\"openPreview.emit($event)\">\n </sq-chat-message>\n </li>\n </ng-container>\n\n <li class=\"list-group-item\" *ngIf=\"(loading$ | async) === true\">\n <ng-container *ngTemplateOutlet=\"loadingTpl || loadingTplDefault\"></ng-container>\n </li>\n </ul>\n\n <div class=\"user-input mt-auto\" *ngIf=\"enableChat\">\n <div class=\"py-2\">\n <ng-container *ngTemplateOutlet=\"inputTpl\"></ng-container>\n <div class=\"text-end small text-muted px-3\" *ngIf=\"showCredits\">\n powered by {{modelDescription?.displayName}} <ng-container *ngIf=\"privacyUrl\"> - <a [href]=\"privacyUrl\" target=\"_blank\">privacy notice</a></ng-container>\n </div>\n </div>\n </div>\n </div>\n</ng-container>\n\n<!-- NG TEMPLATES-->\n\n<ng-template #loadingTplDefault>\n <div class=\"spinner-grow text-success d-block mx-auto my-5\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n</ng-template>\n\n<ng-template #inputTpl>\n <div class=\"px-3 py-1\">\n <div class=\"ast-input-container\">\n <i class=\"fas fa-search\"></i>\n <input #questionInput\n type=\"text\" class=\"form-control\"\n placeholder=\"Ask something\" autofocus\n [(ngModel)]=\"question\"\n (keyup)=\"onKeyUp($event)\"\n [disabled]=\"(loading$ | async) || (chatService.streaming$ | async)\">\n <button\n *ngIf=\"!(chatService.streaming$ | async) && !(loading$ | async)\"\n type=\"button\"\n class=\"btn btn-light ms-2\"\n title=\"Send message\"\n (click)=\"submitQuestion()\">\n <i class=\"fas fa-paper-plane\"></i>\n </button>\n <!--<button\n *ngIf=\"(chatService.streaming$ | async)\"\n type=\"button\"\n class=\"btn btn-light ms-2\"\n title=\"Stop generating\"\n (click)=\"terminateFetch()\">\n <i class=\"fas fa-stop\"></i>\n </button>-->\n <button\n *ngIf=\"messageToEdit\"\n type=\"button\"\n class=\"btn btn-light ms-2\"\n title=\"Cancel edition\"\n (click)=\"messageToEdit = undefined; question = ''\">\n <i class=\"fas fa-undo-alt\"></i>\n </button>\n <div class=\"sq-floating-scroll\" *ngIf=\"!isAtBottom\">\n <button class=\"btn shadow\" (click)=\"scrollDown()\">\n <i class=\"fas fa-angle-double-down\"></i>\n </button>\n </div>\n </div>\n </div>\n</ng-template>\n", styles: [".ast-primary{color:var(--ast-primary-color, #005DA7);background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover{background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover:hover{color:var(--ast-primary-color, #005DA7)}.ast-secondary{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-secondary-bg, #FFF8F1)}.ast-btn{border:0;text-align:left;padding-top:.5rem;padding-bottom:.5rem;display:flex;align-items:center}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference,:host ::ng-deep .attachment .reference{position:relative;bottom:var(--ast-reference-bottom, .3em);font-weight:var(--ast-reference-font-weight, bold);padding:var(--ast-reference-padding, 0 .2em);margin:var(--ast-reference-margin, 0 .1em);border-radius:var(--ast-reference-border-radius, .2em);background-color:var(--ast-reference-background-color, lightblue);color:var(--ast-reference-color, black)}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference{font-size:var(--ast-reference-message-font-size, .7em)}:host ::ng-deep .attachment .reference{font-size:var(--ast-reference-attachment-font-size, 13px)}:host{font-size:.875rem}:host>div>.user-input>div:not(.progress),:host>div>ul>li{width:var(--ast-chat-container-width, 100%);max-width:100%;margin-left:auto;margin-right:auto}:host>div>ul{padding-top:var(--ast-chat-padding-top, 0);padding-bottom:var(--ast-chat-padding-bottom, 0)}li.attachment>p{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;-webkit-line-clamp:3}li.attachment.expanded>p{display:block}.progress{--bs-progress-height: 3px}.progress.disabled{--bs-progress-height: 20px;--bs-progress-bar-bg: var(--bs-danger)}.user-input{z-index:1}.user-input ul.list-group{max-height:30vh}.form-control:disabled{background-color:#ededed}a.disabled{cursor:default;opacity:.5}.no-max-height{max-height:initial!important}.sq-floating-scroll{position:absolute;right:50%;bottom:75px;text-align:center}.sq-floating-scroll .btn{background-color:#fff}.sq-floating-scroll .btn:hover{background-color:#fff;opacity:.9}.ast-input-container{display:flex;align-items:center;background-color:var(--ast-input-bg, #F8F8F8);border-radius:var(--ast-size-3, .75rem)}.ast-input-container>i{padding-left:var(--ast-size-3, .75rem);color:#212529bf}.ast-input-container input{padding-left:var(--ast-size-3, .75rem);padding-right:var(--ast-size-3, .75rem)}.ast-input-container input,.ast-input-container button,.ast-input-container button:hover{background-color:transparent;border:0}.ast-input-container button:not(:hover){color:#212529bf}sq-chat-message.sq-user-message{float:var(--ast-user-message-float, none)}\n"] }]
|
|
1728
|
+
}], ctorParameters: function () { return []; }, propDecorators: { instanceId: [{
|
|
1729
|
+
type: Input
|
|
1730
|
+
}], query: [{
|
|
1731
|
+
type: Input
|
|
1732
|
+
}], protocol: [{
|
|
1733
|
+
type: Input
|
|
1734
|
+
}], messageHandlers: [{
|
|
1735
|
+
type: Input
|
|
1736
|
+
}], automaticScrollToLastResponse: [{
|
|
1737
|
+
type: Input
|
|
1738
|
+
}], chat: [{
|
|
1739
|
+
type: Input
|
|
1740
|
+
}], enableChat: [{
|
|
1741
|
+
type: Input
|
|
1742
|
+
}], showCredits: [{
|
|
1743
|
+
type: Input
|
|
1744
|
+
}], customAssistantIcon: [{
|
|
1745
|
+
type: Input
|
|
1746
|
+
}], data: [{
|
|
1747
|
+
type: Output
|
|
1748
|
+
}], referenceClicked: [{
|
|
1749
|
+
type: Output
|
|
1750
|
+
}], openPreview: [{
|
|
1751
|
+
type: Output
|
|
1752
|
+
}], loading$: [{
|
|
1753
|
+
type: Output,
|
|
1754
|
+
args: ["loading"]
|
|
1755
|
+
}], error: [{
|
|
1756
|
+
type: Output
|
|
1757
|
+
}], _config: [{
|
|
1758
|
+
type: Output,
|
|
1759
|
+
args: ["config"]
|
|
1760
|
+
}], messageList: [{
|
|
1761
|
+
type: ViewChild,
|
|
1762
|
+
args: ['messageList']
|
|
1763
|
+
}], questionInput: [{
|
|
1764
|
+
type: ViewChild,
|
|
1765
|
+
args: ['questionInput']
|
|
1766
|
+
}], loadingTpl: [{
|
|
1767
|
+
type: ContentChild,
|
|
1768
|
+
args: ['loadingTpl']
|
|
1769
|
+
}] } });
|
|
1770
|
+
|
|
1771
|
+
class SavedChatsComponent {
|
|
1772
|
+
constructor() {
|
|
1773
|
+
this.load = new EventEmitter();
|
|
1774
|
+
this.delete = new EventEmitter();
|
|
1775
|
+
this.subscription = new Subscription();
|
|
1776
|
+
this.groupedSavedChats$ = new BehaviorSubject([]);
|
|
1777
|
+
this.loginService = inject(LoginService);
|
|
1778
|
+
this.instanceManagerService = inject(InstanceManagerService);
|
|
1779
|
+
this.auditService = inject(AuditWebService);
|
|
1780
|
+
this.modalService = inject(ModalService);
|
|
1781
|
+
this.notificationsService = inject(NotificationsService);
|
|
1782
|
+
}
|
|
1783
|
+
ngOnInit() {
|
|
1784
|
+
this.subscription.add(this.loginService.events.pipe(filter(e => e.type === 'login-complete'), tap(_ => this.instantiateChatService()), switchMap(_ => this.chatService.initProcess$), filter(success => !!success), tap(_ => {
|
|
1785
|
+
this.onListSavedChat();
|
|
1786
|
+
this.chatService.listSavedChat();
|
|
1787
|
+
})).subscribe());
|
|
1788
|
+
}
|
|
1789
|
+
ngOnDestroy() {
|
|
1790
|
+
this.subscription.unsubscribe();
|
|
1791
|
+
}
|
|
1792
|
+
instantiateChatService() {
|
|
1793
|
+
this.chatService = this.instanceManagerService.getInstance(this.instanceId);
|
|
1794
|
+
}
|
|
1795
|
+
onListSavedChat() {
|
|
1796
|
+
this.subscription.add(this.chatService.savedChats$.subscribe((savedChats) => this.groupedSavedChats$.next(this._groupSavedChatsByDate(savedChats))));
|
|
1797
|
+
}
|
|
1798
|
+
onLoad(savedChat) {
|
|
1799
|
+
this.chatService.loadSavedChat$.next(savedChat);
|
|
1800
|
+
this.load.emit(savedChat);
|
|
1801
|
+
}
|
|
1802
|
+
onDelete(savedChat) {
|
|
1803
|
+
this.modalService
|
|
1804
|
+
.confirm({
|
|
1805
|
+
title: "Delete saved discussion",
|
|
1806
|
+
message: `You are about to delete the discussion "${savedChat.title}". Do you want to continue?`,
|
|
1807
|
+
buttons: [
|
|
1808
|
+
new ModalButton({ result: -2 /* ModalResult.Cancel */ }),
|
|
1809
|
+
new ModalButton({ result: -1 /* ModalResult.OK */, text: "Confirm", primary: true })
|
|
1810
|
+
],
|
|
1811
|
+
confirmType: 2 /* ConfirmType.Warning */
|
|
1812
|
+
}).then(res => {
|
|
1813
|
+
if (res === -1 /* ModalResult.OK */) {
|
|
1814
|
+
this.subscription.add(this.chatService.deleteSavedChat([savedChat.id])
|
|
1815
|
+
.pipe(tap(() => {
|
|
1816
|
+
this.notificationsService.success(`The saved discussion "${savedChat.title}" has been successfully deleted.`);
|
|
1817
|
+
this.delete.emit(savedChat);
|
|
1818
|
+
this.chatService.listSavedChat();
|
|
1819
|
+
}), catchError((error) => {
|
|
1820
|
+
console.error('Error occurred while deleting the saved chat:', error);
|
|
1821
|
+
this.notificationsService.error(`Error occurred while deleting the saved discussion "${savedChat.title}"`);
|
|
1822
|
+
return throwError(() => error);
|
|
1823
|
+
})).subscribe());
|
|
1824
|
+
}
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
_groupSavedChatsByDate(savedChats) {
|
|
1828
|
+
const groupedSavedChats = new Map();
|
|
1829
|
+
savedChats
|
|
1830
|
+
.sort((a, b) => parseISO(b.modifiedUTC).getTime() - parseISO(a.modifiedUTC).getTime())
|
|
1831
|
+
.forEach(savedChat => {
|
|
1832
|
+
const groupKey = this._getTimeKey(parseISO(savedChat.modifiedUTC));
|
|
1833
|
+
if (!groupedSavedChats.has(groupKey)) {
|
|
1834
|
+
groupedSavedChats.set(groupKey, []);
|
|
1835
|
+
}
|
|
1836
|
+
groupedSavedChats.get(groupKey).push(savedChat);
|
|
1837
|
+
});
|
|
1838
|
+
return Array.from(groupedSavedChats, ([key, value]) => ({ key, value }));
|
|
1839
|
+
;
|
|
1840
|
+
}
|
|
1841
|
+
_getTimeKey(date) {
|
|
1842
|
+
if (isToday(date)) {
|
|
1843
|
+
return 'Today';
|
|
1844
|
+
}
|
|
1845
|
+
else if (isYesterday(date)) {
|
|
1846
|
+
return 'Yesterday';
|
|
1847
|
+
}
|
|
1848
|
+
else if (isThisWeek(date)) {
|
|
1849
|
+
return 'This week';
|
|
1850
|
+
}
|
|
1851
|
+
else if (differenceInDays(endOfYesterday(), date) <= 7) {
|
|
1852
|
+
return 'Last week';
|
|
1853
|
+
}
|
|
1854
|
+
else if (isThisMonth(date)) {
|
|
1855
|
+
return 'This month';
|
|
1856
|
+
}
|
|
1857
|
+
else if (differenceInMonths(endOfYesterday(), date) <= 1) {
|
|
1858
|
+
return 'Last month';
|
|
1859
|
+
}
|
|
1860
|
+
else if (isThisQuarter(date)) {
|
|
1861
|
+
return 'This quarter';
|
|
1862
|
+
}
|
|
1863
|
+
else if (differenceInMonths(endOfYesterday(), date) <= 3) {
|
|
1864
|
+
return 'Last quarter';
|
|
1865
|
+
}
|
|
1866
|
+
else if (isThisYear(date)) {
|
|
1867
|
+
return 'This year';
|
|
1868
|
+
}
|
|
1869
|
+
else if (differenceInYears(endOfYesterday(), date) === 1) {
|
|
1870
|
+
return 'Last year';
|
|
1871
|
+
}
|
|
1872
|
+
else {
|
|
1873
|
+
return format(date, 'yyyy');
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
SavedChatsComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: SavedChatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1878
|
+
SavedChatsComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: SavedChatsComponent, isStandalone: true, selector: "sq-saved-chats-v3", inputs: { instanceId: "instanceId" }, outputs: { load: "load", delete: "delete" }, ngImport: i0, template: "<div *ngFor=\"let group of (groupedSavedChats$ | async)\" class=\"saved-chats\">\n <div class=\"saved-chat-date\">{{group.key}}</div>\n <div *ngFor=\"let savedChat of group.value\" class=\"saved-chat p-2\">\n <span class=\"title me-1\" (click)=\"onLoad(savedChat)\">{{savedChat.title}}</span>\n <i class=\"saved-chat-actions fas fa-trash ms-1\" [sqTooltip]=\"'Delete'\" (click)=\"onDelete(savedChat)\"></i>\n </div>\n</div>\n", styles: [".saved-chats .saved-chat-date{font-weight:500;color:#a9a9a9;margin-top:.5rem}.saved-chats .saved-chat{display:flex;align-items:center;cursor:pointer;margin-left:.25rem}.saved-chats .saved-chat span{flex-grow:1;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.saved-chats .saved-chat .saved-chat-actions{display:none}.saved-chats .saved-chat:hover{color:#ff732e;background-color:#fff8f1}.saved-chats .saved-chat:hover .saved-chat-actions{display:block}.saved-chats .title{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: ModalModule }, { kind: "ngmodule", type: UtilsModule }, { kind: "directive", type: i2$1.TooltipDirective, selector: "[sqTooltip]", inputs: ["sqTooltip", "sqTooltipData", "sqTooltipTemplate", "placement", "fallbackPlacements", "delay", "hoverableTooltip", "tooltipClass"] }] });
|
|
1879
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: SavedChatsComponent, decorators: [{
|
|
1880
|
+
type: Component,
|
|
1881
|
+
args: [{ selector: 'sq-saved-chats-v3', standalone: true, imports: [CommonModule, ModalModule, UtilsModule], template: "<div *ngFor=\"let group of (groupedSavedChats$ | async)\" class=\"saved-chats\">\n <div class=\"saved-chat-date\">{{group.key}}</div>\n <div *ngFor=\"let savedChat of group.value\" class=\"saved-chat p-2\">\n <span class=\"title me-1\" (click)=\"onLoad(savedChat)\">{{savedChat.title}}</span>\n <i class=\"saved-chat-actions fas fa-trash ms-1\" [sqTooltip]=\"'Delete'\" (click)=\"onDelete(savedChat)\"></i>\n </div>\n</div>\n", styles: [".saved-chats .saved-chat-date{font-weight:500;color:#a9a9a9;margin-top:.5rem}.saved-chats .saved-chat{display:flex;align-items:center;cursor:pointer;margin-left:.25rem}.saved-chats .saved-chat span{flex-grow:1;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.saved-chats .saved-chat .saved-chat-actions{display:none}.saved-chats .saved-chat:hover{color:#ff732e;background-color:#fff8f1}.saved-chats .saved-chat:hover .saved-chat-actions{display:block}.saved-chats .title{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
|
|
1882
|
+
}], propDecorators: { instanceId: [{
|
|
1883
|
+
type: Input
|
|
1884
|
+
}], load: [{
|
|
1885
|
+
type: Output
|
|
1886
|
+
}], delete: [{
|
|
1887
|
+
type: Output
|
|
1888
|
+
}] } });
|
|
1889
|
+
|
|
1890
|
+
var _enAssistant = {
|
|
1891
|
+
"assistant": {}
|
|
1892
|
+
};
|
|
1893
|
+
|
|
1894
|
+
var _frAssistant = {
|
|
1895
|
+
"assistant": {}
|
|
1896
|
+
};
|
|
1897
|
+
|
|
1898
|
+
var _deAssistant = {
|
|
1899
|
+
"assistant": {}
|
|
1900
|
+
};
|
|
1901
|
+
|
|
1902
|
+
const enAssistant = Utils.merge({}, _enAssistant);
|
|
1903
|
+
const frAssistant = Utils.merge({}, _frAssistant);
|
|
1904
|
+
const deAssistant = Utils.merge({}, _deAssistant);
|
|
1905
|
+
|
|
1906
|
+
/**
|
|
1907
|
+
* Generated bundle index. Do not edit.
|
|
1908
|
+
*/
|
|
1909
|
+
|
|
1910
|
+
export { ChatComponent, ChatService, ChatSettingsV3Component, FormatIconComponent, InitialsAvatarComponent, InstanceManagerService, RestChatService, SavedChatsComponent, WebSocketChatService, chatConfigSchema, deAssistant, enAssistant, frAssistant, globalSettingsSchema };
|
|
1911
|
+
//# sourceMappingURL=sinequa-assistant-chat.mjs.map
|