@let _tool = tool();
@let isApprovalRequested = _tool.type === 'tool-approval-requested';
@let showDetails =
showDefaultToolDetails() || toolDetailsComponent() || _tool.error || isApprovalRequested;
@let isToolExpanded = expanded();
<fieldset
class="c8y-fieldset p-b-4 ai-tool-call__fieldset"
[class.fit-w]="isApprovalRequested"
[attr.aria-label]="getFieldsetLabel(_tool)"
>
@if (isApprovalRequested) {
<legend>
<i
class="icon-16 text-warning m-r-4"
[c8yIcon]="'exclamation-triangle'"
aria-hidden="true"
></i>
{{ getFieldsetLabel(_tool) }}
</legend>
}
<button
class="btn-clean ai-tool-call__btn"
[attr.aria-expanded]="showDetails ? isToolExpanded : null"
[attr.aria-controls]="showDetails ? 'tool-call-' + _tool.toolCallId : null"
type="button"
[attr.data-cy]="'tool-call-' + _tool.toolName"
[disabled]="!showDetails || null"
(click)="toggleExpanded()"
>
@if (_tool.type === 'tool-approval-responded') {
<i
class="icon-16 m-r-4"
[class.text-success]="_tool.approval?.approved"
[class.text-muted]="!_tool.approval?.approved"
[c8yIcon]="_tool.approval?.approved ? 'check' : 'ban'"
aria-hidden="true"
></i>
} @else if (isExecuting()) {
<i
class="icon-spin icon-16 text-primary m-r-4"
c8yIcon="spinner"
aria-hidden="true"
></i>
} @else {
<!-- Treat it as an error if a tool still thinks its executing when the message containing it is not -->
@if (_tool.error || isExecuting()) {
<i
class="icon-16 text-danger m-r-4"
[c8yIcon]="'exclamation-circle'"
aria-hidden="true"
></i>
} @else if (!isApprovalRequested) {
<i
class="icon-16 text-success m-r-4"
[c8yIcon]="'check'"
aria-hidden="true"
></i>
}
}
@if (showDetails) {
<i
class="m-r-4"
[c8yIcon]="isToolExpanded ? 'expand-arrow' : 'forward'"
aria-hidden="true"
></i>
}
<span
[class.small]="!isApprovalRequested"
[class.flex-grow]="isApprovalRequested"
>
{{ getToolLabel(_tool) | translate }}
</span>
</button>
<!-- If this is an artifact tool, render it - but do so lazily only on first open,
since these components could be heavyweight (e.g. Monaco)
-->
@if (showDetails && (isToolExpanded || everExpanded())) {
<div
class="collapse tool-details m-t-8 p-8"
[attr.aria-label]="'Tool details for message latest tool: ' + _tool.toolName"
role="region"
[collapse]="!isToolExpanded"
[id]="'tool-call-' + _tool.toolCallId"
[isAnimated]="true"
data-cy="ai-tool-component"
>
@if (toolDetailsComponent()) {
<ng-container
[ngComponentOutlet]="toolDetailsComponent()"
[ngComponentOutletInputs]="{ tool: _tool }"
></ng-container>
} @else {
@let noneLabel = '(no data)' | translate;
<pre
class="fit-w small text-pre-wrap text-break-word"
[attr.data-label]="'Tool input' | translate"
>{{ _tool.input || noneLabel | json }}</pre
>
@if (_tool.type === 'tool-result') {
@if (
typeof _tool.output === 'string' || _tool.output === undefined || _tool.output === null
) {
<pre
class="fit-w small text-pre-wrap text-break-word"
[attr.data-label]="'Tool output' | translate"
>{{ _tool.output || noneLabel }}</pre
>
} @else {
<pre class="fit-w small text-pre-wrap text-break-word">{{ _tool.output | json }}</pre>
}
}
}
</div>
}
@if (isApprovalRequested && approvalEnabled()) {
<div
class="btn-toolbar m-l-0 m-t-8 m-b-8"
[attr.aria-label]="'Tool approval actions' | translate"
role="toolbar"
>
<button
class="btn btn-danger btn-sm"
type="button"
[attr.data-cy]="'tool-deny-' + _tool.toolName"
(click)="approvalResponse.emit({ approved: false })"
>
<i
class="m-r-4"
[c8yIcon]="'block'"
aria-hidden="true"
></i>
{{ 'Deny' | translate }}
</button>
@if (!rememberApprovalEnabled()) {
<!-- Tools requiring explicit approval on every call get no "always approve" options -->
<button
class="btn btn-default btn-sm"
type="button"
[attr.data-cy]="'tool-approve-' + _tool.toolName"
(click)="approvalResponse.emit({ approved: true })"
>
{{ 'Approve' | translate }}
</button>
} @else {
<div
class="btn-group"
role="group"
>
<button
class="btn btn-default btn-sm"
type="button"
[attr.data-cy]="'tool-approve-' + _tool.toolName"
(click)="approvalResponse.emit({ approved: true })"
>
{{ 'Approve' | translate }}
</button>
<div
class="btn-group"
dropdown
[container]="'body'"
c8yDropdownFocusTrap
#approveDropdown="bs-dropdown"
>
<button
class="btn btn-default btn-sm dropdown-toggle"
aria-label="{{ 'More approval options' | translate }}"
aria-haspopup="true"
[attr.aria-expanded]="approveDropdown.isOpen"
type="button"
dropdownToggle
[attr.data-cy]="'tool-approve-options-' + _tool.toolName"
>
<i
c8yIcon="caret-down"
aria-hidden="true"
></i>
</button>
<ul
class="dropdown-menu"
role="menu"
*dropdownMenu
>
<li>
<button
class="a-i-start"
title="{{ 'Always approve this tool' | translate }}"
type="button"
[attr.data-cy]="'tool-approve-always-' + _tool.toolName"
(click)="approvalResponse.emit({ approved: true, remember: 'tool' })"
>
<i
class="icon-16"
c8yIcon="ok"
aria-hidden="true"
></i>
<span>
{{ 'Always approve this tool' | translate }}
<small class="d-block text-muted">
{{ 'For this conversation only' | translate }}
</small>
</span>
</button>
</li>
<li>
<button
class="a-i-start"
title="{{ 'Always approve all tools' | translate }}"
type="button"
[attr.data-cy]="'tool-approve-always-all-' + _tool.toolName"
(click)="approvalResponse.emit({ approved: true, remember: 'all' })"
>
<i
class="icon-16"
c8yIcon="approval"
aria-hidden="true"
></i>
<span>
{{ 'Always approve all tools' | translate }}
<small class="d-block text-muted">
{{
'For this conversation only, unlike the global auto-approve mode'
| translate
}}
</small>
</span>
</button>
</li>
</ul>
</div>
</div>
}
</div>
}
</fieldset>