@sinequa/assistant 3.7.0 → 3.7.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.
@@ -66,7 +66,7 @@ class InstanceManagerService {
66
66
  */
67
67
  getInstance(key) {
68
68
  if (!this.checkInstance(key)) {
69
- throw new Error(`No chat instance found for the given key : '${key}'`);
69
+ throw new Error(`No assistant instance found for the given key : '${key}'`);
70
70
  }
71
71
  return this._serviceInstances.get(key);
72
72
  }
@@ -225,10 +225,6 @@ const uiSettingsSchema = z.object({
225
225
  displaySystemPrompt: z.boolean(),
226
226
  displayUserPrompt: z.boolean()
227
227
  });
228
- // Define the Zod representation for the chatStarter object
229
- const chatStarterSchema = z.object({
230
- text: z.string()
231
- });
232
228
  // Define the Zod representation for the defaultValues object
233
229
  const defaultValuesSchema = z.object({
234
230
  service_id: z.string(),
@@ -244,6 +240,15 @@ const defaultValuesSchema = z.object({
244
240
  systemPrompt: z.string(),
245
241
  userPrompt: z.string()
246
242
  });
243
+ // Define the Zod representation for the chatStarter object
244
+ const chatStarterSchema = z.object({
245
+ text: z.string()
246
+ });
247
+ // Define the Zod representation for the action object
248
+ const actionSchema = z.object({
249
+ forcedWorkflow: z.string(),
250
+ forcedWorkflowProperties: z.record(z.unknown()).optional() // forcedWorkflowProperties must be an object (Map equivalent)
251
+ });
247
252
  // Define the Zod representation for the modeSettings object
248
253
  const initializationSchema = z.object({
249
254
  event: z.enum(['Query', 'Prompt']),
@@ -257,7 +262,8 @@ const modeSettingsSchema = z.object({
257
262
  enabledUserInput: z.boolean(),
258
263
  displayUserPrompt: z.boolean(),
259
264
  sendUserPrompt: z.boolean(),
260
- initialization: initializationSchema
265
+ initialization: initializationSchema,
266
+ actions: z.record(actionSchema).optional()
261
267
  }).refine(data => !(data.initialization.chatStarters?.length && (data.sendUserPrompt || data.displayUserPrompt || data.initialization.event === 'Query')), {
262
268
  message: "Incompatible configuration for using chatStarters ('sendUserPrompt' and 'displayUserPrompt' must be false, 'initialization.event' must not be 'Query')",
263
269
  });
@@ -2108,7 +2114,7 @@ class ChatComponent extends AbstractFacet {
2108
2114
  this._actions = [];
2109
2115
  this._resetChatAction = new Action({
2110
2116
  icon: 'fas fa-sync',
2111
- title: "Reset chat",
2117
+ title: "Reset assistant",
2112
2118
  action: () => this.newChat()
2113
2119
  });
2114
2120
  this._sub = new Subscription();
@@ -2177,6 +2183,9 @@ class ChatComponent extends AbstractFacet {
2177
2183
  this._sub.unsubscribe();
2178
2184
  this._dataSubscription?.unsubscribe();
2179
2185
  this._reloadSubscription?.unsubscribe();
2186
+ if (this.chatService instanceof WebSocketChatService) {
2187
+ this.chatService.stopConnection();
2188
+ }
2180
2189
  }
2181
2190
  get isAdmin() {
2182
2191
  return this.principalService.principal?.isAdministrator || false;
@@ -2203,6 +2212,17 @@ class ChatComponent extends AbstractFacet {
2203
2212
  this.instanceManagerService.storeInstance(this.instanceId, this.chatService);
2204
2213
  }
2205
2214
  get actions() { return this._actions; }
2215
+ /**
2216
+ * Handles the changes in the chat component.
2217
+ * If the chat service is a WebSocketChatService, it handles the override of the message handlers if they exist.
2218
+ * Initializes the chat with the provided chat messages if they exist, otherwise loads the default chat.
2219
+ * If the chat is initialized, the initialization event is "Query", the query changes, and the queryChangeShouldTriggerReload function is provided,
2220
+ * then the chat should be reloaded if the function returns true. Otherwise, the chat should be reloaded by default.
2221
+ * It takes into account the ongoing streaming process and the ongoing stopping process to trigger that conditionally define the logic
2222
+ * of the reload :
2223
+ * - If the chat is streaming, then stop the generation and wait for the fetch to complete before reloading the chat.
2224
+ * - If the chat is stopping the generation, then wait for the fetch to complete before reloading the chat.
2225
+ */
2206
2226
  _handleChanges() {
2207
2227
  const changes = this.changes$.value;
2208
2228
  // If the chat service is a WebSocketChatService, handle the override of the message handlers if exists
@@ -2294,6 +2314,12 @@ class ChatComponent extends AbstractFacet {
2294
2314
  }
2295
2315
  }
2296
2316
  }
2317
+ /**
2318
+ * Triggers a reload after the query change.
2319
+ * This method performs the necessary operations to reload the chat after a query change.
2320
+ * It sets the system and user messages, resets the savedChatId, generates a new chatId,
2321
+ * generates a new chat audit event, and handles the query mode.
2322
+ */
2297
2323
  _triggerReloadAfterQueryChange() {
2298
2324
  const systemMsg = { role: 'system', content: this.config.defaultValues.systemPrompt, additionalProperties: { display: false } };
2299
2325
  const userMsg = { role: 'user', content: ChatService.formatPrompt(this.config.defaultValues.userPrompt, { principal: this.principalService.principal }), additionalProperties: { display: this.config.modeSettings.displayUserPrompt } };
@@ -2302,18 +2328,37 @@ class ChatComponent extends AbstractFacet {
2302
2328
  this.chatService.generateAuditEvent('new-chat', { 'configuration': JSON.stringify(this.chatService.chatConfig$.value) }); // Generate a new chat audit event
2303
2329
  this._handleQueryMode(systemMsg, userMsg);
2304
2330
  }
2331
+ /**
2332
+ * Adds a scroll listener to the message list element.
2333
+ * The listener is triggered when any of the following events occur:
2334
+ * - Loading state changes
2335
+ * - Messages change
2336
+ * - Streaming state changes
2337
+ * - Scroll event occurs on the message list element
2338
+ *
2339
+ * When the listener is triggered, it updates the `isAtBottom` property.
2340
+ */
2305
2341
  _addScrollListener() {
2306
2342
  this._sub.add(merge(this.loading$, this.messages$, this.chatService.streaming$, fromEvent(this.messageList.nativeElement, 'scroll')).subscribe(() => {
2307
2343
  this.isAtBottom = this._toggleScrollButtonVisibility();
2308
2344
  this.cdr.detectChanges();
2309
2345
  }));
2310
2346
  }
2347
+ /**
2348
+ * Get the model description based on the defaultValues service_id and model_id
2349
+ */
2311
2350
  updateModelDescription() {
2312
2351
  this.modelDescription = this.chatService.getModel(this.config.defaultValues.service_id, this.config.defaultValues.model_id);
2313
2352
  this.cdr.detectChanges();
2314
2353
  }
2354
+ /**
2355
+ * Submits a question from the user.
2356
+ * If the user is editing a previous message, removes all subsequent messages from the chat history.
2357
+ * Triggers the fetch of the answer for the submitted question by calling _fetchAnswer().
2358
+ * Clears the input value in the UI.
2359
+ */
2315
2360
  submitQuestion() {
2316
- if (this.chatService.streaming$.value) {
2361
+ if (!!this.chatService.streaming$.value || !!this.chatService.stoppingGeneration$.value) {
2317
2362
  return;
2318
2363
  }
2319
2364
  if (this.question.trim() && this.messages$.value && this.chatService.chatHistory) {
@@ -2337,6 +2382,13 @@ class ChatComponent extends AbstractFacet {
2337
2382
  this.questionInput.nativeElement.style.height = `auto`;
2338
2383
  }
2339
2384
  }
2385
+ /**
2386
+ * Triggers the fetch of the answer for the given question and updates the conversation.
2387
+ * Generates an audit event for the user input.
2388
+ *
2389
+ * @param question - The question asked by the user.
2390
+ * @param conversation - The current conversation messages.
2391
+ */
2340
2392
  _fetchAnswer(question, conversation) {
2341
2393
  const userMsg = { role: 'user', content: question, additionalProperties: { display: true, isUserInput: true, additionalWorkflowProperties: this.config.additionalWorkflowProperties } };
2342
2394
  const messages = [...conversation, userMsg];
@@ -2348,7 +2400,7 @@ class ChatComponent extends AbstractFacet {
2348
2400
  * Depending on the connection's state :
2349
2401
  * - If connected => given a list of messages, the chat endpoint is invoked for a continuation and updates the list of messages accordingly.
2350
2402
  * - If any other state => a connection error message is displayed in the chat.
2351
- * @param messages
2403
+ * @param messages The list of messages to invoke the chat endpoint with
2352
2404
  */
2353
2405
  fetch(messages) {
2354
2406
  this._updateConnectionStatus();
@@ -2472,12 +2524,18 @@ class ChatComponent extends AbstractFacet {
2472
2524
  this.scrollDown();
2473
2525
  }
2474
2526
  }
2527
+ /**
2528
+ * @returns true if the chat discussion is scrolled down to the bottom, false otherwise
2529
+ */
2475
2530
  _toggleScrollButtonVisibility() {
2476
2531
  if (this.messageList?.nativeElement) {
2477
2532
  return Math.round(this.messageList?.nativeElement.scrollHeight - this.messageList?.nativeElement.scrollTop - 1) <= this.messageList?.nativeElement.clientHeight;
2478
2533
  }
2479
2534
  return true;
2480
2535
  }
2536
+ /**
2537
+ * Scroll down to the bottom of the chat discussion
2538
+ */
2481
2539
  scrollDown() {
2482
2540
  setTimeout(() => {
2483
2541
  if (this.messageList?.nativeElement) {
@@ -2501,6 +2559,30 @@ class ChatComponent extends AbstractFacet {
2501
2559
  this.chatService.generateAuditEvent('new-chat', { 'configuration': JSON.stringify(this.chatService.chatConfig$.value) }); // Generate a new chat audit event
2502
2560
  this.loadDefaultChat(); // Start a new chat
2503
2561
  }
2562
+ /**
2563
+ * Attaches the specified document IDs to the assistant.
2564
+ * If the chat is streaming or stopping the generation, the operation is not allowed.
2565
+ * If no document IDs are provided, the operation is not allowed.
2566
+ * If the action for attaching a document is not defined at the application customization level, an error is logged.
2567
+ * @param ids - An array of document IDs to attach.
2568
+ */
2569
+ attachToChat(ids) {
2570
+ if (!!this.chatService.streaming$.value || !!this.chatService.stoppingGeneration$.value) {
2571
+ return;
2572
+ }
2573
+ if (!ids || ids?.length < 1) {
2574
+ return;
2575
+ }
2576
+ const attachDocAction = this.config.modeSettings.actions?.["attachDocAction"];
2577
+ if (!attachDocAction) {
2578
+ console.error(`No action is defined for attaching a document to the assistant "${this.instanceId}"`);
2579
+ return;
2580
+ }
2581
+ const userMsg = { role: 'user', content: '', additionalProperties: { display: false, isUserInput: false, type: "Action", forcedWorkflow: attachDocAction.forcedWorkflow, forcedWorkflowProperties: { ...(attachDocAction.forcedWorkflowProperties || {}), ids }, additionalWorkflowProperties: this.config.additionalWorkflowProperties } };
2582
+ const messages = [...this.chatService.chatHistory, userMsg];
2583
+ this.messages$.next(messages);
2584
+ this.fetch(messages);
2585
+ }
2504
2586
  /**
2505
2587
  * Start the default chat with the defaultValues settings
2506
2588
  * If the chat is meant to be initialized with event === "Query", the corresponding user query message will be added to the chat history
@@ -2560,7 +2642,7 @@ class ChatComponent extends AbstractFacet {
2560
2642
  */
2561
2643
  openChat(messages, savedChatId) {
2562
2644
  if (!messages || !Array.isArray(messages)) {
2563
- console.error('Error occurs while trying to load the chat discussion. Invalid messages received :', messages);
2645
+ console.error('Error occurs while trying to load the discussion. Invalid messages received :', messages);
2564
2646
  return;
2565
2647
  }
2566
2648
  if (savedChatId) {
@@ -2593,14 +2675,26 @@ class ChatComponent extends AbstractFacet {
2593
2675
  this.question = '';
2594
2676
  this.terminateFetch();
2595
2677
  }
2678
+ /**
2679
+ * Fetch and Load the saved chat from the saved chat index.
2680
+ * If the saved chat is found, the chat discussion will be loaded with the provided messages and chatId
2681
+ */
2596
2682
  onLoadChat() {
2597
2683
  this.loading$.next(true);
2598
2684
  this._sub.add(this.chatService.loadSavedChat$
2599
2685
  .pipe(filter(savedChat => !!savedChat), switchMap(savedChat => this.chatService.getSavedChat(savedChat.id)), filter(savedChatHistory => !!savedChatHistory), tap(savedChatHistory => this.openChat(savedChatHistory.history, savedChatHistory.id))).subscribe());
2600
2686
  }
2687
+ /**
2688
+ * Stop the generation of the current assistant's answer.
2689
+ * The fetch subscription will be terminated.
2690
+ */
2601
2691
  stopGeneration() {
2602
2692
  this.chatService.stopGeneration().subscribe(() => this.terminateFetch());
2603
2693
  }
2694
+ /**
2695
+ * Terminate the fetch process by unsubscribing from the data subscription and updating the loading status to false.
2696
+ * Additionally, focus on the chat input if the focusAfterResponse flag is set to true.
2697
+ */
2604
2698
  terminateFetch() {
2605
2699
  this._dataSubscription?.unsubscribe();
2606
2700
  this._dataSubscription = undefined;
@@ -2680,6 +2774,10 @@ class ChatComponent extends AbstractFacet {
2680
2774
  // return the index of the message in the filtered history
2681
2775
  return filteredHistory[index - numberOfHiddenMessagesInMessages$BeforeIndex].additionalProperties.rank;
2682
2776
  }
2777
+ /**
2778
+ * Handles the key up event for 'Backspace' and 'Enter' keys.
2779
+ * @param event - The keyboard event.
2780
+ */
2683
2781
  onKeyUp(event) {
2684
2782
  switch (event.key) {
2685
2783
  case 'Backspace':
@@ -2696,6 +2794,11 @@ class ChatComponent extends AbstractFacet {
2696
2794
  break;
2697
2795
  }
2698
2796
  }
2797
+ /**
2798
+ * Calculates and adjusts the height of the question input element based on its content.
2799
+ * If the Enter key is pressed without the Shift key, it prevents the default behavior.
2800
+ * @param event The keyboard event
2801
+ */
2699
2802
  calculateHeight(event) {
2700
2803
  if (event?.key === 'Enter' && !event.shiftKey) {
2701
2804
  event?.preventDefault();