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

Implements

OnInit OnDestroy OnChanges

Metadata

Relationships

@let agentHealthErrorMsg = _agentHealthError();
@if (agentHealthErrorMsg) {
  @if (isLoadingAiResponse()) {
    <c8y-loading class="m-auto"></c8y-loading>
  } @else {
    <c8y-ui-empty-state
      class="m-auto"
      [icon]="'settings'"
      [title]="'AI agent is not available.' | translate"
    >
      <span>{{ agentHealthErrorMsg }}</span>

      @if (canCreate) {
        <div class="text-center m-t-16 m-b-16">
          <button
            class="btn btn-primary"
            (click)="createAgent()"
          >
            {{ 'Create agent' | translate }}
          </button>
        </div>
      } @else {
        <p
          class="text-pre-wrap m-t-8"
          data-cy="agent-health-detailed-messages"
        >
          <small>{{ agentHealthDetailedMessages() }}</small>
        </p>
      }

      <p c8y-guide-docs>
        <small
          translate
          ngNonBindable
        >
          Find out more in the
          <a c8y-guide-href="/docs/ai">user documentation</a>.
        </small>
      </p>
    </c8y-ui-empty-state>
  }
}

@if (!agentHealthErrorMsg) {
  <c8y-ai-chat
    (onMessage)="sendMessage($event)"
    [isLoading]="isLoadingAiResponse()"
    (onCancel)="cancel()"
    [config]="chatConfig() ?? {}"
    [prompt]="prompt"
    [suggestionsTemplate]="suggestionsRef"
    [welcomeTemplate]="welcomeTemplate()"
    [cumulativeUsage]="chatConfig()?.showCumulativeUsage ? cumulativeUsage() : null"
  >
    @let messages$ = messages();
    @for (message of messages$; track $index; let i = $index) {
      <c8y-ai-chat-message [message]="message">
        @if (message.role !== 'user' && model) {
          <!-- Visually hidden label included when users copy-paste from the chat, e.g. to report issues. It's quite helpful to know the model. -->
          <span
            class="hidden-copy-label"
            aria-hidden="true"
          >
            {{ `(Using model: ${model})` }}</span
          >
        }

        @if (message.role === 'user') {
          <div
            class="message-content"
            data-cy="user-message-content"
            [innerHTML]="message.content | markdownToHtml | async"
          ></div>
        } @else if (
          message.role === 'assistant' && isLoadingAiResponse() && i === messages$.length - 1
        ) {
          <div>
            <!-- Last assistant message uses reactive computed context -->
            <ng-container
              [ngComponentOutlet]="assistantMessageComponent()"
              [ngComponentOutletInputs]="{ assistantMessageContext: lastAssistantMessageContext() }"
            ></ng-container>
          </div>
        } @else {
          <ng-container
            [ngComponentOutlet]="assistantMessageComponent()"
            [ngComponentOutletInputs]="{
              assistantMessageContext: {
                message: message,
                config: assistantMessageDisplayConfig(),
                isMessageLoading: isLoadingAiResponse() && i === messages$.length - 1,
                messageDisplayIndex: messages$.length - 1 - i
              }
            }"
          ></ng-container>
        }

        @let isLastMessage = i === messages$.length - 1;

        @if (message.role === 'user') {
          <c8y-ai-chat-message-action
            icon="pencil"
            [tooltip]="'Edit and resend this message' | translate"
            [disabled]="isLoadingAiResponse()"
            (click)="reprompt(message)"
          ></c8y-ai-chat-message-action>
        }

        @if (
          message.role === 'assistant' &&
          i > 0 &&
          chatConfig()?.showUsagePerMessage &&
          message.usage
        ) {
          <c8y-ai-chat-message-action [custom]="true">
            <span
              class="tag tag--info m-l-auto"
              [tooltip]="
                'Input tokens: {{ input }} \nOutput tokens: {{ output }}'
                  | translate
                    : {
                        input: message.usage.inputTokens || 0 | c8yNumber: 'floor' : '1.0-0',
                        output: message.usage.outputTokens || 0 | c8yNumber: 'floor' : '1.0-0'
                      }
              "
            >
              {{ 'Tokens used: {{total}}' | translate: { total: message.usage.totalTokens || 0 |
              c8yNumber: 'floor' : '1.0-0' } }}
            </span>
          </c8y-ai-chat-message-action>
        }

        @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {
          <c8y-ai-chat-message-action
            icon="thumbs-up"
            [tooltip]="'This is useful' | translate"
            [disabled]="isLoadingAiResponse()"
            (click)="rate(message, true)"
          ></c8y-ai-chat-message-action>
        }

        @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {
          <c8y-ai-chat-message-action
            icon="thumbs-down"
            [tooltip]="'This is not useful' | translate"
            [disabled]="isLoadingAiResponse()"
            (click)="rate(message, false)"
          ></c8y-ai-chat-message-action>
        }

        @if (
          message.role === 'assistant' &&
          !isLoadingAiResponse() &&
          chatConfig()?.showDeleteAction &&
          isLastMessage
        ) {
          <c8y-ai-chat-message-action
            icon="delete-bin"
            [tooltip]="'Delete last exchange' | translate"
            [disabled]="isLoadingAiResponse()"
            (click)="deleteLastExchange()"
          ></c8y-ai-chat-message-action>
        }

        <!-- Only allow regenerating the last message, otherwise we could be deleting a lot of useful message history -->
        @if (message.role === 'assistant' && i > 0 && isLastMessage && !isLoadingAiResponse()) {
          <c8y-ai-chat-message-action
            icon="refresh"
            [tooltip]="'Regenerate this response' | translate"
            [disabled]="isLoadingAiResponse()"
            (click)="reload(message)"
          ></c8y-ai-chat-message-action>
        }
      </c8y-ai-chat-message>
    }

    @let agentErrorMsg = agentRequestError();
    @if (agentErrorMsg) {
      <c8y-ai-chat-message [message]="{ role: 'assistant', content: agentErrorMsg }">
        <div
          class="alert alert-danger d-flex a-i-center gap-8"
          role="alert"
        >
          <!-- Since errors come from the backend the only translation is for the fallback error message supplied by the UI. -->
          <div class="flex-grow text-pre-wrap">{{ agentErrorMsg | translate }}</div>
        </div>
      </c8y-ai-chat-message>
    }

    <ng-template #suggestionsRef>
      @if (!isLoadingAiResponse()) {
        <!-- As soon as we have any suggestions (even empty) these take priority over what we restored from history -->
        @let activeSuggestions = suggestions() === undefined ? _restoredSuggestions : suggestions();
        @for (suggestion of activeSuggestions; track $index) {
          <c8y-ai-chat-suggestion
            [icon]="suggestion.icon || 'c8y-bulb'"
            [useAiButtons]="true"
            [prompt]="suggestion.prompt"
            [label]="suggestion.label ?? suggestion.prompt"
            (suggestionClicked)="sendMessage($event)"
            [disabled]="isLoadingAiResponse()"
          ></c8y-ai-chat-suggestion>
        }
      }
    </ng-template>
  </c8y-ai-chat>
}

results matching ""

    No results matching ""