ai/ai-chat/ai-chat.component.ts

Description

An interactive chat interface component for AI-powered conversations. Displays messages in a conversation format with support for loading states, custom configuration, and markdown formatting.

Example

Metadata

Relationships

Depends on

<div
  class="d-col fit-h fit-w flex-grow min-height-0"
  [attr.aria-label]="config.headline | translate"
  role="region"
>
  @if (messages().length > 0) {
    <div
      [attr.aria-label]="'Chat conversation' | translate"
      aria-live="polite"
      aria-atomic="false"
      role="log"
      #chatScrollContainer
      [ngClass]="{
        'inner-scroll': true,
        'd-col-reverse': true,
        'scrollbar-only-on-hover': config.scrollbarOnlyOnHover,
        'flex-grow': true,
        'min-height-0': true,
        'bg-level-0': true
      }"
    >
      <div class="d-col p-l-16 p-r-16">
        <ng-content select="[slot='before-messages']"></ng-content>
        <ng-content select="c8y-ai-chat-message"></ng-content>
        <ng-content select="[slot='after-messages']"></ng-content>
      </div>
    </div>
  }
  <div
    [ngClass]="{
      'd-col fit-h': messages().length === 0
    }"
  >
    @if (messages().length === 0) {
      <div
        class="p-24 d-col fit-h inner-scroll"
        aria-live="polite"
        role="status"
        [ngClass]="{
          'j-c-start': config.welcomePosition === 'top',
          'j-c-center': config.welcomePosition === 'center',
          'j-c-end': config.welcomePosition === 'bottom'
        }"
      >
        <h4 class="m-b-16 text-medium">{{ config.headline | translate }}</h4>
        @if (config.title.length > 0) {
          <p class="p-b-8 text-balance">{{ config.title | translate }}</p>
        }
        <div class="text-balance chat-message min-height-0">
          <div
            class="text-muted"
            [innerHTML]="config.welcomeText | translate | markdownToHtml | async"
          ></div>
          @if (welcomeTemplate) {
            <ng-container *ngTemplateOutlet="welcomeTemplate"></ng-container>
          }
        </div>
      </div>
    }
    <div
      class="chat-input"
      [class.bg-level-1]="config.appearance !== 'flat'"
    >
      <!-- For simple cases allow ng-content projection; however this doesn't seem to work with dynamic 
       suggestion lists from a signal (e.g. returned from the AI) so also support `suggestionsTemplate` for that. 
      -->
      @if (!isLoading) {
        <div
          [class]="
            'd-flex inner-scroll gap-8 p-l-16 p-r-16 p-b-8 ' +
            (config.suggestionsLayout === 'vertical' ? 'flex-wrap' : 'a-i-center')
          "
        >
          <ng-content select="c8y-ai-chat-suggestion"></ng-content>

          @if (suggestionsTemplate) {
            <ng-container *ngTemplateOutlet="suggestionsTemplate"></ng-container>
          }
        </div>
      }
      <div class="chat-input-group">
        <label
          class="sr-only"
          for="chat-input-{{ componentId }}"
        >
          {{ config.placeholder | translate }}
        </label>
        <textarea
          class="form-control no-resize"
          [class.text-muted]="isLoading"
          style="max-height: 200px !important"
          [attr.aria-label]="config.placeholder | translate"
          id="chat-input-{{ componentId }}"
          placeholder="{{ config.placeholder | translate }}"
          [attr.aria-describedby]="config.disclaimerText ? 'chat-disclaimer-' + componentId : null"
          [attr.aria-busy]="isLoading"
          [(ngModel)]="prompt"
          (keydown.enter)="!isLoading && sendMessage($event)"
          [disabled]="disabled"
          c8y-textarea-autoresize
        ></textarea>
        <div class="chat-input-group-btn">
          @if (!isLoading) {
            <button
              class="btn btn-dot"
              [attr.title]="config.sendButtonText || '' | translate"
              [attr.aria-label]="config.sendButtonText || '' | translate"
              type="button"
              (click)="sendMessage($event)"
              [disabled]="disabled || prompt.trim().length === 0"
            >
              <i [c8yIcon]="config.userInterfaceIcons.send || 'arrow-circle-right'"></i>
            </button>
          } @else {
            <button
              class="btn btn-dot btn-dot--danger"
              [attr.title]="config.cancelButtonText || '' | translate"
              [attr.aria-label]="config.cancelButtonText || '' | translate"
              type="button"
              (click)="cancel()"
            >
              <i [c8yIcon]="config.userInterfaceIcons.cancel || 'stop'"></i>
            </button>
          }
        </div>
      </div>
      <div class="d-flex">
        @if (config.disclaimerText) {
          <div
            class="text-muted m-b-8 text-10 p-l-16"
            id="chat-disclaimer-{{ componentId }}"
            role="note"
          >
            {{ config.disclaimerText | translate }}
          </div>
        }
        @if (cumulativeUsage) {
          <span
            class="tag tag--info m-l-auto"
            [tooltip]="
              'Input tokens: {{ input }} \n Output tokens: {{ output }}'
                | translate
                  : {
                      input: cumulativeUsage?.inputTokens || 0 | c8yNumber: 'floor' : '1.0-0',
                      output: cumulativeUsage?.outputTokens || 0 | c8yNumber: 'floor' : '1.0-0'
                    }
            "
          >
            {{ 'Tokens used: {{total}}' | translate: { total: (cumulativeUsage?.totalTokens || 0) |
            c8yNumber : 'floor':'1.0-0' } }}
          </span>
        }
      </div>
    </div>
  </div>
</div>

results matching ""

    No results matching ""