ai/ai-chat/ai-chat-assistant-part.component.ts
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.
| changeDetection | ChangeDetectionStrategy.OnPush |
||||||||||||||||||||
| selector | c8y-ai-chat-assistant-part |
||||||||||||||||||||
| standalone | true |
||||||||||||||||||||
| imports |
C8yTranslatePipe
CollapseModule
MarkdownToHtmlPipe
JsonToMarkdownPipe
|
||||||||||||||||||||
| templateVariables |
|
||||||||||||||||||||
| templateUrl | ./ai-chat-assistant-part.component.html |
NgComponentOutlet
C8yTranslateDirective
C8yTranslatePipe
IconDirective
CollapseModule
MarkdownToHtmlPipe
AsyncPipe
AiChatToolCallComponent
JsonToMarkdownPipe
@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>
}
}