@acorex-platform/module-conversation-client 0.0.38 → 0.0.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/{acorex-platform-module-conversation-client-acorex-platform-module-conversation-client-XTzQQhii.mjs → acorex-platform-module-conversation-client-acorex-platform-module-conversation-client-DUR2oyfP.mjs} +9727 -8104
- package/fesm2022/acorex-platform-module-conversation-client-acorex-platform-module-conversation-client-DUR2oyfP.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-conversation-client-after-message-sent.command-BMGagKtP.mjs → acorex-platform-module-conversation-client-after-message-sent.command-qTWATpgC.mjs} +2 -2
- package/fesm2022/{acorex-platform-module-conversation-client-after-message-sent.command-BMGagKtP.mjs.map → acorex-platform-module-conversation-client-after-message-sent.command-qTWATpgC.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-module-conversation-client-assist-delegated-agent-detail-popup.component-D1SDsHvl.mjs → acorex-platform-module-conversation-client-assist-delegated-agent-detail-popup.component-DQWhOmBE.mjs} +8 -7
- package/fesm2022/{acorex-platform-module-conversation-client-assist-delegated-agent-detail-popup.component-D1SDsHvl.mjs.map → acorex-platform-module-conversation-client-assist-delegated-agent-detail-popup.component-DQWhOmBE.mjs.map} +1 -1
- package/fesm2022/acorex-platform-module-conversation-client-assist-message-renderer.component-DyVepuaW.mjs +1756 -0
- package/fesm2022/acorex-platform-module-conversation-client-assist-message-renderer.component-DyVepuaW.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-conversation-client-assist-tool-detail-popup.component-D8na_1QJ.mjs +120 -0
- package/fesm2022/{acorex-platform-module-conversation-client-assist-tool-detail-popup.component-BV1YH96m.mjs.map → acorex-platform-module-conversation-client-assist-tool-detail-popup.component-D8na_1QJ.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-module-conversation-client-chat-call-session-page.component-nE7xP4bF.mjs → acorex-platform-module-conversation-client-chat-call-session-page.component-BeS4Duip.mjs} +747 -25
- package/fesm2022/acorex-platform-module-conversation-client-chat-call-session-page.component-BeS4Duip.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-conversation-client-chat-call-start-popup.component-DXkqkS50.mjs → acorex-platform-module-conversation-client-chat-call-start-popup.component-BbEhkLLh.mjs} +4 -4
- package/fesm2022/{acorex-platform-module-conversation-client-chat-call-start-popup.component-DXkqkS50.mjs.map → acorex-platform-module-conversation-client-chat-call-start-popup.component-BbEhkLLh.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-module-conversation-client-chat-conversation-info-media-view.component-SYCtAqcV.mjs → acorex-platform-module-conversation-client-chat-conversation-info-media-view.component-Bnbj0e6M.mjs} +3 -3
- package/fesm2022/{acorex-platform-module-conversation-client-chat-conversation-info-media-view.component-SYCtAqcV.mjs.map → acorex-platform-module-conversation-client-chat-conversation-info-media-view.component-Bnbj0e6M.mjs.map} +1 -1
- package/fesm2022/acorex-platform-module-conversation-client-chat-conversation-info-panel.component-bwT1-lAn.mjs +497 -0
- package/fesm2022/acorex-platform-module-conversation-client-chat-conversation-info-panel.component-bwT1-lAn.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-conversation-client-comments-page.component-Dv_x3ztZ.mjs → acorex-platform-module-conversation-client-comments-page.component-BxUz9jTX.mjs} +2 -2
- package/fesm2022/{acorex-platform-module-conversation-client-comments-page.component-Dv_x3ztZ.mjs.map → acorex-platform-module-conversation-client-comments-page.component-BxUz9jTX.mjs.map} +1 -1
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entities-channel-audience.mjs +277 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entities-channel-audience.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entities-channel-post.mjs +530 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entities-channel-post.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entities-channel-receipt.mjs +206 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entities-channel-receipt.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entities-message.mjs +65 -17
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entities-message.mjs.map +1 -1
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entities-room.mjs +231 -59
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entities-room.mjs.map +1 -1
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-acorex-platform-module-conversation-client-contracts-entity-catalog-CV_sCYGf.mjs +87 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-acorex-platform-module-conversation-client-contracts-entity-catalog-CV_sCYGf.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-channel-audience.entity-DyaUcESJ.mjs +213 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-channel-audience.entity-DyaUcESJ.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-channel-post.entity-CbBcChEt.mjs +466 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-channel-post.entity-CbBcChEt.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-channel-receipt.entity-DtRFj8zV.mjs +142 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-channel-receipt.entity-DtRFj8zV.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-conversation-client-contracts-entity-catalog-message.entity-Dz7cO1P4.mjs → acorex-platform-module-conversation-client-contracts-entity-catalog-message.entity-CJk1wEvA.mjs} +35 -19
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-message.entity-CJk1wEvA.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-room.entity-Ctt7qzfF.mjs +403 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-room.entity-Ctt7qzfF.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog.mjs +1 -1
- package/fesm2022/acorex-platform-module-conversation-client-contracts.mjs +846 -4
- package/fesm2022/acorex-platform-module-conversation-client-contracts.mjs.map +1 -1
- package/fesm2022/acorex-platform-module-conversation-client-local-entities.mjs +3 -0
- package/fesm2022/acorex-platform-module-conversation-client-local-entities.mjs.map +1 -1
- package/fesm2022/{acorex-platform-module-conversation-client-mark-conversation-unread.command-8NoSuUwe.mjs → acorex-platform-module-conversation-client-mark-conversation-unread.command-juoWxu7I.mjs} +2 -2
- package/fesm2022/{acorex-platform-module-conversation-client-mark-conversation-unread.command-8NoSuUwe.mjs.map → acorex-platform-module-conversation-client-mark-conversation-unread.command-juoWxu7I.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-module-conversation-client-open-chat.command-BgJ0Z36e.mjs → acorex-platform-module-conversation-client-open-chat.command-ridcdmj7.mjs} +2 -2
- package/fesm2022/{acorex-platform-module-conversation-client-open-chat.command-BgJ0Z36e.mjs.map → acorex-platform-module-conversation-client-open-chat.command-ridcdmj7.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-module-conversation-client-retry-assist-response.command-lcNz4Hpu.mjs → acorex-platform-module-conversation-client-retry-assist-response.command-BsYxaH3g.mjs} +2 -2
- package/fesm2022/{acorex-platform-module-conversation-client-retry-assist-response.command-lcNz4Hpu.mjs.map → acorex-platform-module-conversation-client-retry-assist-response.command-BsYxaH3g.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-module-conversation-client-sync-read-state.command-DxccFsD9.mjs → acorex-platform-module-conversation-client-sync-read-state.command-DsCyIdEk.mjs} +7 -2
- package/fesm2022/acorex-platform-module-conversation-client-sync-read-state.command-DsCyIdEk.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-conversation-client.mjs +1 -1
- package/package.json +14 -2
- package/types/acorex-platform-module-conversation-client-contracts-entities-channel-audience.d.ts +5 -0
- package/types/acorex-platform-module-conversation-client-contracts-entities-channel-post.d.ts +5 -0
- package/types/acorex-platform-module-conversation-client-contracts-entities-channel-receipt.d.ts +5 -0
- package/types/acorex-platform-module-conversation-client-contracts.d.ts +432 -2
- package/types/acorex-platform-module-conversation-client.d.ts +676 -161
- package/fesm2022/acorex-platform-module-conversation-client-acorex-platform-module-conversation-client-XTzQQhii.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-conversation-client-assist-tool-detail-popup.component-BV1YH96m.mjs +0 -120
- package/fesm2022/acorex-platform-module-conversation-client-chat-call-session-page.component-nE7xP4bF.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-conversation-client-chat-conversation-info-panel.component-51PtojIM.mjs +0 -482
- package/fesm2022/acorex-platform-module-conversation-client-chat-conversation-info-panel.component-51PtojIM.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-acorex-platform-module-conversation-client-contracts-entity-catalog-Cy3LwYMZ.mjs +0 -52
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-acorex-platform-module-conversation-client-contracts-entity-catalog-Cy3LwYMZ.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-message.entity-Dz7cO1P4.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-room.entity-B9txvc3y.mjs +0 -263
- package/fesm2022/acorex-platform-module-conversation-client-contracts-entity-catalog-room.entity-B9txvc3y.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-conversation-client-sync-read-state.command-DxccFsD9.mjs.map +0 -1
|
@@ -0,0 +1,1756 @@
|
|
|
1
|
+
import { AXPMarkdownViewerComponent, AXPMarkdownTemplateDirective } from '@acorex-platform/framework-client/layout/components';
|
|
2
|
+
import { AXPEntityCardFieldsResolver, AXPEntityCommandTriggerViewModel, AXPEntityCardComponent, AXPEntityCardSkeletonComponent, AXPEntityService } from '@acorex-platform/framework-client/layout/entity';
|
|
3
|
+
import { axpAiParseSupervisorAgentToolName, AI_CHAT_GENERATED_SPEECH_REF_TYPE, AI_CHAT_GENERATED_IMAGE_REF_TYPE, axpAiChatMessageGetText, AXPAiAssistChatModelCatalogService, axpAiChatMessageIsDelegatedReflectionExcluded, axpAiDelegatedAgentPromptPreview, axpAiChatToolOrAgentResultBodyJson, axpAiDelegatedAgentResultSegments, axpAiDelegatedAgentOutcomeResponsesPlainText } from '@acorex-platform/module-ai-management-client';
|
|
4
|
+
import * as i1$1 from '@acorex/cdk/accordion';
|
|
5
|
+
import { AXAccordionCdkModule } from '@acorex/cdk/accordion';
|
|
6
|
+
import * as i2 from '@acorex/components/alert';
|
|
7
|
+
import { AXAlertModule } from '@acorex/components/alert';
|
|
8
|
+
import * as i1$2 from '@acorex/components/button';
|
|
9
|
+
import { AXButtonModule } from '@acorex/components/button';
|
|
10
|
+
import * as i5 from '@acorex/components/code-editor';
|
|
11
|
+
import { AXCodeEditorModule } from '@acorex/components/code-editor';
|
|
12
|
+
import { AXConversationVoiceRendererComponent, AXConversationImageRendererComponent, AXConversationService, AXConversationTextRendererComponent } from '@acorex/components/conversation';
|
|
13
|
+
import * as i2$1 from '@acorex/components/decorators';
|
|
14
|
+
import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
15
|
+
import { AXPopupService } from '@acorex/components/popup';
|
|
16
|
+
import * as i1 from '@acorex/core/translation';
|
|
17
|
+
import { AXTranslationModule, AXTranslationService } from '@acorex/core/translation';
|
|
18
|
+
import { AsyncPipe, NgTemplateOutlet, DecimalPipe } from '@angular/common';
|
|
19
|
+
import * as i0 from '@angular/core';
|
|
20
|
+
import { inject, input, signal, computed, effect, ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
|
|
21
|
+
import * as i1$3 from '@angular/forms';
|
|
22
|
+
import { FormsModule } from '@angular/forms';
|
|
23
|
+
import { axmIsFormTemplateDesignerAssistLaunch } from '@acorex-platform/module-conversation-client/contracts';
|
|
24
|
+
import { i as axmResolveI18nLabel, j as axmSyntheticEmbedMessage, k as axmChatRenderableAssistWidgetNode, l as axmPartitionAssistAnswerText, m as axmParseAssistTranscriptTextEnvelope, n as AXMAssistChatDebugModeService, o as AXMChatMessageApi, d as AXMConversationFocusService, p as AXM_AI_MANAGEMENT_MODULE_NAME, q as AXM_AI_MANAGEMENT_AGENT_ENTITY_NAME, r as axmReadAssistResponseStatus, s as axmIsAssistRetryableStatus, t as axmReadAssistResponseError, u as axmReadAssistAiTranscript, v as axmParseAssistTranscriptLinePayload, w as axmReadAssistAiTranscriptRaw, x as axmAssistUserFacingPayloadLine, y as axmIsAssistStreamingStatus, z as axmAssistHasFinalizeMetadata, B as axmSanitizeAssistUserAnswerText, C as axmAssistUserVisibleItemsForLine, D as axmReadAssistFollowUpStatus, E as axmReadPlainTextMessagePayload, F as AXMNodeMessageRendererComponent, G as AXMAssistSuggestionChipsComponent } from './acorex-platform-module-conversation-client-acorex-platform-module-conversation-client-DUR2oyfP.mjs';
|
|
25
|
+
import { AXPPageContextRegistry } from '@acorex-platform/framework-client/core';
|
|
26
|
+
import { AXPSystemActionType, runtimeQueryPayloadData } from '@acorex-platform/framework-shared/core';
|
|
27
|
+
import { resolveEntityCardLayout, buildDefaultEntityCardLayout, resolveEntityCardAvatarPath, AXPEntityCommandScope } from '@acorex-platform/framework-shared/entity';
|
|
28
|
+
import { AXPCommandService, AXPQueryService } from '@acorex-platform/framework-client/runtime';
|
|
29
|
+
import { AXPFileStorageService } from '@acorex-platform/framework-client/common';
|
|
30
|
+
|
|
31
|
+
//#region ---- Imports ----
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region ---- Dialog ----
|
|
34
|
+
/**
|
|
35
|
+
* Opens a read-only specialist sub-run transcript for one delegated {@code agent:*} tool call.
|
|
36
|
+
* Renders {@link AXPAiChatMessage} lines (role + responses[]) in conversation-owned UI.
|
|
37
|
+
*/
|
|
38
|
+
async function axmOpenAssistDelegatedAgentDetailDialog(options) {
|
|
39
|
+
const callId = options.callId?.trim() ?? '';
|
|
40
|
+
if (!callId) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const entry = options.delegatedTranscriptsByCallId[callId];
|
|
44
|
+
const catalogId = axpAiParseSupervisorAgentToolName(options.toolName);
|
|
45
|
+
const catalogTitle = catalogId != null ? options.resolveCatalogTitle?.(catalogId)?.trim() : undefined;
|
|
46
|
+
const titleBase = options.resolvedAgentLabel?.trim() ||
|
|
47
|
+
entry?.agentName?.trim() ||
|
|
48
|
+
catalogTitle ||
|
|
49
|
+
options.toolName.trim() ||
|
|
50
|
+
callId;
|
|
51
|
+
const [titleSuffix, missingHint] = await Promise.all([
|
|
52
|
+
options.translationService.translateAsync('@conversation:chat.assist-renderer.delegated-agent.dialog-title-suffix'),
|
|
53
|
+
options.translationService.translateAsync('@conversation:chat.assist-renderer.delegated-agent.transcript-missing'),
|
|
54
|
+
]);
|
|
55
|
+
const comp = (await import('./acorex-platform-module-conversation-client-assist-delegated-agent-detail-popup.component-DQWhOmBE.mjs'))
|
|
56
|
+
.AXMAssistDelegatedAgentDetailPopupComponent;
|
|
57
|
+
const data = {
|
|
58
|
+
delegationCallId: callId,
|
|
59
|
+
messages: entry?.messages ?? [],
|
|
60
|
+
runInput: entry?.runInput,
|
|
61
|
+
agentName: entry?.agentName?.trim() || titleBase,
|
|
62
|
+
emptyHint: entry?.messages?.length ? undefined : missingHint,
|
|
63
|
+
agentTitleById: options.agentTitleById ?? (catalogId && catalogTitle ? { [catalogId]: catalogTitle } : {}),
|
|
64
|
+
};
|
|
65
|
+
await options.popupService.open(comp, {
|
|
66
|
+
title: `${titleBase} — ${titleSuffix ?? 'Full Specialist Run'}`,
|
|
67
|
+
size: 'lg',
|
|
68
|
+
data,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
|
|
73
|
+
//#region ---- Imports -----
|
|
74
|
+
async function axmOpenAssistToolDetailDialog(options) {
|
|
75
|
+
const comp = (await import('./acorex-platform-module-conversation-client-assist-tool-detail-popup.component-D8na_1QJ.mjs'))
|
|
76
|
+
.AXMAssistToolDetailPopupComponent;
|
|
77
|
+
const data = {
|
|
78
|
+
toolTitle: options.toolTitle.trim() || 'Tool',
|
|
79
|
+
inputJson: options.inputJson,
|
|
80
|
+
outputJson: options.outputJson,
|
|
81
|
+
};
|
|
82
|
+
await options.popupService.open(comp, {
|
|
83
|
+
title: data.toolTitle,
|
|
84
|
+
size: 'md',
|
|
85
|
+
data,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
|
|
90
|
+
//#region ----- Imports -----
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region ----- Constants -----
|
|
93
|
+
const ENTITY_CARD_COMMANDS = new Set([
|
|
94
|
+
'Entity:Query:Default',
|
|
95
|
+
'Entity:GetDetails',
|
|
96
|
+
'Entity:Insert:Default',
|
|
97
|
+
'Entity:Create',
|
|
98
|
+
'Entity:Update:Default',
|
|
99
|
+
]);
|
|
100
|
+
const PAGE_ACTION_COMMAND = 'Page:RunAction';
|
|
101
|
+
const MAX_ENTITY_CARDS = 3;
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region ----- Public API -----
|
|
104
|
+
/**
|
|
105
|
+
* Collects unique entity rows from assist tool calls so the transcript can render
|
|
106
|
+
* {@link axp-entity-card} instead of dumping query JSON.
|
|
107
|
+
*/
|
|
108
|
+
function axmAssistCollectEntityCardRefs(transcript) {
|
|
109
|
+
const resultsByCallId = collectToolResultsByCallId(transcript);
|
|
110
|
+
const refs = [];
|
|
111
|
+
const seen = new Set();
|
|
112
|
+
for (const line of transcript) {
|
|
113
|
+
if (line.role !== 'assistant') {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
for (const seg of line.responses) {
|
|
117
|
+
if (seg.type !== 'tool' && seg.type !== 'agent') {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const command = seg.content.command?.trim();
|
|
121
|
+
if (!command || !ENTITY_CARD_COMMANDS.has(command)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const entity = readEntityName(seg.content.arguments);
|
|
125
|
+
if (!entity) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const result = typeof seg.callId === 'string' ? resultsByCallId.get(seg.callId.trim()) : undefined;
|
|
129
|
+
const ids = collectRecordIds(seg.content.arguments, result);
|
|
130
|
+
for (const id of ids) {
|
|
131
|
+
const key = `${entity}:${id}`;
|
|
132
|
+
if (seen.has(key) || refs.length >= MAX_ENTITY_CARDS) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
seen.add(key);
|
|
136
|
+
refs.push({ entity, id, variant: 'compact' });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (refs.length === 1) {
|
|
141
|
+
refs[0].variant = 'card';
|
|
142
|
+
}
|
|
143
|
+
return refs;
|
|
144
|
+
}
|
|
145
|
+
/** Reads a successful Page:RunAction tool result for the compact “filter applied” strip. */
|
|
146
|
+
function axmAssistReadPageActionResult(transcript) {
|
|
147
|
+
const resultsByCallId = collectToolResultsByCallId(transcript);
|
|
148
|
+
for (let i = transcript.length - 1; i >= 0; i -= 1) {
|
|
149
|
+
const line = transcript[i];
|
|
150
|
+
if (line.role !== 'assistant') {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
for (let s = line.responses.length - 1; s >= 0; s -= 1) {
|
|
154
|
+
const seg = line.responses[s];
|
|
155
|
+
if (seg.type !== 'tool' && seg.type !== 'agent') {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (seg.content.command?.trim() !== PAGE_ACTION_COMMAND) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const callId = typeof seg.callId === 'string' ? seg.callId.trim() : '';
|
|
162
|
+
const parsed = parseUnknown(resultsByCallId.get(callId));
|
|
163
|
+
const snapshot = readSnapshot(parsed);
|
|
164
|
+
const args = asRecord(seg.content.arguments);
|
|
165
|
+
const actionId = readString(args?.['actionId']) ?? readString(args?.['id']);
|
|
166
|
+
const result = toPageActionResult(actionId, snapshot);
|
|
167
|
+
if (result) {
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region ----- Parsing -----
|
|
176
|
+
function collectToolResultsByCallId(transcript) {
|
|
177
|
+
const map = new Map();
|
|
178
|
+
for (const line of transcript) {
|
|
179
|
+
if (line.role !== 'tool') {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
for (const seg of line.responses) {
|
|
183
|
+
if (seg.type !== 'tool_result' && seg.type !== 'agent_result') {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const callId = seg.callId?.trim();
|
|
187
|
+
if (!callId) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
map.set(callId, seg.type === 'tool_result' ? seg.content : seg.content);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return map;
|
|
194
|
+
}
|
|
195
|
+
function readEntityName(args) {
|
|
196
|
+
const rec = asRecord(args);
|
|
197
|
+
if (!rec) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return (readModuleEntity(rec['entity']) ??
|
|
201
|
+
readModuleEntity(rec['entityFullName']) ??
|
|
202
|
+
readModuleEntity(rec['entityName']) ??
|
|
203
|
+
combineModuleEntity(rec['moduleName'], rec['entityName']));
|
|
204
|
+
}
|
|
205
|
+
function collectRecordIds(args, result) {
|
|
206
|
+
const fromArgs = readId(asRecord(args)?.['id']);
|
|
207
|
+
const unwrapped = unwrapData(parseUnknown(result));
|
|
208
|
+
const fromResult = collectIdsFromValue(unwrapped);
|
|
209
|
+
const ids = fromResult.length > 0 ? fromResult : fromArgs ? [fromArgs] : [];
|
|
210
|
+
return ids.slice(0, MAX_ENTITY_CARDS);
|
|
211
|
+
}
|
|
212
|
+
function collectIdsFromValue(value) {
|
|
213
|
+
if (Array.isArray(value)) {
|
|
214
|
+
return value.map((item) => readId(asRecord(item)?.['id'])).filter((id) => Boolean(id));
|
|
215
|
+
}
|
|
216
|
+
const rec = asRecord(value);
|
|
217
|
+
if (!rec) {
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
if (Array.isArray(rec['items'])) {
|
|
221
|
+
return collectIdsFromValue(rec['items']);
|
|
222
|
+
}
|
|
223
|
+
const entityData = asRecord(rec['entityData']);
|
|
224
|
+
const nestedId = readId(entityData?.['id']) ?? readId(rec['id']);
|
|
225
|
+
return nestedId ? [nestedId] : [];
|
|
226
|
+
}
|
|
227
|
+
function toPageActionResult(actionId, snapshot) {
|
|
228
|
+
if (!snapshot && !actionId) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
const kind = resolvePageActionKind(actionId);
|
|
232
|
+
const label = resolvePageActionLabel(kind, snapshot);
|
|
233
|
+
if (!label) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const undo = resolveUndo(kind, snapshot);
|
|
237
|
+
return {
|
|
238
|
+
kind,
|
|
239
|
+
label,
|
|
240
|
+
...(undo ? { undoActionId: undo.actionId, undoArgs: undo.args } : {}),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function resolvePageActionKind(actionId) {
|
|
244
|
+
const id = (actionId ?? '').toLowerCase();
|
|
245
|
+
if (id.includes('search')) {
|
|
246
|
+
return 'search';
|
|
247
|
+
}
|
|
248
|
+
if (id.includes('filter')) {
|
|
249
|
+
return 'filter';
|
|
250
|
+
}
|
|
251
|
+
if (id.includes('view')) {
|
|
252
|
+
return 'view';
|
|
253
|
+
}
|
|
254
|
+
return 'other';
|
|
255
|
+
}
|
|
256
|
+
function resolvePageActionLabel(kind, snapshot) {
|
|
257
|
+
if (kind === 'search') {
|
|
258
|
+
const search = snapshot?.quickSearch?.trim();
|
|
259
|
+
return search || '';
|
|
260
|
+
}
|
|
261
|
+
const filters = (snapshot?.filters ?? []).filter((item) => item.hidden !== true);
|
|
262
|
+
if (filters.length === 0) {
|
|
263
|
+
return axmResolveI18nLabel(kind === 'filter' ? '' : snapshot?.view?.title || snapshot?.title);
|
|
264
|
+
}
|
|
265
|
+
return filters
|
|
266
|
+
.map((item) => {
|
|
267
|
+
const title = axmResolveI18nLabel(item.title) || item.field;
|
|
268
|
+
const value = formatFilterValue(item.value);
|
|
269
|
+
return value ? `${title} ${value}` : title;
|
|
270
|
+
})
|
|
271
|
+
.filter(Boolean)
|
|
272
|
+
.join(', ');
|
|
273
|
+
}
|
|
274
|
+
function resolveUndo(kind, snapshot) {
|
|
275
|
+
const actions = snapshot?.actions ?? [];
|
|
276
|
+
const has = (id) => actions.some((action) => action.id === id);
|
|
277
|
+
if (kind === 'search' && has('set-search')) {
|
|
278
|
+
return { actionId: 'set-search', args: { value: '' } };
|
|
279
|
+
}
|
|
280
|
+
if (kind === 'filter' && has('clear-filters')) {
|
|
281
|
+
return { actionId: 'clear-filters', args: {} };
|
|
282
|
+
}
|
|
283
|
+
if (kind === 'filter' && has('remove-filter')) {
|
|
284
|
+
const field = snapshot?.filters?.find((item) => item.hidden !== true)?.field;
|
|
285
|
+
if (field) {
|
|
286
|
+
return { actionId: 'remove-filter', args: { field } };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
function readSnapshot(value) {
|
|
292
|
+
const rec = asRecord(unwrapData(value));
|
|
293
|
+
if (!rec) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
const snapshot = asRecord(rec['snapshot']) ?? (typeof rec['kind'] === 'string' ? rec : null);
|
|
297
|
+
if (!snapshot || typeof snapshot['kind'] !== 'string') {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
return snapshot;
|
|
301
|
+
}
|
|
302
|
+
function unwrapData(value) {
|
|
303
|
+
const rec = asRecord(value);
|
|
304
|
+
if (!rec) {
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
307
|
+
if ('data' in rec && rec['data'] != null) {
|
|
308
|
+
return unwrapData(rec['data']);
|
|
309
|
+
}
|
|
310
|
+
if ('payload' in rec && rec['payload'] != null) {
|
|
311
|
+
return unwrapData(rec['payload']);
|
|
312
|
+
}
|
|
313
|
+
return rec;
|
|
314
|
+
}
|
|
315
|
+
function parseUnknown(value) {
|
|
316
|
+
if (typeof value === 'string') {
|
|
317
|
+
const trimmed = value.trim();
|
|
318
|
+
if (!trimmed) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
return JSON.parse(trimmed);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
return value;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return value;
|
|
329
|
+
}
|
|
330
|
+
function asRecord(value) {
|
|
331
|
+
if (typeof value !== 'object' || value == null || Array.isArray(value)) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
return value;
|
|
335
|
+
}
|
|
336
|
+
function readString(value) {
|
|
337
|
+
if (typeof value !== 'string') {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
const trimmed = value.trim();
|
|
341
|
+
return trimmed || null;
|
|
342
|
+
}
|
|
343
|
+
function readModuleEntity(value) {
|
|
344
|
+
const text = readString(value);
|
|
345
|
+
if (!text || !text.includes('.')) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
return text;
|
|
349
|
+
}
|
|
350
|
+
function combineModuleEntity(moduleName, entityName) {
|
|
351
|
+
const module = readString(moduleName);
|
|
352
|
+
const entity = readString(entityName);
|
|
353
|
+
if (!module || !entity) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
return entity.includes('.') ? entity : `${module}.${entity}`;
|
|
357
|
+
}
|
|
358
|
+
function readId(value) {
|
|
359
|
+
if (typeof value === 'string' && value.trim()) {
|
|
360
|
+
return value.trim();
|
|
361
|
+
}
|
|
362
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
363
|
+
return String(value);
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
function formatFilterValue(value) {
|
|
368
|
+
if (typeof value === 'string') {
|
|
369
|
+
return value.trim();
|
|
370
|
+
}
|
|
371
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
372
|
+
return String(value);
|
|
373
|
+
}
|
|
374
|
+
if (Array.isArray(value)) {
|
|
375
|
+
return value.map((item) => formatFilterValue(item)).filter(Boolean).join(', ');
|
|
376
|
+
}
|
|
377
|
+
return '';
|
|
378
|
+
}
|
|
379
|
+
//#endregion
|
|
380
|
+
|
|
381
|
+
//#region ----- Imports -----
|
|
382
|
+
//#endregion
|
|
383
|
+
//#region ----- Component -----
|
|
384
|
+
/**
|
|
385
|
+
* Loads one entity row and renders it with the shared {@link axp-entity-card}
|
|
386
|
+
* used by list and lookup popovers (Open Details stays the card footer action).
|
|
387
|
+
*/
|
|
388
|
+
class AXMChatEntityCardComponent {
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region ----- Lifecycle -----
|
|
391
|
+
constructor() {
|
|
392
|
+
//#region ----- Services & Dependencies -----
|
|
393
|
+
this.commandService = inject(AXPCommandService);
|
|
394
|
+
this.queryService = inject(AXPQueryService);
|
|
395
|
+
this.cardFieldsResolver = inject(AXPEntityCardFieldsResolver);
|
|
396
|
+
//#endregion
|
|
397
|
+
//#region ----- Inputs -----
|
|
398
|
+
this.entity = input.required(/* @ts-ignore */
|
|
399
|
+
...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
|
|
400
|
+
this.entityId = input.required(/* @ts-ignore */
|
|
401
|
+
...(ngDevMode ? [{ debugName: "entityId" }] : /* istanbul ignore next */ []));
|
|
402
|
+
this.variant = input('card', /* @ts-ignore */
|
|
403
|
+
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region ----- State Signals -----
|
|
406
|
+
this.entityDefinition = signal(null, /* @ts-ignore */
|
|
407
|
+
...(ngDevMode ? [{ debugName: "entityDefinition" }] : /* istanbul ignore next */ []));
|
|
408
|
+
this.entityData = signal(null, /* @ts-ignore */
|
|
409
|
+
...(ngDevMode ? [{ debugName: "entityData" }] : /* istanbul ignore next */ []));
|
|
410
|
+
this.isLoading = signal(false, /* @ts-ignore */
|
|
411
|
+
...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
412
|
+
this.loadFailed = signal(false, /* @ts-ignore */
|
|
413
|
+
...(ngDevMode ? [{ debugName: "loadFailed" }] : /* istanbul ignore next */ []));
|
|
414
|
+
//#endregion
|
|
415
|
+
//#region ----- Computed Properties -----
|
|
416
|
+
this.cardLayout = computed(() => {
|
|
417
|
+
const definition = this.entityDefinition();
|
|
418
|
+
if (!definition) {
|
|
419
|
+
return undefined;
|
|
420
|
+
}
|
|
421
|
+
return resolveEntityCardLayout(definition) ?? buildDefaultEntityCardLayout(definition, definition.columns) ?? undefined;
|
|
422
|
+
}, /* @ts-ignore */
|
|
423
|
+
...(ngDevMode ? [{ debugName: "cardLayout" }] : /* istanbul ignore next */ []));
|
|
424
|
+
this.cardFields = computed(() => {
|
|
425
|
+
const definition = this.entityDefinition();
|
|
426
|
+
const layout = this.cardLayout();
|
|
427
|
+
if (!definition) {
|
|
428
|
+
return [];
|
|
429
|
+
}
|
|
430
|
+
return this.cardFieldsResolver.resolve(definition, layout);
|
|
431
|
+
}, /* @ts-ignore */
|
|
432
|
+
...(ngDevMode ? [{ debugName: "cardFields" }] : /* istanbul ignore next */ []));
|
|
433
|
+
this.showSkeletonAvatar = computed(() => {
|
|
434
|
+
const definition = this.entityDefinition();
|
|
435
|
+
const layout = this.cardLayout();
|
|
436
|
+
if (layout?.header?.avatar) {
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
return !!definition && !!resolveEntityCardAvatarPath(definition, definition.columns);
|
|
440
|
+
}, /* @ts-ignore */
|
|
441
|
+
...(ngDevMode ? [{ debugName: "showSkeletonAvatar" }] : /* istanbul ignore next */ []));
|
|
442
|
+
this.openDetailsActions = computed(() => {
|
|
443
|
+
const definition = this.entityDefinition();
|
|
444
|
+
if (!definition) {
|
|
445
|
+
return [];
|
|
446
|
+
}
|
|
447
|
+
return [
|
|
448
|
+
new AXPEntityCommandTriggerViewModel(definition, {
|
|
449
|
+
title: '@general:actions.open-details.title',
|
|
450
|
+
command: 'Entity:OpenDetails',
|
|
451
|
+
priority: 'primary',
|
|
452
|
+
type: AXPSystemActionType.View,
|
|
453
|
+
scope: AXPEntityCommandScope.Individual,
|
|
454
|
+
default: true,
|
|
455
|
+
}),
|
|
456
|
+
];
|
|
457
|
+
}, /* @ts-ignore */
|
|
458
|
+
...(ngDevMode ? [{ debugName: "openDetailsActions" }] : /* istanbul ignore next */ []));
|
|
459
|
+
effect(() => {
|
|
460
|
+
const entity = this.entity().trim();
|
|
461
|
+
const id = this.entityId().trim();
|
|
462
|
+
if (!entity || !id) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
void this.loadDetails(entity, id);
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
//#endregion
|
|
469
|
+
//#region ----- Event Handlers -----
|
|
470
|
+
onCardCommandClick(actionName) {
|
|
471
|
+
const commandName = actionName.split('&')[0];
|
|
472
|
+
if (commandName === 'Entity:OpenDetails') {
|
|
473
|
+
void this.navigateToDetails();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
//#endregion
|
|
477
|
+
//#region ----- Data Loading -----
|
|
478
|
+
async loadDetails(entity, id) {
|
|
479
|
+
this.isLoading.set(true);
|
|
480
|
+
this.loadFailed.set(false);
|
|
481
|
+
this.entityDefinition.set(null);
|
|
482
|
+
this.entityData.set(null);
|
|
483
|
+
try {
|
|
484
|
+
const result = await this.queryService.fetch('Entity:GetDetails', { entity, id });
|
|
485
|
+
const data = runtimeQueryPayloadData(result);
|
|
486
|
+
if (data?.entityDefinition) {
|
|
487
|
+
this.entityDefinition.set(data.entityDefinition);
|
|
488
|
+
}
|
|
489
|
+
if (data?.entityData && typeof data.entityData === 'object') {
|
|
490
|
+
this.entityData.set(data.entityData);
|
|
491
|
+
}
|
|
492
|
+
if (!data?.entityDefinition || !data?.entityData) {
|
|
493
|
+
this.loadFailed.set(true);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
this.loadFailed.set(true);
|
|
498
|
+
}
|
|
499
|
+
finally {
|
|
500
|
+
this.isLoading.set(false);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async navigateToDetails() {
|
|
504
|
+
const id = this.entityId().trim();
|
|
505
|
+
if (!id) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
await this.commandService.execute('Entity:OpenDetails', {
|
|
509
|
+
entity: this.entity().trim(),
|
|
510
|
+
data: { id },
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMChatEntityCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
514
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMChatEntityCardComponent, isStandalone: true, selector: "axm-chat-entity-card", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, entityId: { classPropertyName: "entityId", publicName: "entityId", isSignal: true, isRequired: true, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"axm-chat-entity-card\">\n @if (isLoading()) {\n <!-- ----- Loading placeholder while Entity:GetDetails is fetched ----- -->\n <axp-entity-card-skeleton\n [fieldCount]=\"3\"\n [badgeCount]=\"1\"\n [showContent]=\"variant() === 'card'\"\n [showFooter]=\"true\"\n [showAvatar]=\"showSkeletonAvatar()\"\n [embedded]=\"true\"\n />\n }\n <!-- ----- Shared entity card when the row has loaded ----- -->\n @else if (entityDefinition(); as definition) {\n <!---->\n <!-- ----- Render the card when row details are present ----- -->\n @if (entityData(); as row) {\n <axp-entity-card\n [entity]=\"definition\"\n [layout]=\"cardLayout()\"\n [row]=\"row\"\n [fields]=\"cardFields()\"\n [variant]=\"variant()\"\n [interactive]=\"false\"\n [embedded]=\"true\"\n [disableLookupPopover]=\"true\"\n primaryActionLook=\"outline\"\n [primaryActions]=\"openDetailsActions()\"\n (commandClick)=\"onCardCommandClick($event)\"\n />\n }\n }\n <!-- ----- Quiet fallback when the row cannot be loaded ----- -->\n @else if (loadFailed()) {\n <p class=\"axm-chat-entity-card__error\">\n {{ '@conversation:mini-chat.entity-card.load-error' | translate | async }}\n </p>\n }\n</div>\n", styles: [":host{display:block;width:100%;min-width:0}.axm-chat-entity-card{min-width:0}.axm-chat-entity-card__error{margin:0;padding:.625rem .75rem;border-radius:.75rem;border:1px solid rgb(var(--ax-sys-color-border-light-surface));font-size:.8125rem;color:rgb(var(--ax-sys-color-on-surface));opacity:.72}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPEntityCardComponent, selector: "axp-entity-card", inputs: ["entity", "layout", "row", "fields", "userStylingRules", "variant", "interactive", "embedded", "elevated", "disableLookupPopover", "fillHost", "extraExpanded", "selected", "showNewBadge", "showSelection", "showClose", "primaryActions", "primaryActionLook", "secondaryActionLoader"], outputs: ["extraExpandedChange", "selectionChange", "commandClick", "closeClick"] }, { kind: "component", type: AXPEntityCardSkeletonComponent, selector: "axp-entity-card-skeleton", inputs: ["fieldCount", "badgeCount", "showFooter", "showHeaderMenu", "showContent", "rowLayout", "showAvatar", "fillHost", "embedded", "elevated"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: i1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
515
|
+
}
|
|
516
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMChatEntityCardComponent, decorators: [{
|
|
517
|
+
type: Component,
|
|
518
|
+
args: [{ selector: 'axm-chat-entity-card', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AsyncPipe, AXTranslationModule, AXPEntityCardComponent, AXPEntityCardSkeletonComponent], template: "<div class=\"axm-chat-entity-card\">\n @if (isLoading()) {\n <!-- ----- Loading placeholder while Entity:GetDetails is fetched ----- -->\n <axp-entity-card-skeleton\n [fieldCount]=\"3\"\n [badgeCount]=\"1\"\n [showContent]=\"variant() === 'card'\"\n [showFooter]=\"true\"\n [showAvatar]=\"showSkeletonAvatar()\"\n [embedded]=\"true\"\n />\n }\n <!-- ----- Shared entity card when the row has loaded ----- -->\n @else if (entityDefinition(); as definition) {\n <!---->\n <!-- ----- Render the card when row details are present ----- -->\n @if (entityData(); as row) {\n <axp-entity-card\n [entity]=\"definition\"\n [layout]=\"cardLayout()\"\n [row]=\"row\"\n [fields]=\"cardFields()\"\n [variant]=\"variant()\"\n [interactive]=\"false\"\n [embedded]=\"true\"\n [disableLookupPopover]=\"true\"\n primaryActionLook=\"outline\"\n [primaryActions]=\"openDetailsActions()\"\n (commandClick)=\"onCardCommandClick($event)\"\n />\n }\n }\n <!-- ----- Quiet fallback when the row cannot be loaded ----- -->\n @else if (loadFailed()) {\n <p class=\"axm-chat-entity-card__error\">\n {{ '@conversation:mini-chat.entity-card.load-error' | translate | async }}\n </p>\n }\n</div>\n", styles: [":host{display:block;width:100%;min-width:0}.axm-chat-entity-card{min-width:0}.axm-chat-entity-card__error{margin:0;padding:.625rem .75rem;border-radius:.75rem;border:1px solid rgb(var(--ax-sys-color-border-light-surface));font-size:.8125rem;color:rgb(var(--ax-sys-color-on-surface));opacity:.72}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
|
|
519
|
+
}], ctorParameters: () => [], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], entityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityId", required: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }] } });
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Detects strong RTL scripts (Arabic / Persian / Hebrew and related blocks) in a string
|
|
523
|
+
* so each assist transcript segment can set `dir` independently of the app locale.
|
|
524
|
+
*/
|
|
525
|
+
const ASSIST_SEGMENT_STRONG_RTL = /[\u0591-\u08FF\uFB1D-\uFDFF\uFE70-\uFEFC]/u;
|
|
526
|
+
/**
|
|
527
|
+
* Returns `rtl` when the segment contains any character from RTL scripts (e.g. Persian/Farsi),
|
|
528
|
+
* otherwise `ltr`. Empty or whitespace-only strings are `ltr`.
|
|
529
|
+
*/
|
|
530
|
+
function axmAssistSegmentDirection(text) {
|
|
531
|
+
if (text == null)
|
|
532
|
+
return 'ltr';
|
|
533
|
+
const s = String(text);
|
|
534
|
+
if (!s.trim())
|
|
535
|
+
return 'ltr';
|
|
536
|
+
return ASSIST_SEGMENT_STRONG_RTL.test(s) ? 'rtl' : 'ltr';
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
//#endregion
|
|
540
|
+
//#region ---- Component ----
|
|
541
|
+
/**
|
|
542
|
+
* Renders an assist {@code fileId} via {@link AXConversationVoiceRendererComponent} using {@code mediaId} so
|
|
543
|
+
* {@link AXUploaderService} resolves a playback URL (conversation rejects persisted {@code data:} URLs).
|
|
544
|
+
*/
|
|
545
|
+
class AXMAssistBotAudioLineComponent {
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region ---- Lifecycle ----
|
|
548
|
+
constructor() {
|
|
549
|
+
//#region ---- Inputs ----
|
|
550
|
+
this.parentMessage = input.required(/* @ts-ignore */
|
|
551
|
+
...(ngDevMode ? [{ debugName: "parentMessage" }] : /* istanbul ignore next */ []));
|
|
552
|
+
this.fileId = input.required(/* @ts-ignore */
|
|
553
|
+
...(ngDevMode ? [{ debugName: "fileId" }] : /* istanbul ignore next */ []));
|
|
554
|
+
this.mimeType = input('audio/mpeg', /* @ts-ignore */
|
|
555
|
+
...(ngDevMode ? [{ debugName: "mimeType" }] : /* istanbul ignore next */ []));
|
|
556
|
+
//#endregion
|
|
557
|
+
//#region ---- Services ----
|
|
558
|
+
this.fileStorage = inject(AXPFileStorageService);
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region ---- State ----
|
|
561
|
+
this.fileReady = signal(false, /* @ts-ignore */
|
|
562
|
+
...(ngDevMode ? [{ debugName: "fileReady" }] : /* istanbul ignore next */ []));
|
|
563
|
+
this.resolvedMimeType = signal('audio/mpeg', /* @ts-ignore */
|
|
564
|
+
...(ngDevMode ? [{ debugName: "resolvedMimeType" }] : /* istanbul ignore next */ []));
|
|
565
|
+
this.errorText = signal(null, /* @ts-ignore */
|
|
566
|
+
...(ngDevMode ? [{ debugName: "errorText" }] : /* istanbul ignore next */ []));
|
|
567
|
+
this.loadGeneration = 0;
|
|
568
|
+
//#endregion
|
|
569
|
+
//#region ---- Synthetic message ----
|
|
570
|
+
this.syntheticMessage = computed(() => {
|
|
571
|
+
const fileId = this.fileId()?.trim() ?? '';
|
|
572
|
+
const parent = this.parentMessage();
|
|
573
|
+
if (!this.fileReady() || !fileId) {
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
return axmSyntheticEmbedMessage(parent, `voice-${fileId}`, {
|
|
577
|
+
type: 'voice',
|
|
578
|
+
payload: {
|
|
579
|
+
type: 'voice',
|
|
580
|
+
url: '',
|
|
581
|
+
mediaId: fileId,
|
|
582
|
+
duration: 0,
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
}, /* @ts-ignore */
|
|
586
|
+
...(ngDevMode ? [{ debugName: "syntheticMessage" }] : /* istanbul ignore next */ []));
|
|
587
|
+
effect(() => {
|
|
588
|
+
const id = this.fileId()?.trim() ?? '';
|
|
589
|
+
const gen = ++this.loadGeneration;
|
|
590
|
+
this.fileReady.set(false);
|
|
591
|
+
this.errorText.set(null);
|
|
592
|
+
if (!id) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
void this.fileStorage
|
|
596
|
+
.getInfo(id)
|
|
597
|
+
.then((info) => {
|
|
598
|
+
if (gen !== this.loadGeneration) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const storedMime = info.mimeType?.trim();
|
|
602
|
+
const fallbackMime = this.mimeType().trim();
|
|
603
|
+
const mimeType = storedMime && storedMime.startsWith('audio/')
|
|
604
|
+
? storedMime
|
|
605
|
+
: fallbackMime.startsWith('audio/')
|
|
606
|
+
? fallbackMime
|
|
607
|
+
: 'audio/mpeg';
|
|
608
|
+
this.resolvedMimeType.set(mimeType);
|
|
609
|
+
this.fileReady.set(true);
|
|
610
|
+
})
|
|
611
|
+
.catch(() => {
|
|
612
|
+
if (gen !== this.loadGeneration) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
this.errorText.set('@conversation:chat.assist-renderer.errors.audio-load');
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMAssistBotAudioLineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
620
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMAssistBotAudioLineComponent, isStandalone: true, selector: "axm-assist-bot-audio-line", inputs: { parentMessage: { classPropertyName: "parentMessage", publicName: "parentMessage", isSignal: true, isRequired: true, transformFunction: null }, fileId: { classPropertyName: "fileId", publicName: "fileId", isSignal: true, isRequired: true, transformFunction: null }, mimeType: { classPropertyName: "mimeType", publicName: "mimeType", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
621
|
+
@if (syntheticMessage(); as sm) {
|
|
622
|
+
<ax-conversation-voice-renderer [message]="sm" />
|
|
623
|
+
} @else if (errorText()) {
|
|
624
|
+
<p class="axm-assist-bot-audio-line__err">{{ (errorText() ?? '' | translate | async) ?? '' }}</p>
|
|
625
|
+
}
|
|
626
|
+
`, isInline: true, styles: [":host{display:block;max-inline-size:min(100%,28rem)}.axm-assist-bot-audio-line__err{margin:0;font-size:.8125rem;color:rgb(var(--ax-sys-color-danger-600, 220 38 38))}\n"], dependencies: [{ kind: "component", type: AXConversationVoiceRendererComponent, selector: "ax-conversation-voice-renderer", inputs: ["message"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i1.AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
627
|
+
}
|
|
628
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMAssistBotAudioLineComponent, decorators: [{
|
|
629
|
+
type: Component,
|
|
630
|
+
args: [{ selector: 'axm-assist-bot-audio-line', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXConversationVoiceRendererComponent, AXTranslationModule, AsyncPipe], template: `
|
|
631
|
+
@if (syntheticMessage(); as sm) {
|
|
632
|
+
<ax-conversation-voice-renderer [message]="sm" />
|
|
633
|
+
} @else if (errorText()) {
|
|
634
|
+
<p class="axm-assist-bot-audio-line__err">{{ (errorText() ?? '' | translate | async) ?? '' }}</p>
|
|
635
|
+
}
|
|
636
|
+
`, styles: [":host{display:block;max-inline-size:min(100%,28rem)}.axm-assist-bot-audio-line__err{margin:0;font-size:.8125rem;color:rgb(var(--ax-sys-color-danger-600, 220 38 38))}\n"] }]
|
|
637
|
+
}], ctorParameters: () => [], propDecorators: { parentMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentMessage", required: true }] }], fileId: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileId", required: true }] }], mimeType: [{ type: i0.Input, args: [{ isSignal: true, alias: "mimeType", required: false }] }] } });
|
|
638
|
+
|
|
639
|
+
//#endregion
|
|
640
|
+
//#region ---- Component ----
|
|
641
|
+
/**
|
|
642
|
+
* Renders an assist {@code fileId} via {@link AXConversationImageRendererComponent} using {@code mediaId} so
|
|
643
|
+
* {@link AXUploaderService} resolves a display URL (conversation rejects persisted {@code data:} URLs).
|
|
644
|
+
*/
|
|
645
|
+
class AXMAssistBotImageLineComponent {
|
|
646
|
+
//#endregion
|
|
647
|
+
//#region ---- Lifecycle ----
|
|
648
|
+
constructor() {
|
|
649
|
+
//#region ---- Inputs ----
|
|
650
|
+
this.parentMessage = input.required(/* @ts-ignore */
|
|
651
|
+
...(ngDevMode ? [{ debugName: "parentMessage" }] : /* istanbul ignore next */ []));
|
|
652
|
+
this.fileId = input.required(/* @ts-ignore */
|
|
653
|
+
...(ngDevMode ? [{ debugName: "fileId" }] : /* istanbul ignore next */ []));
|
|
654
|
+
this.mimeType = input('application/octet-stream', /* @ts-ignore */
|
|
655
|
+
...(ngDevMode ? [{ debugName: "mimeType" }] : /* istanbul ignore next */ []));
|
|
656
|
+
this.name = input(undefined, /* @ts-ignore */
|
|
657
|
+
...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
658
|
+
//#endregion
|
|
659
|
+
//#region ---- Services ----
|
|
660
|
+
this.fileStorage = inject(AXPFileStorageService);
|
|
661
|
+
//#endregion
|
|
662
|
+
//#region ---- State ----
|
|
663
|
+
this.fileReady = signal(false, /* @ts-ignore */
|
|
664
|
+
...(ngDevMode ? [{ debugName: "fileReady" }] : /* istanbul ignore next */ []));
|
|
665
|
+
this.resolvedMimeType = signal('image/png', /* @ts-ignore */
|
|
666
|
+
...(ngDevMode ? [{ debugName: "resolvedMimeType" }] : /* istanbul ignore next */ []));
|
|
667
|
+
this.errorText = signal(null, /* @ts-ignore */
|
|
668
|
+
...(ngDevMode ? [{ debugName: "errorText" }] : /* istanbul ignore next */ []));
|
|
669
|
+
this.loadGeneration = 0;
|
|
670
|
+
//#endregion
|
|
671
|
+
//#region ---- Synthetic message ----
|
|
672
|
+
this.syntheticMessage = computed(() => {
|
|
673
|
+
const fileId = this.fileId()?.trim() ?? '';
|
|
674
|
+
const parent = this.parentMessage();
|
|
675
|
+
if (!this.fileReady() || !fileId) {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
return axmSyntheticEmbedMessage(parent, `img-${fileId}`, {
|
|
679
|
+
type: 'image',
|
|
680
|
+
payload: {
|
|
681
|
+
type: 'image',
|
|
682
|
+
images: [
|
|
683
|
+
{
|
|
684
|
+
mediaId: fileId,
|
|
685
|
+
url: '',
|
|
686
|
+
thumbnailUrl: '',
|
|
687
|
+
width: 400,
|
|
688
|
+
height: 300,
|
|
689
|
+
mimeType: this.resolvedMimeType(),
|
|
690
|
+
},
|
|
691
|
+
],
|
|
692
|
+
caption: undefined,
|
|
693
|
+
},
|
|
694
|
+
});
|
|
695
|
+
}, /* @ts-ignore */
|
|
696
|
+
...(ngDevMode ? [{ debugName: "syntheticMessage" }] : /* istanbul ignore next */ []));
|
|
697
|
+
effect(() => {
|
|
698
|
+
const id = this.fileId()?.trim() ?? '';
|
|
699
|
+
const gen = ++this.loadGeneration;
|
|
700
|
+
this.fileReady.set(false);
|
|
701
|
+
this.errorText.set(null);
|
|
702
|
+
if (!id) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
void this.fileStorage
|
|
706
|
+
.getInfo(id)
|
|
707
|
+
.then((info) => {
|
|
708
|
+
if (gen !== this.loadGeneration) {
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
const storedMime = info.mimeType?.trim();
|
|
712
|
+
const fallbackMime = this.mimeType().trim();
|
|
713
|
+
const mimeType = storedMime && storedMime.startsWith('image/')
|
|
714
|
+
? storedMime
|
|
715
|
+
: fallbackMime.startsWith('image/')
|
|
716
|
+
? fallbackMime
|
|
717
|
+
: 'image/png';
|
|
718
|
+
this.resolvedMimeType.set(mimeType);
|
|
719
|
+
this.fileReady.set(true);
|
|
720
|
+
})
|
|
721
|
+
.catch(() => {
|
|
722
|
+
if (gen !== this.loadGeneration) {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
this.errorText.set('@conversation:chat.assist-renderer.errors.image-load');
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMAssistBotImageLineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
730
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMAssistBotImageLineComponent, isStandalone: true, selector: "axm-assist-bot-image-line", inputs: { parentMessage: { classPropertyName: "parentMessage", publicName: "parentMessage", isSignal: true, isRequired: true, transformFunction: null }, fileId: { classPropertyName: "fileId", publicName: "fileId", isSignal: true, isRequired: true, transformFunction: null }, mimeType: { classPropertyName: "mimeType", publicName: "mimeType", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
731
|
+
@if (syntheticMessage(); as sm) {
|
|
732
|
+
<ax-conversation-image-renderer [message]="sm" />
|
|
733
|
+
} @else if (errorText()) {
|
|
734
|
+
<p class="axm-assist-bot-image-line__err">{{ (errorText() ?? '' | translate | async) ?? '' }}</p>
|
|
735
|
+
}
|
|
736
|
+
`, isInline: true, styles: [":host{display:block;max-inline-size:min(100%,40rem)}.axm-assist-bot-image-line__err{margin:0;font-size:.8125rem;color:rgb(var(--ax-sys-color-danger-600, 220 38 38))}\n"], dependencies: [{ kind: "component", type: AXConversationImageRendererComponent, selector: "ax-conversation-image-renderer", inputs: ["message"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i1.AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
737
|
+
}
|
|
738
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMAssistBotImageLineComponent, decorators: [{
|
|
739
|
+
type: Component,
|
|
740
|
+
args: [{ selector: 'axm-assist-bot-image-line', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXConversationImageRendererComponent, AXTranslationModule, AsyncPipe], template: `
|
|
741
|
+
@if (syntheticMessage(); as sm) {
|
|
742
|
+
<ax-conversation-image-renderer [message]="sm" />
|
|
743
|
+
} @else if (errorText()) {
|
|
744
|
+
<p class="axm-assist-bot-image-line__err">{{ (errorText() ?? '' | translate | async) ?? '' }}</p>
|
|
745
|
+
}
|
|
746
|
+
`, styles: [":host{display:block;max-inline-size:min(100%,40rem)}.axm-assist-bot-image-line__err{margin:0;font-size:.8125rem;color:rgb(var(--ax-sys-color-danger-600, 220 38 38))}\n"] }]
|
|
747
|
+
}], ctorParameters: () => [], propDecorators: { parentMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentMessage", required: true }] }], fileId: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileId", required: true }] }], mimeType: [{ type: i0.Input, args: [{ isSignal: true, alias: "mimeType", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }] } });
|
|
748
|
+
|
|
749
|
+
//#region ---- Imports ----
|
|
750
|
+
//#endregion
|
|
751
|
+
//#region ---- Component ----
|
|
752
|
+
/** Routes assist {@code fileId} segments to image or voice renderers based on stored file metadata. */
|
|
753
|
+
class AXMAssistBotFileLineComponent {
|
|
754
|
+
//#endregion
|
|
755
|
+
//#region ---- Lifecycle ----
|
|
756
|
+
constructor() {
|
|
757
|
+
//#region ---- Inputs ----
|
|
758
|
+
this.parentMessage = input.required(/* @ts-ignore */
|
|
759
|
+
...(ngDevMode ? [{ debugName: "parentMessage" }] : /* istanbul ignore next */ []));
|
|
760
|
+
this.fileId = input.required(/* @ts-ignore */
|
|
761
|
+
...(ngDevMode ? [{ debugName: "fileId" }] : /* istanbul ignore next */ []));
|
|
762
|
+
//#endregion
|
|
763
|
+
//#region ---- Services ----
|
|
764
|
+
this.fileStorage = inject(AXPFileStorageService);
|
|
765
|
+
//#endregion
|
|
766
|
+
//#region ---- State ----
|
|
767
|
+
this.mediaKind = signal('unknown', /* @ts-ignore */
|
|
768
|
+
...(ngDevMode ? [{ debugName: "mediaKind" }] : /* istanbul ignore next */ []));
|
|
769
|
+
this.errorText = signal(null, /* @ts-ignore */
|
|
770
|
+
...(ngDevMode ? [{ debugName: "errorText" }] : /* istanbul ignore next */ []));
|
|
771
|
+
this.loadGeneration = 0;
|
|
772
|
+
effect(() => {
|
|
773
|
+
const id = this.fileId()?.trim() ?? '';
|
|
774
|
+
const gen = ++this.loadGeneration;
|
|
775
|
+
this.mediaKind.set('unknown');
|
|
776
|
+
this.errorText.set(null);
|
|
777
|
+
if (!id) {
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
void this.fileStorage
|
|
781
|
+
.getInfo(id)
|
|
782
|
+
.then((info) => {
|
|
783
|
+
if (gen !== this.loadGeneration) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
this.mediaKind.set(resolveAssistBotFileMediaKind(info.mimeType, info.category, info.refType));
|
|
787
|
+
if (this.mediaKind() === 'unknown') {
|
|
788
|
+
this.errorText.set('@conversation:chat.assist-renderer.errors.file-load');
|
|
789
|
+
}
|
|
790
|
+
})
|
|
791
|
+
.catch(() => {
|
|
792
|
+
if (gen !== this.loadGeneration) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
this.errorText.set('@conversation:chat.assist-renderer.errors.file-load');
|
|
796
|
+
});
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMAssistBotFileLineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
800
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMAssistBotFileLineComponent, isStandalone: true, selector: "axm-assist-bot-file-line", inputs: { parentMessage: { classPropertyName: "parentMessage", publicName: "parentMessage", isSignal: true, isRequired: true, transformFunction: null }, fileId: { classPropertyName: "fileId", publicName: "fileId", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
801
|
+
@switch (mediaKind()) {
|
|
802
|
+
@case ('image') {
|
|
803
|
+
<axm-assist-bot-image-line [parentMessage]="parentMessage()" [fileId]="fileId()" />
|
|
804
|
+
}
|
|
805
|
+
@case ('audio') {
|
|
806
|
+
<axm-assist-bot-audio-line [parentMessage]="parentMessage()" [fileId]="fileId()" />
|
|
807
|
+
}
|
|
808
|
+
@default {
|
|
809
|
+
@if (errorText()) {
|
|
810
|
+
<p class="axm-assist-bot-file-line__err">{{ (errorText() ?? '' | translate | async) ?? '' }}</p>
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
`, isInline: true, styles: [":host{display:block}.axm-assist-bot-file-line__err{margin:0;font-size:.8125rem;color:rgb(var(--ax-sys-color-danger-600, 220 38 38))}\n"], dependencies: [{ kind: "component", type: AXMAssistBotAudioLineComponent, selector: "axm-assist-bot-audio-line", inputs: ["parentMessage", "fileId", "mimeType"] }, { kind: "component", type: AXMAssistBotImageLineComponent, selector: "axm-assist-bot-image-line", inputs: ["parentMessage", "fileId", "mimeType", "name"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i1.AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
815
|
+
}
|
|
816
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMAssistBotFileLineComponent, decorators: [{
|
|
817
|
+
type: Component,
|
|
818
|
+
args: [{ selector: 'axm-assist-bot-file-line', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXMAssistBotAudioLineComponent, AXMAssistBotImageLineComponent, AXTranslationModule, AsyncPipe], template: `
|
|
819
|
+
@switch (mediaKind()) {
|
|
820
|
+
@case ('image') {
|
|
821
|
+
<axm-assist-bot-image-line [parentMessage]="parentMessage()" [fileId]="fileId()" />
|
|
822
|
+
}
|
|
823
|
+
@case ('audio') {
|
|
824
|
+
<axm-assist-bot-audio-line [parentMessage]="parentMessage()" [fileId]="fileId()" />
|
|
825
|
+
}
|
|
826
|
+
@default {
|
|
827
|
+
@if (errorText()) {
|
|
828
|
+
<p class="axm-assist-bot-file-line__err">{{ (errorText() ?? '' | translate | async) ?? '' }}</p>
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
`, styles: [":host{display:block}.axm-assist-bot-file-line__err{margin:0;font-size:.8125rem;color:rgb(var(--ax-sys-color-danger-600, 220 38 38))}\n"] }]
|
|
833
|
+
}], ctorParameters: () => [], propDecorators: { parentMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentMessage", required: true }] }], fileId: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileId", required: true }] }] } });
|
|
834
|
+
//#region ---- Helpers ----
|
|
835
|
+
function resolveAssistBotFileMediaKind(mimeType, category, refType) {
|
|
836
|
+
const mime = mimeType?.trim().toLowerCase() ?? '';
|
|
837
|
+
if (mime.startsWith('audio/') || category === 'ai-generated-speech' || refType === AI_CHAT_GENERATED_SPEECH_REF_TYPE) {
|
|
838
|
+
return 'audio';
|
|
839
|
+
}
|
|
840
|
+
if (mime.startsWith('image/') || category === 'ai-generated-image' || refType === AI_CHAT_GENERATED_IMAGE_REF_TYPE) {
|
|
841
|
+
return 'image';
|
|
842
|
+
}
|
|
843
|
+
return 'unknown';
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
//#region ---- Imports ----
|
|
847
|
+
//#endregion
|
|
848
|
+
//#region ---- Component ----
|
|
849
|
+
class AXMAssistMessageRendererComponent {
|
|
850
|
+
constructor() {
|
|
851
|
+
//#region ---- Inputs ----
|
|
852
|
+
this.message = input.required(/* @ts-ignore */
|
|
853
|
+
...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
854
|
+
//#endregion
|
|
855
|
+
//#region ---- Template helpers (exposed for template) ----
|
|
856
|
+
this.axpAiChatMessageGetText = axpAiChatMessageGetText;
|
|
857
|
+
this.axmAssistWidgetNodeFromUnknown = axmChatRenderableAssistWidgetNode;
|
|
858
|
+
this.partitionAnswerText = axmPartitionAssistAnswerText;
|
|
859
|
+
this.parseAssistLineText = axmParseAssistTranscriptTextEnvelope;
|
|
860
|
+
this.segmentDir = axmAssistSegmentDirection;
|
|
861
|
+
//#endregion
|
|
862
|
+
//#region ---- State ----
|
|
863
|
+
this.entityService = inject(AXPEntityService);
|
|
864
|
+
this.pageContext = inject(AXPPageContextRegistry);
|
|
865
|
+
this.assistDebugMode = inject(AXMAssistChatDebugModeService);
|
|
866
|
+
this.translation = inject(AXTranslationService);
|
|
867
|
+
this.popupService = inject(AXPopupService);
|
|
868
|
+
this.messageApi = inject(AXMChatMessageApi);
|
|
869
|
+
this.focus = inject(AXMConversationFocusService);
|
|
870
|
+
this.conversationService = inject(AXConversationService);
|
|
871
|
+
this.modelCatalog = inject(AXPAiAssistChatModelCatalogService);
|
|
872
|
+
this.agentData = this.entityService.withEntity(AXM_AI_MANAGEMENT_MODULE_NAME, AXM_AI_MANAGEMENT_AGENT_ENTITY_NAME).data();
|
|
873
|
+
this.loadingAgentIds = new Set();
|
|
874
|
+
this.loadingModelIds = new Set();
|
|
875
|
+
this.agentTitleById = signal({}, /* @ts-ignore */
|
|
876
|
+
...(ngDevMode ? [{ debugName: "agentTitleById" }] : /* istanbul ignore next */ []));
|
|
877
|
+
this.modelTitleById = signal({}, /* @ts-ignore */
|
|
878
|
+
...(ngDevMode ? [{ debugName: "modelTitleById" }] : /* istanbul ignore next */ []));
|
|
879
|
+
this.contentState = signal('ready', /* @ts-ignore */
|
|
880
|
+
...(ngDevMode ? [{ debugName: "contentState" }] : /* istanbul ignore next */ []));
|
|
881
|
+
this.isRetrying = signal(false, /* @ts-ignore */
|
|
882
|
+
...(ngDevMode ? [{ debugName: "isRetrying" }] : /* istanbul ignore next */ []));
|
|
883
|
+
/** Debug transcript/tools UI — only after settings resolve and debug is on. */
|
|
884
|
+
this.showDebugUi = computed(() => this.assistDebugMode.resolved() && this.assistDebugMode.active(), /* @ts-ignore */
|
|
885
|
+
...(ngDevMode ? [{ debugName: "showDebugUi" }] : /* istanbul ignore next */ []));
|
|
886
|
+
/** User-facing UI (no tools/thinking accordions); used while unresolved and when debug is off. */
|
|
887
|
+
this.showNonDebugUi = computed(() => !this.showDebugUi(), /* @ts-ignore */
|
|
888
|
+
...(ngDevMode ? [{ debugName: "showNonDebugUi" }] : /* istanbul ignore next */ []));
|
|
889
|
+
/** Persisted assist run metadata (mock writes {@code assistRun*} keys on finalize). */
|
|
890
|
+
this.assistRunDebug = computed(() => {
|
|
891
|
+
const meta = this.message().metadata;
|
|
892
|
+
if (!meta) {
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
const usage = meta['assistRunUsageTotals'];
|
|
896
|
+
const durationRaw = meta['assistRunDurationMs'];
|
|
897
|
+
const modelEntityId = meta['assistRunModelEntityId'];
|
|
898
|
+
const modelTitle = meta['assistRunModelTitle'];
|
|
899
|
+
const durationMs = typeof durationRaw === 'number' && Number.isFinite(durationRaw) ? durationRaw : undefined;
|
|
900
|
+
const id = typeof modelEntityId === 'string' && modelEntityId.trim() ? modelEntityId.trim() : undefined;
|
|
901
|
+
const storedTitle = typeof modelTitle === 'string' && modelTitle.trim() ? modelTitle.trim() : undefined;
|
|
902
|
+
const resolvedTitle = id ? this.modelTitleById()[id] : undefined;
|
|
903
|
+
const title = storedTitle || resolvedTitle;
|
|
904
|
+
if (!usage && durationMs == null && !id && !title) {
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
return { usage, durationMs, modelEntityId: id, modelTitle: title };
|
|
908
|
+
}, /* @ts-ignore */
|
|
909
|
+
...(ngDevMode ? [{ debugName: "assistRunDebug" }] : /* istanbul ignore next */ []));
|
|
910
|
+
this.ensureRunModelTitleLoaded = effect(() => {
|
|
911
|
+
if (!this.showDebugUi()) {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
const meta = this.message().metadata;
|
|
915
|
+
if (!meta) {
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
const storedTitle = typeof meta['assistRunModelTitle'] === 'string' ? meta['assistRunModelTitle'].trim() : '';
|
|
919
|
+
if (storedTitle) {
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const modelEntityId = typeof meta['assistRunModelEntityId'] === 'string' ? meta['assistRunModelEntityId'].trim() : '';
|
|
923
|
+
if (!modelEntityId) {
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
this.ensureModelTitleLoaded(modelEntityId);
|
|
927
|
+
}, /* @ts-ignore */
|
|
928
|
+
...(ngDevMode ? [{ debugName: "ensureRunModelTitleLoaded" }] : /* istanbul ignore next */ []));
|
|
929
|
+
this.assistResponseStatus = computed(() => axmReadAssistResponseStatus(this.message().metadata), /* @ts-ignore */
|
|
930
|
+
...(ngDevMode ? [{ debugName: "assistResponseStatus" }] : /* istanbul ignore next */ []));
|
|
931
|
+
this.showRetryScreen = computed(() => {
|
|
932
|
+
const message = this.message();
|
|
933
|
+
if (this.focus.focusedConversationIdSignal() !== message.conversationId) {
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
return axmIsAssistRetryableStatus(this.assistResponseStatus()) && !this.isRetrying();
|
|
937
|
+
}, /* @ts-ignore */
|
|
938
|
+
...(ngDevMode ? [{ debugName: "showRetryScreen" }] : /* istanbul ignore next */ []));
|
|
939
|
+
this.assistResponseError = computed(() => axmReadAssistResponseError(this.message().metadata), /* @ts-ignore */
|
|
940
|
+
...(ngDevMode ? [{ debugName: "assistResponseError" }] : /* istanbul ignore next */ []));
|
|
941
|
+
this.assistRetryTitleKey = computed(() => (this.assistResponseStatus() === 'failed' ? '@conversation:chat.assist-renderer.retry.failed.title' : '@conversation:chat.assist-renderer.retry.incomplete.title'), /* @ts-ignore */
|
|
942
|
+
...(ngDevMode ? [{ debugName: "assistRetryTitleKey" }] : /* istanbul ignore next */ []));
|
|
943
|
+
this.assistRetryDescriptionKey = computed(() => (this.assistResponseStatus() === 'failed' ? '@conversation:chat.assist-renderer.retry.failed.description' : '@conversation:chat.assist-renderer.retry.incomplete.description'), /* @ts-ignore */
|
|
944
|
+
...(ngDevMode ? [{ debugName: "assistRetryDescriptionKey" }] : /* istanbul ignore next */ []));
|
|
945
|
+
this.assistRetryAlertIcon = computed(() => (this.assistResponseStatus() === 'failed' ? 'fa-light fa-circle-exclamation' : 'fa-light fa-circle-pause'), /* @ts-ignore */
|
|
946
|
+
...(ngDevMode ? [{ debugName: "assistRetryAlertIcon" }] : /* istanbul ignore next */ []));
|
|
947
|
+
this.rendererState = computed(() => ({
|
|
948
|
+
deliveryStatus: this.message().status,
|
|
949
|
+
contentState: this.showRetryScreen() ? 'error' : this.contentState(),
|
|
950
|
+
}), /* @ts-ignore */
|
|
951
|
+
...(ngDevMode ? [{ debugName: "rendererState" }] : /* istanbul ignore next */ []));
|
|
952
|
+
//#endregion
|
|
953
|
+
//#region ---- Transcript UI ----
|
|
954
|
+
this.transcriptLines = computed(() => {
|
|
955
|
+
return axmReadAssistAiTranscript(this.message().metadata) ?? [];
|
|
956
|
+
}, /* @ts-ignore */
|
|
957
|
+
...(ngDevMode ? [{ debugName: "transcriptLines" }] : /* istanbul ignore next */ []));
|
|
958
|
+
this.delegatedTranscriptsByCallId = computed(() => {
|
|
959
|
+
const meta = this.message().metadata;
|
|
960
|
+
if (typeof meta !== 'object' || meta == null) {
|
|
961
|
+
return {};
|
|
962
|
+
}
|
|
963
|
+
const delegated = meta['assistRunDelegatedTranscriptsByCallId'];
|
|
964
|
+
if (typeof delegated !== 'object' || delegated == null || Array.isArray(delegated)) {
|
|
965
|
+
return {};
|
|
966
|
+
}
|
|
967
|
+
return delegated;
|
|
968
|
+
}, /* @ts-ignore */
|
|
969
|
+
...(ngDevMode ? [{ debugName: "delegatedTranscriptsByCallId" }] : /* istanbul ignore next */ []));
|
|
970
|
+
this.toolResultByCallId = computed(() => {
|
|
971
|
+
const map = new Map();
|
|
972
|
+
for (const line of this.transcriptLines()) {
|
|
973
|
+
if (line.role !== 'tool') {
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
for (const seg of line.responses) {
|
|
977
|
+
if (seg.type === 'tool_result' || seg.type === 'agent_result') {
|
|
978
|
+
const callId = seg.callId;
|
|
979
|
+
if (typeof callId === 'string' && callId.trim()) {
|
|
980
|
+
map.set(callId.trim(), line);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return map;
|
|
986
|
+
}, /* @ts-ignore */
|
|
987
|
+
...(ngDevMode ? [{ debugName: "toolResultByCallId" }] : /* istanbul ignore next */ []));
|
|
988
|
+
this.useTranscriptUi = computed(() => this.transcriptLines().length > 0, /* @ts-ignore */
|
|
989
|
+
...(ngDevMode ? [{ debugName: "useTranscriptUi" }] : /* istanbul ignore next */ []));
|
|
990
|
+
/** Last transcript line for the non-debug bubble ({@link axmParseAssistTranscriptLinePayload} or engine transcript). */
|
|
991
|
+
this.assistPayloadLine = computed(() => {
|
|
992
|
+
const fromPayload = axmParseAssistTranscriptLinePayload(this.message().payload);
|
|
993
|
+
if (fromPayload) {
|
|
994
|
+
return fromPayload;
|
|
995
|
+
}
|
|
996
|
+
const raw = axmReadAssistAiTranscriptRaw(this.message().metadata);
|
|
997
|
+
if (raw.length > 0) {
|
|
998
|
+
return axmAssistUserFacingPayloadLine(raw);
|
|
999
|
+
}
|
|
1000
|
+
return null;
|
|
1001
|
+
}, /* @ts-ignore */
|
|
1002
|
+
...(ngDevMode ? [{ debugName: "assistPayloadLine" }] : /* istanbul ignore next */ []));
|
|
1003
|
+
/**
|
|
1004
|
+
* Debug transcript hides user-facing answer segments when the unified payload bubble is shown.
|
|
1005
|
+
*/
|
|
1006
|
+
this.assistHideTranscriptAnswerSegments = computed(() => !this.isStreaming() && this.assistPayloadLine() != null, /* @ts-ignore */
|
|
1007
|
+
...(ngDevMode ? [{ debugName: "assistHideTranscriptAnswerSegments" }] : /* istanbul ignore next */ []));
|
|
1008
|
+
/**
|
|
1009
|
+
* Transcript lines that render at least one visible segment. Skips e.g. {@code tool} role lines
|
|
1010
|
+
* whose results are merged into assistant tool UI (would otherwise produce empty {@code axm-assist-bot__line}).
|
|
1011
|
+
*/
|
|
1012
|
+
this.visibleTranscriptLines = computed(() => {
|
|
1013
|
+
const lines = this.transcriptLines();
|
|
1014
|
+
const out = [];
|
|
1015
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
1016
|
+
const line = lines[lineIndex];
|
|
1017
|
+
if (this.transcriptLineHasVisibleUi(line)) {
|
|
1018
|
+
out.push({ line, lineIndex });
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return out;
|
|
1022
|
+
}, /* @ts-ignore */
|
|
1023
|
+
...(ngDevMode ? [{ debugName: "visibleTranscriptLines" }] : /* istanbul ignore next */ []));
|
|
1024
|
+
this.syncAgentTitles = effect(() => {
|
|
1025
|
+
const ids = new Set();
|
|
1026
|
+
for (const line of this.transcriptLines()) {
|
|
1027
|
+
if (line.role !== 'assistant') {
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
for (const seg of line.responses) {
|
|
1031
|
+
if (seg.type !== 'tool' && seg.type !== 'agent') {
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
const agentId = this.extractAgentIdFromCommand(seg.content.command);
|
|
1035
|
+
if (agentId) {
|
|
1036
|
+
ids.add(agentId);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
for (const tc of this.toolCalls()) {
|
|
1041
|
+
const agentId = this.extractAgentIdFromCommand(tc.name);
|
|
1042
|
+
if (agentId) {
|
|
1043
|
+
ids.add(agentId);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
for (const id of ids) {
|
|
1047
|
+
this.ensureAgentTitleLoaded(id);
|
|
1048
|
+
}
|
|
1049
|
+
}, /* @ts-ignore */
|
|
1050
|
+
...(ngDevMode ? [{ debugName: "syncAgentTitles" }] : /* istanbul ignore next */ []));
|
|
1051
|
+
//#endregion
|
|
1052
|
+
//#region ---- Legacy computed ----
|
|
1053
|
+
this.isStreaming = computed(() => {
|
|
1054
|
+
const status = this.assistResponseStatus();
|
|
1055
|
+
if (!axmIsAssistStreamingStatus(status)) {
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1058
|
+
const meta = this.message().metadata;
|
|
1059
|
+
if (axmAssistHasFinalizeMetadata(meta)) {
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
const transcripts = meta?.['transcripts'];
|
|
1063
|
+
return !(Array.isArray(transcripts) && transcripts.length > 0);
|
|
1064
|
+
}, /* @ts-ignore */
|
|
1065
|
+
...(ngDevMode ? [{ debugName: "isStreaming" }] : /* istanbul ignore next */ []));
|
|
1066
|
+
/** Phase label for the streaming activity strip (translation key suffix via {@link assistActivityMessageKey}). */
|
|
1067
|
+
this.assistStreamingPhase = computed(() => {
|
|
1068
|
+
if (!this.isStreaming()) {
|
|
1069
|
+
return 'starting';
|
|
1070
|
+
}
|
|
1071
|
+
const tools = this.toolCalls();
|
|
1072
|
+
if (tools.some((t) => t.state === 'running')) {
|
|
1073
|
+
return 'using-tools';
|
|
1074
|
+
}
|
|
1075
|
+
const think = this.streamThink().trim();
|
|
1076
|
+
const text = this.streamText().trim();
|
|
1077
|
+
if (think && !text) {
|
|
1078
|
+
return 'planning';
|
|
1079
|
+
}
|
|
1080
|
+
if (text) {
|
|
1081
|
+
return 'responding';
|
|
1082
|
+
}
|
|
1083
|
+
return 'starting';
|
|
1084
|
+
}, /* @ts-ignore */
|
|
1085
|
+
...(ngDevMode ? [{ debugName: "assistStreamingPhase" }] : /* istanbul ignore next */ []));
|
|
1086
|
+
this.assistActivityMessageKey = computed(() => {
|
|
1087
|
+
const phase = this.assistStreamingPhase();
|
|
1088
|
+
return `@conversation:chat.assist-renderer.activity.${phase}`;
|
|
1089
|
+
}, /* @ts-ignore */
|
|
1090
|
+
...(ngDevMode ? [{ debugName: "assistActivityMessageKey" }] : /* istanbul ignore next */ []));
|
|
1091
|
+
this.streamThink = computed(() => {
|
|
1092
|
+
const v = this.readMeta('streamDraftThink');
|
|
1093
|
+
return typeof v === 'string' ? v : '';
|
|
1094
|
+
}, /* @ts-ignore */
|
|
1095
|
+
...(ngDevMode ? [{ debugName: "streamThink" }] : /* istanbul ignore next */ []));
|
|
1096
|
+
this.streamText = computed(() => {
|
|
1097
|
+
const d = this.readMeta('streamDraftText');
|
|
1098
|
+
return typeof d === 'string' ? d : '';
|
|
1099
|
+
}, /* @ts-ignore */
|
|
1100
|
+
...(ngDevMode ? [{ debugName: "streamText" }] : /* istanbul ignore next */ []));
|
|
1101
|
+
/** Answer markdown while streaming (draft) or after finalize (payload); shared by debug and non-debug. */
|
|
1102
|
+
this.assistAnswerMarkdown = computed(() => {
|
|
1103
|
+
const live = axmSanitizeAssistUserAnswerText(this.streamText());
|
|
1104
|
+
if (this.isStreaming()) {
|
|
1105
|
+
return live;
|
|
1106
|
+
}
|
|
1107
|
+
return axmSanitizeAssistUserAnswerText(this.parsed().body.trim() || live);
|
|
1108
|
+
}, /* @ts-ignore */
|
|
1109
|
+
...(ngDevMode ? [{ debugName: "assistAnswerMarkdown" }] : /* istanbul ignore next */ []));
|
|
1110
|
+
/** Non-text payload segments shown below the answer after finalize. */
|
|
1111
|
+
this.assistPayloadAppendItems = computed(() => {
|
|
1112
|
+
if (this.isStreaming()) {
|
|
1113
|
+
return [];
|
|
1114
|
+
}
|
|
1115
|
+
const line = this.assistPayloadLine();
|
|
1116
|
+
if (!line) {
|
|
1117
|
+
return [];
|
|
1118
|
+
}
|
|
1119
|
+
return axmAssistUserVisibleItemsForLine(line, this.assistLineVisibilityOptions())
|
|
1120
|
+
.filter((item) => item.kind !== 'text')
|
|
1121
|
+
.sort((a, b) => {
|
|
1122
|
+
if (a.kind === 'followUp') {
|
|
1123
|
+
return 1;
|
|
1124
|
+
}
|
|
1125
|
+
if (b.kind === 'followUp') {
|
|
1126
|
+
return -1;
|
|
1127
|
+
}
|
|
1128
|
+
return 0;
|
|
1129
|
+
});
|
|
1130
|
+
}, /* @ts-ignore */
|
|
1131
|
+
...(ngDevMode ? [{ debugName: "assistPayloadAppendItems" }] : /* istanbul ignore next */ []));
|
|
1132
|
+
this.isFollowUpPreparing = computed(() => {
|
|
1133
|
+
if (this.isStreaming()) {
|
|
1134
|
+
return false;
|
|
1135
|
+
}
|
|
1136
|
+
return axmReadAssistFollowUpStatus(this.message().metadata) === 'preparing';
|
|
1137
|
+
}, /* @ts-ignore */
|
|
1138
|
+
...(ngDevMode ? [{ debugName: "isFollowUpPreparing" }] : /* istanbul ignore next */ []));
|
|
1139
|
+
this.assistEntityCardRefs = computed(() => {
|
|
1140
|
+
if (this.isStreaming() || this.showDebugUi()) {
|
|
1141
|
+
return [];
|
|
1142
|
+
}
|
|
1143
|
+
return axmAssistCollectEntityCardRefs(this.transcriptLines());
|
|
1144
|
+
}, /* @ts-ignore */
|
|
1145
|
+
...(ngDevMode ? [{ debugName: "assistEntityCardRefs" }] : /* istanbul ignore next */ []));
|
|
1146
|
+
this.assistPageActionResult = computed(() => {
|
|
1147
|
+
if (this.isStreaming() || this.showDebugUi()) {
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1150
|
+
return axmAssistReadPageActionResult(this.transcriptLines());
|
|
1151
|
+
}, /* @ts-ignore */
|
|
1152
|
+
...(ngDevMode ? [{ debugName: "assistPageActionResult" }] : /* istanbul ignore next */ []));
|
|
1153
|
+
this.assistPageActionLabel = computed(() => {
|
|
1154
|
+
const result = this.assistPageActionResult();
|
|
1155
|
+
if (!result?.label) {
|
|
1156
|
+
return '';
|
|
1157
|
+
}
|
|
1158
|
+
return axmResolveI18nLabel(result.label, (key) => this.translation.translateSync(key));
|
|
1159
|
+
}, /* @ts-ignore */
|
|
1160
|
+
...(ngDevMode ? [{ debugName: "assistPageActionLabel" }] : /* istanbul ignore next */ []));
|
|
1161
|
+
this.pageActionUndoBusy = signal(false, /* @ts-ignore */
|
|
1162
|
+
...(ngDevMode ? [{ debugName: "pageActionUndoBusy" }] : /* istanbul ignore next */ []));
|
|
1163
|
+
/**
|
|
1164
|
+
* Non-debug stream panel: skip the empty wrapper when there is no answer, append content, or live stream.
|
|
1165
|
+
*/
|
|
1166
|
+
this.showNonDebugStreamPanel = computed(() => this.isStreaming() ||
|
|
1167
|
+
this.assistAnswerMarkdown().trim().length > 0 ||
|
|
1168
|
+
this.assistPayloadAppendItems().length > 0 ||
|
|
1169
|
+
this.assistEntityCardRefs().length > 0 ||
|
|
1170
|
+
this.assistPageActionResult() != null ||
|
|
1171
|
+
this.isFollowUpPreparing(), /* @ts-ignore */
|
|
1172
|
+
...(ngDevMode ? [{ debugName: "showNonDebugStreamPanel" }] : /* istanbul ignore next */ []));
|
|
1173
|
+
this.toolCalls = computed(() => {
|
|
1174
|
+
const raw = this.readMeta('toolCalls');
|
|
1175
|
+
return Array.isArray(raw) ? raw : [];
|
|
1176
|
+
}, /* @ts-ignore */
|
|
1177
|
+
...(ngDevMode ? [{ debugName: "toolCalls" }] : /* istanbul ignore next */ []));
|
|
1178
|
+
this.parsed = computed(() => {
|
|
1179
|
+
if (this.isStreaming()) {
|
|
1180
|
+
return { thinking: null, body: '' };
|
|
1181
|
+
}
|
|
1182
|
+
const line = this.assistPayloadLine();
|
|
1183
|
+
if (line) {
|
|
1184
|
+
const textParts = axmAssistUserVisibleItemsForLine(line, this.assistLineVisibilityOptions())
|
|
1185
|
+
.filter((i) => i.kind === 'text')
|
|
1186
|
+
.map((i) => axmParseAssistTranscriptTextEnvelope(i.content).body.trim())
|
|
1187
|
+
.filter((t) => t.length > 0);
|
|
1188
|
+
return { thinking: null, body: axmSanitizeAssistUserAnswerText(textParts.join('\n\n')) };
|
|
1189
|
+
}
|
|
1190
|
+
const plainText = axmReadPlainTextMessagePayload(this.message().payload);
|
|
1191
|
+
if (plainText.trim()) {
|
|
1192
|
+
const envelope = axmParseAssistTranscriptTextEnvelope(plainText);
|
|
1193
|
+
return {
|
|
1194
|
+
thinking: envelope.thinking,
|
|
1195
|
+
body: axmSanitizeAssistUserAnswerText(envelope.body.trim()),
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
return { thinking: null, body: '' };
|
|
1199
|
+
}, /* @ts-ignore */
|
|
1200
|
+
...(ngDevMode ? [{ debugName: "parsed" }] : /* istanbul ignore next */ []));
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Assistant text shown in the transcript: hides delegated sub-run reflection lines from the main markdown stream
|
|
1204
|
+
* unless debug mode is on.
|
|
1205
|
+
*/
|
|
1206
|
+
assistTranscriptAssistantTextBodyForDisplay(line, body) {
|
|
1207
|
+
if (this.assistHideTranscriptAnswerSegments()) {
|
|
1208
|
+
return '';
|
|
1209
|
+
}
|
|
1210
|
+
if (axpAiChatMessageIsDelegatedReflectionExcluded(line) && !this.showDebugUi()) {
|
|
1211
|
+
return '';
|
|
1212
|
+
}
|
|
1213
|
+
return body ?? '';
|
|
1214
|
+
}
|
|
1215
|
+
transcriptLineHasVisibleUi(line) {
|
|
1216
|
+
const debug = this.showDebugUi();
|
|
1217
|
+
for (const seg of line.responses) {
|
|
1218
|
+
if (this.transcriptSegmentHasVisibleUi(line, seg, debug)) {
|
|
1219
|
+
return true;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
transcriptSegmentHasVisibleUi(line, seg, debug) {
|
|
1225
|
+
if (line.role === 'user' && seg.type === 'file') {
|
|
1226
|
+
return true;
|
|
1227
|
+
}
|
|
1228
|
+
if (line.role === 'user' && seg.type === 'text') {
|
|
1229
|
+
return true;
|
|
1230
|
+
}
|
|
1231
|
+
if (line.role === 'assistant' && seg.type === 'think' && debug) {
|
|
1232
|
+
return true;
|
|
1233
|
+
}
|
|
1234
|
+
if (line.role === 'assistant' && seg.type === 'text') {
|
|
1235
|
+
const parsed = this.parseAssistLineText(seg.content);
|
|
1236
|
+
if (debug && parsed.thinking) {
|
|
1237
|
+
return true;
|
|
1238
|
+
}
|
|
1239
|
+
if (debug && this.assistHideTranscriptAnswerSegments()) {
|
|
1240
|
+
return false;
|
|
1241
|
+
}
|
|
1242
|
+
return parsed.body.trim().length > 0;
|
|
1243
|
+
}
|
|
1244
|
+
if (line.role === 'assistant' && (seg.type === 'agent' || seg.type === 'tool') && this.isDelegatedAgentCommand(seg.content.command)) {
|
|
1245
|
+
if (debug) {
|
|
1246
|
+
return true;
|
|
1247
|
+
}
|
|
1248
|
+
const res = this.toolResultForCallId(seg.callId);
|
|
1249
|
+
const outcomeSegs = this.delegatedAgentOutcomeSegmentsForCall(res);
|
|
1250
|
+
const hasRenderableOutcome = outcomeSegs.some((s) => {
|
|
1251
|
+
if (s.type === 'file' || s.type === 'node') {
|
|
1252
|
+
return true;
|
|
1253
|
+
}
|
|
1254
|
+
if (s.type === 'text' || s.type === 'think') {
|
|
1255
|
+
return s.content.trim().length > 0;
|
|
1256
|
+
}
|
|
1257
|
+
if (s.type === 'followUp') {
|
|
1258
|
+
return s.content.length > 0;
|
|
1259
|
+
}
|
|
1260
|
+
return false;
|
|
1261
|
+
});
|
|
1262
|
+
if (hasRenderableOutcome) {
|
|
1263
|
+
return true;
|
|
1264
|
+
}
|
|
1265
|
+
return this.delegatedAgentAnswerForCall(res).trim().length > 0;
|
|
1266
|
+
}
|
|
1267
|
+
if (line.role === 'assistant' && seg.type === 'tool' && debug) {
|
|
1268
|
+
return true;
|
|
1269
|
+
}
|
|
1270
|
+
if (line.role === 'assistant' && seg.type === 'node') {
|
|
1271
|
+
if (debug && this.assistHideTranscriptAnswerSegments()) {
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
return axmChatRenderableAssistWidgetNode(seg.content) != null;
|
|
1275
|
+
}
|
|
1276
|
+
if (line.role === 'assistant' && seg.type === 'file') {
|
|
1277
|
+
if (debug && this.assistHideTranscriptAnswerSegments()) {
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
return true;
|
|
1281
|
+
}
|
|
1282
|
+
if (line.role === 'assistant' && seg.type === 'followUp') {
|
|
1283
|
+
if (debug) {
|
|
1284
|
+
return false;
|
|
1285
|
+
}
|
|
1286
|
+
return seg.content.length > 0;
|
|
1287
|
+
}
|
|
1288
|
+
if (line.role === 'tool' && (seg.type === 'tool_result' || seg.type === 'agent_result')) {
|
|
1289
|
+
return false;
|
|
1290
|
+
}
|
|
1291
|
+
if (line.role === 'system' && seg.type === 'text' && debug) {
|
|
1292
|
+
return true;
|
|
1293
|
+
}
|
|
1294
|
+
return debug || line.role !== 'assistant';
|
|
1295
|
+
}
|
|
1296
|
+
syntheticTextMessage(lineIndex, segIndex, text) {
|
|
1297
|
+
return axmSyntheticEmbedMessage(this.message(), `ut-${lineIndex}-${segIndex}`, {
|
|
1298
|
+
type: 'text',
|
|
1299
|
+
payload: { type: 'text', text },
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
syntheticNodeEmbedMessage(lineIndex, segIndex, content) {
|
|
1303
|
+
return axmSyntheticEmbedMessage(this.message(), `node-${lineIndex}-${segIndex}`, {
|
|
1304
|
+
type: 'node',
|
|
1305
|
+
payload: { type: 'node', content },
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
syntheticAppendNodeMessage(itemIndex, content) {
|
|
1309
|
+
return axmSyntheticEmbedMessage(this.message(), `append-node-${itemIndex}`, {
|
|
1310
|
+
type: 'node',
|
|
1311
|
+
payload: { type: 'node', content },
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
markdownCodeLanguage(segment) {
|
|
1315
|
+
if (this.isMarkdownCodeSegment(segment) && segment.language.trim()) {
|
|
1316
|
+
return segment.language;
|
|
1317
|
+
}
|
|
1318
|
+
return 'javascript';
|
|
1319
|
+
}
|
|
1320
|
+
markdownCodeValue(segment) {
|
|
1321
|
+
if (this.isMarkdownCodeSegment(segment)) {
|
|
1322
|
+
return segment.code;
|
|
1323
|
+
}
|
|
1324
|
+
return '';
|
|
1325
|
+
}
|
|
1326
|
+
resolveToolDisplayTitle(command) {
|
|
1327
|
+
const agentId = this.extractAgentIdFromCommand(command);
|
|
1328
|
+
if (!agentId) {
|
|
1329
|
+
return command?.trim() || 'Tool';
|
|
1330
|
+
}
|
|
1331
|
+
const title = this.agentTitleById()[agentId];
|
|
1332
|
+
if (title) {
|
|
1333
|
+
return title;
|
|
1334
|
+
}
|
|
1335
|
+
return agentId;
|
|
1336
|
+
}
|
|
1337
|
+
/** Faint label for the provider model used by a delegated agent (or tool call metadata). */
|
|
1338
|
+
resolveToolModelLabel(command, callId) {
|
|
1339
|
+
const fromCall = this.toolCallEntryFor(callId)?.modelLabel?.trim();
|
|
1340
|
+
if (fromCall) {
|
|
1341
|
+
return fromCall;
|
|
1342
|
+
}
|
|
1343
|
+
const scopedCallId = callId?.trim();
|
|
1344
|
+
if (scopedCallId) {
|
|
1345
|
+
const fromTranscript = this.delegatedTranscriptsByCallId()[scopedCallId]?.modelLabel?.trim();
|
|
1346
|
+
if (fromTranscript) {
|
|
1347
|
+
return fromTranscript;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return undefined;
|
|
1351
|
+
}
|
|
1352
|
+
resolveToolBadge(command, fallback) {
|
|
1353
|
+
return this.extractAgentIdFromCommand(command) ? '@conversation:chat.assist-renderer.badges.agent' : fallback;
|
|
1354
|
+
}
|
|
1355
|
+
toolResultForCallId(callId) {
|
|
1356
|
+
if (!callId || !callId.trim()) {
|
|
1357
|
+
return null;
|
|
1358
|
+
}
|
|
1359
|
+
return this.toolResultByCallId().get(callId.trim()) ?? null;
|
|
1360
|
+
}
|
|
1361
|
+
toolCallEntryFor(callId) {
|
|
1362
|
+
if (!callId?.trim()) {
|
|
1363
|
+
return undefined;
|
|
1364
|
+
}
|
|
1365
|
+
return this.toolCalls().find((t) => t.id === callId.trim());
|
|
1366
|
+
}
|
|
1367
|
+
delegatedPromptPreview(argumentsValue) {
|
|
1368
|
+
return axpAiDelegatedAgentPromptPreview(argumentsValue);
|
|
1369
|
+
}
|
|
1370
|
+
isDelegatedAgentCommand(command) {
|
|
1371
|
+
return axpAiParseSupervisorAgentToolName(command) != null;
|
|
1372
|
+
}
|
|
1373
|
+
delegatedAgentOutcomeSegmentsForCall(res) {
|
|
1374
|
+
if (!res) {
|
|
1375
|
+
return [];
|
|
1376
|
+
}
|
|
1377
|
+
const raw = axpAiChatToolOrAgentResultBodyJson(res);
|
|
1378
|
+
if (!raw.trim()) {
|
|
1379
|
+
return [];
|
|
1380
|
+
}
|
|
1381
|
+
try {
|
|
1382
|
+
const o = JSON.parse(raw);
|
|
1383
|
+
if (o['success'] === false) {
|
|
1384
|
+
return [];
|
|
1385
|
+
}
|
|
1386
|
+
return axpAiDelegatedAgentResultSegments(o);
|
|
1387
|
+
}
|
|
1388
|
+
catch {
|
|
1389
|
+
return [];
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
delegatedAnswerFromToolResult(resultLine) {
|
|
1393
|
+
if (!resultLine) {
|
|
1394
|
+
return '';
|
|
1395
|
+
}
|
|
1396
|
+
const raw = axpAiChatToolOrAgentResultBodyJson(resultLine);
|
|
1397
|
+
try {
|
|
1398
|
+
const o = JSON.parse(raw);
|
|
1399
|
+
if (o['success'] === false) {
|
|
1400
|
+
const err = o['error'];
|
|
1401
|
+
return typeof err === 'string' && err.trim() ? err.trim() : 'Request failed.';
|
|
1402
|
+
}
|
|
1403
|
+
const segs = axpAiDelegatedAgentResultSegments(o);
|
|
1404
|
+
return axpAiDelegatedAgentOutcomeResponsesPlainText(segs);
|
|
1405
|
+
}
|
|
1406
|
+
catch {
|
|
1407
|
+
return raw.trim();
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
isMarkdownCodeSegment(segment) {
|
|
1411
|
+
if (!segment || typeof segment !== 'object') {
|
|
1412
|
+
return false;
|
|
1413
|
+
}
|
|
1414
|
+
const candidate = segment;
|
|
1415
|
+
return candidate.type === 'code' && typeof candidate.language === 'string' && typeof candidate.code === 'string';
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Parses the same agent JSON body shape as {@link delegatedAnswerFromToolResult} from raw tool `output` stored in metadata.
|
|
1419
|
+
*/
|
|
1420
|
+
delegatedAnswerFromToolOutputString(output) {
|
|
1421
|
+
if (!output?.trim()) {
|
|
1422
|
+
return '';
|
|
1423
|
+
}
|
|
1424
|
+
const raw = output.trim();
|
|
1425
|
+
try {
|
|
1426
|
+
const o = JSON.parse(raw);
|
|
1427
|
+
if (o['success'] === false) {
|
|
1428
|
+
const err = o['error'];
|
|
1429
|
+
return typeof err === 'string' && err.trim() ? err.trim() : 'Request failed.';
|
|
1430
|
+
}
|
|
1431
|
+
const segs = axpAiDelegatedAgentResultSegments(o);
|
|
1432
|
+
return axpAiDelegatedAgentOutcomeResponsesPlainText(segs);
|
|
1433
|
+
}
|
|
1434
|
+
catch {
|
|
1435
|
+
return raw;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
delegatedAgentAnswerForCall(res) {
|
|
1439
|
+
return this.delegatedAnswerFromToolResult(res);
|
|
1440
|
+
}
|
|
1441
|
+
delegatedAgentLabelForCall(callId) {
|
|
1442
|
+
const resultLine = this.toolResultForCallId(callId);
|
|
1443
|
+
if (!resultLine) {
|
|
1444
|
+
return undefined;
|
|
1445
|
+
}
|
|
1446
|
+
return this.parseDelegatedAgentToolResult(axpAiChatToolOrAgentResultBodyJson(resultLine)).agentName;
|
|
1447
|
+
}
|
|
1448
|
+
async openDelegatedAgentTranscriptDialog(callId, toolName, resolvedAgentLabel) {
|
|
1449
|
+
await axmOpenAssistDelegatedAgentDetailDialog({
|
|
1450
|
+
callId: callId ?? '',
|
|
1451
|
+
toolName,
|
|
1452
|
+
delegatedTranscriptsByCallId: this.delegatedTranscriptsByCallId(),
|
|
1453
|
+
resolvedAgentLabel: resolvedAgentLabel ?? this.delegatedAgentLabelForCall(callId),
|
|
1454
|
+
resolveCatalogTitle: (agentId) => this.agentTitleById()[agentId],
|
|
1455
|
+
agentTitleById: this.agentTitleById(),
|
|
1456
|
+
popupService: this.popupService,
|
|
1457
|
+
translationService: this.translation,
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
async openAssistToolDetailDialog(options) {
|
|
1461
|
+
const outputJson = options.output?.trim() ||
|
|
1462
|
+
(options.resultLine ? this.formatToolResultBody(options.resultLine) : '');
|
|
1463
|
+
await axmOpenAssistToolDetailDialog({
|
|
1464
|
+
toolTitle: options.toolTitle,
|
|
1465
|
+
inputJson: this.formatArgs(options.argumentsValue),
|
|
1466
|
+
outputJson: outputJson.trim() || undefined,
|
|
1467
|
+
popupService: this.popupService,
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
async openTranscriptToolDetail(callId, command, argumentsValue, resolvedAgentLabel) {
|
|
1471
|
+
if (this.isDelegatedAgentCommand(command)) {
|
|
1472
|
+
await this.openDelegatedAgentTranscriptDialog(callId, command, resolvedAgentLabel);
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
await this.openAssistToolDetailDialog({
|
|
1476
|
+
toolTitle: this.resolveToolDisplayTitle(command),
|
|
1477
|
+
argumentsValue,
|
|
1478
|
+
resultLine: this.toolResultForCallId(callId),
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
async openLegacyToolDetail(tc) {
|
|
1482
|
+
if (this.isDelegatedAgentCommand(tc.name)) {
|
|
1483
|
+
await this.openDelegatedAgentTranscriptDialog(tc.id, tc.name, this.delegatedAgentLabelForCall(tc.id));
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
await this.openAssistToolDetailDialog({
|
|
1487
|
+
toolTitle: this.resolveToolDisplayTitle(tc.name),
|
|
1488
|
+
argumentsValue: tc.arguments,
|
|
1489
|
+
output: tc.output,
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
parseDelegatedAgentToolResult(content) {
|
|
1493
|
+
const trimmed = content?.trim() ?? '';
|
|
1494
|
+
if (!trimmed) {
|
|
1495
|
+
return {};
|
|
1496
|
+
}
|
|
1497
|
+
try {
|
|
1498
|
+
const o = JSON.parse(trimmed);
|
|
1499
|
+
if (o['success'] === false || o['success'] !== true) {
|
|
1500
|
+
return {};
|
|
1501
|
+
}
|
|
1502
|
+
const data = o['data'];
|
|
1503
|
+
if (data == null || typeof data !== 'object' || Array.isArray(data)) {
|
|
1504
|
+
return {};
|
|
1505
|
+
}
|
|
1506
|
+
const agentName = data['agentName'];
|
|
1507
|
+
return typeof agentName === 'string' && agentName.trim() ? { agentName: agentName.trim() } : {};
|
|
1508
|
+
}
|
|
1509
|
+
catch {
|
|
1510
|
+
return {};
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* Human-readable wall time for the assist API run (mock stores {@code assistRunDurationMs}).
|
|
1515
|
+
*/
|
|
1516
|
+
formatAssistRunDuration(ms) {
|
|
1517
|
+
if (!Number.isFinite(ms) || ms < 0) {
|
|
1518
|
+
return '—';
|
|
1519
|
+
}
|
|
1520
|
+
if (ms < 1000) {
|
|
1521
|
+
return `${Math.round(ms)} ms`;
|
|
1522
|
+
}
|
|
1523
|
+
return `${(ms / 1000).toFixed(1)} s`;
|
|
1524
|
+
}
|
|
1525
|
+
formatFallbackSegment(seg) {
|
|
1526
|
+
try {
|
|
1527
|
+
return JSON.stringify(seg, null, 2);
|
|
1528
|
+
}
|
|
1529
|
+
catch {
|
|
1530
|
+
return String(seg);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
formatToolResultBody(line) {
|
|
1534
|
+
return this.formatJson(axpAiChatToolOrAgentResultBodyJson(line));
|
|
1535
|
+
}
|
|
1536
|
+
assistThinkKey(lineIndex, segIndex) {
|
|
1537
|
+
return `think:${lineIndex}-${segIndex}`;
|
|
1538
|
+
}
|
|
1539
|
+
assistThinkStreamKey() {
|
|
1540
|
+
return 'think:stream';
|
|
1541
|
+
}
|
|
1542
|
+
assistThinkFinalKey() {
|
|
1543
|
+
return 'think:final';
|
|
1544
|
+
}
|
|
1545
|
+
assistRunMetaKey() {
|
|
1546
|
+
return 'run-meta';
|
|
1547
|
+
}
|
|
1548
|
+
assistAgentKey(callId) {
|
|
1549
|
+
return `agent:${(callId ?? '').trim() || 'unknown'}`;
|
|
1550
|
+
}
|
|
1551
|
+
assistToolKey(callId) {
|
|
1552
|
+
return `tool:${(callId ?? '').trim() || 'unknown'}`;
|
|
1553
|
+
}
|
|
1554
|
+
assistLegacyToolAccordionKey(tc) {
|
|
1555
|
+
return this.isDelegatedAgentCommand(tc.name) ? this.assistAgentKey(tc.id) : this.assistToolKey(tc.id);
|
|
1556
|
+
}
|
|
1557
|
+
onAssistLegacyAccordionClick(event, tc) {
|
|
1558
|
+
this.onAssistAccordionClick(event, this.assistLegacyToolAccordionKey(tc));
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Accordion is collapsed by default; open only when the user has persisted `open: true` for this key.
|
|
1562
|
+
*/
|
|
1563
|
+
assistSectionIsCollapsed(key) {
|
|
1564
|
+
return !this.isAssistSectionOpen(key);
|
|
1565
|
+
}
|
|
1566
|
+
isAssistSectionOpen(key) {
|
|
1567
|
+
const m = this.message().metadata;
|
|
1568
|
+
if (typeof m !== 'object' || m == null) {
|
|
1569
|
+
return false;
|
|
1570
|
+
}
|
|
1571
|
+
const assistUi = m['assistUi'];
|
|
1572
|
+
return assistUi?.openByKey?.[key] === true;
|
|
1573
|
+
}
|
|
1574
|
+
onAssistAccordionClick(event, key) {
|
|
1575
|
+
const c = event?.component;
|
|
1576
|
+
if (!c) {
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
const open = !c.isCollapsed();
|
|
1580
|
+
void this.persistAssistUiOpenKey(key, open);
|
|
1581
|
+
}
|
|
1582
|
+
async persistAssistUiOpenKey(key, open) {
|
|
1583
|
+
const message = this.message();
|
|
1584
|
+
const prev = (message.metadata ?? {});
|
|
1585
|
+
const prevUi = prev['assistUi'] ?? {};
|
|
1586
|
+
const openByKey = { ...(prevUi.openByKey ?? {}) };
|
|
1587
|
+
if (open) {
|
|
1588
|
+
openByKey[key] = true;
|
|
1589
|
+
}
|
|
1590
|
+
else {
|
|
1591
|
+
delete openByKey[key];
|
|
1592
|
+
}
|
|
1593
|
+
const nextMeta = { ...prev, assistUi: { ...prevUi, openByKey } };
|
|
1594
|
+
const updated = { ...message, metadata: nextMeta };
|
|
1595
|
+
await this.messageApi.updateMessageMetadata(message.id, nextMeta);
|
|
1596
|
+
}
|
|
1597
|
+
assistLineVisibilityOptions() {
|
|
1598
|
+
const conversationId = this.message().conversationId?.trim() ?? '';
|
|
1599
|
+
const active = this.conversationService.activeConversation();
|
|
1600
|
+
const conversation = active?.id === conversationId
|
|
1601
|
+
? active
|
|
1602
|
+
: this.conversationService.conversations().find((item) => item.id === conversationId);
|
|
1603
|
+
const metadata = conversation?.metadata;
|
|
1604
|
+
return axmIsFormTemplateDesignerAssistLaunch(metadata) ? { hideWidgetNodes: true } : {};
|
|
1605
|
+
}
|
|
1606
|
+
//#endregion
|
|
1607
|
+
//#region ----- Page Action -----
|
|
1608
|
+
async onUndoPageAction() {
|
|
1609
|
+
const result = this.assistPageActionResult();
|
|
1610
|
+
const actionId = result?.undoActionId?.trim();
|
|
1611
|
+
if (!result || !actionId || this.pageActionUndoBusy()) {
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
this.pageActionUndoBusy.set(true);
|
|
1615
|
+
try {
|
|
1616
|
+
await this.pageContext.runAction(actionId, result.undoArgs ?? {});
|
|
1617
|
+
}
|
|
1618
|
+
finally {
|
|
1619
|
+
this.pageActionUndoBusy.set(false);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
//#endregion
|
|
1623
|
+
//#region ---- Retry ----
|
|
1624
|
+
async onRetryAssistResponse() {
|
|
1625
|
+
if (this.isRetrying() || !this.showRetryScreen()) {
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
const message = this.message();
|
|
1629
|
+
this.isRetrying.set(true);
|
|
1630
|
+
try {
|
|
1631
|
+
await this.messageApi.retryAssistResponse(message.conversationId, message.id);
|
|
1632
|
+
}
|
|
1633
|
+
catch (error) {
|
|
1634
|
+
console.error('Failed to retry assist response:', error);
|
|
1635
|
+
}
|
|
1636
|
+
finally {
|
|
1637
|
+
this.isRetrying.set(false);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
//#endregion
|
|
1641
|
+
//#region ---- Formatters ----
|
|
1642
|
+
formatArgs(args) {
|
|
1643
|
+
if (args == null)
|
|
1644
|
+
return '';
|
|
1645
|
+
if (typeof args === 'string')
|
|
1646
|
+
return this.tryPrettyJson(args);
|
|
1647
|
+
try {
|
|
1648
|
+
return JSON.stringify(args, null, 2);
|
|
1649
|
+
}
|
|
1650
|
+
catch {
|
|
1651
|
+
return String(args);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
formatJson(raw) {
|
|
1655
|
+
return this.tryPrettyJson(raw);
|
|
1656
|
+
}
|
|
1657
|
+
//#endregion
|
|
1658
|
+
//#region ---- Utilities ----
|
|
1659
|
+
readMeta(key) {
|
|
1660
|
+
const meta = this.message().metadata;
|
|
1661
|
+
if (typeof meta !== 'object' || meta == null)
|
|
1662
|
+
return undefined;
|
|
1663
|
+
return meta[key];
|
|
1664
|
+
}
|
|
1665
|
+
tryPrettyJson(raw) {
|
|
1666
|
+
const trimmed = raw.trim();
|
|
1667
|
+
if (!trimmed)
|
|
1668
|
+
return '';
|
|
1669
|
+
try {
|
|
1670
|
+
return JSON.stringify(JSON.parse(trimmed), null, 2);
|
|
1671
|
+
}
|
|
1672
|
+
catch {
|
|
1673
|
+
return raw;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
extractAgentIdFromCommand(command) {
|
|
1677
|
+
if (typeof command !== 'string') {
|
|
1678
|
+
return null;
|
|
1679
|
+
}
|
|
1680
|
+
const [prefix, id] = command.split(':');
|
|
1681
|
+
if (prefix?.trim().toLowerCase() !== 'agent') {
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1684
|
+
const resolvedId = id?.trim();
|
|
1685
|
+
return resolvedId ? resolvedId : null;
|
|
1686
|
+
}
|
|
1687
|
+
ensureAgentTitleLoaded(agentId) {
|
|
1688
|
+
if (!agentId || this.agentTitleById()[agentId] || this.loadingAgentIds.has(agentId)) {
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
this.loadingAgentIds.add(agentId);
|
|
1692
|
+
void this.agentData
|
|
1693
|
+
.byKey(agentId)
|
|
1694
|
+
.then((agent) => {
|
|
1695
|
+
const title = (this.translation.resolve(agent?.title)?.trim() || agent?.name?.trim() || agent?.id || agentId).trim();
|
|
1696
|
+
this.agentTitleById.update((current) => ({ ...current, [agentId]: title }));
|
|
1697
|
+
})
|
|
1698
|
+
.catch(() => {
|
|
1699
|
+
this.agentTitleById.update((current) => ({ ...current, [agentId]: agentId }));
|
|
1700
|
+
})
|
|
1701
|
+
.finally(() => {
|
|
1702
|
+
this.loadingAgentIds.delete(agentId);
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
ensureModelTitleLoaded(modelEntityId) {
|
|
1706
|
+
if (!modelEntityId || this.modelTitleById()[modelEntityId] || this.loadingModelIds.has(modelEntityId)) {
|
|
1707
|
+
return;
|
|
1708
|
+
}
|
|
1709
|
+
this.loadingModelIds.add(modelEntityId);
|
|
1710
|
+
void this.modelCatalog
|
|
1711
|
+
.listModels()
|
|
1712
|
+
.then((rows) => {
|
|
1713
|
+
const match = rows.find((row) => row.id === modelEntityId);
|
|
1714
|
+
const title = match?.title?.trim() || match?.name?.trim() || modelEntityId;
|
|
1715
|
+
this.modelTitleById.update((current) => ({ ...current, [modelEntityId]: title }));
|
|
1716
|
+
})
|
|
1717
|
+
.catch(() => {
|
|
1718
|
+
this.modelTitleById.update((current) => ({ ...current, [modelEntityId]: modelEntityId }));
|
|
1719
|
+
})
|
|
1720
|
+
.finally(() => {
|
|
1721
|
+
this.loadingModelIds.delete(modelEntityId);
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMAssistMessageRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1725
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMAssistMessageRendererComponent, isStandalone: true, selector: "axm-assist-message-renderer", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"axm-assist-renderer\">\n @if (showDebugUi() && useTranscriptUi() && !isStreaming()) {\n <div class=\"axm-assist-bot\" role=\"region\" [attr.aria-label]=\"('@conversation:chat.assist-renderer.aria.assistant-response' | translate | async) ?? ''\">\n @for (item of visibleTranscriptLines(); track item.lineIndex) {\n @let line = item.line;\n @let lineIndex = item.lineIndex;\n <div class=\"axm-assist-bot__line\" [class.axm-assist-bot__line--user]=\"line.role === 'user'\" [class.axm-assist-bot__line--tool-role]=\"line.role === 'tool'\">\n @for (seg of line.responses; track segIndex; let segIndex = $index) {\n @if (line.role === 'user' && seg.type === 'file') {\n <axm-assist-bot-file-line [parentMessage]=\"message()\" [fileId]=\"seg.content.fileId\" />\n } @else if (line.role === 'user' && seg.type === 'text') {\n <div class=\"axm-assist-bot__segment\" [attr.dir]=\"segmentDir(seg.content)\">\n <ax-conversation-text-renderer [message]=\"syntheticTextMessage(lineIndex, segIndex, seg.content)\" />\n </div>\n } @else if (line.role === 'assistant' && seg.type === 'think' && showDebugUi()) {\n <div class=\"axm-assist-renderer__accordion-group\" axAccordionGroup [accordion]=\"false\">\n <div\n class=\"axm-assist-renderer__thinking-accordion axm-assist-renderer__collapse-shell mb-0!\"\n axAccordionItem\n #thinkAcc=\"axAccordionItem\"\n [isCollapsed]=\"assistSectionIsCollapsed(assistThinkKey(lineIndex, segIndex))\"\n (onClick)=\"onAssistAccordionClick($event, assistThinkKey(lineIndex, segIndex))\"\n >\n <div class=\"axm-assist-renderer__accordion-header\" axAccordionItemHeader role=\"button\" [attr.aria-expanded]=\"!thinkAcc.isCollapsed()\">\n <div class=\"axm-assist-renderer__accordion-header-main\">\n <span class=\"axm-assist-renderer__accordion-header-title\">\n <i class=\"fa-light fa-chevron-down axm-assist-renderer__thinking-chevron\" [class.axm-assist-renderer__thinking-chevron--expanded]=\"!thinkAcc.isCollapsed()\" aria-hidden=\"true\"></i>\n <i class=\"fa-light fa-brain axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n {{ '@conversation:chat.assist-renderer.sections.thinking' | translate | async }}\n </span>\n </div>\n </div>\n <div axAccordionItemContent>\n <div class=\"axm-assist-renderer__collapse-body\">\n <div class=\"axm-assist-renderer__thinking-body axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(seg.content)\">\n <axp-markdown-viewer [markdown]=\"seg.content\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n </div>\n </div>\n </div>\n </div>\n } @else if (line.role === 'assistant' && seg.type === 'text') {\n @let parsedLine = parseAssistLineText(seg.content);\n @let displayBody = assistTranscriptAssistantTextBodyForDisplay(line, parsedLine.body);\n @let partitionedLine = partitionAnswerText(displayBody);\n @if (showDebugUi() && parsedLine.thinking) {\n <div class=\"axm-assist-renderer__accordion-group\" axAccordionGroup [accordion]=\"false\">\n <div\n class=\"axm-assist-renderer__thinking-accordion axm-assist-renderer__collapse-shell mb-0!\"\n axAccordionItem\n #thinkAcc=\"axAccordionItem\"\n [isCollapsed]=\"assistSectionIsCollapsed(assistThinkKey(lineIndex, segIndex))\"\n (onClick)=\"onAssistAccordionClick($event, assistThinkKey(lineIndex, segIndex))\"\n >\n <div class=\"axm-assist-renderer__accordion-header\" axAccordionItemHeader role=\"button\" [attr.aria-expanded]=\"!thinkAcc.isCollapsed()\">\n <div class=\"axm-assist-renderer__accordion-header-main\">\n <span class=\"axm-assist-renderer__accordion-header-title\">\n <i class=\"fa-light fa-chevron-down axm-assist-renderer__thinking-chevron\" [class.axm-assist-renderer__thinking-chevron--expanded]=\"!thinkAcc.isCollapsed()\" aria-hidden=\"true\"></i>\n <i class=\"fa-light fa-brain axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n {{ '@conversation:chat.assist-renderer.sections.thinking' | translate | async }}\n </span>\n </div>\n </div>\n <div axAccordionItemContent>\n <div class=\"axm-assist-renderer__collapse-body\">\n <div class=\"axm-assist-renderer__thinking-body axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(parsedLine.thinking)\">\n <axp-markdown-viewer [markdown]=\"parsedLine.thinking ?? ''\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n @if (partitionedLine.markdown.trim()) {\n <div class=\"axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(partitionedLine.markdown)\">\n <axp-markdown-viewer [markdown]=\"partitionedLine.markdown\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n }\n @for (renderable of partitionedLine.renderable; track $index) {\n @if (renderable.type === 'node' && axmAssistWidgetNodeFromUnknown(renderable.content)) {\n <div class=\"axm-assist-bot__segment\" dir=\"auto\">\n <axm-chat-user-form-renderer [message]=\"syntheticNodeEmbedMessage(lineIndex, segIndex * 100 + $index, renderable.content)\" />\n </div>\n }\n }\n } @else if (line.role === 'assistant' && (seg.type === 'agent' || seg.type === 'tool') && isDelegatedAgentCommand(seg.content.command)) {\n @if (showDebugUi()) {\n <div class=\"axm-assist-renderer__tool-row\">\n <div class=\"axm-assist-renderer__tool-row-main\">\n <i class=\"fa-light fa-people-group axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n <span class=\"axm-assist-renderer__tool-row-title\">{{ resolveToolDisplayTitle(seg.content.command) }}</span>\n @if (resolveToolModelLabel(seg.content.command, seg.callId); as modelLabel) {\n <span class=\"axm-assist-renderer__tool-model-label\">{{ modelLabel }}</span>\n }\n </div>\n <ax-button\n look=\"outline\"\n color=\"primary\"\n class=\"ax-xs\"\n [text]=\"('@conversation:chat.assist-renderer.delegated-agent.detail' | translate | async) ?? ''\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.delegated-agent.detail-aria' | translate | async) ?? ''\"\n (onClick)=\"openTranscriptToolDetail(seg.callId, seg.content.command, seg.content.arguments, delegatedAgentLabelForCall(seg.callId))\"\n ></ax-button>\n </div>\n } @else {\n <div class=\"axm-assist-renderer__tool-row\">\n <div class=\"axm-assist-renderer__tool-row-main\">\n <i class=\"fa-light fa-people-group axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n <span class=\"axm-assist-renderer__tool-row-title\">{{ resolveToolDisplayTitle(seg.content.command) }}</span>\n @if (resolveToolModelLabel(seg.content.command, seg.callId); as modelLabel) {\n <span class=\"axm-assist-renderer__tool-model-label\">{{ modelLabel }}</span>\n }\n </div>\n <ax-button\n look=\"outline\"\n color=\"primary\"\n class=\"ax-xs\"\n [text]=\"('@conversation:chat.assist-renderer.delegated-agent.detail' | translate | async) ?? ''\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.delegated-agent.detail-aria' | translate | async) ?? ''\"\n (onClick)=\"openTranscriptToolDetail(seg.callId, seg.content.command, seg.content.arguments, delegatedAgentLabelForCall(seg.callId))\"\n ></ax-button>\n </div>\n @let res = toolResultForCallId(seg.callId);\n @let outcomeSegs = delegatedAgentOutcomeSegmentsForCall(res);\n @if (outcomeSegs.length > 0) {\n @for (outSeg of outcomeSegs; track outSegIndex; let outSegIndex = $index) {\n @switch (outSeg.type) {\n @case ('text') {\n @let parsedOut = parseAssistLineText(outSeg.content);\n @let partitionedOut = partitionAnswerText(parsedOut.body);\n @if (partitionedOut.markdown.trim()) {\n <div class=\"axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(partitionedOut.markdown)\">\n <axp-markdown-viewer [markdown]=\"partitionedOut.markdown\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n }\n @for (renderable of partitionedOut.renderable; track renderableIndex; let renderableIndex = $index) {\n @if (renderable.type === 'node' && axmAssistWidgetNodeFromUnknown(renderable.content)) {\n <div class=\"axm-assist-bot__segment\" dir=\"auto\">\n <axm-chat-user-form-renderer [message]=\"syntheticNodeEmbedMessage(lineIndex, segIndex * 1000 + outSegIndex * 10 + renderableIndex, renderable.content)\" />\n </div>\n }\n }\n }\n @case ('think') {\n @if (showDebugUi()) {\n <div class=\"axm-assist-bot__segment\" [attr.dir]=\"segmentDir(outSeg.content)\">\n {{ outSeg.content }}\n </div>\n }\n }\n @case ('file') {\n <axm-assist-bot-file-line [parentMessage]=\"message()\" [fileId]=\"outSeg.content.fileId\" />\n }\n @case ('node') {\n @let assistNodeWidget = axmAssistWidgetNodeFromUnknown(outSeg.content);\n @if (assistNodeWidget) {\n <div class=\"axm-assist-bot__segment\" dir=\"auto\">\n <axm-chat-user-form-renderer [message]=\"syntheticNodeEmbedMessage(lineIndex, segIndex * 1000 + outSegIndex, outSeg.content)\" />\n </div>\n }\n }\n }\n }\n } @else if (delegatedAgentAnswerForCall(res).trim()) {\n @let agentFallbackText = delegatedAgentAnswerForCall(res);\n <div class=\"axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(agentFallbackText)\">\n <axp-markdown-viewer [markdown]=\"agentFallbackText\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n }\n }\n } @else if (line.role === 'assistant' && seg.type === 'tool' && showDebugUi()) {\n <div class=\"axm-assist-renderer__tool-row\">\n <div class=\"axm-assist-renderer__tool-row-main\">\n <i class=\"fa-light fa-screwdriver-wrench axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n <span class=\"axm-assist-renderer__tool-row-title\">{{ resolveToolDisplayTitle(seg.content.command) }}</span>\n </div>\n <ax-button\n look=\"outline\"\n color=\"primary\"\n class=\"ax-xs\"\n [text]=\"('@conversation:chat.assist-renderer.delegated-agent.detail' | translate | async) ?? ''\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.delegated-agent.detail-aria' | translate | async) ?? ''\"\n (onClick)=\"openTranscriptToolDetail(seg.callId, seg.content.command, seg.content.arguments)\"\n ></ax-button>\n </div>\n } @else if (line.role === 'assistant' && seg.type === 'node') {\n @let assistNodeWidget = axmAssistWidgetNodeFromUnknown(seg.content);\n @if (assistNodeWidget) {\n <div class=\"axm-assist-bot__segment\" dir=\"auto\">\n <axm-chat-user-form-renderer [message]=\"syntheticNodeEmbedMessage(lineIndex, segIndex, seg.content)\" />\n </div>\n }\n } @else if (line.role === 'assistant' && seg.type === 'file' && !assistHideTranscriptAnswerSegments()) {\n <axm-assist-bot-file-line [parentMessage]=\"message()\" [fileId]=\"seg.content.fileId\" />\n } @else if (line.role === 'tool' && (seg.type === 'tool_result' || seg.type === 'agent_result')) {\n <!-- merged into the corresponding tool/agent accordion -->\n } @else if (line.role === 'system' && seg.type === 'text' && showDebugUi()) {\n <div class=\"axm-assist-bot__segment\" [attr.dir]=\"segmentDir(seg.content)\">\n <ax-conversation-text-renderer [message]=\"syntheticTextMessage(lineIndex, segIndex, seg.content)\" />\n </div>\n } @else {\n @if (showDebugUi() || line.role !== 'assistant') {\n <div class=\"axm-assist-bot__fallback axm-assist-bot__segment\" [attr.dir]=\"segmentDir(formatFallbackSegment(seg))\">\n {{ formatFallbackSegment(seg) }}\n </div>\n }\n }\n }\n </div>\n }\n </div>\n <ng-container *ngTemplateOutlet=\"assistUnifiedAnswerBlock\" />\n } @else if (!showDebugUi() && showNonDebugStreamPanel()) {\n <div class=\"axm-assist-renderer__stream-content axm-assist-renderer__stream-content--non-debug\" role=\"region\" [attr.aria-label]=\"('@conversation:chat.assist-renderer.aria.assistant-response' | translate | async) ?? ''\">\n @if (isStreaming()) {\n <div class=\"axm-assist-renderer__streaming-activity\" role=\"status\" aria-live=\"polite\">\n <span class=\"axm-assist-renderer__activity-spin inline-block w-3 h-3 rounded-full border-2 border-primary-500 border-t-transparent fa-spin shrink-0\" aria-hidden=\"true\"></span>\n <span class=\"axm-assist-renderer__activity-label text-xs text-muted italic\">{{ assistActivityMessageKey() | translate | async }}</span>\n </div>\n }\n <ng-container *ngTemplateOutlet=\"assistUnifiedAnswerBlock\" />\n @if (isStreaming() && !assistAnswerMarkdown()) {\n <div class=\"axm-assist-renderer__waiting\">\n <span class=\"axm-assist-renderer__dot\"></span>\n <span class=\"axm-assist-renderer__dot\"></span>\n <span class=\"axm-assist-renderer__dot\"></span>\n </div>\n }\n </div>\n } @else if (showDebugUi()) {\n @if (isStreaming()) {\n <div class=\"axm-assist-renderer__stream-content\">\n <div class=\"axm-assist-renderer__streaming-activity\" role=\"status\" aria-live=\"polite\">\n <span class=\"axm-assist-renderer__activity-spin inline-block w-3 h-3 rounded-full border-2 border-primary-500 border-t-transparent fa-spin shrink-0\" aria-hidden=\"true\"></span>\n <span class=\"axm-assist-renderer__activity-label text-xs text-muted italic\">{{ assistActivityMessageKey() | translate | async }}</span>\n </div>\n @if (streamThink().trim()) {\n <div class=\"axm-assist-renderer__accordion-group\" axAccordionGroup [accordion]=\"false\">\n <div\n class=\"axm-assist-renderer__thinking-accordion axm-assist-renderer__collapse-shell mb-0!\"\n axAccordionItem\n #thinkAcc=\"axAccordionItem\"\n [isCollapsed]=\"assistSectionIsCollapsed(assistThinkStreamKey())\"\n (onClick)=\"onAssistAccordionClick($event, assistThinkStreamKey())\"\n >\n <div class=\"axm-assist-renderer__accordion-header\" axAccordionItemHeader role=\"button\" [attr.aria-expanded]=\"!thinkAcc.isCollapsed()\">\n <div class=\"axm-assist-renderer__accordion-header-main\">\n <span class=\"axm-assist-renderer__accordion-header-title\">\n <i class=\"fa-light fa-chevron-down axm-assist-renderer__thinking-chevron\" [class.axm-assist-renderer__thinking-chevron--expanded]=\"!thinkAcc.isCollapsed()\" aria-hidden=\"true\"></i>\n <i class=\"fa-light fa-brain axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n {{ '@conversation:chat.assist-renderer.sections.thinking' | translate | async }}\n </span>\n </div>\n </div>\n <div axAccordionItemContent>\n <div class=\"axm-assist-renderer__collapse-body\">\n <div class=\"axm-assist-renderer__thinking-body axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(streamThink())\">\n <axp-markdown-viewer [markdown]=\"streamThink()\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n <ng-container *ngTemplateOutlet=\"assistUnifiedAnswerBlock\" />\n @if (toolCalls().length) {\n <div class=\"axm-assist-renderer__tools\">\n @for (tc of toolCalls(); track tc.id) {\n @if (isDelegatedAgentCommand(tc.name)) {\n <div class=\"axm-assist-renderer__tool-row\">\n <div class=\"axm-assist-renderer__tool-row-main\">\n @if (tc.state === 'running') {\n <span class=\"axm-assist-renderer__agent-live-spin inline-block w-3 h-3 rounded-full border-2 border-primary-500 border-t-transparent fa-spin shrink-0\" aria-hidden=\"true\"></span>\n } @else {\n <span class=\"inline-block w-3 h-3 rounded-full bg-success-500 shrink-0\" aria-hidden=\"true\"></span>\n }\n <i class=\"fa-light fa-people-group axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n <span class=\"axm-assist-renderer__tool-row-title\">{{ resolveToolDisplayTitle(tc.name) }}</span>\n @if (resolveToolModelLabel(tc.name, tc.id); as modelLabel) {\n <span class=\"axm-assist-renderer__tool-model-label\">{{ modelLabel }}</span>\n }\n </div>\n <ax-button\n look=\"outline\"\n color=\"primary\"\n class=\"ax-xs\"\n [disabled]=\"tc.state === 'running'\"\n [text]=\"('@conversation:chat.assist-renderer.delegated-agent.detail' | translate | async) ?? ''\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.delegated-agent.detail-aria' | translate | async) ?? ''\"\n (onClick)=\"openLegacyToolDetail(tc)\"\n ></ax-button>\n </div>\n @if (tc.nestedStreamText?.trim()) {\n <div class=\"axm-assist-renderer__agent-live-stream\" [attr.dir]=\"segmentDir(tc.nestedStreamText ?? '')\">\n {{ tc.nestedStreamText }}\n </div>\n }\n } @else {\n <div class=\"axm-assist-renderer__tool-row\">\n <div class=\"axm-assist-renderer__tool-row-main\">\n @if (tc.state === 'running') {\n <span class=\"axm-assist-renderer__agent-live-spin inline-block w-3 h-3 rounded-full border-2 border-primary-500 border-t-transparent fa-spin shrink-0\" aria-hidden=\"true\"></span>\n } @else {\n <span class=\"inline-block w-3 h-3 rounded-full bg-success-500 shrink-0\" aria-hidden=\"true\"></span>\n }\n <i class=\"fa-light fa-screwdriver-wrench axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n <span class=\"axm-assist-renderer__tool-row-title\">{{ resolveToolDisplayTitle(tc.name) }}</span>\n </div>\n <ax-button\n look=\"outline\"\n color=\"primary\"\n class=\"ax-xs\"\n [disabled]=\"tc.state === 'running'\"\n [text]=\"('@conversation:chat.assist-renderer.delegated-agent.detail' | translate | async) ?? ''\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.delegated-agent.detail-aria' | translate | async) ?? ''\"\n (onClick)=\"openLegacyToolDetail(tc)\"\n ></ax-button>\n </div>\n }\n }\n </div>\n }\n @if (isStreaming() && !assistAnswerMarkdown() && !streamThink().trim() && !toolCalls().length) {\n <div class=\"axm-assist-renderer__waiting\">\n <span class=\"axm-assist-renderer__dot\"></span>\n <span class=\"axm-assist-renderer__dot\"></span>\n <span class=\"axm-assist-renderer__dot\"></span>\n </div>\n }\n </div>\n } @else if (!useTranscriptUi()) {\n @if (parsed().thinking) {\n <div class=\"axm-assist-renderer__accordion-group\" axAccordionGroup [accordion]=\"false\">\n <div\n class=\"axm-assist-renderer__thinking-accordion axm-assist-renderer__collapse-shell mb-0!\"\n axAccordionItem\n #thinkAcc=\"axAccordionItem\"\n [isCollapsed]=\"assistSectionIsCollapsed(assistThinkFinalKey())\"\n (onClick)=\"onAssistAccordionClick($event, assistThinkFinalKey())\"\n >\n <div class=\"axm-assist-renderer__accordion-header\" axAccordionItemHeader role=\"button\" [attr.aria-expanded]=\"!thinkAcc.isCollapsed()\">\n <div class=\"axm-assist-renderer__accordion-header-main\">\n <span class=\"axm-assist-renderer__accordion-header-title\">\n <i class=\"fa-light fa-chevron-down axm-assist-renderer__thinking-chevron\" [class.axm-assist-renderer__thinking-chevron--expanded]=\"!thinkAcc.isCollapsed()\" aria-hidden=\"true\"></i>\n <i class=\"fa-light fa-brain axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n {{ '@conversation:chat.assist-renderer.sections.thinking' | translate | async }}\n </span>\n </div>\n </div>\n <div axAccordionItemContent>\n <div class=\"axm-assist-renderer__collapse-body\">\n <div class=\"axm-assist-renderer__thinking-body axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(parsed().thinking)\">\n <axp-markdown-viewer [markdown]=\"parsed().thinking ?? ''\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n @if (toolCalls().length) {\n <div class=\"axm-assist-renderer__tools\">\n @for (tc of toolCalls(); track tc.id) {\n <div class=\"axm-assist-renderer__tool-row\">\n <div class=\"axm-assist-renderer__tool-row-main\">\n <i class=\"fa-light axm-assist-renderer__tool-title-icon\" [class.fa-people-group]=\"isDelegatedAgentCommand(tc.name)\" [class.fa-screwdriver-wrench]=\"!isDelegatedAgentCommand(tc.name)\" aria-hidden=\"true\"></i>\n <span class=\"axm-assist-renderer__tool-row-title\">{{ resolveToolDisplayTitle(tc.name) }}</span>\n @if (isDelegatedAgentCommand(tc.name) && resolveToolModelLabel(tc.name, tc.id); as modelLabel) {\n <span class=\"axm-assist-renderer__tool-model-label\">{{ modelLabel }}</span>\n }\n </div>\n <ax-button\n look=\"outline\"\n color=\"primary\"\n class=\"ax-xs\"\n [text]=\"('@conversation:chat.assist-renderer.delegated-agent.detail' | translate | async) ?? ''\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.delegated-agent.detail-aria' | translate | async) ?? ''\"\n (onClick)=\"openLegacyToolDetail(tc)\"\n ></ax-button>\n </div>\n }\n </div>\n }\n <ng-container *ngTemplateOutlet=\"assistUnifiedAnswerBlock\" />\n }\n }\n @if (showDebugUi() && assistRunDebug(); as dbg) {\n <footer class=\"axm-assist-renderer__run-meta-wrap\" role=\"region\" [attr.aria-label]=\"('@conversation:chat.assist-renderer.aria.run-usage' | translate | async) ?? ''\">\n <div class=\"axm-assist-renderer__accordion-group\" axAccordionGroup [accordion]=\"false\">\n <div\n class=\"axm-assist-renderer__thinking-accordion axm-assist-renderer__collapse-shell axm-assist-renderer__run-meta-accordion mb-0!\"\n axAccordionItem\n #runMetaAcc=\"axAccordionItem\"\n [isCollapsed]=\"assistSectionIsCollapsed(assistRunMetaKey())\"\n (onClick)=\"onAssistAccordionClick($event, assistRunMetaKey())\"\n >\n <div class=\"axm-assist-renderer__accordion-header\" axAccordionItemHeader role=\"button\" [attr.aria-expanded]=\"!runMetaAcc.isCollapsed()\">\n <div class=\"axm-assist-renderer__accordion-header-main\">\n <span class=\"axm-assist-renderer__accordion-header-title\">\n <i class=\"fa-light fa-chevron-down axm-assist-renderer__thinking-chevron\" [class.axm-assist-renderer__thinking-chevron--expanded]=\"!runMetaAcc.isCollapsed()\" aria-hidden=\"true\"></i>\n <i class=\"fa-light fa-chart-line axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n {{ '@conversation:chat.assist-renderer.run-meta.title' | translate | async }}\n </span>\n <!-- @if (dbg.modelTitle || dbg.modelEntityId) {\n <span class=\"axm-assist-renderer__run-meta-header-sub\">{{ dbg.modelTitle || dbg.modelEntityId }}</span>\n } -->\n </div>\n </div>\n <div axAccordionItemContent>\n <div class=\"axm-assist-renderer__collapse-body\">\n <div class=\"axm-assist-renderer__run-meta axm-assist-renderer__run-meta--panel\">\n <div class=\"axm-assist-renderer__run-meta-grid\">\n @if (dbg.modelTitle || dbg.modelEntityId) {\n <div class=\"axm-assist-renderer__meta-tile axm-assist-renderer__meta-tile--wide\">\n <div class=\"axm-assist-renderer__meta-tile-label\">\n {{ '@conversation:chat.assist-renderer.run-meta.model' | translate | async }}\n </div>\n <div class=\"axm-assist-renderer__meta-tile-value\">{{ dbg.modelTitle || dbg.modelEntityId }}</div>\n <!-- @if (dbg.modelTitle && dbg.modelEntityId) {\n <div class=\"axm-assist-renderer__meta-tile-sub\">{{ dbg.modelEntityId }}</div>\n } -->\n </div>\n }\n @if (dbg.durationMs != null) {\n <div class=\"axm-assist-renderer__meta-tile\">\n <div class=\"axm-assist-renderer__meta-tile-label\">\n {{ '@conversation:chat.assist-renderer.run-meta.duration' | translate | async }}\n </div>\n <div class=\"axm-assist-renderer__meta-tile-value axm-assist-renderer__meta-tile-value--mono\">\n {{ formatAssistRunDuration(dbg.durationMs) }}\n </div>\n </div>\n }\n @if (dbg.usage; as totals) {\n <div class=\"axm-assist-renderer__meta-tile\">\n <div class=\"axm-assist-renderer__meta-tile-label\">\n {{ '@ai-management:chat.usage-prompt' | translate | async }}\n </div>\n <div class=\"axm-assist-renderer__meta-tile-value axm-assist-renderer__meta-tile-value--mono\">\n {{ totals.promptTokens | number }}\n @if (totals.cachedPromptTokens) {\n <span class=\"axm-assist-renderer__meta-tile-note\">(+{{ totals.cachedPromptTokens | number }} {{ '@ai-management:chat.usage-cached' | translate | async }})</span>\n }\n </div>\n </div>\n <div class=\"axm-assist-renderer__meta-tile\">\n <div class=\"axm-assist-renderer__meta-tile-label\">\n {{ '@ai-management:chat.usage-completion' | translate | async }}\n </div>\n <div class=\"axm-assist-renderer__meta-tile-value axm-assist-renderer__meta-tile-value--mono\">\n {{ totals.completionTokens | number }}\n </div>\n </div>\n <div class=\"axm-assist-renderer__meta-tile axm-assist-renderer__meta-tile--accent\">\n <div class=\"axm-assist-renderer__meta-tile-label\">\n {{ '@conversation:chat.assist-renderer.run-meta.total-tokens' | translate | async }}\n </div>\n <div class=\"axm-assist-renderer__meta-tile-value axm-assist-renderer__meta-tile-value--mono\">\n {{ totals.totalTokens | number }}\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </footer>\n }\n\n <ng-template #assistUnifiedAnswerBlock>\n @if (assistAnswerMarkdown()) {\n <div class=\"axm-assist-renderer__text axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(assistAnswerMarkdown())\">\n <axp-markdown-viewer [markdown]=\"assistAnswerMarkdown()\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n <!-- @if (isStreaming()) {\n <span class=\"axm-assist-renderer__cursor\"></span>\n } -->\n }\n @if (!isStreaming()) {\n @if (assistPageActionResult(); as pageAction) {\n <!-- ----- Live page chrome result (filter / search) ----- -->\n <div class=\"axm-assist-renderer__page-action\" role=\"status\">\n <span class=\"axm-assist-renderer__page-action-icon\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-circle-check\"></i>\n </span>\n <div class=\"axm-assist-renderer__page-action-copy\">\n <span class=\"axm-assist-renderer__page-action-title\">\n {{\n (pageAction.kind === 'search'\n ? '@conversation:mini-chat.page-action.search-applied'\n : '@conversation:mini-chat.page-action.filter-applied') | translate | async\n }}\n </span>\n @if (assistPageActionLabel(); as actionLabel) {\n <span class=\"axm-assist-renderer__page-action-label\">{{ (actionLabel | translate | async) ?? actionLabel }}</span>\n }\n </div>\n @if (pageAction.undoActionId) {\n <ax-button\n class=\"ax-xs\"\n look=\"blank\"\n color=\"primary\"\n [disabled]=\"pageActionUndoBusy()\"\n [text]=\"('@conversation:mini-chat.page-action.undo' | translate | async) ?? ''\"\n (onClick)=\"onUndoPageAction()\"\n ></ax-button>\n }\n </div>\n }\n @if (assistEntityCardRefs().length > 0) {\n <!-- ----- Entity rows from query/get tools, rendered as shared cards ----- -->\n <div class=\"axm-assist-renderer__entity-cards\">\n @for (ref of assistEntityCardRefs(); track ref.entity + ':' + ref.id) {\n <axm-chat-entity-card [entity]=\"ref.entity\" [entityId]=\"ref.id\" [variant]=\"ref.variant\" />\n }\n </div>\n }\n @for (item of assistPayloadAppendItems(); track $index; let itemIndex = $index) {\n @switch (item.kind) {\n @case ('node') {\n @if (axmAssistWidgetNodeFromUnknown(item.content)) {\n <div class=\"axm-assist-bot__segment\" dir=\"auto\">\n <axm-chat-user-form-renderer [message]=\"syntheticAppendNodeMessage(itemIndex, item.content)\" />\n </div>\n }\n }\n @case ('file') {\n <axm-assist-bot-file-line [parentMessage]=\"message()\" [fileId]=\"item.fileId\" />\n }\n @case ('followUp') {\n <div class=\"axm-assist-bot__segment\" dir=\"auto\">\n <axm-assist-suggestion-chips [items]=\"item.items\" />\n </div>\n }\n }\n }\n <!---->\n <!-- ----- Follow-up suggestions preparing ----- -->\n @if (isFollowUpPreparing()) {\n <div\n class=\"axm-assist-renderer__streaming-activity axm-assist-renderer__follow-up-preparing\"\n role=\"status\"\n aria-live=\"polite\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.follow-up.aria' | translate | async) ?? ''\"\n >\n <span class=\"axm-assist-renderer__activity-spin inline-block w-3 h-3 rounded-full border-2 border-primary-500 border-t-transparent fa-spin shrink-0\" aria-hidden=\"true\"></span>\n <span class=\"axm-assist-renderer__activity-label text-xs text-muted italic\">{{ '@conversation:chat.assist-renderer.follow-up.preparing' | translate | async }}</span>\n </div>\n }\n }\n </ng-template>\n\n @if (showRetryScreen()) {\n <ax-alert class=\"axm-assist-renderer__retry-alert\" color=\"danger\" role=\"alert\" [attr.aria-label]=\"('@conversation:chat.assist-renderer.retry.aria.panel' | translate | async) ?? ''\">\n <ax-icon><i [class]=\"assistRetryAlertIcon()\"></i></ax-icon>\n <ax-title>{{ assistRetryTitleKey() | translate | async }}</ax-title>\n <ax-content>\n <div class=\"axm-assist-renderer__retry-alert-body\">\n <div class=\"axm-assist-renderer__retry-alert-text\">\n <p class=\"axm-assist-renderer__retry-description\">\n {{ assistRetryDescriptionKey() | translate | async }}\n </p>\n <!---->\n <!-- ----- Technical error text only in debug mode ----- -->\n @if (showDebugUi() && assistResponseError()) {\n <p class=\"axm-assist-renderer__retry-error-detail\">\n {{ assistResponseError() }}\n </p>\n }\n </div>\n\n <ax-button class=\"axm-assist-renderer__retry-action ax-sm\" color=\"danger\" look=\"solid\" [disabled]=\"isRetrying()\" [text]=\"('@conversation:chat.assist-renderer.retry.action' | translate | async) ?? ''\" (onClick)=\"onRetryAssistResponse()\">\n <i class=\"fa-light fa-arrows-rotate\" prefix></i>\n </ax-button>\n </div>\n </ax-content>\n </ax-alert>\n }\n</div>\n", styles: ["@layer properties;.axm-assist-renderer{display:flex;flex-direction:column;gap:.25rem}.axm-assist-renderer axm-assist-suggestion-chips>div{padding:0;padding-top:calc(var(--spacing, .25rem) * 4)}.axm-assist-renderer .axm-assist-renderer__cursor{display:inline-block;inline-size:6px;block-size:1em;margin-inline-start:2px;background:rgb(var(--ax-sys-color-primary-500));border-radius:1px;animation:axm-blink .8s steps(2) infinite;vertical-align:text-bottom}.axm-assist-renderer .axm-assist-renderer__stream-content{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}.axm-assist-renderer .axm-assist-renderer__streaming-activity{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 2);padding-block:.15rem}.axm-assist-renderer .axm-assist-renderer__activity-label{line-height:1.45}.axm-assist-renderer .axm-assist-renderer__follow-up-preparing{padding-block-start:.75rem}.axm-assist-renderer .axm-assist-renderer__text{white-space:pre-line;word-break:break-word;line-height:1.85;font-size:.95rem;letter-spacing:.002em;text-wrap:pretty}.axm-assist-renderer .axm-assist-renderer__markdown .axp-markdown-viewer__content :is(p,ul,ol){margin-block:.45rem}.axm-assist-renderer .axm-assist-renderer__markdown .axp-markdown-viewer__content li{line-height:1.8}.axm-assist-renderer .axm-assist-renderer__accordion-group{width:100%}.axm-assist-renderer .axm-assist-renderer__collapse-shell{border-radius:.625rem;overflow:hidden;background:rgb(var(--ax-sys-color-lighter-surface))}.axm-assist-renderer .axm-assist-renderer__collapse-shell>.axm-assist-renderer__accordion-header:hover{background:rgb(var(--ax-sys-color-light-surface))}.axm-assist-renderer .axm-assist-renderer__collapse-body{margin:0;padding:0;border-radius:0 0 .5rem .5rem;background:transparent}.axm-assist-renderer .axm-assist-renderer__collapse-body>.axm-assist-renderer__agent-live-block--in-accordion{padding-inline:.5rem;padding-block-end:.35rem}.axm-assist-renderer .axm-assist-renderer__accordion-header{display:flex;cursor:pointer;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);--tw-border-style: none;border-style:none;padding:.5rem .75rem;background:transparent;border-radius:.625rem .625rem 0 0;transition:background-color .15s ease}.axm-assist-renderer .axm-assist-renderer__accordion-header-main{display:flex;flex-direction:column;min-inline-size:0;gap:.1rem}.axm-assist-renderer .axm-assist-renderer__accordion-header-title{display:inline-flex;align-items:center;--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);font-size:.8125rem;line-height:1.25;color:rgb(var(--ax-sys-color-on-surface));gap:.4rem}.axm-assist-renderer .axm-assist-renderer__header-trailing{display:inline-flex;flex-shrink:0;align-items:center;gap:.35rem}.axm-assist-renderer .axm-assist-renderer__delegated-agent-detail-link{text-decoration-line:underline;padding:.25rem;padding-top:0}.axm-assist-renderer .axm-assist-renderer__badge{display:inline-flex;flex-shrink:0;align-items:center;justify-content:center;overflow:hidden;border-radius:calc(infinity * 1px);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);text-overflow:ellipsis;white-space:nowrap;max-width:min(100%,14rem);padding:.1875rem .65rem;font-size:.6875rem;line-height:1.3;letter-spacing:.03em}.axm-assist-renderer .axm-assist-renderer__badge--warning{background:rgb(var(--ax-sys-color-warning-100))}.axm-assist-renderer .axm-assist-renderer__badge--primary{background:rgb(var(--ax-sys-color-primary-100))}.axm-assist-renderer .axm-assist-renderer__badge--success{background:rgb(var(--ax-sys-color-success-100))}.axm-assist-renderer .axm-assist-renderer__thinking-chevron{display:inline-block;flex-shrink:0;font-size:.65rem;opacity:.65;transition:transform .2s ease;line-height:1}.axm-assist-renderer .axm-assist-renderer__thinking-chevron--expanded{transform:rotate(180deg)}.axm-assist-renderer .axm-assist-renderer__thinking-body{padding:.625rem .75rem .75rem;font-size:.78rem;color:rgb(var(--ax-sys-color-neutral-700));word-break:break-word;line-height:1.55}.axm-assist-renderer .axm-assist-renderer__thinking-body.axm-assist-renderer__markdown .axp-markdown-viewer__content{font-size:inherit;color:inherit;line-height:inherit}.axm-assist-renderer .axm-assist-renderer__thinking-body.axm-assist-renderer__markdown .axp-markdown-viewer__content :is(p,ul,ol){margin-block:.35rem}.axm-assist-renderer .axm-assist-renderer__thinking-body.axm-assist-renderer__markdown .axp-markdown-viewer__content li{line-height:inherit}.axm-assist-renderer .axm-assist-renderer__tools{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}.axm-assist-renderer .axm-assist-renderer__tool-row{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);padding:.5rem .65rem;border-radius:.625rem;background:rgb(var(--ax-sys-color-lighter-surface));border:1px solid rgb(var(--ax-sys-color-border-lighter-surface))}.axm-assist-renderer .axm-assist-renderer__tool-row-main{display:flex;min-width:0px;align-items:center;gap:.45rem}.axm-assist-renderer .axm-assist-renderer__tool-row-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);color:rgb(var(--ax-sys-color-on-surface))}.axm-assist-renderer .axm-assist-renderer__tool-model-label{flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-normal, 400);font-weight:var(--font-weight-normal, 400);max-width:9rem;letter-spacing:.01em;color:rgb(var(--ax-sys-color-neutral-600));opacity:.78}.axm-assist-renderer .axm-assist-renderer__tool-card{border-radius:.625rem;overflow:hidden}.axm-assist-renderer .axm-assist-renderer__tool-body{display:flex;flex-direction:column;padding:.625rem .75rem .75rem;gap:.35rem}.axm-assist-renderer .axm-assist-renderer__tool-section-label{--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);text-transform:uppercase;font-size:.625rem;letter-spacing:.06em;color:rgb(var(--ax-sys-color-neutral-700))}.axm-assist-renderer .axm-assist-renderer__tool-pre{margin:0;overflow:auto;border-radius:var(--radius-md, .375rem);max-block-size:10rem;background:rgb(var(--ax-sys-color-light-surface));padding:.5rem;font-family:var(--ax-font-mono, monospace);font-size:.675rem;line-height:1.45;word-break:break-word}.axm-assist-renderer .axm-assist-renderer__run-meta-wrap{margin-block-start:.45rem}.axm-assist-renderer .axm-assist-renderer__run-meta-header-sub{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.7rem;line-height:1.35;color:rgb(var(--ax-sys-color-neutral-700));max-width:100%}.axm-assist-renderer .axm-assist-renderer__run-meta{display:flex;flex-direction:column;margin-block-start:.45rem;padding:.55rem .65rem .6rem;border-radius:.625rem;background:rgb(var(--ax-sys-color-lighter-surface));gap:.45rem}.axm-assist-renderer .axm-assist-renderer__run-meta--panel{margin-block-start:0;padding:.45rem .5rem .55rem;background:transparent;border-radius:0}.axm-assist-renderer .axm-assist-renderer__run-meta-grid{display:flex;gap:.4rem}.axm-assist-renderer .axm-assist-renderer__meta-tile{min-width:0px;padding:.4rem .5rem .45rem;border-radius:.45rem;background:rgb(var(--ax-sys-color-light-surface))}.axm-assist-renderer .axm-assist-renderer__meta-tile--wide{grid-column:1/-1}.axm-assist-renderer .axm-assist-renderer__meta-tile--accent{background:rgb(var(--ax-sys-color-primary-50))}.axm-assist-renderer .axm-assist-renderer__meta-tile-label{--tw-font-weight: var(--font-weight-bold, 700);font-weight:var(--font-weight-bold, 700);text-transform:uppercase;font-size:.6rem;letter-spacing:.06em;color:rgb(var(--ax-sys-color-neutral-700));margin-block-end:.2rem}.axm-assist-renderer .axm-assist-renderer__meta-tile-value{--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);font-size:.78rem;line-height:1.35;color:rgb(var(--ax-sys-color-on-surface));word-break:break-word}.axm-assist-renderer .axm-assist-renderer__meta-tile-value--mono{font-variant-numeric:tabular-nums;font-feature-settings:\"tnum\" 1}.axm-assist-renderer .axm-assist-renderer__meta-tile-sub{margin-block-start:.15rem;font-size:.65rem;line-height:1.3;color:rgb(var(--ax-sys-color-neutral-700));font-family:var(--ax-font-mono, ui-monospace, monospace);word-break:break-all}.axm-assist-renderer .axm-assist-renderer__meta-tile-note{display:inline;--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);margin-inline-start:.2rem;font-size:.65rem;color:rgb(var(--ax-sys-color-success-700))}.axm-assist-renderer .axm-assist-renderer__tool-editor-wrap{overflow:hidden;border-radius:var(--radius-md, .375rem);background:rgb(var(--ax-sys-color-light-surface))}.axm-assist-renderer .axm-assist-renderer__waiting{display:flex;gap:.25rem;padding:.25rem 0}.axm-assist-renderer .axm-assist-renderer__dot{inline-size:6px;block-size:6px;border-radius:50%;background:rgb(var(--ax-sys-color-neutral-500));animation:axm-blink 1.2s ease-in-out infinite}.axm-assist-renderer .axm-assist-renderer__dot:nth-child(2){animation-delay:.2s}.axm-assist-renderer .axm-assist-renderer__dot:nth-child(3){animation-delay:.4s}.axm-assist-renderer .axm-assist-renderer__agent-live-block{display:flex;flex-direction:column;border-radius:.75rem;background:rgb(var(--ax-sys-color-lighter-surface));padding:.75rem .875rem;gap:.65rem;max-inline-size:min(42rem,100%)}.axm-assist-renderer .axm-assist-renderer__agent-live-block--in-accordion{background:rgb(var(--ax-sys-color-light-surface));border-radius:.5rem;padding:.5rem .55rem .55rem;max-inline-size:none}.axm-assist-renderer .axm-assist-renderer__agent-live-header{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}.axm-assist-renderer .axm-assist-renderer__agent-live-title{flex:1;--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);font-size:.8125rem;color:rgb(var(--ax-sys-color-on-surface));min-inline-size:0}.axm-assist-renderer .axm-assist-renderer__agent-live-prompt{align-self:flex-end;font-size:.8125rem;line-height:1.55;word-break:break-word;padding:.5rem .75rem;border-radius:1.25rem;background:rgb(var(--ax-sys-color-lightest-surface));max-inline-size:min(92%,36rem)}.axm-assist-renderer .axm-assist-renderer__agent-live-stream{font-size:.8125rem;line-height:1.55;white-space:pre-line;word-break:break-word;padding-block-start:.15rem}.axm-assist-renderer .axm-assist-renderer__agent-live-nested-tools{display:flex;flex-direction:column;gap:.35rem;padding-block-start:.35rem}.axm-assist-renderer .axm-assist-renderer__agent-live-nested-row{display:flex;align-items:center;gap:.45rem;font-size:.72rem}.axm-assist-renderer .axm-assist-renderer__agent-live-nested-name{flex:1;overflow:hidden;text-overflow:ellipsis;font-family:var(--ax-font-mono, monospace);font-size:.7rem;min-inline-size:0}.axm-assist-renderer .axm-assist-renderer__agent-live-nested-status{flex-shrink:0;color:rgb(var(--ax-sys-color-neutral-600))}.axm-assist-renderer .axm-assist-renderer__retry-alert-body{display:flex;align-items:flex-start;gap:.75rem}.axm-assist-renderer .axm-assist-renderer__retry-alert-text{display:flex;flex:1;flex-direction:column;min-inline-size:0;gap:.3rem}.axm-assist-renderer .axm-assist-renderer__retry-description{margin:0;font-size:.8125rem;line-height:1.5;opacity:.92}.axm-assist-renderer .axm-assist-renderer__retry-error-detail{margin:.15rem 0 0;font-size:.75rem;line-height:1.45;font-family:var(--ax-font-mono, ui-monospace, monospace);word-break:break-word;opacity:.88}.axm-assist-renderer .axm-assist-renderer__retry-action{flex-shrink:0;align-self:center}.axm-assist-bot,.axm-assist-bot .axm-assist-bot__final-line{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3)}.axm-assist-bot .axm-assist-bot__segment{unicode-bidi:isolate;font-size:.95rem;line-height:1.8;letter-spacing:.002em;color:rgb(var(--ax-sys-color-on-surface));text-wrap:pretty}.axm-assist-bot .axm-assist-bot__segment:has(axm-chat-user-form-renderer){inline-size:100%;max-inline-size:100%}.axm-assist-bot .axm-assist-bot__segment ax-conversation-text-renderer{display:block}.axm-assist-bot .axm-assist-bot__segment ax-conversation-text-renderer :is(p,ul,ol){margin-block:.45rem}.axm-assist-bot .axm-assist-bot__segment ax-conversation-text-renderer li{line-height:1.8}.axm-assist-bot .axm-assist-bot__line--user{align-self:flex-end}.axm-assist-bot .axm-assist-bot__line--tool-role{align-self:stretch}.axm-assist-bot .axm-assist-bot__tool-result{border-radius:var(--radius-lg, .5rem);background:rgb(var(--ax-sys-color-lighter-surface));padding:.5rem .75rem}.axm-assist-bot .axm-assist-bot__fallback{font-size:.88rem;line-height:1.75;word-break:break-word;opacity:.9}.axm-assist-bot .axm-assist-renderer__page-action{display:flex;align-items:flex-start;gap:.625rem;margin-block-start:.5rem;padding:.625rem .75rem;border-radius:.75rem;border:1px solid rgb(var(--ax-sys-color-border-success-lightest-surface));background:rgb(var(--ax-sys-color-success-50))}.axm-assist-bot .axm-assist-renderer__page-action-icon{display:grid;place-items:center;width:1.25rem;height:1.25rem;color:rgb(var(--ax-sys-color-success-600));flex-shrink:0;margin-block-start:.1rem}.axm-assist-bot .axm-assist-renderer__page-action-copy{display:flex;flex-direction:column;gap:.125rem;min-width:0;flex:1}.axm-assist-bot .axm-assist-renderer__page-action-title{font-size:.8125rem;font-weight:600;color:rgb(var(--ax-sys-color-on-surface))}.axm-assist-bot .axm-assist-renderer__page-action-label{font-size:.75rem;line-height:1.4;color:rgb(var(--ax-sys-color-on-surface));opacity:.72}.axm-assist-bot .axm-assist-renderer__entity-cards{display:flex;flex-direction:column;gap:.625rem;margin-block-start:.5rem}@keyframes axm-blink{0%{opacity:1}50%{opacity:0}}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: AXConversationTextRendererComponent, selector: "ax-conversation-text-renderer", inputs: ["message"] }, { kind: "component", type: AXMNodeMessageRendererComponent, selector: "axm-chat-user-form-renderer", inputs: ["message", "node"], outputs: ["formSubmitted"] }, { kind: "component", type: AXMAssistBotFileLineComponent, selector: "axm-assist-bot-file-line", inputs: ["parentMessage", "fileId"] }, { kind: "component", type: AXMAssistSuggestionChipsComponent, selector: "axm-assist-suggestion-chips", inputs: ["items", "compact", "sendInPlace", "disabled"], outputs: ["conversationCreated"] }, { kind: "component", type: AXMChatEntityCardComponent, selector: "axm-chat-entity-card", inputs: ["entity", "entityId", "variant"] }, { kind: "ngmodule", type: AXAccordionCdkModule }, { kind: "directive", type: i1$1.AXAccordionGroupDirective, selector: "[axAccordionGroup]", inputs: ["accordion", "activeIndex", "collapsedOnItemClick"], exportAs: ["axAccordionGroup"] }, { kind: "directive", type: i1$1.AXAccordionItemContentDirective, selector: "[axAccordionItemContent]", inputs: ["transition"], exportAs: ["axAccordionItemContent"] }, { kind: "directive", type: i1$1.AXAccordionItemHeaderDirective, selector: "[axAccordionItemHeader]", exportAs: ["axAccordionItemHeader"] }, { kind: "directive", type: i1$1.AXAccordionItemDirective, selector: "[axAccordionItem]", inputs: ["isCollapsed"], outputs: ["isCollapsedChange", "onClick"], exportAs: ["axAccordionItem"] }, { kind: "ngmodule", type: AXAlertModule }, { kind: "component", type: i2.AXAlertComponent, selector: "ax-alert", inputs: ["color", "timeOut"], outputs: ["colorChange", "onClosed", "timeOutChange"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$2.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXCodeEditorModule }, { kind: "component", type: i5.AXCodeEditorComponent, selector: "ax-code-editor", inputs: ["disabled", "value", "state", "name", "id", "language", "readonly", "placeholder", "lineNumbers", "lineWrapping", "tabSize", "indentWithTab", "theme", "extensions", "ariaLabel", "focusOnReady", "formatOnSave", "minRow", "customCompletions"], outputs: ["onValueChanged", "valueChange", "stateChange", "readonlyChange", "disabledChange", "ready", "save"] }, { kind: "component", type: AXPMarkdownViewerComponent, selector: "axp-markdown-viewer", inputs: ["markdown"] }, { kind: "directive", type: AXPMarkdownTemplateDirective, selector: "ng-template[axpMarkdownTemplate]", inputs: ["axpMarkdownTemplate"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1.AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
1726
|
+
}
|
|
1727
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMAssistMessageRendererComponent, decorators: [{
|
|
1728
|
+
type: Component,
|
|
1729
|
+
args: [{ selector: 'axm-assist-message-renderer', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [
|
|
1730
|
+
AXConversationTextRendererComponent,
|
|
1731
|
+
AXMNodeMessageRendererComponent,
|
|
1732
|
+
AXMAssistBotFileLineComponent,
|
|
1733
|
+
AXMAssistSuggestionChipsComponent,
|
|
1734
|
+
AXMChatEntityCardComponent,
|
|
1735
|
+
AXAccordionCdkModule,
|
|
1736
|
+
AXAlertModule,
|
|
1737
|
+
AXButtonModule,
|
|
1738
|
+
AXDecoratorModule,
|
|
1739
|
+
AXCodeEditorModule,
|
|
1740
|
+
AXPMarkdownViewerComponent,
|
|
1741
|
+
AXPMarkdownTemplateDirective,
|
|
1742
|
+
FormsModule,
|
|
1743
|
+
AXTranslationModule,
|
|
1744
|
+
AsyncPipe,
|
|
1745
|
+
DecimalPipe,
|
|
1746
|
+
NgTemplateOutlet,
|
|
1747
|
+
], template: "<div class=\"axm-assist-renderer\">\n @if (showDebugUi() && useTranscriptUi() && !isStreaming()) {\n <div class=\"axm-assist-bot\" role=\"region\" [attr.aria-label]=\"('@conversation:chat.assist-renderer.aria.assistant-response' | translate | async) ?? ''\">\n @for (item of visibleTranscriptLines(); track item.lineIndex) {\n @let line = item.line;\n @let lineIndex = item.lineIndex;\n <div class=\"axm-assist-bot__line\" [class.axm-assist-bot__line--user]=\"line.role === 'user'\" [class.axm-assist-bot__line--tool-role]=\"line.role === 'tool'\">\n @for (seg of line.responses; track segIndex; let segIndex = $index) {\n @if (line.role === 'user' && seg.type === 'file') {\n <axm-assist-bot-file-line [parentMessage]=\"message()\" [fileId]=\"seg.content.fileId\" />\n } @else if (line.role === 'user' && seg.type === 'text') {\n <div class=\"axm-assist-bot__segment\" [attr.dir]=\"segmentDir(seg.content)\">\n <ax-conversation-text-renderer [message]=\"syntheticTextMessage(lineIndex, segIndex, seg.content)\" />\n </div>\n } @else if (line.role === 'assistant' && seg.type === 'think' && showDebugUi()) {\n <div class=\"axm-assist-renderer__accordion-group\" axAccordionGroup [accordion]=\"false\">\n <div\n class=\"axm-assist-renderer__thinking-accordion axm-assist-renderer__collapse-shell mb-0!\"\n axAccordionItem\n #thinkAcc=\"axAccordionItem\"\n [isCollapsed]=\"assistSectionIsCollapsed(assistThinkKey(lineIndex, segIndex))\"\n (onClick)=\"onAssistAccordionClick($event, assistThinkKey(lineIndex, segIndex))\"\n >\n <div class=\"axm-assist-renderer__accordion-header\" axAccordionItemHeader role=\"button\" [attr.aria-expanded]=\"!thinkAcc.isCollapsed()\">\n <div class=\"axm-assist-renderer__accordion-header-main\">\n <span class=\"axm-assist-renderer__accordion-header-title\">\n <i class=\"fa-light fa-chevron-down axm-assist-renderer__thinking-chevron\" [class.axm-assist-renderer__thinking-chevron--expanded]=\"!thinkAcc.isCollapsed()\" aria-hidden=\"true\"></i>\n <i class=\"fa-light fa-brain axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n {{ '@conversation:chat.assist-renderer.sections.thinking' | translate | async }}\n </span>\n </div>\n </div>\n <div axAccordionItemContent>\n <div class=\"axm-assist-renderer__collapse-body\">\n <div class=\"axm-assist-renderer__thinking-body axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(seg.content)\">\n <axp-markdown-viewer [markdown]=\"seg.content\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n </div>\n </div>\n </div>\n </div>\n } @else if (line.role === 'assistant' && seg.type === 'text') {\n @let parsedLine = parseAssistLineText(seg.content);\n @let displayBody = assistTranscriptAssistantTextBodyForDisplay(line, parsedLine.body);\n @let partitionedLine = partitionAnswerText(displayBody);\n @if (showDebugUi() && parsedLine.thinking) {\n <div class=\"axm-assist-renderer__accordion-group\" axAccordionGroup [accordion]=\"false\">\n <div\n class=\"axm-assist-renderer__thinking-accordion axm-assist-renderer__collapse-shell mb-0!\"\n axAccordionItem\n #thinkAcc=\"axAccordionItem\"\n [isCollapsed]=\"assistSectionIsCollapsed(assistThinkKey(lineIndex, segIndex))\"\n (onClick)=\"onAssistAccordionClick($event, assistThinkKey(lineIndex, segIndex))\"\n >\n <div class=\"axm-assist-renderer__accordion-header\" axAccordionItemHeader role=\"button\" [attr.aria-expanded]=\"!thinkAcc.isCollapsed()\">\n <div class=\"axm-assist-renderer__accordion-header-main\">\n <span class=\"axm-assist-renderer__accordion-header-title\">\n <i class=\"fa-light fa-chevron-down axm-assist-renderer__thinking-chevron\" [class.axm-assist-renderer__thinking-chevron--expanded]=\"!thinkAcc.isCollapsed()\" aria-hidden=\"true\"></i>\n <i class=\"fa-light fa-brain axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n {{ '@conversation:chat.assist-renderer.sections.thinking' | translate | async }}\n </span>\n </div>\n </div>\n <div axAccordionItemContent>\n <div class=\"axm-assist-renderer__collapse-body\">\n <div class=\"axm-assist-renderer__thinking-body axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(parsedLine.thinking)\">\n <axp-markdown-viewer [markdown]=\"parsedLine.thinking ?? ''\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n @if (partitionedLine.markdown.trim()) {\n <div class=\"axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(partitionedLine.markdown)\">\n <axp-markdown-viewer [markdown]=\"partitionedLine.markdown\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n }\n @for (renderable of partitionedLine.renderable; track $index) {\n @if (renderable.type === 'node' && axmAssistWidgetNodeFromUnknown(renderable.content)) {\n <div class=\"axm-assist-bot__segment\" dir=\"auto\">\n <axm-chat-user-form-renderer [message]=\"syntheticNodeEmbedMessage(lineIndex, segIndex * 100 + $index, renderable.content)\" />\n </div>\n }\n }\n } @else if (line.role === 'assistant' && (seg.type === 'agent' || seg.type === 'tool') && isDelegatedAgentCommand(seg.content.command)) {\n @if (showDebugUi()) {\n <div class=\"axm-assist-renderer__tool-row\">\n <div class=\"axm-assist-renderer__tool-row-main\">\n <i class=\"fa-light fa-people-group axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n <span class=\"axm-assist-renderer__tool-row-title\">{{ resolveToolDisplayTitle(seg.content.command) }}</span>\n @if (resolveToolModelLabel(seg.content.command, seg.callId); as modelLabel) {\n <span class=\"axm-assist-renderer__tool-model-label\">{{ modelLabel }}</span>\n }\n </div>\n <ax-button\n look=\"outline\"\n color=\"primary\"\n class=\"ax-xs\"\n [text]=\"('@conversation:chat.assist-renderer.delegated-agent.detail' | translate | async) ?? ''\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.delegated-agent.detail-aria' | translate | async) ?? ''\"\n (onClick)=\"openTranscriptToolDetail(seg.callId, seg.content.command, seg.content.arguments, delegatedAgentLabelForCall(seg.callId))\"\n ></ax-button>\n </div>\n } @else {\n <div class=\"axm-assist-renderer__tool-row\">\n <div class=\"axm-assist-renderer__tool-row-main\">\n <i class=\"fa-light fa-people-group axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n <span class=\"axm-assist-renderer__tool-row-title\">{{ resolveToolDisplayTitle(seg.content.command) }}</span>\n @if (resolveToolModelLabel(seg.content.command, seg.callId); as modelLabel) {\n <span class=\"axm-assist-renderer__tool-model-label\">{{ modelLabel }}</span>\n }\n </div>\n <ax-button\n look=\"outline\"\n color=\"primary\"\n class=\"ax-xs\"\n [text]=\"('@conversation:chat.assist-renderer.delegated-agent.detail' | translate | async) ?? ''\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.delegated-agent.detail-aria' | translate | async) ?? ''\"\n (onClick)=\"openTranscriptToolDetail(seg.callId, seg.content.command, seg.content.arguments, delegatedAgentLabelForCall(seg.callId))\"\n ></ax-button>\n </div>\n @let res = toolResultForCallId(seg.callId);\n @let outcomeSegs = delegatedAgentOutcomeSegmentsForCall(res);\n @if (outcomeSegs.length > 0) {\n @for (outSeg of outcomeSegs; track outSegIndex; let outSegIndex = $index) {\n @switch (outSeg.type) {\n @case ('text') {\n @let parsedOut = parseAssistLineText(outSeg.content);\n @let partitionedOut = partitionAnswerText(parsedOut.body);\n @if (partitionedOut.markdown.trim()) {\n <div class=\"axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(partitionedOut.markdown)\">\n <axp-markdown-viewer [markdown]=\"partitionedOut.markdown\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n }\n @for (renderable of partitionedOut.renderable; track renderableIndex; let renderableIndex = $index) {\n @if (renderable.type === 'node' && axmAssistWidgetNodeFromUnknown(renderable.content)) {\n <div class=\"axm-assist-bot__segment\" dir=\"auto\">\n <axm-chat-user-form-renderer [message]=\"syntheticNodeEmbedMessage(lineIndex, segIndex * 1000 + outSegIndex * 10 + renderableIndex, renderable.content)\" />\n </div>\n }\n }\n }\n @case ('think') {\n @if (showDebugUi()) {\n <div class=\"axm-assist-bot__segment\" [attr.dir]=\"segmentDir(outSeg.content)\">\n {{ outSeg.content }}\n </div>\n }\n }\n @case ('file') {\n <axm-assist-bot-file-line [parentMessage]=\"message()\" [fileId]=\"outSeg.content.fileId\" />\n }\n @case ('node') {\n @let assistNodeWidget = axmAssistWidgetNodeFromUnknown(outSeg.content);\n @if (assistNodeWidget) {\n <div class=\"axm-assist-bot__segment\" dir=\"auto\">\n <axm-chat-user-form-renderer [message]=\"syntheticNodeEmbedMessage(lineIndex, segIndex * 1000 + outSegIndex, outSeg.content)\" />\n </div>\n }\n }\n }\n }\n } @else if (delegatedAgentAnswerForCall(res).trim()) {\n @let agentFallbackText = delegatedAgentAnswerForCall(res);\n <div class=\"axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(agentFallbackText)\">\n <axp-markdown-viewer [markdown]=\"agentFallbackText\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n }\n }\n } @else if (line.role === 'assistant' && seg.type === 'tool' && showDebugUi()) {\n <div class=\"axm-assist-renderer__tool-row\">\n <div class=\"axm-assist-renderer__tool-row-main\">\n <i class=\"fa-light fa-screwdriver-wrench axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n <span class=\"axm-assist-renderer__tool-row-title\">{{ resolveToolDisplayTitle(seg.content.command) }}</span>\n </div>\n <ax-button\n look=\"outline\"\n color=\"primary\"\n class=\"ax-xs\"\n [text]=\"('@conversation:chat.assist-renderer.delegated-agent.detail' | translate | async) ?? ''\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.delegated-agent.detail-aria' | translate | async) ?? ''\"\n (onClick)=\"openTranscriptToolDetail(seg.callId, seg.content.command, seg.content.arguments)\"\n ></ax-button>\n </div>\n } @else if (line.role === 'assistant' && seg.type === 'node') {\n @let assistNodeWidget = axmAssistWidgetNodeFromUnknown(seg.content);\n @if (assistNodeWidget) {\n <div class=\"axm-assist-bot__segment\" dir=\"auto\">\n <axm-chat-user-form-renderer [message]=\"syntheticNodeEmbedMessage(lineIndex, segIndex, seg.content)\" />\n </div>\n }\n } @else if (line.role === 'assistant' && seg.type === 'file' && !assistHideTranscriptAnswerSegments()) {\n <axm-assist-bot-file-line [parentMessage]=\"message()\" [fileId]=\"seg.content.fileId\" />\n } @else if (line.role === 'tool' && (seg.type === 'tool_result' || seg.type === 'agent_result')) {\n <!-- merged into the corresponding tool/agent accordion -->\n } @else if (line.role === 'system' && seg.type === 'text' && showDebugUi()) {\n <div class=\"axm-assist-bot__segment\" [attr.dir]=\"segmentDir(seg.content)\">\n <ax-conversation-text-renderer [message]=\"syntheticTextMessage(lineIndex, segIndex, seg.content)\" />\n </div>\n } @else {\n @if (showDebugUi() || line.role !== 'assistant') {\n <div class=\"axm-assist-bot__fallback axm-assist-bot__segment\" [attr.dir]=\"segmentDir(formatFallbackSegment(seg))\">\n {{ formatFallbackSegment(seg) }}\n </div>\n }\n }\n }\n </div>\n }\n </div>\n <ng-container *ngTemplateOutlet=\"assistUnifiedAnswerBlock\" />\n } @else if (!showDebugUi() && showNonDebugStreamPanel()) {\n <div class=\"axm-assist-renderer__stream-content axm-assist-renderer__stream-content--non-debug\" role=\"region\" [attr.aria-label]=\"('@conversation:chat.assist-renderer.aria.assistant-response' | translate | async) ?? ''\">\n @if (isStreaming()) {\n <div class=\"axm-assist-renderer__streaming-activity\" role=\"status\" aria-live=\"polite\">\n <span class=\"axm-assist-renderer__activity-spin inline-block w-3 h-3 rounded-full border-2 border-primary-500 border-t-transparent fa-spin shrink-0\" aria-hidden=\"true\"></span>\n <span class=\"axm-assist-renderer__activity-label text-xs text-muted italic\">{{ assistActivityMessageKey() | translate | async }}</span>\n </div>\n }\n <ng-container *ngTemplateOutlet=\"assistUnifiedAnswerBlock\" />\n @if (isStreaming() && !assistAnswerMarkdown()) {\n <div class=\"axm-assist-renderer__waiting\">\n <span class=\"axm-assist-renderer__dot\"></span>\n <span class=\"axm-assist-renderer__dot\"></span>\n <span class=\"axm-assist-renderer__dot\"></span>\n </div>\n }\n </div>\n } @else if (showDebugUi()) {\n @if (isStreaming()) {\n <div class=\"axm-assist-renderer__stream-content\">\n <div class=\"axm-assist-renderer__streaming-activity\" role=\"status\" aria-live=\"polite\">\n <span class=\"axm-assist-renderer__activity-spin inline-block w-3 h-3 rounded-full border-2 border-primary-500 border-t-transparent fa-spin shrink-0\" aria-hidden=\"true\"></span>\n <span class=\"axm-assist-renderer__activity-label text-xs text-muted italic\">{{ assistActivityMessageKey() | translate | async }}</span>\n </div>\n @if (streamThink().trim()) {\n <div class=\"axm-assist-renderer__accordion-group\" axAccordionGroup [accordion]=\"false\">\n <div\n class=\"axm-assist-renderer__thinking-accordion axm-assist-renderer__collapse-shell mb-0!\"\n axAccordionItem\n #thinkAcc=\"axAccordionItem\"\n [isCollapsed]=\"assistSectionIsCollapsed(assistThinkStreamKey())\"\n (onClick)=\"onAssistAccordionClick($event, assistThinkStreamKey())\"\n >\n <div class=\"axm-assist-renderer__accordion-header\" axAccordionItemHeader role=\"button\" [attr.aria-expanded]=\"!thinkAcc.isCollapsed()\">\n <div class=\"axm-assist-renderer__accordion-header-main\">\n <span class=\"axm-assist-renderer__accordion-header-title\">\n <i class=\"fa-light fa-chevron-down axm-assist-renderer__thinking-chevron\" [class.axm-assist-renderer__thinking-chevron--expanded]=\"!thinkAcc.isCollapsed()\" aria-hidden=\"true\"></i>\n <i class=\"fa-light fa-brain axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n {{ '@conversation:chat.assist-renderer.sections.thinking' | translate | async }}\n </span>\n </div>\n </div>\n <div axAccordionItemContent>\n <div class=\"axm-assist-renderer__collapse-body\">\n <div class=\"axm-assist-renderer__thinking-body axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(streamThink())\">\n <axp-markdown-viewer [markdown]=\"streamThink()\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n <ng-container *ngTemplateOutlet=\"assistUnifiedAnswerBlock\" />\n @if (toolCalls().length) {\n <div class=\"axm-assist-renderer__tools\">\n @for (tc of toolCalls(); track tc.id) {\n @if (isDelegatedAgentCommand(tc.name)) {\n <div class=\"axm-assist-renderer__tool-row\">\n <div class=\"axm-assist-renderer__tool-row-main\">\n @if (tc.state === 'running') {\n <span class=\"axm-assist-renderer__agent-live-spin inline-block w-3 h-3 rounded-full border-2 border-primary-500 border-t-transparent fa-spin shrink-0\" aria-hidden=\"true\"></span>\n } @else {\n <span class=\"inline-block w-3 h-3 rounded-full bg-success-500 shrink-0\" aria-hidden=\"true\"></span>\n }\n <i class=\"fa-light fa-people-group axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n <span class=\"axm-assist-renderer__tool-row-title\">{{ resolveToolDisplayTitle(tc.name) }}</span>\n @if (resolveToolModelLabel(tc.name, tc.id); as modelLabel) {\n <span class=\"axm-assist-renderer__tool-model-label\">{{ modelLabel }}</span>\n }\n </div>\n <ax-button\n look=\"outline\"\n color=\"primary\"\n class=\"ax-xs\"\n [disabled]=\"tc.state === 'running'\"\n [text]=\"('@conversation:chat.assist-renderer.delegated-agent.detail' | translate | async) ?? ''\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.delegated-agent.detail-aria' | translate | async) ?? ''\"\n (onClick)=\"openLegacyToolDetail(tc)\"\n ></ax-button>\n </div>\n @if (tc.nestedStreamText?.trim()) {\n <div class=\"axm-assist-renderer__agent-live-stream\" [attr.dir]=\"segmentDir(tc.nestedStreamText ?? '')\">\n {{ tc.nestedStreamText }}\n </div>\n }\n } @else {\n <div class=\"axm-assist-renderer__tool-row\">\n <div class=\"axm-assist-renderer__tool-row-main\">\n @if (tc.state === 'running') {\n <span class=\"axm-assist-renderer__agent-live-spin inline-block w-3 h-3 rounded-full border-2 border-primary-500 border-t-transparent fa-spin shrink-0\" aria-hidden=\"true\"></span>\n } @else {\n <span class=\"inline-block w-3 h-3 rounded-full bg-success-500 shrink-0\" aria-hidden=\"true\"></span>\n }\n <i class=\"fa-light fa-screwdriver-wrench axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n <span class=\"axm-assist-renderer__tool-row-title\">{{ resolveToolDisplayTitle(tc.name) }}</span>\n </div>\n <ax-button\n look=\"outline\"\n color=\"primary\"\n class=\"ax-xs\"\n [disabled]=\"tc.state === 'running'\"\n [text]=\"('@conversation:chat.assist-renderer.delegated-agent.detail' | translate | async) ?? ''\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.delegated-agent.detail-aria' | translate | async) ?? ''\"\n (onClick)=\"openLegacyToolDetail(tc)\"\n ></ax-button>\n </div>\n }\n }\n </div>\n }\n @if (isStreaming() && !assistAnswerMarkdown() && !streamThink().trim() && !toolCalls().length) {\n <div class=\"axm-assist-renderer__waiting\">\n <span class=\"axm-assist-renderer__dot\"></span>\n <span class=\"axm-assist-renderer__dot\"></span>\n <span class=\"axm-assist-renderer__dot\"></span>\n </div>\n }\n </div>\n } @else if (!useTranscriptUi()) {\n @if (parsed().thinking) {\n <div class=\"axm-assist-renderer__accordion-group\" axAccordionGroup [accordion]=\"false\">\n <div\n class=\"axm-assist-renderer__thinking-accordion axm-assist-renderer__collapse-shell mb-0!\"\n axAccordionItem\n #thinkAcc=\"axAccordionItem\"\n [isCollapsed]=\"assistSectionIsCollapsed(assistThinkFinalKey())\"\n (onClick)=\"onAssistAccordionClick($event, assistThinkFinalKey())\"\n >\n <div class=\"axm-assist-renderer__accordion-header\" axAccordionItemHeader role=\"button\" [attr.aria-expanded]=\"!thinkAcc.isCollapsed()\">\n <div class=\"axm-assist-renderer__accordion-header-main\">\n <span class=\"axm-assist-renderer__accordion-header-title\">\n <i class=\"fa-light fa-chevron-down axm-assist-renderer__thinking-chevron\" [class.axm-assist-renderer__thinking-chevron--expanded]=\"!thinkAcc.isCollapsed()\" aria-hidden=\"true\"></i>\n <i class=\"fa-light fa-brain axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n {{ '@conversation:chat.assist-renderer.sections.thinking' | translate | async }}\n </span>\n </div>\n </div>\n <div axAccordionItemContent>\n <div class=\"axm-assist-renderer__collapse-body\">\n <div class=\"axm-assist-renderer__thinking-body axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(parsed().thinking)\">\n <axp-markdown-viewer [markdown]=\"parsed().thinking ?? ''\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n @if (toolCalls().length) {\n <div class=\"axm-assist-renderer__tools\">\n @for (tc of toolCalls(); track tc.id) {\n <div class=\"axm-assist-renderer__tool-row\">\n <div class=\"axm-assist-renderer__tool-row-main\">\n <i class=\"fa-light axm-assist-renderer__tool-title-icon\" [class.fa-people-group]=\"isDelegatedAgentCommand(tc.name)\" [class.fa-screwdriver-wrench]=\"!isDelegatedAgentCommand(tc.name)\" aria-hidden=\"true\"></i>\n <span class=\"axm-assist-renderer__tool-row-title\">{{ resolveToolDisplayTitle(tc.name) }}</span>\n @if (isDelegatedAgentCommand(tc.name) && resolveToolModelLabel(tc.name, tc.id); as modelLabel) {\n <span class=\"axm-assist-renderer__tool-model-label\">{{ modelLabel }}</span>\n }\n </div>\n <ax-button\n look=\"outline\"\n color=\"primary\"\n class=\"ax-xs\"\n [text]=\"('@conversation:chat.assist-renderer.delegated-agent.detail' | translate | async) ?? ''\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.delegated-agent.detail-aria' | translate | async) ?? ''\"\n (onClick)=\"openLegacyToolDetail(tc)\"\n ></ax-button>\n </div>\n }\n </div>\n }\n <ng-container *ngTemplateOutlet=\"assistUnifiedAnswerBlock\" />\n }\n }\n @if (showDebugUi() && assistRunDebug(); as dbg) {\n <footer class=\"axm-assist-renderer__run-meta-wrap\" role=\"region\" [attr.aria-label]=\"('@conversation:chat.assist-renderer.aria.run-usage' | translate | async) ?? ''\">\n <div class=\"axm-assist-renderer__accordion-group\" axAccordionGroup [accordion]=\"false\">\n <div\n class=\"axm-assist-renderer__thinking-accordion axm-assist-renderer__collapse-shell axm-assist-renderer__run-meta-accordion mb-0!\"\n axAccordionItem\n #runMetaAcc=\"axAccordionItem\"\n [isCollapsed]=\"assistSectionIsCollapsed(assistRunMetaKey())\"\n (onClick)=\"onAssistAccordionClick($event, assistRunMetaKey())\"\n >\n <div class=\"axm-assist-renderer__accordion-header\" axAccordionItemHeader role=\"button\" [attr.aria-expanded]=\"!runMetaAcc.isCollapsed()\">\n <div class=\"axm-assist-renderer__accordion-header-main\">\n <span class=\"axm-assist-renderer__accordion-header-title\">\n <i class=\"fa-light fa-chevron-down axm-assist-renderer__thinking-chevron\" [class.axm-assist-renderer__thinking-chevron--expanded]=\"!runMetaAcc.isCollapsed()\" aria-hidden=\"true\"></i>\n <i class=\"fa-light fa-chart-line axm-assist-renderer__tool-title-icon\" aria-hidden=\"true\"></i>\n {{ '@conversation:chat.assist-renderer.run-meta.title' | translate | async }}\n </span>\n <!-- @if (dbg.modelTitle || dbg.modelEntityId) {\n <span class=\"axm-assist-renderer__run-meta-header-sub\">{{ dbg.modelTitle || dbg.modelEntityId }}</span>\n } -->\n </div>\n </div>\n <div axAccordionItemContent>\n <div class=\"axm-assist-renderer__collapse-body\">\n <div class=\"axm-assist-renderer__run-meta axm-assist-renderer__run-meta--panel\">\n <div class=\"axm-assist-renderer__run-meta-grid\">\n @if (dbg.modelTitle || dbg.modelEntityId) {\n <div class=\"axm-assist-renderer__meta-tile axm-assist-renderer__meta-tile--wide\">\n <div class=\"axm-assist-renderer__meta-tile-label\">\n {{ '@conversation:chat.assist-renderer.run-meta.model' | translate | async }}\n </div>\n <div class=\"axm-assist-renderer__meta-tile-value\">{{ dbg.modelTitle || dbg.modelEntityId }}</div>\n <!-- @if (dbg.modelTitle && dbg.modelEntityId) {\n <div class=\"axm-assist-renderer__meta-tile-sub\">{{ dbg.modelEntityId }}</div>\n } -->\n </div>\n }\n @if (dbg.durationMs != null) {\n <div class=\"axm-assist-renderer__meta-tile\">\n <div class=\"axm-assist-renderer__meta-tile-label\">\n {{ '@conversation:chat.assist-renderer.run-meta.duration' | translate | async }}\n </div>\n <div class=\"axm-assist-renderer__meta-tile-value axm-assist-renderer__meta-tile-value--mono\">\n {{ formatAssistRunDuration(dbg.durationMs) }}\n </div>\n </div>\n }\n @if (dbg.usage; as totals) {\n <div class=\"axm-assist-renderer__meta-tile\">\n <div class=\"axm-assist-renderer__meta-tile-label\">\n {{ '@ai-management:chat.usage-prompt' | translate | async }}\n </div>\n <div class=\"axm-assist-renderer__meta-tile-value axm-assist-renderer__meta-tile-value--mono\">\n {{ totals.promptTokens | number }}\n @if (totals.cachedPromptTokens) {\n <span class=\"axm-assist-renderer__meta-tile-note\">(+{{ totals.cachedPromptTokens | number }} {{ '@ai-management:chat.usage-cached' | translate | async }})</span>\n }\n </div>\n </div>\n <div class=\"axm-assist-renderer__meta-tile\">\n <div class=\"axm-assist-renderer__meta-tile-label\">\n {{ '@ai-management:chat.usage-completion' | translate | async }}\n </div>\n <div class=\"axm-assist-renderer__meta-tile-value axm-assist-renderer__meta-tile-value--mono\">\n {{ totals.completionTokens | number }}\n </div>\n </div>\n <div class=\"axm-assist-renderer__meta-tile axm-assist-renderer__meta-tile--accent\">\n <div class=\"axm-assist-renderer__meta-tile-label\">\n {{ '@conversation:chat.assist-renderer.run-meta.total-tokens' | translate | async }}\n </div>\n <div class=\"axm-assist-renderer__meta-tile-value axm-assist-renderer__meta-tile-value--mono\">\n {{ totals.totalTokens | number }}\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </footer>\n }\n\n <ng-template #assistUnifiedAnswerBlock>\n @if (assistAnswerMarkdown()) {\n <div class=\"axm-assist-renderer__text axm-assist-bot__segment axm-assist-renderer__markdown\" [attr.dir]=\"segmentDir(assistAnswerMarkdown())\">\n <axp-markdown-viewer [markdown]=\"assistAnswerMarkdown()\">\n <ng-template axpMarkdownTemplate=\"code\" let-segment>\n <div class=\"axm-assist-renderer__tool-editor-wrap\" style=\"direction: ltr\">\n <ax-code-editor [ngModel]=\"markdownCodeValue(segment)\" [readonly]=\"true\" [language]=\"markdownCodeLanguage(segment)\" [lineNumbers]=\"true\" class=\"axm-assist-renderer__tool-editor\"></ax-code-editor>\n </div>\n </ng-template>\n </axp-markdown-viewer>\n </div>\n <!-- @if (isStreaming()) {\n <span class=\"axm-assist-renderer__cursor\"></span>\n } -->\n }\n @if (!isStreaming()) {\n @if (assistPageActionResult(); as pageAction) {\n <!-- ----- Live page chrome result (filter / search) ----- -->\n <div class=\"axm-assist-renderer__page-action\" role=\"status\">\n <span class=\"axm-assist-renderer__page-action-icon\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-circle-check\"></i>\n </span>\n <div class=\"axm-assist-renderer__page-action-copy\">\n <span class=\"axm-assist-renderer__page-action-title\">\n {{\n (pageAction.kind === 'search'\n ? '@conversation:mini-chat.page-action.search-applied'\n : '@conversation:mini-chat.page-action.filter-applied') | translate | async\n }}\n </span>\n @if (assistPageActionLabel(); as actionLabel) {\n <span class=\"axm-assist-renderer__page-action-label\">{{ (actionLabel | translate | async) ?? actionLabel }}</span>\n }\n </div>\n @if (pageAction.undoActionId) {\n <ax-button\n class=\"ax-xs\"\n look=\"blank\"\n color=\"primary\"\n [disabled]=\"pageActionUndoBusy()\"\n [text]=\"('@conversation:mini-chat.page-action.undo' | translate | async) ?? ''\"\n (onClick)=\"onUndoPageAction()\"\n ></ax-button>\n }\n </div>\n }\n @if (assistEntityCardRefs().length > 0) {\n <!-- ----- Entity rows from query/get tools, rendered as shared cards ----- -->\n <div class=\"axm-assist-renderer__entity-cards\">\n @for (ref of assistEntityCardRefs(); track ref.entity + ':' + ref.id) {\n <axm-chat-entity-card [entity]=\"ref.entity\" [entityId]=\"ref.id\" [variant]=\"ref.variant\" />\n }\n </div>\n }\n @for (item of assistPayloadAppendItems(); track $index; let itemIndex = $index) {\n @switch (item.kind) {\n @case ('node') {\n @if (axmAssistWidgetNodeFromUnknown(item.content)) {\n <div class=\"axm-assist-bot__segment\" dir=\"auto\">\n <axm-chat-user-form-renderer [message]=\"syntheticAppendNodeMessage(itemIndex, item.content)\" />\n </div>\n }\n }\n @case ('file') {\n <axm-assist-bot-file-line [parentMessage]=\"message()\" [fileId]=\"item.fileId\" />\n }\n @case ('followUp') {\n <div class=\"axm-assist-bot__segment\" dir=\"auto\">\n <axm-assist-suggestion-chips [items]=\"item.items\" />\n </div>\n }\n }\n }\n <!---->\n <!-- ----- Follow-up suggestions preparing ----- -->\n @if (isFollowUpPreparing()) {\n <div\n class=\"axm-assist-renderer__streaming-activity axm-assist-renderer__follow-up-preparing\"\n role=\"status\"\n aria-live=\"polite\"\n [attr.aria-label]=\"('@conversation:chat.assist-renderer.follow-up.aria' | translate | async) ?? ''\"\n >\n <span class=\"axm-assist-renderer__activity-spin inline-block w-3 h-3 rounded-full border-2 border-primary-500 border-t-transparent fa-spin shrink-0\" aria-hidden=\"true\"></span>\n <span class=\"axm-assist-renderer__activity-label text-xs text-muted italic\">{{ '@conversation:chat.assist-renderer.follow-up.preparing' | translate | async }}</span>\n </div>\n }\n }\n </ng-template>\n\n @if (showRetryScreen()) {\n <ax-alert class=\"axm-assist-renderer__retry-alert\" color=\"danger\" role=\"alert\" [attr.aria-label]=\"('@conversation:chat.assist-renderer.retry.aria.panel' | translate | async) ?? ''\">\n <ax-icon><i [class]=\"assistRetryAlertIcon()\"></i></ax-icon>\n <ax-title>{{ assistRetryTitleKey() | translate | async }}</ax-title>\n <ax-content>\n <div class=\"axm-assist-renderer__retry-alert-body\">\n <div class=\"axm-assist-renderer__retry-alert-text\">\n <p class=\"axm-assist-renderer__retry-description\">\n {{ assistRetryDescriptionKey() | translate | async }}\n </p>\n <!---->\n <!-- ----- Technical error text only in debug mode ----- -->\n @if (showDebugUi() && assistResponseError()) {\n <p class=\"axm-assist-renderer__retry-error-detail\">\n {{ assistResponseError() }}\n </p>\n }\n </div>\n\n <ax-button class=\"axm-assist-renderer__retry-action ax-sm\" color=\"danger\" look=\"solid\" [disabled]=\"isRetrying()\" [text]=\"('@conversation:chat.assist-renderer.retry.action' | translate | async) ?? ''\" (onClick)=\"onRetryAssistResponse()\">\n <i class=\"fa-light fa-arrows-rotate\" prefix></i>\n </ax-button>\n </div>\n </ax-content>\n </ax-alert>\n }\n</div>\n", styles: ["@layer properties;.axm-assist-renderer{display:flex;flex-direction:column;gap:.25rem}.axm-assist-renderer axm-assist-suggestion-chips>div{padding:0;padding-top:calc(var(--spacing, .25rem) * 4)}.axm-assist-renderer .axm-assist-renderer__cursor{display:inline-block;inline-size:6px;block-size:1em;margin-inline-start:2px;background:rgb(var(--ax-sys-color-primary-500));border-radius:1px;animation:axm-blink .8s steps(2) infinite;vertical-align:text-bottom}.axm-assist-renderer .axm-assist-renderer__stream-content{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}.axm-assist-renderer .axm-assist-renderer__streaming-activity{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 2);padding-block:.15rem}.axm-assist-renderer .axm-assist-renderer__activity-label{line-height:1.45}.axm-assist-renderer .axm-assist-renderer__follow-up-preparing{padding-block-start:.75rem}.axm-assist-renderer .axm-assist-renderer__text{white-space:pre-line;word-break:break-word;line-height:1.85;font-size:.95rem;letter-spacing:.002em;text-wrap:pretty}.axm-assist-renderer .axm-assist-renderer__markdown .axp-markdown-viewer__content :is(p,ul,ol){margin-block:.45rem}.axm-assist-renderer .axm-assist-renderer__markdown .axp-markdown-viewer__content li{line-height:1.8}.axm-assist-renderer .axm-assist-renderer__accordion-group{width:100%}.axm-assist-renderer .axm-assist-renderer__collapse-shell{border-radius:.625rem;overflow:hidden;background:rgb(var(--ax-sys-color-lighter-surface))}.axm-assist-renderer .axm-assist-renderer__collapse-shell>.axm-assist-renderer__accordion-header:hover{background:rgb(var(--ax-sys-color-light-surface))}.axm-assist-renderer .axm-assist-renderer__collapse-body{margin:0;padding:0;border-radius:0 0 .5rem .5rem;background:transparent}.axm-assist-renderer .axm-assist-renderer__collapse-body>.axm-assist-renderer__agent-live-block--in-accordion{padding-inline:.5rem;padding-block-end:.35rem}.axm-assist-renderer .axm-assist-renderer__accordion-header{display:flex;cursor:pointer;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);--tw-border-style: none;border-style:none;padding:.5rem .75rem;background:transparent;border-radius:.625rem .625rem 0 0;transition:background-color .15s ease}.axm-assist-renderer .axm-assist-renderer__accordion-header-main{display:flex;flex-direction:column;min-inline-size:0;gap:.1rem}.axm-assist-renderer .axm-assist-renderer__accordion-header-title{display:inline-flex;align-items:center;--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);font-size:.8125rem;line-height:1.25;color:rgb(var(--ax-sys-color-on-surface));gap:.4rem}.axm-assist-renderer .axm-assist-renderer__header-trailing{display:inline-flex;flex-shrink:0;align-items:center;gap:.35rem}.axm-assist-renderer .axm-assist-renderer__delegated-agent-detail-link{text-decoration-line:underline;padding:.25rem;padding-top:0}.axm-assist-renderer .axm-assist-renderer__badge{display:inline-flex;flex-shrink:0;align-items:center;justify-content:center;overflow:hidden;border-radius:calc(infinity * 1px);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);text-overflow:ellipsis;white-space:nowrap;max-width:min(100%,14rem);padding:.1875rem .65rem;font-size:.6875rem;line-height:1.3;letter-spacing:.03em}.axm-assist-renderer .axm-assist-renderer__badge--warning{background:rgb(var(--ax-sys-color-warning-100))}.axm-assist-renderer .axm-assist-renderer__badge--primary{background:rgb(var(--ax-sys-color-primary-100))}.axm-assist-renderer .axm-assist-renderer__badge--success{background:rgb(var(--ax-sys-color-success-100))}.axm-assist-renderer .axm-assist-renderer__thinking-chevron{display:inline-block;flex-shrink:0;font-size:.65rem;opacity:.65;transition:transform .2s ease;line-height:1}.axm-assist-renderer .axm-assist-renderer__thinking-chevron--expanded{transform:rotate(180deg)}.axm-assist-renderer .axm-assist-renderer__thinking-body{padding:.625rem .75rem .75rem;font-size:.78rem;color:rgb(var(--ax-sys-color-neutral-700));word-break:break-word;line-height:1.55}.axm-assist-renderer .axm-assist-renderer__thinking-body.axm-assist-renderer__markdown .axp-markdown-viewer__content{font-size:inherit;color:inherit;line-height:inherit}.axm-assist-renderer .axm-assist-renderer__thinking-body.axm-assist-renderer__markdown .axp-markdown-viewer__content :is(p,ul,ol){margin-block:.35rem}.axm-assist-renderer .axm-assist-renderer__thinking-body.axm-assist-renderer__markdown .axp-markdown-viewer__content li{line-height:inherit}.axm-assist-renderer .axm-assist-renderer__tools{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}.axm-assist-renderer .axm-assist-renderer__tool-row{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);padding:.5rem .65rem;border-radius:.625rem;background:rgb(var(--ax-sys-color-lighter-surface));border:1px solid rgb(var(--ax-sys-color-border-lighter-surface))}.axm-assist-renderer .axm-assist-renderer__tool-row-main{display:flex;min-width:0px;align-items:center;gap:.45rem}.axm-assist-renderer .axm-assist-renderer__tool-row-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);color:rgb(var(--ax-sys-color-on-surface))}.axm-assist-renderer .axm-assist-renderer__tool-model-label{flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-normal, 400);font-weight:var(--font-weight-normal, 400);max-width:9rem;letter-spacing:.01em;color:rgb(var(--ax-sys-color-neutral-600));opacity:.78}.axm-assist-renderer .axm-assist-renderer__tool-card{border-radius:.625rem;overflow:hidden}.axm-assist-renderer .axm-assist-renderer__tool-body{display:flex;flex-direction:column;padding:.625rem .75rem .75rem;gap:.35rem}.axm-assist-renderer .axm-assist-renderer__tool-section-label{--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);text-transform:uppercase;font-size:.625rem;letter-spacing:.06em;color:rgb(var(--ax-sys-color-neutral-700))}.axm-assist-renderer .axm-assist-renderer__tool-pre{margin:0;overflow:auto;border-radius:var(--radius-md, .375rem);max-block-size:10rem;background:rgb(var(--ax-sys-color-light-surface));padding:.5rem;font-family:var(--ax-font-mono, monospace);font-size:.675rem;line-height:1.45;word-break:break-word}.axm-assist-renderer .axm-assist-renderer__run-meta-wrap{margin-block-start:.45rem}.axm-assist-renderer .axm-assist-renderer__run-meta-header-sub{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.7rem;line-height:1.35;color:rgb(var(--ax-sys-color-neutral-700));max-width:100%}.axm-assist-renderer .axm-assist-renderer__run-meta{display:flex;flex-direction:column;margin-block-start:.45rem;padding:.55rem .65rem .6rem;border-radius:.625rem;background:rgb(var(--ax-sys-color-lighter-surface));gap:.45rem}.axm-assist-renderer .axm-assist-renderer__run-meta--panel{margin-block-start:0;padding:.45rem .5rem .55rem;background:transparent;border-radius:0}.axm-assist-renderer .axm-assist-renderer__run-meta-grid{display:flex;gap:.4rem}.axm-assist-renderer .axm-assist-renderer__meta-tile{min-width:0px;padding:.4rem .5rem .45rem;border-radius:.45rem;background:rgb(var(--ax-sys-color-light-surface))}.axm-assist-renderer .axm-assist-renderer__meta-tile--wide{grid-column:1/-1}.axm-assist-renderer .axm-assist-renderer__meta-tile--accent{background:rgb(var(--ax-sys-color-primary-50))}.axm-assist-renderer .axm-assist-renderer__meta-tile-label{--tw-font-weight: var(--font-weight-bold, 700);font-weight:var(--font-weight-bold, 700);text-transform:uppercase;font-size:.6rem;letter-spacing:.06em;color:rgb(var(--ax-sys-color-neutral-700));margin-block-end:.2rem}.axm-assist-renderer .axm-assist-renderer__meta-tile-value{--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);font-size:.78rem;line-height:1.35;color:rgb(var(--ax-sys-color-on-surface));word-break:break-word}.axm-assist-renderer .axm-assist-renderer__meta-tile-value--mono{font-variant-numeric:tabular-nums;font-feature-settings:\"tnum\" 1}.axm-assist-renderer .axm-assist-renderer__meta-tile-sub{margin-block-start:.15rem;font-size:.65rem;line-height:1.3;color:rgb(var(--ax-sys-color-neutral-700));font-family:var(--ax-font-mono, ui-monospace, monospace);word-break:break-all}.axm-assist-renderer .axm-assist-renderer__meta-tile-note{display:inline;--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);margin-inline-start:.2rem;font-size:.65rem;color:rgb(var(--ax-sys-color-success-700))}.axm-assist-renderer .axm-assist-renderer__tool-editor-wrap{overflow:hidden;border-radius:var(--radius-md, .375rem);background:rgb(var(--ax-sys-color-light-surface))}.axm-assist-renderer .axm-assist-renderer__waiting{display:flex;gap:.25rem;padding:.25rem 0}.axm-assist-renderer .axm-assist-renderer__dot{inline-size:6px;block-size:6px;border-radius:50%;background:rgb(var(--ax-sys-color-neutral-500));animation:axm-blink 1.2s ease-in-out infinite}.axm-assist-renderer .axm-assist-renderer__dot:nth-child(2){animation-delay:.2s}.axm-assist-renderer .axm-assist-renderer__dot:nth-child(3){animation-delay:.4s}.axm-assist-renderer .axm-assist-renderer__agent-live-block{display:flex;flex-direction:column;border-radius:.75rem;background:rgb(var(--ax-sys-color-lighter-surface));padding:.75rem .875rem;gap:.65rem;max-inline-size:min(42rem,100%)}.axm-assist-renderer .axm-assist-renderer__agent-live-block--in-accordion{background:rgb(var(--ax-sys-color-light-surface));border-radius:.5rem;padding:.5rem .55rem .55rem;max-inline-size:none}.axm-assist-renderer .axm-assist-renderer__agent-live-header{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}.axm-assist-renderer .axm-assist-renderer__agent-live-title{flex:1;--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);font-size:.8125rem;color:rgb(var(--ax-sys-color-on-surface));min-inline-size:0}.axm-assist-renderer .axm-assist-renderer__agent-live-prompt{align-self:flex-end;font-size:.8125rem;line-height:1.55;word-break:break-word;padding:.5rem .75rem;border-radius:1.25rem;background:rgb(var(--ax-sys-color-lightest-surface));max-inline-size:min(92%,36rem)}.axm-assist-renderer .axm-assist-renderer__agent-live-stream{font-size:.8125rem;line-height:1.55;white-space:pre-line;word-break:break-word;padding-block-start:.15rem}.axm-assist-renderer .axm-assist-renderer__agent-live-nested-tools{display:flex;flex-direction:column;gap:.35rem;padding-block-start:.35rem}.axm-assist-renderer .axm-assist-renderer__agent-live-nested-row{display:flex;align-items:center;gap:.45rem;font-size:.72rem}.axm-assist-renderer .axm-assist-renderer__agent-live-nested-name{flex:1;overflow:hidden;text-overflow:ellipsis;font-family:var(--ax-font-mono, monospace);font-size:.7rem;min-inline-size:0}.axm-assist-renderer .axm-assist-renderer__agent-live-nested-status{flex-shrink:0;color:rgb(var(--ax-sys-color-neutral-600))}.axm-assist-renderer .axm-assist-renderer__retry-alert-body{display:flex;align-items:flex-start;gap:.75rem}.axm-assist-renderer .axm-assist-renderer__retry-alert-text{display:flex;flex:1;flex-direction:column;min-inline-size:0;gap:.3rem}.axm-assist-renderer .axm-assist-renderer__retry-description{margin:0;font-size:.8125rem;line-height:1.5;opacity:.92}.axm-assist-renderer .axm-assist-renderer__retry-error-detail{margin:.15rem 0 0;font-size:.75rem;line-height:1.45;font-family:var(--ax-font-mono, ui-monospace, monospace);word-break:break-word;opacity:.88}.axm-assist-renderer .axm-assist-renderer__retry-action{flex-shrink:0;align-self:center}.axm-assist-bot,.axm-assist-bot .axm-assist-bot__final-line{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3)}.axm-assist-bot .axm-assist-bot__segment{unicode-bidi:isolate;font-size:.95rem;line-height:1.8;letter-spacing:.002em;color:rgb(var(--ax-sys-color-on-surface));text-wrap:pretty}.axm-assist-bot .axm-assist-bot__segment:has(axm-chat-user-form-renderer){inline-size:100%;max-inline-size:100%}.axm-assist-bot .axm-assist-bot__segment ax-conversation-text-renderer{display:block}.axm-assist-bot .axm-assist-bot__segment ax-conversation-text-renderer :is(p,ul,ol){margin-block:.45rem}.axm-assist-bot .axm-assist-bot__segment ax-conversation-text-renderer li{line-height:1.8}.axm-assist-bot .axm-assist-bot__line--user{align-self:flex-end}.axm-assist-bot .axm-assist-bot__line--tool-role{align-self:stretch}.axm-assist-bot .axm-assist-bot__tool-result{border-radius:var(--radius-lg, .5rem);background:rgb(var(--ax-sys-color-lighter-surface));padding:.5rem .75rem}.axm-assist-bot .axm-assist-bot__fallback{font-size:.88rem;line-height:1.75;word-break:break-word;opacity:.9}.axm-assist-bot .axm-assist-renderer__page-action{display:flex;align-items:flex-start;gap:.625rem;margin-block-start:.5rem;padding:.625rem .75rem;border-radius:.75rem;border:1px solid rgb(var(--ax-sys-color-border-success-lightest-surface));background:rgb(var(--ax-sys-color-success-50))}.axm-assist-bot .axm-assist-renderer__page-action-icon{display:grid;place-items:center;width:1.25rem;height:1.25rem;color:rgb(var(--ax-sys-color-success-600));flex-shrink:0;margin-block-start:.1rem}.axm-assist-bot .axm-assist-renderer__page-action-copy{display:flex;flex-direction:column;gap:.125rem;min-width:0;flex:1}.axm-assist-bot .axm-assist-renderer__page-action-title{font-size:.8125rem;font-weight:600;color:rgb(var(--ax-sys-color-on-surface))}.axm-assist-bot .axm-assist-renderer__page-action-label{font-size:.75rem;line-height:1.4;color:rgb(var(--ax-sys-color-on-surface));opacity:.72}.axm-assist-bot .axm-assist-renderer__entity-cards{display:flex;flex-direction:column;gap:.625rem;margin-block-start:.5rem}@keyframes axm-blink{0%{opacity:1}50%{opacity:0}}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
|
|
1748
|
+
}], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: true }] }] } });
|
|
1749
|
+
|
|
1750
|
+
var assistMessageRenderer_component = /*#__PURE__*/Object.freeze({
|
|
1751
|
+
__proto__: null,
|
|
1752
|
+
AXMAssistMessageRendererComponent: AXMAssistMessageRendererComponent
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
export { AXMAssistBotFileLineComponent as A, axmAssistSegmentDirection as a, assistMessageRenderer_component as b };
|
|
1756
|
+
//# sourceMappingURL=acorex-platform-module-conversation-client-assist-message-renderer.component-DyVepuaW.mjs.map
|