@c8y/ngx-components 1023.82.2 → 1023.82.4

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,14 @@
1
1
  import { NgComponentOutlet, AsyncPipe } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
3
  import { Injectable, inject, input, computed, output, signal, viewChildren, ChangeDetectionStrategy, Component, Injector, Input } from '@angular/core';
4
- import { GainsightService, DatePipe, ModalService, AlertService, C8Y_PLUGIN_CONTEXT_PATH, AppStateService, Status, LoadingComponent, EmptyStateComponent, C8yTranslateDirective, GuideDocsComponent, GuideHrefDirective, MarkdownToHtmlPipe, C8yTranslatePipe, ContextRouteService } from '@c8y/ngx-components';
4
+ import { GainsightService, DatePipe, ModalService, AlertService, C8Y_PLUGIN_CONTEXT_PATH, AppStateService, Status, LoadingComponent, EmptyStateComponent, C8yTranslateDirective, GuideDocsComponent, GuideHrefDirective, MarkdownToHtmlPipe, C8yTranslatePipe, NumberPipe, ContextRouteService } from '@c8y/ngx-components';
5
5
  import { TranslateService } from '@ngx-translate/core';
6
6
  import { AIService, defaultPruneMessagesForAgent } from '@c8y/ngx-components/ai';
7
7
  import { gettext } from '@c8y/ngx-components/gettext';
8
8
  import { AiChatAssistantMessageComponent, AiChatComponent, AiChatSuggestionComponent, AiChatMessageComponent, AiChatMessageActionComponent } from '@c8y/ngx-components/ai/ai-chat';
9
9
  import { CollapseModule } from 'ngx-bootstrap/collapse';
10
+ import * as i1 from 'ngx-bootstrap/tooltip';
11
+ import { TooltipModule } from 'ngx-bootstrap/tooltip';
10
12
  import { WidgetConfigService, WidgetConfigFeedbackComponent } from '@c8y/ngx-components/context-dashboard';
11
13
  import { of, isObservable } from 'rxjs';
12
14
 
@@ -363,7 +365,7 @@ class AgentChatComponent {
363
365
  * if an agent of that name doesn't already exist. This is an alternative to the usual mechanism of configuring
364
366
  * agents using a `.agent.c8y.ts` file.
365
367
  */
366
- this.autoCreateAgents = input(true, ...(ngDevMode ? [{ debugName: "autoCreateAgents" }] : []));
368
+ this.autoCreateAgents = input(false, ...(ngDevMode ? [{ debugName: "autoCreateAgents" }] : []));
367
369
  /** Variables to pass to the AI agent for placeholders specified in the agent's system prompt. */
368
370
  this.variables = input({}, ...(ngDevMode ? [{ debugName: "variables" }] : []));
369
371
  /**
@@ -405,6 +407,10 @@ class AgentChatComponent {
405
407
  * or to make what is sent to the agent different from what is rendered in the UI.
406
408
  */
407
409
  this.pruneMessagesForAgent = input(undefined, ...(ngDevMode ? [{ debugName: "pruneMessagesForAgent" }] : []));
410
+ /** When true, skips the agent health check on initialization.
411
+ * Use this when the component is used with the test endpoint (snapshot mode) where the agent
412
+ * does not need to exist on the backend. Default: false. */
413
+ this.skipHealthCheck = input(false, ...(ngDevMode ? [{ debugName: "skipHealthCheck" }] : []));
408
414
  /** Input that provides a previously-saved chat history snapshot to restore. */
409
415
  this.initialChatHistory = input(...(ngDevMode ? [undefined, { debugName: "initialChatHistory" }] : []));
410
416
  /** Suggestions that were restored from the initial chat history; used if `suggestions` input is not yet set. */
@@ -439,6 +445,11 @@ class AgentChatComponent {
439
445
  this.onToolResult = output();
440
446
  /** Notified when the user presses the positive or negative feedback buttons for an assistant message.*/
441
447
  this.onFeedback = output();
448
+ /**
449
+ * Emitted after any meaningful mutation of the messages list (send, finish, reprompt, reload, delete, reset).
450
+ * Allows the parent to react to conversation changes, e.g. to persist the conversation.
451
+ */
452
+ this.onMessagesChange = output();
442
453
  this._isLoading = signal(true, ...(ngDevMode ? [{ debugName: "_isLoading" }] : [])); // initially true while we do our initial ping of the agent health - prevents message flashing up before ready
443
454
  this.aiAgentManagerApplicationName = gettext('AI Agent Manager');
444
455
  /** Stores any error with the agent or backend microservice. This error message is already translated. */
@@ -448,16 +459,28 @@ class AgentChatComponent {
448
459
  /** Stores any detailed error messages from the backend. */
449
460
  this.agentHealthDetailedMessages = signal(undefined, ...(ngDevMode ? [{ debugName: "agentHealthDetailedMessages" }] : []));
450
461
  /**
451
- * Stores any error that occurred during streaming the current message from the agent.
462
+ * Stores any error from LLM call.
452
463
  *
453
464
  * If possible this string is translated already or registered with `gettext`.
454
465
  * In practice, this may not be translated if it comes from the backend.
455
466
  */
456
- this.agentStreamError = signal(undefined, ...(ngDevMode ? [{ debugName: "agentStreamError" }] : []));
467
+ this.agentRequestError = signal(undefined, ...(ngDevMode ? [{ debugName: "agentRequestError" }] : []));
457
468
  // A stream of AI messages representing the conversation.
458
469
  // NB: this is NOT part of the API of this component and may change in future
459
470
  // (e.g. we may improve AIMessage class)
460
471
  this.messages = signal([], ...(ngDevMode ? [{ debugName: "messages" }] : []));
472
+ /** Computed cumulative token usage across all messages in the conversation. */
473
+ this.cumulativeUsage = computed(() => {
474
+ const msgs = this.messages();
475
+ return msgs.reduce((acc, msg) => {
476
+ if ('usage' in msg && msg.usage) {
477
+ acc.inputTokens += msg.usage.inputTokens || 0;
478
+ acc.outputTokens += msg.usage.outputTokens || 0;
479
+ acc.totalTokens += msg.usage.totalTokens || 0;
480
+ }
481
+ return acc;
482
+ }, { inputTokens: 0, outputTokens: 0, totalTokens: 0 });
483
+ }, ...(ngDevMode ? [{ debugName: "cumulativeUsage" }] : []));
461
484
  /**
462
485
  * Computed signal holding the last assistant message, which is the one which may be rapidly changing as we
463
486
  * stream it back. This allows us to update the UI reactively without affecting the rest of the message history.
@@ -514,25 +537,13 @@ class AgentChatComponent {
514
537
  return this.pluginContextPath || this.appState.currentApplication?.value?.contextPath || '';
515
538
  }
516
539
  async ngOnInit() {
517
- // Restore chat history first, if provided
518
- this._restoreChatHistory(this.initialChatHistory());
519
- // Set the loading indicator to prevent interactions while we establish whether the agent is useable
520
- this._isLoading.set(true);
521
- try {
522
- const agentHealth = await this.aiService.getAgentHealth(this.agentName, this.currentContextPath);
523
- this._agentHealth.set(agentHealth);
524
- this.agentHealthDetailedMessages.set(agentHealth.messages?.join('\n'));
525
- this._agentHealthError.set(this.handleAgentError(agentHealth));
526
- if (this.agentDefinition && this.agentDefinition.snapshot) {
527
- this.createAgent();
528
- }
529
- }
530
- catch (ex) {
531
- console.warn('Error getting agent manager health:', ex);
532
- this.userAnalyticsService.sendAgentUnavailable('agent-health-check-failed');
533
- this._agentHealthError.set(this.translateService.instant(gettext('This tenant does not have the {{agentManager}} microservice. Please contact your administrator.'), { agentManager: this.translateService.instant(this.aiAgentManagerApplicationName) }));
540
+ await this.initializeChat();
541
+ }
542
+ async ngOnChanges(changes) {
543
+ if ((changes['agent'] && this.agent()) || changes['initialChatHistory']) {
544
+ this.resetMessages();
545
+ await this.initializeChat();
534
546
  }
535
- this._isLoading.set(false);
536
547
  }
537
548
  /**
538
549
  * Save the current chat history and suggestions to an opaque, versioned, JSON-serializable snapshot of the chat history for persistence.
@@ -560,8 +571,9 @@ class AgentChatComponent {
560
571
  components[components.length - 1].setThinkingExpanded(false);
561
572
  }
562
573
  this.messages.set([...this.messages(), message]);
574
+ this.onMessagesChange.emit(this.messages());
563
575
  this._isLoading.set(true);
564
- this.agentStreamError.set(undefined); // Clear any previous stream errors
576
+ this.agentRequestError.set(undefined); // Clear any previous stream errors
565
577
  this.abortController = new AbortController();
566
578
  this._clientToolOutputs = {};
567
579
  // Prepare messages for AI service, optionally including grounding message
@@ -576,60 +588,19 @@ class AgentChatComponent {
576
588
  };
577
589
  messagesForAI.splice(messagesForAI.length - 1, 0, groundingMsg);
578
590
  }
579
- this.messages.set([...this.messages(), { role: 'assistant', content: [] }]);
580
- const stream = await this.aiService.stream$(this.agentDefinition || this.agentName, messagesForAI, this.variables(), this.abortController, this.currentContextPath);
581
- if (this.assistantSubscription) {
582
- this.assistantSubscription.unsubscribe();
591
+ const currentAssistantMessage = {
592
+ role: 'assistant',
593
+ content: []
594
+ };
595
+ this.messages.set([...this.messages(), currentAssistantMessage]);
596
+ // Branch based on agent type: object agents use a single non-streaming request,
597
+ // text agents use SSE streaming
598
+ if (this.agentDefinition?.definition.type === 'object') {
599
+ await this._sendObjectMessage(currentAssistantMessage, messagesForAI);
600
+ }
601
+ else {
602
+ await this._sendStreamMessage(messagesForAI);
583
603
  }
584
- this.assistantSubscription = stream.subscribe({
585
- next: async (response) => {
586
- if (response?.changedPart?.type === 'response-metadata') {
587
- if (response.changedPart.model) {
588
- this.model = response.changedPart.model;
589
- }
590
- if (response.changedPart.systemPrompt) {
591
- this.systemPrompt = response.changedPart.systemPrompt;
592
- }
593
- }
594
- else {
595
- try {
596
- await this.processAgentMessage(response.message, response.changedPart);
597
- }
598
- catch (error) {
599
- console.error('Error processing agent message:', error, response.changedPart, response.message);
600
- this.cancel();
601
- const errorMessage = error instanceof Error
602
- ? error.message
603
- : gettext('An error occurred while processing the response from the AI agent.');
604
- this.agentStreamError.set(errorMessage);
605
- }
606
- }
607
- },
608
- complete: () => {
609
- if (this._isLoading()) {
610
- // Should never happen, but worth knowing if it does (indicates bug in processAgentMessage)
611
- console.error('AI response stream completed but message loading indicator was not reset');
612
- }
613
- },
614
- error: async (error) => {
615
- // Currently errors from the AIService are often a big JSON with no human-friendly message anywhere
616
- // (e.g. {error:{error:{statusCode:400, name:"AI-APICallError", ...}},
617
- // so we can only show the detail in the console and have to give a non-informative error to the user;
618
- // but if the backend could add a user-friendly "message" we could render that
619
- console.error('Error in AI stream:', error);
620
- const errorMessage = typeof error === 'string'
621
- ? error
622
- : error?.message || gettext('An error occurred while communicating with the AI agent.');
623
- this.agentStreamError.set(errorMessage);
624
- // Remove the empty assistant message that was added
625
- const messages = this.messages();
626
- if (messages[messages.length - 1]?.role === 'assistant') {
627
- this.messages.set(messages.slice(0, -1));
628
- }
629
- this._isLoading.set(false);
630
- this.userAnalyticsService.sendAssistantMessageError(errorMessage, this.messages(), await this.getAnalyticsMetadataContext());
631
- }
632
- });
633
604
  }
634
605
  ngOnDestroy() {
635
606
  this.cancel();
@@ -646,6 +617,7 @@ class AgentChatComponent {
646
617
  messageCount: this.messages().length - index - 1
647
618
  }), Status.DANGER, { cancel: gettext('Cancel'), ok: gettext('Delete and replace') });
648
619
  this.messages.set(this.messages().slice(0, index));
620
+ this.onMessagesChange.emit(this.messages());
649
621
  this.prompt = userMessage.content;
650
622
  }
651
623
  catch (e) {
@@ -670,6 +642,7 @@ class AgentChatComponent {
670
642
  const userMessage = this.messages()[index - 1];
671
643
  if (index > -1) {
672
644
  this.messages.set(this.messages().slice(0, index - 1));
645
+ this.onMessagesChange.emit(this.messages());
673
646
  this.sendMessage({
674
647
  role: 'user',
675
648
  content: userMessage.content,
@@ -678,13 +651,50 @@ class AgentChatComponent {
678
651
  }
679
652
  }
680
653
  cancel() {
654
+ const abortMessage = this.translateService.instant(gettext('AI response cancelled.'));
681
655
  if (this.abortController) {
682
- this.abortController.abort();
656
+ this.abortController.abort(abortMessage);
683
657
  }
684
658
  if (this.assistantSubscription) {
685
659
  this.assistantSubscription.unsubscribe();
686
660
  }
687
661
  this._isLoading.set(false);
662
+ const lastMessage = this.messages()[this.messages().length - 1];
663
+ if (lastMessage && lastMessage.role === 'assistant' && !lastMessage.content) {
664
+ this.messages.set(this.messages().slice(0, -1));
665
+ this.onMessagesChange.emit(this.messages());
666
+ // if user aborts sending message immediately, before abort controller is instantiated,
667
+ // error message won't be set and we need to do it manually here
668
+ if (!this.agentRequestError()) {
669
+ this.agentRequestError.set(abortMessage);
670
+ }
671
+ }
672
+ }
673
+ /** Clears all messages and cancels any in-flight stream. */
674
+ resetMessages() {
675
+ this.cancel();
676
+ this.messages.set([]);
677
+ this.onMessagesChange.emit(this.messages());
678
+ }
679
+ /** Removes the last user message and all assistant messages that follow it from the conversation. */
680
+ deleteLastExchange() {
681
+ const msgs = this.messages();
682
+ if (msgs.length === 0)
683
+ return;
684
+ // Find the index of the last user message
685
+ let lastUserIndex = -1;
686
+ for (let i = msgs.length - 1; i >= 0; i--) {
687
+ if (msgs[i].role === 'user') {
688
+ lastUserIndex = i;
689
+ break;
690
+ }
691
+ }
692
+ // If no user message found, do nothing
693
+ if (lastUserIndex === -1)
694
+ return;
695
+ // Remove from the last user message onwards
696
+ this.messages.set(msgs.slice(0, lastUserIndex));
697
+ this.onMessagesChange.emit(this.messages());
688
698
  }
689
699
  /**
690
700
  * Overrides the output of a specific tool result (in the current assistant message), including optionally indicating that an error occurred.
@@ -717,6 +727,16 @@ class AgentChatComponent {
717
727
  this._isLoading.set(false);
718
728
  }
719
729
  // Private methods go here:
730
+ async initializeChat() {
731
+ this._restoreChatHistory(this.initialChatHistory());
732
+ if (this.skipHealthCheck()) {
733
+ this._agentHealthError.set(undefined);
734
+ this._isLoading.set(false);
735
+ }
736
+ else {
737
+ await this.checkAgentHealth();
738
+ }
739
+ }
720
740
  /** Restore chat history from a previously-saved snapshot. Does nothing if the component already has some messages. */
721
741
  _restoreChatHistory(history) {
722
742
  if (!history)
@@ -729,6 +749,25 @@ class AgentChatComponent {
729
749
  this.messages.set(restored.messages);
730
750
  this._restoredSuggestions = restored.suggestions;
731
751
  }
752
+ async checkAgentHealth() {
753
+ // Set the loading indicator to prevent interactions while we establish whether the agent is useable
754
+ this._isLoading.set(true);
755
+ try {
756
+ const agentHealth = await this.aiService.getAgentHealth(this.agentName, this.currentContextPath);
757
+ this._agentHealth.set(agentHealth);
758
+ this.agentHealthDetailedMessages.set(agentHealth.messages?.join('\n'));
759
+ this._agentHealthError.set(this.handleAgentError(agentHealth));
760
+ if (this.agentDefinition && this.agentDefinition.snapshot) {
761
+ this.createAgent();
762
+ }
763
+ }
764
+ catch (ex) {
765
+ console.warn('Error getting agent manager health:', ex);
766
+ this.userAnalyticsService.sendAgentUnavailable('agent-health-check-failed');
767
+ this._agentHealthError.set(this.translateService.instant(gettext('This tenant does not have the {{agentManager}} microservice. Please contact your administrator.'), { agentManager: this.translateService.instant(this.aiAgentManagerApplicationName) }));
768
+ }
769
+ this._isLoading.set(false);
770
+ }
732
771
  async getAnalyticsMetadataContext() {
733
772
  return await this.userAnalyticsService.getAnalyticsMetadataContext(this.agentName, this.agentDefinition, this.userAnalyticsContext(), this.model, this.systemPrompt);
734
773
  }
@@ -835,6 +874,8 @@ class AgentChatComponent {
835
874
  }
836
875
  }
837
876
  if (updatedAssistantMsg.finishReason === 'stop') {
877
+ currentAssistantMsg.usage = updatedAssistantMsg.usage;
878
+ this.onMessagesChange.emit(this.messages());
838
879
  this.onMessageFinish.emit(currentAssistantMsg);
839
880
  void this.getAnalyticsMetadataContext()
840
881
  .then(analyticsContext => {
@@ -855,8 +896,112 @@ class AgentChatComponent {
855
896
  }
856
897
  }
857
898
  }
899
+ /** Handles sending a message to a text-type agent via SSE streaming. */
900
+ async _sendStreamMessage(messagesForAI) {
901
+ const stream = await this.aiService.stream$(this.agentDefinition || this.agentName, messagesForAI, this.variables(), this.abortController, this.currentContextPath);
902
+ if (this.assistantSubscription) {
903
+ this.assistantSubscription.unsubscribe();
904
+ }
905
+ this.assistantSubscription = stream.subscribe({
906
+ next: async (response) => {
907
+ if (response?.changedPart?.type === 'response-metadata') {
908
+ if (response.changedPart.model) {
909
+ this.model = response.changedPart.model;
910
+ }
911
+ if (response.changedPart.systemPrompt) {
912
+ this.systemPrompt = response.changedPart.systemPrompt;
913
+ }
914
+ }
915
+ else {
916
+ try {
917
+ await this.processAgentMessage(response.message, response.changedPart);
918
+ }
919
+ catch (error) {
920
+ console.error('Error processing agent message:', error, response.changedPart, response.message);
921
+ this.cancel();
922
+ const errorMessage = error instanceof Error
923
+ ? error.message
924
+ : gettext('An error occurred while processing the response from the AI agent.');
925
+ this.agentRequestError.set(errorMessage);
926
+ }
927
+ }
928
+ },
929
+ complete: () => {
930
+ if (this._isLoading()) {
931
+ // Should never happen, but worth knowing if it does (indicates bug in processAgentMessage)
932
+ console.error('AI response stream completed but message loading indicator was not reset');
933
+ }
934
+ },
935
+ error: async (error) => {
936
+ // Currently errors from the AIService are often a big JSON with no human-friendly message anywhere
937
+ // (e.g. {error:{error:{statusCode:400, name:"AI-APICallError", ...}},
938
+ // so we can only show the detail in the console and have to give a non-informative error to the user;
939
+ // but if the backend could add a user-friendly "message" we could render that
940
+ console.error('Error in AI stream:', error);
941
+ const errorMessage = typeof error === 'string'
942
+ ? error
943
+ : error?.message || gettext('An error occurred while communicating with the AI agent.');
944
+ this.agentRequestError.set(errorMessage);
945
+ // Remove the empty assistant message that was added
946
+ const messages = this.messages();
947
+ if (messages[messages.length - 1]?.role === 'assistant') {
948
+ this.messages.set(messages.slice(0, -1));
949
+ this.onMessagesChange.emit(this.messages());
950
+ }
951
+ this._isLoading.set(false);
952
+ this.userAnalyticsService.sendAssistantMessageError(errorMessage, this.messages(), await this.getAnalyticsMetadataContext());
953
+ }
954
+ });
955
+ }
956
+ /** Handles sending a message to an object-type agent via a single non-streaming request.
957
+ * The response JSON is rendered as a fenced code block in the assistant message.
958
+ */
959
+ async _sendObjectMessage(currentAssistantMessage, messagesForAI) {
960
+ try {
961
+ const response = await this.aiService.callObjectAgent(this.agentDefinition, messagesForAI, this.variables(), this.abortController);
962
+ const jsonContent = JSON.stringify(response.object ?? response, null, 2);
963
+ currentAssistantMessage.content = [{ type: 'object', jsonContent }];
964
+ currentAssistantMessage.timestamp = new Date().toISOString();
965
+ currentAssistantMessage.finishReason = 'stop';
966
+ if (response.totalUsage) {
967
+ currentAssistantMessage.usage = {
968
+ inputTokens: response.totalUsage.inputTokens,
969
+ outputTokens: response.totalUsage.outputTokens,
970
+ totalTokens: response.totalUsage.totalTokens
971
+ };
972
+ }
973
+ this._isLoading.set(false);
974
+ this.messages.set([...this.messages()]);
975
+ this.onMessagesChange.emit(this.messages());
976
+ this.onMessageFinish.emit(currentAssistantMessage);
977
+ void this.getAnalyticsMetadataContext()
978
+ .then(analyticsContext => {
979
+ this.userAnalyticsService.sendAssistantMessageComplete(currentAssistantMessage, this.messages(), analyticsContext);
980
+ })
981
+ .catch(error => {
982
+ console.error('Error sending analytics:', error);
983
+ });
984
+ }
985
+ catch (error) {
986
+ console.error('Error in object agent request:', error);
987
+ const errorMessage = typeof error === 'string'
988
+ ? error
989
+ : error?.message || gettext('An error occurred while communicating with the AI agent.');
990
+ this.agentRequestError.set(errorMessage);
991
+ // Remove the empty assistant message that was added
992
+ const messages = this.messages();
993
+ if (messages[messages.length - 1] === currentAssistantMessage) {
994
+ this.messages.set(messages.slice(0, -1));
995
+ }
996
+ this.onMessagesChange.emit(this.messages());
997
+ this._isLoading.set(false);
998
+ }
999
+ finally {
1000
+ this._isLoading.set(false);
1001
+ }
1002
+ }
858
1003
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AgentChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
859
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AgentChatComponent, isStandalone: true, selector: "c8y-agent-chat", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null }, chatConfig: { classPropertyName: "chatConfig", publicName: "chatConfig", isSignal: true, isRequired: false, transformFunction: null }, welcomeTemplate: { classPropertyName: "welcomeTemplate", publicName: "welcomeTemplate", isSignal: true, isRequired: false, transformFunction: null }, autoCreateAgents: { classPropertyName: "autoCreateAgents", publicName: "autoCreateAgents", isSignal: true, isRequired: false, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: false, transformFunction: null }, assistantMessageComponent: { classPropertyName: "assistantMessageComponent", publicName: "assistantMessageComponent", isSignal: true, isRequired: false, transformFunction: null }, assistantMessageDisplayConfig: { classPropertyName: "assistantMessageDisplayConfig", publicName: "assistantMessageDisplayConfig", isSignal: true, isRequired: false, transformFunction: null }, preprocessAgentMessage: { classPropertyName: "preprocessAgentMessage", publicName: "preprocessAgentMessage", isSignal: true, isRequired: false, transformFunction: null }, pruneMessagesForAgent: { classPropertyName: "pruneMessagesForAgent", publicName: "pruneMessagesForAgent", isSignal: true, isRequired: false, transformFunction: null }, initialChatHistory: { classPropertyName: "initialChatHistory", publicName: "initialChatHistory", isSignal: true, isRequired: false, transformFunction: null }, groundingContextProvider: { classPropertyName: "groundingContextProvider", publicName: "groundingContextProvider", isSignal: true, isRequired: false, transformFunction: null }, userAnalyticsContext: { classPropertyName: "userAnalyticsContext", publicName: "userAnalyticsContext", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onMessageFinish: "onMessageFinish", onToolResult: "onToolResult", onFeedback: "onFeedback" }, viewQueries: [{ propertyName: "assistantMessageComponents", predicate: AiChatAssistantMessageComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@let agentHealthErrorMsg = _agentHealthError();\n@if (agentHealthErrorMsg) {\n @if (isLoadingAiResponse()) {\n <c8y-loading class=\"m-auto\"></c8y-loading>\n } @else {\n <c8y-ui-empty-state\n class=\"m-auto\"\n [icon]=\"'settings'\"\n [title]=\"'AI agent is not available.' | translate\"\n >\n <span>{{ agentHealthErrorMsg }}</span>\n\n @if (canCreate) {\n <div class=\"text-center m-t-16 m-b-16\">\n <button\n class=\"btn btn-primary\"\n (click)=\"createAgent()\"\n >\n {{ 'Create agent' | translate }}\n </button>\n </div>\n } @else {\n <p\n class=\"text-pre-wrap m-t-8\"\n data-cy=\"agent-health-detailed-messages\"\n >\n <small>{{ agentHealthDetailedMessages() }}</small>\n </p>\n }\n\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a c8y-guide-href=\"/docs/ai\">user documentation</a>.\n </small>\n </p>\n </c8y-ui-empty-state>\n }\n}\n\n@if (!agentHealthErrorMsg) {\n <c8y-ai-chat\n (onMessage)=\"sendMessage($event)\"\n [isLoading]=\"isLoadingAiResponse()\"\n (onCancel)=\"cancel()\"\n [config]=\"chatConfig() ?? {}\"\n [prompt]=\"prompt\"\n [suggestionsTemplate]=\"suggestionsRef\"\n [welcomeTemplate]=\"welcomeTemplate()\"\n >\n @let messages$ = messages();\n @for (message of messages$; track $index; let i = $index) {\n <c8y-ai-chat-message [message]=\"message\">\n @if (message.role !== 'user' && model) {\n <!-- Visually hidden label included when users copy-paste from the chat, e.g. to report issues. It's quite helpful to know the model. -->\n <span\n class=\"hidden-copy-label\"\n aria-hidden=\"true\"\n >\n {{ `(Using model: ${model})` }}</span\n >\n }\n\n @if (message.role === 'user') {\n <div\n class=\"message-content\"\n data-cy=\"user-message-content\"\n [innerHTML]=\"message.content | markdownToHtml | async\"\n ></div>\n } @else if (\n message.role === 'assistant' && isLoadingAiResponse() && i === messages$.length - 1\n ) {\n <div>\n <!-- Last assistant message uses reactive computed context -->\n <ng-container\n [ngComponentOutlet]=\"assistantMessageComponent()\"\n [ngComponentOutletInputs]=\"{ assistantMessageContext: lastAssistantMessageContext() }\"\n ></ng-container>\n </div>\n } @else {\n <ng-container\n [ngComponentOutlet]=\"assistantMessageComponent()\"\n [ngComponentOutletInputs]=\"{\n assistantMessageContext: {\n message: message,\n config: assistantMessageDisplayConfig(),\n isMessageLoading: isLoadingAiResponse() && i === messages$.length - 1,\n messageDisplayIndex: messages$.length - 1 - i\n }\n }\"\n ></ng-container>\n }\n\n @let isLastMessage = i === messages$.length - 1;\n\n @if (message.role === 'user') {\n <c8y-ai-chat-message-action\n icon=\"pencil\"\n [tooltip]=\"'Edit and resend this message' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"reprompt(message)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-up\"\n [tooltip]=\"'This is useful' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"rate(message, true)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-down\"\n [tooltip]=\"'This is not useful' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"rate(message, false)\"\n ></c8y-ai-chat-message-action>\n }\n\n <!-- Only allow regenerating the last message, otherwise we could be deleting a lot of useful message history -->\n @if (message.role === 'assistant' && i > 0 && isLastMessage && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"refresh\"\n [tooltip]=\"'Regenerate this response' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"reload(message)\"\n ></c8y-ai-chat-message-action>\n }\n </c8y-ai-chat-message>\n }\n\n @let agentErrorMsg = agentStreamError();\n @if (agentErrorMsg) {\n <c8y-ai-chat-message [message]=\"{ role: 'assistant', content: agentErrorMsg }\">\n <div\n class=\"alert alert-danger d-flex a-i-center gap-8\"\n role=\"alert\"\n >\n <i\n class=\"c8y-icon c8y-icon-warning text-danger\"\n aria-hidden=\"true\"\n ></i>\n <!-- Since errors come from the backend the only translation is for the fallback error message supplied by the UI. -->\n <div class=\"flex-grow text-pre-wrap\">{{ agentErrorMsg | translate }}</div>\n </div>\n </c8y-ai-chat-message>\n }\n\n <ng-template #suggestionsRef>\n @if (!isLoadingAiResponse()) {\n <!-- As soon as we have any suggestions (even empty) these take priority over what we restored from history -->\n @let activeSuggestions = suggestions() === undefined ? _restoredSuggestions : suggestions();\n @for (suggestion of activeSuggestions; track $index) {\n <c8y-ai-chat-suggestion\n [icon]=\"suggestion.icon || 'c8y-bulb'\"\n [useAiButtons]=\"true\"\n [prompt]=\"suggestion.prompt\"\n [label]=\"suggestion.label ?? suggestion.prompt\"\n (suggestionClicked)=\"sendMessage($event)\"\n [disabled]=\"isLoadingAiResponse()\"\n ></c8y-ai-chat-suggestion>\n }\n }\n </ng-template>\n </c8y-ai-chat>\n}\n", styles: ["@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fade-in-out{animation:fadeIn .2s ease-in}.fade-in-out.ng-leave-active{animation:fadeOut .2s ease-out}.hidden-copy-label{display:block;font-size:0;line-height:0;-webkit-user-select:text;user-select:text}\n"], dependencies: [{ kind: "component", type: AiChatComponent, selector: "c8y-ai-chat", inputs: ["isLoading", "disabled", "prompt", "suggestionsTemplate", "welcomeTemplate", "config"], outputs: ["onMessage", "onCancel"] }, { kind: "component", type: AiChatSuggestionComponent, selector: "c8y-ai-chat-suggestion", inputs: ["label", "prompt", "icon", "useAiButtons", "disabled"], outputs: ["suggestionClicked"] }, { kind: "component", type: AiChatMessageComponent, selector: "c8y-ai-chat-message", inputs: ["role", "message"] }, { kind: "component", type: AiChatMessageActionComponent, selector: "c8y-ai-chat-message-action", inputs: ["custom", "disabled", "tooltip", "icon"], outputs: ["actionClicked"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "ngmodule", type: CollapseModule }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: GuideDocsComponent, selector: "[c8y-guide-docs]" }, { kind: "directive", type: GuideHrefDirective, selector: "[c8y-guide-href]", inputs: ["c8y-guide-href"] }, { kind: "pipe", type: MarkdownToHtmlPipe, name: "markdownToHtml" }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1004
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AgentChatComponent, isStandalone: true, selector: "c8y-agent-chat", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null }, chatConfig: { classPropertyName: "chatConfig", publicName: "chatConfig", isSignal: true, isRequired: false, transformFunction: null }, welcomeTemplate: { classPropertyName: "welcomeTemplate", publicName: "welcomeTemplate", isSignal: true, isRequired: false, transformFunction: null }, autoCreateAgents: { classPropertyName: "autoCreateAgents", publicName: "autoCreateAgents", isSignal: true, isRequired: false, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: false, transformFunction: null }, assistantMessageComponent: { classPropertyName: "assistantMessageComponent", publicName: "assistantMessageComponent", isSignal: true, isRequired: false, transformFunction: null }, assistantMessageDisplayConfig: { classPropertyName: "assistantMessageDisplayConfig", publicName: "assistantMessageDisplayConfig", isSignal: true, isRequired: false, transformFunction: null }, preprocessAgentMessage: { classPropertyName: "preprocessAgentMessage", publicName: "preprocessAgentMessage", isSignal: true, isRequired: false, transformFunction: null }, pruneMessagesForAgent: { classPropertyName: "pruneMessagesForAgent", publicName: "pruneMessagesForAgent", isSignal: true, isRequired: false, transformFunction: null }, skipHealthCheck: { classPropertyName: "skipHealthCheck", publicName: "skipHealthCheck", isSignal: true, isRequired: false, transformFunction: null }, initialChatHistory: { classPropertyName: "initialChatHistory", publicName: "initialChatHistory", isSignal: true, isRequired: false, transformFunction: null }, groundingContextProvider: { classPropertyName: "groundingContextProvider", publicName: "groundingContextProvider", isSignal: true, isRequired: false, transformFunction: null }, userAnalyticsContext: { classPropertyName: "userAnalyticsContext", publicName: "userAnalyticsContext", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onMessageFinish: "onMessageFinish", onToolResult: "onToolResult", onFeedback: "onFeedback", onMessagesChange: "onMessagesChange" }, viewQueries: [{ propertyName: "assistantMessageComponents", predicate: AiChatAssistantMessageComponent, descendants: true, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "@let agentHealthErrorMsg = _agentHealthError();\n@if (agentHealthErrorMsg) {\n @if (isLoadingAiResponse()) {\n <c8y-loading class=\"m-auto\"></c8y-loading>\n } @else {\n <c8y-ui-empty-state\n class=\"m-auto\"\n [icon]=\"'settings'\"\n [title]=\"'AI agent is not available.' | translate\"\n >\n <span>{{ agentHealthErrorMsg }}</span>\n\n @if (canCreate) {\n <div class=\"text-center m-t-16 m-b-16\">\n <button\n class=\"btn btn-primary\"\n (click)=\"createAgent()\"\n >\n {{ 'Create agent' | translate }}\n </button>\n </div>\n } @else {\n <p\n class=\"text-pre-wrap m-t-8\"\n data-cy=\"agent-health-detailed-messages\"\n >\n <small>{{ agentHealthDetailedMessages() }}</small>\n </p>\n }\n\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a c8y-guide-href=\"/docs/ai\">user documentation</a>.\n </small>\n </p>\n </c8y-ui-empty-state>\n }\n}\n\n@if (!agentHealthErrorMsg) {\n <c8y-ai-chat\n (onMessage)=\"sendMessage($event)\"\n [isLoading]=\"isLoadingAiResponse()\"\n (onCancel)=\"cancel()\"\n [config]=\"chatConfig() ?? {}\"\n [prompt]=\"prompt\"\n [suggestionsTemplate]=\"suggestionsRef\"\n [welcomeTemplate]=\"welcomeTemplate()\"\n [cumulativeUsage]=\"cumulativeUsage()\"\n >\n @let messages$ = messages();\n @for (message of messages$; track $index; let i = $index) {\n <c8y-ai-chat-message [message]=\"message\">\n @if (message.role !== 'user' && model) {\n <!-- Visually hidden label included when users copy-paste from the chat, e.g. to report issues. It's quite helpful to know the model. -->\n <span\n class=\"hidden-copy-label\"\n aria-hidden=\"true\"\n >\n {{ `(Using model: ${model})` }}</span\n >\n }\n\n @if (message.role === 'user') {\n <div\n class=\"message-content\"\n data-cy=\"user-message-content\"\n [innerHTML]=\"message.content | markdownToHtml | async\"\n ></div>\n } @else if (\n message.role === 'assistant' && isLoadingAiResponse() && i === messages$.length - 1\n ) {\n <div>\n <!-- Last assistant message uses reactive computed context -->\n <ng-container\n [ngComponentOutlet]=\"assistantMessageComponent()\"\n [ngComponentOutletInputs]=\"{ assistantMessageContext: lastAssistantMessageContext() }\"\n ></ng-container>\n </div>\n } @else {\n <ng-container\n [ngComponentOutlet]=\"assistantMessageComponent()\"\n [ngComponentOutletInputs]=\"{\n assistantMessageContext: {\n message: message,\n config: assistantMessageDisplayConfig(),\n isMessageLoading: isLoadingAiResponse() && i === messages$.length - 1,\n messageDisplayIndex: messages$.length - 1 - i\n }\n }\"\n ></ng-container>\n }\n\n @let isLastMessage = i === messages$.length - 1;\n\n @if (message.role === 'user') {\n <c8y-ai-chat-message-action\n icon=\"pencil\"\n [tooltip]=\"'Edit and resend this message' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"reprompt(message)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (\n message.role === 'assistant' &&\n i > 0 &&\n chatConfig()?.showUsagePerMessage &&\n message.usage\n ) {\n <c8y-ai-chat-message-action [custom]=\"true\">\n <span\n class=\"tag tag--info m-l-auto\"\n [tooltip]=\"\n 'Input tokens: {{ input }} \\nOutput tokens: {{ output }}'\n | translate\n : {\n input: message.usage.inputTokens || 0 | c8yNumber: 'floor' : '1.0-0',\n output: message.usage.outputTokens || 0 | c8yNumber: 'floor' : '1.0-0'\n }\n \"\n >\n {{ 'Tokens used: {{total}}' | translate: { total: message.usage.totalTokens || 0 |\n c8yNumber: 'floor' : '1.0-0' } }}\n </span>\n </c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-up\"\n [tooltip]=\"'This is useful' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"rate(message, true)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-down\"\n [tooltip]=\"'This is not useful' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"rate(message, false)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (\n message.role === 'assistant' &&\n !isLoadingAiResponse() &&\n chatConfig()?.showDeleteAction &&\n isLastMessage\n ) {\n <c8y-ai-chat-message-action\n icon=\"delete-bin\"\n [tooltip]=\"'Delete last exchange' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"deleteLastExchange()\"\n ></c8y-ai-chat-message-action>\n }\n\n <!-- Only allow regenerating the last message, otherwise we could be deleting a lot of useful message history -->\n @if (message.role === 'assistant' && i > 0 && isLastMessage && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"refresh\"\n [tooltip]=\"'Regenerate this response' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"reload(message)\"\n ></c8y-ai-chat-message-action>\n }\n </c8y-ai-chat-message>\n }\n\n @let agentErrorMsg = agentRequestError();\n @if (agentErrorMsg) {\n <c8y-ai-chat-message [message]=\"{ role: 'assistant', content: agentErrorMsg }\">\n <div\n class=\"alert alert-danger d-flex a-i-center gap-8\"\n role=\"alert\"\n >\n <i\n class=\"c8y-icon c8y-icon-warning text-danger\"\n aria-hidden=\"true\"\n ></i>\n <!-- Since errors come from the backend the only translation is for the fallback error message supplied by the UI. -->\n <div class=\"flex-grow text-pre-wrap\">{{ agentErrorMsg | translate }}</div>\n </div>\n </c8y-ai-chat-message>\n }\n\n <ng-template #suggestionsRef>\n @if (!isLoadingAiResponse()) {\n <!-- As soon as we have any suggestions (even empty) these take priority over what we restored from history -->\n @let activeSuggestions = suggestions() === undefined ? _restoredSuggestions : suggestions();\n @for (suggestion of activeSuggestions; track $index) {\n <c8y-ai-chat-suggestion\n [icon]=\"suggestion.icon || 'c8y-bulb'\"\n [useAiButtons]=\"true\"\n [prompt]=\"suggestion.prompt\"\n [label]=\"suggestion.label ?? suggestion.prompt\"\n (suggestionClicked)=\"sendMessage($event)\"\n [disabled]=\"isLoadingAiResponse()\"\n ></c8y-ai-chat-suggestion>\n }\n }\n </ng-template>\n </c8y-ai-chat>\n}\n", styles: ["@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fade-in-out{animation:fadeIn .2s ease-in}.fade-in-out.ng-leave-active{animation:fadeOut .2s ease-out}.hidden-copy-label{display:block;font-size:0;line-height:0;-webkit-user-select:text;user-select:text}\n"], dependencies: [{ kind: "component", type: AiChatComponent, selector: "c8y-ai-chat", inputs: ["isLoading", "disabled", "prompt", "suggestionsTemplate", "welcomeTemplate", "cumulativeUsage", "config"], outputs: ["onMessage", "onCancel"] }, { kind: "component", type: AiChatSuggestionComponent, selector: "c8y-ai-chat-suggestion", inputs: ["label", "prompt", "icon", "useAiButtons", "disabled"], outputs: ["suggestionClicked"] }, { kind: "component", type: AiChatMessageComponent, selector: "c8y-ai-chat-message", inputs: ["role", "message"] }, { kind: "component", type: AiChatMessageActionComponent, selector: "c8y-ai-chat-message-action", inputs: ["custom", "disabled", "tooltip", "icon"], outputs: ["actionClicked"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "ngmodule", type: CollapseModule }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: GuideDocsComponent, selector: "[c8y-guide-docs]" }, { kind: "directive", type: GuideHrefDirective, selector: "[c8y-guide-href]", inputs: ["c8y-guide-href"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i1.TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "pipe", type: MarkdownToHtmlPipe, name: "markdownToHtml" }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: NumberPipe, name: "c8yNumber" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
860
1005
  }
861
1006
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AgentChatComponent, decorators: [{
862
1007
  type: Component,
@@ -874,9 +1019,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
874
1019
  CollapseModule,
875
1020
  C8yTranslateDirective,
876
1021
  GuideDocsComponent,
877
- GuideHrefDirective
878
- ], template: "@let agentHealthErrorMsg = _agentHealthError();\n@if (agentHealthErrorMsg) {\n @if (isLoadingAiResponse()) {\n <c8y-loading class=\"m-auto\"></c8y-loading>\n } @else {\n <c8y-ui-empty-state\n class=\"m-auto\"\n [icon]=\"'settings'\"\n [title]=\"'AI agent is not available.' | translate\"\n >\n <span>{{ agentHealthErrorMsg }}</span>\n\n @if (canCreate) {\n <div class=\"text-center m-t-16 m-b-16\">\n <button\n class=\"btn btn-primary\"\n (click)=\"createAgent()\"\n >\n {{ 'Create agent' | translate }}\n </button>\n </div>\n } @else {\n <p\n class=\"text-pre-wrap m-t-8\"\n data-cy=\"agent-health-detailed-messages\"\n >\n <small>{{ agentHealthDetailedMessages() }}</small>\n </p>\n }\n\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a c8y-guide-href=\"/docs/ai\">user documentation</a>.\n </small>\n </p>\n </c8y-ui-empty-state>\n }\n}\n\n@if (!agentHealthErrorMsg) {\n <c8y-ai-chat\n (onMessage)=\"sendMessage($event)\"\n [isLoading]=\"isLoadingAiResponse()\"\n (onCancel)=\"cancel()\"\n [config]=\"chatConfig() ?? {}\"\n [prompt]=\"prompt\"\n [suggestionsTemplate]=\"suggestionsRef\"\n [welcomeTemplate]=\"welcomeTemplate()\"\n >\n @let messages$ = messages();\n @for (message of messages$; track $index; let i = $index) {\n <c8y-ai-chat-message [message]=\"message\">\n @if (message.role !== 'user' && model) {\n <!-- Visually hidden label included when users copy-paste from the chat, e.g. to report issues. It's quite helpful to know the model. -->\n <span\n class=\"hidden-copy-label\"\n aria-hidden=\"true\"\n >\n {{ `(Using model: ${model})` }}</span\n >\n }\n\n @if (message.role === 'user') {\n <div\n class=\"message-content\"\n data-cy=\"user-message-content\"\n [innerHTML]=\"message.content | markdownToHtml | async\"\n ></div>\n } @else if (\n message.role === 'assistant' && isLoadingAiResponse() && i === messages$.length - 1\n ) {\n <div>\n <!-- Last assistant message uses reactive computed context -->\n <ng-container\n [ngComponentOutlet]=\"assistantMessageComponent()\"\n [ngComponentOutletInputs]=\"{ assistantMessageContext: lastAssistantMessageContext() }\"\n ></ng-container>\n </div>\n } @else {\n <ng-container\n [ngComponentOutlet]=\"assistantMessageComponent()\"\n [ngComponentOutletInputs]=\"{\n assistantMessageContext: {\n message: message,\n config: assistantMessageDisplayConfig(),\n isMessageLoading: isLoadingAiResponse() && i === messages$.length - 1,\n messageDisplayIndex: messages$.length - 1 - i\n }\n }\"\n ></ng-container>\n }\n\n @let isLastMessage = i === messages$.length - 1;\n\n @if (message.role === 'user') {\n <c8y-ai-chat-message-action\n icon=\"pencil\"\n [tooltip]=\"'Edit and resend this message' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"reprompt(message)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-up\"\n [tooltip]=\"'This is useful' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"rate(message, true)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-down\"\n [tooltip]=\"'This is not useful' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"rate(message, false)\"\n ></c8y-ai-chat-message-action>\n }\n\n <!-- Only allow regenerating the last message, otherwise we could be deleting a lot of useful message history -->\n @if (message.role === 'assistant' && i > 0 && isLastMessage && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"refresh\"\n [tooltip]=\"'Regenerate this response' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"reload(message)\"\n ></c8y-ai-chat-message-action>\n }\n </c8y-ai-chat-message>\n }\n\n @let agentErrorMsg = agentStreamError();\n @if (agentErrorMsg) {\n <c8y-ai-chat-message [message]=\"{ role: 'assistant', content: agentErrorMsg }\">\n <div\n class=\"alert alert-danger d-flex a-i-center gap-8\"\n role=\"alert\"\n >\n <i\n class=\"c8y-icon c8y-icon-warning text-danger\"\n aria-hidden=\"true\"\n ></i>\n <!-- Since errors come from the backend the only translation is for the fallback error message supplied by the UI. -->\n <div class=\"flex-grow text-pre-wrap\">{{ agentErrorMsg | translate }}</div>\n </div>\n </c8y-ai-chat-message>\n }\n\n <ng-template #suggestionsRef>\n @if (!isLoadingAiResponse()) {\n <!-- As soon as we have any suggestions (even empty) these take priority over what we restored from history -->\n @let activeSuggestions = suggestions() === undefined ? _restoredSuggestions : suggestions();\n @for (suggestion of activeSuggestions; track $index) {\n <c8y-ai-chat-suggestion\n [icon]=\"suggestion.icon || 'c8y-bulb'\"\n [useAiButtons]=\"true\"\n [prompt]=\"suggestion.prompt\"\n [label]=\"suggestion.label ?? suggestion.prompt\"\n (suggestionClicked)=\"sendMessage($event)\"\n [disabled]=\"isLoadingAiResponse()\"\n ></c8y-ai-chat-suggestion>\n }\n }\n </ng-template>\n </c8y-ai-chat>\n}\n", styles: ["@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fade-in-out{animation:fadeIn .2s ease-in}.fade-in-out.ng-leave-active{animation:fadeOut .2s ease-out}.hidden-copy-label{display:block;font-size:0;line-height:0;-webkit-user-select:text;user-select:text}\n"] }]
879
- }], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], chatConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "chatConfig", required: false }] }], welcomeTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "welcomeTemplate", required: false }] }], autoCreateAgents: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCreateAgents", required: false }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: false }] }], assistantMessageComponent: [{ type: i0.Input, args: [{ isSignal: true, alias: "assistantMessageComponent", required: false }] }], assistantMessageDisplayConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "assistantMessageDisplayConfig", required: false }] }], preprocessAgentMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "preprocessAgentMessage", required: false }] }], pruneMessagesForAgent: [{ type: i0.Input, args: [{ isSignal: true, alias: "pruneMessagesForAgent", required: false }] }], initialChatHistory: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialChatHistory", required: false }] }], groundingContextProvider: [{ type: i0.Input, args: [{ isSignal: true, alias: "groundingContextProvider", required: false }] }], userAnalyticsContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "userAnalyticsContext", required: false }] }], onMessageFinish: [{ type: i0.Output, args: ["onMessageFinish"] }], onToolResult: [{ type: i0.Output, args: ["onToolResult"] }], onFeedback: [{ type: i0.Output, args: ["onFeedback"] }], assistantMessageComponents: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => AiChatAssistantMessageComponent), { isSignal: true }] }] } });
1022
+ GuideHrefDirective,
1023
+ TooltipModule,
1024
+ NumberPipe
1025
+ ], template: "@let agentHealthErrorMsg = _agentHealthError();\n@if (agentHealthErrorMsg) {\n @if (isLoadingAiResponse()) {\n <c8y-loading class=\"m-auto\"></c8y-loading>\n } @else {\n <c8y-ui-empty-state\n class=\"m-auto\"\n [icon]=\"'settings'\"\n [title]=\"'AI agent is not available.' | translate\"\n >\n <span>{{ agentHealthErrorMsg }}</span>\n\n @if (canCreate) {\n <div class=\"text-center m-t-16 m-b-16\">\n <button\n class=\"btn btn-primary\"\n (click)=\"createAgent()\"\n >\n {{ 'Create agent' | translate }}\n </button>\n </div>\n } @else {\n <p\n class=\"text-pre-wrap m-t-8\"\n data-cy=\"agent-health-detailed-messages\"\n >\n <small>{{ agentHealthDetailedMessages() }}</small>\n </p>\n }\n\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a c8y-guide-href=\"/docs/ai\">user documentation</a>.\n </small>\n </p>\n </c8y-ui-empty-state>\n }\n}\n\n@if (!agentHealthErrorMsg) {\n <c8y-ai-chat\n (onMessage)=\"sendMessage($event)\"\n [isLoading]=\"isLoadingAiResponse()\"\n (onCancel)=\"cancel()\"\n [config]=\"chatConfig() ?? {}\"\n [prompt]=\"prompt\"\n [suggestionsTemplate]=\"suggestionsRef\"\n [welcomeTemplate]=\"welcomeTemplate()\"\n [cumulativeUsage]=\"cumulativeUsage()\"\n >\n @let messages$ = messages();\n @for (message of messages$; track $index; let i = $index) {\n <c8y-ai-chat-message [message]=\"message\">\n @if (message.role !== 'user' && model) {\n <!-- Visually hidden label included when users copy-paste from the chat, e.g. to report issues. It's quite helpful to know the model. -->\n <span\n class=\"hidden-copy-label\"\n aria-hidden=\"true\"\n >\n {{ `(Using model: ${model})` }}</span\n >\n }\n\n @if (message.role === 'user') {\n <div\n class=\"message-content\"\n data-cy=\"user-message-content\"\n [innerHTML]=\"message.content | markdownToHtml | async\"\n ></div>\n } @else if (\n message.role === 'assistant' && isLoadingAiResponse() && i === messages$.length - 1\n ) {\n <div>\n <!-- Last assistant message uses reactive computed context -->\n <ng-container\n [ngComponentOutlet]=\"assistantMessageComponent()\"\n [ngComponentOutletInputs]=\"{ assistantMessageContext: lastAssistantMessageContext() }\"\n ></ng-container>\n </div>\n } @else {\n <ng-container\n [ngComponentOutlet]=\"assistantMessageComponent()\"\n [ngComponentOutletInputs]=\"{\n assistantMessageContext: {\n message: message,\n config: assistantMessageDisplayConfig(),\n isMessageLoading: isLoadingAiResponse() && i === messages$.length - 1,\n messageDisplayIndex: messages$.length - 1 - i\n }\n }\"\n ></ng-container>\n }\n\n @let isLastMessage = i === messages$.length - 1;\n\n @if (message.role === 'user') {\n <c8y-ai-chat-message-action\n icon=\"pencil\"\n [tooltip]=\"'Edit and resend this message' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"reprompt(message)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (\n message.role === 'assistant' &&\n i > 0 &&\n chatConfig()?.showUsagePerMessage &&\n message.usage\n ) {\n <c8y-ai-chat-message-action [custom]=\"true\">\n <span\n class=\"tag tag--info m-l-auto\"\n [tooltip]=\"\n 'Input tokens: {{ input }} \\nOutput tokens: {{ output }}'\n | translate\n : {\n input: message.usage.inputTokens || 0 | c8yNumber: 'floor' : '1.0-0',\n output: message.usage.outputTokens || 0 | c8yNumber: 'floor' : '1.0-0'\n }\n \"\n >\n {{ 'Tokens used: {{total}}' | translate: { total: message.usage.totalTokens || 0 |\n c8yNumber: 'floor' : '1.0-0' } }}\n </span>\n </c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-up\"\n [tooltip]=\"'This is useful' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"rate(message, true)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-down\"\n [tooltip]=\"'This is not useful' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"rate(message, false)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (\n message.role === 'assistant' &&\n !isLoadingAiResponse() &&\n chatConfig()?.showDeleteAction &&\n isLastMessage\n ) {\n <c8y-ai-chat-message-action\n icon=\"delete-bin\"\n [tooltip]=\"'Delete last exchange' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"deleteLastExchange()\"\n ></c8y-ai-chat-message-action>\n }\n\n <!-- Only allow regenerating the last message, otherwise we could be deleting a lot of useful message history -->\n @if (message.role === 'assistant' && i > 0 && isLastMessage && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"refresh\"\n [tooltip]=\"'Regenerate this response' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"reload(message)\"\n ></c8y-ai-chat-message-action>\n }\n </c8y-ai-chat-message>\n }\n\n @let agentErrorMsg = agentRequestError();\n @if (agentErrorMsg) {\n <c8y-ai-chat-message [message]=\"{ role: 'assistant', content: agentErrorMsg }\">\n <div\n class=\"alert alert-danger d-flex a-i-center gap-8\"\n role=\"alert\"\n >\n <i\n class=\"c8y-icon c8y-icon-warning text-danger\"\n aria-hidden=\"true\"\n ></i>\n <!-- Since errors come from the backend the only translation is for the fallback error message supplied by the UI. -->\n <div class=\"flex-grow text-pre-wrap\">{{ agentErrorMsg | translate }}</div>\n </div>\n </c8y-ai-chat-message>\n }\n\n <ng-template #suggestionsRef>\n @if (!isLoadingAiResponse()) {\n <!-- As soon as we have any suggestions (even empty) these take priority over what we restored from history -->\n @let activeSuggestions = suggestions() === undefined ? _restoredSuggestions : suggestions();\n @for (suggestion of activeSuggestions; track $index) {\n <c8y-ai-chat-suggestion\n [icon]=\"suggestion.icon || 'c8y-bulb'\"\n [useAiButtons]=\"true\"\n [prompt]=\"suggestion.prompt\"\n [label]=\"suggestion.label ?? suggestion.prompt\"\n (suggestionClicked)=\"sendMessage($event)\"\n [disabled]=\"isLoadingAiResponse()\"\n ></c8y-ai-chat-suggestion>\n }\n }\n </ng-template>\n </c8y-ai-chat>\n}\n", styles: ["@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fade-in-out{animation:fadeIn .2s ease-in}.fade-in-out.ng-leave-active{animation:fadeOut .2s ease-out}.hidden-copy-label{display:block;font-size:0;line-height:0;-webkit-user-select:text;user-select:text}\n"] }]
1026
+ }], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], chatConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "chatConfig", required: false }] }], welcomeTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "welcomeTemplate", required: false }] }], autoCreateAgents: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCreateAgents", required: false }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: false }] }], assistantMessageComponent: [{ type: i0.Input, args: [{ isSignal: true, alias: "assistantMessageComponent", required: false }] }], assistantMessageDisplayConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "assistantMessageDisplayConfig", required: false }] }], preprocessAgentMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "preprocessAgentMessage", required: false }] }], pruneMessagesForAgent: [{ type: i0.Input, args: [{ isSignal: true, alias: "pruneMessagesForAgent", required: false }] }], skipHealthCheck: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipHealthCheck", required: false }] }], initialChatHistory: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialChatHistory", required: false }] }], groundingContextProvider: [{ type: i0.Input, args: [{ isSignal: true, alias: "groundingContextProvider", required: false }] }], userAnalyticsContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "userAnalyticsContext", required: false }] }], onMessageFinish: [{ type: i0.Output, args: ["onMessageFinish"] }], onToolResult: [{ type: i0.Output, args: ["onToolResult"] }], onFeedback: [{ type: i0.Output, args: ["onFeedback"] }], onMessagesChange: [{ type: i0.Output, args: ["onMessagesChange"] }], assistantMessageComponents: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => AiChatAssistantMessageComponent), { isSignal: true }] }] } });
880
1027
 
881
1028
  class WidgetAiChatSectionComponent {
882
1029
  constructor() {
@@ -962,7 +1109,7 @@ class WidgetAiChatSectionComponent {
962
1109
  }
963
1110
  }
964
1111
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WidgetAiChatSectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
965
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: WidgetAiChatSectionComponent, isStandalone: true, selector: "c8y-widget-ai-chat-section", inputs: { loadComponentConfig: "loadComponentConfig", suggestions: "suggestions", agent: "agent", chatConfig: "chatConfig", useContextAsVariable: "useContextAsVariable", contextVariableName: "contextVariableName", variables: "variables", onToolResult: "onToolResult" }, ngImport: i0, template: "<c8y-widget-config-feedback>\n <div\n class=\"m-l-4 btn-ai btn-ai-hint btn-sm\"\n [title]=\"'AI code assistant' | translate\"\n >\n <span></span>\n </div>\n</c8y-widget-config-feedback>\n\n<c8y-agent-chat\n #agentChat\n [chatConfig]=\"chatConfig\"\n [suggestions]=\"agentChat.isWelcoming() ? suggestions : []\"\n [agent]=\"agent\"\n [variables]=\"variables | async\"\n (onToolResult)=\"toolResultHandler($event)\"\n [preprocessAgentMessage]=\"_componentConfig().preprocessAgentMessage\"\n [pruneMessagesForAgent]=\"_componentConfig().pruneMessagesForAgent\"\n [assistantMessageDisplayConfig]=\"_componentConfig().assistantMessageDisplayConfig || {}\"\n></c8y-agent-chat>\n", dependencies: [{ kind: "component", type: AgentChatComponent, selector: "c8y-agent-chat", inputs: ["agent", "suggestions", "chatConfig", "welcomeTemplate", "autoCreateAgents", "variables", "assistantMessageComponent", "assistantMessageDisplayConfig", "preprocessAgentMessage", "pruneMessagesForAgent", "initialChatHistory", "groundingContextProvider", "userAnalyticsContext"], outputs: ["onMessageFinish", "onToolResult", "onFeedback"] }, { kind: "component", type: WidgetConfigFeedbackComponent, selector: "c8y-widget-config-feedback" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
1112
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: WidgetAiChatSectionComponent, isStandalone: true, selector: "c8y-widget-ai-chat-section", inputs: { loadComponentConfig: "loadComponentConfig", suggestions: "suggestions", agent: "agent", chatConfig: "chatConfig", useContextAsVariable: "useContextAsVariable", contextVariableName: "contextVariableName", variables: "variables", onToolResult: "onToolResult" }, ngImport: i0, template: "<c8y-widget-config-feedback>\n <div\n class=\"m-l-4 btn-ai btn-ai-hint btn-sm\"\n [title]=\"'AI code assistant' | translate\"\n >\n <span></span>\n </div>\n</c8y-widget-config-feedback>\n\n<c8y-agent-chat\n #agentChat\n [chatConfig]=\"chatConfig\"\n [suggestions]=\"agentChat.isWelcoming() ? suggestions : []\"\n [agent]=\"agent\"\n [variables]=\"variables | async\"\n (onToolResult)=\"toolResultHandler($event)\"\n [preprocessAgentMessage]=\"_componentConfig().preprocessAgentMessage\"\n [pruneMessagesForAgent]=\"_componentConfig().pruneMessagesForAgent\"\n [assistantMessageDisplayConfig]=\"_componentConfig().assistantMessageDisplayConfig || {}\"\n></c8y-agent-chat>\n", dependencies: [{ kind: "component", type: AgentChatComponent, selector: "c8y-agent-chat", inputs: ["agent", "suggestions", "chatConfig", "welcomeTemplate", "autoCreateAgents", "variables", "assistantMessageComponent", "assistantMessageDisplayConfig", "preprocessAgentMessage", "pruneMessagesForAgent", "skipHealthCheck", "initialChatHistory", "groundingContextProvider", "userAnalyticsContext"], outputs: ["onMessageFinish", "onToolResult", "onFeedback", "onMessagesChange"] }, { kind: "component", type: WidgetConfigFeedbackComponent, selector: "c8y-widget-config-feedback" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
966
1113
  }
967
1114
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WidgetAiChatSectionComponent, decorators: [{
968
1115
  type: Component,