@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.
Files changed (53) hide show
  1. package/chat/chat-message/chat-message.component.d.ts +157 -0
  2. package/chat/chat-reference/chat-reference.component.d.ts +118 -0
  3. package/chat/chat-settings-v3/chat-settings-v3.component.d.ts +41 -0
  4. package/chat/chat.component.d.ts +1112 -0
  5. package/chat/chat.service.d.ts +1046 -0
  6. package/chat/format-icon/format-icon.component.d.ts +10 -0
  7. package/chat/format-icon/icons.d.ts +5 -0
  8. package/chat/index.d.ts +5 -0
  9. package/chat/initials-avatar/initials-avatar.component.d.ts +35 -0
  10. package/chat/instance-manager.service.d.ts +28 -0
  11. package/chat/messages/de.d.ts +4 -0
  12. package/chat/messages/en.d.ts +4 -0
  13. package/chat/messages/fr.d.ts +4 -0
  14. package/chat/messages/index.d.ts +4 -0
  15. package/chat/public-api.d.ts +11 -0
  16. package/chat/rest-chat.service.d.ts +28 -0
  17. package/chat/saved-chats/saved-chats.component.d.ts +37 -0
  18. package/chat/styles/assistant.scss +61 -0
  19. package/chat/styles/references.scss +23 -0
  20. package/chat/types.d.ts +1241 -0
  21. package/chat/websocket-chat.service.d.ts +97 -0
  22. package/esm2020/chat/chat-message/chat-message.component.mjs +181 -0
  23. package/esm2020/chat/chat-reference/chat-reference.component.mjs +40 -0
  24. package/esm2020/chat/chat-settings-v3/chat-settings-v3.component.mjs +103 -0
  25. package/esm2020/chat/chat.component.mjs +369 -0
  26. package/esm2020/chat/chat.service.mjs +185 -0
  27. package/esm2020/chat/format-icon/format-icon.component.mjs +23 -0
  28. package/esm2020/chat/format-icon/icons.mjs +138 -0
  29. package/esm2020/chat/initials-avatar/initials-avatar.component.mjs +60 -0
  30. package/esm2020/chat/instance-manager.service.mjs +46 -0
  31. package/esm2020/chat/messages/de.mjs +4 -0
  32. package/esm2020/chat/messages/en.mjs +4 -0
  33. package/esm2020/chat/messages/fr.mjs +4 -0
  34. package/esm2020/chat/messages/index.mjs +9 -0
  35. package/esm2020/chat/public-api.mjs +12 -0
  36. package/esm2020/chat/rest-chat.service.mjs +164 -0
  37. package/esm2020/chat/saved-chats/saved-chats.component.mjs +132 -0
  38. package/esm2020/chat/sinequa-assistant-chat.mjs +5 -0
  39. package/esm2020/chat/types.mjs +85 -0
  40. package/esm2020/chat/websocket-chat.service.mjs +430 -0
  41. package/esm2020/public-api.mjs +3 -0
  42. package/esm2020/sinequa-assistant.mjs +5 -0
  43. package/fesm2015/sinequa-assistant-chat.mjs +1925 -0
  44. package/fesm2015/sinequa-assistant-chat.mjs.map +1 -0
  45. package/fesm2015/sinequa-assistant.mjs +9 -0
  46. package/fesm2015/sinequa-assistant.mjs.map +1 -0
  47. package/fesm2020/sinequa-assistant-chat.mjs +1911 -0
  48. package/fesm2020/sinequa-assistant-chat.mjs.map +1 -0
  49. package/fesm2020/sinequa-assistant.mjs +9 -0
  50. package/fesm2020/sinequa-assistant.mjs.map +1 -0
  51. package/index.d.ts +5 -0
  52. package/package.json +46 -0
  53. package/public-api.d.ts +1 -0
@@ -0,0 +1,1925 @@
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
+ var _a, _b, _c;
322
+ const key = this.chatInstanceId;
323
+ const userSettingsConfig = this.assistants[key] || {};
324
+ const defaultChatConfig = (_c = (_b = (_a = this.appService.app) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.assistants) === null || _c === void 0 ? void 0 : _c[key];
325
+ // Validate the object against the schema
326
+ try {
327
+ chatConfigSchema.parse(defaultChatConfig);
328
+ // Merge configs with override desc order: user preferences > app config > default config
329
+ const chatConfig = Object.assign(Object.assign({}, defaultChatConfig), userSettingsConfig);
330
+ // Update the chat config and store it in the user preferences
331
+ this.updateChatConfig(chatConfig, false);
332
+ this.initConfig$.next(true);
333
+ }
334
+ catch (error) {
335
+ this.notificationsService.error(`Missing valid configuration for the assistant instance '${key}'`);
336
+ throw new Error(`Missing valid configuration for the assistant instance '${key}' . \n ${JSON.stringify(error.issues, null, 2)}`);
337
+ }
338
+ }
339
+ /**
340
+ * Update the chat config and store it in the user preferences
341
+ * @param config The updated chat config
342
+ * @param notify Whether to notify the user about the update
343
+ * @param successCallback The callback to execute if the update is successful
344
+ * @param errorCallback The callback to execute if the update fails
345
+ */
346
+ updateChatConfig(config, notify = true, successCallback, errorCallback) {
347
+ this.chatConfig$.next(config);
348
+ const assistants = Object.assign({}, this.assistants);
349
+ assistants[this.chatInstanceId] = config;
350
+ this.userSettingsService.patch({ assistants }).subscribe(next => {
351
+ if (notify) {
352
+ successCallback ? successCallback() : this.notificationsService.success(`The configuration of the assistant instance '${this.chatInstanceId}' has been successfully updated`);
353
+ }
354
+ }, error => {
355
+ if (notify) {
356
+ errorCallback ? errorCallback() : this.notificationsService.error(`The update of the assistant instance '${this.chatInstanceId}' configuration failed`);
357
+ }
358
+ console.error("Could not patch assistants!", error);
359
+ });
360
+ }
361
+ /**
362
+ * Get the model description for the given (serviceId + modelId)
363
+ * If a model is not found, an error message is returned
364
+ * @param serviceId The serviceId of the model
365
+ * @param modelId The modelId of the model
366
+ * @returns The model description
367
+ */
368
+ getModel(serviceId, modelId) {
369
+ var _a;
370
+ let model = (_a = this.models) === null || _a === void 0 ? void 0 : _a.find(m => m.serviceId === serviceId && m.modelId === modelId);
371
+ // Handle obsolete config
372
+ if (!model) {
373
+ this.notificationsService.error(`FATAL ERROR : The model (serviceId = '${serviceId}', modelId = '${modelId}') is no longer available. Please contact an admin for further information.`);
374
+ throw new Error(`FATAL ERROR : The model (serviceId = '${serviceId}', modelId = '${modelId}') is no longer available`);
375
+ }
376
+ return model;
377
+ }
378
+ // Todo: pending implementation
379
+ notifyAudit(messagesHistory, model) {
380
+ // let numberOfUserMessages = 0;
381
+ // let numberOfAttachments = 0;
382
+ // let numberOfAssistantMessages = 0;
383
+ // for(let m of messagesHistory) {
384
+ // if(m.$attachment) numberOfAttachments++;
385
+ // else if(m.role === 'user') numberOfUserMessages++;
386
+ // else if (m.role === 'assistant') numberOfAssistantMessages++;
387
+ // }
388
+ // this.auditService.notify({
389
+ // type: 'Chat_Messages',
390
+ // detail: {
391
+ // // message: messagesHistory.map(m => m.role.toUpperCase() + ': '+ (m.$attachment? `attachment ${m.$attachment?.$record.title}` : m.content)).join('\n\n'),
392
+ // message: messagesHistory.map(m => m.role.toUpperCase() + ': '+ m.content).join('\n\n'),
393
+ // // numberOfUserMessages,
394
+ // // numberOfAttachments,
395
+ // // numberOfAssistantMessages,
396
+ // model
397
+ // }
398
+ // });
399
+ }
400
+ /**
401
+ * Format a date string in UTC to a local date string
402
+ * @param value Date string in UTC to format
403
+ * @returns A formatted local date string
404
+ */
405
+ formatDateTime(value) {
406
+ return this.intlService["formatTime"](value, { day: "numeric", month: "short", year: "numeric" });
407
+ }
408
+ /**
409
+ * Takes a text prompt that may contain placeholders for variables
410
+ * and replaces these placeholders if it finds a match in the given
411
+ * context object.
412
+ */
413
+ static formatPrompt(prompt, context) {
414
+ return prompt.replace(/{{(.*?)}}/g, (match, expr) => { var _a; return (_a = get(context, expr)) !== null && _a !== void 0 ? _a : match; });
415
+ }
416
+ /**
417
+ * @returns A Globally Unique Identifier
418
+ */
419
+ static generateGUID() {
420
+ return Utils.guid();
421
+ }
422
+ }
423
+ ChatService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
424
+ ChatService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatService });
425
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatService, decorators: [{
426
+ type: Injectable
427
+ }] });
428
+
429
+ class WebSocketChatService extends ChatService {
430
+ constructor() {
431
+ super();
432
+ this.connectionBuilt$ = new Subject(); // Emit when the connection is built
433
+ this.connectionStarted$ = new Subject(); // Emit when the connection is started
434
+ this.messageHandlers = new Map();
435
+ this.actionMap = new Map();
436
+ this.content = "";
437
+ this.attachments = [];
438
+ this.signalRService = inject(SignalRWebService);
439
+ this.authenticationService = inject(AuthenticationService);
440
+ }
441
+ /**
442
+ * Initialize the chat process after the login is complete.
443
+ * It includes building and starting a connection, executing parallel requests for models and functions, and handling errors during the process.
444
+ *
445
+ * @returns An Observable<boolean> indicating the success of the initialization process.
446
+ */
447
+ init() {
448
+ return this.loginService.events.pipe(filter((e) => e.type === 'login-complete'), tap(() => this.getRequestsUrl()),
449
+ // Build the connection and handle the completion with the connectionBuilt$ subject
450
+ switchMap(() => this.buildConnection()), tap(() => {
451
+ this.initMessageHandlers();
452
+ this.connectionBuilt$.next();
453
+ }),
454
+ // Start the connection and handle the completion with the connectionStarted$ subject
455
+ switchMap(() => this.startConnection()),
456
+ // Execute parallel requests for models and functions
457
+ switchMap(() => {
458
+ this.connectionStarted$.next();
459
+ return forkJoin([
460
+ this.listModels(),
461
+ this.listFunctions()
462
+ ]);
463
+ }),
464
+ // Map the results of parallel requests to a boolean indicating success
465
+ map(([models, functions]) => {
466
+ this.initProcess$.next(true);
467
+ return !!models && !!functions;
468
+ }),
469
+ // Any errors during the process are caught, logged, and re-thrown to propagate the error further
470
+ catchError((error) => {
471
+ console.error('Error occurred:', error);
472
+ return throwError(() => error);
473
+ }),
474
+ // Cache and replay the emitted value for subsequent subscribers
475
+ shareReplay(1));
476
+ }
477
+ /**
478
+ * Define the assistant endpoint to use for the websocket requests
479
+ * It can be overridden by the app config
480
+ */
481
+ getRequestsUrl() {
482
+ if (this.chatConfig$.value.globalSettings.websocketEndpoint) {
483
+ this.REQUEST_URL = this.chatConfig$.value.globalSettings.websocketEndpoint;
484
+ }
485
+ else {
486
+ throw new Error(`The property 'websocketEndpoint' must be provided when attempting to use 'WebSocket' in assistant instance`);
487
+ }
488
+ }
489
+ listModels() {
490
+ const modelsSubject = new Subject();
491
+ this.connection.on('ListModels', (res) => {
492
+ var _a;
493
+ this.models = (_a = res.models) === null || _a === void 0 ? void 0 : _a.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: Object.assign(Object.assign({}, this.chatConfig$.value.contextSettings), { app: this.appService.appName, query })
532
+ };
533
+ if (this.chatConfig$.value.saveChats) {
534
+ data.instanceId = this.chatInstanceId;
535
+ data.savedChatId = this.savedChatId;
536
+ }
537
+ let response = { role: "assistant", content: "", additionalProperties: { display: true } }; // here display: true is needed in order to be able to show the progress
538
+ // Create a Subject to signal completion
539
+ const completion$ = new Subject();
540
+ // Create observables for each non-global handler in the messageHandlers map (default and eventual custom ones) once it is triggered by the hub connection
541
+ const observables = Array
542
+ .from(this.messageHandlers.entries())
543
+ .filter(([eventName, eventHandler]) => !eventHandler.isGlobalHandler)
544
+ .map(([eventName, eventHandler]) => {
545
+ return fromEvent(this.connection, eventName).pipe(map((event) => eventHandler.handler(event)) // Execute the corresponding handler
546
+ );
547
+ });
548
+ // Then merge them into a single observable in order to simulate the streaming behavior
549
+ const combined$ = merge(...observables).pipe(takeUntil(completion$) // Complete the observable when completion$ emits
550
+ );
551
+ // Invoke the Chat method
552
+ this.connection.invoke('Chat', data)
553
+ .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
554
+ .catch(error => {
555
+ console.error('Error invoking Chat:', error);
556
+ return Promise.resolve(); // Return a resolved promise to handle the error and prevent unhandled promise rejection
557
+ })
558
+ .finally(() => {
559
+ this.streaming$.next(false); // Complete streaming regardless of success or error
560
+ this.actionMap.clear(); // Clear the actionMap
561
+ this.content = ""; // Clear the content
562
+ this.attachments = []; // Clear the attachments
563
+ this.executionTime = ""; // Clear the executionTime
564
+ completion$.next(); // Emit a signal to complete the observables
565
+ completion$.complete(); // Complete the subject
566
+ });
567
+ // Return the merged observables
568
+ return combined$.pipe(map(() => {
569
+ // Define $progress from the actionMap
570
+ const actions = Array.from(this.actionMap.values());
571
+ const $progress = actions.length > 0
572
+ ? actions.map((a) => {
573
+ var _a, _b;
574
+ return ({
575
+ title: (_a = a.displayName) !== null && _a !== void 0 ? _a : "",
576
+ content: (_b = a.displayValue) !== null && _b !== void 0 ? _b : "",
577
+ done: a.executionTime !== undefined,
578
+ time: a.executionTime,
579
+ });
580
+ })
581
+ : undefined;
582
+ // Define the attachment used in the context of the response message
583
+ const $attachment = this.attachments;
584
+ // As soon as the first content or $progress is defined, the assistant is considered as streaming
585
+ if (!!this.content || $progress || $attachment.length > 0) {
586
+ response = Object.assign(Object.assign({}, response), { content: this.content, additionalProperties: Object.assign(Object.assign({}, response.additionalProperties), { $progress, $attachment }) });
587
+ }
588
+ // Return the result
589
+ return { history: [...messages, response], executionTime: this.executionTime };
590
+ }));
591
+ }
592
+ listSavedChat() {
593
+ const data = {
594
+ instanceId: this.chatInstanceId,
595
+ debug: this.chatConfig$.value.debug
596
+ };
597
+ this.connection.on('SavedChatList', (res) => {
598
+ this.savedChats$.next(res.savedChats); // emits the result to the savedChats$ subject
599
+ });
600
+ // Invoke the method SavedChatList
601
+ this.connection.invoke('SavedChatList', data)
602
+ .catch(error => {
603
+ console.error('Error invoking SavedChatList:', error);
604
+ return Promise.resolve();
605
+ });
606
+ }
607
+ getSavedChat(id) {
608
+ const savedChatSubject = new Subject();
609
+ const data = {
610
+ instanceId: this.chatInstanceId,
611
+ savedChatId: id,
612
+ debug: this.chatConfig$.value.debug
613
+ };
614
+ this.connection.on('SavedChatGet', (res) => {
615
+ savedChatSubject.next(res.savedChat);
616
+ savedChatSubject.complete();
617
+ });
618
+ // Invoke the method SavedChatGet
619
+ this.connection.invoke('SavedChatGet', data)
620
+ .catch(error => {
621
+ console.error('Error invoking SavedChatGet:', error);
622
+ savedChatSubject.complete();
623
+ return Promise.resolve();
624
+ });
625
+ return savedChatSubject.asObservable();
626
+ }
627
+ deleteSavedChat(ids) {
628
+ const deleteSavedChatSubject = new Subject();
629
+ const data = {
630
+ instanceId: this.chatInstanceId,
631
+ SavedChatIds: ids,
632
+ debug: this.chatConfig$.value.debug
633
+ };
634
+ this.connection.on('SavedChatDelete', (res) => {
635
+ deleteSavedChatSubject.next(res.deleteCount);
636
+ deleteSavedChatSubject.complete();
637
+ });
638
+ // Invoke the method SavedChatDelete
639
+ this.connection.invoke('SavedChatDelete', data)
640
+ .catch(error => {
641
+ console.error('Error invoking SavedChatDelete:', error);
642
+ deleteSavedChatSubject.complete();
643
+ return Promise.resolve();
644
+ });
645
+ return deleteSavedChatSubject.asObservable();
646
+ }
647
+ /**
648
+ * Initialize out-of-the-box handlers
649
+ * 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
650
+ */
651
+ initMessageHandlers() {
652
+ this.addMessageHandler("Debug", { handler: (debug) => console.log(debug),
653
+ isGlobalHandler: true });
654
+ this.addMessageHandler("ActionStart", { handler: (action) => this.actionMap.set(action.guid, action),
655
+ isGlobalHandler: false });
656
+ this.addMessageHandler("ActionResult", {
657
+ handler: (action) => this.actionMap.set(action.guid, Object.assign(Object.assign({}, this.actionMap.get(action.guid)), action)),
658
+ isGlobalHandler: false
659
+ });
660
+ this.addMessageHandler("ActionStop", {
661
+ handler: (action) => this.actionMap.set(action.guid, Object.assign(Object.assign({}, this.actionMap.get(action.guid)), action)),
662
+ isGlobalHandler: false
663
+ });
664
+ this.addMessageHandler("ContextMessage", {
665
+ handler: (message) => this.attachments.push(message.metadata),
666
+ isGlobalHandler: false
667
+ });
668
+ this.addMessageHandler("Message", {
669
+ handler: (message) => this.content += message !== null && message !== void 0 ? message : "",
670
+ isGlobalHandler: false
671
+ });
672
+ this.addMessageHandler("History", {
673
+ handler: (history) => {
674
+ this.chatHistory = history.history;
675
+ this.executionTime = history.executionTime;
676
+ },
677
+ isGlobalHandler: false
678
+ });
679
+ this.addMessageHandler("Error", {
680
+ handler: (error) => {
681
+ console.error(error);
682
+ this.notificationsService.error(error);
683
+ },
684
+ isGlobalHandler: true
685
+ });
686
+ this.addMessageHandler("Quota", {
687
+ handler: (message) => {
688
+ if (message.quota.maxQuotaReached) {
689
+ const msg = `Sorry, you have exceeded the allowed quota. Please retry starting from ${this.formatDateTime(message.quota.nextResetUTC)}.`;
690
+ console.error(msg);
691
+ this.notificationsService.error(msg);
692
+ }
693
+ },
694
+ isGlobalHandler: true
695
+ });
696
+ }
697
+ /**
698
+ * Override and register the entire messageHandlers map by merging the provided map with the default one
699
+ * @param messageHandlers
700
+ */
701
+ overrideMessageHandlers(messageHandlers) {
702
+ // Clear the already registered global chat handlers before merging the new ones
703
+ this.messageHandlers.forEach((eventHandler, eventName) => {
704
+ if (eventHandler.isGlobalHandler) {
705
+ this.unsubscribeMessageHandler(eventName);
706
+ }
707
+ });
708
+ // Merge the new event handlers with the existing ones
709
+ this.messageHandlers = new Map([...this.messageHandlers, ...messageHandlers]);
710
+ // Register the global chat handlers among the merged map
711
+ this.messageHandlers.forEach((eventHandler, eventName) => {
712
+ if (eventHandler.isGlobalHandler) {
713
+ this.registerMessageHandler(eventName, eventHandler);
714
+ }
715
+ });
716
+ }
717
+ /**
718
+ * Add a listener for a specific event.
719
+ * If a listener for this same event already exists, it will be overridden.
720
+ * If the listener has "isChatGlobalHandler" set to true, it will be registered to the hub connection.
721
+ * @param eventName Name of the event to register a listener for
722
+ * @param eventHandler The handler to be called when the event is received
723
+ */
724
+ addMessageHandler(eventName, eventHandler) {
725
+ this.messageHandlers.set(eventName, eventHandler);
726
+ if (eventHandler.isGlobalHandler) {
727
+ this.registerMessageHandler(eventName, eventHandler);
728
+ }
729
+ }
730
+ /**
731
+ * Dynamically register a listener for a specific event.
732
+ * If a listener for this event already exists, it will be overridden.
733
+ * @param eventName Name of the event to register a listener for
734
+ * @param eventHandler The handler to be called when the event is received
735
+ */
736
+ registerMessageHandler(eventName, eventHandler) {
737
+ if (!this.connection) {
738
+ console.log("No connection found to register the listener" + eventName);
739
+ return;
740
+ }
741
+ this.connection.on(eventName, (data) => {
742
+ eventHandler.handler(data);
743
+ });
744
+ }
745
+ /**
746
+ * Remove a listener for a specific event from the messageHandlers map and unsubscribe from receiving messages for this event from the SignalR hub.
747
+ * @param eventName Name of the event to remove the listener for
748
+ */
749
+ removeMessageHandler(eventName) {
750
+ this.messageHandlers.delete(eventName);
751
+ this.unsubscribeMessageHandler(eventName);
752
+ }
753
+ /**
754
+ * Unsubscribe from receiving messages for a specific event from the SignalR hub.
755
+ * ALL its related listeners will be removed from hub connection
756
+ * This is needed to prevent accumulating old listeners when overriding the entire messageHandlers map
757
+ * @param eventName Name of the event
758
+ */
759
+ unsubscribeMessageHandler(eventName) {
760
+ this.connection.off(eventName);
761
+ }
762
+ /**
763
+ * Build a connection to the signalR websocket and register default listeners to the methods defined in the server hub class
764
+ * @param options The options for the connection. It overrides the default options
765
+ * @param logLevel Define the log level displayed in the console
766
+ * @returns Promise that resolves when the connection is built
767
+ */
768
+ buildConnection(options) {
769
+ return new Promise((resolve, reject) => {
770
+ if (!this.REQUEST_URL) {
771
+ reject(new Error("No endpoint provided to connect the websocket to"));
772
+ return;
773
+ }
774
+ const logLevel = this.getLogLevel();
775
+ this.connection = this.signalRService.buildConnection(this.REQUEST_URL, Object.assign(Object.assign({}, this.defaultOptions), options), logLevel);
776
+ resolve();
777
+ });
778
+ }
779
+ /**
780
+ * Start the connection
781
+ * @returns Promise that resolves when the connection is started
782
+ */
783
+ startConnection() {
784
+ return this.signalRService.startConnection(this.connection);
785
+ }
786
+ /**
787
+ * Stop the connection
788
+ * @returns Promise that resolves when the connection is stopped
789
+ */
790
+ stopConnection() {
791
+ return this.signalRService.stopConnection(this.connection);
792
+ }
793
+ getTransports() {
794
+ var _a;
795
+ switch ((_a = this.chatConfig$.value) === null || _a === void 0 ? void 0 : _a.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
+ var _a;
808
+ switch ((_a = this.chatConfig$.value) === null || _a === void 0 ? void 0 : _a.globalSettings.signalRLogLevel) {
809
+ case "Critical":
810
+ return LogLevel.Critical; // Log level for diagnostic messages that indicate a failure that will terminate the entire application.
811
+ case "Debug":
812
+ return LogLevel.Debug; // Log level for low severity diagnostic messages.
813
+ case "Error":
814
+ return LogLevel.Error; // Log level for diagnostic messages that indicate a failure in the current operation.
815
+ case "Information":
816
+ return LogLevel.Information; // Log level for informational diagnostic messages.
817
+ case "None":
818
+ return LogLevel.None; // The highest possible log level. Used when configuring logging to indicate that no log messages should be emitted.
819
+ case "Trace":
820
+ return LogLevel.Trace; // Log level for very low severity diagnostic messages.
821
+ case "Warning":
822
+ return LogLevel.Warning; // Log level for diagnostic messages that indicate a non-fatal problem.
823
+ default:
824
+ return LogLevel.None; // The highest possible log level. Used when configuring logging to indicate that no log messages should be emitted.
825
+ }
826
+ }
827
+ get defaultOptions() {
828
+ let headers = {
829
+ "sinequa-force-camel-case": "true",
830
+ "x-language": this.intlService.currentLocale.name,
831
+ "ui-language": this.intlService.currentLocale.name,
832
+ };
833
+ if (this.authenticationService.processedCredentials) {
834
+ headers = Object.assign(Object.assign({}, headers), { "sinequa-csrf-token": this.authenticationService.processedCredentials.data.csrfToken });
835
+ }
836
+ ;
837
+ // For the first GET request sent by signalR to start a WebSocket protocol,
838
+ // as far as we know, signalR only lets us tweak the request with this access token factory
839
+ // so we pass along the Sinequa CSRF token to pass the CSRF check..
840
+ return {
841
+ transport: this.getTransports(),
842
+ withCredentials: true,
843
+ headers,
844
+ accessTokenFactory: () => { var _a, _b; return ((_b = (_a = this.authenticationService.processedCredentials) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.csrfToken) || ""; }
845
+ };
846
+ }
847
+ }
848
+ WebSocketChatService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: WebSocketChatService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
849
+ WebSocketChatService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: WebSocketChatService });
850
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: WebSocketChatService, decorators: [{
851
+ type: Injectable
852
+ }], ctorParameters: function () { return []; } });
853
+
854
+ class InitialsAvatarComponent {
855
+ constructor() {
856
+ this.fullName = '';
857
+ this.size = 1.5; // in rem
858
+ }
859
+ /**
860
+ * Gives initials of a name and a safe color background to use,
861
+ * assuming text color will be white
862
+ * @param fullName full name to evaluate intials for
863
+ * @param split string to use has splitter for `fullName`
864
+ * @returns an `object` containing initials and color
865
+ */
866
+ getInitialsAndColorFromFullName(fullName, split = ' ') {
867
+ return { initials: this.getInitialsFromFullName(fullName, split), color: this.getColorFromName(fullName) };
868
+ }
869
+ /**
870
+ * Gives initials of a name, ie:
871
+ * ```
872
+ * getInitialForFullName('John Snow') => 'JS'
873
+ * getInitialForFullName('Geralt of Rivia', ' of ') => 'GR'
874
+ * ```
875
+ * @param fullName full name to evaluate intial for
876
+ * @param split string to use has splitter
877
+ * @returns string containg the first letter of splitted name
878
+ */
879
+ getInitialsFromFullName(fullName, split = ' ') {
880
+ var _a, _b;
881
+ if (!fullName)
882
+ return '';
883
+ const names = fullName.split(split);
884
+ return names[0][0] + ((_b = (_a = names[1]) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : '');
885
+ }
886
+ /**
887
+ * Gets a random color using text as seed
888
+ * @param text string to use for color generation
889
+ * @returns string formatted like `rgb(xxx, xxx, xxx)`
890
+ */
891
+ getColorFromName(text) {
892
+ const safeColor = new SafeColor({
893
+ color: [255, 255, 255],
894
+ contrast: 4.5
895
+ });
896
+ return safeColor.random(text);
897
+ }
898
+ }
899
+ InitialsAvatarComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: InitialsAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
900
+ 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" }] });
901
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: InitialsAvatarComponent, decorators: [{
902
+ type: Component,
903
+ 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"] }]
904
+ }], propDecorators: { fullName: [{
905
+ type: Input
906
+ }], size: [{
907
+ type: Input
908
+ }] } });
909
+
910
+ const defaultFormatIcons = {
911
+ "extractslocations": { icon: "far fa-file-alt" },
912
+ "matchlocations": { icon: "far fa-flag" },
913
+ "geo": { icon: "fas fa-map-marker-alt" },
914
+ "person": { icon: "fas fa-user" },
915
+ "company": { icon: "fas fa-building" },
916
+ "title": { icon: "fas fa-tag" },
917
+ "modified": { icon: "far fa-calendar-alt" },
918
+ "size": { icon: "fas fa-weight-hanging" },
919
+ "treepath": { icon: "fas fa-folder-open" },
920
+ "filename": { icon: "far fa-file-alt" },
921
+ "authors": { icon: "fas fa-user-edit" },
922
+ "accesslists": { icon: "fas fa-lock" },
923
+ "doctype": { icon: "far fa-file" },
924
+ "documentlanguages": { icon: "fas fa-globe-americas" },
925
+ "globalrelevance": { icon: "far fa-star" },
926
+ "indexationtime": { icon: "fas fa-search" },
927
+ "concepts": { icon: "far fa-comment-dots" },
928
+ "keywords": { icon: "fas fa-tags" },
929
+ "matchingpartnames": { icon: "fas fa-align-left" },
930
+ "msgfrom": { icon: "fas fa-envelope" },
931
+ "msgto": { icon: "fas fa-envelope-open-text" },
932
+ "file": { icon: "far fa-file" },
933
+ "htm": { icon: "fas fa-globe-europe", color: '#4545bf' },
934
+ "html": { icon: "fas fa-globe-europe", color: '#4545bf' },
935
+ "xhtm": { icon: "fas fa-globe-europe", color: '#4545bf' },
936
+ "xhtml": { icon: "fas fa-globe-europe", color: '#4545bf' },
937
+ "mht": { icon: "fas fa-globe-europe", color: '#4545bf' },
938
+ "doc": { icon: "far fa-file-word", color: '#3f3fca' },
939
+ "docx": { icon: "far fa-file-word", color: '#3f3fca' },
940
+ "docm": { icon: "far fa-file-word", color: '#3f3fca' },
941
+ "dot": { icon: "far fa-file-word", color: '#3f3fca' },
942
+ "dotx": { icon: "far fa-file-word", color: '#3f3fca' },
943
+ "dotm": { icon: "far fa-file-word", color: '#3f3fca' },
944
+ "rtf": { icon: "far fa-file-word", color: '#3f3fca' },
945
+ "odt": { icon: "far fa-file-word", color: 'grey' },
946
+ "ott": { icon: "far fa-file-word", color: 'grey' },
947
+ "gdoc": { icon: "far fa-file-word", color: 'blue' },
948
+ "xls": { icon: "far fa-file-excel", color: 'green' },
949
+ "xlsx": { icon: "far fa-file-excel", color: 'green' },
950
+ "xlt": { icon: "far fa-file-excel", color: 'green' },
951
+ "xltx": { icon: "far fa-file-excel", color: 'green' },
952
+ "xlsm": { icon: "far fa-file-excel", color: 'green' },
953
+ "xltm": { icon: "far fa-file-excel", color: 'green' },
954
+ "gsheet": { icon: "far fa-file-excel", color: 'darkgreen' },
955
+ "ods": { icon: "far fa-file-excel", color: 'lightgreen' },
956
+ "ots": { icon: "far fa-file-excel", color: 'lightgreen' },
957
+ "ppt": { icon: "far fa-file-powerpoint", color: '#e64b30' },
958
+ "pptx": { icon: "far fa-file-powerpoint", color: '#e64b30' },
959
+ "pptm": { icon: "far fa-file-powerpoint", color: '#e64b30' },
960
+ "pptm2": { icon: "far fa-file-powerpoint", color: '#e64b30' },
961
+ "pps": { icon: "far fa-file-powerpoint", color: '#e64b30' },
962
+ "ppsx": { icon: "far fa-file-powerpoint", color: '#e64b30' },
963
+ "ppsm": { icon: "far fa-file-powerpoint", color: '#e64b30' },
964
+ "pot": { icon: "far fa-file-powerpoint", color: '#e64b30' },
965
+ "potx": { icon: "far fa-file-powerpoint", color: '#e64b30' },
966
+ "potm": { icon: "far fa-file-powerpoint", color: '#e64b30' },
967
+ "odp": { icon: "far fa-file-powerpoint", color: 'red' },
968
+ "otp": { icon: "far fa-file-powerpoint", color: 'red' },
969
+ "gslides": { icon: "far fa-file-powerpoint", color: 'orange' },
970
+ "pdf": { icon: "far fa-file-pdf", color: '#ec2e2e' },
971
+ "jpg": { icon: "far fa-file-image", color: '#4545bf' },
972
+ "jpeg": { icon: "far fa-file-image", color: '#4545bf' },
973
+ "bmp": { icon: "far fa-file-image", color: '#4545bf' },
974
+ "tiff": { icon: "far fa-file-image", color: '#4545bf' },
975
+ "tif": { icon: "far fa-file-image", color: '#4545bf' },
976
+ "gif": { icon: "far fa-file-image", color: '#4545bf' },
977
+ "png": { icon: "far fa-file-image", color: '#4545bf' },
978
+ "mp4": { icon: "far fa-file-video", color: '#4545bf' },
979
+ "flv": { icon: "far fa-file-video", color: '#4545bf' },
980
+ "swf": { icon: "far fa-file-video", color: '#4545bf' },
981
+ "mts": { icon: "far fa-file-video", color: '#4545bf' },
982
+ "divx": { icon: "far fa-file-video", color: '#4545bf' },
983
+ "wmv": { icon: "far fa-file-video", color: '#4545bf' },
984
+ "avi": { icon: "far fa-file-video", color: '#4545bf' },
985
+ "mov": { icon: "far fa-file-video", color: '#4545bf' },
986
+ "mpg": { icon: "far fa-file-video", color: '#4545bf' },
987
+ "mpeg": { icon: "far fa-file-video", color: '#4545bf' },
988
+ "asf": { icon: "far fa-file-video", color: '#4545bf' },
989
+ "rm": { icon: "far fa-file-video", color: '#4545bf' },
990
+ "mp3": { icon: "far fa-file-audio", color: 'lightblue' },
991
+ "wav": { icon: "far fa-file-audio", color: 'lightblue' },
992
+ "ogg": { icon: "far fa-file-audio", color: 'lightblue' },
993
+ "wma": { icon: "far fa-file-audio", color: 'lightblue' },
994
+ "aac": { icon: "far fa-file-audio", color: 'lightblue' },
995
+ "m3u": { icon: "far fa-file-audio", color: 'lightblue' },
996
+ "txt": { icon: "far fa-file-alt", color: '#202020' },
997
+ "text": { icon: "far fa-file-alt", color: '#202020' },
998
+ "xml": { icon: "far fa-file-code", color: '#4545bf' },
999
+ "cs": { icon: "far fa-file-code", color: '#4545bf' },
1000
+ "java": { icon: "far fa-file-code", color: '#4545bf' },
1001
+ "cpp": { icon: "far fa-file-code", color: '#4545bf' },
1002
+ "c": { icon: "far fa-file-code", color: '#4545bf' },
1003
+ "h": { icon: "far fa-file-code", color: '#4545bf' },
1004
+ "hpp": { icon: "far fa-file-code", color: '#4545bf' },
1005
+ "js": { icon: "far fa-file-code", color: '#4545bf' },
1006
+ "ts": { icon: "far fa-file-code", color: '#4545bf' },
1007
+ "zip": { icon: "far fa-file-archive", color: 'yellow' },
1008
+ "7zip": { icon: "far fa-file-archive", color: 'yellow' },
1009
+ "7z": { icon: "far fa-file-archive", color: 'yellow' },
1010
+ "rar": { icon: "far fa-file-archive", color: 'yellow' },
1011
+ "gz": { icon: "far fa-file-archive", color: 'yellow' },
1012
+ "notes": { icon: "fas fa-file-invoice", color: 'orange' },
1013
+ "quickr": { icon: "fas fa-file-invoice", color: 'orange' },
1014
+ "email": { icon: "far fa-envelope", color: 'black' },
1015
+ "mail": { icon: "far fa-envelope", color: 'black' },
1016
+ "msg": { icon: "far fa-envelope", color: 'black' },
1017
+ "mdb": { icon: "far fa-database", color: 'purple' },
1018
+ "odb": { icon: "far fa-database", color: 'darkred' },
1019
+ "otb": { icon: "far fa-database", color: 'darkred' },
1020
+ "xsn": { icon: "fas fa-file-excel", color: 'purple' },
1021
+ "gform": { icon: "fas fa-file-excel", color: 'purple' },
1022
+ "one": { icon: "far fa-book", color: 'purple' },
1023
+ "odf": { icon: "fas fa-file-medical-alt", color: 'grey' },
1024
+ "otf": { icon: "fas fa-file-medical-alt", color: 'grey' },
1025
+ "vsdx": { icon: "far fa-object-group", color: 'purple' },
1026
+ "vsx": { icon: "far fa-object-group", color: 'purple' },
1027
+ "vtx": { icon: "far fa-object-group", color: 'purple' },
1028
+ "vdx": { icon: "far fa-object-group", color: 'purple' },
1029
+ "vssx": { icon: "far fa-object-group", color: 'purple' },
1030
+ "vstx": { icon: "far fa-object-group", color: 'purple' },
1031
+ "vsdm": { icon: "far fa-object-group", color: 'purple' },
1032
+ "vssm": { icon: "far fa-object-group", color: 'purple' },
1033
+ "vstm": { icon: "far fa-object-group", color: 'purple' },
1034
+ "vdw": { icon: "far fa-object-group", color: 'purple' },
1035
+ "vsd": { icon: "far fa-object-group", color: 'purple' },
1036
+ "vss": { icon: "far fa-object-group", color: 'purple' },
1037
+ "vst": { icon: "far fa-object-group", color: 'purple' },
1038
+ "odg": { icon: "far fa-object-group", color: 'orange' },
1039
+ "otg": { icon: "far fa-object-group", color: 'orange' },
1040
+ "gdraw": { icon: "far fa-object-group", color: 'red' },
1041
+ "pub": { icon: "far fa-object-group", color: 'darkgreen' },
1042
+ "ldap": { icon: "far fa-users", color: 'brown' },
1043
+ "ad": { icon: "far fa-users", color: 'brown' },
1044
+ "mmp": { icon: "fas fa-file-medical", color: 'grey' },
1045
+ "mppx": { icon: "fas fa-file-medical", color: 'grey' },
1046
+ };
1047
+
1048
+ class FormatIconComponent {
1049
+ constructor() {
1050
+ this._formatIcons = defaultFormatIcons;
1051
+ }
1052
+ ngOnChanges() {
1053
+ const icon = this.extension ? this._formatIcons[this.extension] : undefined;
1054
+ this.icon = (icon === null || icon === void 0 ? void 0 : icon.icon) || this._formatIcons.file.icon;
1055
+ }
1056
+ }
1057
+ FormatIconComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: FormatIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1058
+ 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 });
1059
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: FormatIconComponent, decorators: [{
1060
+ type: Component,
1061
+ args: [{ selector: 'sq-format-icon', encapsulation: ViewEncapsulation.None, standalone: true, imports: [CommonModule], template: "<span *ngIf=\"icon\" class=\"{{icon}}\"></span>" }]
1062
+ }], propDecorators: { extension: [{
1063
+ type: Input
1064
+ }] } });
1065
+
1066
+ class ChatReferenceComponent {
1067
+ constructor() {
1068
+ this.openDocument = new EventEmitter();
1069
+ this.openPreview = new EventEmitter();
1070
+ }
1071
+ get parts() {
1072
+ if (!this.attachment)
1073
+ return [];
1074
+ return this.attachment.parts.filter(part => (!this.partId || part.partId === this.partId) && !!part.text);
1075
+ }
1076
+ expandAttachment() {
1077
+ if (this.partId)
1078
+ return;
1079
+ this.attachment['$expanded'] = !this.attachment['$expanded'];
1080
+ }
1081
+ }
1082
+ ChatReferenceComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatReferenceComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1083
+ 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"] }] });
1084
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatReferenceComponent, decorators: [{
1085
+ type: Component,
1086
+ 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"] }]
1087
+ }], propDecorators: { reference: [{
1088
+ type: Input
1089
+ }], attachment: [{
1090
+ type: Input
1091
+ }], partId: [{
1092
+ type: Input
1093
+ }], openDocument: [{
1094
+ type: Output
1095
+ }], openPreview: [{
1096
+ type: Output
1097
+ }] } });
1098
+
1099
+ class ChatMessageComponent {
1100
+ constructor(searchService, ui, principalService, cdr, el) {
1101
+ this.searchService = searchService;
1102
+ this.ui = ui;
1103
+ this.principalService = principalService;
1104
+ this.cdr = cdr;
1105
+ this.el = el;
1106
+ this.canEdit = false;
1107
+ this.canRegenerate = false;
1108
+ this.canCopy = false;
1109
+ this.referenceClicked = new EventEmitter();
1110
+ this.edit = new EventEmitter();
1111
+ this.regenerate = new EventEmitter();
1112
+ this.openPreview = new EventEmitter();
1113
+ this.references = [];
1114
+ this.referenceMap = new Map();
1115
+ this.showReferences = true;
1116
+ /**
1117
+ * This Unified plugin looks a text nodes and replaces any reference in the
1118
+ * form [1], [2.3], etc. with custom nodes of type "chat-reference".
1119
+ */
1120
+ this.referencePlugin = (tree) => {
1121
+ const references = new Set();
1122
+ // Visit all text nodes
1123
+ visit(tree, "text", (node, index, parent) => {
1124
+ var _a;
1125
+ let text = node.value;
1126
+ text = this.reformatReferences(text);
1127
+ const matches = this.getReferenceMatches(text);
1128
+ // Quit if no references were found
1129
+ if (matches.length === 0) {
1130
+ return CONTINUE;
1131
+ }
1132
+ const nodes = [];
1133
+ for (let match of matches) {
1134
+ const refId = match[1].trim();
1135
+ const [ref] = refId.split(".");
1136
+ // We find a valid reference in the text
1137
+ if (!isNaN(+ref)) {
1138
+ references.add(+ref); // Add it to the set of used references
1139
+ // If needed, insert a text node before the reference
1140
+ const current = (_a = nodes.at(-1)) !== null && _a !== void 0 ? _a : { end: 0 };
1141
+ if (match.index > current.end) {
1142
+ nodes.push({ type: "text", value: text.substring(current.end, match.index), end: match.index });
1143
+ }
1144
+ // Add a custom reference node
1145
+ nodes.push({ type: "chat-reference", refId, end: match.index + match[0].length });
1146
+ }
1147
+ }
1148
+ // Quit if no references were found
1149
+ if (nodes.length === 0) {
1150
+ return CONTINUE;
1151
+ }
1152
+ if (nodes.at(-1).end < text.length) {
1153
+ nodes.push({ type: "text", value: text.substring(nodes.at(-1).end, text.length), end: text.length });
1154
+ }
1155
+ // Delete the current text node from the parent and replace it with the new nodes
1156
+ parent.children.splice(index, 1, ...nodes);
1157
+ return index + nodes.length; // Visit the next node after the inserted ones
1158
+ });
1159
+ if (references.size > 0) {
1160
+ this.references = Array.from(references.values())
1161
+ .sort((a, b) => a - b)
1162
+ .map(r => '' + r);
1163
+ this.cdr.detectChanges();
1164
+ }
1165
+ return tree;
1166
+ };
1167
+ this.placeholderPlugin = (tree) => {
1168
+ visit(tree, "text", (node, index, parent) => {
1169
+ parent.children.push({ type: "streaming-placeholder" });
1170
+ return EXIT;
1171
+ }, true);
1172
+ return tree;
1173
+ };
1174
+ }
1175
+ get name() {
1176
+ return !this.principalService.principal ? ''
1177
+ : this.principalService.principal['fullName'] || this.principalService.principal.name;
1178
+ }
1179
+ ngOnChanges(changes) {
1180
+ var _a;
1181
+ if (changes.streaming) {
1182
+ this.collapseProgress = !this.streaming;
1183
+ }
1184
+ if (changes.message && this.message.role === "assistant") {
1185
+ this.references = [];
1186
+ this.referenceMap.clear();
1187
+ for (let m of this.conversation) {
1188
+ if (m.additionalProperties.$attachment) {
1189
+ for (const attachment of m.additionalProperties.$attachment) {
1190
+ this.referenceMap.set('' + attachment.contextId, Object.assign(Object.assign({}, attachment), { $partId: (_a = attachment.parts) === null || _a === void 0 ? void 0 : _a[0].partId }));
1191
+ for (let i = 0; i < attachment.parts.length; i++) {
1192
+ const refId = `${attachment.contextId}.${attachment.parts[i].partId}`;
1193
+ this.referenceMap.set(refId, Object.assign(Object.assign({}, attachment), { $partId: attachment.parts[i].partId }));
1194
+ }
1195
+ }
1196
+ }
1197
+ }
1198
+ this.processor = unified()
1199
+ .use(remarkParse)
1200
+ .use(remarkGfm)
1201
+ .use(() => this.referencePlugin);
1202
+ if (this.streaming) {
1203
+ this.processor = this.processor.use(() => this.placeholderPlugin);
1204
+ }
1205
+ }
1206
+ }
1207
+ ngAfterViewInit() {
1208
+ var _a;
1209
+ (_a = Prism === null || Prism === void 0 ? void 0 : Prism.highlightAllUnder) === null || _a === void 0 ? void 0 : _a.call(Prism, this.el.nativeElement);
1210
+ }
1211
+ openDocument(record) {
1212
+ const url = record.url1 || record.originalUrl;
1213
+ if (url) {
1214
+ // Open the URL in a new tab
1215
+ window.open(url, '_blank');
1216
+ }
1217
+ this.referenceClicked.emit(record);
1218
+ }
1219
+ /**
1220
+ * Reformat [ids: 12.2, 42.5] to [12.2][42.5]
1221
+ */
1222
+ reformatReferences(content) {
1223
+ return content.replace(/\[(?:ids?:?\s*)?(?:documents?:?\s*)?(\s*(?:,?\s*\d+(?:\.\d+)?(?:\.part)?\s*)+)\]/g, (str, match) => `[${match.replace(/\.part/g, "").split(',').join("] [")}]`);
1224
+ }
1225
+ /**
1226
+ * Match all references in a given message
1227
+ */
1228
+ getReferenceMatches(content) {
1229
+ return Array.from(content.matchAll(/\[(\s*\d+(?:\.\d+)?\s*)\]/g));
1230
+ }
1231
+ copyToClipboard(text) {
1232
+ this.ui.copyToClipboard(text);
1233
+ }
1234
+ }
1235
+ 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 });
1236
+ 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 });
1237
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatMessageComponent, decorators: [{
1238
+ type: Component,
1239
+ args: [{ selector: "sq-chat-message", changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, UtilsModule, CollapseModule, RemarkModule,
1240
+ 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"] }]
1241
+ }], ctorParameters: function () { return [{ type: i1$1.SearchService }, { type: i2$1.UIService }, { type: i3.PrincipalWebService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }]; }, propDecorators: { message: [{
1242
+ type: Input
1243
+ }], conversation: [{
1244
+ type: Input
1245
+ }], assistantIcon: [{
1246
+ type: Input
1247
+ }], streaming: [{
1248
+ type: Input
1249
+ }], canEdit: [{
1250
+ type: Input
1251
+ }], canRegenerate: [{
1252
+ type: Input
1253
+ }], canCopy: [{
1254
+ type: Input
1255
+ }], referenceClicked: [{
1256
+ type: Output
1257
+ }], edit: [{
1258
+ type: Output
1259
+ }], regenerate: [{
1260
+ type: Output
1261
+ }], openPreview: [{
1262
+ type: Output
1263
+ }] } });
1264
+
1265
+ class RestChatService extends ChatService {
1266
+ constructor() {
1267
+ super();
1268
+ this.jsonMethodWebService = inject(JsonMethodPluginService);
1269
+ }
1270
+ /**
1271
+ * Initialize the chat process after the login is complete.
1272
+ * It listens for the 'login-complete' event, initializes necessary URL, and performs parallel requests for models, functions and quota data.
1273
+ * @returns An Observable<boolean> indicating the success of the initialization process.
1274
+ */
1275
+ init() {
1276
+ return this.loginService.events.pipe(filter((e) => e.type === 'login-complete'), tap(() => this.getRequestsUrl()),
1277
+ // Execute parallel requests for models and functions
1278
+ switchMap(() => forkJoin([
1279
+ this.listModels(),
1280
+ this.listFunctions()
1281
+ ])),
1282
+ // Map the results of parallel requests to a boolean indicating success
1283
+ map(([models, functions]) => !!models && !!functions),
1284
+ // Any errors during the process are caught, logged, and re-thrown to propagate the error further
1285
+ catchError((error) => {
1286
+ console.error('Error occurred:', error);
1287
+ return throwError(() => error);
1288
+ }),
1289
+ // cache and replay the emitted value for subsequent subscribers, ensuring the initialization logic is only executed once even if there are multiple subscribers
1290
+ shareReplay(1));
1291
+ }
1292
+ /**
1293
+ * Define the GLLM plugin to use for the http requests
1294
+ * It can be overridden by the app config
1295
+ */
1296
+ getRequestsUrl() {
1297
+ if (this.chatConfig$.value.globalSettings.restEndpoint) {
1298
+ this.REQUEST_URL = this.chatConfig$.value.globalSettings.restEndpoint;
1299
+ }
1300
+ else {
1301
+ throw new Error(`The property 'restEndpoint' must be provided when attempting to use 'REST' in assistant instance`);
1302
+ }
1303
+ }
1304
+ listModels() {
1305
+ const data = {
1306
+ action: "listmodels",
1307
+ debug: this.chatConfig$.value.debug
1308
+ };
1309
+ return this.jsonMethodWebService.get(this.REQUEST_URL, data).pipe(map(res => res.models), tap(models => this.models = models === null || models === void 0 ? void 0 : models.filter(model => !!model.enable)), catchError((error) => {
1310
+ console.error('Error invoking listmodels:', error);
1311
+ return throwError(() => error);
1312
+ }));
1313
+ }
1314
+ listFunctions() {
1315
+ const data = {
1316
+ action: "listfunctions",
1317
+ debug: this.chatConfig$.value.debug
1318
+ };
1319
+ return this.jsonMethodWebService.get(this.REQUEST_URL, data).pipe(map(res => res.functions), tap(functions => this.functions = functions), catchError((error) => {
1320
+ console.error('Error invoking listfunctions:', error);
1321
+ return throwError(() => error);
1322
+ }));
1323
+ }
1324
+ fetch(messages, query = this.searchService.query) {
1325
+ // Start streaming by invoking the Chat method
1326
+ this.streaming$.next(true);
1327
+ // Prepare the payload to send to the Chat method
1328
+ const data = {
1329
+ action: "chat",
1330
+ history: messages,
1331
+ functions: this.chatConfig$.value.functions,
1332
+ debug: this.chatConfig$.value.debug,
1333
+ serviceSettings: this.chatConfig$.value.serviceSettings,
1334
+ contextSettings: Object.assign(Object.assign({}, this.chatConfig$.value.contextSettings), { app: this.appService.appName, query })
1335
+ };
1336
+ if (this.chatConfig$.value.saveChats) {
1337
+ data.instanceId = this.chatInstanceId;
1338
+ data.savedChatId = this.savedChatId;
1339
+ }
1340
+ // Request the Chat endpoint
1341
+ return this.jsonMethodWebService.post(this.REQUEST_URL, data).pipe(tap((res) => {
1342
+ if (res.quota.maxQuotaReached) {
1343
+ const msg = `Sorry, you have exceeded the allowed quota. Please retry starting from ${this.formatDateTime(res.quota.nextResetUTC)}.`;
1344
+ this.notificationsService.error(msg);
1345
+ throw new Error(msg);
1346
+ }
1347
+ }), map((res) => {
1348
+ var _a;
1349
+ // Define $progress from the actions property of the response
1350
+ let $progress;
1351
+ if (((_a = res.actions) === null || _a === void 0 ? void 0 : _a.length) > 0) {
1352
+ const actions = Object.values(res.actions.reduce((acc, item) => {
1353
+ acc[item.guid] = Object.assign(Object.assign({}, (acc[item.guid] || {})), item);
1354
+ return acc;
1355
+ }, {}));
1356
+ $progress = actions.map((a) => {
1357
+ var _a, _b;
1358
+ return ({
1359
+ title: (_a = a.displayName) !== null && _a !== void 0 ? _a : "",
1360
+ content: (_b = a.displayValue) !== null && _b !== void 0 ? _b : "",
1361
+ done: a.executionTime !== undefined,
1362
+ time: a.executionTime,
1363
+ });
1364
+ });
1365
+ }
1366
+ // Define the response message
1367
+ const response = Object.assign(Object.assign({}, res.history.at(-1)), { additionalProperties: { display: true } });
1368
+ if ($progress) {
1369
+ response.additionalProperties.$progress = $progress;
1370
+ }
1371
+ if (res.context) {
1372
+ response.additionalProperties.$attachment = res.context.map((ctx) => ctx.additionalProperties);
1373
+ }
1374
+ // Update the chat history with the history property of the res
1375
+ this.chatHistory = res.history;
1376
+ // Return the result
1377
+ return { history: [...messages, response], executionTime: res.executionTime };
1378
+ }), tap(() => this.notifyAudit(this.chatHistory, this.chatConfig$.value.serviceSettings.service_id)), finalize(() => this.streaming$.next(false)));
1379
+ }
1380
+ listSavedChat() {
1381
+ const data = {
1382
+ action: "SavedChatList",
1383
+ instanceId: this.chatInstanceId,
1384
+ debug: this.chatConfig$.value.debug
1385
+ };
1386
+ this.jsonMethodWebService.get(this.REQUEST_URL, data).subscribe(res => this.savedChats$.next(res.savedChats), error => {
1387
+ console.error('Error occurred while calling the SavedChatList API:', error);
1388
+ this.notificationsService.error('Error occurred while calling the SavedChatList API');
1389
+ });
1390
+ }
1391
+ getSavedChat(id) {
1392
+ const data = {
1393
+ action: "SavedChatGet",
1394
+ instanceId: this.chatInstanceId,
1395
+ savedChatId: id,
1396
+ debug: this.chatConfig$.value.debug
1397
+ };
1398
+ return this.jsonMethodWebService.get(this.REQUEST_URL, data).pipe(map(res => res.savedChat), catchError((error) => {
1399
+ console.error('Error occurred while calling the SavedChatGet API:', error);
1400
+ this.notificationsService.error('Error occurred while calling the SavedChatGet API');
1401
+ return throwError(() => error);
1402
+ }));
1403
+ }
1404
+ deleteSavedChat(ids) {
1405
+ const data = {
1406
+ action: "SavedChatDelete",
1407
+ instanceId: this.chatInstanceId,
1408
+ savedChatIds: ids,
1409
+ debug: this.chatConfig$.value.debug
1410
+ };
1411
+ return this.jsonMethodWebService.post(this.REQUEST_URL, data).pipe(map(res => res.deletedCount), catchError((error) => {
1412
+ console.error('Error occurred while calling the SavedChatDelete API:', error);
1413
+ this.notificationsService.error('Error occurred while calling the SavedChatDelete API ');
1414
+ return throwError(() => error);
1415
+ }));
1416
+ }
1417
+ }
1418
+ RestChatService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: RestChatService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1419
+ RestChatService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: RestChatService });
1420
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: RestChatService, decorators: [{
1421
+ type: Injectable
1422
+ }], ctorParameters: function () { return []; } });
1423
+
1424
+ class ChatComponent extends AbstractFacet {
1425
+ constructor() {
1426
+ super();
1427
+ /** Define the protocol to be used for this chat instance*/
1428
+ this.protocol = "WEBSOCKET";
1429
+ /** Map of listeners overriding default registered ones*/
1430
+ this.messageHandlers = new Map();
1431
+ /** When the assistant answer a user question, automatically scroll down to the bottom of the discussion */
1432
+ this.automaticScrollToLastResponse = false;
1433
+ this.enableChat = true;
1434
+ this.showCredits = true;
1435
+ this.customAssistantIcon = '';
1436
+ this.data = new EventEmitter();
1437
+ this.referenceClicked = new EventEmitter();
1438
+ this.openPreview = new EventEmitter();
1439
+ this.loading$ = new EventEmitter(false);
1440
+ this.error = new EventEmitter();
1441
+ this._config = new EventEmitter();
1442
+ this.messages$ = new BehaviorSubject(undefined);
1443
+ this.question = '';
1444
+ this._actions = [];
1445
+ this.sub = new Subscription();
1446
+ this.changes$ = new BehaviorSubject(undefined);
1447
+ this.handleFirstChanges = false;
1448
+ this.isAtBottom = true;
1449
+ this.initializationError = false;
1450
+ this.loginService = inject(LoginService);
1451
+ this.websocketService = inject(WebSocketChatService);
1452
+ this.restService = inject(RestChatService);
1453
+ this.instanceManagerService = inject(InstanceManagerService);
1454
+ this.searchService = inject(SearchService);
1455
+ this.principalService = inject(PrincipalWebService);
1456
+ this.cdr = inject(ChangeDetectorRef);
1457
+ this._actions.push(new Action({
1458
+ icon: 'fas fa-sync',
1459
+ title: 'Reset chat',
1460
+ action: () => this.loadDefaultChat()
1461
+ }));
1462
+ }
1463
+ ngOnInit() {
1464
+ 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 => {
1465
+ this.config = config;
1466
+ this._config.emit(config);
1467
+ try {
1468
+ this.updateModelDescription();
1469
+ if (!this.handleFirstChanges) {
1470
+ this.handleChanges();
1471
+ this.addScrollListener();
1472
+ this.handleFirstChanges = true;
1473
+ }
1474
+ }
1475
+ catch (error) {
1476
+ this.initializationError = true;
1477
+ throw error;
1478
+ }
1479
+ })).subscribe());
1480
+ }
1481
+ ngOnChanges(changes) {
1482
+ this.changes$.next(changes);
1483
+ if (this.config) {
1484
+ this.handleChanges();
1485
+ }
1486
+ }
1487
+ ngOnDestroy() {
1488
+ var _a;
1489
+ this.sub.unsubscribe();
1490
+ (_a = this.dataSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
1491
+ }
1492
+ instantiateChatService() {
1493
+ switch (this.protocol) {
1494
+ case 'REST':
1495
+ this.chatService = this.restService;
1496
+ break;
1497
+ case 'WEBSOCKET':
1498
+ this.chatService = this.websocketService;
1499
+ break;
1500
+ default:
1501
+ throw new Error(`Could not found a ChatService implementation corresponding to the provided protocol: '${this.protocol}'`);
1502
+ }
1503
+ this.chatService.setChatInstanceId(this.instanceId);
1504
+ this.instanceManagerService.storeInstance(this.instanceId, this.chatService);
1505
+ }
1506
+ get actions() { return this._actions; }
1507
+ handleChanges() {
1508
+ const changes = this.changes$.value;
1509
+ if ((changes === null || changes === void 0 ? void 0 : changes.messageHandlers) && this.messageHandlers && this.chatService instanceof WebSocketChatService) {
1510
+ this.chatService.overrideMessageHandlers(this.messageHandlers);
1511
+ }
1512
+ if (!this.messages$.value || (changes === null || changes === void 0 ? void 0 : changes.chat)) {
1513
+ // Load the chat
1514
+ this.chat ? this.openChat(this.chat.messages) : this.loadDefaultChat();
1515
+ }
1516
+ }
1517
+ addScrollListener() {
1518
+ this.sub.add(merge(this.loading$, this.messages$, this.chatService.streaming$, fromEvent(this.messageList.nativeElement, 'scroll')).subscribe(() => {
1519
+ this.isAtBottom = this.toggleScrollButtonVisibility();
1520
+ this.cdr.detectChanges();
1521
+ }));
1522
+ }
1523
+ updateModelDescription() {
1524
+ var _a;
1525
+ this.modelDescription = this.chatService.getModel(this.config.serviceSettings.service_id, this.config.serviceSettings.model_id);
1526
+ this.assistantIcon = !!this.customAssistantIcon ? this.customAssistantIcon : 'sq-sinequa';
1527
+ switch ((_a = this.modelDescription) === null || _a === void 0 ? void 0 : _a.provider) {
1528
+ case 'Google':
1529
+ this.privacyUrl = '';
1530
+ break;
1531
+ case 'AzureOpenAI':
1532
+ this.privacyUrl = 'https://learn.microsoft.com/en-us/legal/cognitive-services/openai/data-privacy';
1533
+ break;
1534
+ case 'OpenAI':
1535
+ this.privacyUrl = 'https://openai.com/enterprise-privacy';
1536
+ break;
1537
+ case 'Cohere':
1538
+ this.privacyUrl = 'https://cohere.com/security';
1539
+ break;
1540
+ }
1541
+ this.cdr.detectChanges();
1542
+ }
1543
+ submitQuestion() {
1544
+ if (this.question.trim() && this.messages$.value && this.chatService.chatHistory) {
1545
+ if (this.messageToEdit !== undefined) {
1546
+ // Update the messages in the UI
1547
+ this.messages$.next(this.messages$.value.slice(0, this.messageToEdit));
1548
+ // Update the raw messages in the chat history which is the clean version used to make the next request
1549
+ this.chatService.chatHistory = this.chatService.chatHistory.slice(0, this.messageToEdit);
1550
+ this.messageToEdit = undefined;
1551
+ }
1552
+ // Re-attach the $progress and $attachment of the last response to the last assistant's response in the chat history
1553
+ this.chatService.chatHistory.at(-1).additionalProperties.$progress = this.messages$.value.at(-1).additionalProperties.$progress;
1554
+ this.chatService.chatHistory.at(-1).additionalProperties.$attachment = this.messages$.value.at(-1).additionalProperties.$attachment;
1555
+ // Fetch the answer
1556
+ this.fetchAnswer(this.question.trim(), this.chatService.chatHistory);
1557
+ // Clear the input value in the UI
1558
+ this.questionInput.nativeElement.value = '';
1559
+ }
1560
+ }
1561
+ fetchAnswer(question, conversation) {
1562
+ const userMsg = { role: 'user', content: question, additionalProperties: { display: true } };
1563
+ const messages = [...conversation, userMsg];
1564
+ this.messages$.next(messages);
1565
+ this.fetch(messages);
1566
+ }
1567
+ /**
1568
+ * Given a list of messages, fetch the server for a continuation and updates
1569
+ * the list of messages accordingly.
1570
+ * @param messages
1571
+ */
1572
+ fetch(messages) {
1573
+ var _a;
1574
+ this.cdr.detectChanges();
1575
+ this.loading$.next(true);
1576
+ (_a = this.dataSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
1577
+ this.dataSubscription = this.chatService.fetch(messages, this.query)
1578
+ .subscribe({
1579
+ next: res => this.updateData(res.history),
1580
+ error: err => {
1581
+ this.terminateFetch();
1582
+ console.error(err);
1583
+ this.error.emit(err);
1584
+ },
1585
+ complete: () => {
1586
+ this.terminateFetch();
1587
+ }
1588
+ });
1589
+ if (this.automaticScrollToLastResponse) {
1590
+ this.scrollDown();
1591
+ }
1592
+ }
1593
+ /**
1594
+ * Update the UI with the new messages
1595
+ * @param messages
1596
+ */
1597
+ updateData(messages) {
1598
+ this.messages$.next(messages);
1599
+ this.data.emit(messages);
1600
+ this.loading$.next(false);
1601
+ this.question = '';
1602
+ if (this.automaticScrollToLastResponse) {
1603
+ this.scrollDown();
1604
+ }
1605
+ }
1606
+ toggleScrollButtonVisibility() {
1607
+ var _a, _b, _c, _d;
1608
+ if ((_a = this.messageList) === null || _a === void 0 ? void 0 : _a.nativeElement) {
1609
+ return Math.round(((_b = this.messageList) === null || _b === void 0 ? void 0 : _b.nativeElement.scrollHeight) - ((_c = this.messageList) === null || _c === void 0 ? void 0 : _c.nativeElement.scrollTop) - 1) <= ((_d = this.messageList) === null || _d === void 0 ? void 0 : _d.nativeElement.clientHeight);
1610
+ }
1611
+ return true;
1612
+ }
1613
+ scrollDown() {
1614
+ setTimeout(() => {
1615
+ var _a;
1616
+ if ((_a = this.messageList) === null || _a === void 0 ? void 0 : _a.nativeElement) {
1617
+ this.messageList.nativeElement.scrollTop = this.messageList.nativeElement.scrollHeight;
1618
+ this.cdr.detectChanges();
1619
+ }
1620
+ }, 10);
1621
+ }
1622
+ newChat() {
1623
+ this.chatService.listSavedChat(); // Refresh the list of saved chats
1624
+ this.loadDefaultChat(); // Start a new chat
1625
+ }
1626
+ loadDefaultChat() {
1627
+ this.openChat([
1628
+ { role: 'system', content: this.config.uiSettings.systemPrompt, additionalProperties: { display: false } },
1629
+ { role: 'user', content: ChatService.formatPrompt(this.config.uiSettings.userPrompt, { principal: this.principalService.principal }), additionalProperties: { display: true } },
1630
+ ]);
1631
+ }
1632
+ openChat(messages, chatId) {
1633
+ if (!messages || !Array.isArray(messages)) {
1634
+ console.error('Error occurs while trying to load the chat discussion. Invalid messages received :', messages);
1635
+ return;
1636
+ }
1637
+ this.chatService.setSavedChatId(chatId || ChatService.generateGUID());
1638
+ this.resetChat();
1639
+ this.messages$.next(messages);
1640
+ this.chatService.chatHistory = messages;
1641
+ const lastMessage = messages.at(-1);
1642
+ if (lastMessage && lastMessage.role === 'user') {
1643
+ this.fetch(messages); // If the last message if from a user, an answer from the assistant is expected
1644
+ }
1645
+ else {
1646
+ this.updateData(messages); // If the last message if from the assistant, we can load the conversation right away
1647
+ this.terminateFetch();
1648
+ }
1649
+ }
1650
+ resetChat() {
1651
+ if (this.messages$.value) {
1652
+ this.messages$.next(undefined); // Reset chat
1653
+ }
1654
+ this.chatService.chatHistory = undefined; // Reset chat history
1655
+ this.question = '';
1656
+ this.terminateFetch();
1657
+ }
1658
+ onLoadChat() {
1659
+ this.loading$.next(true);
1660
+ this.sub.add(this.chatService.loadSavedChat$
1661
+ .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
1662
+ ).subscribe());
1663
+ }
1664
+ terminateFetch() {
1665
+ var _a;
1666
+ (_a = this.dataSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
1667
+ this.dataSubscription = undefined;
1668
+ this.loading$.next(false);
1669
+ setTimeout(() => {
1670
+ var _a;
1671
+ (_a = this.questionInput) === null || _a === void 0 ? void 0 : _a.nativeElement.focus();
1672
+ });
1673
+ this.cdr.detectChanges();
1674
+ }
1675
+ editMessage(index) {
1676
+ var _a;
1677
+ this.messageToEdit = index;
1678
+ this.question = this.chatService.chatHistory[index].content;
1679
+ (_a = this.questionInput) === null || _a === void 0 ? void 0 : _a.nativeElement.focus();
1680
+ }
1681
+ regenerateMessage(index) {
1682
+ // Define the chat history based on which the assistant will generate a new answer
1683
+ const slicedMessages = this.chatService.chatHistory.slice(0, index);
1684
+ // Accordingly update the messages in the UI
1685
+ this.messages$.next(slicedMessages);
1686
+ // Fetch the answer
1687
+ this.fetch(slicedMessages);
1688
+ }
1689
+ onKeyUp(event) {
1690
+ switch (event.key) {
1691
+ case 'ArrowUp':
1692
+ this.navigateMessage(-1);
1693
+ break;
1694
+ case 'ArrowDown':
1695
+ this.navigateMessage(1);
1696
+ break;
1697
+ case 'Enter':
1698
+ this.submitQuestion();
1699
+ break;
1700
+ default:
1701
+ break;
1702
+ }
1703
+ // Handle Shift + Enter
1704
+ if (event.shiftKey && event.key === 'Enter') {
1705
+ this.submitQuestion();
1706
+ }
1707
+ }
1708
+ navigateMessage(direction) {
1709
+ var _a;
1710
+ if (!this.chatService.chatHistory || this.chatService.chatHistory.length < 1) {
1711
+ return;
1712
+ }
1713
+ const userMessages = this.chatService.chatHistory.filter(m => m.role === 'user');
1714
+ if (userMessages.length < 1) {
1715
+ return;
1716
+ }
1717
+ this.currentMessageIndex = ((_a = this.currentMessageIndex) !== null && _a !== void 0 ? _a : userMessages.length) + direction;
1718
+ if (this.currentMessageIndex < 0) {
1719
+ // If the user presses up arrow on the first message, stay at the first message
1720
+ this.currentMessageIndex = 0;
1721
+ }
1722
+ else if (this.currentMessageIndex >= userMessages.length) {
1723
+ // If the user presses down arrow on the last previous message, clear the input
1724
+ this.currentMessageIndex = undefined;
1725
+ this.question = '';
1726
+ return;
1727
+ }
1728
+ this.question = userMessages[this.currentMessageIndex].content;
1729
+ }
1730
+ }
1731
+ ChatComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1732
+ 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: [
1733
+ RestChatService,
1734
+ WebSocketChatService
1735
+ ], 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 });
1736
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatComponent, decorators: [{
1737
+ type: Component,
1738
+ args: [{ selector: 'sq-chat-v3', providers: [
1739
+ RestChatService,
1740
+ WebSocketChatService
1741
+ ], 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"] }]
1742
+ }], ctorParameters: function () { return []; }, propDecorators: { instanceId: [{
1743
+ type: Input
1744
+ }], query: [{
1745
+ type: Input
1746
+ }], protocol: [{
1747
+ type: Input
1748
+ }], messageHandlers: [{
1749
+ type: Input
1750
+ }], automaticScrollToLastResponse: [{
1751
+ type: Input
1752
+ }], chat: [{
1753
+ type: Input
1754
+ }], enableChat: [{
1755
+ type: Input
1756
+ }], showCredits: [{
1757
+ type: Input
1758
+ }], customAssistantIcon: [{
1759
+ type: Input
1760
+ }], data: [{
1761
+ type: Output
1762
+ }], referenceClicked: [{
1763
+ type: Output
1764
+ }], openPreview: [{
1765
+ type: Output
1766
+ }], loading$: [{
1767
+ type: Output,
1768
+ args: ["loading"]
1769
+ }], error: [{
1770
+ type: Output
1771
+ }], _config: [{
1772
+ type: Output,
1773
+ args: ["config"]
1774
+ }], messageList: [{
1775
+ type: ViewChild,
1776
+ args: ['messageList']
1777
+ }], questionInput: [{
1778
+ type: ViewChild,
1779
+ args: ['questionInput']
1780
+ }], loadingTpl: [{
1781
+ type: ContentChild,
1782
+ args: ['loadingTpl']
1783
+ }] } });
1784
+
1785
+ class SavedChatsComponent {
1786
+ constructor() {
1787
+ this.load = new EventEmitter();
1788
+ this.delete = new EventEmitter();
1789
+ this.subscription = new Subscription();
1790
+ this.groupedSavedChats$ = new BehaviorSubject([]);
1791
+ this.loginService = inject(LoginService);
1792
+ this.instanceManagerService = inject(InstanceManagerService);
1793
+ this.auditService = inject(AuditWebService);
1794
+ this.modalService = inject(ModalService);
1795
+ this.notificationsService = inject(NotificationsService);
1796
+ }
1797
+ ngOnInit() {
1798
+ this.subscription.add(this.loginService.events.pipe(filter(e => e.type === 'login-complete'), tap(_ => this.instantiateChatService()), switchMap(_ => this.chatService.initProcess$), filter(success => !!success), tap(_ => {
1799
+ this.onListSavedChat();
1800
+ this.chatService.listSavedChat();
1801
+ })).subscribe());
1802
+ }
1803
+ ngOnDestroy() {
1804
+ this.subscription.unsubscribe();
1805
+ }
1806
+ instantiateChatService() {
1807
+ this.chatService = this.instanceManagerService.getInstance(this.instanceId);
1808
+ }
1809
+ onListSavedChat() {
1810
+ this.subscription.add(this.chatService.savedChats$.subscribe((savedChats) => this.groupedSavedChats$.next(this._groupSavedChatsByDate(savedChats))));
1811
+ }
1812
+ onLoad(savedChat) {
1813
+ this.chatService.loadSavedChat$.next(savedChat);
1814
+ this.load.emit(savedChat);
1815
+ }
1816
+ onDelete(savedChat) {
1817
+ this.modalService
1818
+ .confirm({
1819
+ title: "Delete saved discussion",
1820
+ message: `You are about to delete the discussion "${savedChat.title}". Do you want to continue?`,
1821
+ buttons: [
1822
+ new ModalButton({ result: -2 /* ModalResult.Cancel */ }),
1823
+ new ModalButton({ result: -1 /* ModalResult.OK */, text: "Confirm", primary: true })
1824
+ ],
1825
+ confirmType: 2 /* ConfirmType.Warning */
1826
+ }).then(res => {
1827
+ if (res === -1 /* ModalResult.OK */) {
1828
+ this.subscription.add(this.chatService.deleteSavedChat([savedChat.id])
1829
+ .pipe(tap(() => {
1830
+ this.notificationsService.success(`The saved discussion "${savedChat.title}" has been successfully deleted.`);
1831
+ this.delete.emit(savedChat);
1832
+ this.chatService.listSavedChat();
1833
+ }), catchError((error) => {
1834
+ console.error('Error occurred while deleting the saved chat:', error);
1835
+ this.notificationsService.error(`Error occurred while deleting the saved discussion "${savedChat.title}"`);
1836
+ return throwError(() => error);
1837
+ })).subscribe());
1838
+ }
1839
+ });
1840
+ }
1841
+ _groupSavedChatsByDate(savedChats) {
1842
+ const groupedSavedChats = new Map();
1843
+ savedChats
1844
+ .sort((a, b) => parseISO(b.modifiedUTC).getTime() - parseISO(a.modifiedUTC).getTime())
1845
+ .forEach(savedChat => {
1846
+ const groupKey = this._getTimeKey(parseISO(savedChat.modifiedUTC));
1847
+ if (!groupedSavedChats.has(groupKey)) {
1848
+ groupedSavedChats.set(groupKey, []);
1849
+ }
1850
+ groupedSavedChats.get(groupKey).push(savedChat);
1851
+ });
1852
+ return Array.from(groupedSavedChats, ([key, value]) => ({ key, value }));
1853
+ ;
1854
+ }
1855
+ _getTimeKey(date) {
1856
+ if (isToday(date)) {
1857
+ return 'Today';
1858
+ }
1859
+ else if (isYesterday(date)) {
1860
+ return 'Yesterday';
1861
+ }
1862
+ else if (isThisWeek(date)) {
1863
+ return 'This week';
1864
+ }
1865
+ else if (differenceInDays(endOfYesterday(), date) <= 7) {
1866
+ return 'Last week';
1867
+ }
1868
+ else if (isThisMonth(date)) {
1869
+ return 'This month';
1870
+ }
1871
+ else if (differenceInMonths(endOfYesterday(), date) <= 1) {
1872
+ return 'Last month';
1873
+ }
1874
+ else if (isThisQuarter(date)) {
1875
+ return 'This quarter';
1876
+ }
1877
+ else if (differenceInMonths(endOfYesterday(), date) <= 3) {
1878
+ return 'Last quarter';
1879
+ }
1880
+ else if (isThisYear(date)) {
1881
+ return 'This year';
1882
+ }
1883
+ else if (differenceInYears(endOfYesterday(), date) === 1) {
1884
+ return 'Last year';
1885
+ }
1886
+ else {
1887
+ return format(date, 'yyyy');
1888
+ }
1889
+ }
1890
+ }
1891
+ SavedChatsComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: SavedChatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1892
+ 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"] }] });
1893
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: SavedChatsComponent, decorators: [{
1894
+ type: Component,
1895
+ 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"] }]
1896
+ }], propDecorators: { instanceId: [{
1897
+ type: Input
1898
+ }], load: [{
1899
+ type: Output
1900
+ }], delete: [{
1901
+ type: Output
1902
+ }] } });
1903
+
1904
+ var _enAssistant = {
1905
+ "assistant": {}
1906
+ };
1907
+
1908
+ var _frAssistant = {
1909
+ "assistant": {}
1910
+ };
1911
+
1912
+ var _deAssistant = {
1913
+ "assistant": {}
1914
+ };
1915
+
1916
+ const enAssistant = Utils.merge({}, _enAssistant);
1917
+ const frAssistant = Utils.merge({}, _frAssistant);
1918
+ const deAssistant = Utils.merge({}, _deAssistant);
1919
+
1920
+ /**
1921
+ * Generated bundle index. Do not edit.
1922
+ */
1923
+
1924
+ export { ChatComponent, ChatService, ChatSettingsV3Component, FormatIconComponent, InitialsAvatarComponent, InstanceManagerService, RestChatService, SavedChatsComponent, WebSocketChatService, chatConfigSchema, deAssistant, enAssistant, frAssistant, globalSettingsSchema };
1925
+ //# sourceMappingURL=sinequa-assistant-chat.mjs.map