max-agent-ui 0.0.12 → 0.0.14
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/README.md +111 -65
- package/fesm2022/max-agent-ui.mjs +591 -77
- package/fesm2022/max-agent-ui.mjs.map +1 -1
- package/index.d.ts +382 -0
- package/package.json +11 -10
- package/types/max-agent-ui.d.ts +0 -154
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as i2 from '@angular/common';
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
|
+
import * as i1 from '@angular/common/http';
|
|
4
|
+
import { HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
|
3
5
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { InjectionToken,
|
|
6
|
+
import { Pipe, InjectionToken, Inject, Optional, Injectable, signal, isDevMode, HostListener, Input, ViewChild, Component } from '@angular/core';
|
|
5
7
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
|
-
import * as
|
|
8
|
+
import * as i3 from '@angular/forms';
|
|
7
9
|
import { FormsModule } from '@angular/forms';
|
|
8
|
-
import * as
|
|
10
|
+
import * as i4 from 'primeng/button';
|
|
9
11
|
import { ButtonModule } from 'primeng/button';
|
|
10
|
-
import * as
|
|
11
|
-
import { DrawerModule } from 'primeng/drawer';
|
|
12
|
-
import * as i4 from 'primeng/textarea';
|
|
12
|
+
import * as i5 from 'primeng/textarea';
|
|
13
13
|
import { TextareaModule } from 'primeng/textarea';
|
|
14
|
-
import * as
|
|
14
|
+
import * as i6 from 'primeng/tooltip';
|
|
15
15
|
import { TooltipModule } from 'primeng/tooltip';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { throwError, switchMap, of } from 'rxjs';
|
|
17
|
+
import { marked } from 'marked';
|
|
18
18
|
|
|
19
19
|
const DEFAULT_ASSISTANT_BOOTSTRAP = {
|
|
20
20
|
initialMessage: undefined,
|
|
@@ -22,21 +22,21 @@ const DEFAULT_ASSISTANT_BOOTSTRAP = {
|
|
|
22
22
|
};
|
|
23
23
|
const ASSISTANT_BOOTSTRAP_BY_APP = {
|
|
24
24
|
cortex: {
|
|
25
|
-
initialMessage:
|
|
25
|
+
initialMessage: "Hello there! I'm Max, your Maxtime Virtual Assistant. How can I help you today?",
|
|
26
26
|
initialSuggestions: [
|
|
27
27
|
'What sessions are scheduled for today?',
|
|
28
28
|
'Which lecturers have planned leave this week?',
|
|
29
29
|
'List the students who are on leave today.',
|
|
30
|
-
'List the students who were absent
|
|
30
|
+
'List the students who were absent yesterday.',
|
|
31
31
|
'Generate the attendance report for a session conducted yesterday.'
|
|
32
32
|
]
|
|
33
33
|
},
|
|
34
34
|
nemesys: {
|
|
35
|
-
initialMessage:
|
|
35
|
+
initialMessage: "Hello there! I'm Max, your Maxtime Virtual Assistant. How can I help you today?",
|
|
36
36
|
initialSuggestions: ['Show scheduling plans', 'Check resource availability', 'Find conflicts']
|
|
37
37
|
},
|
|
38
38
|
attendance: {
|
|
39
|
-
initialMessage:
|
|
39
|
+
initialMessage: "Hello there! I'm Max, your Maxtime Virtual Assistant. How can I help you today?",
|
|
40
40
|
initialSuggestions: ['Show attendance summary', 'Find missing attendance', 'View reports']
|
|
41
41
|
}
|
|
42
42
|
};
|
|
@@ -48,25 +48,80 @@ function getAssistantBootstrapConfig(app) {
|
|
|
48
48
|
return ASSISTANT_BOOTSTRAP_BY_APP[appKey] ?? DEFAULT_ASSISTANT_BOOTSTRAP;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
class MaxAssistantMarkdownPipe {
|
|
52
|
+
transform(value) {
|
|
53
|
+
if (!value) {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
// Assistant responses may contain Markdown, but they must not be able to
|
|
57
|
+
// inject raw HTML. Angular sanitises the generated HTML again when it is
|
|
58
|
+
// assigned through [innerHTML].
|
|
59
|
+
const escapedValue = value
|
|
60
|
+
.replaceAll('&', '&')
|
|
61
|
+
.replaceAll('<', '<')
|
|
62
|
+
.replaceAll('>', '>');
|
|
63
|
+
return marked.parse(escapedValue, {
|
|
64
|
+
async: false,
|
|
65
|
+
breaks: true,
|
|
66
|
+
gfm: true
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MaxAssistantMarkdownPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
70
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.14", ngImport: i0, type: MaxAssistantMarkdownPipe, isStandalone: true, name: "maxAssistantMarkdown" });
|
|
71
|
+
}
|
|
72
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MaxAssistantMarkdownPipe, decorators: [{
|
|
73
|
+
type: Pipe,
|
|
74
|
+
args: [{
|
|
75
|
+
name: 'maxAssistantMarkdown',
|
|
76
|
+
standalone: true,
|
|
77
|
+
pure: true
|
|
78
|
+
}]
|
|
79
|
+
}] });
|
|
80
|
+
|
|
51
81
|
const MAX_ASSISTANT_CONFIG = new InjectionToken('MAX_ASSISTANT_CONFIG', {
|
|
52
82
|
factory: () => ({})
|
|
53
83
|
});
|
|
54
84
|
|
|
55
|
-
const DEFAULT_ASSISTANT_CHAT_URL = '
|
|
85
|
+
const DEFAULT_ASSISTANT_CHAT_URL = '/agent-api/v1/chat';
|
|
56
86
|
const DEFAULT_APP = 'cortex';
|
|
87
|
+
const DEFAULT_TIMEZONE = 'UTC';
|
|
57
88
|
class MaxAssistantService {
|
|
58
|
-
config
|
|
59
|
-
http
|
|
60
|
-
|
|
89
|
+
config;
|
|
90
|
+
http;
|
|
91
|
+
constructor(config, http) {
|
|
92
|
+
this.config = config;
|
|
93
|
+
this.http = http;
|
|
94
|
+
}
|
|
95
|
+
sendMessage(message, conversationId, config = {}, confirmation, history = []) {
|
|
61
96
|
const assistantConfig = {
|
|
62
97
|
...this.config,
|
|
63
|
-
...config
|
|
98
|
+
...this.compactConfig(config)
|
|
64
99
|
};
|
|
65
100
|
const request = {
|
|
66
101
|
message,
|
|
67
102
|
conversationId,
|
|
103
|
+
history,
|
|
68
104
|
app: this.getApp(assistantConfig),
|
|
69
|
-
|
|
105
|
+
context: this.getContext(assistantConfig),
|
|
106
|
+
...(confirmation ? { confirmation } : {})
|
|
107
|
+
};
|
|
108
|
+
return this.postMessage(request, assistantConfig);
|
|
109
|
+
}
|
|
110
|
+
confirmAction(actionId, decision, conversationId, config = {}, history = []) {
|
|
111
|
+
return this.sendMessage(decision === 'approve' ? 'Approve action' : 'Reject action', conversationId, config, { actionId, decision }, history);
|
|
112
|
+
}
|
|
113
|
+
sendStructuredAction(message, action, conversationId, config = {}, history = []) {
|
|
114
|
+
const assistantConfig = {
|
|
115
|
+
...this.config,
|
|
116
|
+
...this.compactConfig(config)
|
|
117
|
+
};
|
|
118
|
+
const request = {
|
|
119
|
+
message,
|
|
120
|
+
conversationId,
|
|
121
|
+
history,
|
|
122
|
+
app: this.getApp(assistantConfig),
|
|
123
|
+
context: this.getContext(assistantConfig),
|
|
124
|
+
structuredAction: action
|
|
70
125
|
};
|
|
71
126
|
return this.postMessage(request, assistantConfig);
|
|
72
127
|
}
|
|
@@ -74,7 +129,7 @@ class MaxAssistantService {
|
|
|
74
129
|
if (!this.http) {
|
|
75
130
|
return throwError(() => new Error('HttpClient is not available. Add provideHttpClient() to the host app.'));
|
|
76
131
|
}
|
|
77
|
-
return this.http.post(this.getChatUrl(config), request);
|
|
132
|
+
return this.http.post(this.getChatUrl(config), request, { headers: this.getHeaders(config) });
|
|
78
133
|
}
|
|
79
134
|
getChatUrl(config) {
|
|
80
135
|
return config.assistantApiUrl?.trim() || DEFAULT_ASSISTANT_CHAT_URL;
|
|
@@ -88,15 +143,63 @@ class MaxAssistantService {
|
|
|
88
143
|
}
|
|
89
144
|
return config.user ?? {};
|
|
90
145
|
}
|
|
91
|
-
|
|
92
|
-
|
|
146
|
+
getContext(config) {
|
|
147
|
+
const configured = typeof config.context === 'function'
|
|
148
|
+
? config.context()
|
|
149
|
+
: config.context ?? {};
|
|
150
|
+
return {
|
|
151
|
+
permissions: configured.permissions ?? [],
|
|
152
|
+
timezone: configured.timezone
|
|
153
|
+
?? Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
154
|
+
?? DEFAULT_TIMEZONE,
|
|
155
|
+
...(configured.tenantId ? { tenantId: configured.tenantId } : {}),
|
|
156
|
+
...(configured.organisationId
|
|
157
|
+
? { organisationId: configured.organisationId }
|
|
158
|
+
: {}),
|
|
159
|
+
...(configured.userId ? { userId: configured.userId } : {}),
|
|
160
|
+
...(configured.role ? { role: configured.role } : {}),
|
|
161
|
+
...(configured.route ? { route: configured.route } : {}),
|
|
162
|
+
...(configured.module ? { module: configured.module } : {})
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
getHeaders(config) {
|
|
166
|
+
const user = this.getUser(config);
|
|
167
|
+
let headers = new HttpHeaders({ 'Content-Type': 'application/json' });
|
|
168
|
+
const configuredHeaders = typeof config.requestHeaders === 'function'
|
|
169
|
+
? config.requestHeaders()
|
|
170
|
+
: config.requestHeaders ?? {};
|
|
171
|
+
for (const [name, value] of Object.entries(configuredHeaders)) {
|
|
172
|
+
const trimmedName = name.trim();
|
|
173
|
+
const trimmedValue = value.trim();
|
|
174
|
+
if (trimmedName && trimmedValue) {
|
|
175
|
+
headers = headers.set(trimmedName, trimmedValue);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (user.token) {
|
|
179
|
+
headers = headers.set('x-cortex-auth-token', String(user.token));
|
|
180
|
+
}
|
|
181
|
+
if (user.version) {
|
|
182
|
+
headers = headers.set('x-cortex-version', String(user.version));
|
|
183
|
+
}
|
|
184
|
+
return headers;
|
|
185
|
+
}
|
|
186
|
+
compactConfig(config) {
|
|
187
|
+
return Object.fromEntries(Object.entries(config).filter(([, value]) => value !== undefined));
|
|
188
|
+
}
|
|
189
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MaxAssistantService, deps: [{ token: MAX_ASSISTANT_CONFIG }, { token: i1.HttpClient, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
190
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MaxAssistantService, providedIn: 'root' });
|
|
93
191
|
}
|
|
94
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
192
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MaxAssistantService, decorators: [{
|
|
95
193
|
type: Injectable,
|
|
96
194
|
args: [{
|
|
97
195
|
providedIn: 'root'
|
|
98
196
|
}]
|
|
99
|
-
}]
|
|
197
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
198
|
+
type: Inject,
|
|
199
|
+
args: [MAX_ASSISTANT_CONFIG]
|
|
200
|
+
}] }, { type: i1.HttpClient, decorators: [{
|
|
201
|
+
type: Optional
|
|
202
|
+
}] }] });
|
|
100
203
|
|
|
101
204
|
const ASSISTANT_UI_COPY = {
|
|
102
205
|
title: 'Max',
|
|
@@ -104,32 +207,51 @@ const ASSISTANT_UI_COPY = {
|
|
|
104
207
|
launcherLabel: 'Open Maxtime virtual assistant',
|
|
105
208
|
openTooltip: 'Open Maxtime virtual assistant',
|
|
106
209
|
closeLabel: 'Close Maxtime virtual assistant',
|
|
210
|
+
clearLabel: 'Clear chat session',
|
|
211
|
+
clearTooltip: 'Clear chat session',
|
|
107
212
|
placeholder: 'Ask Maxtime virtual assistant',
|
|
108
213
|
typingLabel: 'Maxtime virtual assistant is typing',
|
|
109
214
|
errorMessage: 'Maxtime virtual assistant could not be reached. Please try again.'
|
|
110
215
|
};
|
|
111
216
|
class MaxAssistantComponent {
|
|
217
|
+
assistantService;
|
|
218
|
+
destroyRef;
|
|
219
|
+
launcher;
|
|
112
220
|
messagesViewport;
|
|
113
221
|
app;
|
|
114
222
|
assistantApiUrl;
|
|
115
|
-
user
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
223
|
+
user;
|
|
224
|
+
context;
|
|
225
|
+
requestHeaders;
|
|
226
|
+
panelVisible = signal(false, ...(ngDevMode ? [{ debugName: "panelVisible" }] : []));
|
|
227
|
+
isSending = signal(false, ...(ngDevMode ? [{ debugName: "isSending" }] : []));
|
|
228
|
+
input = signal('', ...(ngDevMode ? [{ debugName: "input" }] : []));
|
|
229
|
+
conversationId = signal(undefined, ...(ngDevMode ? [{ debugName: "conversationId" }] : []));
|
|
230
|
+
messages = signal([], ...(ngDevMode ? [{ debugName: "messages" }] : []));
|
|
231
|
+
expandedDetailMessageIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedDetailMessageIds" }] : []));
|
|
232
|
+
expandedTraceStepKeys = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedTraceStepKeys" }] : []));
|
|
233
|
+
developerDetailsEnabled = isDevMode();
|
|
234
|
+
constructor(assistantService, destroyRef) {
|
|
235
|
+
this.assistantService = assistantService;
|
|
236
|
+
this.destroyRef = destroyRef;
|
|
237
|
+
}
|
|
124
238
|
openPanel() {
|
|
125
239
|
this.panelVisible.set(true);
|
|
126
|
-
if (!this.messages().length
|
|
127
|
-
this.messages.set(
|
|
128
|
-
this.createMessage('assistant', this.bootstrapConfig.initialMessage, this.bootstrapConfig.initialSuggestions)
|
|
129
|
-
]);
|
|
240
|
+
if (!this.messages().length) {
|
|
241
|
+
this.messages.set(this.createDefaultMessages());
|
|
130
242
|
}
|
|
131
243
|
this.scrollToLatest();
|
|
132
244
|
}
|
|
245
|
+
togglePanel() {
|
|
246
|
+
if (this.panelVisible()) {
|
|
247
|
+
this.closePanel();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
this.openPanel();
|
|
251
|
+
}
|
|
252
|
+
get launcherLabel() {
|
|
253
|
+
return this.panelVisible() ? this.uiCopy.closeLabel : this.uiCopy.launcherLabel;
|
|
254
|
+
}
|
|
133
255
|
get uiCopy() {
|
|
134
256
|
return ASSISTANT_UI_COPY;
|
|
135
257
|
}
|
|
@@ -138,17 +260,38 @@ class MaxAssistantComponent {
|
|
|
138
260
|
}
|
|
139
261
|
closePanel() {
|
|
140
262
|
this.panelVisible.set(false);
|
|
263
|
+
queueMicrotask(() => this.launcher?.nativeElement.focus({ preventScroll: true }));
|
|
264
|
+
}
|
|
265
|
+
onDocumentEscape() {
|
|
266
|
+
if (this.expandedDetailMessageIds().size) {
|
|
267
|
+
this.expandedDetailMessageIds.set(new Set());
|
|
268
|
+
this.expandedTraceStepKeys.set(new Set());
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (this.panelVisible()) {
|
|
272
|
+
this.closePanel();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
clearChatSession() {
|
|
276
|
+
this.input.set('');
|
|
277
|
+
this.isSending.set(false);
|
|
278
|
+
this.conversationId.set(undefined);
|
|
279
|
+
this.expandedDetailMessageIds.set(new Set());
|
|
280
|
+
this.expandedTraceStepKeys.set(new Set());
|
|
281
|
+
this.messages.set(this.createDefaultMessages());
|
|
282
|
+
this.scrollToLatest();
|
|
141
283
|
}
|
|
142
284
|
sendMessage(message = this.input()) {
|
|
143
285
|
const trimmedMessage = message.trim();
|
|
144
286
|
if (!trimmedMessage || this.isSending()) {
|
|
145
287
|
return;
|
|
146
288
|
}
|
|
289
|
+
const history = this.getConversationHistory();
|
|
147
290
|
this.input.set('');
|
|
148
291
|
this.appendMessage(this.createMessage('user', trimmedMessage));
|
|
149
292
|
this.isSending.set(true);
|
|
150
293
|
this.assistantService
|
|
151
|
-
.sendMessage(trimmedMessage, this.conversationId(), this.getAssistantConfig())
|
|
294
|
+
.sendMessage(trimmedMessage, this.conversationId(), this.getAssistantConfig(), undefined, history)
|
|
152
295
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
153
296
|
.subscribe({
|
|
154
297
|
next: response => {
|
|
@@ -156,17 +299,127 @@ class MaxAssistantComponent {
|
|
|
156
299
|
this.appendMessage(this.createAssistantResponseMessage(response, trimmedMessage));
|
|
157
300
|
this.isSending.set(false);
|
|
158
301
|
},
|
|
159
|
-
error:
|
|
302
|
+
error: error => {
|
|
160
303
|
this.appendMessage({
|
|
161
|
-
...this.createMessage('assistant', this.
|
|
304
|
+
...this.createMessage('assistant', this.resolveErrorMessage(error)),
|
|
162
305
|
isError: true
|
|
163
306
|
});
|
|
164
307
|
this.isSending.set(false);
|
|
165
308
|
}
|
|
166
309
|
});
|
|
167
310
|
}
|
|
311
|
+
suggestionLabel(suggestion) {
|
|
312
|
+
return typeof suggestion === 'string' ? suggestion : suggestion.label;
|
|
313
|
+
}
|
|
168
314
|
useSuggestion(suggestion) {
|
|
169
|
-
|
|
315
|
+
if (typeof suggestion === 'string' || !suggestion.structuredAction) {
|
|
316
|
+
this.sendMessage(typeof suggestion === 'string' ? suggestion : suggestion.message);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const label = suggestion.label.trim();
|
|
320
|
+
if (!label || this.isSending()) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const history = this.getConversationHistory();
|
|
324
|
+
this.appendMessage(this.createMessage('user', label));
|
|
325
|
+
this.isSending.set(true);
|
|
326
|
+
this.assistantService
|
|
327
|
+
.sendStructuredAction(suggestion.message, suggestion.structuredAction, this.conversationId(), this.getAssistantConfig(), history)
|
|
328
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
329
|
+
.subscribe({
|
|
330
|
+
next: response => {
|
|
331
|
+
this.conversationId.set(response.conversationId);
|
|
332
|
+
this.appendMessage(this.createAssistantResponseMessage(response, suggestion.message));
|
|
333
|
+
this.isSending.set(false);
|
|
334
|
+
},
|
|
335
|
+
error: error => {
|
|
336
|
+
this.appendMessage({
|
|
337
|
+
...this.createMessage('assistant', this.resolveErrorMessage(error)),
|
|
338
|
+
isError: true
|
|
339
|
+
});
|
|
340
|
+
this.isSending.set(false);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
getTableRowActions(table, rowIndex) {
|
|
345
|
+
return table.actions?.filter(action => action.rowIndex === rowIndex) ?? [];
|
|
346
|
+
}
|
|
347
|
+
useTableAction(action) {
|
|
348
|
+
const label = action.label.trim();
|
|
349
|
+
if (!label || this.isSending()) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const history = this.getConversationHistory();
|
|
353
|
+
this.appendMessage(this.createMessage('user', label));
|
|
354
|
+
this.isSending.set(true);
|
|
355
|
+
this.assistantService
|
|
356
|
+
.sendStructuredAction(action.message, action.structuredAction, this.conversationId(), this.getAssistantConfig(), history)
|
|
357
|
+
.pipe(switchMap(response => {
|
|
358
|
+
if (!action.confirmOnClick || !response.pendingAction) {
|
|
359
|
+
return of(response);
|
|
360
|
+
}
|
|
361
|
+
return this.assistantService.confirmAction(response.pendingAction.id, 'approve', response.conversationId, this.getAssistantConfig(), history);
|
|
362
|
+
}), takeUntilDestroyed(this.destroyRef))
|
|
363
|
+
.subscribe({
|
|
364
|
+
next: response => {
|
|
365
|
+
this.conversationId.set(response.conversationId);
|
|
366
|
+
this.appendMessage(this.createAssistantResponseMessage(response, action.message));
|
|
367
|
+
this.isSending.set(false);
|
|
368
|
+
},
|
|
369
|
+
error: error => {
|
|
370
|
+
this.appendMessage({
|
|
371
|
+
...this.createMessage('assistant', this.resolveErrorMessage(error)),
|
|
372
|
+
isError: true
|
|
373
|
+
});
|
|
374
|
+
this.isSending.set(false);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
resolvePendingAction(message, decision) {
|
|
379
|
+
const pendingAction = message.pendingAction;
|
|
380
|
+
const currentConversationId = this.conversationId();
|
|
381
|
+
if (!pendingAction ||
|
|
382
|
+
!currentConversationId ||
|
|
383
|
+
message.actionDecision ||
|
|
384
|
+
this.isSending()) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
this.messages.update(messages => messages.map(item => item.id === message.id ? { ...item, actionDecision: decision } : item));
|
|
388
|
+
const userMessage = decision === 'approve'
|
|
389
|
+
? `Approve: ${pendingAction.summary}`
|
|
390
|
+
: `Reject: ${pendingAction.summary}`;
|
|
391
|
+
const history = this.getConversationHistory();
|
|
392
|
+
this.appendMessage(this.createMessage('user', userMessage));
|
|
393
|
+
this.isSending.set(true);
|
|
394
|
+
this.assistantService
|
|
395
|
+
.confirmAction(pendingAction.id, decision, currentConversationId, this.getAssistantConfig(), history)
|
|
396
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
397
|
+
.subscribe({
|
|
398
|
+
next: response => {
|
|
399
|
+
this.conversationId.set(response.conversationId);
|
|
400
|
+
this.appendMessage(this.createAssistantResponseMessage(response, userMessage));
|
|
401
|
+
this.isSending.set(false);
|
|
402
|
+
},
|
|
403
|
+
error: error => {
|
|
404
|
+
this.messages.update(messages => messages.map(item => item.id === message.id ? { ...item, actionDecision: undefined } : item));
|
|
405
|
+
this.appendMessage({
|
|
406
|
+
...this.createMessage('assistant', this.resolveErrorMessage(error)),
|
|
407
|
+
isError: true
|
|
408
|
+
});
|
|
409
|
+
this.isSending.set(false);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
resolveErrorMessage(error) {
|
|
414
|
+
if (error instanceof HttpErrorResponse &&
|
|
415
|
+
typeof error.error === 'object' &&
|
|
416
|
+
error.error !== null &&
|
|
417
|
+
'message' in error.error &&
|
|
418
|
+
typeof error.error.message === 'string' &&
|
|
419
|
+
error.error.message.trim()) {
|
|
420
|
+
return error.error.message.trim();
|
|
421
|
+
}
|
|
422
|
+
return this.uiCopy.errorMessage;
|
|
170
423
|
}
|
|
171
424
|
onComposerKeydown(event) {
|
|
172
425
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
@@ -174,49 +427,181 @@ class MaxAssistantComponent {
|
|
|
174
427
|
this.sendMessage();
|
|
175
428
|
}
|
|
176
429
|
}
|
|
177
|
-
|
|
178
|
-
return this.
|
|
430
|
+
isDetailsVisible(messageId) {
|
|
431
|
+
return this.expandedDetailMessageIds().has(messageId);
|
|
432
|
+
}
|
|
433
|
+
toggleDetails(messageId, initialStepId) {
|
|
434
|
+
const opening = !this.isDetailsVisible(messageId);
|
|
435
|
+
this.expandedDetailMessageIds.set(opening ? new Set([messageId]) : new Set());
|
|
436
|
+
this.expandedTraceStepKeys.set(opening && initialStepId !== undefined
|
|
437
|
+
? new Set([this.traceStepKey(messageId, initialStepId)])
|
|
438
|
+
: new Set());
|
|
179
439
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
440
|
+
isTraceStepExpanded(messageId, stepId) {
|
|
441
|
+
return this.expandedTraceStepKeys().has(this.traceStepKey(messageId, stepId));
|
|
442
|
+
}
|
|
443
|
+
toggleTraceStep(messageId, stepId) {
|
|
444
|
+
const key = this.traceStepKey(messageId, stepId);
|
|
445
|
+
this.expandedTraceStepKeys.update(expandedKeys => {
|
|
446
|
+
const next = new Set(expandedKeys);
|
|
447
|
+
if (next.has(key)) {
|
|
448
|
+
next.delete(key);
|
|
186
449
|
}
|
|
187
450
|
else {
|
|
188
|
-
|
|
451
|
+
next.add(key);
|
|
189
452
|
}
|
|
190
|
-
return
|
|
453
|
+
return next;
|
|
191
454
|
});
|
|
192
|
-
|
|
193
|
-
|
|
455
|
+
}
|
|
456
|
+
getResponseGrid(responseDetails) {
|
|
457
|
+
return responseDetails.grid?.length ? responseDetails.grid : responseDetails.tables ?? [];
|
|
458
|
+
}
|
|
459
|
+
formatTraceValue(value) {
|
|
460
|
+
return JSON.stringify(value, null, 2);
|
|
461
|
+
}
|
|
462
|
+
traceMap(value) {
|
|
463
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
464
|
+
? value
|
|
465
|
+
: undefined;
|
|
466
|
+
}
|
|
467
|
+
traceMapField(value, key) {
|
|
468
|
+
return this.traceMap(this.traceMap(value)?.[key]);
|
|
469
|
+
}
|
|
470
|
+
traceArrayField(value, key) {
|
|
471
|
+
const field = this.traceMap(value)?.[key];
|
|
472
|
+
return Array.isArray(field) ? field : [];
|
|
473
|
+
}
|
|
474
|
+
traceField(value, key) {
|
|
475
|
+
return this.traceMap(value)?.[key];
|
|
476
|
+
}
|
|
477
|
+
traceEntries(value, excludedKeys = []) {
|
|
478
|
+
const record = this.traceMap(value);
|
|
479
|
+
const excluded = new Set(excludedKeys.map(key => key.toLocaleLowerCase()));
|
|
480
|
+
return record
|
|
481
|
+
? Object.entries(record)
|
|
482
|
+
.filter(([key, entry]) => entry !== undefined && !excluded.has(key.toLocaleLowerCase()))
|
|
483
|
+
.map(([key, entry]) => ({
|
|
484
|
+
key,
|
|
485
|
+
label: this.formatTraceLabel(key),
|
|
486
|
+
value: entry
|
|
487
|
+
}))
|
|
488
|
+
: [];
|
|
489
|
+
}
|
|
490
|
+
formatTraceLabel(value) {
|
|
491
|
+
return value
|
|
492
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
493
|
+
.replace(/_/g, ' ')
|
|
494
|
+
.replace(/\b\w/g, character => character.toUpperCase());
|
|
495
|
+
}
|
|
496
|
+
formatTraceScalar(value) {
|
|
497
|
+
if (value === null) {
|
|
498
|
+
return 'None';
|
|
499
|
+
}
|
|
500
|
+
if (value === undefined) {
|
|
501
|
+
return 'Not supplied';
|
|
502
|
+
}
|
|
503
|
+
if (typeof value === 'boolean') {
|
|
504
|
+
return value ? 'Yes' : 'No';
|
|
505
|
+
}
|
|
506
|
+
if (typeof value === 'number') {
|
|
507
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(6);
|
|
194
508
|
}
|
|
509
|
+
if (typeof value === 'string') {
|
|
510
|
+
return value || 'Empty';
|
|
511
|
+
}
|
|
512
|
+
return JSON.stringify(value);
|
|
195
513
|
}
|
|
196
|
-
|
|
197
|
-
return
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
514
|
+
formatSimilarity(value) {
|
|
515
|
+
return typeof value === 'number' ? value.toFixed(4) : '—';
|
|
516
|
+
}
|
|
517
|
+
getHttpStatus(step) {
|
|
518
|
+
const output = this.traceMap(step.output);
|
|
519
|
+
const http = this.traceMap(output?.['http']);
|
|
520
|
+
const status = http?.['statusCode'] ?? output?.['statusCode'] ?? step.error?.statusCode;
|
|
521
|
+
return typeof status === 'number' ? status : undefined;
|
|
522
|
+
}
|
|
523
|
+
isHttpSuccessful(step) {
|
|
524
|
+
const status = this.getHttpStatus(step);
|
|
525
|
+
return status !== undefined && status >= 200 && status < 300;
|
|
526
|
+
}
|
|
527
|
+
traceStepTone(step) {
|
|
528
|
+
return step.category === 'api_call' && this.isHttpSuccessful(step)
|
|
529
|
+
? 'succeeded'
|
|
530
|
+
: step.status;
|
|
531
|
+
}
|
|
532
|
+
traceStepStatus(step) {
|
|
533
|
+
if (step.category === 'api_call') {
|
|
534
|
+
const status = this.getHttpStatus(step);
|
|
535
|
+
if (status !== undefined) {
|
|
536
|
+
return `HTTP ${status} ${this.isHttpSuccessful(step) ? 'Success' : 'Error'}`;
|
|
537
|
+
}
|
|
538
|
+
return step.status === 'failed' ? 'Transport failed' : 'Completed';
|
|
539
|
+
}
|
|
540
|
+
return step.status === 'succeeded'
|
|
541
|
+
? 'Completed'
|
|
542
|
+
: step.status === 'failed'
|
|
543
|
+
? 'Stopped'
|
|
544
|
+
: 'Running';
|
|
545
|
+
}
|
|
546
|
+
formatTraceCategory(category) {
|
|
547
|
+
const labels = {
|
|
548
|
+
request: 'Request intake',
|
|
549
|
+
intent_projection: 'Intent generalisation',
|
|
550
|
+
embedding: 'Intent embedding',
|
|
551
|
+
capability_routing: 'Capability matching',
|
|
552
|
+
argument_extraction: 'Argument extraction',
|
|
553
|
+
api_call: 'Nemesys API',
|
|
554
|
+
action: 'Workflow outcome',
|
|
555
|
+
response: 'Assistant response'
|
|
201
556
|
};
|
|
557
|
+
return labels[category] ?? this.formatTraceLabel(category);
|
|
558
|
+
}
|
|
559
|
+
getAssistantConfig() {
|
|
560
|
+
const config = {};
|
|
561
|
+
if (this.assistantApiUrl) {
|
|
562
|
+
config.assistantApiUrl = this.assistantApiUrl;
|
|
563
|
+
}
|
|
564
|
+
if (this.app) {
|
|
565
|
+
config.app = this.app;
|
|
566
|
+
}
|
|
567
|
+
if (this.user) {
|
|
568
|
+
config.user = this.user;
|
|
569
|
+
}
|
|
570
|
+
if (this.context) {
|
|
571
|
+
config.context = this.context;
|
|
572
|
+
}
|
|
573
|
+
if (this.requestHeaders) {
|
|
574
|
+
config.requestHeaders = this.requestHeaders;
|
|
575
|
+
}
|
|
576
|
+
return config;
|
|
202
577
|
}
|
|
203
578
|
appendMessage(message) {
|
|
204
579
|
this.messages.update(messages => [...messages, message]);
|
|
205
580
|
this.scrollToLatest();
|
|
206
581
|
}
|
|
582
|
+
createDefaultMessages() {
|
|
583
|
+
if (!this.bootstrapConfig.initialMessage) {
|
|
584
|
+
return [];
|
|
585
|
+
}
|
|
586
|
+
return [
|
|
587
|
+
this.createMessage('assistant', this.bootstrapConfig.initialMessage, this.bootstrapConfig.initialSuggestions)
|
|
588
|
+
];
|
|
589
|
+
}
|
|
207
590
|
createAssistantResponseMessage(response, userQuery) {
|
|
208
591
|
const displayResponse = this.createDisplayResponse(response, userQuery);
|
|
209
|
-
return this.createMessage('assistant', displayResponse.agentInferredResponse || response.answer, response.suggestions, response.sources, response.intent, response.intentLabel, response.app, response.productLabel, response.apiModule, response.confidence, response.parameters ?? response.entities, response.apiCalls, response.mockResult, response.llmUsed, response.availableModules, displayResponse);
|
|
592
|
+
return this.createMessage('assistant', displayResponse.agentInferredResponse || response.answer, response.suggestions, response.sources, response.intent, response.intentLabel, response.app ?? response.product, response.productLabel, response.apiModule ?? response.contextResolution?.moduleKey, response.confidence ?? response.contextResolution?.confidence, response.parameters ?? response.entities, response.apiCalls ?? this.getToolApiCalls(response), response.apiResult, response.mockResult, response.llmUsed, response.availableModules, displayResponse, response.status, response.pendingAction, response.toolExecution, response.cards, response.contextResolution.capabilityKey, response.developerTrace);
|
|
210
593
|
}
|
|
211
|
-
createMessage(role, content, suggestions = [], sources = [], intent, intentLabel, app, productLabel, apiModule, confidence, parameters, apiCalls, mockResult, llmUsed, availableModules, displayResponse) {
|
|
594
|
+
createMessage(role, content, suggestions = [], sources = [], intent, intentLabel, app, productLabel, apiModule, confidence, parameters, apiCalls, apiResult, mockResult, llmUsed, availableModules, displayResponse, status, pendingAction, toolExecution, cards, capabilityKey, developerTrace) {
|
|
212
595
|
return {
|
|
213
596
|
id: this.createId(),
|
|
214
597
|
role,
|
|
215
598
|
content,
|
|
216
599
|
displayResponse,
|
|
217
600
|
formattedParameters: this.formatJson(displayResponse?.agentExtractedIntent?.parameters),
|
|
601
|
+
formattedApiHeaders: this.formatJson(displayResponse?.agentExtractedIntent?.api?.headers),
|
|
218
602
|
formattedApiBody: this.formatJson(displayResponse?.agentExtractedIntent?.api?.body),
|
|
219
603
|
formattedApiResponse: this.formatJson(displayResponse?.apiResponse),
|
|
604
|
+
formattedSampleJsonResponse: this.formatJson(displayResponse?.sampleJsonResponse),
|
|
220
605
|
suggestions,
|
|
221
606
|
sources,
|
|
222
607
|
intent,
|
|
@@ -228,50 +613,162 @@ class MaxAssistantComponent {
|
|
|
228
613
|
parameters,
|
|
229
614
|
entities: parameters,
|
|
230
615
|
apiCalls,
|
|
616
|
+
apiResult,
|
|
231
617
|
mockResult,
|
|
232
618
|
llmUsed,
|
|
233
619
|
availableModules,
|
|
620
|
+
status,
|
|
621
|
+
pendingAction,
|
|
622
|
+
toolExecution,
|
|
623
|
+
cards,
|
|
624
|
+
capabilityKey,
|
|
625
|
+
developerTrace,
|
|
234
626
|
createdAt: new Date()
|
|
235
627
|
};
|
|
236
628
|
}
|
|
237
629
|
createDisplayResponse(response, userQuery) {
|
|
238
630
|
const provided = response.displayResponse ?? {};
|
|
239
631
|
const extractedIntent = this.createExtractedIntent(response, provided.agentExtractedIntent);
|
|
632
|
+
const grid = this.getDisplayTables(provided, response);
|
|
240
633
|
return {
|
|
241
634
|
userQuery: provided.userQuery || userQuery,
|
|
242
635
|
agentInferredResponse: provided.agentInferredResponse || response.answer,
|
|
243
636
|
agentExtractedIntent: extractedIntent,
|
|
244
|
-
apiResponse: provided.apiResponse ??
|
|
637
|
+
apiResponse: provided.apiResponse ?? response.apiResult,
|
|
638
|
+
sampleJsonResponse: provided.sampleJsonResponse ?? this.getSampleJsonResponseFallback(response),
|
|
639
|
+
grid,
|
|
640
|
+
tables: grid,
|
|
245
641
|
markdown: provided.markdown
|
|
246
642
|
};
|
|
247
643
|
}
|
|
248
644
|
createExtractedIntent(response, provided) {
|
|
249
645
|
return {
|
|
250
|
-
project: provided?.project ?? response.productLabel ?? response.app,
|
|
251
|
-
projectKey: provided?.projectKey ?? response.app,
|
|
252
|
-
module: provided?.module ?? response.apiModule,
|
|
253
|
-
intent: provided?.intent ?? response.intent,
|
|
254
|
-
intentLabel: provided?.intentLabel
|
|
255
|
-
|
|
646
|
+
project: provided?.project ?? response.productLabel ?? response.product ?? response.app,
|
|
647
|
+
projectKey: provided?.projectKey ?? response.product ?? response.app,
|
|
648
|
+
module: provided?.module ?? response.apiModule ?? response.contextResolution?.moduleKey,
|
|
649
|
+
intent: provided?.intent ?? response.intent ?? response.contextResolution?.capabilityKey,
|
|
650
|
+
intentLabel: provided?.intentLabel
|
|
651
|
+
?? response.intentLabel
|
|
652
|
+
?? response.contextResolution?.capabilityLabel,
|
|
653
|
+
confidence: provided?.confidence
|
|
654
|
+
?? response.confidence
|
|
655
|
+
?? response.contextResolution?.confidence,
|
|
256
656
|
parameters: provided?.parameters ?? response.parameters ?? response.entities,
|
|
257
|
-
api: provided?.api ?? response.apiCalls?.[0]
|
|
657
|
+
api: provided?.api ?? response.apiCalls?.[0] ?? this.getToolApiCalls(response)?.[0]
|
|
258
658
|
};
|
|
259
659
|
}
|
|
260
|
-
|
|
660
|
+
getToolApiCalls(response) {
|
|
661
|
+
if (!response.toolExecution) {
|
|
662
|
+
return undefined;
|
|
663
|
+
}
|
|
664
|
+
return [{
|
|
665
|
+
method: response.toolExecution.method,
|
|
666
|
+
url: response.toolExecution.endpoint,
|
|
667
|
+
purpose: response.toolExecution.toolName
|
|
668
|
+
}];
|
|
669
|
+
}
|
|
670
|
+
getSampleJsonResponseFallback(response) {
|
|
261
671
|
if (!response.mockResult || !Object.keys(response.mockResult).length) {
|
|
262
672
|
return undefined;
|
|
263
673
|
}
|
|
264
674
|
return response.mockResult;
|
|
265
675
|
}
|
|
676
|
+
getDisplayTables(provided, response) {
|
|
677
|
+
if (provided.grid?.length) {
|
|
678
|
+
return provided.grid;
|
|
679
|
+
}
|
|
680
|
+
if (provided.tables?.length) {
|
|
681
|
+
return provided.tables;
|
|
682
|
+
}
|
|
683
|
+
const rows = this.extractTableRows(provided.apiResponse ?? response.apiResult);
|
|
684
|
+
if (!rows.length) {
|
|
685
|
+
return [];
|
|
686
|
+
}
|
|
687
|
+
const columns = this.getTableColumns(rows);
|
|
688
|
+
return [
|
|
689
|
+
{
|
|
690
|
+
title: 'API Response',
|
|
691
|
+
columns,
|
|
692
|
+
rows: rows.map(row => this.formatTableRow(row, columns.map(column => column.field)))
|
|
693
|
+
}
|
|
694
|
+
];
|
|
695
|
+
}
|
|
696
|
+
extractTableRows(value) {
|
|
697
|
+
if (Array.isArray(value)) {
|
|
698
|
+
return value.filter(this.isFieldMap);
|
|
699
|
+
}
|
|
700
|
+
if (!this.isFieldMap(value)) {
|
|
701
|
+
return [];
|
|
702
|
+
}
|
|
703
|
+
const body = value['body'];
|
|
704
|
+
if (Array.isArray(body)) {
|
|
705
|
+
return body.filter(this.isFieldMap);
|
|
706
|
+
}
|
|
707
|
+
const items = value['items'];
|
|
708
|
+
if (Array.isArray(items)) {
|
|
709
|
+
return items.filter(this.isFieldMap);
|
|
710
|
+
}
|
|
711
|
+
return [];
|
|
712
|
+
}
|
|
713
|
+
getTableColumns(rows) {
|
|
714
|
+
const fields = Array.from(new Set(rows.flatMap(row => Object.keys(row)))).slice(0, 8);
|
|
715
|
+
return fields.map(field => ({
|
|
716
|
+
field,
|
|
717
|
+
header: this.toColumnHeader(field)
|
|
718
|
+
}));
|
|
719
|
+
}
|
|
720
|
+
formatTableRow(row, fields) {
|
|
721
|
+
return Object.fromEntries(fields.map(field => [field, this.formatCellValue(row[field])]));
|
|
722
|
+
}
|
|
723
|
+
formatCellValue(value) {
|
|
724
|
+
if (Array.isArray(value)) {
|
|
725
|
+
return value.map(item => this.formatCellValue(item)).join(', ');
|
|
726
|
+
}
|
|
727
|
+
if (this.isFieldMap(value)) {
|
|
728
|
+
return value['label'] ?? value['name'] ?? value['code'] ?? JSON.stringify(value);
|
|
729
|
+
}
|
|
730
|
+
return value ?? '';
|
|
731
|
+
}
|
|
732
|
+
toColumnHeader(field) {
|
|
733
|
+
return field
|
|
734
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
735
|
+
.replace(/[_-]+/g, ' ')
|
|
736
|
+
.replace(/\b\w/g, character => character.toUpperCase());
|
|
737
|
+
}
|
|
266
738
|
formatJson(value) {
|
|
267
|
-
if (value === undefined) {
|
|
739
|
+
if (value === undefined || this.isEmptyObject(value)) {
|
|
268
740
|
return undefined;
|
|
269
741
|
}
|
|
270
742
|
return JSON.stringify(value, null, 2);
|
|
271
743
|
}
|
|
744
|
+
isEmptyObject(value) {
|
|
745
|
+
return !!value && typeof value === 'object' && !Array.isArray(value) && !Object.keys(value).length;
|
|
746
|
+
}
|
|
747
|
+
isFieldMap(value) {
|
|
748
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
749
|
+
}
|
|
272
750
|
createId() {
|
|
273
751
|
return globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`;
|
|
274
752
|
}
|
|
753
|
+
traceStepKey(messageId, stepId) {
|
|
754
|
+
return `${messageId}:${stepId}`;
|
|
755
|
+
}
|
|
756
|
+
getConversationHistory() {
|
|
757
|
+
const messages = this.messages();
|
|
758
|
+
const firstUserIndex = messages.findIndex(message => message.role === 'user');
|
|
759
|
+
if (firstUserIndex < 0) {
|
|
760
|
+
return [];
|
|
761
|
+
}
|
|
762
|
+
return messages
|
|
763
|
+
.slice(firstUserIndex)
|
|
764
|
+
.filter(message => !message.isError)
|
|
765
|
+
.slice(-20)
|
|
766
|
+
.map(message => ({
|
|
767
|
+
role: message.role,
|
|
768
|
+
content: message.content.slice(0, 4_000),
|
|
769
|
+
...(message.capabilityKey ? { capabilityKey: message.capabilityKey } : {})
|
|
770
|
+
}));
|
|
771
|
+
}
|
|
275
772
|
scrollToLatest() {
|
|
276
773
|
setTimeout(() => {
|
|
277
774
|
const element = this.messagesViewport?.nativeElement;
|
|
@@ -280,13 +777,23 @@ class MaxAssistantComponent {
|
|
|
280
777
|
}
|
|
281
778
|
});
|
|
282
779
|
}
|
|
283
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
284
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: MaxAssistantComponent, isStandalone: true, selector: "max-assistant", inputs: { app: "app", assistantApiUrl: "assistantApiUrl", user: "user" }, viewQueries: [{ propertyName: "messagesViewport", first: true, predicate: ["messagesViewport"], descendants: true }], ngImport: i0, template: "<button\n pButton\n type=\"button\"\n class=\"assistant-launcher\"\n [attr.aria-label]=\"uiCopy.launcherLabel\"\n [pTooltip]=\"uiCopy.openTooltip\"\n tooltipPosition=\"left\"\n (click)=\"openPanel()\"\n>\n <span class=\"mascot\" aria-hidden=\"true\"></span>\n</button>\n\n<p-drawer\n [visible]=\"panelVisible()\"\n (visibleChange)=\"panelVisible.set($event)\"\n position=\"right\"\n styleClass=\"assistant-drawer\"\n [modal]=\"false\"\n [blockScroll]=\"false\"\n [showCloseIcon]=\"false\"\n>\n <section class=\"assistant-panel\" [attr.aria-label]=\"uiCopy.title\">\n <header class=\"assistant-header\">\n <span class=\"mascot large\" aria-hidden=\"true\"></span>\n <div>\n <h2>{{ uiCopy.title }}</h2>\n <span>{{ uiCopy.subtitle }}</span>\n </div>\n <button\n pButton\n type=\"button\"\n icon=\"pi pi-times\"\n text\n rounded\n severity=\"secondary\"\n [attr.aria-label]=\"uiCopy.closeLabel\"\n (click)=\"closePanel()\"\n ></button>\n </header>\n\n <div #messagesViewport class=\"assistant-messages\">\n @for(message of messages(); track message.id) {\n <article class=\"message {{ message.role }}\" [class.error]=\"message.isError\">\n @if(message.displayResponse; as responseDetails) {\n <p class=\"assistant-result\">{{ responseDetails.agentInferredResponse || message.content }}</p>\n <button\n type=\"button\"\n class=\"inference-toggle\"\n [attr.aria-expanded]=\"isInferenceDetailsVisible(message.id)\"\n (click)=\"toggleInferenceDetails(message.id)\"\n >\n {{ isInferenceDetailsVisible(message.id) ? 'Hide Details' : 'Show Details' }}\n </button>\n\n @if(isInferenceDetailsVisible(message.id)) {\n <div class=\"response-details\">\n @if(responseDetails.userQuery) {\n <section class=\"response-section\">\n <h3>User Query</h3>\n <p class=\"quoted\">\"{{ responseDetails.userQuery }}\"</p>\n </section>\n }\n\n @if(responseDetails.agentExtractedIntent; as intent) {\n <section class=\"response-section\">\n <h3>Agent Extracted Intent</h3>\n <dl class=\"detail-grid\">\n @if(intent.project || intent.projectKey) {\n <div>\n <dt>Project</dt>\n <dd>\n {{ intent.project || intent.projectKey }}\n @if(intent.projectKey) {\n <span>{{ intent.projectKey }}</span>\n }\n </dd>\n </div>\n }\n @if(intent.module) {\n <div>\n <dt>Module</dt>\n <dd>{{ intent.module }}</dd>\n </div>\n }\n @if(intent.intentLabel) {\n <div>\n <dt>Intent Label</dt>\n <dd>{{ intent.intentLabel }}</dd>\n </div>\n }\n @if(intent.intent) {\n <div>\n <dt>Intent</dt>\n <dd>{{ intent.intent }}</dd>\n </div>\n }\n @if(intent.confidence !== undefined) {\n <div>\n <dt>Confidence</dt>\n <dd>{{ intent.confidence | percent: '1.0-0' }}</dd>\n </div>\n }\n </dl>\n\n @if(message.formattedParameters) {\n <div class=\"json-block\">\n <strong>Parameters</strong>\n <pre>{{ message.formattedParameters }}</pre>\n </div>\n }\n\n @if(intent.api?.url || intent.api?.method) {\n <div class=\"api-call-summary\">\n <strong>API Called</strong>\n <code>{{ intent.api?.method || 'GET' }} {{ intent.api?.url || 'URL not provided' }}</code>\n </div>\n }\n\n @if(message.formattedApiBody) {\n <div class=\"json-block\">\n <strong>API Body</strong>\n <pre>{{ message.formattedApiBody }}</pre>\n </div>\n }\n </section>\n }\n\n @if(message.formattedApiResponse) {\n <section class=\"response-section\">\n <h3>API Response</h3>\n <div class=\"json-block\">\n <strong>JSON</strong>\n <pre>{{ message.formattedApiResponse }}</pre>\n </div>\n </section>\n }\n </div>\n }\n } @else {\n <p>{{ message.content }}</p>\n }\n @if(message.role === 'assistant' && message.suggestions?.length) {\n <div class=\"suggestions\">\n @for(suggestion of message.suggestions ?? []; track suggestion) {\n <button\n pButton\n type=\"button\"\n size=\"small\"\n text\n severity=\"secondary\"\n class=\"suggestion-chip\"\n [label]=\"suggestion\"\n (click)=\"useSuggestion(suggestion)\"\n ></button>\n }\n </div>\n }\n </article>\n }\n @if(isSending()) {\n <article class=\"message assistant typing\" [attr.aria-label]=\"uiCopy.typingLabel\">\n <span></span>\n <span></span>\n <span></span>\n </article>\n }\n </div>\n\n <form class=\"assistant-composer\" (ngSubmit)=\"sendMessage()\">\n <textarea\n pTextarea\n name=\"assistantPrompt\"\n rows=\"2\"\n [placeholder]=\"uiCopy.placeholder\"\n [ngModel]=\"input()\"\n (ngModelChange)=\"input.set($event)\"\n (keydown)=\"onComposerKeydown($event)\"\n >\n </textarea>\n <button\n pButton\n type=\"submit\"\n icon=\"pi pi-send\"\n aria-label=\"Send message\"\n [disabled]=\"!input().trim() || isSending()\"\n ></button>\n </form>\n </section>\n</p-drawer>\n", styles: [":host{position:fixed;right:24px;bottom:24px;z-index:1100}.assistant-launcher{width:70px!important;height:56px!important;min-height:60px!important;padding:0 5px;border:0!important;border-radius:18px!important;color:inherit!important;background:#d7edff!important;box-shadow:0 0 0 1px #0078c824,0 10px 24px #0f172a24!important;overflow:visible}.assistant-launcher:hover,.assistant-launcher:focus-visible{background:#fff!important;box-shadow:0 0 0 2px #0078c838,0 12px 28px #0f172a29!important}.mascot{position:relative;display:grid;place-items:center;width:64px;height:56px;background:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"120\" height=\"96\" viewBox=\"14 10 92 82\" role=\"img\" aria-label=\"Max smiling cloud AI mascot\">%0A <defs>%0A <filter id=\"softShadow\" x=\"-15%\" y=\"-15%\" width=\"130%\" height=\"140%\">%0A <feDropShadow dx=\"0\" dy=\"5\" stdDeviation=\"5\" flood-color=\"%230078C8\" flood-opacity=\"0.16\"/>%0A </filter>%0A </defs>%0A%0A <g fill=\"%230078C8\" opacity=\"0.28\">%0A <circle cx=\"34\" cy=\"57\" r=\"24\"/>%0A <circle cx=\"58\" cy=\"39\" r=\"31\"/>%0A <circle cx=\"86\" cy=\"58\" r=\"25\"/>%0A <rect x=\"28\" y=\"55\" width=\"68\" height=\"27\" rx=\"13.5\"/>%0A </g>%0A%0A <g fill=\"%230078C8\" opacity=\"0.68\" filter=\"url(%23softShadow)\">%0A <circle cx=\"35\" cy=\"59\" r=\"20\"/>%0A <circle cx=\"59\" cy=\"43\" r=\"27\"/>%0A <circle cx=\"84\" cy=\"60\" r=\"21\"/>%0A <rect x=\"31\" y=\"57\" width=\"64\" height=\"24\" rx=\"12\"/>%0A </g>%0A%0A <g fill=\"%23FFD54A\">%0A <path d=\"M46 48l3.4 6.8 6.8 3.4-6.8 3.4L46 68.4l-3.4-6.8-6.8-3.4 6.8-3.4z\"/>%0A <path d=\"M74 48l3.4 6.8 6.8 3.4-6.8 3.4-3.4 6.8-3.4-6.8-6.8-3.4 6.8-3.4z\"/>%0A <path d=\"M88 28l2 4 4 2-4 2-2 4-2-4-4-2 4-2z\"/>%0A </g>%0A%0A <path%0A d=\"M49 72c6 7 20 7 26 0\"%0A fill=\"none\"%0A stroke=\"%23FFD54A\"%0A stroke-width=\"4\"%0A stroke-linecap=\"round\"%0A />%0A</svg>%0A') center/contain no-repeat}.mascot.large{width:58px;height:46px}::ng-deep .assistant-drawer{width:min(560px,100vw)!important}::ng-deep .assistant-drawer .p-drawer-content{padding:0}::ng-deep .assistant-drawer .p-drawer-header{display:none}.assistant-panel{display:grid;grid-template-rows:auto 1fr auto;height:100%;min-height:0;background:var(--background-color, #f8fafc)}.assistant-header,.assistant-composer{display:flex;gap:12px;align-items:center;padding:16px;background:#fff;border-bottom:1px solid var(--border-color, #e2e8f0)}.assistant-header h2{margin:0;font-size:20px}.assistant-header span,.sources span{color:#64748b;font-size:12px;font-weight:700}.assistant-header button{margin-left:auto}.assistant-messages{min-height:0;overflow:auto;padding:18px}.message{width:fit-content;max-width:92%;margin-bottom:14px;padding:12px;border:1px solid var(--border-color, #e2e8f0);border-radius:var(--medium-border-radius, 8px);background:#fff;box-shadow:0 6px 18px #0f172a0f}.message.assistant{width:100%;max-width:100%}.message.user{width:fit-content;max-width:88%;margin-left:auto;color:#fff;background:var(--primary-color, #2563eb);border-color:var(--primary-color, #2563eb)}.message.error{border-color:var(--danger-color, #dc2626)}.message p{margin:0;line-height:1.45}.assistant-result{color:#1e293b}.inference-toggle{display:flex;width:fit-content;margin:10px 0 0 auto;padding:0;border:0;color:var(--primary-color, #2563eb);background:transparent;font:inherit;font-size:13px;font-weight:400;text-decoration:underline;text-underline-offset:3px;cursor:pointer}.inference-toggle:hover{color:var(--primary-dark-color, #1d4ed8)}.inference-toggle:focus-visible{outline:2px solid var(--primary-color, #2563eb);outline-offset:3px}.suggestions{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.suggestion-chip{border:1px solid rgba(var(--primary-color-rgb, 37, 99, 235),.28);color:var(--primary-dark-color, #1d4ed8);background:rgba(var(--primary-color-rgb, 37, 99, 235),.08);font-weight:700;box-shadow:0 4px 14px #0f172a0f;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.suggestion-chip:hover{border-color:rgba(var(--primary-color-rgb, 37, 99, 235),.42);color:var(--primary-dark-color, #1d4ed8);background:rgba(var(--primary-color-rgb, 37, 99, 235),.14)}.response-details{display:grid;gap:12px;margin-top:12px}.response-section{display:grid;gap:8px;padding-bottom:12px;border-bottom:1px solid var(--border-color, #e2e8f0)}.response-section:last-child{padding-bottom:0;border-bottom:0}.response-section h3{margin:0;color:#0f172a;font-size:13px;font-weight:800}.quoted{padding:8px 10px;border-left:5px solid var(--primary-color, #2563eb);border-radius:var(--border-radius, 6px);color:#1e293b;background:#f8fafc}.detail-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:0}.detail-grid div{min-width:0;padding:8px;border-radius:var(--border-radius, 6px);background:#f8fafc}.detail-grid dt{margin-bottom:3px;color:#64748b;font-size:11px;font-weight:800;text-transform:uppercase}.detail-grid dd{margin:0;color:#334155;font-size:13px;font-weight:700;line-height:1.35;overflow-wrap:anywhere}.detail-grid dd span{display:block;margin-top:2px;color:#64748b;font-size:11px}.api-call-summary,.json-block{display:grid;gap:6px;padding:8px;border-radius:var(--border-radius, 6px);background:#f8fafc}.api-call-summary strong,.json-block strong{color:#64748b;font-size:11px;font-weight:800;text-transform:uppercase}.api-call-summary code{color:var(--secondary-color, #0f172a);font-weight:800;white-space:normal;word-break:break-word}.json-block pre{max-height:220px;margin:0;overflow:auto;color:#0f172a;font-size:12px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}.typing{display:flex;width:fit-content;max-width:fit-content;gap:5px;align-items:center;padding:4px 0;border:0;background:transparent;box-shadow:none}.typing span{width:7px;height:7px;border-radius:50%;background:var(--primary-color, #2563eb);animation:typing-dot .85s infinite ease-in-out}.typing span:nth-child(2){animation-delay:.15s}.assistant-composer{border-top:1px solid var(--border-color, #e2e8f0);border-bottom:0}.assistant-composer textarea{flex:1;min-height:46px;max-height:120px;resize:vertical}@keyframes typing-dot{0%,80%,to{opacity:.35;transform:translateY(0)}40%{opacity:1;transform:translateY(-4px)}}@keyframes pulse{to{opacity:.35;transform:translateY(-2px)}}@media(max-width:640px){:host{right:16px;bottom:16px}.assistant-launcher{width:62px!important;min-width:62px!important;height:54px!important;min-height:54px!important}.assistant-messages{padding:14px}.detail-grid{grid-template-columns:1fr}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i3.Drawer, selector: "p-drawer", inputs: ["appendTo", "motionOptions", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i4.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "pipe", type: i6.PercentPipe, name: "percent" }] });
|
|
780
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MaxAssistantComponent, deps: [{ token: MaxAssistantService }, { token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
781
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.14", type: MaxAssistantComponent, isStandalone: true, selector: "max-assistant", inputs: { app: "app", assistantApiUrl: "assistantApiUrl", user: "user", context: "context", requestHeaders: "requestHeaders" }, host: { listeners: { "document:keydown.escape": "onDocumentEscape()" } }, viewQueries: [{ propertyName: "launcher", first: true, predicate: ["launcher"], descendants: true }, { propertyName: "messagesViewport", first: true, predicate: ["messagesViewport"], descendants: true }], ngImport: i0, template: "<ng-template #mascotTemplate let-large=\"large\">\r\n <span class=\"mascot\" [class.large]=\"large\" aria-hidden=\"true\">\r\n <span class=\"mascot-aura\"></span>\r\n <svg class=\"mascot-art\" viewBox=\"0 0 64 64\" focusable=\"false\">\r\n <path class=\"mascot-sparkle\" d=\"M54 9v6M51 12h6\" />\r\n <path class=\"mascot-antenna\" d=\"M32 14V9\" />\r\n <circle class=\"mascot-signal\" cx=\"32\" cy=\"7\" r=\"3\" />\r\n <rect class=\"mascot-ear mascot-ear-left\" x=\"5\" y=\"28\" width=\"7\" height=\"15\" rx=\"3.5\" />\r\n <rect class=\"mascot-ear mascot-ear-right\" x=\"52\" y=\"28\" width=\"7\" height=\"15\" rx=\"3.5\" />\r\n <rect class=\"mascot-shell\" x=\"8\" y=\"14\" width=\"48\" height=\"42\" rx=\"18\" />\r\n <path class=\"mascot-shell-shine\" d=\"M17 24c4-7 12-9 20-8\" />\r\n <rect class=\"mascot-face\" x=\"14\" y=\"21\" width=\"36\" height=\"26\" rx=\"12\" />\r\n <g class=\"mascot-eyes\">\r\n <rect class=\"mascot-eye\" x=\"21\" y=\"29\" width=\"5\" height=\"7\" rx=\"2.5\" />\r\n <rect class=\"mascot-eye\" x=\"38\" y=\"29\" width=\"5\" height=\"7\" rx=\"2.5\" />\r\n </g>\r\n <path class=\"mascot-smile\" d=\"M25 39c2 2 4.5 3 7 3s5-1 7-3\" />\r\n <path class=\"mascot-mark\" d=\"M26 18l6 5 6-5\" />\r\n </svg>\r\n </span>\r\n</ng-template>\r\n\r\n<button\n #launcher\n pButton\n type=\"button\"\n class=\"assistant-launcher\"\r\n [class.is-open]=\"panelVisible()\"\r\n [attr.aria-label]=\"launcherLabel\"\r\n [attr.aria-pressed]=\"panelVisible()\"\r\n [pTooltip]=\"launcherLabel\"\r\n tooltipPosition=\"left\"\r\n (click)=\"togglePanel()\"\r\n>\r\n <ng-container\r\n [ngTemplateOutlet]=\"mascotTemplate\"\r\n [ngTemplateOutletContext]=\"{ large: false }\"\r\n ></ng-container>\n</button>\n\n<aside\n class=\"assistant-window\"\n [class.is-open]=\"panelVisible()\"\n [attr.aria-hidden]=\"!panelVisible()\"\n [attr.inert]=\"panelVisible() ? null : ''\"\n [attr.aria-label]=\"uiCopy.title\"\n role=\"complementary\"\n>\n <section class=\"assistant-panel\">\n <header class=\"assistant-header\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"mascotTemplate\"\r\n [ngTemplateOutletContext]=\"{ large: true }\"\r\n ></ng-container>\r\n <div class=\"assistant-identity\">\r\n <h2 class=\"assistant-name\">{{ uiCopy.title }}</h2>\r\n <span class=\"assistant-subtitle\">{{ uiCopy.subtitle }}</span>\r\n </div>\r\n <button\r\n pButton\r\n type=\"button\"\r\n icon=\"pi pi-refresh\"\r\n text\r\n rounded\r\n severity=\"secondary\"\r\n class=\"header-action\"\r\n [attr.aria-label]=\"uiCopy.clearLabel\"\r\n [pTooltip]=\"uiCopy.clearTooltip\"\r\n tooltipPosition=\"bottom\"\r\n (click)=\"clearChatSession()\"\r\n ></button>\r\n <button\r\n pButton\r\n type=\"button\"\r\n icon=\"pi pi-times\"\r\n text\r\n rounded\r\n severity=\"secondary\"\r\n class=\"header-action\"\r\n [attr.aria-label]=\"uiCopy.closeLabel\"\r\n (click)=\"closePanel()\"\r\n ></button>\r\n </header>\r\n\r\n <div #messagesViewport class=\"assistant-messages\">\r\n @for(message of messages(); track message.id) {\r\n <article class=\"message {{ message.role }}\" [class.error]=\"message.isError\">\r\n @if(message.displayResponse; as responseDetails) {\r\n <div\n class=\"assistant-result assistant-markdown\"\n [innerHTML]=\"(responseDetails.markdown || responseDetails.agentInferredResponse || message.content) | maxAssistantMarkdown\"\n ></div>\n @if(message.cards?.length) {\n <div class=\"assistant-cards\">\n @for(card of message.cards ?? []; track $index) {\n <section class=\"assistant-card\">\n <header>\n <strong>{{ card.title }}</strong>\n @if(card.subtitle) {\n <span>{{ card.subtitle }}</span>\n }\n </header>\n <div class=\"assistant-card-metrics\">\n @for(metric of card.metrics; track metric.label) {\n <div class=\"assistant-card-metric\" [attr.data-tone]=\"metric.tone || 'default'\">\n <span>{{ metric.label }}</span>\n <strong>{{ metric.value }}</strong>\n </div>\n }\n </div>\n @if(card.footer) {\n <footer>{{ card.footer }}</footer>\n }\n </section>\n }\n </div>\n }\n @if(message.status) {\n <span class=\"assistant-status\">{{ message.status.replaceAll('_', ' ') }}</span>\r\n }\r\n @if(message.pendingAction; as pendingAction) {\r\n <section class=\"pending-action\" aria-label=\"Cortex action confirmation\">\r\n <div class=\"pending-action-heading\">\r\n <strong>Confirmation required</strong>\r\n <span>{{ pendingAction.method }}</span>\r\n </div>\r\n <p>{{ pendingAction.summary }}</p>\r\n <small>Expires {{ pendingAction.expiresAt | date: 'mediumTime' }}</small>\r\n @if(message.actionDecision; as actionDecision) {\r\n <p class=\"action-decision\">\r\n {{ actionDecision === 'approve' ? 'Approval sent' : 'Rejection sent' }}\r\n </p>\r\n } @else {\r\n <div class=\"pending-action-buttons\">\r\n <button\r\n pButton\r\n type=\"button\"\r\n size=\"small\"\r\n severity=\"success\"\r\n label=\"Approve\"\r\n [disabled]=\"isSending()\"\r\n (click)=\"resolvePendingAction(message, 'approve')\"\r\n ></button>\r\n <button\r\n pButton\r\n type=\"button\"\r\n size=\"small\"\r\n outlined\r\n severity=\"secondary\"\r\n label=\"Reject\"\r\n [disabled]=\"isSending()\"\r\n (click)=\"resolvePendingAction(message, 'reject')\"\r\n ></button>\r\n </div>\r\n }\r\n </section>\r\n }\r\n @if(getResponseGrid(responseDetails).length) {\r\n <div class=\"assistant-tables\">\r\n @for(table of getResponseGrid(responseDetails); track $index) {\r\n <section class=\"assistant-table-section\">\r\n @if(table.title) {\r\n <h3>{{ table.title }}</h3>\r\n }\r\n <div class=\"assistant-table-wrap\">\r\n <table>\r\n <thead>\r\n <tr>\r\n @for(column of table.columns; track column.field) {\n <th>{{ column.header }}</th>\n }\n @if(table.actions?.length) {\n <th>Actions</th>\n }\n </tr>\r\n </thead>\r\n <tbody>\r\n @for(row of table.rows; track $index; let rowIndex = $index) {\n <tr>\n @for(column of table.columns; track column.field) {\n <td>{{ row[column.field] }}</td>\n }\n @if(table.actions?.length) {\n <td>\n <div class=\"assistant-table-actions\">\n @for(action of getTableRowActions(table, rowIndex); track action.label) {\n <button\n pButton\n type=\"button\"\n size=\"small\"\n [severity]=\"action.tone || 'secondary'\"\n [outlined]=\"action.tone === 'danger'\"\n [label]=\"action.label\"\n [disabled]=\"isSending()\"\n (click)=\"useTableAction(action)\"\n ></button>\n }\n </div>\n </td>\n }\n </tr>\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n </section>\r\n }\r\n </div>\r\n }\r\n @if(message.sources?.length) {\r\n <div class=\"assistant-sources\">\r\n <strong>Sources</strong>\r\n @for(source of message.sources ?? []; track source.citation) {\r\n <span>{{ source.citation }}</span>\r\n }\r\n </div>\r\n }\r\n @if(developerDetailsEnabled && message.developerTrace?.steps?.length) {\n <button\n type=\"button\"\n class=\"details-toggle\"\n [attr.aria-expanded]=\"isDetailsVisible(message.id)\"\n (click)=\"toggleDetails(message.id, message.developerTrace!.steps[0]?.id)\"\n >\n {{ isDetailsVisible(message.id) ? 'Hide Details' : 'Show Details' }}\n </button>\n\n @if(isDetailsVisible(message.id)) {\n <div class=\"details-dialog-backdrop\" (click)=\"toggleDetails(message.id)\">\n <section\n class=\"developer-details\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Agent processing details\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"developer-details-header\">\n <div>\n <span class=\"developer-eyebrow\">Developer trace</span>\n <h3>How this request was processed</h3>\n <p>A technical, stage-by-stage account of routing, inference, tools and results.</p>\n </div>\n <div class=\"details-dialog-actions\">\n <button\n type=\"button\"\n class=\"details-dialog-close\"\n aria-label=\"Close processing details\"\n (click)=\"toggleDetails(message.id)\"\n >\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </header>\n\n @if(message.developerTrace!.run; as run) {\n <section class=\"trace-run-summary\" aria-label=\"Run summary\">\n <div class=\"trace-metric\"><span>Total runtime</span><strong>{{ run.durationMs }} ms</strong></div>\n <div class=\"trace-metric\"><span>Model calls</span><strong>{{ run.llmCalls }}</strong></div>\n <div class=\"trace-metric\"><span>Embedding calls</span><strong>{{ run.embeddingCalls }}</strong></div>\n <div class=\"trace-metric\"><span>API calls</span><strong>{{ run.apiCalls }}</strong></div>\n <div class=\"trace-loop-policy\">\n <div>\n <span>Execution architecture</span>\n <strong>Deterministic staged pipeline</strong>\n </div>\n <span class=\"trace-no-loop\">No autonomous model loop</span>\n <p>{{ run.loopPolicy }}</p>\n </div>\n </section>\n }\n\n <ol class=\"workflow-trace\">\n @for(step of message.developerTrace!.steps; track step.id; let stepIndex = $index) {\n <li class=\"workflow-step\" [attr.data-status]=\"traceStepTone(step)\">\n <section class=\"workflow-section\">\n <button\n type=\"button\"\n class=\"workflow-step-toggle\"\n [attr.aria-expanded]=\"isTraceStepExpanded(message.id, step.id)\"\n (click)=\"toggleTraceStep(message.id, step.id)\"\n >\n <div>\n <span class=\"workflow-step-number\">Stage {{ stepIndex + 1 }}</span>\n <h4>{{ step.title }}</h4>\n <span class=\"workflow-category\">{{ formatTraceCategory(step.category) }}</span>\n </div>\n <div class=\"workflow-meta\">\n <span class=\"workflow-status\">{{ traceStepStatus(step) }}</span>\n @if(step.durationMs !== undefined) {\n <span>{{ step.durationMs }} ms</span>\n }\n <i\n class=\"pi workflow-expand-icon\"\n [class.pi-chevron-down]=\"isTraceStepExpanded(message.id, step.id)\"\n [class.pi-chevron-right]=\"!isTraceStepExpanded(message.id, step.id)\"\n aria-hidden=\"true\"\n ></i>\n </div>\n </button>\n\n @if(isTraceStepExpanded(message.id, step.id)) {\n <div class=\"workflow-step-content\">\n @if(step.metadata?.summary) {\n <p class=\"workflow-summary\">{{ step.metadata!.summary }}</p>\n }\n\n @if(step.metadata?.technology; as technology) {\n <div class=\"trace-technology\">\n <span class=\"trace-section-label\">Technology</span>\n <div class=\"trace-inline-facts\">\n <span><small>Component</small>{{ technology.component }}</span>\n @if(technology.model) { <span><small>Model</small>{{ technology.model }}</span> }\n @if(technology.library) { <span><small>Library</small>{{ technology.library }}</span> }\n @if(technology.provider) { <span><small>Provider</small>{{ technology.provider }}</span> }\n @if(technology.operation) { <span><small>Operation</small>{{ technology.operation }}</span> }\n </div>\n </div>\n }\n\n @if(step.metadata?.control; as control) {\n <div class=\"trace-control\">\n <div>\n <span class=\"trace-section-label\">Stop control</span>\n <strong>{{ formatTraceLabel(control.mode) }}</strong>\n </div>\n @if(control.maximumIterations !== undefined) {\n <span>{{ control.iterationsUsed ?? 0 }} / {{ control.maximumIterations }} iteration</span>\n }\n <p>{{ control.stopCondition }}</p>\n </div>\n }\n\n @switch(step.category) {\n @case('request') {\n @if(traceField(step.input, 'userQuery'); as userQuery) {\n <div class=\"trace-query\">\n <span>Original user query</span>\n <blockquote>{{ formatTraceScalar(userQuery) }}</blockquote>\n </div>\n }\n <div class=\"trace-card-grid\">\n @for(group of [\n { label: 'Product', value: traceMapField(step.input, 'product') },\n { label: 'User', value: traceMapField(step.input, 'user') },\n { label: 'Request context', value: traceMapField(step.input, 'requestContext') },\n { label: 'Conversation', value: traceMapField(step.input, 'conversation') },\n { label: 'Live API context', value: traceMapField(step.input, 'liveApi') }\n ]; track group.label) {\n @if(group.value) {\n <section class=\"trace-data-card\">\n <h5>{{ group.label }}</h5>\n <dl class=\"trace-field-list\">\n @for(entry of traceEntries(group.value, group.label === 'User' ? ['permissions'] : []); track entry.key) {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n </dl>\n </section>\n }\n }\n </div>\n }\n\n @case('intent_projection') {\n <div class=\"trace-transform\">\n <div>\n <span>Original query</span>\n <p>{{ formatTraceScalar(traceField(step.output, 'originalUserQuery') ?? traceField(step.input, 'originalUserQuery')) }}</p>\n </div>\n <span class=\"trace-transform-arrow\" aria-hidden=\"true\">\u2192</span>\n <div class=\"trace-transform-result\">\n <span>Generalised intent sent to embedding</span>\n <p>{{ formatTraceScalar(traceField(step.output, 'generalisedIntent') ?? traceField(step.output, 'routingText')) }}</p>\n </div>\n </div>\n @if(traceArrayField(step.output, 'removedArguments').length) {\n <div class=\"trace-section\">\n <span class=\"trace-section-label\">Arguments removed before routing</span>\n <div class=\"trace-entity-list\">\n @for(argument of traceArrayField(step.output, 'removedArguments'); track $index) {\n <span><small>{{ formatTraceScalar(traceField(argument, 'kind')) }}</small>{{ formatTraceScalar(traceField(argument, 'value')) }}</span>\n }\n </div>\n </div>\n }\n }\n\n @case('embedding') {\n <div class=\"trace-query trace-query-compact\">\n <span>Embedded text</span>\n <blockquote>{{ formatTraceScalar(traceField(step.input, 'generalisedIntent')) }}</blockquote>\n </div>\n <div class=\"trace-stat-grid\">\n <div><span>Embedding model</span><strong>{{ formatTraceScalar(traceField(step.output, 'model') ?? traceField(step.input, 'model')) }}</strong></div>\n <div><span>Dimensions</span><strong>{{ formatTraceScalar(traceField(step.output, 'dimensions')) }}</strong></div>\n <div><span>Vector magnitude</span><strong>{{ formatTraceScalar(traceField(step.output, 'vectorMagnitude')) }}</strong></div>\n <div><span>Full vector exposed</span><strong>{{ formatTraceScalar(traceField(step.output, 'fullVectorShown')) }}</strong></div>\n </div>\n @if(traceField(step.output, 'vectorPreview'); as vectorPreview) {\n <div class=\"trace-vector\">\n <span>Vector preview</span>\n <code>{{ formatTraceScalar(vectorPreview) }}</code>\n </div>\n }\n }\n\n @case('capability_routing') {\n <div class=\"trace-supabase-call\">\n <div><span>Supabase RPC</span><strong>{{ formatTraceScalar(traceField(step.input, 'rpc')) }}</strong></div>\n <div><span>Database</span><strong>{{ formatTraceScalar(traceField(step.input, 'database')) }}</strong></div>\n @if(traceArrayField(step.input, 'tables').length) {\n <div class=\"trace-table-list\">\n <span>Tables searched</span>\n <div>\n @for(table of traceArrayField(step.input, 'tables'); track $index) {\n <code>{{ formatTraceScalar(table) }}</code>\n }\n </div>\n </div>\n }\n </div>\n @if(traceMapField(step.output, 'decision'); as decision) {\n <section class=\"trace-decision\">\n <span>Routing decision</span>\n <strong>{{ formatTraceScalar(decision['selectedCapabilityLabel'] ?? decision['selectedCapabilityKey']) }}</strong>\n <div>\n <span>Status: {{ formatTraceScalar(decision['status']) }}</span>\n <span>Confidence: {{ formatSimilarity(decision['confidence']) }}</span>\n <span>Runner-up: {{ formatSimilarity(decision['runnerUpConfidence']) }}</span>\n </div>\n </section>\n }\n @if(traceArrayField(step.output, 'matchedCapabilities').length) {\n <div class=\"trace-ranking-wrap\">\n <span class=\"trace-section-label\">Ranked capability matches returned by Supabase</span>\n <table class=\"trace-ranking\">\n <thead><tr><th>#</th><th>Capability</th><th>Combined</th><th>Semantic</th><th>Definition</th><th>Example</th><th>Keyword</th></tr></thead>\n <tbody>\n @for(candidate of traceArrayField(step.output, 'matchedCapabilities'); track $index) {\n <tr>\n <td>{{ formatTraceScalar(traceField(candidate, 'rank')) }}</td>\n <td><strong>{{ formatTraceScalar(traceField(candidate, 'label')) }}</strong><code>{{ formatTraceScalar(traceField(candidate, 'capabilityKey')) }}</code></td>\n <td>{{ formatSimilarity(traceField(candidate, 'combinedScore')) }}</td>\n <td>{{ formatSimilarity(traceField(candidate, 'semanticScore')) }}</td>\n <td>{{ formatSimilarity(traceField(candidate, 'definitionScore')) }}</td>\n <td>{{ formatSimilarity(traceField(candidate, 'exampleScore')) }}</td>\n <td>{{ formatSimilarity(traceField(candidate, 'keywordScore')) }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n }\n\n @case('argument_extraction') {\n <div class=\"trace-card-grid\">\n @if(traceMapField(step.output, 'selectedCapability') ?? traceMapField(step.input, 'selectedCapability'); as capability) {\n <section class=\"trace-data-card trace-data-card-accent\">\n <h5>Selected capability</h5>\n <dl class=\"trace-field-list\">\n @for(entry of traceEntries(capability); track entry.key) {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n </dl>\n </section>\n }\n @if(traceMapField(step.input, 'tool'); as tool) {\n <section class=\"trace-data-card\">\n <h5>Allowlisted tool</h5>\n <dl class=\"trace-field-list\">\n @for(entry of traceEntries(tool); track entry.key) {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n </dl>\n </section>\n }\n </div>\n @if(traceMapField(step.output, 'extractedArguments'); as arguments) {\n <section class=\"trace-arguments\">\n <span class=\"trace-section-label\">Arguments extracted from the original query</span>\n <dl class=\"trace-argument-grid\">\n @for(entry of traceEntries(arguments); track entry.key) {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n </dl>\n </section>\n }\n @if(traceMapField(step.output, 'plannedToolCall'); as plannedCall) {\n <div class=\"trace-tool-call\">\n <span>Planned tool call</span>\n <strong>{{ formatTraceScalar(plannedCall['method']) }} \u00B7 {{ formatTraceScalar(plannedCall['toolName']) }}</strong>\n <pre>{{ formatTraceValue(plannedCall['arguments']) }}</pre>\n </div>\n }\n }\n\n @case('api_call') {\n <div class=\"trace-api-route\">\n <span class=\"trace-http-method\">{{ formatTraceScalar(traceField(step.input, 'method')) }}</span>\n <code>{{ formatTraceScalar(traceField(step.input, 'path')) }}</code>\n @if(getHttpStatus(step); as statusCode) {\n <span class=\"trace-http-status\" [class.success]=\"isHttpSuccessful(step)\" [class.error]=\"!isHttpSuccessful(step)\">\n HTTP {{ statusCode }} \u00B7 {{ isHttpSuccessful(step) ? 'Success' : 'Error' }}\n </span>\n }\n </div>\n <div class=\"trace-payload-grid\">\n @if(traceField(step.input, 'body'); as requestBody) {\n <section><span>Request payload</span><pre>{{ formatTraceValue(requestBody) }}</pre></section>\n }\n @if(traceField(step.output, 'response'); as apiResponse) {\n <section><span>API response</span><pre>{{ formatTraceValue(apiResponse) }}</pre></section>\n }\n </div>\n }\n\n @case('action') {\n <div class=\"trace-card-grid\">\n <section class=\"trace-data-card\">\n <h5>Workflow input</h5>\n <dl class=\"trace-field-list\">\n @for(entry of traceEntries(step.input); track entry.key) {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n </dl>\n </section>\n <section class=\"trace-data-card trace-data-card-accent\">\n <h5>Outcome</h5>\n <dl class=\"trace-field-list\">\n @for(entry of traceEntries(step.output); track entry.key) {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n </dl>\n </section>\n </div>\n }\n\n @case('response') {\n @if(traceField(step.output, 'answer'); as answer) {\n <div class=\"trace-query\"><span>Final grounded response</span><blockquote>{{ formatTraceScalar(answer) }}</blockquote></div>\n }\n <dl class=\"trace-argument-grid\">\n @for(entry of traceEntries(step.output); track entry.key) {\n @if(entry.key !== 'answer') {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n }\n </dl>\n }\n\n @default {\n <div class=\"trace-payload-grid\">\n @if(step.input !== undefined) {\n <section><span>Input</span><pre>{{ formatTraceValue(step.input) }}</pre></section>\n }\n @if(step.output !== undefined) {\n <section><span>Output</span><pre>{{ formatTraceValue(step.output) }}</pre></section>\n }\n </div>\n }\n }\n\n @if(step.error; as error) {\n <div class=\"trace-error\" [class.trace-warning]=\"step.category === 'api_call' && isHttpSuccessful(step)\">\n <strong class=\"trace-error-heading\">\n @if(step.category === 'api_call' && isHttpSuccessful(step)) {\n Post-response processing stopped\n } @else {\n Step stopped@if(error.statusCode) { \u00B7 HTTP {{ error.statusCode }} }\n }\n </strong>\n <strong class=\"trace-error-legacy\" aria-hidden=\"true\">\n Failed@if(error.statusCode) { \u2014 HTTP {{ error.statusCode }} }\n </strong>\n <span>{{ error.message }}</span>\n </div>\n }\n </div>\n }\n </section>\n </li>\n }\n </ol>\n </section>\n </div>\n }\n }\n } @else {\r\n @if(message.role === 'assistant') {\r\n <div\r\n class=\"assistant-markdown\"\r\n [innerHTML]=\"message.content | maxAssistantMarkdown\"\r\n ></div>\r\n } @else {\r\n <p>{{ message.content }}</p>\r\n }\r\n }\r\n @if(message.role === 'assistant' && message.suggestions?.length) {\n <div class=\"suggestions\">\r\n @for(suggestion of message.suggestions ?? []; track suggestion) {\r\n <button\r\n pButton\r\n type=\"button\"\r\n size=\"small\"\r\n text\r\n severity=\"secondary\"\n class=\"suggestion-chip\"\n [label]=\"suggestionLabel(suggestion)\"\n (click)=\"useSuggestion(suggestion)\"\n ></button>\r\n }\n </div>\n }\n </article>\n }\r\n @if(isSending()) {\r\n <article class=\"message assistant typing\" [attr.aria-label]=\"uiCopy.typingLabel\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </article>\r\n }\r\n </div>\r\n\r\n <form class=\"assistant-composer\" (ngSubmit)=\"sendMessage()\">\r\n <textarea\r\n pTextarea\r\n name=\"assistantPrompt\"\r\n rows=\"2\"\r\n [placeholder]=\"uiCopy.placeholder\"\r\n [ngModel]=\"input()\"\r\n (ngModelChange)=\"input.set($event)\"\r\n (keydown)=\"onComposerKeydown($event)\"\r\n >\r\n </textarea>\r\n <button\r\n pButton\r\n type=\"submit\"\r\n icon=\"pi pi-send\"\r\n aria-label=\"Send message\"\r\n [disabled]=\"!input().trim() || isSending()\"\r\n ></button>\r\n </form>\n </section>\n</aside>\n", styles: [":host{position:fixed;right:24px;bottom:24px;z-index:1100}.assistant-launcher{position:relative;width:64px!important;height:64px!important;min-height:64px!important;padding:7px;border:1px solid rgba(124,58,237,.24)!important;border-radius:22px!important;color:inherit!important;background:radial-gradient(circle at 28% 16%,rgba(255,255,255,.98),transparent 38%),linear-gradient(145deg,#fff7ed,#f5f3ff 52%,#fdf2f8)!important;box-shadow:0 0 0 1px #ffffffd9 inset,0 12px 30px #5b21b62e!important;overflow:visible;transition:border-color .22s ease,box-shadow .22s ease,transform .22s cubic-bezier(.2,.8,.2,1);z-index:2}.assistant-launcher:before,.assistant-launcher:after{content:\"\";position:absolute;top:50%;left:50%;width:22px;height:2px;border-radius:999px;background:#0f6cae;opacity:0;transform:translate(-50%,-50%) scale(.6) rotate(0);transition:opacity .18s ease,transform .22s ease;z-index:1}.assistant-launcher:before{transform:translate(-50%,-50%) scale(.6) rotate(45deg)}.assistant-launcher:after{transform:translate(-50%,-50%) scale(.6) rotate(-45deg)}.assistant-launcher:hover,.assistant-launcher:focus-visible{border-color:#ec489975!important;background:radial-gradient(circle at 30% 18%,#fff,transparent 38%),linear-gradient(145deg,#ffedd5,#ede9fe,#fce7f3)!important;box-shadow:0 0 0 3px #a855f71a,0 16px 34px #6d28d938!important;transform:translateY(-2px)}.assistant-launcher.is-open{background:#fff!important;box-shadow:0 0 0 2px #0078c838,0 14px 32px #0f172a2e!important;transform:translateY(0)}.assistant-launcher.is-open:before,.assistant-launcher.is-open:after{opacity:1}.assistant-launcher.is-open:before{transform:translate(-50%,-50%) scale(1) rotate(45deg)}.assistant-launcher.is-open:after{transform:translate(-50%,-50%) scale(1) rotate(-45deg)}.mascot{position:relative;display:inline-grid;width:54px;height:54px;place-items:center;transform-origin:50% 72%;transition:opacity .18s ease,transform .24s ease,filter .24s ease;z-index:0}.mascot-aura{position:absolute;inset:5px;border-radius:50%;background:conic-gradient(from 25deg,#f472b66b,#fbbf2452,#2dd4bf57,#8b5cf66b,#f472b66b);filter:blur(7px);animation:mascot-aura 3s ease-in-out infinite}.mascot-art{position:relative;width:100%;height:100%;overflow:visible;filter:drop-shadow(0 5px 5px rgba(2,75,116,.2));animation:mascot-idle 3.4s ease-in-out infinite}.mascot-shell{fill:#7c3aed}.mascot-ear-left{fill:#ec4899}.mascot-ear-right{fill:#f97316}.mascot-ear{transform-box:fill-box;transform-origin:center}.mascot-face{fill:#211747;stroke:#ffffff4d;stroke-width:1}.mascot-signal{fill:#facc15;stroke:#fff;stroke-width:1.5;transform-box:fill-box;transform-origin:center;animation:mascot-signal 2.2s ease-out infinite}.mascot-antenna,.mascot-sparkle,.mascot-shell-shine,.mascot-smile,.mascot-mark{fill:none;stroke-linecap:round;stroke-linejoin:round}.mascot-antenna{stroke:#6d28d9;stroke-width:2.5}.mascot-sparkle{stroke:#ec4899;stroke-width:2;transform-box:fill-box;transform-origin:center;animation:mascot-sparkle 3.4s ease-in-out infinite}.mascot-shell-shine{stroke:#e9d5ffd1;stroke-width:2}.mascot-eye{fill:#5eead4;transform-box:fill-box;transform-origin:center;animation:mascot-blink 5.2s ease-in-out infinite}.mascot-smile{stroke:#fbbf24;stroke-width:2}.mascot-mark{stroke:#ffffffd1;stroke-width:2.2}.assistant-launcher:hover .mascot-art,.assistant-launcher:focus-visible .mascot-art{animation:mascot-greet .7s cubic-bezier(.2,.8,.2,1)}.assistant-launcher:hover .mascot-ear-right,.assistant-launcher:focus-visible .mascot-ear-right{animation:mascot-ear-wave .7s ease-in-out}.assistant-launcher.is-open .mascot{filter:saturate(1.08);opacity:.24;transform:scale(.82) rotate(-5deg)}.mascot.large{width:52px;height:52px;flex:0 0 auto}@keyframes mascot-idle{0%,to{transform:translateY(0) rotate(0)}50%{transform:translateY(-2px) rotate(1deg)}}@keyframes mascot-greet{0%,to{transform:translateY(0) rotate(0)}35%{transform:translateY(-4px) rotate(-5deg)}65%{transform:translateY(-2px) rotate(4deg)}}@keyframes mascot-ear-wave{0%,to{transform:rotate(0) scaleY(1)}50%{transform:rotate(-12deg) scaleY(1.12)}}@keyframes mascot-blink{0%,45%,49%,to{transform:scaleY(1)}47%{transform:scaleY(.12)}}@keyframes mascot-signal{0%,55%,to{opacity:1;transform:scale(1)}70%{opacity:.55;transform:scale(1.42)}}@keyframes mascot-sparkle{0%,62%,to{opacity:0;transform:scale(.5) rotate(0)}72%{opacity:1;transform:scale(1) rotate(90deg)}84%{opacity:0;transform:scale(.7) rotate(150deg)}}@keyframes mascot-aura{0%,to{opacity:.45;transform:scale(.88)}50%{opacity:.85;transform:scale(1.08)}}.assistant-window{position:fixed;top:50%;right:24px;z-index:1;width:min(560px,100vw - 48px);height:min(80vh,820px);max-height:calc(100dvh - 48px);border:1px solid rgba(148,163,184,.24);border-radius:24px;background:#fffffff5;box-shadow:0 24px 70px #0f172a33,0 0 0 1px #ffffffad inset;overflow:hidden;opacity:0;visibility:hidden;pointer-events:none;translate:0 -50%;transform:translate3d(40px,28px,0) scale(.94);transform-origin:right bottom;transition:opacity .22s ease,transform .3s cubic-bezier(.4,0,1,1),visibility 0s linear .3s;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);will-change:opacity,transform}.assistant-window.is-open{opacity:1;visibility:visible;pointer-events:auto;transform:translateZ(0) scale(1);transition:opacity .28s ease,transform .46s cubic-bezier(.16,1,.3,1),visibility 0s}.assistant-panel{display:grid;grid-template-rows:auto 1fr auto;height:100%;min-height:0;border-radius:inherit;background:linear-gradient(180deg,#f8fafcd1,#f1f5f9f5),var(--background-color, #f8fafc);overflow:hidden}.assistant-header,.assistant-composer{display:flex;gap:12px;align-items:center;padding:16px 18px;background:#ffffffeb;border-bottom:1px solid rgba(226,232,240,.86)}.assistant-identity{display:grid;gap:5px;min-width:0}.assistant-name{margin:0;width:fit-content;color:var(--primary-color, #2563eb);font-family:Montserrat,Arial,sans-serif;font-size:23px;font-weight:700;letter-spacing:-.045em;line-height:.9}.assistant-subtitle{color:#64748b;font-size:10px;font-weight:600;letter-spacing:.1em;line-height:1.2;text-transform:uppercase}.sources span{color:#64748b;font-size:12px;font-weight:600}.assistant-header .header-action:first-of-type{margin-left:auto}.assistant-header .header-action{width:38px;height:38px;margin-left:0;color:#475569;background:transparent}.assistant-header .header-action:hover{background:#0f172a0f}.assistant-messages{min-height:0;overflow:auto;padding:18px;scrollbar-width:thin}.message{position:relative;width:fit-content;max-width:92%;margin-bottom:14px;padding:12px;border:1px solid var(--border-color, #e2e8f0);border-radius:16px;background:#fff;box-shadow:0 1px 2px #0f172a14,0 10px 24px #0f172a0f;-webkit-user-select:text;user-select:text}.message.assistant{width:100%;max-width:100%}.message.user{width:fit-content;max-width:88%;margin-left:auto;color:#fff;background:var(--primary-color, #2563eb);border-color:var(--primary-color, #2563eb);border-bottom-right-radius:6px}.message.user p{color:inherit}.message.error{border-color:var(--danger-color, #dc2626)}.message p{margin:0;line-height:1.45}.assistant-result{color:#1e293b}.assistant-cards{display:grid;gap:10px;margin-top:12px}.assistant-card{overflow:hidden;border:1px solid #dbeafe;border-radius:14px;background:linear-gradient(145deg,#f8fbff,#fff);box-shadow:0 8px 22px #0f172a0f}.assistant-card>header{display:grid;gap:2px;padding:11px 12px;border-bottom:1px solid #e2e8f0}.assistant-card>header strong{color:#0f172a;font-size:14px}.assistant-card>header span,.assistant-card>footer{color:#64748b;font-size:11px}.assistant-card-metrics{display:grid;grid-template-columns:repeat(3,minmax(0,1fr))}.assistant-card-metric{display:grid;min-width:0;gap:3px;padding:11px 9px;border-right:1px solid #e2e8f0}.assistant-card-metric:last-child{border-right:0}.assistant-card-metric span{color:#64748b;font-size:10px;font-weight:600;text-transform:uppercase}.assistant-card-metric strong{color:#1e293b;font-size:13px;overflow-wrap:anywhere}.assistant-card-metric[data-tone=positive] strong{color:#047857}.assistant-card-metric[data-tone=warning] strong{color:#b45309}.assistant-card>footer{padding:8px 12px;border-top:1px solid #e2e8f0;background:#f8fafcb8}:host ::ng-deep .assistant-markdown{line-height:1.45}:host ::ng-deep .assistant-markdown>:first-child{margin-top:0}:host ::ng-deep .assistant-markdown>:last-child{margin-bottom:0}:host ::ng-deep .assistant-markdown p{margin:0 0 8px}:host ::ng-deep .assistant-markdown ul,:host ::ng-deep .assistant-markdown ol{margin:8px 0;padding-left:22px}:host ::ng-deep .assistant-markdown li+li{margin-top:4px}:host ::ng-deep .assistant-markdown strong{font-weight:700}:host ::ng-deep .assistant-markdown code{padding:1px 4px;border-radius:4px;background:#e2e8f0;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.9em}:host ::ng-deep .assistant-markdown a{color:var(--primary-color, #2563eb);text-decoration:underline}.assistant-status{display:inline-flex;margin-top:8px;padding:3px 8px;border-radius:999px;color:#475569;background:#e2e8f0;font-size:11px;font-weight:600;text-transform:capitalize}.pending-action{display:grid;gap:9px;margin-top:12px;padding:12px;border:1px solid #f59e0b;border-radius:12px;background:#fffbeb}.pending-action-heading{display:flex;align-items:center;justify-content:space-between;color:#92400e}.pending-action-heading span{padding:2px 7px;border-radius:999px;color:#78350f;background:#fde68a;font-size:11px;font-weight:700}.pending-action small{color:#78716c}.pending-action-buttons{display:flex;gap:8px}.action-decision{color:#475569;font-weight:600}.assistant-sources{display:grid;gap:4px;margin-top:12px;padding-top:10px;border-top:1px solid #e2e8f0;color:#64748b;font-size:12px}.assistant-tables{display:grid;gap:10px;margin-top:12px}.assistant-table-section{display:grid;gap:6px}.assistant-table-section h3{margin:0;color:#0f172a;font-size:13px;font-weight:600}.assistant-table-wrap{overflow:auto;border:1px solid var(--border-color, #e2e8f0);border-radius:var(--border-radius, 6px)}.assistant-table-wrap table{width:100%;min-width:420px;border-collapse:collapse;background:#fff}.assistant-table-wrap th,.assistant-table-wrap td{padding:8px 10px;border-bottom:1px solid var(--border-color, #e2e8f0);color:#334155;font-size:12px;line-height:1.35;text-align:left;vertical-align:top}.assistant-table-wrap th{color:#475569;font-weight:600;background:#f8fafc}.assistant-table-wrap tr:last-child td{border-bottom:0}.assistant-table-actions{display:flex;flex-wrap:wrap;gap:6px;min-width:150px}.details-toggle{display:flex;width:fit-content;margin:10px 0 0 auto;padding:0;border:0;color:var(--primary-color, #2563eb);background:transparent;font:inherit;font-size:13px;font-weight:400;text-decoration:underline;text-underline-offset:3px;cursor:pointer}.details-toggle:hover{color:var(--primary-dark-color, #1d4ed8)}.details-toggle:focus-visible{outline:2px solid var(--primary-color, #2563eb);outline-offset:3px}.developer-details{display:grid;gap:12px;margin-top:12px;padding:12px;border:1px solid var(--border-color, #e2e8f0);border-radius:10px;background:#f8fafc}.developer-details-header h3,.developer-details-header p{margin:0}.developer-details-header h3{color:#0f172a;font-size:13px;font-weight:600}.developer-details-header p{margin-top:2px;color:#64748b;font-size:11px}.workflow-trace{display:grid;gap:0;margin:0;padding:0;list-style:none}.workflow-step{display:block;min-width:0}.workflow-category{color:#64748b;font-size:9px;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.workflow-meta{display:grid;flex:0 0 auto;gap:2px;justify-items:end;color:#64748b;font-size:9px}.workflow-status{padding:2px 5px;border-radius:999px;color:#334155;background:#e2e8f0;font-weight:600;text-transform:capitalize}.workflow-step[data-status=succeeded] .workflow-status{color:#166534;background:#dcfce7}.workflow-step[data-status=failed] .workflow-status{color:#991b1b;background:#fee2e2}.trace-json{display:grid;gap:4px;margin-top:8px}.trace-json>span{color:#64748b;font-size:10px;font-weight:600;text-transform:uppercase}.trace-json pre{max-height:280px;margin:0;padding:8px;overflow:auto;border-radius:6px;color:#dbeafe;background:#0f172a;font-size:10px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}.trace-error{display:grid;gap:3px;margin-top:8px;padding:8px;border-left:3px solid #dc2626;border-radius:4px;color:#991b1b;background:#fef2f2;font-size:11px}.developer-details-header{display:flex;gap:12px;align-items:flex-start;justify-content:space-between}.developer-eyebrow,.trace-section-label{color:#2563eb;font-size:9px;font-weight:600;letter-spacing:.08em;text-transform:uppercase}.trace-run-summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:7px}.trace-metric{display:grid;gap:3px;min-width:0;padding:9px;border:1px solid #e2e8f0;border-radius:8px;background:#fff}.trace-metric span,.trace-loop-policy span{color:#64748b;font-size:9px;font-weight:600}.trace-metric strong{color:#0f172a;font-size:13px}.trace-loop-policy{display:grid;grid-template-columns:minmax(0,1fr) auto;grid-column:1/-1;gap:5px 12px;align-items:center;padding:10px;border:1px solid #c7d2fe;border-radius:9px;background:#eef2ff}.trace-loop-policy div{display:grid;gap:2px}.trace-loop-policy strong{color:#312e81;font-size:11px}.trace-loop-policy p{grid-column:1/-1;margin:0;color:#475569;font-size:10px;line-height:1.45}.trace-no-loop{padding:3px 7px;border-radius:999px;color:#166534!important;background:#dcfce7;font-weight:600!important}.workflow-summary{margin:9px 0 0;color:#475569;font-size:10.5px;line-height:1.5}.trace-technology{display:grid;gap:6px;margin-top:10px;padding:8px;border:1px solid #dbeafe;border-radius:7px;background:#f8fbff}.trace-inline-facts{display:flex;flex-wrap:wrap;gap:5px}.trace-inline-facts>span{display:grid;gap:1px;max-width:100%;padding:4px 6px;border-radius:5px;color:#1e293b;background:#fff;font-size:9.5px;overflow-wrap:anywhere}.trace-inline-facts small{color:#64748b;font-size:8px;font-weight:600;text-transform:uppercase}.trace-control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:4px 8px;margin-top:8px;padding:8px;border-left:3px solid #8b5cf6;border-radius:5px;background:#f5f3ff}.trace-control>div{display:grid;gap:2px}.trace-control strong,.trace-control>span{color:#5b21b6;font-size:9.5px}.trace-control p{grid-column:1/-1;margin:0;color:#475569;font-size:9.5px;line-height:1.4}.trace-query,.trace-section,.trace-arguments,.trace-ranking-wrap{display:grid;gap:5px;margin-top:10px}.trace-query>span,.trace-transform>div>span,.trace-vector>span,.trace-payload-grid section>span,.trace-tool-call>span,.trace-supabase-call span,.trace-decision>span{color:#64748b;font-size:9px;font-weight:600;text-transform:uppercase}.trace-query blockquote{margin:0;padding:8px 10px;border-left:3px solid #2563eb;border-radius:5px;color:#172554;background:#eff6ff;font-size:11px;font-weight:600;line-height:1.45}.trace-query-compact blockquote{border-color:#8b5cf6;color:#4c1d95;background:#f5f3ff}.trace-card-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px;margin-top:9px}.trace-data-card{min-width:0;padding:8px;border:1px solid #e2e8f0;border-radius:7px;background:#f8fafc}.trace-data-card-accent{border-color:#bfdbfe;background:#eff6ff}.trace-data-card h5{margin:0 0 5px;color:#334155;font-size:10px}.trace-field-list,.trace-argument-grid{display:grid;gap:4px;margin:0}.trace-field-list>div{display:grid;grid-template-columns:minmax(70px,.7fr) minmax(0,1.3fr);gap:6px}.trace-field-list dt,.trace-argument-grid dt{color:#64748b;font-size:8.5px}.trace-field-list dd,.trace-argument-grid dd{margin:0;color:#1e293b;font-size:9px;font-weight:600;overflow-wrap:anywhere}.trace-transform{display:grid;grid-template-columns:minmax(0,1fr) 18px minmax(0,1fr);gap:6px;align-items:stretch;margin-top:10px}.trace-transform>div{display:grid;gap:5px;padding:9px;border:1px solid #e2e8f0;border-radius:7px;background:#f8fafc}.trace-transform .trace-transform-result{border-color:#bfdbfe;background:#eff6ff}.trace-transform p{margin:0;color:#1e293b;font-size:10px;font-weight:600;line-height:1.4}.trace-transform-arrow{display:grid;place-items:center;color:#2563eb;font-size:16px;font-weight:600}.trace-entity-list{display:flex;flex-wrap:wrap;gap:5px}.trace-entity-list>span{display:flex;gap:5px;align-items:center;padding:4px 7px;border:1px solid #fed7aa;border-radius:999px;color:#9a3412;background:#fff7ed;font-size:9.5px}.trace-entity-list small{color:#c2410c;font-size:7.5px;font-weight:600;text-transform:uppercase}.trace-stat-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin-top:8px}.trace-stat-grid>div{display:grid;gap:2px;padding:7px;border:1px solid #e2e8f0;border-radius:6px}.trace-stat-grid span{color:#64748b;font-size:8.5px}.trace-stat-grid strong{color:#0f172a;font-size:10px;overflow-wrap:anywhere}.trace-vector{display:grid;gap:4px;margin-top:7px}.trace-vector code{padding:7px;overflow:auto;border-radius:5px;color:#c4b5fd;background:#1e1b4b;font-size:8.5px;white-space:nowrap}.trace-supabase-call{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin-top:10px;padding:9px;border:1px solid #a7f3d0;border-radius:7px;background:#ecfdf5}.trace-supabase-call>div{display:grid;gap:2px}.trace-supabase-call strong{color:#065f46;font-size:10px;overflow-wrap:anywhere}.trace-table-list{grid-column:1/-1}.trace-table-list>div{display:flex;flex-wrap:wrap;gap:4px}.trace-table-list code{padding:3px 5px;border-radius:4px;color:#065f46;background:#d1fae5;font-size:8.5px}.trace-decision{display:grid;gap:4px;margin-top:7px;padding:9px;border-left:3px solid #16a34a;border-radius:5px;background:#f0fdf4}.trace-decision strong{color:#14532d;font-size:11px}.trace-decision div{display:flex;flex-wrap:wrap;gap:8px;color:#166534;font-size:8.5px}.trace-ranking-wrap{overflow:auto}.trace-ranking{width:100%;min-width:610px;border-collapse:collapse;font-size:8.5px}.trace-ranking th,.trace-ranking td{padding:6px;border-bottom:1px solid #e2e8f0;color:#475569;text-align:right;vertical-align:top}.trace-ranking th{color:#64748b;background:#f8fafc;font-size:8px;text-transform:uppercase}.trace-ranking th:nth-child(2),.trace-ranking td:nth-child(2){text-align:left}.trace-ranking td:nth-child(2){display:grid;gap:2px;min-width:145px}.trace-ranking strong{color:#1e293b;font-size:9px}.trace-ranking code{color:#64748b;font-size:7.5px}.trace-argument-grid{grid-template-columns:repeat(2,minmax(0,1fr));margin-top:3px}.trace-argument-grid>div{min-width:0;padding:7px;border:1px solid #e2e8f0;border-radius:6px;background:#f8fafc}.trace-tool-call,.trace-api-route{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:8px;padding:8px;border-radius:6px;color:#dbeafe;background:#0f172a}.trace-tool-call>span{width:100%;color:#94a3b8}.trace-tool-call strong{color:#bfdbfe;font-size:10px}.trace-tool-call pre,.trace-payload-grid pre{width:100%;max-height:220px;margin:0;overflow:auto;color:#dbeafe;font-size:9px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}.trace-api-route>code{flex:1 1 180px;color:#e2e8f0;font-size:9px;overflow-wrap:anywhere}.trace-http-method,.trace-http-status{padding:3px 6px;border-radius:4px;color:#fff;background:#2563eb;font-size:8.5px;font-weight:600}.trace-http-status.success{color:#bbf7d0;background:#166534}.trace-http-status.error{color:#fecaca;background:#991b1b}.trace-payload-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin-top:7px}.trace-payload-grid section{display:grid;gap:5px;min-width:0;padding:8px;border:1px solid #334155;border-radius:6px;background:#0f172a}.trace-payload-grid section>span{color:#94a3b8}.trace-error.trace-warning{border-left-color:#f59e0b;color:#92400e;background:#fffbeb}.trace-error-legacy{display:none}.trace-error-heading{font-size:10px}.details-dialog-backdrop{position:fixed;inset:0;z-index:20;display:grid;padding:14px;place-items:center;background:#0f172a7a;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}.details-dialog-backdrop .developer-details{display:block;width:100%;max-height:100%;margin:0;padding:0 14px 14px;overflow:auto;overscroll-behavior:contain;border:0;border-radius:14px;background:#fff;box-shadow:0 24px 70px #0f172a4d}.details-dialog-backdrop .developer-details-header{position:sticky;top:0;z-index:3;margin:0 -14px 14px;padding:14px;border-bottom:1px solid #e2e8f0;background:#fffffff7;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.details-dialog-actions{display:flex;flex:0 0 auto;gap:7px;align-items:center}.details-dialog-close{display:grid;width:30px;height:30px;padding:0;place-items:center;border:1px solid #e2e8f0;border-radius:8px;color:#475569;background:#fff;cursor:pointer}.details-dialog-close:hover{color:#0f172a;background:#f1f5f9}.details-dialog-close:focus-visible{outline:2px solid #2563eb;outline-offset:2px}.details-dialog-backdrop .trace-run-summary{margin-bottom:6px;padding-bottom:14px;border-bottom:1px solid #cbd5e1}.details-dialog-backdrop .trace-metric{padding:0 9px;border:0;border-right:1px solid #e2e8f0;border-radius:0;background:transparent}.details-dialog-backdrop .trace-metric:nth-child(4){border-right:0}.details-dialog-backdrop .trace-loop-policy{margin-top:5px;padding:9px 0 0;border:0;border-top:1px solid #e2e8f0;border-radius:0;background:transparent}.details-dialog-backdrop .workflow-trace{display:block}.details-dialog-backdrop .workflow-step{display:block;border-bottom:1px solid #cbd5e1}.workflow-section{display:block}.workflow-step-toggle{display:flex;gap:14px;align-items:center;justify-content:space-between;width:100%;padding:14px 2px;border:0;color:inherit;background:transparent;font:inherit;text-align:left;cursor:pointer}.workflow-step-toggle:hover{background:#f8fafc}.workflow-step-toggle:focus-visible{outline:2px solid #2563eb;outline-offset:-2px}.workflow-step-toggle>div:first-child{display:grid;min-width:0;gap:2px}.workflow-step-toggle h4{margin:0;color:#0f172a;font-size:12px;font-weight:600;line-height:1.4;text-decoration:underline;text-decoration-color:#94a3b8;text-underline-offset:4px}.workflow-step-number{display:block;color:#2563eb;font-size:10px;font-weight:600}.workflow-expand-icon{color:#64748b;font-size:10px}.workflow-step-content{padding:0 2px 16px}.details-dialog-backdrop .workflow-summary{margin-top:0;padding-bottom:10px;border-bottom:1px solid #e2e8f0}.details-dialog-backdrop .trace-technology,.details-dialog-backdrop .trace-control{padding:9px 0;border:0;border-bottom:1px solid #e2e8f0;border-radius:0;background:transparent}.details-dialog-backdrop .trace-control{border-left:0}.details-dialog-backdrop .trace-inline-facts>span{padding:0 8px 0 0;border-right:1px solid #e2e8f0;border-radius:0;background:transparent}.details-dialog-backdrop :is(h3,h4,h5,strong,.workflow-status,.trace-section-label,.developer-eyebrow){font-weight:600!important}@media (max-width: 520px){.trace-run-summary,.trace-card-grid,.trace-stat-grid,.trace-payload-grid,.trace-argument-grid,.trace-transform{grid-template-columns:1fr}.trace-transform-arrow{transform:rotate(90deg)}}.suggestions{display:grid;gap:0;margin-top:12px}.suggestion-chip{justify-content:flex-start;width:100%;padding:6px 0;border:1px solid transparent;border-radius:999px;color:var(--primary-color, #2563eb);background:transparent;font-weight:600;box-shadow:none;text-align:left;transition:padding .16s ease,border-color .16s ease,background .16s ease,box-shadow .16s ease}.suggestion-chip:hover{padding:6px 12px;border-color:rgba(var(--primary-color-rgb, 37, 99, 235),.28);color:var(--primary-color, #2563eb);background:rgba(var(--primary-color-rgb, 37, 99, 235),.08);box-shadow:0 4px 14px #0f172a0f}.response-details{display:grid;gap:12px;margin-top:12px}.response-section{display:grid;gap:8px;padding-bottom:12px;border-bottom:1px solid var(--border-color, #e2e8f0)}.response-section:last-child{padding-bottom:0;border-bottom:0}.response-section h3{margin:0;color:#0f172a;font-size:13px;font-weight:600}.quoted{padding:8px 10px;border-left:5px solid var(--primary-color, #2563eb);border-radius:var(--border-radius, 6px);color:#1e293b;background:#f8fafc}.detail-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:0}.detail-grid div{min-width:0;padding:8px;border-radius:var(--border-radius, 6px);background:#f8fafc}.detail-grid dt{margin-bottom:3px;color:#64748b;font-size:11px;font-weight:600;text-transform:uppercase}.detail-grid dd{margin:0;color:#334155;font-size:13px;font-weight:600;line-height:1.35;overflow-wrap:anywhere}.detail-grid dd span{display:block;margin-top:2px;color:#64748b;font-size:11px}.api-call-summary,.json-block{display:grid;gap:6px;padding:8px;border-radius:var(--border-radius, 6px);background:#f8fafc}.api-call-summary .detail-label,.json-block .detail-label{color:#64748b;font-size:11px;font-weight:600;text-transform:uppercase}.api-call-summary code{color:var(--secondary-color, #0f172a);font-weight:600;white-space:normal;word-break:break-word}.json-block pre{max-height:220px;margin:0;overflow:auto;color:#0f172a;font-size:12px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}.typing{display:flex;width:fit-content;max-width:fit-content;gap:5px;align-items:center;padding:4px 0;border:0;background:transparent;box-shadow:none}.typing span{width:7px;height:7px;border-radius:50%;background:var(--primary-color, #2563eb);animation:typing-dot .85s infinite ease-in-out}.typing span:nth-child(2){animation-delay:.15s}.assistant-composer{padding:14px 16px 16px;border-top:1px solid rgba(226,232,240,.9);border-bottom:0;box-shadow:0 -10px 30px #0f172a0a}.assistant-composer textarea{flex:1;min-height:46px;max-height:120px;border-radius:18px;background:#fff;resize:vertical}.assistant-composer button[type=submit]{width:44px;height:44px;border-radius:16px}@keyframes typing-dot{0%,80%,to{opacity:.35;transform:translateY(0)}40%{opacity:1;transform:translateY(-4px)}}@keyframes pulse{to{opacity:.35;transform:translateY(-2px)}}@media (prefers-reduced-motion: reduce){.mascot-aura,.mascot-art,.mascot-eye,.mascot-signal,.mascot-sparkle,.assistant-launcher:hover .mascot-art,.assistant-launcher:focus-visible .mascot-art,.assistant-launcher:hover .mascot-ear-right,.assistant-launcher:focus-visible .mascot-ear-right{animation:none}.assistant-launcher,.mascot{transition-duration:.01ms}.assistant-window,.assistant-window.is-open{transition:none}}@media (max-width: 640px){:host{right:16px;bottom:16px}.assistant-window{right:12px;width:calc(100vw - 24px);height:min(84vh,100dvh - 24px);max-height:calc(100dvh - 24px);border-radius:20px}.assistant-messages{padding:14px}.detail-grid,.assistant-card-metrics{grid-template-columns:1fr}.assistant-card-metric{border-right:0;border-bottom:1px solid #e2e8f0}.assistant-card-metric:last-child{border-bottom:0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i3.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i4.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i5.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "pipe", type: i2.DatePipe, name: "date" }, { kind: "pipe", type: MaxAssistantMarkdownPipe, name: "maxAssistantMarkdown" }] });
|
|
285
782
|
}
|
|
286
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
783
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: MaxAssistantComponent, decorators: [{
|
|
287
784
|
type: Component,
|
|
288
|
-
args: [{ selector: 'max-assistant', imports: [CommonModule, FormsModule, ButtonModule, DrawerModule, TextareaModule, TooltipModule], template: "<button\n pButton\n type=\"button\"\n class=\"assistant-launcher\"\n [attr.aria-label]=\"uiCopy.launcherLabel\"\n [pTooltip]=\"uiCopy.openTooltip\"\n tooltipPosition=\"left\"\n (click)=\"openPanel()\"\n>\n <span class=\"mascot\" aria-hidden=\"true\"></span>\n</button>\n\n<p-drawer\n [visible]=\"panelVisible()\"\n (visibleChange)=\"panelVisible.set($event)\"\n position=\"right\"\n styleClass=\"assistant-drawer\"\n [modal]=\"false\"\n [blockScroll]=\"false\"\n [showCloseIcon]=\"false\"\n>\n <section class=\"assistant-panel\" [attr.aria-label]=\"uiCopy.title\">\n <header class=\"assistant-header\">\n <span class=\"mascot large\" aria-hidden=\"true\"></span>\n <div>\n <h2>{{ uiCopy.title }}</h2>\n <span>{{ uiCopy.subtitle }}</span>\n </div>\n <button\n pButton\n type=\"button\"\n icon=\"pi pi-times\"\n text\n rounded\n severity=\"secondary\"\n [attr.aria-label]=\"uiCopy.closeLabel\"\n (click)=\"closePanel()\"\n ></button>\n </header>\n\n <div #messagesViewport class=\"assistant-messages\">\n @for(message of messages(); track message.id) {\n <article class=\"message {{ message.role }}\" [class.error]=\"message.isError\">\n @if(message.displayResponse; as responseDetails) {\n <p class=\"assistant-result\">{{ responseDetails.agentInferredResponse || message.content }}</p>\n <button\n type=\"button\"\n class=\"inference-toggle\"\n [attr.aria-expanded]=\"isInferenceDetailsVisible(message.id)\"\n (click)=\"toggleInferenceDetails(message.id)\"\n >\n {{ isInferenceDetailsVisible(message.id) ? 'Hide Details' : 'Show Details' }}\n </button>\n\n @if(isInferenceDetailsVisible(message.id)) {\n <div class=\"response-details\">\n @if(responseDetails.userQuery) {\n <section class=\"response-section\">\n <h3>User Query</h3>\n <p class=\"quoted\">\"{{ responseDetails.userQuery }}\"</p>\n </section>\n }\n\n @if(responseDetails.agentExtractedIntent; as intent) {\n <section class=\"response-section\">\n <h3>Agent Extracted Intent</h3>\n <dl class=\"detail-grid\">\n @if(intent.project || intent.projectKey) {\n <div>\n <dt>Project</dt>\n <dd>\n {{ intent.project || intent.projectKey }}\n @if(intent.projectKey) {\n <span>{{ intent.projectKey }}</span>\n }\n </dd>\n </div>\n }\n @if(intent.module) {\n <div>\n <dt>Module</dt>\n <dd>{{ intent.module }}</dd>\n </div>\n }\n @if(intent.intentLabel) {\n <div>\n <dt>Intent Label</dt>\n <dd>{{ intent.intentLabel }}</dd>\n </div>\n }\n @if(intent.intent) {\n <div>\n <dt>Intent</dt>\n <dd>{{ intent.intent }}</dd>\n </div>\n }\n @if(intent.confidence !== undefined) {\n <div>\n <dt>Confidence</dt>\n <dd>{{ intent.confidence | percent: '1.0-0' }}</dd>\n </div>\n }\n </dl>\n\n @if(message.formattedParameters) {\n <div class=\"json-block\">\n <strong>Parameters</strong>\n <pre>{{ message.formattedParameters }}</pre>\n </div>\n }\n\n @if(intent.api?.url || intent.api?.method) {\n <div class=\"api-call-summary\">\n <strong>API Called</strong>\n <code>{{ intent.api?.method || 'GET' }} {{ intent.api?.url || 'URL not provided' }}</code>\n </div>\n }\n\n @if(message.formattedApiBody) {\n <div class=\"json-block\">\n <strong>API Body</strong>\n <pre>{{ message.formattedApiBody }}</pre>\n </div>\n }\n </section>\n }\n\n @if(message.formattedApiResponse) {\n <section class=\"response-section\">\n <h3>API Response</h3>\n <div class=\"json-block\">\n <strong>JSON</strong>\n <pre>{{ message.formattedApiResponse }}</pre>\n </div>\n </section>\n }\n </div>\n }\n } @else {\n <p>{{ message.content }}</p>\n }\n @if(message.role === 'assistant' && message.suggestions?.length) {\n <div class=\"suggestions\">\n @for(suggestion of message.suggestions ?? []; track suggestion) {\n <button\n pButton\n type=\"button\"\n size=\"small\"\n text\n severity=\"secondary\"\n class=\"suggestion-chip\"\n [label]=\"suggestion\"\n (click)=\"useSuggestion(suggestion)\"\n ></button>\n }\n </div>\n }\n </article>\n }\n @if(isSending()) {\n <article class=\"message assistant typing\" [attr.aria-label]=\"uiCopy.typingLabel\">\n <span></span>\n <span></span>\n <span></span>\n </article>\n }\n </div>\n\n <form class=\"assistant-composer\" (ngSubmit)=\"sendMessage()\">\n <textarea\n pTextarea\n name=\"assistantPrompt\"\n rows=\"2\"\n [placeholder]=\"uiCopy.placeholder\"\n [ngModel]=\"input()\"\n (ngModelChange)=\"input.set($event)\"\n (keydown)=\"onComposerKeydown($event)\"\n >\n </textarea>\n <button\n pButton\n type=\"submit\"\n icon=\"pi pi-send\"\n aria-label=\"Send message\"\n [disabled]=\"!input().trim() || isSending()\"\n ></button>\n </form>\n </section>\n</p-drawer>\n", styles: [":host{position:fixed;right:24px;bottom:24px;z-index:1100}.assistant-launcher{width:70px!important;height:56px!important;min-height:60px!important;padding:0 5px;border:0!important;border-radius:18px!important;color:inherit!important;background:#d7edff!important;box-shadow:0 0 0 1px #0078c824,0 10px 24px #0f172a24!important;overflow:visible}.assistant-launcher:hover,.assistant-launcher:focus-visible{background:#fff!important;box-shadow:0 0 0 2px #0078c838,0 12px 28px #0f172a29!important}.mascot{position:relative;display:grid;place-items:center;width:64px;height:56px;background:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"120\" height=\"96\" viewBox=\"14 10 92 82\" role=\"img\" aria-label=\"Max smiling cloud AI mascot\">%0A <defs>%0A <filter id=\"softShadow\" x=\"-15%\" y=\"-15%\" width=\"130%\" height=\"140%\">%0A <feDropShadow dx=\"0\" dy=\"5\" stdDeviation=\"5\" flood-color=\"%230078C8\" flood-opacity=\"0.16\"/>%0A </filter>%0A </defs>%0A%0A <g fill=\"%230078C8\" opacity=\"0.28\">%0A <circle cx=\"34\" cy=\"57\" r=\"24\"/>%0A <circle cx=\"58\" cy=\"39\" r=\"31\"/>%0A <circle cx=\"86\" cy=\"58\" r=\"25\"/>%0A <rect x=\"28\" y=\"55\" width=\"68\" height=\"27\" rx=\"13.5\"/>%0A </g>%0A%0A <g fill=\"%230078C8\" opacity=\"0.68\" filter=\"url(%23softShadow)\">%0A <circle cx=\"35\" cy=\"59\" r=\"20\"/>%0A <circle cx=\"59\" cy=\"43\" r=\"27\"/>%0A <circle cx=\"84\" cy=\"60\" r=\"21\"/>%0A <rect x=\"31\" y=\"57\" width=\"64\" height=\"24\" rx=\"12\"/>%0A </g>%0A%0A <g fill=\"%23FFD54A\">%0A <path d=\"M46 48l3.4 6.8 6.8 3.4-6.8 3.4L46 68.4l-3.4-6.8-6.8-3.4 6.8-3.4z\"/>%0A <path d=\"M74 48l3.4 6.8 6.8 3.4-6.8 3.4-3.4 6.8-3.4-6.8-6.8-3.4 6.8-3.4z\"/>%0A <path d=\"M88 28l2 4 4 2-4 2-2 4-2-4-4-2 4-2z\"/>%0A </g>%0A%0A <path%0A d=\"M49 72c6 7 20 7 26 0\"%0A fill=\"none\"%0A stroke=\"%23FFD54A\"%0A stroke-width=\"4\"%0A stroke-linecap=\"round\"%0A />%0A</svg>%0A') center/contain no-repeat}.mascot.large{width:58px;height:46px}::ng-deep .assistant-drawer{width:min(560px,100vw)!important}::ng-deep .assistant-drawer .p-drawer-content{padding:0}::ng-deep .assistant-drawer .p-drawer-header{display:none}.assistant-panel{display:grid;grid-template-rows:auto 1fr auto;height:100%;min-height:0;background:var(--background-color, #f8fafc)}.assistant-header,.assistant-composer{display:flex;gap:12px;align-items:center;padding:16px;background:#fff;border-bottom:1px solid var(--border-color, #e2e8f0)}.assistant-header h2{margin:0;font-size:20px}.assistant-header span,.sources span{color:#64748b;font-size:12px;font-weight:700}.assistant-header button{margin-left:auto}.assistant-messages{min-height:0;overflow:auto;padding:18px}.message{width:fit-content;max-width:92%;margin-bottom:14px;padding:12px;border:1px solid var(--border-color, #e2e8f0);border-radius:var(--medium-border-radius, 8px);background:#fff;box-shadow:0 6px 18px #0f172a0f}.message.assistant{width:100%;max-width:100%}.message.user{width:fit-content;max-width:88%;margin-left:auto;color:#fff;background:var(--primary-color, #2563eb);border-color:var(--primary-color, #2563eb)}.message.error{border-color:var(--danger-color, #dc2626)}.message p{margin:0;line-height:1.45}.assistant-result{color:#1e293b}.inference-toggle{display:flex;width:fit-content;margin:10px 0 0 auto;padding:0;border:0;color:var(--primary-color, #2563eb);background:transparent;font:inherit;font-size:13px;font-weight:400;text-decoration:underline;text-underline-offset:3px;cursor:pointer}.inference-toggle:hover{color:var(--primary-dark-color, #1d4ed8)}.inference-toggle:focus-visible{outline:2px solid var(--primary-color, #2563eb);outline-offset:3px}.suggestions{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.suggestion-chip{border:1px solid rgba(var(--primary-color-rgb, 37, 99, 235),.28);color:var(--primary-dark-color, #1d4ed8);background:rgba(var(--primary-color-rgb, 37, 99, 235),.08);font-weight:700;box-shadow:0 4px 14px #0f172a0f;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.suggestion-chip:hover{border-color:rgba(var(--primary-color-rgb, 37, 99, 235),.42);color:var(--primary-dark-color, #1d4ed8);background:rgba(var(--primary-color-rgb, 37, 99, 235),.14)}.response-details{display:grid;gap:12px;margin-top:12px}.response-section{display:grid;gap:8px;padding-bottom:12px;border-bottom:1px solid var(--border-color, #e2e8f0)}.response-section:last-child{padding-bottom:0;border-bottom:0}.response-section h3{margin:0;color:#0f172a;font-size:13px;font-weight:800}.quoted{padding:8px 10px;border-left:5px solid var(--primary-color, #2563eb);border-radius:var(--border-radius, 6px);color:#1e293b;background:#f8fafc}.detail-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:0}.detail-grid div{min-width:0;padding:8px;border-radius:var(--border-radius, 6px);background:#f8fafc}.detail-grid dt{margin-bottom:3px;color:#64748b;font-size:11px;font-weight:800;text-transform:uppercase}.detail-grid dd{margin:0;color:#334155;font-size:13px;font-weight:700;line-height:1.35;overflow-wrap:anywhere}.detail-grid dd span{display:block;margin-top:2px;color:#64748b;font-size:11px}.api-call-summary,.json-block{display:grid;gap:6px;padding:8px;border-radius:var(--border-radius, 6px);background:#f8fafc}.api-call-summary strong,.json-block strong{color:#64748b;font-size:11px;font-weight:800;text-transform:uppercase}.api-call-summary code{color:var(--secondary-color, #0f172a);font-weight:800;white-space:normal;word-break:break-word}.json-block pre{max-height:220px;margin:0;overflow:auto;color:#0f172a;font-size:12px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}.typing{display:flex;width:fit-content;max-width:fit-content;gap:5px;align-items:center;padding:4px 0;border:0;background:transparent;box-shadow:none}.typing span{width:7px;height:7px;border-radius:50%;background:var(--primary-color, #2563eb);animation:typing-dot .85s infinite ease-in-out}.typing span:nth-child(2){animation-delay:.15s}.assistant-composer{border-top:1px solid var(--border-color, #e2e8f0);border-bottom:0}.assistant-composer textarea{flex:1;min-height:46px;max-height:120px;resize:vertical}@keyframes typing-dot{0%,80%,to{opacity:.35;transform:translateY(0)}40%{opacity:1;transform:translateY(-4px)}}@keyframes pulse{to{opacity:.35;transform:translateY(-2px)}}@media(max-width:640px){:host{right:16px;bottom:16px}.assistant-launcher{width:62px!important;min-width:62px!important;height:54px!important;min-height:54px!important}.assistant-messages{padding:14px}.detail-grid{grid-template-columns:1fr}}\n"] }]
|
|
289
|
-
|
|
785
|
+
args: [{ selector: 'max-assistant', imports: [
|
|
786
|
+
CommonModule,
|
|
787
|
+
FormsModule,
|
|
788
|
+
ButtonModule,
|
|
789
|
+
TextareaModule,
|
|
790
|
+
TooltipModule,
|
|
791
|
+
MaxAssistantMarkdownPipe
|
|
792
|
+
], template: "<ng-template #mascotTemplate let-large=\"large\">\r\n <span class=\"mascot\" [class.large]=\"large\" aria-hidden=\"true\">\r\n <span class=\"mascot-aura\"></span>\r\n <svg class=\"mascot-art\" viewBox=\"0 0 64 64\" focusable=\"false\">\r\n <path class=\"mascot-sparkle\" d=\"M54 9v6M51 12h6\" />\r\n <path class=\"mascot-antenna\" d=\"M32 14V9\" />\r\n <circle class=\"mascot-signal\" cx=\"32\" cy=\"7\" r=\"3\" />\r\n <rect class=\"mascot-ear mascot-ear-left\" x=\"5\" y=\"28\" width=\"7\" height=\"15\" rx=\"3.5\" />\r\n <rect class=\"mascot-ear mascot-ear-right\" x=\"52\" y=\"28\" width=\"7\" height=\"15\" rx=\"3.5\" />\r\n <rect class=\"mascot-shell\" x=\"8\" y=\"14\" width=\"48\" height=\"42\" rx=\"18\" />\r\n <path class=\"mascot-shell-shine\" d=\"M17 24c4-7 12-9 20-8\" />\r\n <rect class=\"mascot-face\" x=\"14\" y=\"21\" width=\"36\" height=\"26\" rx=\"12\" />\r\n <g class=\"mascot-eyes\">\r\n <rect class=\"mascot-eye\" x=\"21\" y=\"29\" width=\"5\" height=\"7\" rx=\"2.5\" />\r\n <rect class=\"mascot-eye\" x=\"38\" y=\"29\" width=\"5\" height=\"7\" rx=\"2.5\" />\r\n </g>\r\n <path class=\"mascot-smile\" d=\"M25 39c2 2 4.5 3 7 3s5-1 7-3\" />\r\n <path class=\"mascot-mark\" d=\"M26 18l6 5 6-5\" />\r\n </svg>\r\n </span>\r\n</ng-template>\r\n\r\n<button\n #launcher\n pButton\n type=\"button\"\n class=\"assistant-launcher\"\r\n [class.is-open]=\"panelVisible()\"\r\n [attr.aria-label]=\"launcherLabel\"\r\n [attr.aria-pressed]=\"panelVisible()\"\r\n [pTooltip]=\"launcherLabel\"\r\n tooltipPosition=\"left\"\r\n (click)=\"togglePanel()\"\r\n>\r\n <ng-container\r\n [ngTemplateOutlet]=\"mascotTemplate\"\r\n [ngTemplateOutletContext]=\"{ large: false }\"\r\n ></ng-container>\n</button>\n\n<aside\n class=\"assistant-window\"\n [class.is-open]=\"panelVisible()\"\n [attr.aria-hidden]=\"!panelVisible()\"\n [attr.inert]=\"panelVisible() ? null : ''\"\n [attr.aria-label]=\"uiCopy.title\"\n role=\"complementary\"\n>\n <section class=\"assistant-panel\">\n <header class=\"assistant-header\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"mascotTemplate\"\r\n [ngTemplateOutletContext]=\"{ large: true }\"\r\n ></ng-container>\r\n <div class=\"assistant-identity\">\r\n <h2 class=\"assistant-name\">{{ uiCopy.title }}</h2>\r\n <span class=\"assistant-subtitle\">{{ uiCopy.subtitle }}</span>\r\n </div>\r\n <button\r\n pButton\r\n type=\"button\"\r\n icon=\"pi pi-refresh\"\r\n text\r\n rounded\r\n severity=\"secondary\"\r\n class=\"header-action\"\r\n [attr.aria-label]=\"uiCopy.clearLabel\"\r\n [pTooltip]=\"uiCopy.clearTooltip\"\r\n tooltipPosition=\"bottom\"\r\n (click)=\"clearChatSession()\"\r\n ></button>\r\n <button\r\n pButton\r\n type=\"button\"\r\n icon=\"pi pi-times\"\r\n text\r\n rounded\r\n severity=\"secondary\"\r\n class=\"header-action\"\r\n [attr.aria-label]=\"uiCopy.closeLabel\"\r\n (click)=\"closePanel()\"\r\n ></button>\r\n </header>\r\n\r\n <div #messagesViewport class=\"assistant-messages\">\r\n @for(message of messages(); track message.id) {\r\n <article class=\"message {{ message.role }}\" [class.error]=\"message.isError\">\r\n @if(message.displayResponse; as responseDetails) {\r\n <div\n class=\"assistant-result assistant-markdown\"\n [innerHTML]=\"(responseDetails.markdown || responseDetails.agentInferredResponse || message.content) | maxAssistantMarkdown\"\n ></div>\n @if(message.cards?.length) {\n <div class=\"assistant-cards\">\n @for(card of message.cards ?? []; track $index) {\n <section class=\"assistant-card\">\n <header>\n <strong>{{ card.title }}</strong>\n @if(card.subtitle) {\n <span>{{ card.subtitle }}</span>\n }\n </header>\n <div class=\"assistant-card-metrics\">\n @for(metric of card.metrics; track metric.label) {\n <div class=\"assistant-card-metric\" [attr.data-tone]=\"metric.tone || 'default'\">\n <span>{{ metric.label }}</span>\n <strong>{{ metric.value }}</strong>\n </div>\n }\n </div>\n @if(card.footer) {\n <footer>{{ card.footer }}</footer>\n }\n </section>\n }\n </div>\n }\n @if(message.status) {\n <span class=\"assistant-status\">{{ message.status.replaceAll('_', ' ') }}</span>\r\n }\r\n @if(message.pendingAction; as pendingAction) {\r\n <section class=\"pending-action\" aria-label=\"Cortex action confirmation\">\r\n <div class=\"pending-action-heading\">\r\n <strong>Confirmation required</strong>\r\n <span>{{ pendingAction.method }}</span>\r\n </div>\r\n <p>{{ pendingAction.summary }}</p>\r\n <small>Expires {{ pendingAction.expiresAt | date: 'mediumTime' }}</small>\r\n @if(message.actionDecision; as actionDecision) {\r\n <p class=\"action-decision\">\r\n {{ actionDecision === 'approve' ? 'Approval sent' : 'Rejection sent' }}\r\n </p>\r\n } @else {\r\n <div class=\"pending-action-buttons\">\r\n <button\r\n pButton\r\n type=\"button\"\r\n size=\"small\"\r\n severity=\"success\"\r\n label=\"Approve\"\r\n [disabled]=\"isSending()\"\r\n (click)=\"resolvePendingAction(message, 'approve')\"\r\n ></button>\r\n <button\r\n pButton\r\n type=\"button\"\r\n size=\"small\"\r\n outlined\r\n severity=\"secondary\"\r\n label=\"Reject\"\r\n [disabled]=\"isSending()\"\r\n (click)=\"resolvePendingAction(message, 'reject')\"\r\n ></button>\r\n </div>\r\n }\r\n </section>\r\n }\r\n @if(getResponseGrid(responseDetails).length) {\r\n <div class=\"assistant-tables\">\r\n @for(table of getResponseGrid(responseDetails); track $index) {\r\n <section class=\"assistant-table-section\">\r\n @if(table.title) {\r\n <h3>{{ table.title }}</h3>\r\n }\r\n <div class=\"assistant-table-wrap\">\r\n <table>\r\n <thead>\r\n <tr>\r\n @for(column of table.columns; track column.field) {\n <th>{{ column.header }}</th>\n }\n @if(table.actions?.length) {\n <th>Actions</th>\n }\n </tr>\r\n </thead>\r\n <tbody>\r\n @for(row of table.rows; track $index; let rowIndex = $index) {\n <tr>\n @for(column of table.columns; track column.field) {\n <td>{{ row[column.field] }}</td>\n }\n @if(table.actions?.length) {\n <td>\n <div class=\"assistant-table-actions\">\n @for(action of getTableRowActions(table, rowIndex); track action.label) {\n <button\n pButton\n type=\"button\"\n size=\"small\"\n [severity]=\"action.tone || 'secondary'\"\n [outlined]=\"action.tone === 'danger'\"\n [label]=\"action.label\"\n [disabled]=\"isSending()\"\n (click)=\"useTableAction(action)\"\n ></button>\n }\n </div>\n </td>\n }\n </tr>\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n </section>\r\n }\r\n </div>\r\n }\r\n @if(message.sources?.length) {\r\n <div class=\"assistant-sources\">\r\n <strong>Sources</strong>\r\n @for(source of message.sources ?? []; track source.citation) {\r\n <span>{{ source.citation }}</span>\r\n }\r\n </div>\r\n }\r\n @if(developerDetailsEnabled && message.developerTrace?.steps?.length) {\n <button\n type=\"button\"\n class=\"details-toggle\"\n [attr.aria-expanded]=\"isDetailsVisible(message.id)\"\n (click)=\"toggleDetails(message.id, message.developerTrace!.steps[0]?.id)\"\n >\n {{ isDetailsVisible(message.id) ? 'Hide Details' : 'Show Details' }}\n </button>\n\n @if(isDetailsVisible(message.id)) {\n <div class=\"details-dialog-backdrop\" (click)=\"toggleDetails(message.id)\">\n <section\n class=\"developer-details\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Agent processing details\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"developer-details-header\">\n <div>\n <span class=\"developer-eyebrow\">Developer trace</span>\n <h3>How this request was processed</h3>\n <p>A technical, stage-by-stage account of routing, inference, tools and results.</p>\n </div>\n <div class=\"details-dialog-actions\">\n <button\n type=\"button\"\n class=\"details-dialog-close\"\n aria-label=\"Close processing details\"\n (click)=\"toggleDetails(message.id)\"\n >\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </header>\n\n @if(message.developerTrace!.run; as run) {\n <section class=\"trace-run-summary\" aria-label=\"Run summary\">\n <div class=\"trace-metric\"><span>Total runtime</span><strong>{{ run.durationMs }} ms</strong></div>\n <div class=\"trace-metric\"><span>Model calls</span><strong>{{ run.llmCalls }}</strong></div>\n <div class=\"trace-metric\"><span>Embedding calls</span><strong>{{ run.embeddingCalls }}</strong></div>\n <div class=\"trace-metric\"><span>API calls</span><strong>{{ run.apiCalls }}</strong></div>\n <div class=\"trace-loop-policy\">\n <div>\n <span>Execution architecture</span>\n <strong>Deterministic staged pipeline</strong>\n </div>\n <span class=\"trace-no-loop\">No autonomous model loop</span>\n <p>{{ run.loopPolicy }}</p>\n </div>\n </section>\n }\n\n <ol class=\"workflow-trace\">\n @for(step of message.developerTrace!.steps; track step.id; let stepIndex = $index) {\n <li class=\"workflow-step\" [attr.data-status]=\"traceStepTone(step)\">\n <section class=\"workflow-section\">\n <button\n type=\"button\"\n class=\"workflow-step-toggle\"\n [attr.aria-expanded]=\"isTraceStepExpanded(message.id, step.id)\"\n (click)=\"toggleTraceStep(message.id, step.id)\"\n >\n <div>\n <span class=\"workflow-step-number\">Stage {{ stepIndex + 1 }}</span>\n <h4>{{ step.title }}</h4>\n <span class=\"workflow-category\">{{ formatTraceCategory(step.category) }}</span>\n </div>\n <div class=\"workflow-meta\">\n <span class=\"workflow-status\">{{ traceStepStatus(step) }}</span>\n @if(step.durationMs !== undefined) {\n <span>{{ step.durationMs }} ms</span>\n }\n <i\n class=\"pi workflow-expand-icon\"\n [class.pi-chevron-down]=\"isTraceStepExpanded(message.id, step.id)\"\n [class.pi-chevron-right]=\"!isTraceStepExpanded(message.id, step.id)\"\n aria-hidden=\"true\"\n ></i>\n </div>\n </button>\n\n @if(isTraceStepExpanded(message.id, step.id)) {\n <div class=\"workflow-step-content\">\n @if(step.metadata?.summary) {\n <p class=\"workflow-summary\">{{ step.metadata!.summary }}</p>\n }\n\n @if(step.metadata?.technology; as technology) {\n <div class=\"trace-technology\">\n <span class=\"trace-section-label\">Technology</span>\n <div class=\"trace-inline-facts\">\n <span><small>Component</small>{{ technology.component }}</span>\n @if(technology.model) { <span><small>Model</small>{{ technology.model }}</span> }\n @if(technology.library) { <span><small>Library</small>{{ technology.library }}</span> }\n @if(technology.provider) { <span><small>Provider</small>{{ technology.provider }}</span> }\n @if(technology.operation) { <span><small>Operation</small>{{ technology.operation }}</span> }\n </div>\n </div>\n }\n\n @if(step.metadata?.control; as control) {\n <div class=\"trace-control\">\n <div>\n <span class=\"trace-section-label\">Stop control</span>\n <strong>{{ formatTraceLabel(control.mode) }}</strong>\n </div>\n @if(control.maximumIterations !== undefined) {\n <span>{{ control.iterationsUsed ?? 0 }} / {{ control.maximumIterations }} iteration</span>\n }\n <p>{{ control.stopCondition }}</p>\n </div>\n }\n\n @switch(step.category) {\n @case('request') {\n @if(traceField(step.input, 'userQuery'); as userQuery) {\n <div class=\"trace-query\">\n <span>Original user query</span>\n <blockquote>{{ formatTraceScalar(userQuery) }}</blockquote>\n </div>\n }\n <div class=\"trace-card-grid\">\n @for(group of [\n { label: 'Product', value: traceMapField(step.input, 'product') },\n { label: 'User', value: traceMapField(step.input, 'user') },\n { label: 'Request context', value: traceMapField(step.input, 'requestContext') },\n { label: 'Conversation', value: traceMapField(step.input, 'conversation') },\n { label: 'Live API context', value: traceMapField(step.input, 'liveApi') }\n ]; track group.label) {\n @if(group.value) {\n <section class=\"trace-data-card\">\n <h5>{{ group.label }}</h5>\n <dl class=\"trace-field-list\">\n @for(entry of traceEntries(group.value, group.label === 'User' ? ['permissions'] : []); track entry.key) {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n </dl>\n </section>\n }\n }\n </div>\n }\n\n @case('intent_projection') {\n <div class=\"trace-transform\">\n <div>\n <span>Original query</span>\n <p>{{ formatTraceScalar(traceField(step.output, 'originalUserQuery') ?? traceField(step.input, 'originalUserQuery')) }}</p>\n </div>\n <span class=\"trace-transform-arrow\" aria-hidden=\"true\">\u2192</span>\n <div class=\"trace-transform-result\">\n <span>Generalised intent sent to embedding</span>\n <p>{{ formatTraceScalar(traceField(step.output, 'generalisedIntent') ?? traceField(step.output, 'routingText')) }}</p>\n </div>\n </div>\n @if(traceArrayField(step.output, 'removedArguments').length) {\n <div class=\"trace-section\">\n <span class=\"trace-section-label\">Arguments removed before routing</span>\n <div class=\"trace-entity-list\">\n @for(argument of traceArrayField(step.output, 'removedArguments'); track $index) {\n <span><small>{{ formatTraceScalar(traceField(argument, 'kind')) }}</small>{{ formatTraceScalar(traceField(argument, 'value')) }}</span>\n }\n </div>\n </div>\n }\n }\n\n @case('embedding') {\n <div class=\"trace-query trace-query-compact\">\n <span>Embedded text</span>\n <blockquote>{{ formatTraceScalar(traceField(step.input, 'generalisedIntent')) }}</blockquote>\n </div>\n <div class=\"trace-stat-grid\">\n <div><span>Embedding model</span><strong>{{ formatTraceScalar(traceField(step.output, 'model') ?? traceField(step.input, 'model')) }}</strong></div>\n <div><span>Dimensions</span><strong>{{ formatTraceScalar(traceField(step.output, 'dimensions')) }}</strong></div>\n <div><span>Vector magnitude</span><strong>{{ formatTraceScalar(traceField(step.output, 'vectorMagnitude')) }}</strong></div>\n <div><span>Full vector exposed</span><strong>{{ formatTraceScalar(traceField(step.output, 'fullVectorShown')) }}</strong></div>\n </div>\n @if(traceField(step.output, 'vectorPreview'); as vectorPreview) {\n <div class=\"trace-vector\">\n <span>Vector preview</span>\n <code>{{ formatTraceScalar(vectorPreview) }}</code>\n </div>\n }\n }\n\n @case('capability_routing') {\n <div class=\"trace-supabase-call\">\n <div><span>Supabase RPC</span><strong>{{ formatTraceScalar(traceField(step.input, 'rpc')) }}</strong></div>\n <div><span>Database</span><strong>{{ formatTraceScalar(traceField(step.input, 'database')) }}</strong></div>\n @if(traceArrayField(step.input, 'tables').length) {\n <div class=\"trace-table-list\">\n <span>Tables searched</span>\n <div>\n @for(table of traceArrayField(step.input, 'tables'); track $index) {\n <code>{{ formatTraceScalar(table) }}</code>\n }\n </div>\n </div>\n }\n </div>\n @if(traceMapField(step.output, 'decision'); as decision) {\n <section class=\"trace-decision\">\n <span>Routing decision</span>\n <strong>{{ formatTraceScalar(decision['selectedCapabilityLabel'] ?? decision['selectedCapabilityKey']) }}</strong>\n <div>\n <span>Status: {{ formatTraceScalar(decision['status']) }}</span>\n <span>Confidence: {{ formatSimilarity(decision['confidence']) }}</span>\n <span>Runner-up: {{ formatSimilarity(decision['runnerUpConfidence']) }}</span>\n </div>\n </section>\n }\n @if(traceArrayField(step.output, 'matchedCapabilities').length) {\n <div class=\"trace-ranking-wrap\">\n <span class=\"trace-section-label\">Ranked capability matches returned by Supabase</span>\n <table class=\"trace-ranking\">\n <thead><tr><th>#</th><th>Capability</th><th>Combined</th><th>Semantic</th><th>Definition</th><th>Example</th><th>Keyword</th></tr></thead>\n <tbody>\n @for(candidate of traceArrayField(step.output, 'matchedCapabilities'); track $index) {\n <tr>\n <td>{{ formatTraceScalar(traceField(candidate, 'rank')) }}</td>\n <td><strong>{{ formatTraceScalar(traceField(candidate, 'label')) }}</strong><code>{{ formatTraceScalar(traceField(candidate, 'capabilityKey')) }}</code></td>\n <td>{{ formatSimilarity(traceField(candidate, 'combinedScore')) }}</td>\n <td>{{ formatSimilarity(traceField(candidate, 'semanticScore')) }}</td>\n <td>{{ formatSimilarity(traceField(candidate, 'definitionScore')) }}</td>\n <td>{{ formatSimilarity(traceField(candidate, 'exampleScore')) }}</td>\n <td>{{ formatSimilarity(traceField(candidate, 'keywordScore')) }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n }\n\n @case('argument_extraction') {\n <div class=\"trace-card-grid\">\n @if(traceMapField(step.output, 'selectedCapability') ?? traceMapField(step.input, 'selectedCapability'); as capability) {\n <section class=\"trace-data-card trace-data-card-accent\">\n <h5>Selected capability</h5>\n <dl class=\"trace-field-list\">\n @for(entry of traceEntries(capability); track entry.key) {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n </dl>\n </section>\n }\n @if(traceMapField(step.input, 'tool'); as tool) {\n <section class=\"trace-data-card\">\n <h5>Allowlisted tool</h5>\n <dl class=\"trace-field-list\">\n @for(entry of traceEntries(tool); track entry.key) {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n </dl>\n </section>\n }\n </div>\n @if(traceMapField(step.output, 'extractedArguments'); as arguments) {\n <section class=\"trace-arguments\">\n <span class=\"trace-section-label\">Arguments extracted from the original query</span>\n <dl class=\"trace-argument-grid\">\n @for(entry of traceEntries(arguments); track entry.key) {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n </dl>\n </section>\n }\n @if(traceMapField(step.output, 'plannedToolCall'); as plannedCall) {\n <div class=\"trace-tool-call\">\n <span>Planned tool call</span>\n <strong>{{ formatTraceScalar(plannedCall['method']) }} \u00B7 {{ formatTraceScalar(plannedCall['toolName']) }}</strong>\n <pre>{{ formatTraceValue(plannedCall['arguments']) }}</pre>\n </div>\n }\n }\n\n @case('api_call') {\n <div class=\"trace-api-route\">\n <span class=\"trace-http-method\">{{ formatTraceScalar(traceField(step.input, 'method')) }}</span>\n <code>{{ formatTraceScalar(traceField(step.input, 'path')) }}</code>\n @if(getHttpStatus(step); as statusCode) {\n <span class=\"trace-http-status\" [class.success]=\"isHttpSuccessful(step)\" [class.error]=\"!isHttpSuccessful(step)\">\n HTTP {{ statusCode }} \u00B7 {{ isHttpSuccessful(step) ? 'Success' : 'Error' }}\n </span>\n }\n </div>\n <div class=\"trace-payload-grid\">\n @if(traceField(step.input, 'body'); as requestBody) {\n <section><span>Request payload</span><pre>{{ formatTraceValue(requestBody) }}</pre></section>\n }\n @if(traceField(step.output, 'response'); as apiResponse) {\n <section><span>API response</span><pre>{{ formatTraceValue(apiResponse) }}</pre></section>\n }\n </div>\n }\n\n @case('action') {\n <div class=\"trace-card-grid\">\n <section class=\"trace-data-card\">\n <h5>Workflow input</h5>\n <dl class=\"trace-field-list\">\n @for(entry of traceEntries(step.input); track entry.key) {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n </dl>\n </section>\n <section class=\"trace-data-card trace-data-card-accent\">\n <h5>Outcome</h5>\n <dl class=\"trace-field-list\">\n @for(entry of traceEntries(step.output); track entry.key) {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n </dl>\n </section>\n </div>\n }\n\n @case('response') {\n @if(traceField(step.output, 'answer'); as answer) {\n <div class=\"trace-query\"><span>Final grounded response</span><blockquote>{{ formatTraceScalar(answer) }}</blockquote></div>\n }\n <dl class=\"trace-argument-grid\">\n @for(entry of traceEntries(step.output); track entry.key) {\n @if(entry.key !== 'answer') {\n <div><dt>{{ entry.label }}</dt><dd>{{ formatTraceScalar(entry.value) }}</dd></div>\n }\n }\n </dl>\n }\n\n @default {\n <div class=\"trace-payload-grid\">\n @if(step.input !== undefined) {\n <section><span>Input</span><pre>{{ formatTraceValue(step.input) }}</pre></section>\n }\n @if(step.output !== undefined) {\n <section><span>Output</span><pre>{{ formatTraceValue(step.output) }}</pre></section>\n }\n </div>\n }\n }\n\n @if(step.error; as error) {\n <div class=\"trace-error\" [class.trace-warning]=\"step.category === 'api_call' && isHttpSuccessful(step)\">\n <strong class=\"trace-error-heading\">\n @if(step.category === 'api_call' && isHttpSuccessful(step)) {\n Post-response processing stopped\n } @else {\n Step stopped@if(error.statusCode) { \u00B7 HTTP {{ error.statusCode }} }\n }\n </strong>\n <strong class=\"trace-error-legacy\" aria-hidden=\"true\">\n Failed@if(error.statusCode) { \u2014 HTTP {{ error.statusCode }} }\n </strong>\n <span>{{ error.message }}</span>\n </div>\n }\n </div>\n }\n </section>\n </li>\n }\n </ol>\n </section>\n </div>\n }\n }\n } @else {\r\n @if(message.role === 'assistant') {\r\n <div\r\n class=\"assistant-markdown\"\r\n [innerHTML]=\"message.content | maxAssistantMarkdown\"\r\n ></div>\r\n } @else {\r\n <p>{{ message.content }}</p>\r\n }\r\n }\r\n @if(message.role === 'assistant' && message.suggestions?.length) {\n <div class=\"suggestions\">\r\n @for(suggestion of message.suggestions ?? []; track suggestion) {\r\n <button\r\n pButton\r\n type=\"button\"\r\n size=\"small\"\r\n text\r\n severity=\"secondary\"\n class=\"suggestion-chip\"\n [label]=\"suggestionLabel(suggestion)\"\n (click)=\"useSuggestion(suggestion)\"\n ></button>\r\n }\n </div>\n }\n </article>\n }\r\n @if(isSending()) {\r\n <article class=\"message assistant typing\" [attr.aria-label]=\"uiCopy.typingLabel\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </article>\r\n }\r\n </div>\r\n\r\n <form class=\"assistant-composer\" (ngSubmit)=\"sendMessage()\">\r\n <textarea\r\n pTextarea\r\n name=\"assistantPrompt\"\r\n rows=\"2\"\r\n [placeholder]=\"uiCopy.placeholder\"\r\n [ngModel]=\"input()\"\r\n (ngModelChange)=\"input.set($event)\"\r\n (keydown)=\"onComposerKeydown($event)\"\r\n >\r\n </textarea>\r\n <button\r\n pButton\r\n type=\"submit\"\r\n icon=\"pi pi-send\"\r\n aria-label=\"Send message\"\r\n [disabled]=\"!input().trim() || isSending()\"\r\n ></button>\r\n </form>\n </section>\n</aside>\n", styles: [":host{position:fixed;right:24px;bottom:24px;z-index:1100}.assistant-launcher{position:relative;width:64px!important;height:64px!important;min-height:64px!important;padding:7px;border:1px solid rgba(124,58,237,.24)!important;border-radius:22px!important;color:inherit!important;background:radial-gradient(circle at 28% 16%,rgba(255,255,255,.98),transparent 38%),linear-gradient(145deg,#fff7ed,#f5f3ff 52%,#fdf2f8)!important;box-shadow:0 0 0 1px #ffffffd9 inset,0 12px 30px #5b21b62e!important;overflow:visible;transition:border-color .22s ease,box-shadow .22s ease,transform .22s cubic-bezier(.2,.8,.2,1);z-index:2}.assistant-launcher:before,.assistant-launcher:after{content:\"\";position:absolute;top:50%;left:50%;width:22px;height:2px;border-radius:999px;background:#0f6cae;opacity:0;transform:translate(-50%,-50%) scale(.6) rotate(0);transition:opacity .18s ease,transform .22s ease;z-index:1}.assistant-launcher:before{transform:translate(-50%,-50%) scale(.6) rotate(45deg)}.assistant-launcher:after{transform:translate(-50%,-50%) scale(.6) rotate(-45deg)}.assistant-launcher:hover,.assistant-launcher:focus-visible{border-color:#ec489975!important;background:radial-gradient(circle at 30% 18%,#fff,transparent 38%),linear-gradient(145deg,#ffedd5,#ede9fe,#fce7f3)!important;box-shadow:0 0 0 3px #a855f71a,0 16px 34px #6d28d938!important;transform:translateY(-2px)}.assistant-launcher.is-open{background:#fff!important;box-shadow:0 0 0 2px #0078c838,0 14px 32px #0f172a2e!important;transform:translateY(0)}.assistant-launcher.is-open:before,.assistant-launcher.is-open:after{opacity:1}.assistant-launcher.is-open:before{transform:translate(-50%,-50%) scale(1) rotate(45deg)}.assistant-launcher.is-open:after{transform:translate(-50%,-50%) scale(1) rotate(-45deg)}.mascot{position:relative;display:inline-grid;width:54px;height:54px;place-items:center;transform-origin:50% 72%;transition:opacity .18s ease,transform .24s ease,filter .24s ease;z-index:0}.mascot-aura{position:absolute;inset:5px;border-radius:50%;background:conic-gradient(from 25deg,#f472b66b,#fbbf2452,#2dd4bf57,#8b5cf66b,#f472b66b);filter:blur(7px);animation:mascot-aura 3s ease-in-out infinite}.mascot-art{position:relative;width:100%;height:100%;overflow:visible;filter:drop-shadow(0 5px 5px rgba(2,75,116,.2));animation:mascot-idle 3.4s ease-in-out infinite}.mascot-shell{fill:#7c3aed}.mascot-ear-left{fill:#ec4899}.mascot-ear-right{fill:#f97316}.mascot-ear{transform-box:fill-box;transform-origin:center}.mascot-face{fill:#211747;stroke:#ffffff4d;stroke-width:1}.mascot-signal{fill:#facc15;stroke:#fff;stroke-width:1.5;transform-box:fill-box;transform-origin:center;animation:mascot-signal 2.2s ease-out infinite}.mascot-antenna,.mascot-sparkle,.mascot-shell-shine,.mascot-smile,.mascot-mark{fill:none;stroke-linecap:round;stroke-linejoin:round}.mascot-antenna{stroke:#6d28d9;stroke-width:2.5}.mascot-sparkle{stroke:#ec4899;stroke-width:2;transform-box:fill-box;transform-origin:center;animation:mascot-sparkle 3.4s ease-in-out infinite}.mascot-shell-shine{stroke:#e9d5ffd1;stroke-width:2}.mascot-eye{fill:#5eead4;transform-box:fill-box;transform-origin:center;animation:mascot-blink 5.2s ease-in-out infinite}.mascot-smile{stroke:#fbbf24;stroke-width:2}.mascot-mark{stroke:#ffffffd1;stroke-width:2.2}.assistant-launcher:hover .mascot-art,.assistant-launcher:focus-visible .mascot-art{animation:mascot-greet .7s cubic-bezier(.2,.8,.2,1)}.assistant-launcher:hover .mascot-ear-right,.assistant-launcher:focus-visible .mascot-ear-right{animation:mascot-ear-wave .7s ease-in-out}.assistant-launcher.is-open .mascot{filter:saturate(1.08);opacity:.24;transform:scale(.82) rotate(-5deg)}.mascot.large{width:52px;height:52px;flex:0 0 auto}@keyframes mascot-idle{0%,to{transform:translateY(0) rotate(0)}50%{transform:translateY(-2px) rotate(1deg)}}@keyframes mascot-greet{0%,to{transform:translateY(0) rotate(0)}35%{transform:translateY(-4px) rotate(-5deg)}65%{transform:translateY(-2px) rotate(4deg)}}@keyframes mascot-ear-wave{0%,to{transform:rotate(0) scaleY(1)}50%{transform:rotate(-12deg) scaleY(1.12)}}@keyframes mascot-blink{0%,45%,49%,to{transform:scaleY(1)}47%{transform:scaleY(.12)}}@keyframes mascot-signal{0%,55%,to{opacity:1;transform:scale(1)}70%{opacity:.55;transform:scale(1.42)}}@keyframes mascot-sparkle{0%,62%,to{opacity:0;transform:scale(.5) rotate(0)}72%{opacity:1;transform:scale(1) rotate(90deg)}84%{opacity:0;transform:scale(.7) rotate(150deg)}}@keyframes mascot-aura{0%,to{opacity:.45;transform:scale(.88)}50%{opacity:.85;transform:scale(1.08)}}.assistant-window{position:fixed;top:50%;right:24px;z-index:1;width:min(560px,100vw - 48px);height:min(80vh,820px);max-height:calc(100dvh - 48px);border:1px solid rgba(148,163,184,.24);border-radius:24px;background:#fffffff5;box-shadow:0 24px 70px #0f172a33,0 0 0 1px #ffffffad inset;overflow:hidden;opacity:0;visibility:hidden;pointer-events:none;translate:0 -50%;transform:translate3d(40px,28px,0) scale(.94);transform-origin:right bottom;transition:opacity .22s ease,transform .3s cubic-bezier(.4,0,1,1),visibility 0s linear .3s;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);will-change:opacity,transform}.assistant-window.is-open{opacity:1;visibility:visible;pointer-events:auto;transform:translateZ(0) scale(1);transition:opacity .28s ease,transform .46s cubic-bezier(.16,1,.3,1),visibility 0s}.assistant-panel{display:grid;grid-template-rows:auto 1fr auto;height:100%;min-height:0;border-radius:inherit;background:linear-gradient(180deg,#f8fafcd1,#f1f5f9f5),var(--background-color, #f8fafc);overflow:hidden}.assistant-header,.assistant-composer{display:flex;gap:12px;align-items:center;padding:16px 18px;background:#ffffffeb;border-bottom:1px solid rgba(226,232,240,.86)}.assistant-identity{display:grid;gap:5px;min-width:0}.assistant-name{margin:0;width:fit-content;color:var(--primary-color, #2563eb);font-family:Montserrat,Arial,sans-serif;font-size:23px;font-weight:700;letter-spacing:-.045em;line-height:.9}.assistant-subtitle{color:#64748b;font-size:10px;font-weight:600;letter-spacing:.1em;line-height:1.2;text-transform:uppercase}.sources span{color:#64748b;font-size:12px;font-weight:600}.assistant-header .header-action:first-of-type{margin-left:auto}.assistant-header .header-action{width:38px;height:38px;margin-left:0;color:#475569;background:transparent}.assistant-header .header-action:hover{background:#0f172a0f}.assistant-messages{min-height:0;overflow:auto;padding:18px;scrollbar-width:thin}.message{position:relative;width:fit-content;max-width:92%;margin-bottom:14px;padding:12px;border:1px solid var(--border-color, #e2e8f0);border-radius:16px;background:#fff;box-shadow:0 1px 2px #0f172a14,0 10px 24px #0f172a0f;-webkit-user-select:text;user-select:text}.message.assistant{width:100%;max-width:100%}.message.user{width:fit-content;max-width:88%;margin-left:auto;color:#fff;background:var(--primary-color, #2563eb);border-color:var(--primary-color, #2563eb);border-bottom-right-radius:6px}.message.user p{color:inherit}.message.error{border-color:var(--danger-color, #dc2626)}.message p{margin:0;line-height:1.45}.assistant-result{color:#1e293b}.assistant-cards{display:grid;gap:10px;margin-top:12px}.assistant-card{overflow:hidden;border:1px solid #dbeafe;border-radius:14px;background:linear-gradient(145deg,#f8fbff,#fff);box-shadow:0 8px 22px #0f172a0f}.assistant-card>header{display:grid;gap:2px;padding:11px 12px;border-bottom:1px solid #e2e8f0}.assistant-card>header strong{color:#0f172a;font-size:14px}.assistant-card>header span,.assistant-card>footer{color:#64748b;font-size:11px}.assistant-card-metrics{display:grid;grid-template-columns:repeat(3,minmax(0,1fr))}.assistant-card-metric{display:grid;min-width:0;gap:3px;padding:11px 9px;border-right:1px solid #e2e8f0}.assistant-card-metric:last-child{border-right:0}.assistant-card-metric span{color:#64748b;font-size:10px;font-weight:600;text-transform:uppercase}.assistant-card-metric strong{color:#1e293b;font-size:13px;overflow-wrap:anywhere}.assistant-card-metric[data-tone=positive] strong{color:#047857}.assistant-card-metric[data-tone=warning] strong{color:#b45309}.assistant-card>footer{padding:8px 12px;border-top:1px solid #e2e8f0;background:#f8fafcb8}:host ::ng-deep .assistant-markdown{line-height:1.45}:host ::ng-deep .assistant-markdown>:first-child{margin-top:0}:host ::ng-deep .assistant-markdown>:last-child{margin-bottom:0}:host ::ng-deep .assistant-markdown p{margin:0 0 8px}:host ::ng-deep .assistant-markdown ul,:host ::ng-deep .assistant-markdown ol{margin:8px 0;padding-left:22px}:host ::ng-deep .assistant-markdown li+li{margin-top:4px}:host ::ng-deep .assistant-markdown strong{font-weight:700}:host ::ng-deep .assistant-markdown code{padding:1px 4px;border-radius:4px;background:#e2e8f0;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.9em}:host ::ng-deep .assistant-markdown a{color:var(--primary-color, #2563eb);text-decoration:underline}.assistant-status{display:inline-flex;margin-top:8px;padding:3px 8px;border-radius:999px;color:#475569;background:#e2e8f0;font-size:11px;font-weight:600;text-transform:capitalize}.pending-action{display:grid;gap:9px;margin-top:12px;padding:12px;border:1px solid #f59e0b;border-radius:12px;background:#fffbeb}.pending-action-heading{display:flex;align-items:center;justify-content:space-between;color:#92400e}.pending-action-heading span{padding:2px 7px;border-radius:999px;color:#78350f;background:#fde68a;font-size:11px;font-weight:700}.pending-action small{color:#78716c}.pending-action-buttons{display:flex;gap:8px}.action-decision{color:#475569;font-weight:600}.assistant-sources{display:grid;gap:4px;margin-top:12px;padding-top:10px;border-top:1px solid #e2e8f0;color:#64748b;font-size:12px}.assistant-tables{display:grid;gap:10px;margin-top:12px}.assistant-table-section{display:grid;gap:6px}.assistant-table-section h3{margin:0;color:#0f172a;font-size:13px;font-weight:600}.assistant-table-wrap{overflow:auto;border:1px solid var(--border-color, #e2e8f0);border-radius:var(--border-radius, 6px)}.assistant-table-wrap table{width:100%;min-width:420px;border-collapse:collapse;background:#fff}.assistant-table-wrap th,.assistant-table-wrap td{padding:8px 10px;border-bottom:1px solid var(--border-color, #e2e8f0);color:#334155;font-size:12px;line-height:1.35;text-align:left;vertical-align:top}.assistant-table-wrap th{color:#475569;font-weight:600;background:#f8fafc}.assistant-table-wrap tr:last-child td{border-bottom:0}.assistant-table-actions{display:flex;flex-wrap:wrap;gap:6px;min-width:150px}.details-toggle{display:flex;width:fit-content;margin:10px 0 0 auto;padding:0;border:0;color:var(--primary-color, #2563eb);background:transparent;font:inherit;font-size:13px;font-weight:400;text-decoration:underline;text-underline-offset:3px;cursor:pointer}.details-toggle:hover{color:var(--primary-dark-color, #1d4ed8)}.details-toggle:focus-visible{outline:2px solid var(--primary-color, #2563eb);outline-offset:3px}.developer-details{display:grid;gap:12px;margin-top:12px;padding:12px;border:1px solid var(--border-color, #e2e8f0);border-radius:10px;background:#f8fafc}.developer-details-header h3,.developer-details-header p{margin:0}.developer-details-header h3{color:#0f172a;font-size:13px;font-weight:600}.developer-details-header p{margin-top:2px;color:#64748b;font-size:11px}.workflow-trace{display:grid;gap:0;margin:0;padding:0;list-style:none}.workflow-step{display:block;min-width:0}.workflow-category{color:#64748b;font-size:9px;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.workflow-meta{display:grid;flex:0 0 auto;gap:2px;justify-items:end;color:#64748b;font-size:9px}.workflow-status{padding:2px 5px;border-radius:999px;color:#334155;background:#e2e8f0;font-weight:600;text-transform:capitalize}.workflow-step[data-status=succeeded] .workflow-status{color:#166534;background:#dcfce7}.workflow-step[data-status=failed] .workflow-status{color:#991b1b;background:#fee2e2}.trace-json{display:grid;gap:4px;margin-top:8px}.trace-json>span{color:#64748b;font-size:10px;font-weight:600;text-transform:uppercase}.trace-json pre{max-height:280px;margin:0;padding:8px;overflow:auto;border-radius:6px;color:#dbeafe;background:#0f172a;font-size:10px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}.trace-error{display:grid;gap:3px;margin-top:8px;padding:8px;border-left:3px solid #dc2626;border-radius:4px;color:#991b1b;background:#fef2f2;font-size:11px}.developer-details-header{display:flex;gap:12px;align-items:flex-start;justify-content:space-between}.developer-eyebrow,.trace-section-label{color:#2563eb;font-size:9px;font-weight:600;letter-spacing:.08em;text-transform:uppercase}.trace-run-summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:7px}.trace-metric{display:grid;gap:3px;min-width:0;padding:9px;border:1px solid #e2e8f0;border-radius:8px;background:#fff}.trace-metric span,.trace-loop-policy span{color:#64748b;font-size:9px;font-weight:600}.trace-metric strong{color:#0f172a;font-size:13px}.trace-loop-policy{display:grid;grid-template-columns:minmax(0,1fr) auto;grid-column:1/-1;gap:5px 12px;align-items:center;padding:10px;border:1px solid #c7d2fe;border-radius:9px;background:#eef2ff}.trace-loop-policy div{display:grid;gap:2px}.trace-loop-policy strong{color:#312e81;font-size:11px}.trace-loop-policy p{grid-column:1/-1;margin:0;color:#475569;font-size:10px;line-height:1.45}.trace-no-loop{padding:3px 7px;border-radius:999px;color:#166534!important;background:#dcfce7;font-weight:600!important}.workflow-summary{margin:9px 0 0;color:#475569;font-size:10.5px;line-height:1.5}.trace-technology{display:grid;gap:6px;margin-top:10px;padding:8px;border:1px solid #dbeafe;border-radius:7px;background:#f8fbff}.trace-inline-facts{display:flex;flex-wrap:wrap;gap:5px}.trace-inline-facts>span{display:grid;gap:1px;max-width:100%;padding:4px 6px;border-radius:5px;color:#1e293b;background:#fff;font-size:9.5px;overflow-wrap:anywhere}.trace-inline-facts small{color:#64748b;font-size:8px;font-weight:600;text-transform:uppercase}.trace-control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:4px 8px;margin-top:8px;padding:8px;border-left:3px solid #8b5cf6;border-radius:5px;background:#f5f3ff}.trace-control>div{display:grid;gap:2px}.trace-control strong,.trace-control>span{color:#5b21b6;font-size:9.5px}.trace-control p{grid-column:1/-1;margin:0;color:#475569;font-size:9.5px;line-height:1.4}.trace-query,.trace-section,.trace-arguments,.trace-ranking-wrap{display:grid;gap:5px;margin-top:10px}.trace-query>span,.trace-transform>div>span,.trace-vector>span,.trace-payload-grid section>span,.trace-tool-call>span,.trace-supabase-call span,.trace-decision>span{color:#64748b;font-size:9px;font-weight:600;text-transform:uppercase}.trace-query blockquote{margin:0;padding:8px 10px;border-left:3px solid #2563eb;border-radius:5px;color:#172554;background:#eff6ff;font-size:11px;font-weight:600;line-height:1.45}.trace-query-compact blockquote{border-color:#8b5cf6;color:#4c1d95;background:#f5f3ff}.trace-card-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px;margin-top:9px}.trace-data-card{min-width:0;padding:8px;border:1px solid #e2e8f0;border-radius:7px;background:#f8fafc}.trace-data-card-accent{border-color:#bfdbfe;background:#eff6ff}.trace-data-card h5{margin:0 0 5px;color:#334155;font-size:10px}.trace-field-list,.trace-argument-grid{display:grid;gap:4px;margin:0}.trace-field-list>div{display:grid;grid-template-columns:minmax(70px,.7fr) minmax(0,1.3fr);gap:6px}.trace-field-list dt,.trace-argument-grid dt{color:#64748b;font-size:8.5px}.trace-field-list dd,.trace-argument-grid dd{margin:0;color:#1e293b;font-size:9px;font-weight:600;overflow-wrap:anywhere}.trace-transform{display:grid;grid-template-columns:minmax(0,1fr) 18px minmax(0,1fr);gap:6px;align-items:stretch;margin-top:10px}.trace-transform>div{display:grid;gap:5px;padding:9px;border:1px solid #e2e8f0;border-radius:7px;background:#f8fafc}.trace-transform .trace-transform-result{border-color:#bfdbfe;background:#eff6ff}.trace-transform p{margin:0;color:#1e293b;font-size:10px;font-weight:600;line-height:1.4}.trace-transform-arrow{display:grid;place-items:center;color:#2563eb;font-size:16px;font-weight:600}.trace-entity-list{display:flex;flex-wrap:wrap;gap:5px}.trace-entity-list>span{display:flex;gap:5px;align-items:center;padding:4px 7px;border:1px solid #fed7aa;border-radius:999px;color:#9a3412;background:#fff7ed;font-size:9.5px}.trace-entity-list small{color:#c2410c;font-size:7.5px;font-weight:600;text-transform:uppercase}.trace-stat-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin-top:8px}.trace-stat-grid>div{display:grid;gap:2px;padding:7px;border:1px solid #e2e8f0;border-radius:6px}.trace-stat-grid span{color:#64748b;font-size:8.5px}.trace-stat-grid strong{color:#0f172a;font-size:10px;overflow-wrap:anywhere}.trace-vector{display:grid;gap:4px;margin-top:7px}.trace-vector code{padding:7px;overflow:auto;border-radius:5px;color:#c4b5fd;background:#1e1b4b;font-size:8.5px;white-space:nowrap}.trace-supabase-call{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin-top:10px;padding:9px;border:1px solid #a7f3d0;border-radius:7px;background:#ecfdf5}.trace-supabase-call>div{display:grid;gap:2px}.trace-supabase-call strong{color:#065f46;font-size:10px;overflow-wrap:anywhere}.trace-table-list{grid-column:1/-1}.trace-table-list>div{display:flex;flex-wrap:wrap;gap:4px}.trace-table-list code{padding:3px 5px;border-radius:4px;color:#065f46;background:#d1fae5;font-size:8.5px}.trace-decision{display:grid;gap:4px;margin-top:7px;padding:9px;border-left:3px solid #16a34a;border-radius:5px;background:#f0fdf4}.trace-decision strong{color:#14532d;font-size:11px}.trace-decision div{display:flex;flex-wrap:wrap;gap:8px;color:#166534;font-size:8.5px}.trace-ranking-wrap{overflow:auto}.trace-ranking{width:100%;min-width:610px;border-collapse:collapse;font-size:8.5px}.trace-ranking th,.trace-ranking td{padding:6px;border-bottom:1px solid #e2e8f0;color:#475569;text-align:right;vertical-align:top}.trace-ranking th{color:#64748b;background:#f8fafc;font-size:8px;text-transform:uppercase}.trace-ranking th:nth-child(2),.trace-ranking td:nth-child(2){text-align:left}.trace-ranking td:nth-child(2){display:grid;gap:2px;min-width:145px}.trace-ranking strong{color:#1e293b;font-size:9px}.trace-ranking code{color:#64748b;font-size:7.5px}.trace-argument-grid{grid-template-columns:repeat(2,minmax(0,1fr));margin-top:3px}.trace-argument-grid>div{min-width:0;padding:7px;border:1px solid #e2e8f0;border-radius:6px;background:#f8fafc}.trace-tool-call,.trace-api-route{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:8px;padding:8px;border-radius:6px;color:#dbeafe;background:#0f172a}.trace-tool-call>span{width:100%;color:#94a3b8}.trace-tool-call strong{color:#bfdbfe;font-size:10px}.trace-tool-call pre,.trace-payload-grid pre{width:100%;max-height:220px;margin:0;overflow:auto;color:#dbeafe;font-size:9px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}.trace-api-route>code{flex:1 1 180px;color:#e2e8f0;font-size:9px;overflow-wrap:anywhere}.trace-http-method,.trace-http-status{padding:3px 6px;border-radius:4px;color:#fff;background:#2563eb;font-size:8.5px;font-weight:600}.trace-http-status.success{color:#bbf7d0;background:#166534}.trace-http-status.error{color:#fecaca;background:#991b1b}.trace-payload-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin-top:7px}.trace-payload-grid section{display:grid;gap:5px;min-width:0;padding:8px;border:1px solid #334155;border-radius:6px;background:#0f172a}.trace-payload-grid section>span{color:#94a3b8}.trace-error.trace-warning{border-left-color:#f59e0b;color:#92400e;background:#fffbeb}.trace-error-legacy{display:none}.trace-error-heading{font-size:10px}.details-dialog-backdrop{position:fixed;inset:0;z-index:20;display:grid;padding:14px;place-items:center;background:#0f172a7a;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}.details-dialog-backdrop .developer-details{display:block;width:100%;max-height:100%;margin:0;padding:0 14px 14px;overflow:auto;overscroll-behavior:contain;border:0;border-radius:14px;background:#fff;box-shadow:0 24px 70px #0f172a4d}.details-dialog-backdrop .developer-details-header{position:sticky;top:0;z-index:3;margin:0 -14px 14px;padding:14px;border-bottom:1px solid #e2e8f0;background:#fffffff7;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.details-dialog-actions{display:flex;flex:0 0 auto;gap:7px;align-items:center}.details-dialog-close{display:grid;width:30px;height:30px;padding:0;place-items:center;border:1px solid #e2e8f0;border-radius:8px;color:#475569;background:#fff;cursor:pointer}.details-dialog-close:hover{color:#0f172a;background:#f1f5f9}.details-dialog-close:focus-visible{outline:2px solid #2563eb;outline-offset:2px}.details-dialog-backdrop .trace-run-summary{margin-bottom:6px;padding-bottom:14px;border-bottom:1px solid #cbd5e1}.details-dialog-backdrop .trace-metric{padding:0 9px;border:0;border-right:1px solid #e2e8f0;border-radius:0;background:transparent}.details-dialog-backdrop .trace-metric:nth-child(4){border-right:0}.details-dialog-backdrop .trace-loop-policy{margin-top:5px;padding:9px 0 0;border:0;border-top:1px solid #e2e8f0;border-radius:0;background:transparent}.details-dialog-backdrop .workflow-trace{display:block}.details-dialog-backdrop .workflow-step{display:block;border-bottom:1px solid #cbd5e1}.workflow-section{display:block}.workflow-step-toggle{display:flex;gap:14px;align-items:center;justify-content:space-between;width:100%;padding:14px 2px;border:0;color:inherit;background:transparent;font:inherit;text-align:left;cursor:pointer}.workflow-step-toggle:hover{background:#f8fafc}.workflow-step-toggle:focus-visible{outline:2px solid #2563eb;outline-offset:-2px}.workflow-step-toggle>div:first-child{display:grid;min-width:0;gap:2px}.workflow-step-toggle h4{margin:0;color:#0f172a;font-size:12px;font-weight:600;line-height:1.4;text-decoration:underline;text-decoration-color:#94a3b8;text-underline-offset:4px}.workflow-step-number{display:block;color:#2563eb;font-size:10px;font-weight:600}.workflow-expand-icon{color:#64748b;font-size:10px}.workflow-step-content{padding:0 2px 16px}.details-dialog-backdrop .workflow-summary{margin-top:0;padding-bottom:10px;border-bottom:1px solid #e2e8f0}.details-dialog-backdrop .trace-technology,.details-dialog-backdrop .trace-control{padding:9px 0;border:0;border-bottom:1px solid #e2e8f0;border-radius:0;background:transparent}.details-dialog-backdrop .trace-control{border-left:0}.details-dialog-backdrop .trace-inline-facts>span{padding:0 8px 0 0;border-right:1px solid #e2e8f0;border-radius:0;background:transparent}.details-dialog-backdrop :is(h3,h4,h5,strong,.workflow-status,.trace-section-label,.developer-eyebrow){font-weight:600!important}@media (max-width: 520px){.trace-run-summary,.trace-card-grid,.trace-stat-grid,.trace-payload-grid,.trace-argument-grid,.trace-transform{grid-template-columns:1fr}.trace-transform-arrow{transform:rotate(90deg)}}.suggestions{display:grid;gap:0;margin-top:12px}.suggestion-chip{justify-content:flex-start;width:100%;padding:6px 0;border:1px solid transparent;border-radius:999px;color:var(--primary-color, #2563eb);background:transparent;font-weight:600;box-shadow:none;text-align:left;transition:padding .16s ease,border-color .16s ease,background .16s ease,box-shadow .16s ease}.suggestion-chip:hover{padding:6px 12px;border-color:rgba(var(--primary-color-rgb, 37, 99, 235),.28);color:var(--primary-color, #2563eb);background:rgba(var(--primary-color-rgb, 37, 99, 235),.08);box-shadow:0 4px 14px #0f172a0f}.response-details{display:grid;gap:12px;margin-top:12px}.response-section{display:grid;gap:8px;padding-bottom:12px;border-bottom:1px solid var(--border-color, #e2e8f0)}.response-section:last-child{padding-bottom:0;border-bottom:0}.response-section h3{margin:0;color:#0f172a;font-size:13px;font-weight:600}.quoted{padding:8px 10px;border-left:5px solid var(--primary-color, #2563eb);border-radius:var(--border-radius, 6px);color:#1e293b;background:#f8fafc}.detail-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:0}.detail-grid div{min-width:0;padding:8px;border-radius:var(--border-radius, 6px);background:#f8fafc}.detail-grid dt{margin-bottom:3px;color:#64748b;font-size:11px;font-weight:600;text-transform:uppercase}.detail-grid dd{margin:0;color:#334155;font-size:13px;font-weight:600;line-height:1.35;overflow-wrap:anywhere}.detail-grid dd span{display:block;margin-top:2px;color:#64748b;font-size:11px}.api-call-summary,.json-block{display:grid;gap:6px;padding:8px;border-radius:var(--border-radius, 6px);background:#f8fafc}.api-call-summary .detail-label,.json-block .detail-label{color:#64748b;font-size:11px;font-weight:600;text-transform:uppercase}.api-call-summary code{color:var(--secondary-color, #0f172a);font-weight:600;white-space:normal;word-break:break-word}.json-block pre{max-height:220px;margin:0;overflow:auto;color:#0f172a;font-size:12px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}.typing{display:flex;width:fit-content;max-width:fit-content;gap:5px;align-items:center;padding:4px 0;border:0;background:transparent;box-shadow:none}.typing span{width:7px;height:7px;border-radius:50%;background:var(--primary-color, #2563eb);animation:typing-dot .85s infinite ease-in-out}.typing span:nth-child(2){animation-delay:.15s}.assistant-composer{padding:14px 16px 16px;border-top:1px solid rgba(226,232,240,.9);border-bottom:0;box-shadow:0 -10px 30px #0f172a0a}.assistant-composer textarea{flex:1;min-height:46px;max-height:120px;border-radius:18px;background:#fff;resize:vertical}.assistant-composer button[type=submit]{width:44px;height:44px;border-radius:16px}@keyframes typing-dot{0%,80%,to{opacity:.35;transform:translateY(0)}40%{opacity:1;transform:translateY(-4px)}}@keyframes pulse{to{opacity:.35;transform:translateY(-2px)}}@media (prefers-reduced-motion: reduce){.mascot-aura,.mascot-art,.mascot-eye,.mascot-signal,.mascot-sparkle,.assistant-launcher:hover .mascot-art,.assistant-launcher:focus-visible .mascot-art,.assistant-launcher:hover .mascot-ear-right,.assistant-launcher:focus-visible .mascot-ear-right{animation:none}.assistant-launcher,.mascot{transition-duration:.01ms}.assistant-window,.assistant-window.is-open{transition:none}}@media (max-width: 640px){:host{right:16px;bottom:16px}.assistant-window{right:12px;width:calc(100vw - 24px);height:min(84vh,100dvh - 24px);max-height:calc(100dvh - 24px);border-radius:20px}.assistant-messages{padding:14px}.detail-grid,.assistant-card-metrics{grid-template-columns:1fr}.assistant-card-metric{border-right:0;border-bottom:1px solid #e2e8f0}.assistant-card-metric:last-child{border-bottom:0}}\n"] }]
|
|
793
|
+
}], ctorParameters: () => [{ type: MaxAssistantService }, { type: i0.DestroyRef }], propDecorators: { launcher: [{
|
|
794
|
+
type: ViewChild,
|
|
795
|
+
args: ['launcher']
|
|
796
|
+
}], messagesViewport: [{
|
|
290
797
|
type: ViewChild,
|
|
291
798
|
args: ['messagesViewport']
|
|
292
799
|
}], app: [{
|
|
@@ -295,6 +802,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
295
802
|
type: Input
|
|
296
803
|
}], user: [{
|
|
297
804
|
type: Input
|
|
805
|
+
}], context: [{
|
|
806
|
+
type: Input
|
|
807
|
+
}], requestHeaders: [{
|
|
808
|
+
type: Input
|
|
809
|
+
}], onDocumentEscape: [{
|
|
810
|
+
type: HostListener,
|
|
811
|
+
args: ['document:keydown.escape']
|
|
298
812
|
}] } });
|
|
299
813
|
|
|
300
814
|
/*
|
|
@@ -305,5 +819,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
305
819
|
* Generated bundle index. Do not edit.
|
|
306
820
|
*/
|
|
307
821
|
|
|
308
|
-
export { MAX_ASSISTANT_CONFIG, MaxAssistantComponent };
|
|
822
|
+
export { MAX_ASSISTANT_CONFIG, MaxAssistantComponent, MaxAssistantService };
|
|
309
823
|
//# sourceMappingURL=max-agent-ui.mjs.map
|