@sinequa/assistant 3.1.1 → 3.2.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.
@@ -1,12 +1,12 @@
1
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';
2
+ import { Injectable, EventEmitter, inject, Component, Input, Output, ViewEncapsulation, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild, ContentChild, Inject } from '@angular/core';
3
+ import { Subscription, filter, tap, switchMap, BehaviorSubject, Subject, forkJoin, map, catchError, throwError, shareReplay, fromEvent, merge, takeUntil, take, finalize } from 'rxjs';
4
4
  import * as i3 from '@sinequa/core/web-services';
5
5
  import { PrincipalWebService, UserSettingsWebService, AuditWebService, SignalRWebService, JsonMethodPluginService } from '@sinequa/core/web-services';
6
6
  import * as i1 from '@angular/common';
7
7
  import { CommonModule } from '@angular/common';
8
8
  import * as i2 from '@angular/forms';
9
- import { FormsModule } from '@angular/forms';
9
+ import { FormsModule, Validators, UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
10
10
  import { LoginService, AuthenticationService } from '@sinequa/core/login';
11
11
  import { Action } from '@sinequa/components/action';
12
12
  import { AbstractFacet } from '@sinequa/components/facet';
@@ -16,9 +16,12 @@ import { UserPreferences } from '@sinequa/components/user-settings';
16
16
  import { NotificationsService } from '@sinequa/core/notification';
17
17
  import { z } from 'zod';
18
18
  import { AppService } from '@sinequa/core/app-utils';
19
- import { IntlService } from '@sinequa/core/intl';
19
+ import * as i6$1 from '@sinequa/core/intl';
20
+ import { IntlService, IntlModule } from '@sinequa/core/intl';
20
21
  import get from 'lodash/get';
21
22
  import { Utils } from '@sinequa/core/base';
23
+ import * as i1$2 from '@sinequa/core/modal';
24
+ import { ModalService, ModalButton, ModalModule, MODAL_MODEL } from '@sinequa/core/modal';
22
25
  import { HttpTransportType, LogLevel } from '@microsoft/signalr';
23
26
  import { unified } from 'unified';
24
27
  import remarkParse from 'remark-parse';
@@ -31,8 +34,13 @@ import { CollapseModule } from '@sinequa/components/collapse';
31
34
  import * as i6 from 'ngx-remark';
32
35
  import { RemarkModule } from 'ngx-remark';
33
36
  import SafeColor from 'safecolor';
34
- import { ModalService, ModalButton, ModalModule } from '@sinequa/core/modal';
37
+ import 'prismjs-components-importer/esm';
38
+ import 'prismjs/plugins/autoloader/prism-autoloader';
35
39
  import { parseISO, isToday, isYesterday, isThisWeek, differenceInDays, endOfYesterday, isThisMonth, differenceInMonths, isThisQuarter, isThisYear, differenceInYears, format } from 'date-fns';
40
+ import * as i4 from '@sinequa/components/modal';
41
+ import { BsModalModule } from '@sinequa/components/modal';
42
+ import * as i5$1 from '@sinequa/core/validation';
43
+ import { ValidationModule } from '@sinequa/core/validation';
36
44
 
37
45
  /**
38
46
  * A service to create and manage instances of ChatService dynamically based on the provided component references and the implementation type (http or websocket)
@@ -83,6 +91,7 @@ class ChatSettingsV3Component {
83
91
  this._update = new EventEmitter();
84
92
  this._cancel = new EventEmitter();
85
93
  this.subscription = new Subscription();
94
+ this.functions = [];
86
95
  this.isAdmin = false;
87
96
  this.loginService = inject(LoginService);
88
97
  this.instanceManagerService = inject(InstanceManagerService);
@@ -93,8 +102,8 @@ class ChatSettingsV3Component {
93
102
  this.isAdmin = this.principalService.principal.isAdministrator;
94
103
  // 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
104
  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();
105
+ this.selectedModel = this.chatService.getModel(this.config.defaultValues.service_id, this.config.defaultValues.model_id);
106
+ this.initFunctionsList();
98
107
  }));
99
108
  }
100
109
  ngOnDestroy() {
@@ -102,14 +111,14 @@ class ChatSettingsV3Component {
102
111
  }
103
112
  get hasPrompts() {
104
113
  return this.isAdmin
105
- || !!this.config.uiSettings.systemPrompt
106
- || !!this.config.uiSettings.userPrompt;
114
+ || !!this.config.uiSettings.displaySystemPrompt
115
+ || !!this.config.uiSettings.displayUserPrompt;
107
116
  }
108
117
  get hasAdvancedParameters() {
109
118
  return this.isAdmin
110
119
  || !!this.config.uiSettings.temperature
111
120
  || !!this.config.uiSettings.top_p
112
- || !!this.config.uiSettings.maxTokens;
121
+ || !!this.config.uiSettings.max_tokens;
113
122
  }
114
123
  get hasModel() {
115
124
  return this.isAdmin
@@ -118,49 +127,46 @@ class ChatSettingsV3Component {
118
127
  || !!this.config.uiSettings.debug
119
128
  || !!this.config.uiSettings.temperature
120
129
  || !!this.config.uiSettings.top_p
121
- || !!this.config.uiSettings.maxTokens;
130
+ || !!this.config.uiSettings.max_tokens;
122
131
  }
123
132
  instantiateChatService() {
124
133
  this.chatService = this.instanceManagerService.getInstance(this.instanceId);
125
134
  }
126
135
  onChatModelChange(selectedModel) {
127
136
  // Update properties based on the selected model
128
- this.config.serviceSettings.service_id = selectedModel.serviceId;
129
- this.config.serviceSettings.model_id = selectedModel.modelId;
137
+ this.config.defaultValues.service_id = selectedModel.serviceId;
138
+ this.config.defaultValues.model_id = selectedModel.modelId;
139
+ }
140
+ getFunctionDescription(name) {
141
+ return this.chatService.functions?.find(fn => fn.functionName === name)?.description || "";
130
142
  }
131
143
  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
- }
144
+ // Update the enabled property of the function
145
+ const index = this.config.defaultValues.functions.findIndex(func => func.name === name);
146
+ this.config.defaultValues.functions[index].enabled = this.functions[index].enabled;
138
147
  }
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
- });
148
+ initFunctionsList() {
149
+ this.functions = this.config.defaultValues.functions.filter(func => !!this.chatService.functions?.find(fn => fn.functionName === func.name));
144
150
  }
145
151
  /**
146
152
  * Save the new chat config in the chat service and the user preferences
147
153
  */
148
154
  save() {
149
155
  this.chatService.updateChatConfig(this.config);
150
- this._update.emit();
156
+ this._update.emit(this.config);
151
157
  }
152
158
  /**
153
159
  * Cancel the current changes
154
160
  */
155
161
  cancel() {
156
- this._cancel.emit();
162
+ this._cancel.emit(this.chatService.chatConfig$.value);
157
163
  }
158
164
  }
159
165
  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"] }] });
166
+ 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\" *ngIf=\"isAdmin || config.uiSettings.display\">\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 functions\" class=\"multi-option form-check form-switch\">\n <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" [id]=\"func.name\" [(ngModel)]=\"func.enabled\"\n (ngModelChange)=\"toggleFunctionsSelection(func.name)\">\n <label class=\"form-check-label\" [for]=\"func.name\" [title]=\"getFunctionDescription(func.name)\">{{ func.name }}</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.defaultValues.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.defaultValues.temperature}}</label>\n <input type=\"range\" class=\"form-range form-range-sm\" min=\"0\" max=\"2\" step=\"0.1\" id=\"temperature\"\n [(ngModel)]=\"config.defaultValues.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.defaultValues.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.defaultValues.top_p\">\n </div>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.max_tokens\">\n <label for=\"max-tokens\" class=\"form-label\">Max generated tokens per answer:\n {{config.defaultValues.max_tokens}}</label>\n <input type=\"range\" class=\"form-range form-range-sm\" min=\"1\" max=\"2048\" step=\"1\" id=\"max-tokens\"\n [(ngModel)]=\"config.defaultValues.max_tokens\">\n </div>\n </details>\n\n <hr>\n\n <h5 *ngIf=\"hasPrompts\">Prompts</h5>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.displaySystemPrompt\">\n <label for=\"initialSystemPrompt\" class=\"form-label\">System prompt (hidden)</label>\n <textarea class=\"form-control\" id=\"initialSystemPrompt\" [(ngModel)]=\"config.defaultValues.systemPrompt\"></textarea>\n </div>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.displayUserPrompt\">\n <label for=\"initialUserPrompt\" class=\"form-label\">Initial user prompt</label>\n <textarea class=\"form-control\" id=\"initialUserPrompt\" [(ngModel)]=\"config.defaultValues.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>\n", 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
167
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatSettingsV3Component, decorators: [{
162
168
  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"] }]
169
+ args: [{ selector: 'sq-chat-settings-v3', standalone: true, imports: [CommonModule, FormsModule], template: "<div class=\"sq-chat-settings\" *ngIf=\"isAdmin || config.uiSettings.display\">\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 functions\" class=\"multi-option form-check form-switch\">\n <input class=\"form-check-input\" type=\"checkbox\" role=\"switch\" [id]=\"func.name\" [(ngModel)]=\"func.enabled\"\n (ngModelChange)=\"toggleFunctionsSelection(func.name)\">\n <label class=\"form-check-label\" [for]=\"func.name\" [title]=\"getFunctionDescription(func.name)\">{{ func.name }}</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.defaultValues.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.defaultValues.temperature}}</label>\n <input type=\"range\" class=\"form-range form-range-sm\" min=\"0\" max=\"2\" step=\"0.1\" id=\"temperature\"\n [(ngModel)]=\"config.defaultValues.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.defaultValues.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.defaultValues.top_p\">\n </div>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.max_tokens\">\n <label for=\"max-tokens\" class=\"form-label\">Max generated tokens per answer:\n {{config.defaultValues.max_tokens}}</label>\n <input type=\"range\" class=\"form-range form-range-sm\" min=\"1\" max=\"2048\" step=\"1\" id=\"max-tokens\"\n [(ngModel)]=\"config.defaultValues.max_tokens\">\n </div>\n </details>\n\n <hr>\n\n <h5 *ngIf=\"hasPrompts\">Prompts</h5>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.displaySystemPrompt\">\n <label for=\"initialSystemPrompt\" class=\"form-label\">System prompt (hidden)</label>\n <textarea class=\"form-control\" id=\"initialSystemPrompt\" [(ngModel)]=\"config.defaultValues.systemPrompt\"></textarea>\n </div>\n <div class=\"mb-2\" *ngIf=\"isAdmin || config.uiSettings.displayUserPrompt\">\n <label for=\"initialUserPrompt\" class=\"form-label\">Initial user prompt</label>\n <textarea class=\"form-control\" id=\"initialUserPrompt\" [(ngModel)]=\"config.defaultValues.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>\n", 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
170
  }], propDecorators: { instanceId: [{
165
171
  type: Input
166
172
  }], _update: [{
@@ -171,8 +177,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImpor
171
177
  args: ["cancel"]
172
178
  }] } });
173
179
 
174
- // Define the Zod representation for the globalSettings object
175
- const globalSettingsSchema = z.object({
180
+ // Define the Zod representation for the connectionSettings object
181
+ const connectionSettingsSchema = z.object({
176
182
  restEndpoint: z.string().optional(),
177
183
  websocketEndpoint: z.string().optional(),
178
184
  signalRTransport: z.enum(["WebSockets", "ServerSentEvents", "LongPolling", "None"]),
@@ -186,50 +192,10 @@ const serviceSettingsSchema = z.object({
186
192
  model_id: z.string(),
187
193
  temperature: z.number(),
188
194
  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,
195
+ max_tokens: z.number()
232
196
  });
197
+ // Define the Zod representation for the additionalServiceSettings object
198
+ const additionalServiceSettingsSchema = z.object({});
233
199
  // Define the Zod representation for the uiSettings object
234
200
  const uiSettingsSchema = z.object({
235
201
  display: z.boolean(),
@@ -237,22 +203,58 @@ const uiSettingsSchema = z.object({
237
203
  functions: z.boolean(),
238
204
  temperature: z.boolean(),
239
205
  top_p: z.boolean(),
240
- maxTokens: z.boolean(),
206
+ max_tokens: z.boolean(),
241
207
  debug: z.boolean(),
242
208
  displaySystemPrompt: z.boolean(),
209
+ displayUserPrompt: z.boolean()
210
+ });
211
+ // Define the Zod representation for the defaultValues object
212
+ const defaultValuesSchema = z.object({
213
+ service_id: z.string(),
214
+ model_id: z.string(),
215
+ functions: z.array(z.object({
216
+ name: z.string(),
217
+ enabled: z.boolean()
218
+ })),
219
+ temperature: z.number(),
220
+ top_p: z.number(),
221
+ max_tokens: z.number(),
222
+ debug: z.boolean(),
243
223
  systemPrompt: z.string(),
224
+ userPrompt: z.string()
225
+ });
226
+ // Define the Zod representation for the modeSettings object
227
+ const initializationSchema = z.object({
228
+ event: z.enum(['Query', 'Prompt']),
229
+ forcedWorkflow: z.string().optional(),
230
+ displayUserQuery: z.boolean().optional(), // Optional for event 'Prompt'
231
+ }).refine(data => ((data.event === "Query") ? (!!data.forcedWorkflow && data.displayUserQuery !== undefined && data.displayUserQuery !== null) : true), {
232
+ message: "The 'forcedWorkflow' and 'displayUserQuery' properties must be provided when the initialization's event is 'Query'.",
233
+ });
234
+ const modeSettingsSchema = z.object({
235
+ enabledUserInput: z.boolean(),
244
236
  displayUserPrompt: z.boolean(),
245
- userPrompt: z.string(),
237
+ sendUserPrompt: z.boolean(),
238
+ initialization: initializationSchema
239
+ });
240
+ // Define the Zod representation for the savedChatSettings object
241
+ const savedChatSettingsSchema = z.object({
242
+ enabled: z.boolean(),
243
+ display: z.boolean()
244
+ });
245
+ // Define the Zod representation for the globalSettings object
246
+ const globalSettingsSchema = z.object({
247
+ disclaimer: z.string().optional(),
246
248
  });
247
249
  // Define the Zod representation for the entire ChatConfig object
248
250
  const chatConfigSchema = z.object({
249
- globalSettings: globalSettingsSchema,
250
- serviceSettings: serviceSettingsSchema,
251
- contextSettings: contextSettingsSchema,
251
+ connectionSettings: connectionSettingsSchema,
252
+ defaultValues: defaultValuesSchema,
253
+ modeSettings: modeSettingsSchema,
252
254
  uiSettings: uiSettingsSchema,
253
- functions: z.array(z.string()),
254
- debug: z.boolean(),
255
- saveChats: z.boolean()
255
+ additionalServiceSettings: additionalServiceSettingsSchema,
256
+ savedChatSettings: savedChatSettingsSchema,
257
+ globalSettings: globalSettingsSchema
256
258
  });
257
259
 
258
260
  class ChatService {
@@ -269,7 +271,6 @@ class ChatService {
269
271
  this.savedChats$ = new BehaviorSubject([]);
270
272
  /** Emit the saved chat to load */
271
273
  this.loadSavedChat$ = new BehaviorSubject(undefined);
272
- this.searchService = inject(SearchService);
273
274
  this.userSettingsService = inject(UserSettingsWebService);
274
275
  this.notificationsService = inject(NotificationsService);
275
276
  this.auditService = inject(AuditWebService);
@@ -277,6 +278,7 @@ class ChatService {
277
278
  this.loginService = inject(LoginService);
278
279
  this.appService = inject(AppService);
279
280
  this.intlService = inject(IntlService);
281
+ this.modalService = inject(ModalService);
280
282
  }
281
283
  get assistants() {
282
284
  if (!this.userSettingsService.userSettings)
@@ -314,24 +316,68 @@ class ChatService {
314
316
  this._savedChatId = savedChatId;
315
317
  }
316
318
  /**
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
+ * Initialize the chat config by managing ONLY sub-object **defaultValues** configs of the standard app config (defined in the customization json tab ) and the user preferences.
320
+ * To do so, a tracking mechanism is implemented to notify the user about the available updates in the defaultValues object of the standard app config.
321
+ * The rest of the config object coming from "standard app config" is used as it is without any override.
322
+ * Thus, the user preferences are used only for the defaultValues object.
323
+ * This provide a centralized way to manage the rest of the config object by admins and ensure a unique common behavior for all users.
319
324
  */
320
325
  initChatConfig() {
321
326
  const key = this.chatInstanceId;
322
327
  const userSettingsConfig = this.assistants[key] || {};
323
- const defaultChatConfig = this.appService.app?.data?.assistants?.[key];
324
- // Validate the object against the schema
328
+ const standardChatConfig = this.appService.app?.data?.assistants?.[key];
325
329
  try {
326
- chatConfigSchema.parse(defaultChatConfig);
327
- // Merge configs with override desc order: user preferences > app config > default config
328
- const chatConfig = {
329
- ...defaultChatConfig,
330
- ...userSettingsConfig
331
- };
332
- // Update the chat config and store it in the user preferences
333
- this.updateChatConfig(chatConfig, false);
334
- this.initConfig$.next(true);
330
+ // Validate the whole config object against the schema
331
+ chatConfigSchema.parse(standardChatConfig);
332
+ // If the user preferences do not contain a config's defaultValues object, keep using the standard app config and nothing to store in the user preferences
333
+ if (!userSettingsConfig.defaultValues) {
334
+ this.chatConfig$.next({ ...standardChatConfig });
335
+ this.initConfig$.next(true);
336
+ }
337
+ else { // If the user has its own defaultValues in its userSettings, then we need to check for potential updates made by admins in the meantime and how he wants to manage them
338
+ // Retrieve already stored hashes in the user settings if exists
339
+ const appliedDefaultValuesHash = userSettingsConfig.hashes?.["applied-defaultValues-hash"];
340
+ const skippedDefaultValuesHash = userSettingsConfig.hashes?.["skipped-defaultValues-hash"];
341
+ // Create a hash of the current defaultValues of the standardChatConfig
342
+ const currentDefaultValuesHash = Utils.sha512(JSON.stringify(standardChatConfig.defaultValues));
343
+ // Implement the tracking mechanism to notify the user about the available updates in the defaultValues object of the standard app config
344
+ const condition = (currentDefaultValuesHash !== appliedDefaultValuesHash) && (currentDefaultValuesHash !== skippedDefaultValuesHash);
345
+ if (condition) {
346
+ this.modalService
347
+ .confirm({
348
+ title: "Available updates !",
349
+ message: "Changes have been made to the default configuration. Do you want to update your own version ?",
350
+ buttons: [
351
+ new ModalButton({ result: -4 /* ModalResult.No */, text: "See no more" }),
352
+ new ModalButton({ result: -7 /* ModalResult.Ignore */, text: "Remind me later" }),
353
+ new ModalButton({ result: -1 /* ModalResult.OK */, text: "Update", primary: true })
354
+ ],
355
+ confirmType: 2 /* ConfirmType.Warning */
356
+ }).then(res => {
357
+ if (res === -1 /* ModalResult.OK */) {
358
+ const hashes = { "applied-defaultValues-hash": currentDefaultValuesHash, "skipped-defaultValues-hash": undefined };
359
+ // Update the chat config and store its defaultValues in the user preferences
360
+ this.updateChatConfig({ ...standardChatConfig }, hashes, true);
361
+ this.initConfig$.next(true);
362
+ }
363
+ else if (res === -4 /* ModalResult.No */) {
364
+ // Do not notify the user about changes while this skipped version is not updated
365
+ const hashes = { ...userSettingsConfig.hashes, "skipped-defaultValues-hash": currentDefaultValuesHash };
366
+ this.updateChatConfig({ ...standardChatConfig, defaultValues: userSettingsConfig.defaultValues }, hashes, false);
367
+ this.initConfig$.next(true);
368
+ }
369
+ else {
370
+ // Just pick the version in the user settings, nothing to be updated
371
+ this.chatConfig$.next({ ...standardChatConfig, defaultValues: userSettingsConfig.defaultValues });
372
+ this.initConfig$.next(true);
373
+ }
374
+ });
375
+ }
376
+ else { // No available updates Or updates has been already skipped, then just pick the version in the user settings
377
+ this.chatConfig$.next({ ...standardChatConfig, defaultValues: userSettingsConfig.defaultValues });
378
+ this.initConfig$.next(true);
379
+ }
380
+ }
335
381
  }
336
382
  catch (error) {
337
383
  this.notificationsService.error(`Missing valid configuration for the assistant instance '${key}'`);
@@ -339,25 +385,25 @@ class ChatService {
339
385
  }
340
386
  }
341
387
  /**
342
- * Update the chat config and store it in the user preferences
388
+ * Update the chat config and store its defaultValues in the user preferences
343
389
  * @param config The updated chat config
344
390
  * @param notify Whether to notify the user about the update
345
391
  * @param successCallback The callback to execute if the update is successful
346
392
  * @param errorCallback The callback to execute if the update fails
347
393
  */
348
- updateChatConfig(config, notify = true, successCallback, errorCallback) {
394
+ updateChatConfig(config, hashes, notify = true, successCallback, errorCallback) {
349
395
  this.chatConfig$.next(config);
350
396
  const assistants = Object.assign({}, this.assistants);
351
- assistants[this.chatInstanceId] = config;
352
- this.userSettingsService.patch({ assistants }).subscribe(next => {
353
- if (notify) {
354
- successCallback ? successCallback() : this.notificationsService.success(`The configuration of the assistant instance '${this.chatInstanceId}' has been successfully updated`);
355
- }
356
- }, error => {
397
+ assistants[this.chatInstanceId] = { ...assistants[this.chatInstanceId], defaultValues: config.defaultValues, hashes };
398
+ this.userSettingsService.patch({ assistants }).subscribe(next => { }, error => {
357
399
  if (notify) {
358
400
  errorCallback ? errorCallback() : this.notificationsService.error(`The update of the assistant instance '${this.chatInstanceId}' configuration failed`);
359
401
  }
360
402
  console.error("Could not patch assistants!", error);
403
+ }, () => {
404
+ if (notify) {
405
+ successCallback ? successCallback() : this.notificationsService.success(`The assistant instance '${this.chatInstanceId}' configuration has been successfully updated`);
406
+ }
361
407
  });
362
408
  }
363
409
  /**
@@ -432,10 +478,11 @@ class WebSocketChatService extends ChatService {
432
478
  super();
433
479
  this.connectionBuilt$ = new Subject(); // Emit when the connection is built
434
480
  this.connectionStarted$ = new Subject(); // Emit when the connection is started
435
- this.messageHandlers = new Map();
436
- this.actionMap = new Map();
437
- this.content = "";
438
- this.attachments = [];
481
+ this._messageHandlers = new Map();
482
+ this._actionMap = new Map();
483
+ this._progress = undefined;
484
+ this._content = "";
485
+ this._attachments = [];
439
486
  this.signalRService = inject(SignalRWebService);
440
487
  this.authenticationService = inject(AuthenticationService);
441
488
  }
@@ -480,8 +527,8 @@ class WebSocketChatService extends ChatService {
480
527
  * It can be overridden by the app config
481
528
  */
482
529
  getRequestsUrl() {
483
- if (this.chatConfig$.value.globalSettings.websocketEndpoint) {
484
- this.REQUEST_URL = this.chatConfig$.value.globalSettings.websocketEndpoint;
530
+ if (this.chatConfig$.value.connectionSettings.websocketEndpoint) {
531
+ this.REQUEST_URL = this.chatConfig$.value.connectionSettings.websocketEndpoint;
485
532
  }
486
533
  else {
487
534
  throw new Error(`The property 'websocketEndpoint' must be provided when attempting to use 'WebSocket' in assistant instance`);
@@ -495,7 +542,7 @@ class WebSocketChatService extends ChatService {
495
542
  modelsSubject.complete();
496
543
  });
497
544
  // Send the request to get the list of models
498
- this.connection.invoke('ListModels', { debug: this.chatConfig$.value.debug })
545
+ this.connection.invoke('ListModels', { debug: this.chatConfig$.value.defaultValues.debug })
499
546
  .catch(error => {
500
547
  console.error('Error invoking ListModels:', error);
501
548
  modelsSubject.complete();
@@ -506,12 +553,12 @@ class WebSocketChatService extends ChatService {
506
553
  listFunctions() {
507
554
  const functionsSubject = new Subject();
508
555
  this.connection.on('ListFunctions', (res) => {
509
- this.functions = res.functions;
556
+ this.functions = res.functions?.filter(func => func.enabled);
510
557
  functionsSubject.next(this.functions);
511
558
  functionsSubject.complete();
512
559
  });
513
560
  // Send the request to get the list of functions
514
- this.connection.invoke('ListFunctions', { debug: this.chatConfig$.value.debug })
561
+ this.connection.invoke('ListFunctions', { debug: this.chatConfig$.value.defaultValues.debug })
515
562
  .catch(error => {
516
563
  console.error('Error invoking ListFunctions:', error);
517
564
  functionsSubject.complete();
@@ -519,31 +566,37 @@ class WebSocketChatService extends ChatService {
519
566
  });
520
567
  return functionsSubject.asObservable();
521
568
  }
522
- fetch(messages, query = this.searchService.query) {
569
+ fetch(messages, query) {
523
570
  // Start streaming by invoking the Chat method
524
571
  this.streaming$.next(true);
525
572
  // Prepare the payload to send to the Chat method
526
573
  const data = {
527
574
  history: messages,
528
- functions: this.chatConfig$.value.functions,
529
- debug: this.chatConfig$.value.debug,
530
- serviceSettings: this.chatConfig$.value.serviceSettings,
531
- contextSettings: {
532
- ...this.chatConfig$.value.contextSettings,
575
+ functions: this.chatConfig$.value.defaultValues.functions?.filter(func => func.enabled).map(func => func.name),
576
+ debug: this.chatConfig$.value.defaultValues.debug,
577
+ serviceSettings: {
578
+ service_id: this.chatConfig$.value.defaultValues.service_id,
579
+ model_id: this.chatConfig$.value.defaultValues.model_id,
580
+ top_p: this.chatConfig$.value.defaultValues.top_p,
581
+ temperature: this.chatConfig$.value.defaultValues.temperature,
582
+ max_tokens: this.chatConfig$.value.defaultValues.max_tokens,
583
+ ...this.chatConfig$.value.additionalServiceSettings
584
+ },
585
+ appQuery: {
533
586
  app: this.appService.appName,
534
587
  query
535
588
  }
536
589
  };
537
- if (this.chatConfig$.value.saveChats) {
590
+ if (this.chatConfig$.value.savedChatSettings.enabled) {
538
591
  data.instanceId = this.chatInstanceId;
539
592
  data.savedChatId = this.savedChatId;
540
593
  }
541
594
  let response = { role: "assistant", content: "", additionalProperties: { display: true } }; // here display: true is needed in order to be able to show the progress
542
595
  // Create a Subject to signal completion
543
596
  const completion$ = new Subject();
544
- // Create observables for each non-global handler in the messageHandlers map (default and eventual custom ones) once it is triggered by the hub connection
597
+ // Create observables for each non-global handler in the _messageHandlers map (default and eventual custom ones) once it is triggered by the hub connection
545
598
  const observables = Array
546
- .from(this.messageHandlers.entries())
599
+ .from(this._messageHandlers.entries())
547
600
  .filter(([eventName, eventHandler]) => !eventHandler.isGlobalHandler)
548
601
  .map(([eventName, eventHandler]) => {
549
602
  return fromEvent(this.connection, eventName).pipe(map((event) => eventHandler.handler(event)) // Execute the corresponding handler
@@ -554,25 +607,26 @@ class WebSocketChatService extends ChatService {
554
607
  );
555
608
  // Invoke the Chat method
556
609
  this.connection.invoke('Chat', data)
557
- .then(() => this.notifyAudit(this.chatHistory, this.chatConfig$.value.serviceSettings.service_id)) // When the server indicates it has successfully finished invoking the method, notify the audit service with the recent chat history
610
+ .then(() => this.notifyAudit(this.chatHistory, this.chatConfig$.value.defaultValues.service_id)) // When the server indicates it has successfully finished invoking the method, notify the audit service with the recent chat history
558
611
  .catch(error => {
559
612
  console.error('Error invoking Chat:', error);
560
613
  return Promise.resolve(); // Return a resolved promise to handle the error and prevent unhandled promise rejection
561
614
  })
562
615
  .finally(() => {
563
616
  this.streaming$.next(false); // Complete streaming regardless of success or error
564
- this.actionMap.clear(); // Clear the actionMap
565
- this.content = ""; // Clear the content
566
- this.attachments = []; // Clear the attachments
567
- this.executionTime = ""; // Clear the executionTime
617
+ this._actionMap.clear(); // Clear the _actionMap
618
+ this._content = ""; // Clear the _content
619
+ this._progress = undefined; // Clear the _progress
620
+ this._attachments = []; // Clear the _attachments
621
+ this._executionTime = ""; // Clear the _executionTime
568
622
  completion$.next(); // Emit a signal to complete the observables
569
623
  completion$.complete(); // Complete the subject
570
624
  });
571
625
  // Return the merged observables
572
626
  return combined$.pipe(map(() => {
573
- // Define $progress from the actionMap
574
- const actions = Array.from(this.actionMap.values());
575
- const $progress = actions.length > 0
627
+ // Define $progress from the _actionMap
628
+ const actions = Array.from(this._actionMap.values());
629
+ this._progress = actions.length > 0
576
630
  ? actions.map((a) => ({
577
631
  title: a.displayName ?? "",
578
632
  content: a.displayValue ?? "",
@@ -580,20 +634,18 @@ class WebSocketChatService extends ChatService {
580
634
  time: a.executionTime,
581
635
  }))
582
636
  : undefined;
583
- // Define the attachment used in the context of the response message
584
- const $attachment = this.attachments;
585
- // As soon as the first content or $progress is defined, the assistant is considered as streaming
586
- if (!!this.content || $progress || $attachment.length > 0) {
587
- response = { ...response, content: this.content, additionalProperties: { ...response.additionalProperties, $progress, $attachment } };
637
+ // As soon as the first _content or $progress is defined, the assistant is considered as streaming
638
+ if (!!this._content || this._progress || this._attachments.length > 0) {
639
+ response = { ...response, content: this._content, additionalProperties: { ...response.additionalProperties, $progress: this._progress, $attachment: this._attachments } };
588
640
  }
589
641
  // Return the result
590
- return { history: [...messages, response], executionTime: this.executionTime };
642
+ return { history: [...messages, response], executionTime: this._executionTime };
591
643
  }));
592
644
  }
593
645
  listSavedChat() {
594
646
  const data = {
595
647
  instanceId: this.chatInstanceId,
596
- debug: this.chatConfig$.value.debug
648
+ debug: this.chatConfig$.value.defaultValues.debug
597
649
  };
598
650
  this.connection.on('SavedChatList', (res) => {
599
651
  this.savedChats$.next(res.savedChats); // emits the result to the savedChats$ subject
@@ -610,7 +662,7 @@ class WebSocketChatService extends ChatService {
610
662
  const data = {
611
663
  instanceId: this.chatInstanceId,
612
664
  savedChatId: id,
613
- debug: this.chatConfig$.value.debug
665
+ debug: this.chatConfig$.value.defaultValues.debug
614
666
  };
615
667
  this.connection.on('SavedChatGet', (res) => {
616
668
  savedChatSubject.next(res.savedChat);
@@ -625,12 +677,58 @@ class WebSocketChatService extends ChatService {
625
677
  });
626
678
  return savedChatSubject.asObservable();
627
679
  }
680
+ addSavedChat(messages) {
681
+ const addSavedChatSubject = new Subject();
682
+ const data = {
683
+ instanceId: this.chatInstanceId,
684
+ savedChatId: ChatService.generateGUID(),
685
+ history: messages,
686
+ debug: this.chatConfig$.value.defaultValues.debug
687
+ };
688
+ this.connection.on('SavedChatAdd', (res) => {
689
+ this.setSavedChatId(res.savedChat.id); // Persist the savedChatId
690
+ addSavedChatSubject.next(res.savedChat);
691
+ addSavedChatSubject.complete();
692
+ });
693
+ // Invoke the method SavedChatAdd
694
+ this.connection.invoke('SavedChatAdd', data)
695
+ .catch(error => {
696
+ console.error('Error invoking SavedChatAdd:', error);
697
+ addSavedChatSubject.complete();
698
+ return Promise.resolve();
699
+ });
700
+ return addSavedChatSubject.asObservable();
701
+ }
702
+ updateSavedChat(id, name, messages) {
703
+ const updateSavedChatSubject = new Subject();
704
+ const data = {
705
+ instanceId: this.chatInstanceId,
706
+ savedChatId: id,
707
+ debug: this.chatConfig$.value.defaultValues.debug
708
+ };
709
+ if (name)
710
+ data["title"] = name;
711
+ if (messages)
712
+ data["history"] = messages;
713
+ this.connection.on('SavedChatUpdate', (res) => {
714
+ updateSavedChatSubject.next(res.savedChat);
715
+ updateSavedChatSubject.complete();
716
+ });
717
+ // Invoke the method SavedChatUpdate
718
+ this.connection.invoke('SavedChatUpdate', data)
719
+ .catch(error => {
720
+ console.error('Error invoking SavedChatUpdate:', error);
721
+ updateSavedChatSubject.complete();
722
+ return Promise.resolve();
723
+ });
724
+ return updateSavedChatSubject.asObservable();
725
+ }
628
726
  deleteSavedChat(ids) {
629
727
  const deleteSavedChatSubject = new Subject();
630
728
  const data = {
631
729
  instanceId: this.chatInstanceId,
632
730
  SavedChatIds: ids,
633
- debug: this.chatConfig$.value.debug
731
+ debug: this.chatConfig$.value.defaultValues.debug
634
732
  };
635
733
  this.connection.on('SavedChatDelete', (res) => {
636
734
  deleteSavedChatSubject.next(res.deleteCount);
@@ -652,28 +750,36 @@ class WebSocketChatService extends ChatService {
652
750
  initMessageHandlers() {
653
751
  this.addMessageHandler("Debug", { handler: (debug) => console.log(debug),
654
752
  isGlobalHandler: true });
655
- this.addMessageHandler("ActionStart", { handler: (action) => this.actionMap.set(action.guid, action),
753
+ this.addMessageHandler("ActionStart", { handler: (action) => this._actionMap.set(action.guid, action),
656
754
  isGlobalHandler: false });
657
755
  this.addMessageHandler("ActionResult", {
658
- handler: (action) => this.actionMap.set(action.guid, { ...this.actionMap.get(action.guid), ...action }),
756
+ handler: (action) => this._actionMap.set(action.guid, { ...this._actionMap.get(action.guid), ...action }),
659
757
  isGlobalHandler: false
660
758
  });
661
759
  this.addMessageHandler("ActionStop", {
662
- handler: (action) => this.actionMap.set(action.guid, { ...this.actionMap.get(action.guid), ...action }),
760
+ handler: (action) => this._actionMap.set(action.guid, { ...this._actionMap.get(action.guid), ...action }),
663
761
  isGlobalHandler: false
664
762
  });
665
763
  this.addMessageHandler("ContextMessage", {
666
- handler: (message) => this.attachments.push(message.metadata),
764
+ handler: (message) => this._attachments.push(message.metadata),
667
765
  isGlobalHandler: false
668
766
  });
669
767
  this.addMessageHandler("Message", {
670
- handler: (message) => this.content += message ?? "",
768
+ handler: (message) => this._content += message ?? "",
671
769
  isGlobalHandler: false
672
770
  });
673
771
  this.addMessageHandler("History", {
674
772
  handler: (history) => {
675
773
  this.chatHistory = history.history;
676
- this.executionTime = history.executionTime;
774
+ // Re-attach the $progress and $attachment of the last response to the last assistant's response in the chat history
775
+ this.chatHistory.at(-1).additionalProperties.$progress = this._progress;
776
+ this.chatHistory.at(-1).additionalProperties.$attachment = this._attachments;
777
+ // Save/update the chat if savedChat enabled
778
+ if (this.chatConfig$.value.savedChatSettings.enabled && this.chatHistory.some((msg) => msg.additionalProperties?.isUserInput === true)) {
779
+ const action = !this.savedChatId ? this.addSavedChat(this.chatHistory) : this.updateSavedChat(this.savedChatId, undefined, this.chatHistory);
780
+ action.pipe(take(1)).subscribe();
781
+ }
782
+ this._executionTime = history.executionTime;
677
783
  },
678
784
  isGlobalHandler: false
679
785
  });
@@ -696,20 +802,20 @@ class WebSocketChatService extends ChatService {
696
802
  });
697
803
  }
698
804
  /**
699
- * Override and register the entire messageHandlers map by merging the provided map with the default one
700
- * @param messageHandlers
805
+ * Override and register the entire _messageHandlers map by merging the provided map with the default one
806
+ * @param _messageHandlers
701
807
  */
702
- overrideMessageHandlers(messageHandlers) {
808
+ overrideMessageHandlers(_messageHandlers) {
703
809
  // Clear the already registered global chat handlers before merging the new ones
704
- this.messageHandlers.forEach((eventHandler, eventName) => {
810
+ this._messageHandlers.forEach((eventHandler, eventName) => {
705
811
  if (eventHandler.isGlobalHandler) {
706
812
  this.unsubscribeMessageHandler(eventName);
707
813
  }
708
814
  });
709
815
  // Merge the new event handlers with the existing ones
710
- this.messageHandlers = new Map([...this.messageHandlers, ...messageHandlers]);
816
+ this._messageHandlers = new Map([...this._messageHandlers, ..._messageHandlers]);
711
817
  // Register the global chat handlers among the merged map
712
- this.messageHandlers.forEach((eventHandler, eventName) => {
818
+ this._messageHandlers.forEach((eventHandler, eventName) => {
713
819
  if (eventHandler.isGlobalHandler) {
714
820
  this.registerMessageHandler(eventName, eventHandler);
715
821
  }
@@ -723,7 +829,7 @@ class WebSocketChatService extends ChatService {
723
829
  * @param eventHandler The handler to be called when the event is received
724
830
  */
725
831
  addMessageHandler(eventName, eventHandler) {
726
- this.messageHandlers.set(eventName, eventHandler);
832
+ this._messageHandlers.set(eventName, eventHandler);
727
833
  if (eventHandler.isGlobalHandler) {
728
834
  this.registerMessageHandler(eventName, eventHandler);
729
835
  }
@@ -744,17 +850,17 @@ class WebSocketChatService extends ChatService {
744
850
  });
745
851
  }
746
852
  /**
747
- * Remove a listener for a specific event from the messageHandlers map and unsubscribe from receiving messages for this event from the SignalR hub.
853
+ * Remove a listener for a specific event from the _messageHandlers map and unsubscribe from receiving messages for this event from the SignalR hub.
748
854
  * @param eventName Name of the event to remove the listener for
749
855
  */
750
856
  removeMessageHandler(eventName) {
751
- this.messageHandlers.delete(eventName);
857
+ this._messageHandlers.delete(eventName);
752
858
  this.unsubscribeMessageHandler(eventName);
753
859
  }
754
860
  /**
755
861
  * Unsubscribe from receiving messages for a specific event from the SignalR hub.
756
862
  * ALL its related listeners will be removed from hub connection
757
- * This is needed to prevent accumulating old listeners when overriding the entire messageHandlers map
863
+ * This is needed to prevent accumulating old listeners when overriding the entire _messageHandlers map
758
864
  * @param eventName Name of the event
759
865
  */
760
866
  unsubscribeMessageHandler(eventName) {
@@ -773,7 +879,7 @@ class WebSocketChatService extends ChatService {
773
879
  return;
774
880
  }
775
881
  const logLevel = this.getLogLevel();
776
- this.connection = this.signalRService.buildConnection(this.REQUEST_URL, { ...this.defaultOptions, ...options }, logLevel);
882
+ this.connection = this.signalRService.buildConnection(this.REQUEST_URL, { ...this.defaultOptions, ...options }, logLevel, true);
777
883
  resolve();
778
884
  });
779
885
  }
@@ -792,7 +898,7 @@ class WebSocketChatService extends ChatService {
792
898
  return this.signalRService.stopConnection(this.connection);
793
899
  }
794
900
  getTransports() {
795
- switch (this.chatConfig$.value?.globalSettings.signalRTransport) {
901
+ switch (this.chatConfig$.value?.connectionSettings.signalRTransport) {
796
902
  case "WebSockets":
797
903
  return HttpTransportType.WebSockets;
798
904
  case "ServerSentEvents":
@@ -804,7 +910,7 @@ class WebSocketChatService extends ChatService {
804
910
  }
805
911
  }
806
912
  getLogLevel() {
807
- switch (this.chatConfig$.value?.globalSettings.signalRLogLevel) {
913
+ switch (this.chatConfig$.value?.connectionSettings.signalRLogLevel) {
808
914
  case "Critical":
809
915
  return LogLevel.Critical; // Log level for diagnostic messages that indicate a failure that will terminate the entire application.
810
916
  case "Debug":
@@ -1078,10 +1184,10 @@ class ChatReferenceComponent {
1078
1184
  }
1079
1185
  }
1080
1186
  ChatReferenceComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatReferenceComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1081
- ChatReferenceComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: ChatReferenceComponent, isStandalone: true, selector: "sq-chat-reference", inputs: { reference: "reference", attachment: "attachment", partId: "partId" }, outputs: { openDocument: "openDocument", openPreview: "openPreview" }, ngImport: i0, template: "<div [class.reference-tooltip]=\"!!partId\">\n <div class=\"reference-data\" [class.expanded]=\"attachment['$expanded'] || !!partId\">\n <span class=\"reference me-1\">{{reference}}</span>\n <sq-format-icon [extension]=\"attachment.record.fileext\"></sq-format-icon>\n <a [id]=\"'attachment-'+attachment.recordId\" (click)=\"expandAttachment()\">\n {{attachment.record.title}}\n </a>\n <i class=\"fas fa-eye\" (click)=\"openPreview.emit(attachment)\" [sqTooltip]=\"!partId ? 'Preview document' : ''\"></i>\n <i class=\"fas fa-arrow-up-right-from-square\" *ngIf=\"attachment.record.url1 || attachment.record.originalUrl\"\n (click)=\"openDocument.emit(attachment.record)\" [sqTooltip]=\"!partId ? 'Open document' : ''\"></i>\n </div>\n <div class=\"reference-passages\" *ngIf=\"!!partId || (attachment['$expanded']) && parts.length\">\n <div class=\"reference-passage\" *ngFor=\"let part of parts\">\n <span class=\"reference me-1\">{{reference}}.{{part.partId}}</span>\n <span [innerHTML]=\"part.text\"></span>\n </div>\n </div>\n</div>", styles: [".ast-primary{color:var(--ast-primary-color, #005DA7);background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover{background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover:hover{color:var(--ast-primary-color, #005DA7)}.ast-secondary{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-secondary-bg, #FFF8F1)}.ast-btn{border:0;text-align:left;padding-top:.5rem;padding-bottom:.5rem;display:flex;align-items:center}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference,:host ::ng-deep .attachment .reference{position:relative;bottom:var(--ast-reference-bottom, .3em);font-weight:var(--ast-reference-font-weight, bold);padding:var(--ast-reference-padding, 0 .2em);margin:var(--ast-reference-margin, 0 .1em);border-radius:var(--ast-reference-border-radius, .2em);background-color:var(--ast-reference-background-color, lightblue);color:var(--ast-reference-color, black)}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference{font-size:var(--ast-reference-message-font-size, .7em)}:host ::ng-deep .attachment .reference{font-size:var(--ast-reference-attachment-font-size, 13px)}:host{display:block}:host.expanded,:host:hover{background-color:#fff}.reference-data{display:flex;flex-direction:row;align-items:baseline;padding:var(--ast-size-1, .25rem)}.reference-data a{color:var(--ast-secondary-color, #FF732E);flex-grow:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:var(--font-weight-bold, 500);cursor:pointer}.reference-data i{padding:var(--ast-size-1, .25rem);margin-left:var(--ast-size-1, .25rem);cursor:pointer;color:#000}.reference-data i.active{color:#fff;background-color:var(--ast-secondary-color, #FF732E)}.reference-data:not(.expanded) i{opacity:0}.reference-data:not(.expanded):hover i{opacity:1}.reference-passages{white-space:normal;font-style:italic;font-weight:400;padding:1rem 0;color:#000}.reference-passages .reference-passage{display:flex;align-items:baseline;padding-left:2.5rem;padding-right:1rem;word-wrap:break-word}.reference-passages .reference-passage+.reference-passage{padding-top:1rem}.reference-passages .reference-passage .reference{margin-right:var(--ast-size-2, .5rem)}sq-format-icon{margin-left:var(--ast-size-1, .25rem);margin-right:var(--ast-size-2, .5rem);color:var(--ast-secondary-color, #FF732E)}.reference-tooltip{max-width:600px!important;box-shadow:0 .5rem 1rem #00000026;padding:.5rem;font-size:.875rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: UtilsModule }, { kind: "directive", type: i2$1.TooltipDirective, selector: "[sqTooltip]", inputs: ["sqTooltip", "sqTooltipData", "sqTooltipTemplate", "placement", "fallbackPlacements", "delay", "hoverableTooltip", "tooltipClass"] }, { kind: "component", type: FormatIconComponent, selector: "sq-format-icon", inputs: ["extension"] }] });
1187
+ 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}.dark{--ast-primary-bg: #0d0701;--ast-primary-color: #008cd1;--ast-secondary-bg: #00070e;--ast-secondary-color: #ffa258;--ast-input-bg: #070707;--ast-input-color: rgba(222, 218, 218, .75);--ast-muted-color: rgba(222, 218, 218, .75);--ast-saved-chat-hover-background: #262421;--ast-message-table-border-color: #333333;--ast-message-table-tr-bg: #070707;--ast-message-table-tr-border-color: #222222;--ast-reference-icon-color: white;--ast-reference-icon-active-color: black;--ast-reference-passages-color: white;--ast-reference-expanded-hover-bg: #262421;--ast-message-reference-color: black}: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:var(--ast-reference-expanded-hover-bg, white)}.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:var(--ast-reference-icon-color, black)}.reference-data i.active{color:var(--ast-reference-icon-active-color, white);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:var(--ast-reference-passages-color, black)}.reference-passages .reference-passage{display:flex;align-items:baseline;padding-left:2.5rem;padding-right:1rem;word-wrap:break-word}.reference-passages .reference-passage+.reference-passage{padding-top:1rem}.reference-passages .reference-passage .reference{margin-right:var(--ast-size-2, .5rem)}sq-format-icon{margin-left:var(--ast-size-1, .25rem);margin-right:var(--ast-size-2, .5rem);color:var(--ast-secondary-color, #FF732E)}.reference-tooltip{max-width:600px!important;box-shadow:0 .5rem 1rem #00000026;padding:.5rem;font-size:.875rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: UtilsModule }, { kind: "directive", type: i2$1.TooltipDirective, selector: "[sqTooltip]", inputs: ["sqTooltip", "sqTooltipData", "sqTooltipTemplate", "placement", "fallbackPlacements", "delay", "hoverableTooltip", "tooltipClass"] }, { kind: "component", type: FormatIconComponent, selector: "sq-format-icon", inputs: ["extension"] }] });
1082
1188
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatReferenceComponent, decorators: [{
1083
1189
  type: Component,
1084
- args: [{ selector: 'sq-chat-reference', standalone: true, imports: [CommonModule, UtilsModule, FormatIconComponent], template: "<div [class.reference-tooltip]=\"!!partId\">\n <div class=\"reference-data\" [class.expanded]=\"attachment['$expanded'] || !!partId\">\n <span class=\"reference me-1\">{{reference}}</span>\n <sq-format-icon [extension]=\"attachment.record.fileext\"></sq-format-icon>\n <a [id]=\"'attachment-'+attachment.recordId\" (click)=\"expandAttachment()\">\n {{attachment.record.title}}\n </a>\n <i class=\"fas fa-eye\" (click)=\"openPreview.emit(attachment)\" [sqTooltip]=\"!partId ? 'Preview document' : ''\"></i>\n <i class=\"fas fa-arrow-up-right-from-square\" *ngIf=\"attachment.record.url1 || attachment.record.originalUrl\"\n (click)=\"openDocument.emit(attachment.record)\" [sqTooltip]=\"!partId ? 'Open document' : ''\"></i>\n </div>\n <div class=\"reference-passages\" *ngIf=\"!!partId || (attachment['$expanded']) && parts.length\">\n <div class=\"reference-passage\" *ngFor=\"let part of parts\">\n <span class=\"reference me-1\">{{reference}}.{{part.partId}}</span>\n <span [innerHTML]=\"part.text\"></span>\n </div>\n </div>\n</div>", styles: [".ast-primary{color:var(--ast-primary-color, #005DA7);background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover{background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover:hover{color:var(--ast-primary-color, #005DA7)}.ast-secondary{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-secondary-bg, #FFF8F1)}.ast-btn{border:0;text-align:left;padding-top:.5rem;padding-bottom:.5rem;display:flex;align-items:center}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference,:host ::ng-deep .attachment .reference{position:relative;bottom:var(--ast-reference-bottom, .3em);font-weight:var(--ast-reference-font-weight, bold);padding:var(--ast-reference-padding, 0 .2em);margin:var(--ast-reference-margin, 0 .1em);border-radius:var(--ast-reference-border-radius, .2em);background-color:var(--ast-reference-background-color, lightblue);color:var(--ast-reference-color, black)}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference{font-size:var(--ast-reference-message-font-size, .7em)}:host ::ng-deep .attachment .reference{font-size:var(--ast-reference-attachment-font-size, 13px)}:host{display:block}:host.expanded,:host:hover{background-color:#fff}.reference-data{display:flex;flex-direction:row;align-items:baseline;padding:var(--ast-size-1, .25rem)}.reference-data a{color:var(--ast-secondary-color, #FF732E);flex-grow:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:var(--font-weight-bold, 500);cursor:pointer}.reference-data i{padding:var(--ast-size-1, .25rem);margin-left:var(--ast-size-1, .25rem);cursor:pointer;color:#000}.reference-data i.active{color:#fff;background-color:var(--ast-secondary-color, #FF732E)}.reference-data:not(.expanded) i{opacity:0}.reference-data:not(.expanded):hover i{opacity:1}.reference-passages{white-space:normal;font-style:italic;font-weight:400;padding:1rem 0;color:#000}.reference-passages .reference-passage{display:flex;align-items:baseline;padding-left:2.5rem;padding-right:1rem;word-wrap:break-word}.reference-passages .reference-passage+.reference-passage{padding-top:1rem}.reference-passages .reference-passage .reference{margin-right:var(--ast-size-2, .5rem)}sq-format-icon{margin-left:var(--ast-size-1, .25rem);margin-right:var(--ast-size-2, .5rem);color:var(--ast-secondary-color, #FF732E)}.reference-tooltip{max-width:600px!important;box-shadow:0 .5rem 1rem #00000026;padding:.5rem;font-size:.875rem}\n"] }]
1190
+ 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}.dark{--ast-primary-bg: #0d0701;--ast-primary-color: #008cd1;--ast-secondary-bg: #00070e;--ast-secondary-color: #ffa258;--ast-input-bg: #070707;--ast-input-color: rgba(222, 218, 218, .75);--ast-muted-color: rgba(222, 218, 218, .75);--ast-saved-chat-hover-background: #262421;--ast-message-table-border-color: #333333;--ast-message-table-tr-bg: #070707;--ast-message-table-tr-border-color: #222222;--ast-reference-icon-color: white;--ast-reference-icon-active-color: black;--ast-reference-passages-color: white;--ast-reference-expanded-hover-bg: #262421;--ast-message-reference-color: black}: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:var(--ast-reference-expanded-hover-bg, white)}.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:var(--ast-reference-icon-color, black)}.reference-data i.active{color:var(--ast-reference-icon-active-color, white);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:var(--ast-reference-passages-color, black)}.reference-passages .reference-passage{display:flex;align-items:baseline;padding-left:2.5rem;padding-right:1rem;word-wrap:break-word}.reference-passages .reference-passage+.reference-passage{padding-top:1rem}.reference-passages .reference-passage .reference{margin-right:var(--ast-size-2, .5rem)}sq-format-icon{margin-left:var(--ast-size-1, .25rem);margin-right:var(--ast-size-2, .5rem);color:var(--ast-secondary-color, #FF732E)}.reference-tooltip{max-width:600px!important;box-shadow:0 .5rem 1rem #00000026;padding:.5rem;font-size:.875rem}\n"] }]
1085
1191
  }], propDecorators: { reference: [{
1086
1192
  type: Input
1087
1193
  }], attachment: [{
@@ -1211,6 +1317,26 @@ class ChatMessageComponent {
1211
1317
  }
1212
1318
  this.referenceClicked.emit(record);
1213
1319
  }
1320
+ getLinkText(node) {
1321
+ if (node.text) {
1322
+ return node.text; // Return directly if text is provided in node.text ([Example link](https://example.com))
1323
+ }
1324
+ else if (node.children && node.children.length > 0) {
1325
+ // Recursively search for text content in child nodes
1326
+ for (const child of node.children) {
1327
+ if (child.type === 'text' && child.value) {
1328
+ return child.value; // Return the value of the first text node found ([**Emphasized Link Text**](https://example.com))
1329
+ }
1330
+ else if (child.children && child.children.length > 0) {
1331
+ const textContent = this.getLinkText(child); // Recursively search child nodes ([![Example image](https://example.com/image.png)](https://example.com))
1332
+ if (textContent) {
1333
+ return textContent; // Return text content if found
1334
+ }
1335
+ }
1336
+ }
1337
+ }
1338
+ return 'link'; // Return empty string if no text content is found
1339
+ }
1214
1340
  /**
1215
1341
  * Reformat [ids: 12.2, 42.5] to [12.2][42.5]
1216
1342
  */
@@ -1228,11 +1354,11 @@ class ChatMessageComponent {
1228
1354
  }
1229
1355
  }
1230
1356
  ChatMessageComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatMessageComponent, deps: [{ token: i1$1.SearchService }, { token: i2$1.UIService }, { token: i3.PrincipalWebService }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
1231
- ChatMessageComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: ChatMessageComponent, isStandalone: true, selector: "sq-chat-message", inputs: { message: "message", conversation: "conversation", assistantIcon: "assistantIcon", streaming: "streaming", canEdit: "canEdit", canRegenerate: "canRegenerate", canCopy: "canCopy" }, outputs: { referenceClicked: "referenceClicked", edit: "edit", regenerate: "regenerate", openPreview: "openPreview" }, usesOnChanges: true, ngImport: i0, template: "<!-- Message icon -->\n<span class=\"message-icon\" [title]=\"message.role\">\n <i [ngClass]=\"assistantIcon\" [style.--sq-size.px]=\"24\" *ngIf=\"message.role === 'assistant'\"></i>\n <sq-initials-avatar [fullName]=\"name\" *ngIf=\"message.role !== 'assistant'\"></sq-initials-avatar>\n</span>\n\n<!-- Message body -->\n<div class=\"flex-grow-1\" style=\"min-width: 0;\" [ngClass]=\"'message-'+message.role\">\n\n <div *ngIf=\"message.additionalProperties.$progress as progress\" class=\"small ms-3 mb-2\">\n <a href=\"#\" (click)=\"collapseProgress = !collapseProgress; false\" class=\"text-muted\">\n View progress\n <i class=\"fas fa-chevron-{{collapseProgress? 'right' : 'down'}}\"></i>\n </a>\n <sq-collapse [collapsed]=\"collapseProgress\">\n <ng-template>\n <ul class=\"list-unstyled\">\n <li *ngFor=\"let step of progress\" [title]=\"step.time ?? ''\">\n <i class=\"fas fa-fw fa-check text-success\" *ngIf=\"step.done\"></i>\n <i class=\"fas fa-fw fa-spin fa-circle-notch\" *ngIf=\"!step.done\"></i>\n <span class=\"ms-2 fw-bold\">{{step.title}}</span>\n <span *ngIf=\"step.content\">: {{step.content}}</span>\n </li>\n </ul>\n </ng-template>\n </sq-collapse>\n </div>\n\n <div class=\"message-content\" *ngIf=\"message.content\">\n\n <remark *ngIf=\"message.role === 'assistant'\" [markdown]=\"message.content\" [processor]=\"processor\">\n\n <ng-template remarkTemplate=\"chat-reference\" let-ref>\n\n <a *ngIf=\"referenceMap.get(ref.refId) as attachment; else staticRefTpl\"\n (click)=\"openDocument(attachment.record)\"\n class=\"reference\"\n [sqTooltip]=\"attachment\"\n [sqTooltipTemplate]=\"tooltipTpl\"\n [hoverableTooltip]=\"true\"\n >{{ref.refId}}</a>\n\n <ng-template #staticRefTpl>\n <span class=\"reference\">{{ref.refId}}</span>\n </ng-template>\n\n </ng-template>\n\n <ng-template remarkTemplate=\"streaming-placeholder\">\n <span class=\"placeholder-glow\" *ngIf=\"streaming\">\n <span class=\"placeholder ms-1\"></span>\n </span>\n </ng-template>\n\n <ng-template remarkTemplate=\"code\" let-node>\n <div class=\"card mb-2\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <span>{{node.lang}}</span>\n <button class=\"btn btn-light btn-sm\" (click)=\"copyToClipboard(node.value)\"><i class=\"far fa-fw fa-clipboard\"></i> Copy code</button>\n </div>\n <pre class=\"language-{{node.lang}} my-0 rounded-0 rounded-bottom\"><code class=\"language-{{node.lang}}\">{{node.value}}</code></pre>\n </div>\n </ng-template>\n\n </remark>\n\n <p *ngIf=\"message.role === 'user'\">{{message.content}}</p>\n\n <!-- List of reference, if any -->\n <div *ngIf=\"references?.length\" class=\"references\">\n <span class=\"references-title\">References</span> <i class=\"fas references-collapse\" [class.fa-chevron-down]=\"showReferences\" [class.fa-chevron-right]=\"!showReferences\" (click)=\"showReferences=!showReferences\"></i>\n <sq-collapse [collapsed]=\"!showReferences\">\n <ng-template>\n <ul>\n <ng-container *ngFor=\"let reference of references\">\n <li *ngIf=\"referenceMap.get(reference) as attachment\" class=\"text-truncate\">\n <sq-chat-reference [class.expanded]=\"attachment.$expanded\" [attachment]=\"attachment\" [reference]=\"reference\"\n (openPreview)=\"openPreview.emit($event)\" (openDocument)=\"openDocument($event)\"></sq-chat-reference>\n </li>\n </ng-container>\n </ul>\n </ng-template>\n </sq-collapse>\n </div>\n \n </div>\n\n <!-- Edit / Regenerate floating actions -->\n <div class=\"sq-chat-message-action\">\n <button class=\"btn btn-sm\" *ngIf=\"canEdit\" sqTooltip=\"Edit message\"\n (click)=\"edit.emit(message)\">\n <i class=\"fas fa-edit\"></i>\n </button>\n <button class=\"btn btn-sm\" *ngIf=\"canCopy\" sqTooltip=\"Copy to clipboard\"\n (click)=\"copyToClipboard(message.content)\">\n <i class=\"far fa-clipboard\"></i>\n </button>\n <button class=\"btn btn-sm\" *ngIf=\"canRegenerate\" sqTooltip=\"Regenerate message\"\n (click)=\"regenerate.emit(message)\">\n <i class=\"fas fa-sync-alt\"></i>\n </button>\n </div>\n\n <ng-template #tooltipTpl let-ref>\n <sq-chat-reference class=\"expanded\"\n [attachment]=\"ref\"\n [reference]=\"ref.contextId\"\n [partId]=\"ref.$partId\"\n (openPreview)=\"openPreview.emit($event)\"\n (openDocument)=\"openDocument($event)\">\n </sq-chat-reference>\n </ng-template>\n\n</div>\n", styles: [".ast-primary{color:var(--ast-primary-color, #005DA7);background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover{background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover:hover{color:var(--ast-primary-color, #005DA7)}.ast-secondary{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-secondary-bg, #FFF8F1)}.ast-btn{border:0;text-align:left;padding-top:.5rem;padding-bottom:.5rem;display:flex;align-items:center}:host{display:flex}:host:not(:hover) .sq-chat-message-action{display:none}.message-content{padding:var(--ast-message-padding, var(--ast-size-3, .75rem));border-radius:var(--ast-message-border-radius, var(--ast-size-4, 1rem));display:inline-block;max-width:100%}.message-content .references{margin-top:var(--ast-size-5, 1.25rem)}.message-content .references ul{border-left:.2rem solid var(--ast-secondary-color, #FF732E);padding-left:var(--ast-size-5, 1.25rem);margin-top:var(--ast-size-2, .5rem)}.message-content .references .references-title{font-weight:var(--font-weight-bold, 500)}.message-content .references .references-collapse{cursor:pointer;margin-left:var(--ast-size-2, .5rem)}.message-content ::ng-deep p:last-child{margin-bottom:0}.message-content ::ng-deep .placeholder-glow .placeholder{animation-duration:.4s;width:12px;height:var(--ast-size-4, 1rem);vertical-align:text-bottom}.message-content ::ng-deep img{max-width:100%}.message-content ::ng-deep table{display:block;border:1px solid #ccc;border-collapse:collapse;margin:0;padding:0;min-width:100%;overflow-x:auto;table-layout:fixed}.message-content ::ng-deep table tr{background-color:#f8f8f8;border:1px solid #ddd;padding:.35em}.message-content ::ng-deep table th,.message-content ::ng-deep table td{padding:.625em;text-align:center}.message-content ::ng-deep table th{font-size:.85em;letter-spacing:.1em;text-transform:uppercase}.message-assistant .message-content{background:var(--ast-secondary-bg, #FFF8F1)}.message-user .message-content{background:var(--ast-primary-bg, #f2f8fe);font-weight:var(--ast-user-font-weight, var(--font-weight-bold, 500))}.sq-chat-message-action{position:absolute;right:calc(0rem - var(--ast-size-2, .5rem));top:var(--ast-size-2, .5rem);display:flex;flex-direction:column}.message-icon{margin-top:var(--ast-size-3, .75rem);margin-right:var(--ast-size-4, 1rem)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: UtilsModule }, { kind: "directive", type: i2$1.TooltipDirective, selector: "[sqTooltip]", inputs: ["sqTooltip", "sqTooltipData", "sqTooltipTemplate", "placement", "fallbackPlacements", "delay", "hoverableTooltip", "tooltipClass"] }, { kind: "ngmodule", type: CollapseModule }, { kind: "component", type: i5.Collapse, selector: "sq-collapse", inputs: ["collapsed"] }, { kind: "ngmodule", type: RemarkModule }, { kind: "component", type: i6.RemarkComponent, selector: "remark", inputs: ["markdown", "processor", "debug"] }, { kind: "directive", type: i6.RemarkTemplateDirective, selector: "[remarkTemplate]", inputs: ["remarkTemplate"] }, { kind: "component", type: InitialsAvatarComponent, selector: "sq-initials-avatar", inputs: ["fullName", "size"] }, { kind: "component", type: ChatReferenceComponent, selector: "sq-chat-reference", inputs: ["reference", "attachment", "partId"], outputs: ["openDocument", "openPreview"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1357
+ 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\">\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 <ng-template remarkTemplate=\"link\" let-node>\n <a [href]=\"node.url\" target=\"_blank\" rel=\"noopener noreferrer\">{{getLinkText(node)}}</a>\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}.dark{--ast-primary-bg: #0d0701;--ast-primary-color: #008cd1;--ast-secondary-bg: #00070e;--ast-secondary-color: #ffa258;--ast-input-bg: #070707;--ast-input-color: rgba(222, 218, 218, .75);--ast-muted-color: rgba(222, 218, 218, .75);--ast-saved-chat-hover-background: #262421;--ast-message-table-border-color: #333333;--ast-message-table-tr-bg: #070707;--ast-message-table-tr-border-color: #222222;--ast-reference-icon-color: white;--ast-reference-icon-active-color: black;--ast-reference-passages-color: white;--ast-reference-expanded-hover-bg: #262421;--ast-message-reference-color: black}: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;border-color:var(--ast-message-table-border-color, #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:var(--ast-message-table-tr-bg, #f8f8f8);border:1px solid;border-color:var(--ast-message-table-tr-border-color, #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-content ::ng-deep .reference{color:var(--ast-message-reference-color, black)!important}.message-content ::ng-deep ul,.message-content ::ng-deep ol{display:flex;flex-direction:column;gap:.5rem;padding-right:2rem;margin-left:0;margin-right:0;padding-left:40px;unicode-bidi:-webkit-isolate;unicode-bidi:isolate;list-style:disc}.message-content ::ng-deep p{margin-top:.5rem}.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))}.message-user .message-content p{white-space:pre-line}.sq-chat-message-action{position:absolute;right:calc(0rem - var(--ast-size-2, .5rem));top:0;display:flex;flex-direction:column}.message-icon{margin-top:var(--ast-size-3, .75rem);margin-right:var(--ast-size-4, 1rem)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: UtilsModule }, { kind: "directive", type: i2$1.TooltipDirective, selector: "[sqTooltip]", inputs: ["sqTooltip", "sqTooltipData", "sqTooltipTemplate", "placement", "fallbackPlacements", "delay", "hoverableTooltip", "tooltipClass"] }, { kind: "ngmodule", type: CollapseModule }, { kind: "component", type: i5.Collapse, selector: "sq-collapse", inputs: ["collapsed"] }, { kind: "ngmodule", type: RemarkModule }, { kind: "component", type: i6.RemarkComponent, selector: "remark", inputs: ["markdown", "processor", "debug"] }, { kind: "directive", type: i6.RemarkTemplateDirective, selector: "[remarkTemplate]", inputs: ["remarkTemplate"] }, { kind: "component", type: InitialsAvatarComponent, selector: "sq-initials-avatar", inputs: ["fullName", "size"] }, { kind: "component", type: ChatReferenceComponent, selector: "sq-chat-reference", inputs: ["reference", "attachment", "partId"], outputs: ["openDocument", "openPreview"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1232
1358
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatMessageComponent, decorators: [{
1233
1359
  type: Component,
1234
1360
  args: [{ selector: "sq-chat-message", changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, UtilsModule, CollapseModule, RemarkModule,
1235
- InitialsAvatarComponent, ChatReferenceComponent], template: "<!-- Message icon -->\n<span class=\"message-icon\" [title]=\"message.role\">\n <i [ngClass]=\"assistantIcon\" [style.--sq-size.px]=\"24\" *ngIf=\"message.role === 'assistant'\"></i>\n <sq-initials-avatar [fullName]=\"name\" *ngIf=\"message.role !== 'assistant'\"></sq-initials-avatar>\n</span>\n\n<!-- Message body -->\n<div class=\"flex-grow-1\" style=\"min-width: 0;\" [ngClass]=\"'message-'+message.role\">\n\n <div *ngIf=\"message.additionalProperties.$progress as progress\" class=\"small ms-3 mb-2\">\n <a href=\"#\" (click)=\"collapseProgress = !collapseProgress; false\" class=\"text-muted\">\n View progress\n <i class=\"fas fa-chevron-{{collapseProgress? 'right' : 'down'}}\"></i>\n </a>\n <sq-collapse [collapsed]=\"collapseProgress\">\n <ng-template>\n <ul class=\"list-unstyled\">\n <li *ngFor=\"let step of progress\" [title]=\"step.time ?? ''\">\n <i class=\"fas fa-fw fa-check text-success\" *ngIf=\"step.done\"></i>\n <i class=\"fas fa-fw fa-spin fa-circle-notch\" *ngIf=\"!step.done\"></i>\n <span class=\"ms-2 fw-bold\">{{step.title}}</span>\n <span *ngIf=\"step.content\">: {{step.content}}</span>\n </li>\n </ul>\n </ng-template>\n </sq-collapse>\n </div>\n\n <div class=\"message-content\" *ngIf=\"message.content\">\n\n <remark *ngIf=\"message.role === 'assistant'\" [markdown]=\"message.content\" [processor]=\"processor\">\n\n <ng-template remarkTemplate=\"chat-reference\" let-ref>\n\n <a *ngIf=\"referenceMap.get(ref.refId) as attachment; else staticRefTpl\"\n (click)=\"openDocument(attachment.record)\"\n class=\"reference\"\n [sqTooltip]=\"attachment\"\n [sqTooltipTemplate]=\"tooltipTpl\"\n [hoverableTooltip]=\"true\"\n >{{ref.refId}}</a>\n\n <ng-template #staticRefTpl>\n <span class=\"reference\">{{ref.refId}}</span>\n </ng-template>\n\n </ng-template>\n\n <ng-template remarkTemplate=\"streaming-placeholder\">\n <span class=\"placeholder-glow\" *ngIf=\"streaming\">\n <span class=\"placeholder ms-1\"></span>\n </span>\n </ng-template>\n\n <ng-template remarkTemplate=\"code\" let-node>\n <div class=\"card mb-2\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <span>{{node.lang}}</span>\n <button class=\"btn btn-light btn-sm\" (click)=\"copyToClipboard(node.value)\"><i class=\"far fa-fw fa-clipboard\"></i> Copy code</button>\n </div>\n <pre class=\"language-{{node.lang}} my-0 rounded-0 rounded-bottom\"><code class=\"language-{{node.lang}}\">{{node.value}}</code></pre>\n </div>\n </ng-template>\n\n </remark>\n\n <p *ngIf=\"message.role === 'user'\">{{message.content}}</p>\n\n <!-- List of reference, if any -->\n <div *ngIf=\"references?.length\" class=\"references\">\n <span class=\"references-title\">References</span> <i class=\"fas references-collapse\" [class.fa-chevron-down]=\"showReferences\" [class.fa-chevron-right]=\"!showReferences\" (click)=\"showReferences=!showReferences\"></i>\n <sq-collapse [collapsed]=\"!showReferences\">\n <ng-template>\n <ul>\n <ng-container *ngFor=\"let reference of references\">\n <li *ngIf=\"referenceMap.get(reference) as attachment\" class=\"text-truncate\">\n <sq-chat-reference [class.expanded]=\"attachment.$expanded\" [attachment]=\"attachment\" [reference]=\"reference\"\n (openPreview)=\"openPreview.emit($event)\" (openDocument)=\"openDocument($event)\"></sq-chat-reference>\n </li>\n </ng-container>\n </ul>\n </ng-template>\n </sq-collapse>\n </div>\n \n </div>\n\n <!-- Edit / Regenerate floating actions -->\n <div class=\"sq-chat-message-action\">\n <button class=\"btn btn-sm\" *ngIf=\"canEdit\" sqTooltip=\"Edit message\"\n (click)=\"edit.emit(message)\">\n <i class=\"fas fa-edit\"></i>\n </button>\n <button class=\"btn btn-sm\" *ngIf=\"canCopy\" sqTooltip=\"Copy to clipboard\"\n (click)=\"copyToClipboard(message.content)\">\n <i class=\"far fa-clipboard\"></i>\n </button>\n <button class=\"btn btn-sm\" *ngIf=\"canRegenerate\" sqTooltip=\"Regenerate message\"\n (click)=\"regenerate.emit(message)\">\n <i class=\"fas fa-sync-alt\"></i>\n </button>\n </div>\n\n <ng-template #tooltipTpl let-ref>\n <sq-chat-reference class=\"expanded\"\n [attachment]=\"ref\"\n [reference]=\"ref.contextId\"\n [partId]=\"ref.$partId\"\n (openPreview)=\"openPreview.emit($event)\"\n (openDocument)=\"openDocument($event)\">\n </sq-chat-reference>\n </ng-template>\n\n</div>\n", styles: [".ast-primary{color:var(--ast-primary-color, #005DA7);background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover{background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover:hover{color:var(--ast-primary-color, #005DA7)}.ast-secondary{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-secondary-bg, #FFF8F1)}.ast-btn{border:0;text-align:left;padding-top:.5rem;padding-bottom:.5rem;display:flex;align-items:center}:host{display:flex}:host:not(:hover) .sq-chat-message-action{display:none}.message-content{padding:var(--ast-message-padding, var(--ast-size-3, .75rem));border-radius:var(--ast-message-border-radius, var(--ast-size-4, 1rem));display:inline-block;max-width:100%}.message-content .references{margin-top:var(--ast-size-5, 1.25rem)}.message-content .references ul{border-left:.2rem solid var(--ast-secondary-color, #FF732E);padding-left:var(--ast-size-5, 1.25rem);margin-top:var(--ast-size-2, .5rem)}.message-content .references .references-title{font-weight:var(--font-weight-bold, 500)}.message-content .references .references-collapse{cursor:pointer;margin-left:var(--ast-size-2, .5rem)}.message-content ::ng-deep p:last-child{margin-bottom:0}.message-content ::ng-deep .placeholder-glow .placeholder{animation-duration:.4s;width:12px;height:var(--ast-size-4, 1rem);vertical-align:text-bottom}.message-content ::ng-deep img{max-width:100%}.message-content ::ng-deep table{display:block;border:1px solid #ccc;border-collapse:collapse;margin:0;padding:0;min-width:100%;overflow-x:auto;table-layout:fixed}.message-content ::ng-deep table tr{background-color:#f8f8f8;border:1px solid #ddd;padding:.35em}.message-content ::ng-deep table th,.message-content ::ng-deep table td{padding:.625em;text-align:center}.message-content ::ng-deep table th{font-size:.85em;letter-spacing:.1em;text-transform:uppercase}.message-assistant .message-content{background:var(--ast-secondary-bg, #FFF8F1)}.message-user .message-content{background:var(--ast-primary-bg, #f2f8fe);font-weight:var(--ast-user-font-weight, var(--font-weight-bold, 500))}.sq-chat-message-action{position:absolute;right:calc(0rem - var(--ast-size-2, .5rem));top:var(--ast-size-2, .5rem);display:flex;flex-direction:column}.message-icon{margin-top:var(--ast-size-3, .75rem);margin-right:var(--ast-size-4, 1rem)}\n"] }]
1361
+ 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\">\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 <ng-template remarkTemplate=\"link\" let-node>\n <a [href]=\"node.url\" target=\"_blank\" rel=\"noopener noreferrer\">{{getLinkText(node)}}</a>\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}.dark{--ast-primary-bg: #0d0701;--ast-primary-color: #008cd1;--ast-secondary-bg: #00070e;--ast-secondary-color: #ffa258;--ast-input-bg: #070707;--ast-input-color: rgba(222, 218, 218, .75);--ast-muted-color: rgba(222, 218, 218, .75);--ast-saved-chat-hover-background: #262421;--ast-message-table-border-color: #333333;--ast-message-table-tr-bg: #070707;--ast-message-table-tr-border-color: #222222;--ast-reference-icon-color: white;--ast-reference-icon-active-color: black;--ast-reference-passages-color: white;--ast-reference-expanded-hover-bg: #262421;--ast-message-reference-color: black}: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;border-color:var(--ast-message-table-border-color, #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:var(--ast-message-table-tr-bg, #f8f8f8);border:1px solid;border-color:var(--ast-message-table-tr-border-color, #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-content ::ng-deep .reference{color:var(--ast-message-reference-color, black)!important}.message-content ::ng-deep ul,.message-content ::ng-deep ol{display:flex;flex-direction:column;gap:.5rem;padding-right:2rem;margin-left:0;margin-right:0;padding-left:40px;unicode-bidi:-webkit-isolate;unicode-bidi:isolate;list-style:disc}.message-content ::ng-deep p{margin-top:.5rem}.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))}.message-user .message-content p{white-space:pre-line}.sq-chat-message-action{position:absolute;right:calc(0rem - var(--ast-size-2, .5rem));top:0;display:flex;flex-direction:column}.message-icon{margin-top:var(--ast-size-3, .75rem);margin-right:var(--ast-size-4, 1rem)}\n"] }]
1236
1362
  }], ctorParameters: function () { return [{ type: i1$1.SearchService }, { type: i2$1.UIService }, { type: i3.PrincipalWebService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }]; }, propDecorators: { message: [{
1237
1363
  type: Input
1238
1364
  }], conversation: [{
@@ -1275,7 +1401,10 @@ class RestChatService extends ChatService {
1275
1401
  this.listFunctions()
1276
1402
  ])),
1277
1403
  // Map the results of parallel requests to a boolean indicating success
1278
- map(([models, functions]) => !!models && !!functions),
1404
+ map(([models, functions]) => {
1405
+ this.initProcess$.next(true);
1406
+ return !!models && !!functions;
1407
+ }),
1279
1408
  // Any errors during the process are caught, logged, and re-thrown to propagate the error further
1280
1409
  catchError((error) => {
1281
1410
  console.error('Error occurred:', error);
@@ -1289,8 +1418,8 @@ class RestChatService extends ChatService {
1289
1418
  * It can be overridden by the app config
1290
1419
  */
1291
1420
  getRequestsUrl() {
1292
- if (this.chatConfig$.value.globalSettings.restEndpoint) {
1293
- this.REQUEST_URL = this.chatConfig$.value.globalSettings.restEndpoint;
1421
+ if (this.chatConfig$.value.connectionSettings.restEndpoint) {
1422
+ this.REQUEST_URL = this.chatConfig$.value.connectionSettings.restEndpoint;
1294
1423
  }
1295
1424
  else {
1296
1425
  throw new Error(`The property 'restEndpoint' must be provided when attempting to use 'REST' in assistant instance`);
@@ -1299,7 +1428,7 @@ class RestChatService extends ChatService {
1299
1428
  listModels() {
1300
1429
  const data = {
1301
1430
  action: "listmodels",
1302
- debug: this.chatConfig$.value.debug
1431
+ debug: this.chatConfig$.value.defaultValues.debug
1303
1432
  };
1304
1433
  return this.jsonMethodWebService.get(this.REQUEST_URL, data).pipe(map(res => res.models), tap(models => this.models = models?.filter(model => !!model.enable)), catchError((error) => {
1305
1434
  console.error('Error invoking listmodels:', error);
@@ -1309,30 +1438,36 @@ class RestChatService extends ChatService {
1309
1438
  listFunctions() {
1310
1439
  const data = {
1311
1440
  action: "listfunctions",
1312
- debug: this.chatConfig$.value.debug
1441
+ debug: this.chatConfig$.value.defaultValues.debug
1313
1442
  };
1314
- return this.jsonMethodWebService.get(this.REQUEST_URL, data).pipe(map(res => res.functions), tap(functions => this.functions = functions), catchError((error) => {
1443
+ return this.jsonMethodWebService.get(this.REQUEST_URL, data).pipe(map(res => res.functions), tap((functions) => this.functions = functions?.filter(func => func.enabled && !!this.chatConfig$.value.defaultValues.functions.find(fn => fn.name === func.functionName))), catchError((error) => {
1315
1444
  console.error('Error invoking listfunctions:', error);
1316
1445
  return throwError(() => error);
1317
1446
  }));
1318
1447
  }
1319
- fetch(messages, query = this.searchService.query) {
1448
+ fetch(messages, query) {
1320
1449
  // Start streaming by invoking the Chat method
1321
1450
  this.streaming$.next(true);
1322
1451
  // Prepare the payload to send to the Chat method
1323
1452
  const data = {
1324
1453
  action: "chat",
1325
1454
  history: messages,
1326
- functions: this.chatConfig$.value.functions,
1327
- debug: this.chatConfig$.value.debug,
1328
- serviceSettings: this.chatConfig$.value.serviceSettings,
1329
- contextSettings: {
1330
- ...this.chatConfig$.value.contextSettings,
1455
+ functions: this.chatConfig$.value.defaultValues.functions?.filter(func => func.enabled).map(func => func.name),
1456
+ debug: this.chatConfig$.value.defaultValues.debug,
1457
+ serviceSettings: {
1458
+ service_id: this.chatConfig$.value.defaultValues.service_id,
1459
+ model_id: this.chatConfig$.value.defaultValues.model_id,
1460
+ top_p: this.chatConfig$.value.defaultValues.top_p,
1461
+ temperature: this.chatConfig$.value.defaultValues.temperature,
1462
+ max_tokens: this.chatConfig$.value.defaultValues.max_tokens,
1463
+ ...this.chatConfig$.value.additionalServiceSettings
1464
+ },
1465
+ appQuery: {
1331
1466
  app: this.appService.appName,
1332
1467
  query
1333
1468
  }
1334
1469
  };
1335
- if (this.chatConfig$.value.saveChats) {
1470
+ if (this.chatConfig$.value.savedChatSettings.enabled) {
1336
1471
  data.instanceId = this.chatInstanceId;
1337
1472
  data.savedChatId = this.savedChatId;
1338
1473
  }
@@ -1366,33 +1501,71 @@ class RestChatService extends ChatService {
1366
1501
  if (res.context) {
1367
1502
  response.additionalProperties.$attachment = res.context.map((ctx) => ctx.additionalProperties);
1368
1503
  }
1369
- // Update the chat history with the history property of the res
1504
+ // Update the chat history with the incoming history property of the res AND the processed response message
1370
1505
  this.chatHistory = res.history;
1506
+ this.chatHistory[this.chatHistory.length - 1] = response;
1507
+ // Save/update the chat if savedChat enabled
1508
+ if (this.chatConfig$.value.savedChatSettings.enabled && this.chatHistory.some((msg) => msg.additionalProperties?.isUserInput === true)) {
1509
+ const action = !this.savedChatId ? this.addSavedChat(this.chatHistory) : this.updateSavedChat(this.savedChatId, undefined, this.chatHistory);
1510
+ action.pipe(take(1)).subscribe();
1511
+ }
1371
1512
  // Return the result
1372
1513
  return { history: [...messages, response], executionTime: res.executionTime };
1373
- }), tap(() => this.notifyAudit(this.chatHistory, this.chatConfig$.value.serviceSettings.service_id)), finalize(() => this.streaming$.next(false)));
1514
+ }), tap(() => this.notifyAudit(this.chatHistory, this.chatConfig$.value.defaultValues.service_id)), finalize(() => this.streaming$.next(false)));
1374
1515
  }
1375
1516
  listSavedChat() {
1376
1517
  const data = {
1377
1518
  action: "SavedChatList",
1378
1519
  instanceId: this.chatInstanceId,
1379
- debug: this.chatConfig$.value.debug
1520
+ debug: this.chatConfig$.value.defaultValues.debug
1380
1521
  };
1381
1522
  this.jsonMethodWebService.get(this.REQUEST_URL, data).subscribe(res => this.savedChats$.next(res.savedChats), error => {
1382
- console.error('Error occurred while calling the SavedChatList API:', error);
1383
- this.notificationsService.error('Error occurred while calling the SavedChatList API');
1523
+ console.error('Error occurred while calling the SavedChatList API:', error.error.errorMessage);
1524
+ this.notificationsService.error('Error occurred while calling the SavedChatList API:', error.error.errorMessage);
1384
1525
  });
1385
1526
  }
1527
+ addSavedChat(messages) {
1528
+ const data = {
1529
+ action: "SavedChatAdd",
1530
+ instanceId: this.chatInstanceId,
1531
+ savedChatId: ChatService.generateGUID(),
1532
+ history: messages,
1533
+ debug: this.chatConfig$.value.defaultValues.debug
1534
+ };
1535
+ return this.jsonMethodWebService.post(this.REQUEST_URL, data).pipe(map(res => res.savedChat), tap((savedChat) => this.setSavedChatId(savedChat.id)), // Persist the savedChatId
1536
+ catchError((error) => {
1537
+ console.error('Error occurred while calling the SavedChatAdd API:', error.error.errorMessage);
1538
+ this.notificationsService.error('Error occurred while calling the SavedChatAdd API:', error.error.errorMessage);
1539
+ return throwError(() => error);
1540
+ }));
1541
+ }
1386
1542
  getSavedChat(id) {
1387
1543
  const data = {
1388
1544
  action: "SavedChatGet",
1389
1545
  instanceId: this.chatInstanceId,
1390
1546
  savedChatId: id,
1391
- debug: this.chatConfig$.value.debug
1547
+ debug: this.chatConfig$.value.defaultValues.debug
1392
1548
  };
1393
1549
  return this.jsonMethodWebService.get(this.REQUEST_URL, data).pipe(map(res => res.savedChat), catchError((error) => {
1394
- console.error('Error occurred while calling the SavedChatGet API:', error);
1395
- this.notificationsService.error('Error occurred while calling the SavedChatGet API');
1550
+ console.error('Error occurred while calling the SavedChatGet API:', error.error.errorMessage);
1551
+ this.notificationsService.error('Error occurred while calling the SavedChatGet API:', error.error.errorMessage);
1552
+ return throwError(() => error);
1553
+ }));
1554
+ }
1555
+ updateSavedChat(id, name, messages) {
1556
+ const data = {
1557
+ action: "SavedChatUpdate",
1558
+ instanceId: this.chatInstanceId,
1559
+ savedChatId: id,
1560
+ debug: this.chatConfig$.value.defaultValues.debug
1561
+ };
1562
+ if (name)
1563
+ data["title"] = name;
1564
+ if (messages)
1565
+ data["history"] = messages;
1566
+ return this.jsonMethodWebService.post(this.REQUEST_URL, data).pipe(map(res => res.savedChat), catchError((error) => {
1567
+ console.error('Error occurred while calling the SavedChatUpdate API:', error.error.errorMessage);
1568
+ this.notificationsService.error('Error occurred while calling the SavedChatUpdate API:', error.error.errorMessage);
1396
1569
  return throwError(() => error);
1397
1570
  }));
1398
1571
  }
@@ -1401,11 +1574,11 @@ class RestChatService extends ChatService {
1401
1574
  action: "SavedChatDelete",
1402
1575
  instanceId: this.chatInstanceId,
1403
1576
  savedChatIds: ids,
1404
- debug: this.chatConfig$.value.debug
1577
+ debug: this.chatConfig$.value.defaultValues.debug
1405
1578
  };
1406
1579
  return this.jsonMethodWebService.post(this.REQUEST_URL, data).pipe(map(res => res.deletedCount), catchError((error) => {
1407
- console.error('Error occurred while calling the SavedChatDelete API:', error);
1408
- this.notificationsService.error('Error occurred while calling the SavedChatDelete API ');
1580
+ console.error('Error occurred while calling the SavedChatDelete API:', error.error.errorMessage);
1581
+ this.notificationsService.error('Error occurred while calling the SavedChatDelete API:', error.error.errorMessage);
1409
1582
  return throwError(() => error);
1410
1583
  }));
1411
1584
  }
@@ -1419,14 +1592,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImpor
1419
1592
  class ChatComponent extends AbstractFacet {
1420
1593
  constructor() {
1421
1594
  super();
1595
+ this.loginService = inject(LoginService);
1596
+ this.websocketService = inject(WebSocketChatService);
1597
+ this.restService = inject(RestChatService);
1598
+ this.instanceManagerService = inject(InstanceManagerService);
1599
+ this.searchService = inject(SearchService);
1600
+ this.principalService = inject(PrincipalWebService);
1601
+ this.cdr = inject(ChangeDetectorRef);
1602
+ /** Define the query to use to fetch answers */
1603
+ this.query = this.searchService.query;
1422
1604
  /** Define the protocol to be used for this chat instance*/
1423
1605
  this.protocol = "WEBSOCKET";
1424
1606
  /** Map of listeners overriding default registered ones*/
1425
1607
  this.messageHandlers = new Map();
1426
1608
  /** When the assistant answer a user question, automatically scroll down to the bottom of the discussion */
1427
1609
  this.automaticScrollToLastResponse = false;
1428
- this.enableChat = true;
1429
- this.showCredits = true;
1430
1610
  this.customAssistantIcon = '';
1431
1611
  this.data = new EventEmitter();
1432
1612
  this.referenceClicked = new EventEmitter();
@@ -1439,16 +1619,10 @@ class ChatComponent extends AbstractFacet {
1439
1619
  this._actions = [];
1440
1620
  this.sub = new Subscription();
1441
1621
  this.changes$ = new BehaviorSubject(undefined);
1442
- this.handleFirstChanges = false;
1622
+ this.firstChangesHandled = false;
1443
1623
  this.isAtBottom = true;
1444
1624
  this.initializationError = false;
1445
- this.loginService = inject(LoginService);
1446
- this.websocketService = inject(WebSocketChatService);
1447
- this.restService = inject(RestChatService);
1448
- this.instanceManagerService = inject(InstanceManagerService);
1449
- this.searchService = inject(SearchService);
1450
- this.principalService = inject(PrincipalWebService);
1451
- this.cdr = inject(ChangeDetectorRef);
1625
+ this.enabledUserInput = false;
1452
1626
  this._actions.push(new Action({
1453
1627
  icon: 'fas fa-sync',
1454
1628
  title: 'Reset chat',
@@ -1458,13 +1632,14 @@ class ChatComponent extends AbstractFacet {
1458
1632
  ngOnInit() {
1459
1633
  this.sub.add(this.loginService.events.pipe(filter(e => e.type === 'login-complete'), tap(_ => this.instantiateChatService()), map(_ => this.chatService.initChatConfig()), switchMap(() => this.chatService.initConfig$), filter(initConfig => !!initConfig), switchMap(_ => this.chatService.init()), filter(success => !!success), tap(_ => this.onLoadChat()), switchMap(_ => this.chatService.chatConfig$), tap(config => {
1460
1634
  this.config = config;
1635
+ this.enabledUserInput = this.config.modeSettings.enabledUserInput;
1461
1636
  this._config.emit(config);
1462
1637
  try {
1463
1638
  this.updateModelDescription();
1464
- if (!this.handleFirstChanges) {
1639
+ if (!this.firstChangesHandled) {
1465
1640
  this.handleChanges();
1466
1641
  this.addScrollListener();
1467
- this.handleFirstChanges = true;
1642
+ this.firstChangesHandled = true;
1468
1643
  }
1469
1644
  }
1470
1645
  catch (error) {
@@ -1500,12 +1675,50 @@ class ChatComponent extends AbstractFacet {
1500
1675
  get actions() { return this._actions; }
1501
1676
  handleChanges() {
1502
1677
  const changes = this.changes$.value;
1678
+ // If the chat service is a WebSocketChatService, handle the override of the message handlers if exists
1503
1679
  if (changes?.messageHandlers && this.messageHandlers && this.chatService instanceof WebSocketChatService) {
1504
1680
  this.chatService.overrideMessageHandlers(this.messageHandlers);
1505
1681
  }
1506
- if (!this.messages$.value || changes?.chat) {
1507
- // Load the chat
1508
- this.chat ? this.openChat(this.chat.messages) : this.loadDefaultChat();
1682
+ /**
1683
+ * Initialize the chat with the provided chat messages if exists, otherwise load the default chat
1684
+ * Once the chat is initialized (firstChangesHandled is true), allow opening the chat with the new provided messages (if exists)
1685
+ */
1686
+ if (!this.firstChangesHandled || changes?.chat) {
1687
+ const openChat = () => {
1688
+ if (this.messages$.value && this.config.savedChatSettings.enabled) {
1689
+ this.chatService.listSavedChat(); // Refresh the list of saved chats
1690
+ }
1691
+ this.openChat(this.chat.messages);
1692
+ };
1693
+ this.chat ? openChat() : this.loadDefaultChat();
1694
+ }
1695
+ /**
1696
+ * If the chat is initialized, the initialization event is "Query", the query changes and the queryChangeShouldTriggerReload function is provided,
1697
+ * then the chat should be reloaded if the function returns true
1698
+ * Otherwise, the chat should be reloaded by default
1699
+ */
1700
+ if (this.firstChangesHandled && changes?.query && this.config.modeSettings.initialization.event === 'Query') {
1701
+ const previousQuery = changes.query.previousValue;
1702
+ const currentQuery = changes.query.currentValue;
1703
+ if (this.queryChangeShouldTriggerReload ? this.queryChangeShouldTriggerReload(previousQuery, currentQuery) : true) {
1704
+ this.triggerReloadAfterQueryChange(currentQuery);
1705
+ }
1706
+ }
1707
+ }
1708
+ triggerReloadAfterQueryChange(query) {
1709
+ const systemMsg = { role: 'system', content: this.config.defaultValues.systemPrompt, additionalProperties: { display: false } };
1710
+ const userMsg = { role: 'user', content: ChatService.formatPrompt(this.config.defaultValues.userPrompt, { principal: this.principalService.principal }), additionalProperties: { display: this.config.modeSettings.displayUserPrompt } };
1711
+ /**
1712
+ * If the provided query text is not empty, then add the user query message to the chat history and invoke the assistant
1713
+ * Otherwise, just start a new chat with a warning message inviting the user to perform a full text search to retrieve some results
1714
+ */
1715
+ if (!!this.query.text) {
1716
+ const userQueryMsg = { role: 'user', content: this.query.text, additionalProperties: { display: this.config.modeSettings.initialization.displayUserQuery, query: this.query, forcedWorkflow: this.config.modeSettings.initialization.forcedWorkflow, isUserInput: true } };
1717
+ this.openChat(this.config.modeSettings.sendUserPrompt ? [systemMsg, userMsg, userQueryMsg] : [systemMsg, userQueryMsg]);
1718
+ }
1719
+ else {
1720
+ const warningMsg = { role: 'assistant', content: "You must perform a full text search to retrieve some results", additionalProperties: { display: true } };
1721
+ this.openChat([warningMsg]);
1509
1722
  }
1510
1723
  }
1511
1724
  addScrollListener() {
@@ -1515,7 +1728,7 @@ class ChatComponent extends AbstractFacet {
1515
1728
  }));
1516
1729
  }
1517
1730
  updateModelDescription() {
1518
- this.modelDescription = this.chatService.getModel(this.config.serviceSettings.service_id, this.config.serviceSettings.model_id);
1731
+ this.modelDescription = this.chatService.getModel(this.config.defaultValues.service_id, this.config.defaultValues.model_id);
1519
1732
  this.assistantIcon = !!this.customAssistantIcon ? this.customAssistantIcon : 'sq-sinequa';
1520
1733
  switch (this.modelDescription?.provider) {
1521
1734
  case 'Google':
@@ -1542,17 +1755,15 @@ class ChatComponent extends AbstractFacet {
1542
1755
  this.chatService.chatHistory = this.chatService.chatHistory.slice(0, this.messageToEdit);
1543
1756
  this.messageToEdit = undefined;
1544
1757
  }
1545
- // Re-attach the $progress and $attachment of the last response to the last assistant's response in the chat history
1546
- this.chatService.chatHistory.at(-1).additionalProperties.$progress = this.messages$.value.at(-1).additionalProperties.$progress;
1547
- this.chatService.chatHistory.at(-1).additionalProperties.$attachment = this.messages$.value.at(-1).additionalProperties.$attachment;
1548
1758
  // Fetch the answer
1549
1759
  this.fetchAnswer(this.question.trim(), this.chatService.chatHistory);
1550
1760
  // Clear the input value in the UI
1551
1761
  this.questionInput.nativeElement.value = '';
1762
+ this.questionInput.nativeElement.style.height = `auto`;
1552
1763
  }
1553
1764
  }
1554
1765
  fetchAnswer(question, conversation) {
1555
- const userMsg = { role: 'user', content: question, additionalProperties: { display: true } };
1766
+ const userMsg = { role: 'user', content: question, additionalProperties: { display: true, isUserInput: true } };
1556
1767
  const messages = [...conversation, userMsg];
1557
1768
  this.messages$.next(messages);
1558
1769
  this.fetch(messages);
@@ -1609,22 +1820,56 @@ class ChatComponent extends AbstractFacet {
1609
1820
  }
1610
1821
  }, 10);
1611
1822
  }
1823
+ /**
1824
+ * Start a new chat with the defaultValues settings
1825
+ * The savedChatId in the chat service will be reset, so that the upcoming saved chat operations will be performed on the fresh new chat
1826
+ * If the savedChat feature is enabled, the list of saved chats will be refreshed
1827
+ */
1612
1828
  newChat() {
1613
- this.chatService.listSavedChat(); // Refresh the list of saved chats
1829
+ this.chatService.setSavedChatId(undefined); // Reset the savedChatId
1830
+ if (this.config.savedChatSettings.enabled) {
1831
+ this.chatService.listSavedChat(); // Refresh the list of saved chats
1832
+ }
1614
1833
  this.loadDefaultChat(); // Start a new chat
1615
1834
  }
1835
+ /**
1836
+ * Start the default chat with the defaultValues settings
1837
+ * If the chat is meant to be initialized with event === "Query", the corresponding user query message will be added to the chat history
1838
+ */
1616
1839
  loadDefaultChat() {
1617
- this.openChat([
1618
- { role: 'system', content: this.config.uiSettings.systemPrompt, additionalProperties: { display: false } },
1619
- { role: 'user', content: ChatService.formatPrompt(this.config.uiSettings.userPrompt, { principal: this.principalService.principal }), additionalProperties: { display: true } },
1620
- ]);
1840
+ const systemMsg = { role: 'system', content: this.config.defaultValues.systemPrompt, additionalProperties: { display: false } };
1841
+ const userMsg = { role: 'user', content: ChatService.formatPrompt(this.config.defaultValues.userPrompt, { principal: this.principalService.principal }), additionalProperties: { display: this.config.modeSettings.displayUserPrompt } };
1842
+ if (this.config.modeSettings.initialization.event === 'Query') {
1843
+ // If the provided query text is not empty, then add the user query message to the chat history and invoke the assistant
1844
+ // Otherwise, just start a new chat with a warning message inviting the user to perform a full text search to retrieve some results
1845
+ if (!!this.query.text) {
1846
+ const userQueryMsg = { role: 'user', content: this.query.text, additionalProperties: { display: this.config.modeSettings.initialization.displayUserQuery, query: this.query, forcedWorkflow: this.config.modeSettings.initialization.forcedWorkflow, isUserInput: true } };
1847
+ this.openChat(this.config.modeSettings.sendUserPrompt ? [systemMsg, userMsg, userQueryMsg] : [systemMsg, userQueryMsg]);
1848
+ }
1849
+ else {
1850
+ const warningMsg = { role: 'assistant', content: "You must perform a full text search to retrieve some results", additionalProperties: { display: true } };
1851
+ this.openChat([warningMsg]);
1852
+ }
1853
+ }
1854
+ else {
1855
+ this.openChat([systemMsg, userMsg]);
1856
+ }
1621
1857
  }
1858
+ /**
1859
+ * Start/open a new chat with the provided messages and chatId
1860
+ * If the last message is from the user, a request to the assistant is made to get an answer
1861
+ * If the last message is from the assistant, the conversation is loaded right away
1862
+ * @param messages The list of messages of the chat
1863
+ * @param chatId The id of the chat. If provided (ie. an existing discussion in the saved chat index), update the savedChatId in the chat service for the upcoming saved chat operations
1864
+ */
1622
1865
  openChat(messages, chatId) {
1623
1866
  if (!messages || !Array.isArray(messages)) {
1624
1867
  console.error('Error occurs while trying to load the chat discussion. Invalid messages received :', messages);
1625
1868
  return;
1626
1869
  }
1627
- this.chatService.setSavedChatId(chatId || ChatService.generateGUID());
1870
+ if (chatId) {
1871
+ this.chatService.setSavedChatId(chatId);
1872
+ }
1628
1873
  this.resetChat();
1629
1874
  this.messages$.next(messages);
1630
1875
  this.chatService.chatHistory = messages;
@@ -1637,6 +1882,11 @@ class ChatComponent extends AbstractFacet {
1637
1882
  this.terminateFetch();
1638
1883
  }
1639
1884
  }
1885
+ /**
1886
+ * Reset the chat by clearing the messages and the chat history
1887
+ * The question input will be focused after the chat is reset
1888
+ * The fetch subscription will be terminated
1889
+ */
1640
1890
  resetChat() {
1641
1891
  if (this.messages$.value) {
1642
1892
  this.messages$.next(undefined); // Reset chat
@@ -1648,8 +1898,7 @@ class ChatComponent extends AbstractFacet {
1648
1898
  onLoadChat() {
1649
1899
  this.loading$.next(true);
1650
1900
  this.sub.add(this.chatService.loadSavedChat$
1651
- .pipe(filter(savedChat => !!savedChat), switchMap(savedChat => this.chatService.getSavedChat(savedChat.id)), filter(savedChatHistory => !!savedChatHistory), tap(savedChatHistory => this.openChat(savedChatHistory.History, savedChatHistory.id)), finalize(() => this.chatService.listSavedChat()) // Refresh the list of saved chats
1652
- ).subscribe());
1901
+ .pipe(filter(savedChat => !!savedChat), switchMap(savedChat => this.chatService.getSavedChat(savedChat.id)), filter(savedChatHistory => !!savedChatHistory), tap(savedChatHistory => this.openChat(savedChatHistory.history, savedChatHistory.id))).subscribe());
1653
1902
  }
1654
1903
  terminateFetch() {
1655
1904
  this.dataSubscription?.unsubscribe();
@@ -1675,60 +1924,44 @@ class ChatComponent extends AbstractFacet {
1675
1924
  }
1676
1925
  onKeyUp(event) {
1677
1926
  switch (event.key) {
1678
- case 'ArrowUp':
1679
- this.navigateMessage(-1);
1680
- break;
1681
- case 'ArrowDown':
1682
- this.navigateMessage(1);
1927
+ case 'Backspace':
1928
+ this.calculateHeight();
1683
1929
  break;
1684
1930
  case 'Enter':
1685
- this.submitQuestion();
1931
+ if (!event.shiftKey)
1932
+ this.submitQuestion();
1933
+ this.calculateHeight();
1686
1934
  break;
1687
1935
  default:
1688
1936
  break;
1689
1937
  }
1690
- // Handle Shift + Enter
1691
- if (event.shiftKey && event.key === 'Enter') {
1692
- this.submitQuestion();
1693
- }
1694
1938
  }
1695
- navigateMessage(direction) {
1696
- if (!this.chatService.chatHistory || this.chatService.chatHistory.length < 1) {
1697
- return;
1698
- }
1699
- const userMessages = this.chatService.chatHistory.filter(m => m.role === 'user');
1700
- if (userMessages.length < 1) {
1701
- return;
1702
- }
1703
- this.currentMessageIndex = (this.currentMessageIndex ?? userMessages.length) + direction;
1704
- if (this.currentMessageIndex < 0) {
1705
- // If the user presses up arrow on the first message, stay at the first message
1706
- this.currentMessageIndex = 0;
1707
- }
1708
- else if (this.currentMessageIndex >= userMessages.length) {
1709
- // If the user presses down arrow on the last previous message, clear the input
1710
- this.currentMessageIndex = undefined;
1711
- this.question = '';
1712
- return;
1713
- }
1714
- this.question = userMessages[this.currentMessageIndex].content;
1939
+ calculateHeight() {
1940
+ const maxHeight = 170;
1941
+ const el = this.questionInput.nativeElement;
1942
+ el.style.maxHeight = `${maxHeight}px`;
1943
+ el.style.height = 'auto';
1944
+ el.style.height = `${el.scrollHeight}px`;
1945
+ el.style.overflowY = el.scrollHeight >= maxHeight ? 'scroll' : 'hidden';
1715
1946
  }
1716
1947
  }
1717
1948
  ChatComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1718
- ChatComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: ChatComponent, isStandalone: true, selector: "sq-chat-v3", inputs: { instanceId: "instanceId", query: "query", protocol: "protocol", messageHandlers: "messageHandlers", automaticScrollToLastResponse: "automaticScrollToLastResponse", chat: "chat", enableChat: "enableChat", showCredits: "showCredits", customAssistantIcon: "customAssistantIcon" }, outputs: { data: "data", referenceClicked: "referenceClicked", openPreview: "openPreview", loading$: "loading", error: "error", _config: "config" }, providers: [
1949
+ 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", queryChangeShouldTriggerReload: "queryChangeShouldTriggerReload", protocol: "protocol", messageHandlers: "messageHandlers", automaticScrollToLastResponse: "automaticScrollToLastResponse", chat: "chat", customAssistantIcon: "customAssistantIcon" }, outputs: { data: "data", referenceClicked: "referenceClicked", openPreview: "openPreview", loading$: "loading", error: "error", _config: "config" }, providers: [
1719
1950
  RestChatService,
1720
1951
  WebSocketChatService
1721
- ], queries: [{ propertyName: "loadingTpl", first: true, predicate: ["loadingTpl"], descendants: true }], viewQueries: [{ propertyName: "messageList", first: true, predicate: ["messageList"], descendants: true }, { propertyName: "questionInput", first: true, predicate: ["questionInput"], descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "\n<ng-container *ngIf=\"!initializationError\">\n <div *ngIf=\"messages$ | async as messages; else loadingTpl || loadingTplDefault\" class=\"h-100 d-flex flex-column\">\n\n <ul class=\"list-group list-group-flush overflow-auto pb-5\" #messageList>\n <ng-container *ngFor=\"let message of messages; let index = index; let last = last\">\n <!-- Regular messages -->\n <li class=\"list-group-item\" *ngIf=\"message.additionalProperties.display\"\n [class.opacity-50]=\"messageToEdit && messageToEdit < index + 1\">\n <sq-chat-message\n [class.sq-user-message]=\"message.role !== 'assistant'\"\n [message]=\"message\"\n [conversation]=\"messages\"\n [assistantIcon]=\"customAssistantIcon || assistantIcon\"\n [streaming]=\"last && (chatService.streaming$ | async)\"\n [canEdit]=\"(loading$ | async) === false && (chatService.streaming$ | async) === false && messageToEdit === undefined && message.role !== 'assistant'\"\n [canRegenerate]=\"(loading$ | async) === false && (chatService.streaming$ | async) === false && messageToEdit === undefined && message.role === 'assistant' && last\"\n [canCopy]=\"!(last && (chatService.streaming$ | async)) && messageToEdit === undefined && message.role === 'assistant'\"\n (edit)=\"editMessage(index)\" (regenerate)=\"regenerateMessage(index)\"\n (referenceClicked)=\"referenceClicked.emit($event)\"\n (openPreview)=\"openPreview.emit($event)\">\n </sq-chat-message>\n </li>\n </ng-container>\n\n <li class=\"list-group-item\" *ngIf=\"(loading$ | async) === true\">\n <ng-container *ngTemplateOutlet=\"loadingTpl || loadingTplDefault\"></ng-container>\n </li>\n </ul>\n\n <div class=\"user-input mt-auto\" *ngIf=\"enableChat\">\n <div class=\"py-2\">\n <ng-container *ngTemplateOutlet=\"inputTpl\"></ng-container>\n <div class=\"text-end small text-muted px-3\" *ngIf=\"showCredits\">\n powered by {{modelDescription?.displayName}} <ng-container *ngIf=\"privacyUrl\"> - <a [href]=\"privacyUrl\" target=\"_blank\">privacy notice</a></ng-container>\n </div>\n </div>\n </div>\n </div>\n</ng-container>\n\n<!-- NG TEMPLATES-->\n\n<ng-template #loadingTplDefault>\n <div class=\"spinner-grow text-success d-block mx-auto my-5\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n</ng-template>\n\n<ng-template #inputTpl>\n <div class=\"px-3 py-1\">\n <div class=\"ast-input-container\">\n <i class=\"fas fa-search\"></i>\n <input #questionInput\n type=\"text\" class=\"form-control\"\n placeholder=\"Ask something\" autofocus\n [(ngModel)]=\"question\"\n (keyup)=\"onKeyUp($event)\"\n [disabled]=\"(loading$ | async) || (chatService.streaming$ | async)\">\n <button\n *ngIf=\"!(chatService.streaming$ | async) && !(loading$ | async)\"\n type=\"button\"\n class=\"btn btn-light ms-2\"\n title=\"Send message\"\n (click)=\"submitQuestion()\">\n <i class=\"fas fa-paper-plane\"></i>\n </button>\n <!--<button\n *ngIf=\"(chatService.streaming$ | async)\"\n type=\"button\"\n class=\"btn btn-light ms-2\"\n title=\"Stop generating\"\n (click)=\"terminateFetch()\">\n <i class=\"fas fa-stop\"></i>\n </button>-->\n <button\n *ngIf=\"messageToEdit\"\n type=\"button\"\n class=\"btn btn-light ms-2\"\n title=\"Cancel edition\"\n (click)=\"messageToEdit = undefined; question = ''\">\n <i class=\"fas fa-undo-alt\"></i>\n </button>\n <div class=\"sq-floating-scroll\" *ngIf=\"!isAtBottom\">\n <button class=\"btn shadow\" (click)=\"scrollDown()\">\n <i class=\"fas fa-angle-double-down\"></i>\n </button>\n </div>\n </div>\n </div>\n</ng-template>\n", styles: [".ast-primary{color:var(--ast-primary-color, #005DA7);background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover{background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover:hover{color:var(--ast-primary-color, #005DA7)}.ast-secondary{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-secondary-bg, #FFF8F1)}.ast-btn{border:0;text-align:left;padding-top:.5rem;padding-bottom:.5rem;display:flex;align-items:center}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference,:host ::ng-deep .attachment .reference{position:relative;bottom:var(--ast-reference-bottom, .3em);font-weight:var(--ast-reference-font-weight, bold);padding:var(--ast-reference-padding, 0 .2em);margin:var(--ast-reference-margin, 0 .1em);border-radius:var(--ast-reference-border-radius, .2em);background-color:var(--ast-reference-background-color, lightblue);color:var(--ast-reference-color, black)}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference{font-size:var(--ast-reference-message-font-size, .7em)}:host ::ng-deep .attachment .reference{font-size:var(--ast-reference-attachment-font-size, 13px)}:host{font-size:.875rem}:host>div>.user-input>div:not(.progress),:host>div>ul>li{width:var(--ast-chat-container-width, 100%);max-width:100%;margin-left:auto;margin-right:auto}:host>div>ul{padding-top:var(--ast-chat-padding-top, 0);padding-bottom:var(--ast-chat-padding-bottom, 0)}li.attachment>p{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;-webkit-line-clamp:3}li.attachment.expanded>p{display:block}.progress{--bs-progress-height: 3px}.progress.disabled{--bs-progress-height: 20px;--bs-progress-bar-bg: var(--bs-danger)}.user-input{z-index:1}.user-input ul.list-group{max-height:30vh}.form-control:disabled{background-color:#ededed}a.disabled{cursor:default;opacity:.5}.no-max-height{max-height:initial!important}.sq-floating-scroll{position:absolute;right:50%;bottom:75px;text-align:center}.sq-floating-scroll .btn{background-color:#fff}.sq-floating-scroll .btn:hover{background-color:#fff;opacity:.9}.ast-input-container{display:flex;align-items:center;background-color:var(--ast-input-bg, #F8F8F8);border-radius:var(--ast-size-3, .75rem)}.ast-input-container>i{padding-left:var(--ast-size-3, .75rem);color:#212529bf}.ast-input-container input{padding-left:var(--ast-size-3, .75rem);padding-right:var(--ast-size-3, .75rem)}.ast-input-container input,.ast-input-container button,.ast-input-container button:hover{background-color:transparent;border:0}.ast-input-container button:not(:hover){color:#212529bf}sq-chat-message.sq-user-message{float:var(--ast-user-message-float, none)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ChatMessageComponent, selector: "sq-chat-message", inputs: ["message", "conversation", "assistantIcon", "streaming", "canEdit", "canRegenerate", "canCopy"], outputs: ["referenceClicked", "edit", "regenerate", "openPreview"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1952
+ ], 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 pe-2 pb-2\" #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=\"enabledUserInput\">\n <div class=\"py-2\">\n <ng-container *ngTemplateOutlet=\"inputTpl\"></ng-container>\n <div class=\"text-end small text-muted px-3\" *ngIf=\"!!config?.globalSettings?.disclaimer\">\n {{ config?.globalSettings?.disclaimer }}\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-primary 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 <button disabled class=\"btn btn-light\">\n <i class=\"fas fa-search\"></i>\n </button>\n <textarea #questionInput rows=\"1\"\n type=\"text\" class=\"form-control\"\n placeholder=\"Ask something\" autofocus\n [(ngModel)]=\"question\"\n (keyup)=\"onKeyUp($event)\"\n (keydown)=\"calculateHeight()\"\n [disabled]=\"(loading$ | async) || (chatService.streaming$ | async)\">\n </textarea>\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}.dark{--ast-primary-bg: #0d0701;--ast-primary-color: #008cd1;--ast-secondary-bg: #00070e;--ast-secondary-color: #ffa258;--ast-input-bg: #070707;--ast-input-color: rgba(222, 218, 218, .75);--ast-muted-color: rgba(222, 218, 218, .75);--ast-saved-chat-hover-background: #262421;--ast-message-table-border-color: #333333;--ast-message-table-tr-bg: #070707;--ast-message-table-tr-border-color: #222222;--ast-reference-icon-color: white;--ast-reference-icon-active-color: black;--ast-reference-passages-color: white;--ast-reference-expanded-hover-bg: #262421;--ast-message-reference-color: black}: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:end;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:var(--ast-muted-color, rgba(33, 37, 41, .75))}.ast-input-container textarea{padding-left:var(--ast-size-3, .75rem);padding-right:var(--ast-size-3, .75rem);resize:none}.ast-input-container textarea,.ast-input-container button,.ast-input-container button:hover{background-color:transparent;border:0}.ast-input-container button:hover{color:var(--ast-primary-color, #005DA7)}.ast-input-container button:not(:hover){color:var(--ast-muted-color, rgba(33, 37, 41, .75))}sq-chat-message.sq-user-message{float:var(--ast-user-message-float, none)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ChatMessageComponent, selector: "sq-chat-message", inputs: ["message", "conversation", "assistantIcon", "streaming", "canEdit", "canRegenerate", "canCopy"], outputs: ["referenceClicked", "edit", "regenerate", "openPreview"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1722
1953
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatComponent, decorators: [{
1723
1954
  type: Component,
1724
1955
  args: [{ selector: 'sq-chat-v3', providers: [
1725
1956
  RestChatService,
1726
1957
  WebSocketChatService
1727
- ], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, FormsModule, ChatMessageComponent], template: "\n<ng-container *ngIf=\"!initializationError\">\n <div *ngIf=\"messages$ | async as messages; else loadingTpl || loadingTplDefault\" class=\"h-100 d-flex flex-column\">\n\n <ul class=\"list-group list-group-flush overflow-auto pb-5\" #messageList>\n <ng-container *ngFor=\"let message of messages; let index = index; let last = last\">\n <!-- Regular messages -->\n <li class=\"list-group-item\" *ngIf=\"message.additionalProperties.display\"\n [class.opacity-50]=\"messageToEdit && messageToEdit < index + 1\">\n <sq-chat-message\n [class.sq-user-message]=\"message.role !== 'assistant'\"\n [message]=\"message\"\n [conversation]=\"messages\"\n [assistantIcon]=\"customAssistantIcon || assistantIcon\"\n [streaming]=\"last && (chatService.streaming$ | async)\"\n [canEdit]=\"(loading$ | async) === false && (chatService.streaming$ | async) === false && messageToEdit === undefined && message.role !== 'assistant'\"\n [canRegenerate]=\"(loading$ | async) === false && (chatService.streaming$ | async) === false && messageToEdit === undefined && message.role === 'assistant' && last\"\n [canCopy]=\"!(last && (chatService.streaming$ | async)) && messageToEdit === undefined && message.role === 'assistant'\"\n (edit)=\"editMessage(index)\" (regenerate)=\"regenerateMessage(index)\"\n (referenceClicked)=\"referenceClicked.emit($event)\"\n (openPreview)=\"openPreview.emit($event)\">\n </sq-chat-message>\n </li>\n </ng-container>\n\n <li class=\"list-group-item\" *ngIf=\"(loading$ | async) === true\">\n <ng-container *ngTemplateOutlet=\"loadingTpl || loadingTplDefault\"></ng-container>\n </li>\n </ul>\n\n <div class=\"user-input mt-auto\" *ngIf=\"enableChat\">\n <div class=\"py-2\">\n <ng-container *ngTemplateOutlet=\"inputTpl\"></ng-container>\n <div class=\"text-end small text-muted px-3\" *ngIf=\"showCredits\">\n powered by {{modelDescription?.displayName}} <ng-container *ngIf=\"privacyUrl\"> - <a [href]=\"privacyUrl\" target=\"_blank\">privacy notice</a></ng-container>\n </div>\n </div>\n </div>\n </div>\n</ng-container>\n\n<!-- NG TEMPLATES-->\n\n<ng-template #loadingTplDefault>\n <div class=\"spinner-grow text-success d-block mx-auto my-5\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n</ng-template>\n\n<ng-template #inputTpl>\n <div class=\"px-3 py-1\">\n <div class=\"ast-input-container\">\n <i class=\"fas fa-search\"></i>\n <input #questionInput\n type=\"text\" class=\"form-control\"\n placeholder=\"Ask something\" autofocus\n [(ngModel)]=\"question\"\n (keyup)=\"onKeyUp($event)\"\n [disabled]=\"(loading$ | async) || (chatService.streaming$ | async)\">\n <button\n *ngIf=\"!(chatService.streaming$ | async) && !(loading$ | async)\"\n type=\"button\"\n class=\"btn btn-light ms-2\"\n title=\"Send message\"\n (click)=\"submitQuestion()\">\n <i class=\"fas fa-paper-plane\"></i>\n </button>\n <!--<button\n *ngIf=\"(chatService.streaming$ | async)\"\n type=\"button\"\n class=\"btn btn-light ms-2\"\n title=\"Stop generating\"\n (click)=\"terminateFetch()\">\n <i class=\"fas fa-stop\"></i>\n </button>-->\n <button\n *ngIf=\"messageToEdit\"\n type=\"button\"\n class=\"btn btn-light ms-2\"\n title=\"Cancel edition\"\n (click)=\"messageToEdit = undefined; question = ''\">\n <i class=\"fas fa-undo-alt\"></i>\n </button>\n <div class=\"sq-floating-scroll\" *ngIf=\"!isAtBottom\">\n <button class=\"btn shadow\" (click)=\"scrollDown()\">\n <i class=\"fas fa-angle-double-down\"></i>\n </button>\n </div>\n </div>\n </div>\n</ng-template>\n", styles: [".ast-primary{color:var(--ast-primary-color, #005DA7);background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover{background-color:var(--ast-primary-bg, #f2f8fe)}.ast-primary-hover:hover{color:var(--ast-primary-color, #005DA7)}.ast-secondary{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-secondary-bg, #FFF8F1)}.ast-btn{border:0;text-align:left;padding-top:.5rem;padding-bottom:.5rem;display:flex;align-items:center}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference,:host ::ng-deep .attachment .reference{position:relative;bottom:var(--ast-reference-bottom, .3em);font-weight:var(--ast-reference-font-weight, bold);padding:var(--ast-reference-padding, 0 .2em);margin:var(--ast-reference-margin, 0 .1em);border-radius:var(--ast-reference-border-radius, .2em);background-color:var(--ast-reference-background-color, lightblue);color:var(--ast-reference-color, black)}:host ::ng-deep .reference,:host ::ng-deep .message-content .reference{font-size:var(--ast-reference-message-font-size, .7em)}:host ::ng-deep .attachment .reference{font-size:var(--ast-reference-attachment-font-size, 13px)}:host{font-size:.875rem}:host>div>.user-input>div:not(.progress),:host>div>ul>li{width:var(--ast-chat-container-width, 100%);max-width:100%;margin-left:auto;margin-right:auto}:host>div>ul{padding-top:var(--ast-chat-padding-top, 0);padding-bottom:var(--ast-chat-padding-bottom, 0)}li.attachment>p{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;-webkit-line-clamp:3}li.attachment.expanded>p{display:block}.progress{--bs-progress-height: 3px}.progress.disabled{--bs-progress-height: 20px;--bs-progress-bar-bg: var(--bs-danger)}.user-input{z-index:1}.user-input ul.list-group{max-height:30vh}.form-control:disabled{background-color:#ededed}a.disabled{cursor:default;opacity:.5}.no-max-height{max-height:initial!important}.sq-floating-scroll{position:absolute;right:50%;bottom:75px;text-align:center}.sq-floating-scroll .btn{background-color:#fff}.sq-floating-scroll .btn:hover{background-color:#fff;opacity:.9}.ast-input-container{display:flex;align-items:center;background-color:var(--ast-input-bg, #F8F8F8);border-radius:var(--ast-size-3, .75rem)}.ast-input-container>i{padding-left:var(--ast-size-3, .75rem);color:#212529bf}.ast-input-container input{padding-left:var(--ast-size-3, .75rem);padding-right:var(--ast-size-3, .75rem)}.ast-input-container input,.ast-input-container button,.ast-input-container button:hover{background-color:transparent;border:0}.ast-input-container button:not(:hover){color:#212529bf}sq-chat-message.sq-user-message{float:var(--ast-user-message-float, none)}\n"] }]
1958
+ ], 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 pe-2 pb-2\" #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=\"enabledUserInput\">\n <div class=\"py-2\">\n <ng-container *ngTemplateOutlet=\"inputTpl\"></ng-container>\n <div class=\"text-end small text-muted px-3\" *ngIf=\"!!config?.globalSettings?.disclaimer\">\n {{ config?.globalSettings?.disclaimer }}\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-primary 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 <button disabled class=\"btn btn-light\">\n <i class=\"fas fa-search\"></i>\n </button>\n <textarea #questionInput rows=\"1\"\n type=\"text\" class=\"form-control\"\n placeholder=\"Ask something\" autofocus\n [(ngModel)]=\"question\"\n (keyup)=\"onKeyUp($event)\"\n (keydown)=\"calculateHeight()\"\n [disabled]=\"(loading$ | async) || (chatService.streaming$ | async)\">\n </textarea>\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}.dark{--ast-primary-bg: #0d0701;--ast-primary-color: #008cd1;--ast-secondary-bg: #00070e;--ast-secondary-color: #ffa258;--ast-input-bg: #070707;--ast-input-color: rgba(222, 218, 218, .75);--ast-muted-color: rgba(222, 218, 218, .75);--ast-saved-chat-hover-background: #262421;--ast-message-table-border-color: #333333;--ast-message-table-tr-bg: #070707;--ast-message-table-tr-border-color: #222222;--ast-reference-icon-color: white;--ast-reference-icon-active-color: black;--ast-reference-passages-color: white;--ast-reference-expanded-hover-bg: #262421;--ast-message-reference-color: black}: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:end;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:var(--ast-muted-color, rgba(33, 37, 41, .75))}.ast-input-container textarea{padding-left:var(--ast-size-3, .75rem);padding-right:var(--ast-size-3, .75rem);resize:none}.ast-input-container textarea,.ast-input-container button,.ast-input-container button:hover{background-color:transparent;border:0}.ast-input-container button:hover{color:var(--ast-primary-color, #005DA7)}.ast-input-container button:not(:hover){color:var(--ast-muted-color, rgba(33, 37, 41, .75))}sq-chat-message.sq-user-message{float:var(--ast-user-message-float, none)}\n"] }]
1728
1959
  }], ctorParameters: function () { return []; }, propDecorators: { instanceId: [{
1729
1960
  type: Input
1730
1961
  }], query: [{
1731
1962
  type: Input
1963
+ }], queryChangeShouldTriggerReload: [{
1964
+ type: Input
1732
1965
  }], protocol: [{
1733
1966
  type: Input
1734
1967
  }], messageHandlers: [{
@@ -1737,10 +1970,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImpor
1737
1970
  type: Input
1738
1971
  }], chat: [{
1739
1972
  type: Input
1740
- }], enableChat: [{
1741
- type: Input
1742
- }], showCredits: [{
1743
- type: Input
1744
1973
  }], customAssistantIcon: [{
1745
1974
  type: Input
1746
1975
  }], data: [{
@@ -1793,12 +2022,41 @@ class SavedChatsComponent {
1793
2022
  this.chatService = this.instanceManagerService.getInstance(this.instanceId);
1794
2023
  }
1795
2024
  onListSavedChat() {
1796
- this.subscription.add(this.chatService.savedChats$.subscribe((savedChats) => this.groupedSavedChats$.next(this._groupSavedChatsByDate(savedChats))));
2025
+ this.subscription.add(this.chatService.savedChats$.subscribe((savedChats) => {
2026
+ this.groupedSavedChats$.next(this._groupSavedChatsByDate(savedChats));
2027
+ }));
1797
2028
  }
1798
2029
  onLoad(savedChat) {
2030
+ this.chatService.setSavedChatId(savedChat.id);
1799
2031
  this.chatService.loadSavedChat$.next(savedChat);
2032
+ this.chatService.listSavedChat();
1800
2033
  this.load.emit(savedChat);
1801
2034
  }
2035
+ onRename(savedChat) {
2036
+ const model = {
2037
+ title: 'Rename saved discussion',
2038
+ message: `Please enter a new name for the discussion "${savedChat.title}".`,
2039
+ buttons: [
2040
+ new ModalButton({ result: -2 /* ModalResult.Cancel */ }),
2041
+ new ModalButton({ result: -1 /* ModalResult.OK */, text: "Rename", primary: true })
2042
+ ],
2043
+ output: savedChat.title,
2044
+ validators: [Validators.required]
2045
+ };
2046
+ this.modalService.prompt(model).then(res => {
2047
+ if (res === -1 /* ModalResult.OK */) {
2048
+ this.subscription.add(this.chatService.updateSavedChat(savedChat.id, model.output)
2049
+ .pipe(tap(() => {
2050
+ this.notificationsService.success(`The saved discussion "${savedChat.title}" has been successfully renamed to "${model.output}".`);
2051
+ this.chatService.listSavedChat();
2052
+ }), catchError((error) => {
2053
+ console.error('Error occurred while updating the saved chat:', error);
2054
+ this.notificationsService.error(`Error occurred while updating the saved discussion "${savedChat.title}"`);
2055
+ return throwError(() => error);
2056
+ })).subscribe());
2057
+ }
2058
+ });
2059
+ }
1802
2060
  onDelete(savedChat) {
1803
2061
  this.modalService
1804
2062
  .confirm({
@@ -1875,10 +2133,10 @@ class SavedChatsComponent {
1875
2133
  }
1876
2134
  }
1877
2135
  SavedChatsComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: SavedChatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1878
- SavedChatsComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: SavedChatsComponent, isStandalone: true, selector: "sq-saved-chats-v3", inputs: { instanceId: "instanceId" }, outputs: { load: "load", delete: "delete" }, ngImport: i0, template: "<div *ngFor=\"let group of (groupedSavedChats$ | async)\" class=\"saved-chats\">\n <div class=\"saved-chat-date\">{{group.key}}</div>\n <div *ngFor=\"let savedChat of group.value\" class=\"saved-chat p-2\">\n <span class=\"title me-1\" (click)=\"onLoad(savedChat)\">{{savedChat.title}}</span>\n <i class=\"saved-chat-actions fas fa-trash ms-1\" [sqTooltip]=\"'Delete'\" (click)=\"onDelete(savedChat)\"></i>\n </div>\n</div>\n", styles: [".saved-chats .saved-chat-date{font-weight:500;color:#a9a9a9;margin-top:.5rem}.saved-chats .saved-chat{display:flex;align-items:center;cursor:pointer;margin-left:.25rem}.saved-chats .saved-chat span{flex-grow:1;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.saved-chats .saved-chat .saved-chat-actions{display:none}.saved-chats .saved-chat:hover{color:#ff732e;background-color:#fff8f1}.saved-chats .saved-chat:hover .saved-chat-actions{display:block}.saved-chats .title{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: ModalModule }, { kind: "ngmodule", type: UtilsModule }, { kind: "directive", type: i2$1.TooltipDirective, selector: "[sqTooltip]", inputs: ["sqTooltip", "sqTooltipData", "sqTooltipTemplate", "placement", "fallbackPlacements", "delay", "hoverableTooltip", "tooltipClass"] }] });
2136
+ 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: "<ng-container *ngIf=\"(chatService.chatConfig$ | async)?.savedChatSettings.display\">\n <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\" [class.active]=\"chatService.savedChatId === savedChat.id\"\n (click)=\"onLoad(savedChat)\">\n <span class=\"title me-1\">{{savedChat.title}}</span>\n <i class=\"saved-chat-actions fas fa-pen mx-1\" [sqTooltip]=\"'Rename'\"\n (click)=\"$event.stopPropagation(); onRename(savedChat)\"></i>\n <i class=\"saved-chat-actions fas fa-trash ms-1\" [sqTooltip]=\"'Delete'\"\n (click)=\"$event.stopPropagation(); onDelete(savedChat)\"></i>\n </div>\n </div>\n</ng-container>\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}.dark{--ast-primary-bg: #0d0701;--ast-primary-color: #008cd1;--ast-secondary-bg: #00070e;--ast-secondary-color: #ffa258;--ast-input-bg: #070707;--ast-input-color: rgba(222, 218, 218, .75);--ast-muted-color: rgba(222, 218, 218, .75);--ast-saved-chat-hover-background: #262421;--ast-message-table-border-color: #333333;--ast-message-table-tr-bg: #070707;--ast-message-table-tr-border-color: #222222;--ast-reference-icon-color: white;--ast-reference-icon-active-color: black;--ast-reference-passages-color: white;--ast-reference-expanded-hover-bg: #262421;--ast-message-reference-color: black}.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,.saved-chats .saved-chat.active{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-saved-chat-hover-background, #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: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: ModalModule }, { kind: "ngmodule", type: UtilsModule }, { kind: "directive", type: i2$1.TooltipDirective, selector: "[sqTooltip]", inputs: ["sqTooltip", "sqTooltipData", "sqTooltipTemplate", "placement", "fallbackPlacements", "delay", "hoverableTooltip", "tooltipClass"] }] });
1879
2137
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: SavedChatsComponent, decorators: [{
1880
2138
  type: Component,
1881
- args: [{ selector: 'sq-saved-chats-v3', standalone: true, imports: [CommonModule, ModalModule, UtilsModule], template: "<div *ngFor=\"let group of (groupedSavedChats$ | async)\" class=\"saved-chats\">\n <div class=\"saved-chat-date\">{{group.key}}</div>\n <div *ngFor=\"let savedChat of group.value\" class=\"saved-chat p-2\">\n <span class=\"title me-1\" (click)=\"onLoad(savedChat)\">{{savedChat.title}}</span>\n <i class=\"saved-chat-actions fas fa-trash ms-1\" [sqTooltip]=\"'Delete'\" (click)=\"onDelete(savedChat)\"></i>\n </div>\n</div>\n", styles: [".saved-chats .saved-chat-date{font-weight:500;color:#a9a9a9;margin-top:.5rem}.saved-chats .saved-chat{display:flex;align-items:center;cursor:pointer;margin-left:.25rem}.saved-chats .saved-chat span{flex-grow:1;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.saved-chats .saved-chat .saved-chat-actions{display:none}.saved-chats .saved-chat:hover{color:#ff732e;background-color:#fff8f1}.saved-chats .saved-chat:hover .saved-chat-actions{display:block}.saved-chats .title{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
2139
+ args: [{ selector: 'sq-saved-chats-v3', standalone: true, imports: [CommonModule, ModalModule, UtilsModule], template: "<ng-container *ngIf=\"(chatService.chatConfig$ | async)?.savedChatSettings.display\">\n <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\" [class.active]=\"chatService.savedChatId === savedChat.id\"\n (click)=\"onLoad(savedChat)\">\n <span class=\"title me-1\">{{savedChat.title}}</span>\n <i class=\"saved-chat-actions fas fa-pen mx-1\" [sqTooltip]=\"'Rename'\"\n (click)=\"$event.stopPropagation(); onRename(savedChat)\"></i>\n <i class=\"saved-chat-actions fas fa-trash ms-1\" [sqTooltip]=\"'Delete'\"\n (click)=\"$event.stopPropagation(); onDelete(savedChat)\"></i>\n </div>\n </div>\n</ng-container>\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}.dark{--ast-primary-bg: #0d0701;--ast-primary-color: #008cd1;--ast-secondary-bg: #00070e;--ast-secondary-color: #ffa258;--ast-input-bg: #070707;--ast-input-color: rgba(222, 218, 218, .75);--ast-muted-color: rgba(222, 218, 218, .75);--ast-saved-chat-hover-background: #262421;--ast-message-table-border-color: #333333;--ast-message-table-tr-bg: #070707;--ast-message-table-tr-border-color: #222222;--ast-reference-icon-color: white;--ast-reference-icon-active-color: black;--ast-reference-passages-color: white;--ast-reference-expanded-hover-bg: #262421;--ast-message-reference-color: black}.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,.saved-chats .saved-chat.active{color:var(--ast-secondary-color, #FF732E);background-color:var(--ast-saved-chat-hover-background, #FFF8F1)}.saved-chats .saved-chat:hover .saved-chat-actions{display:block}.saved-chats .title{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
1882
2140
  }], propDecorators: { instanceId: [{
1883
2141
  type: Input
1884
2142
  }], load: [{
@@ -1903,9 +2161,81 @@ const enAssistant = Utils.merge({}, _enAssistant);
1903
2161
  const frAssistant = Utils.merge({}, _frAssistant);
1904
2162
  const deAssistant = Utils.merge({}, _deAssistant);
1905
2163
 
2164
+ class ChatPrompt {
2165
+ constructor(model, modalRef, formBuilder) {
2166
+ this.model = model;
2167
+ this.modalRef = modalRef;
2168
+ this.formBuilder = formBuilder;
2169
+ this.defaultButtons = [
2170
+ new ModalButton({
2171
+ result: -1 /* ModalResult.OK */,
2172
+ primary: true,
2173
+ validation: this.form
2174
+ }),
2175
+ new ModalButton({
2176
+ result: -2 /* ModalResult.Cancel */
2177
+ })
2178
+ ];
2179
+ }
2180
+ ngOnInit() {
2181
+ this.inputControl = new UntypedFormControl(this.model.output, this.model.validators || Validators.required);
2182
+ this.form = this.formBuilder.group({
2183
+ input: this.inputControl
2184
+ });
2185
+ this.formChanges = Utils.subscribe(this.form.valueChanges, (value) => {
2186
+ this.model.output = this.inputControl.value;
2187
+ });
2188
+ }
2189
+ ngOnDestroy() {
2190
+ this.formChanges.unsubscribe();
2191
+ }
2192
+ get title() {
2193
+ return this.model.title ? this.model.title : "msg#modal.prompt.title";
2194
+ }
2195
+ get buttons() {
2196
+ return (this.model.buttons && this.model.buttons.length > 0) ? this.model.buttons : this.defaultButtons;
2197
+ }
2198
+ }
2199
+ ChatPrompt.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatPrompt, deps: [{ token: MODAL_MODEL }, { token: i1$2.ModalRef }, { token: i2.UntypedFormBuilder }], target: i0.ɵɵFactoryTarget.Component });
2200
+ ChatPrompt.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: ChatPrompt, isStandalone: true, selector: "sq-chat-prompt", ngImport: i0, template: `
2201
+ <form name="prompt" novalidate [formGroup]="form">
2202
+ <sq-modal [title]="title" [buttons]="buttons">
2203
+ <div class="mb-3 sq-form-group">
2204
+ <label class="form-label" for="input">{{model.message | sqMessage:model.messageParams}}</label>
2205
+ <input [sqValidation]="form" type="text" class="form-control" id="input" formControlName="input" spellcheck="off" sqAutofocus *ngIf="!model.rowCount">
2206
+ <textarea [sqValidation]="form" type="text" class="form-control" id="input" formControlName="input" spellcheck="on" rows="{{model.rowCount}}" sqAutofocus *ngIf="!!model.rowCount">
2207
+ </textarea>
2208
+ </div>
2209
+ </sq-modal>
2210
+ </form>
2211
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: BsModalModule }, { kind: "component", type: i4.BsModal, selector: "sq-modal", inputs: ["title", "buttons", "showHeader", "showFooter", "isProcessingState"] }, { kind: "ngmodule", type: ValidationModule }, { kind: "directive", type: i5$1.ValidationDirective, selector: "[sqValidation]", inputs: ["sqValidation"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: IntlModule }, { kind: "pipe", type: i6$1.MessagePipe, name: "sqMessage" }] });
2212
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: ChatPrompt, decorators: [{
2213
+ type: Component,
2214
+ args: [{
2215
+ selector: "sq-chat-prompt",
2216
+ template: `
2217
+ <form name="prompt" novalidate [formGroup]="form">
2218
+ <sq-modal [title]="title" [buttons]="buttons">
2219
+ <div class="mb-3 sq-form-group">
2220
+ <label class="form-label" for="input">{{model.message | sqMessage:model.messageParams}}</label>
2221
+ <input [sqValidation]="form" type="text" class="form-control" id="input" formControlName="input" spellcheck="off" sqAutofocus *ngIf="!model.rowCount">
2222
+ <textarea [sqValidation]="form" type="text" class="form-control" id="input" formControlName="input" spellcheck="on" rows="{{model.rowCount}}" sqAutofocus *ngIf="!!model.rowCount">
2223
+ </textarea>
2224
+ </div>
2225
+ </sq-modal>
2226
+ </form>
2227
+ `,
2228
+ standalone: true,
2229
+ imports: [CommonModule, BsModalModule, ValidationModule, ReactiveFormsModule, IntlModule],
2230
+ }]
2231
+ }], ctorParameters: function () { return [{ type: undefined, decorators: [{
2232
+ type: Inject,
2233
+ args: [MODAL_MODEL]
2234
+ }] }, { type: i1$2.ModalRef }, { type: i2.UntypedFormBuilder }]; } });
2235
+
1906
2236
  /**
1907
2237
  * Generated bundle index. Do not edit.
1908
2238
  */
1909
2239
 
1910
- export { ChatComponent, ChatService, ChatSettingsV3Component, FormatIconComponent, InitialsAvatarComponent, InstanceManagerService, RestChatService, SavedChatsComponent, WebSocketChatService, chatConfigSchema, deAssistant, enAssistant, frAssistant, globalSettingsSchema };
2240
+ export { ChatComponent, ChatPrompt, ChatService, ChatSettingsV3Component, FormatIconComponent, InitialsAvatarComponent, InstanceManagerService, RestChatService, SavedChatsComponent, WebSocketChatService, chatConfigSchema, connectionSettingsSchema, deAssistant, enAssistant, frAssistant };
1911
2241
  //# sourceMappingURL=sinequa-assistant-chat.mjs.map