@c8y/ngx-components 1023.68.7 → 1023.71.1
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/ai/agent-chat/index.d.ts +235 -75
- package/ai/agent-chat/index.d.ts.map +1 -1
- package/ai/ai-chat/index.d.ts +176 -26
- package/ai/ai-chat/index.d.ts.map +1 -1
- package/ai/index.d.ts +309 -75
- package/ai/index.d.ts.map +1 -1
- package/asset-properties/index.d.ts.map +1 -1
- package/echart/index.d.ts +4 -0
- package/echart/index.d.ts.map +1 -1
- package/echart/models/index.d.ts +2 -0
- package/echart/models/index.d.ts.map +1 -1
- package/events/events-timeline/index.d.ts +3 -2
- package/events/events-timeline/index.d.ts.map +1 -1
- package/fesm2022/c8y-ngx-components-ai-agent-chat.mjs +680 -242
- package/fesm2022/c8y-ngx-components-ai-agent-chat.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-ai-ai-chat.mjs +343 -44
- package/fesm2022/c8y-ngx-components-ai-ai-chat.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-ai.mjs +187 -75
- package/fesm2022/c8y-ngx-components-ai.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-asset-properties.mjs +11 -5
- package/fesm2022/c8y-ngx-components-asset-properties.mjs.map +1 -1
- package/fesm2022/{c8y-ngx-components-computed-asset-properties-alarm-count-config.component-B2cy8gI7.mjs → c8y-ngx-components-computed-asset-properties-alarm-count-config.component-CPLDClTp.mjs} +3 -3
- package/fesm2022/{c8y-ngx-components-computed-asset-properties-alarm-count-config.component-B2cy8gI7.mjs.map → c8y-ngx-components-computed-asset-properties-alarm-count-config.component-CPLDClTp.mjs.map} +1 -1
- package/fesm2022/{c8y-ngx-components-computed-asset-properties-c8y-ngx-components-computed-asset-properties-BYHnA-5R.mjs → c8y-ngx-components-computed-asset-properties-c8y-ngx-components-computed-asset-properties-9be_iMQg.mjs} +30 -13
- package/fesm2022/c8y-ngx-components-computed-asset-properties-c8y-ngx-components-computed-asset-properties-9be_iMQg.mjs.map +1 -0
- package/fesm2022/{c8y-ngx-components-computed-asset-properties-configuration-snapshot-config.component-C4oL39m8.mjs → c8y-ngx-components-computed-asset-properties-configuration-snapshot-config.component-B2em01_W.mjs} +3 -3
- package/fesm2022/c8y-ngx-components-computed-asset-properties-configuration-snapshot-config.component-B2em01_W.mjs.map +1 -0
- package/fesm2022/{c8y-ngx-components-computed-asset-properties-event-count-config.component-DGwm6_C9.mjs → c8y-ngx-components-computed-asset-properties-event-count-config.component-CQuGa1RI.mjs} +3 -3
- package/fesm2022/c8y-ngx-components-computed-asset-properties-event-count-config.component-CQuGa1RI.mjs.map +1 -0
- package/fesm2022/{c8y-ngx-components-computed-asset-properties-fieldbus-item-status-config.component-BxfCjbYY.mjs → c8y-ngx-components-computed-asset-properties-fieldbus-item-status-config.component-CkmurxJv.mjs} +5 -5
- package/fesm2022/{c8y-ngx-components-computed-asset-properties-fieldbus-item-status-config.component-BxfCjbYY.mjs.map → c8y-ngx-components-computed-asset-properties-fieldbus-item-status-config.component-CkmurxJv.mjs.map} +1 -1
- package/fesm2022/c8y-ngx-components-computed-asset-properties-last-measurement-config.component-CTK9zNUh.mjs +86 -0
- package/fesm2022/c8y-ngx-components-computed-asset-properties-last-measurement-config.component-CTK9zNUh.mjs.map +1 -0
- package/fesm2022/c8y-ngx-components-computed-asset-properties.mjs +1 -1
- package/fesm2022/c8y-ngx-components-device-shell.mjs +1 -1
- package/fesm2022/c8y-ngx-components-device-shell.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-echart-models.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-echart.mjs +39 -18
- package/fesm2022/c8y-ngx-components-echart.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-events-events-timeline.mjs +5 -2
- package/fesm2022/c8y-ngx-components-events-events-timeline.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-operations-operations-timeline.mjs +5 -2
- package/fesm2022/c8y-ngx-components-operations-operations-timeline.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget-ai-config.mjs +106 -4
- package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget-ai-config.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-implementations-html-widget.mjs +14 -122
- package/fesm2022/c8y-ngx-components-widgets-implementations-html-widget.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components.mjs +9 -5
- package/fesm2022/c8y-ngx-components.mjs.map +1 -1
- package/index.d.ts +2 -1
- package/index.d.ts.map +1 -1
- package/locales/de.po +88 -35
- package/locales/es.po +87 -34
- package/locales/fr.po +87 -34
- package/locales/ja_JP.po +87 -35
- package/locales/ko.po +88 -35
- package/locales/locales.pot +83 -30
- package/locales/nl.po +88 -35
- package/locales/pl.po +88 -35
- package/locales/pt_BR.po +87 -34
- package/locales/zh_CN.po +88 -35
- package/locales/zh_TW.po +88 -35
- package/operations/operations-timeline/index.d.ts +3 -2
- package/operations/operations-timeline/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/widgets/implementations/html-widget/index.d.ts +11 -50
- package/widgets/implementations/html-widget/index.d.ts.map +1 -1
- package/fesm2022/c8y-ngx-components-computed-asset-properties-c8y-ngx-components-computed-asset-properties-BYHnA-5R.mjs.map +0 -1
- package/fesm2022/c8y-ngx-components-computed-asset-properties-configuration-snapshot-config.component-C4oL39m8.mjs.map +0 -1
- package/fesm2022/c8y-ngx-components-computed-asset-properties-event-count-config.component-DGwm6_C9.mjs.map +0 -1
- package/fesm2022/c8y-ngx-components-computed-asset-properties-last-measurement-config.component-C1cuxN3L.mjs +0 -92
- package/fesm2022/c8y-ngx-components-computed-asset-properties-last-measurement-config.component-C1cuxN3L.mjs.map +0 -1
|
@@ -1,181 +1,555 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NgComponentOutlet, AsyncPipe } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { Injectable, inject, input, computed, output, signal, viewChildren, Component, Injector, Input } from '@angular/core';
|
|
4
|
+
import { GainsightService, DatePipe, ModalService, AlertService, C8Y_PLUGIN_CONTEXT_PATH, AppStateService, Status, LoadingComponent, EmptyStateComponent, C8yTranslateDirective, GuideDocsComponent, GuideHrefDirective, MarkdownToHtmlPipe, C8yTranslatePipe, ContextRouteService } from '@c8y/ngx-components';
|
|
5
|
+
import { TranslateService } from '@ngx-translate/core';
|
|
6
|
+
import { AIService, defaultPruneMessagesForAgent } from '@c8y/ngx-components/ai';
|
|
7
7
|
import { gettext } from '@c8y/ngx-components/gettext';
|
|
8
|
-
import {
|
|
8
|
+
import { AiChatAssistantMessageComponent, AiChatComponent, AiChatSuggestionComponent, AiChatMessageComponent, AiChatMessageActionComponent } from '@c8y/ngx-components/ai/ai-chat';
|
|
9
|
+
import { CollapseModule } from 'ngx-bootstrap/collapse';
|
|
9
10
|
import { WidgetConfigService, WidgetConfigFeedbackComponent } from '@c8y/ngx-components/context-dashboard';
|
|
11
|
+
import { of, isObservable } from 'rxjs';
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
/** A stateless service used by agent-chat for managing chat history. Not public. */
|
|
14
|
+
class ChatHistoryService {
|
|
15
|
+
/**
|
|
16
|
+
* Save messages and suggestions to an opaque, versioned, JSON-serializable snapshot for persistence.
|
|
17
|
+
*
|
|
18
|
+
* Only recent tool results and reasoning are included to save space.
|
|
19
|
+
* Additional options for compressing the history can be provided using the config parameters.
|
|
20
|
+
*
|
|
21
|
+
* @param messages The current conversation messages to save
|
|
22
|
+
* @param suggestions The current suggestions to include in the snapshot
|
|
23
|
+
* @param config Optional configuration to limit what gets saved
|
|
24
|
+
*/
|
|
25
|
+
save(messages, suggestions, config) {
|
|
26
|
+
let msgs = messages;
|
|
27
|
+
// Apply maxMessages limit - keep most recent messages
|
|
28
|
+
if (config?.maxMessages && msgs.length > config.maxMessages) {
|
|
29
|
+
msgs = msgs.slice(-config.maxMessages);
|
|
20
30
|
}
|
|
31
|
+
const transientToolNames = new Set(config?.transientToolNames || []);
|
|
32
|
+
// NB: in future we could use tool metadata from the MCP servers to identify transient tools automatically
|
|
33
|
+
// Only keep tool results and reasoning for the most recent N messages (=N/2 assistant messages)
|
|
34
|
+
const recentMessageStartIndex = msgs.length - Math.min(10, msgs.length);
|
|
35
|
+
const history = {
|
|
36
|
+
messages: msgs.map((msg, msgIndex) => {
|
|
37
|
+
const isRecentMessage = msgIndex >= recentMessageStartIndex;
|
|
38
|
+
return {
|
|
39
|
+
role: msg.role,
|
|
40
|
+
content: msg.content,
|
|
41
|
+
...(msg.timestamp ? { timestamp: msg.timestamp } : {}),
|
|
42
|
+
...(msg.steps
|
|
43
|
+
? {
|
|
44
|
+
steps: msg.steps.map(step => ({
|
|
45
|
+
// Specifically include just the fields we're interested in
|
|
46
|
+
type: step.type,
|
|
47
|
+
text: step.text,
|
|
48
|
+
aborted: step.aborted,
|
|
49
|
+
// Strip out ALL toolCalls, since these are anyway transient
|
|
50
|
+
toolCalls: undefined,
|
|
51
|
+
// Keep recent reasoning only
|
|
52
|
+
reasoning: isRecentMessage ? step.reasoning : undefined,
|
|
53
|
+
// Keep toolResults only if: in recent 10 messages AND not transient
|
|
54
|
+
toolResults: isRecentMessage && step.toolResults
|
|
55
|
+
? step.toolResults.filter(result => !transientToolNames.has(result.toolName))
|
|
56
|
+
: undefined
|
|
57
|
+
}))
|
|
58
|
+
}
|
|
59
|
+
: {})
|
|
60
|
+
};
|
|
61
|
+
}),
|
|
62
|
+
suggestions: suggestions,
|
|
63
|
+
chatHistoryVersion: 1
|
|
64
|
+
};
|
|
65
|
+
//console.debug("Saving chat history as: ", history);
|
|
66
|
+
return history;
|
|
21
67
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Restore a previously saved chat history snapshot.
|
|
70
|
+
*
|
|
71
|
+
* Validates the snapshot format and returns the messages and suggestions.
|
|
72
|
+
* Throws an error if the snapshot is invalid.
|
|
73
|
+
*
|
|
74
|
+
* @param history A snapshot previously returned by `save()`
|
|
75
|
+
*/
|
|
76
|
+
restore(history) {
|
|
77
|
+
// Validate history structure and messages
|
|
78
|
+
if (typeof history !== 'object' ||
|
|
79
|
+
history === null ||
|
|
80
|
+
!('messages' in history) ||
|
|
81
|
+
!Array.isArray(history['messages'])) {
|
|
82
|
+
throw new Error('Invalid chat history format');
|
|
25
83
|
}
|
|
84
|
+
if (history['chatHistoryVersion'] !== 1) {
|
|
85
|
+
throw new Error(`Cannot load chat history - expected version 1 but got ${history['chatHistoryVersion']}`);
|
|
86
|
+
}
|
|
87
|
+
const messagesArray = history['messages'];
|
|
88
|
+
if (messagesArray.some((msg, _) => !msg || typeof msg !== 'object' || !msg.role || typeof msg.content !== 'string')) {
|
|
89
|
+
throw new Error('Invalid message format in chat history');
|
|
90
|
+
}
|
|
91
|
+
const suggestions = Array.isArray(history['suggestions'])
|
|
92
|
+
? history['suggestions']
|
|
93
|
+
: [];
|
|
94
|
+
return {
|
|
95
|
+
messages: messagesArray,
|
|
96
|
+
suggestions
|
|
97
|
+
};
|
|
26
98
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
99
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ChatHistoryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
100
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ChatHistoryService, providedIn: 'root' }); }
|
|
101
|
+
}
|
|
102
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ChatHistoryService, decorators: [{
|
|
103
|
+
type: Injectable,
|
|
104
|
+
args: [{
|
|
105
|
+
providedIn: 'root'
|
|
106
|
+
}]
|
|
107
|
+
}] });
|
|
108
|
+
|
|
109
|
+
/** A service used to send product experience data to GainSight PX. Not public. */
|
|
110
|
+
class UserAnalyticsService {
|
|
111
|
+
constructor() {
|
|
112
|
+
this.gainsightService = inject(GainsightService);
|
|
113
|
+
/** If needed we could set this to false in production to only include hashes of the messages,
|
|
114
|
+
* if there's a decision that customer data shouldn't be uploaded to GainSight. */
|
|
115
|
+
this.includeCustomerSensitiveDataInAnalytics = true;
|
|
116
|
+
}
|
|
117
|
+
/** Create a dictionary of context from the agent-chat component that is useful to include in all GainSight messages */
|
|
118
|
+
async getAnalyticsMetadataContext(agentName, agentDefinition, userAnalyticsContext, model, systemPrompt) {
|
|
119
|
+
let systemPromptHash = undefined;
|
|
120
|
+
if (systemPrompt) {
|
|
121
|
+
systemPromptHash = await this._computeHash(systemPrompt.map(s => s.text).join('\n\n'));
|
|
33
122
|
}
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.canCollapse = true;
|
|
38
|
-
this.collapsed = true;
|
|
123
|
+
else if (agentDefinition && agentDefinition.definition) {
|
|
124
|
+
// Fall back to getting it from the agent definition (if provided)
|
|
125
|
+
systemPromptHash = await this._computeHash(agentDefinition.definition.agent.system);
|
|
39
126
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
127
|
+
return {
|
|
128
|
+
// User-defined context first
|
|
129
|
+
...(userAnalyticsContext || {}),
|
|
130
|
+
model: model,
|
|
131
|
+
agent: agentName,
|
|
132
|
+
// Since we don't currently have any other version indicator available, a hash of the system prompt is useful to correlate feedback with the exact prompt used
|
|
133
|
+
systemPromptHash: systemPromptHash
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** Compute SHA-256 hash of a string for correlation purposes (assumes a secure context where crypto.subtle.digest is available) */
|
|
137
|
+
async _computeHash(message) {
|
|
138
|
+
const encoder = new TextEncoder();
|
|
139
|
+
const data = encoder.encode(message);
|
|
140
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
141
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
142
|
+
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
143
|
+
}
|
|
144
|
+
/** Summarize message metrics for GainSight analytics (without including any proprietary data) */
|
|
145
|
+
async _summarizeMessagesForGainSight(userMessage, assistantMessage, detailed = false) {
|
|
146
|
+
// NB: Do NOT send any message content to GainSight servers because that's proprietary/personal customer-owned data that we must protect
|
|
147
|
+
// except when includeCustomerSensitiveDataInAnalytics is set;
|
|
148
|
+
// include hashes of messages so we can always correlate with the history stored on our own servers if available
|
|
149
|
+
const allAssistantText = assistantMessage.steps
|
|
150
|
+
? assistantMessage.steps
|
|
151
|
+
?.filter(s => s.text)
|
|
152
|
+
.map(s => s.text || '')
|
|
153
|
+
.join('\n---\n') || ''
|
|
154
|
+
: assistantMessage.content;
|
|
155
|
+
// Collect all unique tool names from each step's toolResults
|
|
156
|
+
const toolNames = assistantMessage.steps
|
|
157
|
+
?.flatMap(s => s.toolResults?.map(tr => tr.toolName) || [])
|
|
158
|
+
?.filter((name, index, self) => self.indexOf(name) === index) || [];
|
|
159
|
+
return {
|
|
160
|
+
assistantMessageTimestamp: assistantMessage.timestamp,
|
|
161
|
+
// Track how long we're spending waiting for the AI
|
|
162
|
+
assistantDurationSecs: assistantMessage.timestamp && userMessage.timestamp
|
|
163
|
+
? (new Date(assistantMessage.timestamp).getTime() -
|
|
164
|
+
new Date(userMessage.timestamp).getTime()) /
|
|
165
|
+
1000
|
|
166
|
+
: undefined,
|
|
167
|
+
assistantMessageHash: await this._computeHash(allAssistantText),
|
|
168
|
+
assistantMessage: this.includeCustomerSensitiveDataInAnalytics ? allAssistantText : undefined,
|
|
169
|
+
// although harder to read, it's useful to include the full message including tool calls and steps the feedback is due to bugs etc
|
|
170
|
+
assistantMessageJSON: this.includeCustomerSensitiveDataInAnalytics && detailed
|
|
171
|
+
? JSON.stringify(assistantMessage)
|
|
172
|
+
: undefined,
|
|
173
|
+
userMessageHash: userMessage ? await this._computeHash(userMessage.content) : '',
|
|
174
|
+
userMessage: this.includeCustomerSensitiveDataInAnalytics ? userMessage.content : undefined,
|
|
175
|
+
assistantMessageSteps: assistantMessage.steps?.length || 0,
|
|
176
|
+
// nb: toolCalls here refers to completed calls (i.e. toolResults), we shouldn't have any calls still in progress at this point
|
|
177
|
+
assistantMessageToolCallsCount: assistantMessage.steps?.reduce((sum, s) => sum + (s.toolResults?.length || 0), 0) || 0,
|
|
178
|
+
assistantMessageToolNames: toolNames.length === 0 ? undefined : toolNames.join(','),
|
|
179
|
+
assistantMessageLines: allAssistantText.split('\n').length,
|
|
180
|
+
userMessageLength: userMessage.content.length,
|
|
181
|
+
userMessageLines: userMessage.content.split('\n').length
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async sendAssistantMessageComplete(assistantMessage, allMessages, analyticsMetadataContext) {
|
|
185
|
+
// Can't use lastIndexOf(assistantMessage) here (as we for for feedback) because allMessages may not yet have been updated with this assistantMessage item
|
|
186
|
+
const userMessage = [...allMessages].reverse().find(m => m.role === 'user');
|
|
187
|
+
if (!userMessage) {
|
|
188
|
+
console.warn('No user message found for assistant message, skipping analytics event');
|
|
189
|
+
return;
|
|
45
190
|
}
|
|
191
|
+
this.gainsightService.triggerEvent('ai.agent.message', {
|
|
192
|
+
messageCount: allMessages.length,
|
|
193
|
+
...(await this._summarizeMessagesForGainSight(userMessage, assistantMessage)),
|
|
194
|
+
...analyticsMetadataContext
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/** Sent when there is a streaming error from the agent */
|
|
198
|
+
async sendAssistantMessageError(error, allMessages, analyticsMetadataContext) {
|
|
199
|
+
this.gainsightService.triggerEvent('ai.agent.error', {
|
|
200
|
+
errorMessage: error,
|
|
201
|
+
messageCount: allMessages.length,
|
|
202
|
+
...analyticsMetadataContext
|
|
203
|
+
});
|
|
46
204
|
}
|
|
47
|
-
|
|
48
|
-
|
|
205
|
+
/** Sent when the agent is unavailable, for example because the microservice is not present or has insufficient permissions.
|
|
206
|
+
* This is useful to find out what kinds of problems users are mostly having to get access.
|
|
207
|
+
*/
|
|
208
|
+
async sendAgentUnavailable(errorType) {
|
|
209
|
+
this.gainsightService.triggerEvent('ai.agent.unavailable', {
|
|
210
|
+
errorType: errorType
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/** Rates an AI message as positive/negative, sending the feedback to GainSight */
|
|
214
|
+
async sendFeedbackOnAssistantMessage(assistantMessage, allMessages, positive, analyticsMetadataContext) {
|
|
215
|
+
const userMessage = allMessages[allMessages.lastIndexOf(assistantMessage) - 1];
|
|
216
|
+
const detailedFeedback = !positive;
|
|
217
|
+
this.gainsightService.triggerEvent('ai.agent.feedback', {
|
|
218
|
+
positive,
|
|
219
|
+
messageCount: allMessages.length,
|
|
220
|
+
...(await this._summarizeMessagesForGainSight(userMessage, assistantMessage, detailedFeedback)),
|
|
221
|
+
...analyticsMetadataContext
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UserAnalyticsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
225
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UserAnalyticsService, providedIn: 'root' }); }
|
|
49
226
|
}
|
|
50
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type:
|
|
51
|
-
type:
|
|
52
|
-
args: [{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
ListItemBodyComponent,
|
|
57
|
-
ListItemIconComponent,
|
|
58
|
-
NgClass,
|
|
59
|
-
JsonPipe,
|
|
60
|
-
MarkdownToHtmlPipe,
|
|
61
|
-
AsyncPipe,
|
|
62
|
-
C8yTranslatePipe
|
|
63
|
-
], host: { class: 'agent-step-feedback' }, template: "@if (!step.reasoning) {\n <div [innerHTML]=\"step.text | markdownToHtml | async\"></div>\n}\n@if (label) {\n <c8y-list-group class=\"m-t-16 m-b-16\">\n <c8y-li\n [active]=\"!loading\"\n [collapsed]=\"collapsed\"\n >\n <c8y-li-icon>\n <span\n class=\"btn-ai btn-ai-hint btn-sm\"\n [ngClass]=\"{ working: loading }\"\n >\n <span></span>\n </span>\n </c8y-li-icon>\n <c8y-li-body>\n {{ label | translate }}\n </c8y-li-body>\n\n @if (canCollapse) {\n <c8y-li-collapse>\n @if (step.reasoning) {\n <div [innerHTML]=\"step.reasoning | markdownToHtml | async\"></div>\n } @else if (step) {\n <pre\n class=\"fit-w\"\n style=\"max-height: 320px\"\n >{{ step | json }}</pre\n >\n }\n </c8y-li-collapse>\n }\n </c8y-li>\n </c8y-list-group>\n}\n" }]
|
|
64
|
-
}], propDecorators: { step: [{
|
|
65
|
-
type: Input,
|
|
66
|
-
args: [{ required: true }]
|
|
67
|
-
}], label: [{
|
|
68
|
-
type: Input
|
|
69
|
-
}], loading: [{
|
|
70
|
-
type: Input
|
|
71
|
-
}], collapsed: [{
|
|
72
|
-
type: Input
|
|
73
|
-
}], canCollapse: [{
|
|
74
|
-
type: Input
|
|
75
|
-
}] } });
|
|
227
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UserAnalyticsService, decorators: [{
|
|
228
|
+
type: Injectable,
|
|
229
|
+
args: [{
|
|
230
|
+
providedIn: 'root'
|
|
231
|
+
}]
|
|
232
|
+
}] });
|
|
76
233
|
|
|
77
234
|
class AgentChatComponent {
|
|
78
235
|
constructor() {
|
|
79
|
-
this.
|
|
80
|
-
this.
|
|
81
|
-
this.
|
|
82
|
-
|
|
83
|
-
this.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
236
|
+
this.translateService = inject(TranslateService);
|
|
237
|
+
this.datePipe = inject(DatePipe);
|
|
238
|
+
this.modalService = inject(ModalService);
|
|
239
|
+
/** Debug logging function for AI messages. Can be enabled at runtime with localStorage.setItem('c8y-debug-log.agent-chat', 'true'); */
|
|
240
|
+
this.debugLog = (category => {
|
|
241
|
+
const isDebugEnabled = localStorage.getItem('c8y-debug-log.' + category) === 'true';
|
|
242
|
+
return (message, ...args) => isDebugEnabled && console.debug(`[${category} DEBUG] ${message}`, ...args);
|
|
243
|
+
})('agent-chat');
|
|
244
|
+
/**
|
|
245
|
+
* Identifies which AI agent from the AI Agent Manager service will be used by this component.
|
|
246
|
+
*
|
|
247
|
+
* Where possible, provide a `ClientAgentDefinition` rather than just a name.
|
|
248
|
+
*
|
|
249
|
+
* It is recommended to define a `toolCallConfig` in the same place you define `ClientAgentDefinition`,
|
|
250
|
+
* and to use the keys from `assistantMessageDisplayConfig.toolCallConfig`
|
|
251
|
+
* in the `ClientAgentDefinition` tools list.
|
|
252
|
+
*/
|
|
253
|
+
this.agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
|
|
254
|
+
/**
|
|
255
|
+
* Clickable suggestions buttons to display just above the chat, for likely next user actions.
|
|
256
|
+
* These may be hardcoded or be set by the AI agent (e.g. in a tool call).
|
|
257
|
+
* This input should be `undefined` to indicate that there are no new suggestions yet, and that any restored from the chat history should be used instead.
|
|
258
|
+
* You can use the `isWelcoming` output to show different suggestions while the initial welcome message is displayed.
|
|
259
|
+
*/
|
|
260
|
+
this.suggestions = input([], ...(ngDevMode ? [{ debugName: "suggestions" }] : []));
|
|
261
|
+
/** ChatConfig to customize the ai-chat component's configuration if desired. */
|
|
262
|
+
this.chatConfig = input(...(ngDevMode ? [undefined, { debugName: "chatConfig" }] : []));
|
|
263
|
+
/** Template for customizing the welcome view using `ng-template`. */
|
|
264
|
+
this.welcomeTemplate = input(...(ngDevMode ? [undefined, { debugName: "welcomeTemplate" }] : []));
|
|
265
|
+
/** Indicates whether the component has zero messages and is showing the welcome page. */
|
|
266
|
+
this.isWelcoming = computed(() => this.messages().length === 0, ...(ngDevMode ? [{ debugName: "isWelcoming" }] : []));
|
|
267
|
+
/** Configures whether this component should automatically create the provided `agent: ClientAgentDefinition`
|
|
268
|
+
* if an agent of that name doesn't already exist. This is an alternative to the usual mechanism of configuring
|
|
269
|
+
* agents using a `.agent.c8y.ts` file.
|
|
270
|
+
*/
|
|
271
|
+
this.autoCreateAgents = input(true, ...(ngDevMode ? [{ debugName: "autoCreateAgents" }] : []));
|
|
272
|
+
/** Variables to pass to the AI agent for placeholders specified in the agent's system prompt. */
|
|
273
|
+
this.variables = input({}, ...(ngDevMode ? [{ debugName: "variables" }] : []));
|
|
274
|
+
/**
|
|
275
|
+
* Allows overriding the component used to render messages from the AI assistant, if the default
|
|
276
|
+
* AiChatAssistantMessageComponent does not do what you need.
|
|
277
|
+
*
|
|
278
|
+
* The component must accept a single input `assistantMessageContext: AssistantMessageContext`.
|
|
279
|
+
* This object contains all data needed to render the assistant message and is forward-compatible with future additions.
|
|
280
|
+
*
|
|
281
|
+
* Example usage:
|
|
282
|
+
* <c8y-agent-chat [assistantMessageComponent]="MyCustomAssistantMessageComponent"></c8y-agent-chat>
|
|
283
|
+
*/
|
|
284
|
+
this.assistantMessageComponent = input(AiChatAssistantMessageComponent, ...(ngDevMode ? [{ debugName: "assistantMessageComponent" }] : []));
|
|
285
|
+
/**
|
|
286
|
+
* Configuration passed to `AiChatAssistantMessageComponent` for controlling how messages from the assistant
|
|
287
|
+
* are rendered, including tool calls.
|
|
288
|
+
*
|
|
289
|
+
* If you are performing any tool calls it is recommended to configure the `toolCallConfig`
|
|
290
|
+
* to provide localized display names for the tools used in your `AgentDefinitionConfig`.
|
|
291
|
+
*/
|
|
292
|
+
this.assistantMessageDisplayConfig = input({}, ...(ngDevMode ? [{ debugName: "assistantMessageDisplayConfig" }] : []));
|
|
293
|
+
/**
|
|
294
|
+
* Optional function to preprocess messages from the assistant before they are displayed, for example if you need to remove special
|
|
295
|
+
* tagged sections from the content to move into simulated tool calls.
|
|
296
|
+
*
|
|
297
|
+
* This function may be called many times for each message, as more steps, tool calls and content are streamed from the agent.
|
|
298
|
+
*
|
|
299
|
+
* If you modify any part of the message other than the `changedPart`, you should replace it with a new shallow copy of the object so
|
|
300
|
+
* that change detection works correctly. This is not necessary for changedPart.
|
|
301
|
+
*
|
|
302
|
+
* @param message The message from the AI assistant.
|
|
303
|
+
* @param changedPart The part of the message that has changed, which can be modified in-place if desired. Currently only tool calls are included here.
|
|
304
|
+
*/
|
|
305
|
+
this.preprocessAgentMessage = input(undefined, ...(ngDevMode ? [{ debugName: "preprocessAgentMessage" }] : []));
|
|
306
|
+
/** Optional function to override how the message history is prepared and compacted ready for sending with
|
|
307
|
+
* agent requests. This overrides the default behaviour from `defaultPruneMessagesForAgent`.
|
|
308
|
+
*
|
|
309
|
+
* For example this can be used to remove unnecessary information to minimize tokens, reduce the number of messages,
|
|
310
|
+
* or to make what is sent to the agent different from what is rendered in the UI.
|
|
311
|
+
*/
|
|
312
|
+
this.pruneMessagesForAgent = input(undefined, ...(ngDevMode ? [{ debugName: "pruneMessagesForAgent" }] : []));
|
|
313
|
+
/** Input that provides a previously-saved chat history snapshot to restore. */
|
|
314
|
+
this.initialChatHistory = input(...(ngDevMode ? [undefined, { debugName: "initialChatHistory" }] : []));
|
|
315
|
+
/** Suggestions that were restored from the initial chat history; used if `suggestions` input is not yet set. */
|
|
316
|
+
this._restoredSuggestions = [];
|
|
317
|
+
/**
|
|
318
|
+
* An optional function that returns a message sent in AI agent requests just before each new user message to provide context or "grounding" to
|
|
319
|
+
* anchor the AI's response - such as the current state of the document the AI is helping with.
|
|
320
|
+
*
|
|
321
|
+
* Typically this string would begin with a special prefix (e.g. "[STATE_SNAPSHOT]") that is described by the system prompt.
|
|
322
|
+
*
|
|
323
|
+
* This message does not form part of the chat history, to avoid confusing the AI agent with older grounding state.
|
|
324
|
+
* It is injected into the message history just before the latest user message as per standard recommendations,
|
|
325
|
+
* since AI gives more attention to the recent content.
|
|
326
|
+
*/
|
|
327
|
+
this.groundingContextProvider = input(...(ngDevMode ? [undefined, { debugName: "groundingContextProvider" }] : []));
|
|
89
328
|
/**
|
|
90
|
-
*
|
|
329
|
+
* Provides application-defined fields to enrich the user feedback tracking data for this component.
|
|
330
|
+
*
|
|
331
|
+
* Do not include Intellectual Property or Personal Identifiable Information in this object, as it may
|
|
332
|
+
* be included in feedback data sent to third-party services.
|
|
91
333
|
*/
|
|
92
|
-
this.
|
|
93
|
-
this.
|
|
334
|
+
this.userAnalyticsContext = input(...(ngDevMode ? [undefined, { debugName: "userAnalyticsContext" }] : []));
|
|
335
|
+
this.onMessageFinish = output();
|
|
336
|
+
/**
|
|
337
|
+
* Notified when a tool result is received from the agent.
|
|
338
|
+
*
|
|
339
|
+
* This is called before it is added to the message displayed in the component.
|
|
340
|
+
*
|
|
341
|
+
* Tools with a client/UI implementation may provide `output` for the tool
|
|
342
|
+
* (e.g. data to render in the UI) by calling `addToolOutput`.
|
|
343
|
+
*/
|
|
344
|
+
this.onToolResult = output();
|
|
345
|
+
/** Notified when the user presses the positive or negative feedback buttons for an assistant message.*/
|
|
346
|
+
this.onFeedback = output();
|
|
347
|
+
this._isLoading = signal(true, ...(ngDevMode ? [{ debugName: "_isLoading" }] : [])); // initially true while we do our initial ping of the agent health - prevents message flashing up before ready
|
|
348
|
+
this.aiAgentManagerApplicationName = gettext('AI Agent Manager');
|
|
349
|
+
/** Stores any error with the agent or backend microservice. This error message is already translated. */
|
|
350
|
+
this._agentHealthError = signal(this.translateService.instant(gettext('Connecting to AI agent...')), ...(ngDevMode ? [{ debugName: "_agentHealthError" }] : []));
|
|
351
|
+
/** Stores the most recent agent health check response, or `undefined` while the initial check is in progress. */
|
|
352
|
+
this._agentHealth = signal(undefined, ...(ngDevMode ? [{ debugName: "_agentHealth" }] : []));
|
|
353
|
+
/** Stores any detailed error messages from the backend. */
|
|
354
|
+
this.agentHealthDetailedMessages = signal(undefined, ...(ngDevMode ? [{ debugName: "agentHealthDetailedMessages" }] : []));
|
|
355
|
+
/**
|
|
356
|
+
* Stores any error that occurred during streaming the current message from the agent.
|
|
357
|
+
*
|
|
358
|
+
* If possible this string is translated already or registered with `gettext`.
|
|
359
|
+
* In practice, this may not be translated if it comes from the backend.
|
|
360
|
+
*/
|
|
361
|
+
this.agentStreamError = signal(undefined, ...(ngDevMode ? [{ debugName: "agentStreamError" }] : []));
|
|
362
|
+
// A stream of AI messages representing the conversation.
|
|
363
|
+
// NB: this is NOT part of the API of this component and may change in future
|
|
364
|
+
// (e.g. we may improve AIMessage class)
|
|
365
|
+
this.messages = signal([], ...(ngDevMode ? [{ debugName: "messages" }] : []));
|
|
366
|
+
/** If the create agent button should be shown to the user. */
|
|
94
367
|
this.canCreate = false;
|
|
95
|
-
this.errorMsg = '';
|
|
96
368
|
this.prompt = '';
|
|
97
369
|
this.aiService = inject(AIService);
|
|
98
|
-
this.
|
|
370
|
+
this.userAnalyticsService = inject(UserAnalyticsService);
|
|
99
371
|
this.alertService = inject(AlertService);
|
|
372
|
+
this.chatHistoryService = inject(ChatHistoryService);
|
|
100
373
|
this.pluginContextPath = inject(C8Y_PLUGIN_CONTEXT_PATH, { optional: true });
|
|
101
374
|
this.appState = inject(AppStateService);
|
|
102
|
-
this.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
})));
|
|
109
|
-
};
|
|
375
|
+
this.assistantMessageComponents = viewChildren(AiChatAssistantMessageComponent, ...(ngDevMode ? [{ debugName: "assistantMessageComponents" }] : []));
|
|
376
|
+
/**
|
|
377
|
+
* Stores client-supplied tool outputs to override tool results for a given toolCallId in the current assistant message.
|
|
378
|
+
* Cleared at the start of each new message.
|
|
379
|
+
*/
|
|
380
|
+
this._clientToolOutputs = {};
|
|
110
381
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
382
|
+
get agentName() {
|
|
383
|
+
const value = this.agent();
|
|
384
|
+
return typeof value === 'string' ? value : value.definition.name;
|
|
385
|
+
}
|
|
386
|
+
get agentDefinition() {
|
|
387
|
+
const value = this.agent();
|
|
388
|
+
if (!value || typeof value === 'string')
|
|
389
|
+
return undefined;
|
|
390
|
+
return value;
|
|
391
|
+
}
|
|
392
|
+
/** Is an AI agent request currently in progress. This can be used to disable UI elements while a reply (which might change the state) is being loaded. */
|
|
393
|
+
get isLoadingAiResponse() {
|
|
394
|
+
return this._isLoading.asReadonly();
|
|
395
|
+
}
|
|
396
|
+
/** Returns the most recent agent health check status such as `ready`, or an error. */
|
|
397
|
+
get agentHealthStatus() {
|
|
398
|
+
return this._agentHealth()?.status || 'loading';
|
|
120
399
|
}
|
|
121
400
|
get currentContextPath() {
|
|
122
401
|
return this.pluginContextPath || this.appState.currentApplication?.value?.contextPath || '';
|
|
123
402
|
}
|
|
124
403
|
async ngOnInit() {
|
|
125
|
-
if
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this.isLoading = true;
|
|
404
|
+
// Restore chat history first, if provided
|
|
405
|
+
this._restoreChatHistory(this.initialChatHistory());
|
|
406
|
+
// Set the loading indicator to prevent interactions while we establish whether the agent is useable
|
|
407
|
+
this._isLoading.set(true);
|
|
130
408
|
try {
|
|
131
409
|
const agentHealth = await this.aiService.getAgentHealth(this.agentName, this.currentContextPath);
|
|
132
|
-
this.
|
|
410
|
+
this._agentHealth.set(agentHealth);
|
|
411
|
+
this.agentHealthDetailedMessages.set(agentHealth.messages?.join('\n'));
|
|
412
|
+
this._agentHealthError.set(this.handleAgentError(agentHealth));
|
|
413
|
+
if (this.agentDefinition && this.agentDefinition.snapshot) {
|
|
414
|
+
this.createAgent();
|
|
415
|
+
}
|
|
133
416
|
}
|
|
134
417
|
catch (ex) {
|
|
135
|
-
|
|
136
|
-
this.
|
|
418
|
+
console.warn('Error getting agent manager health:', ex);
|
|
419
|
+
this.userAnalyticsService.sendAgentUnavailable('agent-health-check-failed');
|
|
420
|
+
this._agentHealthError.set(this.translateService.instant(gettext('This tenant does not have the {{agentManager}} microservice. Please contact your administrator.'), { agentManager: this.translateService.instant(this.aiAgentManagerApplicationName) }));
|
|
137
421
|
}
|
|
138
|
-
this.
|
|
422
|
+
this._isLoading.set(false);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Save the current chat history and suggestions to an opaque, versioned, JSON-serializable snapshot of the chat history for persistence.
|
|
426
|
+
*
|
|
427
|
+
* To save space, only recent tool results and reasoning are included.
|
|
428
|
+
* Additional options for compressing the history can be provided using the config parameters.
|
|
429
|
+
*
|
|
430
|
+
* @param config Optional configuration to limit what gets saved (e.g., filter out specific tools, limit total message count)
|
|
431
|
+
*/
|
|
432
|
+
saveChatHistory(config) {
|
|
433
|
+
return this.chatHistoryService.save(this.messages(), this.suggestions(), config);
|
|
139
434
|
}
|
|
435
|
+
/** Sends a message to the AI */
|
|
140
436
|
async sendMessage(message) {
|
|
141
|
-
this.
|
|
142
|
-
|
|
437
|
+
if (this.isLoadingAiResponse()) {
|
|
438
|
+
// Probably not worth a user-facing error message, but since we assume everywhere that we don't have parallel chats going on at once,
|
|
439
|
+
// don't allow this (e.g. if called from a chat button or other part of the application's UI)
|
|
440
|
+
console.log('Ignoring chat message because previous AI response is still loading');
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
// Collapse the thinking section of the previous assistant message before adding a new one
|
|
444
|
+
const components = this.assistantMessageComponents();
|
|
445
|
+
if (components.length > 0) {
|
|
446
|
+
components[components.length - 1].setThinkingExpanded(false);
|
|
447
|
+
}
|
|
448
|
+
this.messages.set([...this.messages(), message]);
|
|
449
|
+
this._isLoading.set(true);
|
|
450
|
+
this.agentStreamError.set(undefined); // Clear any previous stream errors
|
|
143
451
|
this.abortController = new AbortController();
|
|
452
|
+
this._clientToolOutputs = {};
|
|
144
453
|
const currentAssistantMessage = {
|
|
145
454
|
role: 'assistant',
|
|
146
455
|
content: ''
|
|
147
456
|
};
|
|
148
|
-
|
|
149
|
-
const
|
|
457
|
+
// Prepare messages for AI service, optionally including grounding message
|
|
458
|
+
const pruneMessagesForAgent = this.pruneMessagesForAgent() || defaultPruneMessagesForAgent;
|
|
459
|
+
const messagesForAI = pruneMessagesForAgent(this.messages());
|
|
460
|
+
const groundingProvider = this.groundingContextProvider();
|
|
461
|
+
if (groundingProvider) {
|
|
462
|
+
// Insert grounding message before the last user message
|
|
463
|
+
const groundingMsg = {
|
|
464
|
+
role: 'user',
|
|
465
|
+
content: groundingProvider()
|
|
466
|
+
};
|
|
467
|
+
messagesForAI.splice(messagesForAI.length - 1, 0, groundingMsg);
|
|
468
|
+
}
|
|
469
|
+
this.messages.set([...this.messages(), currentAssistantMessage]);
|
|
470
|
+
const stream = await this.aiService.stream$(this.agentDefinition || this.agentName, messagesForAI, this.variables(), this.abortController, this.currentContextPath);
|
|
150
471
|
if (this.assistantSubscription) {
|
|
151
472
|
this.assistantSubscription.unsubscribe();
|
|
152
473
|
}
|
|
153
|
-
this.assistantSubscription = stream.subscribe(
|
|
474
|
+
this.assistantSubscription = stream.subscribe({
|
|
475
|
+
next: (response) => {
|
|
476
|
+
if (response?.changedPart?.type === 'response-metadata') {
|
|
477
|
+
if (response.changedPart.model) {
|
|
478
|
+
this.model = response.changedPart.model;
|
|
479
|
+
}
|
|
480
|
+
if (response.changedPart.systemPrompt) {
|
|
481
|
+
this.systemPrompt = response.changedPart.systemPrompt;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
this.processAgentMessage(currentAssistantMessage, response.message, response.changedPart);
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
complete: () => {
|
|
489
|
+
if (this._isLoading()) {
|
|
490
|
+
// Should never happen, but worth knowing if it does (indicates bug in processAgentMessage)
|
|
491
|
+
console.error('AI response stream completed but message loading indicator was not reset');
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
error: async (error) => {
|
|
495
|
+
// Currently errors from the AIService are often a big JSON with no human-friendly message anywhere
|
|
496
|
+
// (e.g. {error:{error:{statusCode:400, name:"AI-APICallError", ...}},
|
|
497
|
+
// so we can only show the detail in the console and have to give a non-informative error to the user;
|
|
498
|
+
// but if the backend could add a user-friendly "message" we could render that
|
|
499
|
+
console.error('Error in AI stream:', error);
|
|
500
|
+
const errorMessage = typeof error === 'string'
|
|
501
|
+
? error
|
|
502
|
+
: error?.message || gettext('An error occurred while communicating with the AI agent.');
|
|
503
|
+
this.agentStreamError.set(errorMessage);
|
|
504
|
+
// Remove the empty assistant message that was added
|
|
505
|
+
const messages = this.messages();
|
|
506
|
+
if (messages[messages.length - 1] === currentAssistantMessage) {
|
|
507
|
+
this.messages.set(messages.slice(0, -1));
|
|
508
|
+
}
|
|
509
|
+
this._isLoading.set(false);
|
|
510
|
+
this.userAnalyticsService.sendAssistantMessageError(errorMessage, this.messages(), await this.getAnalyticsMetadataContext());
|
|
511
|
+
}
|
|
512
|
+
});
|
|
154
513
|
}
|
|
155
514
|
ngOnDestroy() {
|
|
156
515
|
this.cancel();
|
|
157
516
|
}
|
|
158
|
-
reprompt(userMessage) {
|
|
159
|
-
const index = this.messages
|
|
517
|
+
async reprompt(userMessage) {
|
|
518
|
+
const index = this.messages().indexOf(userMessage);
|
|
160
519
|
if (index > -1) {
|
|
161
|
-
|
|
162
|
-
|
|
520
|
+
const formattedTimestamp = userMessage.timestamp
|
|
521
|
+
? this.datePipe.transform(userMessage.timestamp, 'adaptiveDate')
|
|
522
|
+
: gettext('(unknown time)');
|
|
523
|
+
try {
|
|
524
|
+
await this.modalService.confirm(gettext('Edit this prompt and delete the following messages'), this.translateService.instant(gettext('Are you sure you want to rewrite the conversation by editing your message from {{timestamp}}, and deleting the following {{messageCount}} assistant and user messages?'), {
|
|
525
|
+
timestamp: formattedTimestamp,
|
|
526
|
+
messageCount: this.messages().length - index - 1
|
|
527
|
+
}), Status.DANGER, { cancel: gettext('Cancel'), ok: gettext('Delete and replace') });
|
|
528
|
+
this.messages.set(this.messages().slice(0, index));
|
|
529
|
+
this.prompt = userMessage.content;
|
|
530
|
+
}
|
|
531
|
+
catch (e) {
|
|
532
|
+
if (!e)
|
|
533
|
+
return; // User dismissed the dialog, do nothing
|
|
534
|
+
throw e; // Unexpected error, rethrow to propagate
|
|
535
|
+
}
|
|
163
536
|
}
|
|
164
537
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
538
|
+
/** Rates an AI message as positive/negative, sending the feedback to GainSight */
|
|
539
|
+
async rate(assistantMessage, positive) {
|
|
540
|
+
this.userAnalyticsService.sendFeedbackOnAssistantMessage(assistantMessage, this.messages(), positive, await this.getAnalyticsMetadataContext());
|
|
541
|
+
this.onFeedback.emit({
|
|
542
|
+
userMessage: this.messages()[this.messages().lastIndexOf(assistantMessage) - 1],
|
|
543
|
+
assistantMessage,
|
|
544
|
+
feedbackPositive: positive
|
|
172
545
|
});
|
|
546
|
+
this.alertService.success(gettext('Thank you for the feedback!'));
|
|
173
547
|
}
|
|
174
548
|
reload(assistantMessage) {
|
|
175
|
-
const index = this.messages
|
|
176
|
-
const userMessage = this.messages
|
|
549
|
+
const index = this.messages().indexOf(assistantMessage);
|
|
550
|
+
const userMessage = this.messages()[index - 1];
|
|
177
551
|
if (index > -1) {
|
|
178
|
-
this.messages
|
|
552
|
+
this.messages.set(this.messages().slice(0, index - 1));
|
|
179
553
|
this.sendMessage({
|
|
180
554
|
role: 'user',
|
|
181
555
|
content: userMessage.content,
|
|
@@ -183,109 +557,198 @@ class AgentChatComponent {
|
|
|
183
557
|
});
|
|
184
558
|
}
|
|
185
559
|
}
|
|
186
|
-
|
|
560
|
+
cancel() {
|
|
187
561
|
if (this.abortController) {
|
|
188
562
|
this.abortController.abort();
|
|
189
563
|
}
|
|
190
|
-
const lastMessage = this.messages$.value[this.messages$.value.length - 1];
|
|
191
|
-
if (lastMessage && lastMessage.role === 'assistant') {
|
|
192
|
-
if (lastMessage.content === '') {
|
|
193
|
-
// if message is aborted before content is even populated, remove it entirely
|
|
194
|
-
this.messages$.next(this.messages$.value.slice(0, -1));
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
// if message has content, mark the last step as aborted to indicate incomplete processing
|
|
198
|
-
const lastMessageSteps = (await firstValueFrom(lastMessage.componentSteps$))
|
|
199
|
-
.map(s => s.origin)
|
|
200
|
-
.map((step, index, arr) => index === arr.length - 1 ? { ...step, aborted: true } : step) || [];
|
|
201
|
-
if (lastMessageSteps.length) {
|
|
202
|
-
lastMessage.componentSteps$ = of(lastMessageSteps).pipe(this.defaultAgentStepRendererPipe);
|
|
203
|
-
this.messages$.next([...this.messages$.value]);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
564
|
if (this.assistantSubscription) {
|
|
208
565
|
this.assistantSubscription.unsubscribe();
|
|
209
566
|
}
|
|
210
|
-
this.
|
|
567
|
+
this._isLoading.set(false);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Overrides the output of a specific tool result (in the current assistant message), including optionally indicating that an error occurred.
|
|
571
|
+
*
|
|
572
|
+
* This can be used to provide output for tool calls that are implemented on the client,
|
|
573
|
+
* and is typically called from `onToolResult`.
|
|
574
|
+
*
|
|
575
|
+
*/
|
|
576
|
+
addToolOutput(toolCallId, output, error) {
|
|
577
|
+
// No validation of the id is performed here; validation is done when applying the override in processAgentMessage
|
|
578
|
+
this.debugLog(`Adding client-side tool ${error ? 'error' : 'output'} for ${toolCallId}: `, output);
|
|
579
|
+
this._clientToolOutputs[toolCallId] = { output: output, error: error ?? false };
|
|
211
580
|
}
|
|
212
581
|
async createAgent() {
|
|
213
|
-
this.
|
|
582
|
+
this._isLoading.set(true);
|
|
214
583
|
try {
|
|
584
|
+
if (!this.agentDefinition) {
|
|
585
|
+
throw new Error('No agent definition provided');
|
|
586
|
+
}
|
|
215
587
|
await this.aiService.createOrUpdateAgent(this.agentDefinition.definition);
|
|
216
|
-
this.
|
|
588
|
+
this._agentHealthError.set(undefined);
|
|
217
589
|
}
|
|
218
590
|
catch (ex) {
|
|
219
|
-
|
|
220
|
-
|
|
591
|
+
// TODO: Distinguish permissions errors to provide a more specific error message.
|
|
592
|
+
console.error('Error configuring AI agent:', ex);
|
|
593
|
+
this.alertService.danger(gettext('Failed to configure the agent.'));
|
|
594
|
+
this._agentHealthError.set(this.translateService.instant(gettext('Failed to configure the agent.')));
|
|
221
595
|
}
|
|
222
|
-
this.
|
|
596
|
+
this._isLoading.set(false);
|
|
223
597
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
598
|
+
// Private methods go here:
|
|
599
|
+
/** Restore chat history from a previously-saved snapshot. Does nothing if the component already has some messages. */
|
|
600
|
+
_restoreChatHistory(history) {
|
|
601
|
+
if (!history)
|
|
228
602
|
return;
|
|
229
|
-
|
|
230
|
-
this.
|
|
231
|
-
if (!agentHealth.isProviderConfigured) {
|
|
232
|
-
this.errorMsg = gettext('AI provider is not configured. Please contact your administrator.');
|
|
603
|
+
// Ignore updates to history once we have messages
|
|
604
|
+
if (this.messages().length > 0) {
|
|
233
605
|
return;
|
|
234
606
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
607
|
+
const restored = this.chatHistoryService.restore(history);
|
|
608
|
+
this.messages.set(restored.messages);
|
|
609
|
+
this._restoredSuggestions = restored.suggestions;
|
|
610
|
+
}
|
|
611
|
+
async getAnalyticsMetadataContext() {
|
|
612
|
+
return await this.userAnalyticsService.getAnalyticsMetadataContext(this.agentName, this.agentDefinition, this.userAnalyticsContext(), this.model, this.systemPrompt);
|
|
613
|
+
}
|
|
614
|
+
handleAgentError(agentHealth) {
|
|
615
|
+
this.canCreate = false;
|
|
616
|
+
const agentManager = this.translateService.instant(this.aiAgentManagerApplicationName);
|
|
617
|
+
if (agentHealth.status != 'ready')
|
|
618
|
+
this.userAnalyticsService.sendAgentUnavailable(agentHealth.status || 'unknown-error');
|
|
619
|
+
// All of these errors are displayed under a headline banner stating "AI agent is not available"
|
|
620
|
+
// From end-user perspective it makes sense to recommend opening AI Agent Manager to configure their provider/agent
|
|
621
|
+
// (or, implicitly, to ask their admin to do that if they don't have access themselves).
|
|
622
|
+
// If they're the application author they could create a `.agent.c8y.ts` file instead, but we don't want to
|
|
623
|
+
// expose such low-level details in the UI.
|
|
624
|
+
switch (agentHealth.status) {
|
|
625
|
+
case 'ready':
|
|
626
|
+
return undefined;
|
|
627
|
+
case 'missing-provider':
|
|
628
|
+
return this.translateService.instant(gettext('Configure a provider in the {{agentManager}} to get started.'), { agentManager });
|
|
629
|
+
case 'missing-permissions':
|
|
630
|
+
return this.translateService.instant(gettext('Add AI Agent permissions to the current user or role to get started.'));
|
|
631
|
+
case 'missing-microservice':
|
|
632
|
+
return this.translateService.instant(gettext('This tenant does not have the {{agentManager}} microservice. Please contact your administrator.'), { agentManager });
|
|
238
633
|
}
|
|
239
|
-
|
|
634
|
+
// Otherwise it's a missing agent definition
|
|
635
|
+
if (agentHealth.canCreate && this.agentDefinition) {
|
|
240
636
|
this.canCreate = true;
|
|
241
|
-
if (this.
|
|
637
|
+
if (this.autoCreateAgents() && this.agentDefinition) {
|
|
242
638
|
this.createAgent();
|
|
243
|
-
return;
|
|
639
|
+
return this.translateService.instant(gettext('Creating the "{{agentName}}" AI agent now...'), { agentName: this.agentName });
|
|
244
640
|
}
|
|
245
|
-
|
|
246
|
-
return;
|
|
641
|
+
// In this case we show a "Create agent" button
|
|
642
|
+
return this.translateService.instant(gettext('Create the "{{agentName}}" AI agent to get started.'), { agentName: this.agentName });
|
|
247
643
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
644
|
+
return this.translateService.instant(gettext('Configure the "{{agentName}}" agent using the {{agentManager}} to get started.'), { agentName: this.agentName, agentManager });
|
|
645
|
+
}
|
|
646
|
+
makeChangedPartNewInstance(updatedAssistantMsg, changedPart) {
|
|
647
|
+
if (!changedPart || !updatedAssistantMsg.steps)
|
|
648
|
+
return changedPart;
|
|
649
|
+
const newInstance = { ...changedPart };
|
|
650
|
+
// For tool calls we often want to modify the output in-place based on client-side information,
|
|
651
|
+
// so we need to ensure changedPart is a new instance to avoid change detection issues
|
|
652
|
+
if (typeof changedPart.type === 'string' && changedPart.type.startsWith('tool-')) {
|
|
653
|
+
for (const step of updatedAssistantMsg.steps) {
|
|
654
|
+
if (step.toolCalls) {
|
|
655
|
+
const toolCallIndex = step.toolCalls.findIndex(tool => tool === changedPart);
|
|
656
|
+
if (toolCallIndex !== -1) {
|
|
657
|
+
step.toolCalls[toolCallIndex] = newInstance;
|
|
658
|
+
return newInstance;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (step.toolResults) {
|
|
662
|
+
const toolResultIndex = step.toolResults.findIndex(tool => tool === changedPart);
|
|
663
|
+
if (toolResultIndex !== -1) {
|
|
664
|
+
step.toolResults[toolResultIndex] = newInstance;
|
|
665
|
+
return newInstance;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
252
668
|
}
|
|
253
|
-
return;
|
|
254
669
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
});
|
|
272
|
-
this.isLoading = false;
|
|
670
|
+
// Should never happen; if we does we need to know
|
|
671
|
+
console.error('Internal error - changed part not found in updated assistant message steps', changedPart, updatedAssistantMsg);
|
|
672
|
+
return changedPart;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Called as responses are incrementally streamed from the agent.
|
|
676
|
+
* @param currentAssistantMsg The message already added to the messages array rendered by the UI,
|
|
677
|
+
* and dynamically updated by this function as more output comes from the agent.
|
|
678
|
+
* @param updatedAssistantMsg contains the latest data received from the agent
|
|
679
|
+
* @param changedPart if provided, indicates which part of the message was changed in this update
|
|
680
|
+
*/
|
|
681
|
+
async processAgentMessage(currentAssistantMsg, updatedAssistantMsg, changedPart) {
|
|
682
|
+
// Apply preprocessing if provided
|
|
683
|
+
const preprocess = this.preprocessAgentMessage();
|
|
684
|
+
if (preprocess) {
|
|
685
|
+
updatedAssistantMsg = preprocess(updatedAssistantMsg, changedPart);
|
|
273
686
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
687
|
+
// Must ensure changedPart is a new instance, otherwise change detection can fail
|
|
688
|
+
// For now this only applies to tools
|
|
689
|
+
changedPart = this.makeChangedPartNewInstance(updatedAssistantMsg, changedPart);
|
|
690
|
+
currentAssistantMsg.content = updatedAssistantMsg.content;
|
|
691
|
+
// Invoke tool callback - which may call addToolOutput
|
|
692
|
+
if (changedPart?.type === 'tool-result') {
|
|
693
|
+
this.debugLog(`Received tool result: ${changedPart.toolName}`, changedPart);
|
|
694
|
+
this.onToolResult.emit(changedPart);
|
|
277
695
|
}
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
696
|
+
if (updatedAssistantMsg.steps) {
|
|
697
|
+
currentAssistantMsg.steps = updatedAssistantMsg.steps; // don't need a deep copy for this use case, and must avoid that so pre-processor can add steps
|
|
698
|
+
// Apply any pending tool output overrides to the incoming steps - and check that none of them refers to a non-existent item
|
|
699
|
+
if (currentAssistantMsg.steps && Object.keys(this._clientToolOutputs).length > 0) {
|
|
700
|
+
for (const [toolCallId, override] of Object.entries(this._clientToolOutputs)) {
|
|
701
|
+
const toolResult = currentAssistantMsg.steps
|
|
702
|
+
?.flatMap(step => step.toolResults || [])
|
|
703
|
+
.find(result => result.toolCallId === toolCallId);
|
|
704
|
+
if (toolResult) {
|
|
705
|
+
toolResult.output = override.output;
|
|
706
|
+
toolResult.error = override.error;
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
throw new Error(`addToolOutput was called with a toolCallId '${toolCallId}' that does not exist in the current assistant message`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (updatedAssistantMsg.finishReason) {
|
|
715
|
+
this.debugLog(`Received assistant message with ${updatedAssistantMsg.steps?.length || 0} steps: `, updatedAssistantMsg);
|
|
716
|
+
try {
|
|
717
|
+
this.handleMessageFinish(currentAssistantMsg, updatedAssistantMsg);
|
|
718
|
+
}
|
|
719
|
+
finally {
|
|
720
|
+
this._isLoading.set(false);
|
|
721
|
+
}
|
|
282
722
|
}
|
|
283
723
|
// as we are mutating the currentAssistantMessage object
|
|
284
724
|
// which is already in the messages array we need to emit a new array reference
|
|
285
|
-
this.messages
|
|
725
|
+
this.messages.set([...this.messages()]);
|
|
726
|
+
}
|
|
727
|
+
handleMessageFinish(currentAssistantMsg, updatedAssistantMsg) {
|
|
728
|
+
currentAssistantMsg.timestamp = new Date().toISOString();
|
|
729
|
+
if (updatedAssistantMsg.finishReason === 'stop') {
|
|
730
|
+
this.onMessageFinish.emit(currentAssistantMsg);
|
|
731
|
+
void this.getAnalyticsMetadataContext()
|
|
732
|
+
.then(analyticsContext => {
|
|
733
|
+
this.userAnalyticsService.sendAssistantMessageComplete(updatedAssistantMsg, this.messages(), analyticsContext);
|
|
734
|
+
})
|
|
735
|
+
.catch(error => {
|
|
736
|
+
console.error('Error sending analytics:', error);
|
|
737
|
+
});
|
|
738
|
+
if (currentAssistantMsg.content.trim().length === 0) {
|
|
739
|
+
// Should be impossible, but adding this as a debugging aid, as it was seen once by a user
|
|
740
|
+
console.warn('Received finish reason "stop" but assistant message content is empty. This may indicate an issue with the AI service or agent configuration.', currentAssistantMsg);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
else if (updatedAssistantMsg.finishReason === 'error') {
|
|
744
|
+
if (currentAssistantMsg.content.trim().length === 0) {
|
|
745
|
+
// Should be impossible, but adding this as a debugging aid, as it was seen once by a user
|
|
746
|
+
console.warn('Received finish reason "error" but assistant message content is empty. This may indicate an issue with the AI service or agent configuration.', currentAssistantMsg);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
286
749
|
}
|
|
287
750
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AgentChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
288
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: AgentChatComponent, isStandalone: true, selector: "c8y-agent-chat", inputs: { agent: "agent", suggestions: "suggestions", title: "title", headline: "headline", welcomeText: "welcomeText", autoInstallAgents: "autoInstallAgents", variables: "variables", stepRenderComponent: "stepRenderComponent" }, outputs: { onMessageDelta: "onMessageDelta", onMessageText: "onMessageText", onMessageFinish: "onMessageFinish", onToolResult: "onToolResult" }, ngImport: i0, template: "@if (hasError && isLoading) {\n <c8y-loading class=\"m-auto\"></c8y-loading>\n}\n@if (hasError && !isLoading) {\n <c8y-ui-empty-state\n class=\"m-auto\"\n [icon]=\"'disclaimer'\"\n [title]=\"'An error occurred' | translate\"\n [subtitle]=\"errorMsg | translate\"\n [horizontal]=\"true\"\n >\n @if (canCreate) {\n <div class=\"text-center m-t-16 m-b-16\">\n <button\n class=\"btn btn-primary\"\n (click)=\"createAgent()\"\n >\n {{ 'Create agent' | translate }}\n </button>\n </div>\n }\n </c8y-ui-empty-state>\n}\n\n@if (!hasError) {\n <c8y-ai-chat\n (onMessage)=\"sendMessage($event)\"\n [isLoading]=\"isLoading\"\n (onCancel)=\"cancel()\"\n [config]=\"{ title: title, headline: headline, welcomeText: welcomeText }\"\n [prompt]=\"prompt\"\n >\n @for (message of messages$ | async; track $index; let i = $index) {\n <c8y-ai-chat-message\n [message]=\"{ role: message.role, content: '', timestamp: message.timestamp }\"\n >\n @if (!message.componentSteps$) {\n <div [innerHTML]=\"message.content | markdownToHtml | async\"></div>\n }\n @for (step of message.componentSteps$ | async; track $index) {\n @if (typeof step.content === 'string') {\n <div [innerHTML]=\"step.content | markdownToHtml | async\"></div>\n } @else {\n <ng-container\n [ngComponentOutlet]=\"step.content\"\n [ngComponentOutletInputs]=\"{ step: step.origin }\"\n ></ng-container>\n }\n }\n\n @if (message.role === 'user') {\n <c8y-ai-chat-message-action\n icon=\"pencil\"\n [tooltip]=\"'Edit and resend this message' | translate\"\n [disabled]=\"isLoading\"\n (click)=\"reprompt(message)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && (i < (messages$ | async)?.length - 1 || !isLoading)) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-up\"\n [tooltip]=\"'This is useful' | translate\"\n [disabled]=\"isLoading\"\n (click)=\"rate(message, true)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && (i < (messages$ | async)?.length - 1 || !isLoading)) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-down\"\n [tooltip]=\"'This is not useful' | translate\"\n [disabled]=\"isLoading\"\n (click)=\"rate(message, false)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && (i < (messages$ | async)?.length - 1 || !isLoading)) {\n <c8y-ai-chat-message-action\n icon=\"refresh\"\n [tooltip]=\"'Regenerate this response' | translate\"\n [disabled]=\"isLoading\"\n (click)=\"reload(message)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (\n !message.content && message.role === 'assistant' && i === (messages$ | async)?.length - 1\n ) {\n <div class=\"text-center\">\n <c8y-loading></c8y-loading>\n </div>\n }\n </c8y-ai-chat-message>\n }\n\n @for (suggestion of suggestions; track $index) {\n <c8y-ai-chat-suggestion\n [icon]=\"suggestion.icon || 'c8y-bulb'\"\n [useAiButtons]=\"true\"\n [label]=\"suggestion.label\"\n [prompt]=\"suggestion.prompt\"\n (suggestionClicked)=\"sendMessage($event)\"\n [disabled]=\"isLoading\"\n ></c8y-ai-chat-suggestion>\n }\n </c8y-ai-chat>\n}\n", dependencies: [{ kind: "component", type: AiChatComponent, selector: "c8y-ai-chat", inputs: ["isLoading", "disabled", "prompt", "config"], outputs: ["onMessage", "onCancel"] }, { kind: "component", type: AiChatSuggestionComponent, selector: "c8y-ai-chat-suggestion", inputs: ["label", "prompt", "icon", "useAiButtons", "disabled"], outputs: ["suggestionClicked"] }, { kind: "component", type: AiChatMessageComponent, selector: "c8y-ai-chat-message", inputs: ["role", "message"] }, { kind: "component", type: AiChatMessageActionComponent, selector: "c8y-ai-chat-message-action", inputs: ["disabled", "tooltip", "icon"], outputs: ["click"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "pipe", type: MarkdownToHtmlPipe, name: "markdownToHtml" }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
|
|
751
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: AgentChatComponent, isStandalone: true, selector: "c8y-agent-chat", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null }, chatConfig: { classPropertyName: "chatConfig", publicName: "chatConfig", isSignal: true, isRequired: false, transformFunction: null }, welcomeTemplate: { classPropertyName: "welcomeTemplate", publicName: "welcomeTemplate", isSignal: true, isRequired: false, transformFunction: null }, autoCreateAgents: { classPropertyName: "autoCreateAgents", publicName: "autoCreateAgents", isSignal: true, isRequired: false, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: false, transformFunction: null }, assistantMessageComponent: { classPropertyName: "assistantMessageComponent", publicName: "assistantMessageComponent", isSignal: true, isRequired: false, transformFunction: null }, assistantMessageDisplayConfig: { classPropertyName: "assistantMessageDisplayConfig", publicName: "assistantMessageDisplayConfig", isSignal: true, isRequired: false, transformFunction: null }, preprocessAgentMessage: { classPropertyName: "preprocessAgentMessage", publicName: "preprocessAgentMessage", isSignal: true, isRequired: false, transformFunction: null }, pruneMessagesForAgent: { classPropertyName: "pruneMessagesForAgent", publicName: "pruneMessagesForAgent", isSignal: true, isRequired: false, transformFunction: null }, initialChatHistory: { classPropertyName: "initialChatHistory", publicName: "initialChatHistory", isSignal: true, isRequired: false, transformFunction: null }, groundingContextProvider: { classPropertyName: "groundingContextProvider", publicName: "groundingContextProvider", isSignal: true, isRequired: false, transformFunction: null }, userAnalyticsContext: { classPropertyName: "userAnalyticsContext", publicName: "userAnalyticsContext", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onMessageFinish: "onMessageFinish", onToolResult: "onToolResult", onFeedback: "onFeedback" }, viewQueries: [{ propertyName: "assistantMessageComponents", predicate: AiChatAssistantMessageComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@let agentHealthErrorMsg = _agentHealthError();\n@if (agentHealthErrorMsg) {\n @if (isLoadingAiResponse()) {\n <c8y-loading class=\"m-auto\"></c8y-loading>\n } @else {\n <c8y-ui-empty-state\n class=\"m-auto\"\n [icon]=\"'settings'\"\n [title]=\"'AI agent is not available.' | translate\"\n >\n <span>{{ agentHealthErrorMsg }}</span>\n\n @if (canCreate) {\n <div class=\"text-center m-t-16 m-b-16\">\n <button\n class=\"btn btn-primary\"\n (click)=\"createAgent()\"\n >\n {{ 'Create agent' | translate }}\n </button>\n </div>\n } @else {\n <p\n class=\"text-pre-wrap m-t-8\"\n data-cy=\"agent-health-detailed-messages\"\n >\n <small>{{ agentHealthDetailedMessages() }}</small>\n </p>\n }\n\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a c8y-guide-href=\"/docs/ai\">user documentation</a>.\n </small>\n </p>\n </c8y-ui-empty-state>\n }\n}\n\n@if (!agentHealthErrorMsg) {\n <c8y-ai-chat\n (onMessage)=\"sendMessage($event)\"\n [isLoading]=\"isLoadingAiResponse()\"\n (onCancel)=\"cancel()\"\n [config]=\"chatConfig() ?? {}\"\n [prompt]=\"prompt\"\n [suggestionsTemplate]=\"suggestionsRef\"\n [welcomeTemplate]=\"welcomeTemplate()\"\n >\n @let messages$ = messages();\n @for (message of messages$; track $index; let i = $index) {\n <c8y-ai-chat-message [message]=\"message\">\n @if (message.role !== 'user' && model) {\n <!-- Visually hidden label included when users copy-paste from the chat, e.g. to report issues. It's quite helpful to know the model. -->\n <span\n class=\"hidden-copy-label\"\n aria-hidden=\"true\"\n >\n {{ `(Using model: ${model})` }}</span\n >\n }\n\n @if (message.role === 'user') {\n <div\n class=\"message-content\"\n data-cy=\"user-message-content\"\n [innerHTML]=\"message.content | markdownToHtml | async\"\n ></div>\n } @else {\n <ng-container\n [ngComponentOutlet]=\"assistantMessageComponent()\"\n [ngComponentOutletInputs]=\"{\n assistantMessageContext: {\n message: message,\n config: assistantMessageDisplayConfig(),\n isMessageLoading: isLoadingAiResponse() && i === messages$.length - 1,\n messageDisplayIndex: messages$.length - 1 - i\n }\n }\"\n ></ng-container>\n }\n\n @let isLastMessage = i === messages$.length - 1;\n\n @if (message.role === 'user') {\n <c8y-ai-chat-message-action\n icon=\"pencil\"\n [tooltip]=\"'Edit and resend this message' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"reprompt(message)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-up\"\n [tooltip]=\"'This is useful' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"rate(message, true)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-down\"\n [tooltip]=\"'This is not useful' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"rate(message, false)\"\n ></c8y-ai-chat-message-action>\n }\n\n <!-- Only allow regenerating the last message, otherwise we could be deleting a lot of useful message history -->\n @if (message.role === 'assistant' && i > 0 && isLastMessage && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"refresh\"\n [tooltip]=\"'Regenerate this response' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"reload(message)\"\n ></c8y-ai-chat-message-action>\n }\n </c8y-ai-chat-message>\n }\n\n @let agentErrorMsg = agentStreamError();\n @if (agentErrorMsg) {\n <c8y-ai-chat-message [message]=\"{ role: 'assistant', content: agentErrorMsg }\">\n <div\n class=\"alert alert-danger d-flex a-i-center gap-8\"\n role=\"alert\"\n >\n <i\n class=\"c8y-icon c8y-icon-warning text-danger\"\n aria-hidden=\"true\"\n ></i>\n <!-- Since errors come from the backend the only translation is for the fallback error message supplied by the UI. -->\n <div class=\"flex-grow text-pre-wrap\">{{ agentErrorMsg | translate }}</div>\n </div>\n </c8y-ai-chat-message>\n }\n\n <ng-template #suggestionsRef>\n @if (!isLoadingAiResponse()) {\n <!-- As soon as we have any suggestions (even empty) these take priority over what we restored from history -->\n @let activeSuggestions = suggestions() === undefined ? _restoredSuggestions : suggestions();\n @for (suggestion of activeSuggestions; track $index) {\n <c8y-ai-chat-suggestion\n [icon]=\"suggestion.icon || 'c8y-bulb'\"\n [useAiButtons]=\"true\"\n [prompt]=\"suggestion.prompt\"\n [label]=\"suggestion.label ?? suggestion.prompt\"\n (suggestionClicked)=\"sendMessage($event)\"\n [disabled]=\"isLoadingAiResponse()\"\n ></c8y-ai-chat-suggestion>\n }\n }\n </ng-template>\n </c8y-ai-chat>\n}\n", styles: ["@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fade-in-out{animation:fadeIn .2s ease-in}.fade-in-out.ng-leave-active{animation:fadeOut .2s ease-out}.hidden-copy-label{display:block;font-size:0;line-height:0;-webkit-user-select:text;user-select:text}\n"], dependencies: [{ kind: "component", type: AiChatComponent, selector: "c8y-ai-chat", inputs: ["isLoading", "disabled", "prompt", "suggestionsTemplate", "welcomeTemplate", "config"], outputs: ["onMessage", "onCancel"] }, { kind: "component", type: AiChatSuggestionComponent, selector: "c8y-ai-chat-suggestion", inputs: ["label", "prompt", "icon", "useAiButtons", "disabled"], outputs: ["suggestionClicked"] }, { kind: "component", type: AiChatMessageComponent, selector: "c8y-ai-chat-message", inputs: ["role", "message"] }, { kind: "component", type: AiChatMessageActionComponent, selector: "c8y-ai-chat-message-action", inputs: ["custom", "disabled", "tooltip", "icon"], outputs: ["actionClicked"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "ngmodule", type: CollapseModule }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: GuideDocsComponent, selector: "[c8y-guide-docs]" }, { kind: "directive", type: GuideHrefDirective, selector: "[c8y-guide-href]", inputs: ["c8y-guide-href"] }, { kind: "pipe", type: MarkdownToHtmlPipe, name: "markdownToHtml" }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
|
|
289
752
|
}
|
|
290
753
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AgentChatComponent, decorators: [{
|
|
291
754
|
type: Component,
|
|
@@ -299,52 +762,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
299
762
|
AsyncPipe,
|
|
300
763
|
NgComponentOutlet,
|
|
301
764
|
C8yTranslatePipe,
|
|
302
|
-
EmptyStateComponent
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}], suggestions: [{
|
|
308
|
-
|
|
309
|
-
}], title: [{
|
|
310
|
-
type: Input
|
|
311
|
-
}], headline: [{
|
|
312
|
-
type: Input
|
|
313
|
-
}], welcomeText: [{
|
|
314
|
-
type: Input
|
|
315
|
-
}], autoInstallAgents: [{
|
|
316
|
-
type: Input
|
|
317
|
-
}], variables: [{
|
|
318
|
-
type: Input
|
|
319
|
-
}], stepRenderComponent: [{
|
|
320
|
-
type: Input
|
|
321
|
-
}], onMessageDelta: [{
|
|
322
|
-
type: Output
|
|
323
|
-
}], onMessageText: [{
|
|
324
|
-
type: Output
|
|
325
|
-
}], onMessageFinish: [{
|
|
326
|
-
type: Output
|
|
327
|
-
}], onToolResult: [{
|
|
328
|
-
type: Output
|
|
329
|
-
}] } });
|
|
765
|
+
EmptyStateComponent,
|
|
766
|
+
CollapseModule,
|
|
767
|
+
C8yTranslateDirective,
|
|
768
|
+
GuideDocsComponent,
|
|
769
|
+
GuideHrefDirective
|
|
770
|
+
], template: "@let agentHealthErrorMsg = _agentHealthError();\n@if (agentHealthErrorMsg) {\n @if (isLoadingAiResponse()) {\n <c8y-loading class=\"m-auto\"></c8y-loading>\n } @else {\n <c8y-ui-empty-state\n class=\"m-auto\"\n [icon]=\"'settings'\"\n [title]=\"'AI agent is not available.' | translate\"\n >\n <span>{{ agentHealthErrorMsg }}</span>\n\n @if (canCreate) {\n <div class=\"text-center m-t-16 m-b-16\">\n <button\n class=\"btn btn-primary\"\n (click)=\"createAgent()\"\n >\n {{ 'Create agent' | translate }}\n </button>\n </div>\n } @else {\n <p\n class=\"text-pre-wrap m-t-8\"\n data-cy=\"agent-health-detailed-messages\"\n >\n <small>{{ agentHealthDetailedMessages() }}</small>\n </p>\n }\n\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a c8y-guide-href=\"/docs/ai\">user documentation</a>.\n </small>\n </p>\n </c8y-ui-empty-state>\n }\n}\n\n@if (!agentHealthErrorMsg) {\n <c8y-ai-chat\n (onMessage)=\"sendMessage($event)\"\n [isLoading]=\"isLoadingAiResponse()\"\n (onCancel)=\"cancel()\"\n [config]=\"chatConfig() ?? {}\"\n [prompt]=\"prompt\"\n [suggestionsTemplate]=\"suggestionsRef\"\n [welcomeTemplate]=\"welcomeTemplate()\"\n >\n @let messages$ = messages();\n @for (message of messages$; track $index; let i = $index) {\n <c8y-ai-chat-message [message]=\"message\">\n @if (message.role !== 'user' && model) {\n <!-- Visually hidden label included when users copy-paste from the chat, e.g. to report issues. It's quite helpful to know the model. -->\n <span\n class=\"hidden-copy-label\"\n aria-hidden=\"true\"\n >\n {{ `(Using model: ${model})` }}</span\n >\n }\n\n @if (message.role === 'user') {\n <div\n class=\"message-content\"\n data-cy=\"user-message-content\"\n [innerHTML]=\"message.content | markdownToHtml | async\"\n ></div>\n } @else {\n <ng-container\n [ngComponentOutlet]=\"assistantMessageComponent()\"\n [ngComponentOutletInputs]=\"{\n assistantMessageContext: {\n message: message,\n config: assistantMessageDisplayConfig(),\n isMessageLoading: isLoadingAiResponse() && i === messages$.length - 1,\n messageDisplayIndex: messages$.length - 1 - i\n }\n }\"\n ></ng-container>\n }\n\n @let isLastMessage = i === messages$.length - 1;\n\n @if (message.role === 'user') {\n <c8y-ai-chat-message-action\n icon=\"pencil\"\n [tooltip]=\"'Edit and resend this message' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"reprompt(message)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-up\"\n [tooltip]=\"'This is useful' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"rate(message, true)\"\n ></c8y-ai-chat-message-action>\n }\n\n @if (message.role === 'assistant' && i > 0 && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"thumbs-down\"\n [tooltip]=\"'This is not useful' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"rate(message, false)\"\n ></c8y-ai-chat-message-action>\n }\n\n <!-- Only allow regenerating the last message, otherwise we could be deleting a lot of useful message history -->\n @if (message.role === 'assistant' && i > 0 && isLastMessage && !isLoadingAiResponse()) {\n <c8y-ai-chat-message-action\n icon=\"refresh\"\n [tooltip]=\"'Regenerate this response' | translate\"\n [disabled]=\"isLoadingAiResponse()\"\n (click)=\"reload(message)\"\n ></c8y-ai-chat-message-action>\n }\n </c8y-ai-chat-message>\n }\n\n @let agentErrorMsg = agentStreamError();\n @if (agentErrorMsg) {\n <c8y-ai-chat-message [message]=\"{ role: 'assistant', content: agentErrorMsg }\">\n <div\n class=\"alert alert-danger d-flex a-i-center gap-8\"\n role=\"alert\"\n >\n <i\n class=\"c8y-icon c8y-icon-warning text-danger\"\n aria-hidden=\"true\"\n ></i>\n <!-- Since errors come from the backend the only translation is for the fallback error message supplied by the UI. -->\n <div class=\"flex-grow text-pre-wrap\">{{ agentErrorMsg | translate }}</div>\n </div>\n </c8y-ai-chat-message>\n }\n\n <ng-template #suggestionsRef>\n @if (!isLoadingAiResponse()) {\n <!-- As soon as we have any suggestions (even empty) these take priority over what we restored from history -->\n @let activeSuggestions = suggestions() === undefined ? _restoredSuggestions : suggestions();\n @for (suggestion of activeSuggestions; track $index) {\n <c8y-ai-chat-suggestion\n [icon]=\"suggestion.icon || 'c8y-bulb'\"\n [useAiButtons]=\"true\"\n [prompt]=\"suggestion.prompt\"\n [label]=\"suggestion.label ?? suggestion.prompt\"\n (suggestionClicked)=\"sendMessage($event)\"\n [disabled]=\"isLoadingAiResponse()\"\n ></c8y-ai-chat-suggestion>\n }\n }\n </ng-template>\n </c8y-ai-chat>\n}\n", styles: ["@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fade-in-out{animation:fadeIn .2s ease-in}.fade-in-out.ng-leave-active{animation:fadeOut .2s ease-out}.hidden-copy-label{display:block;font-size:0;line-height:0;-webkit-user-select:text;user-select:text}\n"] }]
|
|
771
|
+
}], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], chatConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "chatConfig", required: false }] }], welcomeTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "welcomeTemplate", required: false }] }], autoCreateAgents: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCreateAgents", required: false }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: false }] }], assistantMessageComponent: [{ type: i0.Input, args: [{ isSignal: true, alias: "assistantMessageComponent", required: false }] }], assistantMessageDisplayConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "assistantMessageDisplayConfig", required: false }] }], preprocessAgentMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "preprocessAgentMessage", required: false }] }], pruneMessagesForAgent: [{ type: i0.Input, args: [{ isSignal: true, alias: "pruneMessagesForAgent", required: false }] }], initialChatHistory: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialChatHistory", required: false }] }], groundingContextProvider: [{ type: i0.Input, args: [{ isSignal: true, alias: "groundingContextProvider", required: false }] }], userAnalyticsContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "userAnalyticsContext", required: false }] }], onMessageFinish: [{ type: i0.Output, args: ["onMessageFinish"] }], onToolResult: [{ type: i0.Output, args: ["onToolResult"] }], onFeedback: [{ type: i0.Output, args: ["onFeedback"] }], assistantMessageComponents: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => AiChatAssistantMessageComponent), { isSignal: true }] }] } });
|
|
330
772
|
|
|
331
773
|
class WidgetAiChatSectionComponent {
|
|
332
774
|
constructor() {
|
|
775
|
+
this._componentConfig = signal({}, ...(ngDevMode ? [{ debugName: "_componentConfig" }] : []));
|
|
333
776
|
this.suggestions = [];
|
|
334
|
-
|
|
335
|
-
this.title = gettext('What can I help you with?');
|
|
777
|
+
/** Provide the specified `contextVariableName` as a variable that can be used in the prompt. */
|
|
336
778
|
this.useContextAsVariable = true;
|
|
337
779
|
this.contextVariableName = 'c8yContext';
|
|
780
|
+
this.variables$ = of({});
|
|
781
|
+
this.injector = inject(Injector);
|
|
338
782
|
this.widgetConfigService = inject(WidgetConfigService);
|
|
339
783
|
this.alertService = inject(AlertService);
|
|
340
784
|
this.contextRouteService = inject(ContextRouteService);
|
|
341
|
-
/**
|
|
342
|
-
* A component that is used to render each step in the chat. By default, it uses the `AgentStepFeedbackComponent`.
|
|
343
|
-
* You can provide your own component by returning it from this callback.
|
|
344
|
-
*
|
|
345
|
-
* @returns A promise that resolves to a component type.
|
|
346
|
-
*/
|
|
347
|
-
this.loadRenderStepComponent = () => Promise.resolve(AgentStepFeedbackComponent);
|
|
348
785
|
/**
|
|
349
786
|
* A callback that is invoked when a tool result is returned. The function can return either:
|
|
350
787
|
* - `undefined` or `void`: No action is taken.
|
|
@@ -390,7 +827,10 @@ class WidgetAiChatSectionComponent {
|
|
|
390
827
|
[this.contextVariableName]: contextData || this.widgetConfigService.currentConfig.device
|
|
391
828
|
};
|
|
392
829
|
}
|
|
393
|
-
|
|
830
|
+
if (this.loadComponentConfig) {
|
|
831
|
+
const config = await this.loadComponentConfig(this.injector);
|
|
832
|
+
this._componentConfig.set(config);
|
|
833
|
+
}
|
|
394
834
|
}
|
|
395
835
|
/**
|
|
396
836
|
* Handles the tool result returned from the AI agent.
|
|
@@ -414,20 +854,18 @@ class WidgetAiChatSectionComponent {
|
|
|
414
854
|
}
|
|
415
855
|
}
|
|
416
856
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: WidgetAiChatSectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
417
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: WidgetAiChatSectionComponent, isStandalone: true, selector: "c8y-widget-ai-chat-section", inputs: {
|
|
857
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: WidgetAiChatSectionComponent, isStandalone: true, selector: "c8y-widget-ai-chat-section", inputs: { loadComponentConfig: "loadComponentConfig", suggestions: "suggestions", agent: "agent", chatConfig: "chatConfig", useContextAsVariable: "useContextAsVariable", contextVariableName: "contextVariableName", variables: "variables", onToolResult: "onToolResult" }, ngImport: i0, template: "<c8y-widget-config-feedback>\n <div\n class=\"m-l-4 btn-ai btn-ai-hint btn-sm\"\n [title]=\"'AI code assistant' | translate\"\n >\n <span></span>\n </div>\n</c8y-widget-config-feedback>\n\n<c8y-agent-chat\n #agentChat\n [chatConfig]=\"chatConfig\"\n [suggestions]=\"agentChat.isWelcoming() ? suggestions : []\"\n [agent]=\"agent\"\n [variables]=\"variables | async\"\n (onToolResult)=\"toolResultHandler($event)\"\n [preprocessAgentMessage]=\"_componentConfig().preprocessAgentMessage\"\n [pruneMessagesForAgent]=\"_componentConfig().pruneMessagesForAgent\"\n [assistantMessageDisplayConfig]=\"_componentConfig().assistantMessageDisplayConfig || {}\"\n></c8y-agent-chat>\n", dependencies: [{ kind: "component", type: AgentChatComponent, selector: "c8y-agent-chat", inputs: ["agent", "suggestions", "chatConfig", "welcomeTemplate", "autoCreateAgents", "variables", "assistantMessageComponent", "assistantMessageDisplayConfig", "preprocessAgentMessage", "pruneMessagesForAgent", "initialChatHistory", "groundingContextProvider", "userAnalyticsContext"], outputs: ["onMessageFinish", "onToolResult", "onFeedback"] }, { kind: "component", type: WidgetConfigFeedbackComponent, selector: "c8y-widget-config-feedback" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
|
|
418
858
|
}
|
|
419
859
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: WidgetAiChatSectionComponent, decorators: [{
|
|
420
860
|
type: Component,
|
|
421
|
-
args: [{ selector: 'c8y-widget-ai-chat-section', imports: [AgentChatComponent, C8yTranslatePipe,
|
|
422
|
-
}], propDecorators: {
|
|
861
|
+
args: [{ selector: 'c8y-widget-ai-chat-section', imports: [AgentChatComponent, C8yTranslatePipe, WidgetConfigFeedbackComponent, AsyncPipe], standalone: true, template: "<c8y-widget-config-feedback>\n <div\n class=\"m-l-4 btn-ai btn-ai-hint btn-sm\"\n [title]=\"'AI code assistant' | translate\"\n >\n <span></span>\n </div>\n</c8y-widget-config-feedback>\n\n<c8y-agent-chat\n #agentChat\n [chatConfig]=\"chatConfig\"\n [suggestions]=\"agentChat.isWelcoming() ? suggestions : []\"\n [agent]=\"agent\"\n [variables]=\"variables | async\"\n (onToolResult)=\"toolResultHandler($event)\"\n [preprocessAgentMessage]=\"_componentConfig().preprocessAgentMessage\"\n [pruneMessagesForAgent]=\"_componentConfig().pruneMessagesForAgent\"\n [assistantMessageDisplayConfig]=\"_componentConfig().assistantMessageDisplayConfig || {}\"\n></c8y-agent-chat>\n" }]
|
|
862
|
+
}], propDecorators: { loadComponentConfig: [{
|
|
423
863
|
type: Input
|
|
424
|
-
}],
|
|
425
|
-
type: Input
|
|
426
|
-
}], headline: [{
|
|
864
|
+
}], suggestions: [{
|
|
427
865
|
type: Input
|
|
428
|
-
}],
|
|
866
|
+
}], agent: [{
|
|
429
867
|
type: Input
|
|
430
|
-
}],
|
|
868
|
+
}], chatConfig: [{
|
|
431
869
|
type: Input
|
|
432
870
|
}], useContextAsVariable: [{
|
|
433
871
|
type: Input
|
|
@@ -435,7 +873,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
435
873
|
type: Input
|
|
436
874
|
}], variables: [{
|
|
437
875
|
type: Input
|
|
438
|
-
}],
|
|
876
|
+
}], onToolResult: [{
|
|
439
877
|
type: Input
|
|
440
878
|
}] } });
|
|
441
879
|
|
|
@@ -443,5 +881,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
443
881
|
* Generated bundle index. Do not edit.
|
|
444
882
|
*/
|
|
445
883
|
|
|
446
|
-
export { AgentChatComponent,
|
|
884
|
+
export { AgentChatComponent, WidgetAiChatSectionComponent };
|
|
447
885
|
//# sourceMappingURL=c8y-ngx-components-ai-agent-chat.mjs.map
|