@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>
}