@c8y/ngx-components 1024.15.13 → 1024.16.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/fesm2022/c8y-ngx-components-ai-agent-chat.mjs +303 -63
  2. package/fesm2022/c8y-ngx-components-ai-agent-chat.mjs.map +1 -1
  3. package/fesm2022/c8y-ngx-components-ai-agents-html.mjs +9 -0
  4. package/fesm2022/c8y-ngx-components-ai-agents-html.mjs.map +1 -1
  5. package/fesm2022/c8y-ngx-components-ai-agents-scada.mjs +4 -0
  6. package/fesm2022/c8y-ngx-components-ai-agents-scada.mjs.map +1 -1
  7. package/fesm2022/c8y-ngx-components-ai-ai-chat.mjs +394 -52
  8. package/fesm2022/c8y-ngx-components-ai-ai-chat.mjs.map +1 -1
  9. package/fesm2022/c8y-ngx-components-ai.mjs +329 -19
  10. package/fesm2022/c8y-ngx-components-ai.mjs.map +1 -1
  11. package/fesm2022/c8y-ngx-components-bookmarks.mjs +1 -1
  12. package/fesm2022/c8y-ngx-components-bookmarks.mjs.map +1 -1
  13. package/fesm2022/c8y-ngx-components-context-dashboard.mjs +8 -2
  14. package/fesm2022/c8y-ngx-components-context-dashboard.mjs.map +1 -1
  15. package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs +96 -77
  16. package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs.map +1 -1
  17. package/fesm2022/c8y-ngx-components-datapoint-selector.mjs +92 -47
  18. package/fesm2022/c8y-ngx-components-datapoint-selector.mjs.map +1 -1
  19. package/fesm2022/c8y-ngx-components-device-profile.mjs +1 -1
  20. package/fesm2022/c8y-ngx-components-device-profile.mjs.map +1 -1
  21. package/fesm2022/c8y-ngx-components-echart.mjs +39 -12
  22. package/fesm2022/c8y-ngx-components-echart.mjs.map +1 -1
  23. package/fesm2022/c8y-ngx-components-ecosystem.mjs +1 -1
  24. package/fesm2022/c8y-ngx-components-ecosystem.mjs.map +1 -1
  25. package/fesm2022/c8y-ngx-components-protocol-opcua.mjs +1 -1
  26. package/fesm2022/c8y-ngx-components-protocol-opcua.mjs.map +1 -1
  27. package/fesm2022/c8y-ngx-components-register-device.mjs +5 -6
  28. package/fesm2022/c8y-ngx-components-register-device.mjs.map +1 -1
  29. package/fesm2022/c8y-ngx-components-repository-firmware.mjs +1 -1
  30. package/fesm2022/c8y-ngx-components-repository-firmware.mjs.map +1 -1
  31. package/fesm2022/c8y-ngx-components-repository-software.mjs +1 -1
  32. package/fesm2022/c8y-ngx-components-repository-software.mjs.map +1 -1
  33. package/fesm2022/c8y-ngx-components-sub-assets.mjs +30 -10
  34. package/fesm2022/c8y-ngx-components-sub-assets.mjs.map +1 -1
  35. package/fesm2022/c8y-ngx-components-time-context.mjs +13 -9
  36. package/fesm2022/c8y-ngx-components-time-context.mjs.map +1 -1
  37. package/fesm2022/c8y-ngx-components-trusted-certificates.mjs +1 -1
  38. package/fesm2022/c8y-ngx-components-trusted-certificates.mjs.map +1 -1
  39. package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget-ai-config.mjs +3 -2
  40. package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget-ai-config.mjs.map +1 -1
  41. package/fesm2022/c8y-ngx-components-widgets-implementations-asset-table.mjs +4 -6
  42. package/fesm2022/c8y-ngx-components-widgets-implementations-asset-table.mjs.map +1 -1
  43. package/fesm2022/c8y-ngx-components-widgets-implementations-scada.mjs +3 -2
  44. package/fesm2022/c8y-ngx-components-widgets-implementations-scada.mjs.map +1 -1
  45. package/fesm2022/c8y-ngx-components.mjs +122 -24
  46. package/fesm2022/c8y-ngx-components.mjs.map +1 -1
  47. package/locales/de.po +147 -16
  48. package/locales/es.po +143 -11
  49. package/locales/fr.po +459 -324
  50. package/locales/ja_JP.po +143 -11
  51. package/locales/ko.po +143 -11
  52. package/locales/locales.pot +139 -10
  53. package/locales/nl.po +143 -11
  54. package/locales/pl.po +143 -11
  55. package/locales/pt_BR.po +143 -11
  56. package/locales/zh_CN.po +143 -11
  57. package/locales/zh_TW.po +143 -11
  58. package/package.json +1 -1
  59. package/types/c8y-ngx-components-ai-agent-chat.d.ts +111 -4
  60. package/types/c8y-ngx-components-ai-agent-chat.d.ts.map +1 -1
  61. package/types/c8y-ngx-components-ai-agents-html.d.ts.map +1 -1
  62. package/types/c8y-ngx-components-ai-agents-scada.d.ts.map +1 -1
  63. package/types/c8y-ngx-components-ai-ai-chat.d.ts +224 -10
  64. package/types/c8y-ngx-components-ai-ai-chat.d.ts.map +1 -1
  65. package/types/c8y-ngx-components-ai.d.ts +176 -6
  66. package/types/c8y-ngx-components-ai.d.ts.map +1 -1
  67. package/types/c8y-ngx-components-context-dashboard.d.ts.map +1 -1
  68. package/types/c8y-ngx-components-datapoint-explorer-view.d.ts +7 -2
  69. package/types/c8y-ngx-components-datapoint-explorer-view.d.ts.map +1 -1
  70. package/types/c8y-ngx-components-datapoint-selector.d.ts +63 -50
  71. package/types/c8y-ngx-components-datapoint-selector.d.ts.map +1 -1
  72. package/types/c8y-ngx-components-echart.d.ts +12 -0
  73. package/types/c8y-ngx-components-echart.d.ts.map +1 -1
  74. package/types/c8y-ngx-components-register-device.d.ts.map +1 -1
  75. package/types/c8y-ngx-components-sub-assets.d.ts +10 -1
  76. package/types/c8y-ngx-components-sub-assets.d.ts.map +1 -1
  77. package/types/c8y-ngx-components-time-context.d.ts +2 -0
  78. package/types/c8y-ngx-components-time-context.d.ts.map +1 -1
  79. package/types/c8y-ngx-components-widgets-implementations-scada.d.ts.map +1 -1
  80. package/types/c8y-ngx-components.d.ts +37 -5
  81. package/types/c8y-ngx-components.d.ts.map +1 -1
@@ -1,16 +1,19 @@
1
1
  import { NgComponentOutlet, AsyncPipe } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
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, NumberPipe, ContextRouteService } from '@c8y/ngx-components';
3
+ import { Injectable, inject, input, output, ChangeDetectionStrategy, Component, contentChildren, computed, signal, viewChildren, viewChild, Injector, Input } from '@angular/core';
4
+ import { GainsightService, IconDirective, C8yTranslatePipe, DatePipe, ModalService, UserPreferencesService, AlertService, C8Y_PLUGIN_CONTEXT_PATH, AppStateService, Status, LoadingComponent, EmptyStateComponent, C8yTranslateDirective, GuideDocsComponent, GuideHrefDirective, MarkdownToHtmlPipe, NumberPipe, ContextRouteService } from '@c8y/ngx-components';
5
5
  import { TranslateService } from '@ngx-translate/core';
6
- import { AIService, defaultPruneMessagesForAgent } from '@c8y/ngx-components/ai';
6
+ import { AIService, AiToolApprovalMemoryService, defaultPruneMessagesForAgent } from '@c8y/ngx-components/ai';
7
7
  import { gettext } from '@c8y/ngx-components/gettext';
8
- import { AiChatAssistantMessageComponent, AiChatComponent, AiChatSuggestionComponent, AiChatMessageComponent, AiChatMessageActionComponent } from '@c8y/ngx-components/ai/ai-chat';
8
+ import { firstValueFrom, of, isObservable } from 'rxjs';
9
+ import { AiChatActionComponent, AiChatAssistantMessageComponent, AiChatComponent, AiChatSuggestionComponent, AiChatMessageComponent, AiChatMessageActionComponent } from '@c8y/ngx-components/ai/ai-chat';
9
10
  import { CollapseModule } from 'ngx-bootstrap/collapse';
10
- import * as i1 from 'ngx-bootstrap/tooltip';
11
+ import * as i1$1 from 'ngx-bootstrap/tooltip';
11
12
  import { TooltipModule } from 'ngx-bootstrap/tooltip';
13
+ import { CdkTrapFocus } from '@angular/cdk/a11y';
14
+ import * as i1 from 'ngx-bootstrap/dropdown';
15
+ import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
12
16
  import { WidgetConfigService, WidgetConfigFeedbackComponent } from '@c8y/ngx-components/context-dashboard';
13
- import { of, isObservable } from 'rxjs';
14
17
 
15
18
  /** A stateless service used by agent-chat for managing chat history. Not public. */
16
19
  class ChatHistoryService {
@@ -328,7 +331,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
328
331
  }]
329
332
  }] });
330
333
 
334
+ /**
335
+ * Dropdown that lets the user switch between manual and automatic tool approval for the agent chat.
336
+ * Extracted from `c8y-agent-chat` to keep that component small; place it inside a
337
+ * `c8y-ai-chat-action` so it renders in the chat action bar.
338
+ */
339
+ class AgentChatToolApprovalModeComponent {
340
+ constructor() {
341
+ /** Whether the global "approve all tools" (Auto) mode is currently active. */
342
+ this.globalToolApproval = input.required(...(ngDevMode ? [{ debugName: "globalToolApproval" }] : /* istanbul ignore next */ []));
343
+ /** Emits the requested mode when the user picks one: `true` for Auto, `false` for Manual. */
344
+ this.modeChange = output();
345
+ }
346
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: AgentChatToolApprovalModeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
347
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.19", type: AgentChatToolApprovalModeComponent, isStandalone: true, selector: "c8y-agent-chat-tool-approval-mode", inputs: { globalToolApproval: { classPropertyName: "globalToolApproval", publicName: "globalToolApproval", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { modeChange: "modeChange" }, host: { classAttribute: "d-contents" }, ngImport: i0, template: "@let autoLabel = 'Auto' | translate;\n@let manualLabel = 'Manual' | translate;\n<!-- Escape must close only the menu; unstopped it also closes the drawer hosting the chat. -->\n<div\n class=\"dropdown\"\n dropdown\n [dropup]=\"true\"\n #toolApprovalDropdown=\"bs-dropdown\"\n [cdkTrapFocus]=\"toolApprovalDropdown.isOpen\"\n (onHidden)=\"toolApprovalToggle.focus()\"\n (keyup.escape)=\"toolApprovalDropdown.isOpen && $event.stopPropagation()\"\n (keydown.escape)=\"toolApprovalDropdown.isOpen && $event.stopPropagation()\"\n>\n <button\n class=\"dropdown-toggle btn btn-clean btn-sm d-flex a-i-center gap-4\"\n [title]=\"'Tool approval mode' | translate\"\n [attr.aria-expanded]=\"toolApprovalDropdown.isOpen\"\n type=\"button\"\n data-cy=\"chat-tool-approval-mode\"\n dropdownToggle\n #toolApprovalToggle\n >\n <i\n [c8yIcon]=\"globalToolApproval() ? 'bolt' : 'hand-paper-o'\"\n aria-hidden=\"true\"\n ></i>\n <span>{{ globalToolApproval() ? autoLabel : manualLabel }}</span>\n <i\n c8yIcon=\"caret-down\"\n aria-hidden=\"true\"\n ></i>\n </button>\n <ul\n class=\"dropdown-menu\"\n [attr.aria-label]=\"'Tool approval mode' | translate\"\n role=\"menu\"\n *dropdownMenu\n >\n <li\n [class.active]=\"!globalToolApproval()\"\n role=\"none\"\n >\n <button\n class=\"a-i-start\"\n [attr.aria-checked]=\"!globalToolApproval()\"\n role=\"menuitemradio\"\n type=\"button\"\n (click)=\"modeChange.emit(false)\"\n data-cy=\"chat-tool-approval-manual\"\n >\n <i\n [class.invisible]=\"globalToolApproval()\"\n class=\"icon-16 text-primary\"\n [c8yIcon]=\"'check'\"\n aria-hidden=\"true\"\n ></i>\n <i\n class=\"icon-16\"\n [c8yIcon]=\"'hand-paper-o'\"\n aria-hidden=\"true\"\n ></i>\n <span>\n {{ manualLabel }}\n <small class=\"d-block text-muted\">\n {{ 'Ask before running each tool' | translate }}\n </small>\n </span>\n </button>\n </li>\n <li\n [class.active]=\"globalToolApproval()\"\n role=\"none\"\n >\n <button\n class=\"a-i-start\"\n [attr.aria-checked]=\"globalToolApproval()\"\n role=\"menuitemradio\"\n type=\"button\"\n (click)=\"modeChange.emit(true)\"\n data-cy=\"chat-tool-approval-auto\"\n >\n <i\n class=\"text-primary icon-16\"\n [class.invisible]=\"!globalToolApproval()\"\n [c8yIcon]=\"'check'\"\n aria-hidden=\"true\"\n ></i>\n <i\n class=\"icon-16\"\n [c8yIcon]=\"'bolt'\"\n aria-hidden=\"true\"\n ></i>\n <span>\n {{ autoLabel }}\n <small class=\"d-block text-muted\">\n {{ 'Run tools without asking' | translate }}\n </small>\n </span>\n </button>\n </li>\n </ul>\n</div>\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "ngmodule", type: BsDropdownModule }, { kind: "directive", type: i1.BsDropdownMenuDirective, selector: "[bsDropdownMenu],[dropdownMenu]", exportAs: ["bs-dropdown-menu"] }, { kind: "directive", type: i1.BsDropdownToggleDirective, selector: "[bsDropdownToggle],[dropdownToggle]", exportAs: ["bs-dropdown-toggle"] }, { kind: "directive", type: i1.BsDropdownDirective, selector: "[bsDropdown], [dropdown]", inputs: ["placement", "triggers", "container", "dropup", "autoClose", "isAnimated", "insideClick", "isDisabled", "isOpen"], outputs: ["isOpenChange", "onShown", "onHidden"], exportAs: ["bs-dropdown"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
348
+ }
349
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: AgentChatToolApprovalModeComponent, decorators: [{
350
+ type: Component,
351
+ args: [{ selector: 'c8y-agent-chat-tool-approval-mode', changeDetection: ChangeDetectionStrategy.OnPush, imports: [C8yTranslatePipe, IconDirective, BsDropdownModule, CdkTrapFocus], host: { class: 'd-contents' }, template: "@let autoLabel = 'Auto' | translate;\n@let manualLabel = 'Manual' | translate;\n<!-- Escape must close only the menu; unstopped it also closes the drawer hosting the chat. -->\n<div\n class=\"dropdown\"\n dropdown\n [dropup]=\"true\"\n #toolApprovalDropdown=\"bs-dropdown\"\n [cdkTrapFocus]=\"toolApprovalDropdown.isOpen\"\n (onHidden)=\"toolApprovalToggle.focus()\"\n (keyup.escape)=\"toolApprovalDropdown.isOpen && $event.stopPropagation()\"\n (keydown.escape)=\"toolApprovalDropdown.isOpen && $event.stopPropagation()\"\n>\n <button\n class=\"dropdown-toggle btn btn-clean btn-sm d-flex a-i-center gap-4\"\n [title]=\"'Tool approval mode' | translate\"\n [attr.aria-expanded]=\"toolApprovalDropdown.isOpen\"\n type=\"button\"\n data-cy=\"chat-tool-approval-mode\"\n dropdownToggle\n #toolApprovalToggle\n >\n <i\n [c8yIcon]=\"globalToolApproval() ? 'bolt' : 'hand-paper-o'\"\n aria-hidden=\"true\"\n ></i>\n <span>{{ globalToolApproval() ? autoLabel : manualLabel }}</span>\n <i\n c8yIcon=\"caret-down\"\n aria-hidden=\"true\"\n ></i>\n </button>\n <ul\n class=\"dropdown-menu\"\n [attr.aria-label]=\"'Tool approval mode' | translate\"\n role=\"menu\"\n *dropdownMenu\n >\n <li\n [class.active]=\"!globalToolApproval()\"\n role=\"none\"\n >\n <button\n class=\"a-i-start\"\n [attr.aria-checked]=\"!globalToolApproval()\"\n role=\"menuitemradio\"\n type=\"button\"\n (click)=\"modeChange.emit(false)\"\n data-cy=\"chat-tool-approval-manual\"\n >\n <i\n [class.invisible]=\"globalToolApproval()\"\n class=\"icon-16 text-primary\"\n [c8yIcon]=\"'check'\"\n aria-hidden=\"true\"\n ></i>\n <i\n class=\"icon-16\"\n [c8yIcon]=\"'hand-paper-o'\"\n aria-hidden=\"true\"\n ></i>\n <span>\n {{ manualLabel }}\n <small class=\"d-block text-muted\">\n {{ 'Ask before running each tool' | translate }}\n </small>\n </span>\n </button>\n </li>\n <li\n [class.active]=\"globalToolApproval()\"\n role=\"none\"\n >\n <button\n class=\"a-i-start\"\n [attr.aria-checked]=\"globalToolApproval()\"\n role=\"menuitemradio\"\n type=\"button\"\n (click)=\"modeChange.emit(true)\"\n data-cy=\"chat-tool-approval-auto\"\n >\n <i\n class=\"text-primary icon-16\"\n [class.invisible]=\"!globalToolApproval()\"\n [c8yIcon]=\"'check'\"\n aria-hidden=\"true\"\n ></i>\n <i\n class=\"icon-16\"\n [c8yIcon]=\"'bolt'\"\n aria-hidden=\"true\"\n ></i>\n <span>\n {{ autoLabel }}\n <small class=\"d-block text-muted\">\n {{ 'Run tools without asking' | translate }}\n </small>\n </span>\n </button>\n </li>\n </ul>\n</div>\n" }]
352
+ }], propDecorators: { globalToolApproval: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalToolApproval", required: true }] }], modeChange: [{ type: i0.Output, args: ["modeChange"] }] } });
353
+
354
+ /** User preference key for the global "approve all tools" option. */
355
+ const GLOBAL_TOOL_APPROVAL_PREFERENCE_KEY = 'ai-approve-all-tools';
331
356
  class AgentChatComponent {
357
+ get agentName() {
358
+ const value = this.agent();
359
+ return typeof value === 'string' ? value : value.definition.name;
360
+ }
361
+ get agentDefinition() {
362
+ const value = this.agent();
363
+ if (!value || typeof value === 'string')
364
+ return undefined;
365
+ return value;
366
+ }
367
+ /** Is an AI agent request currently in progress. This can be used to disable UI elements while a reply (which might change the state) is being loaded. */
368
+ get isLoadingAiResponse() {
369
+ return this._isLoading.asReadonly();
370
+ }
371
+ /** Returns the most recent agent health check status such as `ready`, or an error. */
372
+ get agentHealthStatus() {
373
+ return this._agentHealth()?.status || 'loading';
374
+ }
375
+ get currentContextPath() {
376
+ return this.pluginContextPath || this.appState.currentApplication?.value?.contextPath || '';
377
+ }
332
378
  constructor() {
333
379
  this.translateService = inject(TranslateService);
334
380
  this.datePipe = inject(DatePipe);
@@ -359,6 +405,12 @@ class AgentChatComponent {
359
405
  this.chatConfig = input(...(ngDevMode ? [undefined, { debugName: "chatConfig" }] : /* istanbul ignore next */ []));
360
406
  /** Template for customizing the welcome view using `ng-template`. */
361
407
  this.welcomeTemplate = input(...(ngDevMode ? [undefined, { debugName: "welcomeTemplate" }] : /* istanbul ignore next */ []));
408
+ /**
409
+ * Action-bar components projected by consumers into the action bar beneath the input box (as
410
+ * opposed to the per-message `c8y-ai-chat-message-action`s). They are forwarded to the inner
411
+ * `c8y-ai-chat` since content queries do not reach the wrapped component.
412
+ */
413
+ this.chatActions = contentChildren(AiChatActionComponent, ...(ngDevMode ? [{ debugName: "chatActions" }] : /* istanbul ignore next */ []));
362
414
  /** Indicates whether the component has zero messages and is showing the welcome page. */
363
415
  this.isWelcoming = computed(() => this.messages().length === 0, ...(ngDevMode ? [{ debugName: "isWelcoming" }] : /* istanbul ignore next */ []));
364
416
  /** Configures whether this component should automatically create the provided `agent: ClientAgentDefinition`
@@ -504,47 +556,46 @@ class AgentChatComponent {
504
556
  message: lastMsg,
505
557
  config: config,
506
558
  isMessageLoading: isLoadingAiResponse,
507
- messageDisplayIndex: 0
559
+ messageDisplayIndex: 0,
560
+ onToolApprovalResponse: this.respondToToolApproval,
561
+ canRememberToolApproval: this.canRememberToolApproval
508
562
  };
509
563
  }, ...(ngDevMode ? [{ debugName: "lastAssistantMessageContext" }] : /* istanbul ignore next */ []));
510
564
  /** If the create agent button should be shown to the user. */
511
565
  this.canCreate = false;
512
566
  this.prompt = '';
513
567
  this.aiService = inject(AIService);
568
+ this.toolApprovalMemory = inject(AiToolApprovalMemoryService);
569
+ this.userPreferences = inject(UserPreferencesService);
570
+ /**
571
+ * Identifies this conversation in the tool approval memory. Remembered approvals ("always
572
+ * approve this tool" and the session-wide "always approve all tools") are scoped to it, so
573
+ * they end with the conversation: a new component instance or a message reset starts a new
574
+ * session that prompts again.
575
+ */
576
+ this._chatSessionId = this.toolApprovalMemory.newSessionId();
577
+ /**
578
+ * The global "approve all tools" option, persisted as a user preference across all chats and
579
+ * page loads. Tools configured with `needsApproval` still prompt on every call.
580
+ */
581
+ this.globalToolApproval = signal(false, ...(ngDevMode ? [{ debugName: "globalToolApproval" }] : /* istanbul ignore next */ []));
514
582
  this.userAnalyticsService = inject(UserAnalyticsService);
515
583
  this.alertService = inject(AlertService);
516
584
  this.chatHistoryService = inject(ChatHistoryService);
517
585
  this.pluginContextPath = inject(C8Y_PLUGIN_CONTEXT_PATH, { optional: true });
518
586
  this.appState = inject(AppStateService);
519
587
  this.assistantMessageComponents = viewChildren(AiChatAssistantMessageComponent, ...(ngDevMode ? [{ debugName: "assistantMessageComponents" }] : /* istanbul ignore next */ []));
588
+ this.chat = viewChild(AiChatComponent, ...(ngDevMode ? [{ debugName: "chat" }] : /* istanbul ignore next */ []));
520
589
  /**
521
590
  * Stores client-supplied tool outputs to override tool results for a given toolCallId in the current assistant message.
522
591
  * Cleared at the start of each new message.
523
592
  */
524
593
  this._clientToolOutputs = {};
525
- }
526
- get agentName() {
527
- const value = this.agent();
528
- return typeof value === 'string' ? value : value.definition.name;
529
- }
530
- get agentDefinition() {
531
- const value = this.agent();
532
- if (!value || typeof value === 'string')
533
- return undefined;
534
- return value;
535
- }
536
- /** Is an AI agent request currently in progress. This can be used to disable UI elements while a reply (which might change the state) is being loaded. */
537
- get isLoadingAiResponse() {
538
- return this._isLoading.asReadonly();
539
- }
540
- /** Returns the most recent agent health check status such as `ready`, or an error. */
541
- get agentHealthStatus() {
542
- return this._agentHealth()?.status || 'loading';
543
- }
544
- get currentContextPath() {
545
- return this.pluginContextPath || this.appState.currentApplication?.value?.contextPath || '';
594
+ this.respondToToolApproval = (part, decision) => this._respondToToolApproval(part, decision);
595
+ this.canRememberToolApproval = part => !this._requiresExplicitApproval(part.toolName);
546
596
  }
547
597
  async ngOnInit() {
598
+ void this._loadGlobalToolApproval();
548
599
  await this.initializeChat();
549
600
  }
550
601
  async ngOnChanges(changes) {
@@ -580,38 +631,11 @@ class AgentChatComponent {
580
631
  }
581
632
  this.messages.set([...this.messages(), message]);
582
633
  this.onMessagesChange.emit(this.messages());
583
- this._isLoading.set(true);
584
- this.agentRequestError.set(undefined); // Clear any previous stream errors
585
- this.abortController = new AbortController();
586
- this._clientToolOutputs = {};
587
- // Prepare messages for AI service, optionally including grounding message
588
- const pruneMessagesForAgent = this.pruneMessagesForAgent() || defaultPruneMessagesForAgent;
589
- const messagesForAI = pruneMessagesForAgent(this.messages());
590
- const groundingProvider = this.groundingContextProvider();
591
- if (groundingProvider) {
592
- // Insert grounding message before the last user message
593
- const groundingMsg = {
594
- role: 'user',
595
- content: groundingProvider()
596
- };
597
- messagesForAI.splice(messagesForAI.length - 1, 0, groundingMsg);
598
- }
599
- const currentAssistantMessage = {
600
- role: 'assistant',
601
- content: []
602
- };
603
- this.messages.set([...this.messages(), currentAssistantMessage]);
604
- // Branch based on agent type: object agents use a single non-streaming request,
605
- // text agents use SSE streaming
606
- if (this.agentDefinition?.definition.type === 'object') {
607
- await this._sendObjectMessage(currentAssistantMessage, messagesForAI);
608
- }
609
- else {
610
- await this._sendStreamMessage(messagesForAI);
611
- }
634
+ await this._dispatchToAgent(true);
612
635
  }
613
636
  ngOnDestroy() {
614
637
  this.cancel();
638
+ this.toolApprovalMemory.endSession(this._chatSessionId);
615
639
  }
616
640
  async reprompt(userMessage) {
617
641
  const index = this.messages().indexOf(userMessage);
@@ -627,6 +651,8 @@ class AgentChatComponent {
627
651
  this.messages.set(this.messages().slice(0, index));
628
652
  this.onMessagesChange.emit(this.messages());
629
653
  this.prompt = userMessage.content;
654
+ // The prompt moved to the input, so follow it there.
655
+ this.chat()?.focusInput();
630
656
  }
631
657
  catch (e) {
632
658
  if (!e)
@@ -653,6 +679,8 @@ class AgentChatComponent {
653
679
  if (index > -1) {
654
680
  this.messages.set(this.messages().slice(0, index - 1));
655
681
  this.onMessagesChange.emit(this.messages());
682
+ // The focused action button goes away with the message.
683
+ this.chat()?.focusInput();
656
684
  this.sendMessage({
657
685
  role: 'user',
658
686
  content: userMessage.content,
@@ -680,9 +708,11 @@ class AgentChatComponent {
680
708
  }
681
709
  }
682
710
  }
683
- /** Clears all messages and cancels any in-flight stream. */
711
+ /** Clears all messages and cancels any in-flight stream. Remembered tool approvals end with the conversation. */
684
712
  resetMessages() {
685
713
  this.cancel();
714
+ this.toolApprovalMemory.endSession(this._chatSessionId);
715
+ this._chatSessionId = this.toolApprovalMemory.newSessionId();
686
716
  this.messages.set([]);
687
717
  this.onMessagesChange.emit(this.messages());
688
718
  }
@@ -705,6 +735,8 @@ class AgentChatComponent {
705
735
  // Remove from the last user message onwards
706
736
  this.messages.set(msgs.slice(0, lastUserIndex));
707
737
  this.onMessagesChange.emit(this.messages());
738
+ // The focused action button goes away with the message.
739
+ this.chat()?.focusInput();
708
740
  }
709
741
  /**
710
742
  * Overrides the output of a specific tool result (in the current assistant message), including optionally indicating that an error occurred.
@@ -737,6 +769,207 @@ class AgentChatComponent {
737
769
  this._isLoading.set(false);
738
770
  }
739
771
  // Private methods go here:
772
+ /**
773
+ * Sends the current conversation to the agent and streams the next assistant message.
774
+ * @param includeGrounding Whether to inject the grounding context message before the last
775
+ * user message. Only applies when the conversation ends with a new user message.
776
+ */
777
+ async _dispatchToAgent(includeGrounding) {
778
+ this._isLoading.set(true);
779
+ this.agentRequestError.set(undefined); // Clear any previous stream errors
780
+ this.abortController = new AbortController();
781
+ this._clientToolOutputs = {};
782
+ // Prepare messages for AI service, optionally including grounding message
783
+ const pruneMessagesForAgent = this.pruneMessagesForAgent() || defaultPruneMessagesForAgent;
784
+ const messagesForAI = pruneMessagesForAgent(this.messages());
785
+ const groundingProvider = this.groundingContextProvider();
786
+ if (includeGrounding && groundingProvider) {
787
+ // Insert grounding message before the last user message
788
+ const groundingMsg = {
789
+ role: 'user',
790
+ content: groundingProvider()
791
+ };
792
+ messagesForAI.splice(messagesForAI.length - 1, 0, groundingMsg);
793
+ }
794
+ const currentAssistantMessage = {
795
+ role: 'assistant',
796
+ content: []
797
+ };
798
+ this.messages.set([...this.messages(), currentAssistantMessage]);
799
+ // Branch based on agent type: object agents use a single non-streaming request,
800
+ // text agents use SSE streaming
801
+ if (this.agentDefinition?.definition.type === 'object') {
802
+ await this._sendObjectMessage(currentAssistantMessage, messagesForAI);
803
+ }
804
+ else {
805
+ await this._sendStreamMessage(messagesForAI);
806
+ }
807
+ }
808
+ /**
809
+ * Records the user's decision for a tool call awaiting approval. If the user chose to remember
810
+ * the decision, it is persisted so future calls are approved without prompting. The conversation
811
+ * only resumes once every approval request in the message has a decision, since the agent
812
+ * rejects a resumed conversation with unanswered approval requests.
813
+ */
814
+ async _respondToToolApproval(part, decision) {
815
+ if (this.isLoadingAiResponse())
816
+ return;
817
+ if (decision.approved && decision.remember) {
818
+ if (decision.remember === 'all') {
819
+ this.toolApprovalMemory.rememberAllTools(this._chatSessionId);
820
+ }
821
+ else {
822
+ this.toolApprovalMemory.rememberTool(this._chatSessionId, part.toolName);
823
+ }
824
+ }
825
+ const recorded = this._recordApprovalDecisions(p => p.toolCallId === part.toolCallId ? decision.approved : undefined);
826
+ if (!recorded)
827
+ return;
828
+ // A newly remembered preference may also cover other approvals still pending in this message
829
+ this._autoApproveRememberedTools();
830
+ await this._resumeIfAllApprovalsDecided();
831
+ }
832
+ /**
833
+ * Maps the pending `tool-approval-requested` parts of the last assistant message through
834
+ * `decide`, converting each part that receives a decision into `tool-approval-responded`.
835
+ * New part/message instances are created so change detection picks the update up.
836
+ * @returns Whether at least one decision was recorded.
837
+ */
838
+ _recordApprovalDecisions(decide) {
839
+ const msgs = this.messages();
840
+ const lastMsg = msgs[msgs.length - 1];
841
+ if (lastMsg?.role !== 'assistant')
842
+ return false;
843
+ let recorded = false;
844
+ const newContent = lastMsg.content.map(p => {
845
+ if (p.type !== 'tool-approval-requested')
846
+ return p;
847
+ const approved = decide(p);
848
+ if (approved === undefined)
849
+ return p;
850
+ recorded = true;
851
+ return {
852
+ ...p,
853
+ type: 'tool-approval-responded',
854
+ approval: { ...p.approval, approvalId: p.approval?.approvalId ?? '', approved }
855
+ };
856
+ });
857
+ if (!recorded)
858
+ return false;
859
+ this.messages.set([...msgs.slice(0, -1), { ...lastMsg, content: newContent }]);
860
+ this.onMessagesChange.emit(this.messages());
861
+ return true;
862
+ }
863
+ /**
864
+ * Approves the pending approval requests for tools the user chose to approve automatically:
865
+ * tools remembered individually for this conversation, every tool when the session-wide YOLO
866
+ * mode ("always approve all tools") is enabled, and every tool when the global "approve all
867
+ * tools" user preference is set. Tools configured with `needsApproval` are never covered -
868
+ * they must be approved explicitly on every call.
869
+ * @returns Whether any request was auto-approved.
870
+ */
871
+ _autoApproveRememberedTools() {
872
+ return this._recordApprovalDecisions(part => {
873
+ if (this._requiresExplicitApproval(part.toolName)) {
874
+ return undefined;
875
+ }
876
+ if (this.toolApprovalMemory.isToolRemembered(this._chatSessionId, part.toolName)) {
877
+ return true;
878
+ }
879
+ if (this.toolApprovalMemory.isApproveAllToolsEnabled(this._chatSessionId) ||
880
+ this.globalToolApproval()) {
881
+ return true;
882
+ }
883
+ return undefined;
884
+ });
885
+ }
886
+ /**
887
+ * Whether the tool is configured with `needsApproval` (inline on its `tools` entry, falling
888
+ * back to the MCP-entry-level setting), meaning every call must be approved explicitly: such
889
+ * tools are never approved automatically, neither by remembered decisions nor by the
890
+ * session-wide or global "approve all tools" options.
891
+ *
892
+ * Without a client-side agent definition (agent given by name only) no configuration is
893
+ * visible, so this returns false.
894
+ */
895
+ _requiresExplicitApproval(toolName) {
896
+ return (this.agentDefinition?.definition.mcp ?? []).some(server => {
897
+ const entry = server.tools?.find(tool => (typeof tool === 'string' ? tool : tool.name) === toolName);
898
+ if (entry === undefined)
899
+ return false;
900
+ // The tool's own needsApproval decides when set; otherwise the MCP-entry-level one applies
901
+ if (typeof entry === 'object' && entry.needsApproval !== undefined) {
902
+ return entry.needsApproval;
903
+ }
904
+ return (server.needsApproval === true ||
905
+ (Array.isArray(server.needsApproval) && server.needsApproval.includes(toolName)));
906
+ });
907
+ }
908
+ /**
909
+ * Sets the global "approve all tools" option and persists it as a user preference. When
910
+ * enabled, pending approval requests (except tools configured with `needsApproval`) are
911
+ * approved immediately and the conversation resumes. Enabling it asks for confirmation first,
912
+ * since it applies to every chat until turned off again, not just the current conversation.
913
+ *
914
+ * If the confirmation is cancelled, the preference is left untouched. The mode selector reflects
915
+ * the current value through its `globalToolApproval()` binding, so no manual reset is required.
916
+ * @param approveAll `true` for the "Auto" mode, `false` for "Manual".
917
+ */
918
+ async setGlobalToolApproval(approveAll) {
919
+ if (approveAll === this.globalToolApproval()) {
920
+ return;
921
+ }
922
+ if (approveAll) {
923
+ try {
924
+ await this.modalService.confirm(gettext('Auto-approve all tools?'), this.translateService.instant(gettext('Tool calls will run without asking for approval, in this and every future chat, until you turn this off again. Tools requiring explicit approval will still ask.')), Status.WARNING, { cancel: gettext('Cancel'), ok: gettext('Auto-approve all tools') });
925
+ }
926
+ catch (e) {
927
+ if (!e) {
928
+ return; // User dismissed the dialog - leave the preference untouched
929
+ }
930
+ throw e; // Unexpected error, rethrow to propagate
931
+ }
932
+ }
933
+ this.globalToolApproval.set(approveAll);
934
+ if (approveAll && this._autoApproveRememberedTools()) {
935
+ void this._resumeIfAllApprovalsDecided();
936
+ }
937
+ try {
938
+ await this.userPreferences.set(GLOBAL_TOOL_APPROVAL_PREFERENCE_KEY, approveAll);
939
+ }
940
+ catch (e) {
941
+ // The mode is applied for the current session, but persistence failed - tell the user it
942
+ // will not carry over to future chats so they are not misled by the confirmation dialog.
943
+ console.error('Failed to save the tool approval preference:', e);
944
+ this.alertService.danger(gettext('Failed to save the tool approval mode.'));
945
+ }
946
+ }
947
+ /** Loads the persisted global "approve all tools" user preference. */
948
+ async _loadGlobalToolApproval() {
949
+ try {
950
+ const stored = await firstValueFrom(this.userPreferences.get(GLOBAL_TOOL_APPROVAL_PREFERENCE_KEY));
951
+ this.globalToolApproval.set(stored === true);
952
+ }
953
+ catch (e) {
954
+ // The chat still works without the preference - the user is simply prompted
955
+ console.error('Failed to load the tool approval preference:', e);
956
+ }
957
+ }
958
+ /**
959
+ * Resumes the conversation if the last assistant message stopped for tool approval and every
960
+ * request now has a decision. The agent then executes (or skips) the tools and continues.
961
+ */
962
+ async _resumeIfAllApprovalsDecided() {
963
+ const msgs = this.messages();
964
+ const lastMsg = msgs[msgs.length - 1];
965
+ if (lastMsg?.role !== 'assistant')
966
+ return;
967
+ const hasDecision = lastMsg.content.some(p => p.type === 'tool-approval-responded');
968
+ const stillPending = lastMsg.content.some(p => p.type === 'tool-approval-requested');
969
+ if (hasDecision && !stillPending) {
970
+ await this._dispatchToAgent(false);
971
+ }
972
+ }
740
973
  async initializeChat() {
741
974
  this._restoreChatHistory(this.initialChatHistory());
742
975
  if (this.skipHealthCheck()) {
@@ -955,6 +1188,11 @@ class AgentChatComponent {
955
1188
  // Should never happen, but worth knowing if it does (indicates bug in processAgentMessage)
956
1189
  console.error('AI response stream completed but message loading indicator was not reset');
957
1190
  }
1191
+ else if (this._autoApproveRememberedTools()) {
1192
+ // Tools the user chose to always approve are approved without prompting; if no other
1193
+ // approvals are pending this resumes the conversation immediately
1194
+ void this._resumeIfAllApprovalsDecided();
1195
+ }
958
1196
  },
959
1197
  error: async (error) => {
960
1198
  // Currently errors from the AIService are often a big JSON with no human-friendly message anywhere
@@ -1029,12 +1267,13 @@ class AgentChatComponent {
1029
1267
  }
1030
1268
  }
1031
1269
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: AgentChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1032
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.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 }, telemetry: { classPropertyName: "telemetry", publicName: "telemetry", 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]=\"chatConfig()?.showCumulativeUsage ? cumulativeUsage() : null\"\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() && telemetry()) {\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() && telemetry()) {\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 <!-- 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", "progressLabel", "message"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], 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 }); }
1270
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.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 }, telemetry: { classPropertyName: "telemetry", publicName: "telemetry", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onMessageFinish: "onMessageFinish", onToolResult: "onToolResult", onFeedback: "onFeedback", onMessagesChange: "onMessagesChange" }, queries: [{ propertyName: "chatActions", predicate: AiChatActionComponent, isSignal: true }], viewQueries: [{ propertyName: "assistantMessageComponents", predicate: AiChatAssistantMessageComponent, descendants: true, isSignal: true }, { propertyName: "chat", first: true, predicate: AiChatComponent, descendants: true, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Instantiates any projected action-bar items so they can be queried and forwarded to c8y-ai-chat.\n The items render nothing here (their content is exposed as a template), only inside the action bar. -->\n<ng-content select=\"c8y-ai-chat-action\"></ng-content>\n\n@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 <!-- Only the message is announced; the actions and links stay out of the live region. -->\n <span role=\"alert\">{{ 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 @let showGlobalToolApprovalToggle = chatConfig()?.showGlobalToolApprovalToggle !== false;\n @if (showGlobalToolApprovalToggle && globalToolApproval()) {\n <div\n class=\"alert alert-info m-b-0 max-width-unset\"\n role=\"status\"\n data-cy=\"chat-global-tool-approval-banner\"\n >\n <p>\n {{\n 'Auto-approve is on. Tool calls run without asking until you turn this off. Tools requiring explicit approval will still ask.'\n | translate\n }}\n </p>\n </div>\n }\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 [actions]=\"chatActions()\"\n [cumulativeUsage]=\"chatConfig()?.showCumulativeUsage ? cumulativeUsage() : null\"\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 onToolApprovalResponse: respondToToolApproval,\n canRememberToolApproval: canRememberToolApproval\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 @let usageLabel =\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 <!-- The tooltip is hover-only, so repeat the breakdown as a label. -->\n <span\n class=\"tag tag--info m-l-auto\"\n [attr.aria-label]=\"usageLabel\"\n tabindex=\"0\"\n [tooltip]=\"usageLabel\"\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() && telemetry()) {\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() && telemetry()) {\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 <!-- 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\n @if (showGlobalToolApprovalToggle) {\n <c8y-ai-chat-action placement=\"left\">\n <c8y-agent-chat-tool-approval-mode\n [globalToolApproval]=\"globalToolApproval()\"\n (modeChange)=\"setGlobalToolApproval($event)\"\n ></c8y-agent-chat-tool-approval-mode>\n </c8y-ai-chat-action>\n }\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", "actions", "config"], outputs: ["onMessage", "onCancel"] }, { kind: "component", type: AiChatActionComponent, selector: "c8y-ai-chat-action", inputs: ["placement"] }, { 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", "progressLabel", "message"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], 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$1.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: "component", type: AgentChatToolApprovalModeComponent, selector: "c8y-agent-chat-tool-approval-mode", inputs: ["globalToolApproval"], outputs: ["modeChange"] }, { 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 }); }
1033
1271
  }
1034
1272
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: AgentChatComponent, decorators: [{
1035
1273
  type: Component,
1036
1274
  args: [{ selector: 'c8y-agent-chat', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
1037
1275
  AiChatComponent,
1276
+ AiChatActionComponent,
1038
1277
  AiChatSuggestionComponent,
1039
1278
  AiChatMessageComponent,
1040
1279
  AiChatMessageActionComponent,
@@ -1049,9 +1288,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1049
1288
  GuideDocsComponent,
1050
1289
  GuideHrefDirective,
1051
1290
  TooltipModule,
1052
- NumberPipe
1053
- ], 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]=\"chatConfig()?.showCumulativeUsage ? cumulativeUsage() : null\"\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() && telemetry()) {\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() && telemetry()) {\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 <!-- 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"] }]
1054
- }], 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 }] }], telemetry: [{ type: i0.Input, args: [{ isSignal: true, alias: "telemetry", 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 }] }] } });
1291
+ NumberPipe,
1292
+ AgentChatToolApprovalModeComponent
1293
+ ], template: "<!-- Instantiates any projected action-bar items so they can be queried and forwarded to c8y-ai-chat.\n The items render nothing here (their content is exposed as a template), only inside the action bar. -->\n<ng-content select=\"c8y-ai-chat-action\"></ng-content>\n\n@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 <!-- Only the message is announced; the actions and links stay out of the live region. -->\n <span role=\"alert\">{{ 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 @let showGlobalToolApprovalToggle = chatConfig()?.showGlobalToolApprovalToggle !== false;\n @if (showGlobalToolApprovalToggle && globalToolApproval()) {\n <div\n class=\"alert alert-info m-b-0 max-width-unset\"\n role=\"status\"\n data-cy=\"chat-global-tool-approval-banner\"\n >\n <p>\n {{\n 'Auto-approve is on. Tool calls run without asking until you turn this off. Tools requiring explicit approval will still ask.'\n | translate\n }}\n </p>\n </div>\n }\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 [actions]=\"chatActions()\"\n [cumulativeUsage]=\"chatConfig()?.showCumulativeUsage ? cumulativeUsage() : null\"\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 onToolApprovalResponse: respondToToolApproval,\n canRememberToolApproval: canRememberToolApproval\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 @let usageLabel =\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 <!-- The tooltip is hover-only, so repeat the breakdown as a label. -->\n <span\n class=\"tag tag--info m-l-auto\"\n [attr.aria-label]=\"usageLabel\"\n tabindex=\"0\"\n [tooltip]=\"usageLabel\"\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() && telemetry()) {\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() && telemetry()) {\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 <!-- 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\n @if (showGlobalToolApprovalToggle) {\n <c8y-ai-chat-action placement=\"left\">\n <c8y-agent-chat-tool-approval-mode\n [globalToolApproval]=\"globalToolApproval()\"\n (modeChange)=\"setGlobalToolApproval($event)\"\n ></c8y-agent-chat-tool-approval-mode>\n </c8y-ai-chat-action>\n }\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"] }]
1294
+ }], ctorParameters: () => [], 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 }] }], chatActions: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => AiChatActionComponent), { isSignal: true }] }], 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 }] }], telemetry: [{ type: i0.Input, args: [{ isSignal: true, alias: "telemetry", 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 }] }], chat: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AiChatComponent), { isSignal: true }] }] } });
1055
1295
 
1056
1296
  class WidgetAiChatSectionComponent {
1057
1297
  constructor() {
@@ -1164,5 +1404,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1164
1404
  * Generated bundle index. Do not edit.
1165
1405
  */
1166
1406
 
1167
- export { AgentChatComponent, WidgetAiChatSectionComponent };
1407
+ export { AgentChatComponent, AgentChatToolApprovalModeComponent, WidgetAiChatSectionComponent };
1168
1408
  //# sourceMappingURL=c8y-ngx-components-ai-agent-chat.mjs.map