ai/ai-chat/ai-chat-assistant-part.component.ts

Description

This renders a part of an assistant message. Currently only ToolCallPart is supported, but later we can expand this to deal with all part types.

Example

Metadata

Relationships

Depends on

@let ctx = assistantMessageContext();
@let _config = ctx.config;
@let toolCallConfig = _config.toolCallConfig || {};
@let _part = part();

@if (_part.type === 'text') {
  <p
    [class.text-muted]="!displayAsPartOfMainAnswer()"
    [innerHTML]="_part.text | markdownToHtml | async"
  ></p>
} @else if (_part.type === 'reasoning') {
  @let reasoningExpanded = expanded();

  <fieldset
    class="c8y-fieldset p-b-4 ai-tool-call__fieldset"
    data-cy="ai-reasoning"
  >
    <button
      class="btn-clean ai-tool-call__btn"
      aria-label="{{ 'Toggle display of the reasoning section' | translate }}"
      [attr.aria-expanded]="reasoningExpanded"
      [attr.aria-controls]="'reasoning-content-message' + ctx.messageDisplayIndex"
      type="button"
      (click)="toggleExpanded()"
    >
      <i
        class="m-r-4"
        [c8yIcon]="'ai-sparkles'"
      ></i>
      @let hideReasoning = 'Hide reasoning' | translate;
      @let showReasoning = 'Show reasoning' | translate;
      <span class="small">{{ reasoningExpanded ? hideReasoning : showReasoning }}</span>
      <i
        class="m-l-4 icon-12 text-muted"
        [c8yIcon]="reasoningExpanded ? 'collapse-arrow' : 'expand-arrow'"
      ></i>
    </button>

    @let ariaLabel =
      'Reasoning for message number {{ number }}' | translate: { number: ctx.messageDisplayIndex };
    <div
      class="collapse reasoning-content"
      [attr.aria-label]="ariaLabel"
      role="region"
      [collapse]="!reasoningExpanded"
      [id]="'reasoning-content-message' + ctx.messageDisplayIndex"
      [isAnimated]="true"
      data-cy="ai-reasoning-content"
    >
      <div
        class="m-t-8 p-b-4 text-muted"
        [innerHTML]="_part.text | markdownToHtml | async"
      ></div>
    </div>
  </fieldset>
} @else if (_part.type === 'object') {
  <p
    [class.text-muted]="!displayAsPartOfMainAnswer()"
    [innerHTML]="_part.jsonContent | jsonToMarkdown | markdownToHtml | async"
    data-cy="ai-object-content"
  ></p>
} @else if (_part.type === 'step-start') {
  <!-- Visually hidden label included when users copy-paste from the chat, e.g. to report issues -->
  <span
    class="ai-step-start hidden-copy-label"
    aria-hidden="true"
    data-cy="ai-step-start-separator"
    translate
    >--- (step separator) ---</span
  >
} @else if (_part.type.startsWith('tool')) {
  @let tool = $any(_part);
  @let toolDisplay = toolCallConfig[tool.toolName];

  <!-- Visually hidden label included when users copy-paste from the chat, e.g. to report issues -->
  <span
    class="hidden-copy-label"
    aria-hidden="true"
    translate
    [translateParams]="{ toolName: tool.toolName }"
    ngNonBindable
    >--- Tool call: {{ toolName }} ---</span
  >

  @if (toolDisplay?.component) {
    <div data-cy="ai-tool-with-custom-component">
      <ng-container
        [ngComponentOutlet]="toolDisplay.component"
        [ngComponentOutletInputs]="{
          tool: tool,
          ctx: ctx
        }"
      ></ng-container>
    </div>
  } @else if (!toolDisplay?.isHidden) {
    <!-- Don't allow expanding while tool call is in progress - it's unlikely to have anything worth rendering,
          and complicates implementation for the client to have to check for the isExecuting case.
        -->
    <c8y-ai-chat-tool-call
      [tool]="tool"
      [isExecuting]="tool.type !== 'tool-result' && ctx.isMessageLoading"
      [toolDetailsComponent]="_config.toolDetailsComponent?.(tool)"
      [showDefaultToolDetails]="_config.showDefaultToolDetails === 'all'"
      [executingLabel]="toolDisplay?.executingLabel"
      [completedLabel]="toolDisplay?.completedLabel"
      [labelProvider]="toolDisplay?.labelProvider"
    ></c8y-ai-chat-tool-call>
  }
}

results matching ""

    No results matching ""