@praxisui/ai 8.0.0-beta.3 → 8.0.0-beta.30
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 +56 -4
- package/fesm2022/praxisui-ai.mjs +1963 -99
- package/index.d.ts +686 -25
- package/package.json +7 -2
package/fesm2022/praxisui-ai.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Component, Injectable, InjectionToken, Optional, Inject, inject, ChangeDetectorRef, ElementRef, ViewChild, Input, ChangeDetectionStrategy, EventEmitter, Output } from '@angular/core';
|
|
2
|
+
import { Component, Injectable, InjectionToken, Optional, Inject, inject, signal, computed, ChangeDetectorRef, ElementRef, ViewChild, Input, ChangeDetectionStrategy, EventEmitter, Output } from '@angular/core';
|
|
3
3
|
import { SchemaType, GoogleGenerativeAI } from '@google/generative-ai';
|
|
4
|
-
import { throwError, from, Observable, of, BehaviorSubject, tap, map as map$1, isObservable, firstValueFrom } from 'rxjs';
|
|
4
|
+
import { throwError, from, Observable, of, BehaviorSubject, tap, map as map$1, catchError as catchError$1, isObservable, firstValueFrom } from 'rxjs';
|
|
5
5
|
import { map, catchError, filter, take } from 'rxjs/operators';
|
|
6
6
|
import { HttpClient, HttpParams, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
|
7
|
+
import { API_URL } from '@praxisui/core';
|
|
7
8
|
import * as i3 from '@angular/common';
|
|
8
9
|
import { CommonModule } from '@angular/common';
|
|
9
10
|
import * as i4 from '@angular/forms';
|
|
@@ -60,13 +61,351 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
60
61
|
* Models for Praxis AI (Centralized)
|
|
61
62
|
*/
|
|
62
63
|
|
|
64
|
+
const PRAXIS_ASSISTANT_CONTEXT_TEXT_LIMIT = 160;
|
|
65
|
+
const PRAXIS_ASSISTANT_CONTEXT_ITEM_LIMIT = 12;
|
|
66
|
+
const PRAXIS_ASSISTANT_CONTEXT_SCHEMA_FIELD_LIMIT = 40;
|
|
67
|
+
const PRAXIS_ASSISTANT_CONTEXT_ATTACHMENT_LIMIT = 8;
|
|
68
|
+
const REDACTED = '[redacted]';
|
|
69
|
+
const PROHIBITED_CONTEXT_KEYS = new Set([
|
|
70
|
+
'file',
|
|
71
|
+
'blob',
|
|
72
|
+
'bytes',
|
|
73
|
+
'base64',
|
|
74
|
+
'previewUrl',
|
|
75
|
+
'currentState',
|
|
76
|
+
'runtimeState',
|
|
77
|
+
'formValues',
|
|
78
|
+
'values',
|
|
79
|
+
'rows',
|
|
80
|
+
'rowData',
|
|
81
|
+
'pendingPatch',
|
|
82
|
+
'diagnostics',
|
|
83
|
+
'payload',
|
|
84
|
+
'config',
|
|
85
|
+
]);
|
|
86
|
+
const SENSITIVE_TEXT_PATTERNS = [
|
|
87
|
+
/\bBearer\s+[A-Za-z0-9\-._~+/]+=*/gi,
|
|
88
|
+
/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
|
|
89
|
+
/\bsk-[A-Za-z0-9]{20,}\b/g,
|
|
90
|
+
/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi,
|
|
91
|
+
/\b\d{3}\.?\d{3}\.?\d{3}-?\d{2}\b/g,
|
|
92
|
+
/\b(?:\+?55\s?)?(?:\(?\d{2}\)?\s?)?(?:9\s?)?\d{4}[-\s]?\d{4}\b/g,
|
|
93
|
+
/\b(?:api[_-]?key|token|secret|password|senha)\s*[:=]\s*['"]?[^'"\s,;]+/gi,
|
|
94
|
+
];
|
|
95
|
+
function sanitizePraxisAssistantText(value, limit = PRAXIS_ASSISTANT_CONTEXT_TEXT_LIMIT) {
|
|
96
|
+
if (value === null || value === undefined)
|
|
97
|
+
return '';
|
|
98
|
+
let sanitized = String(value);
|
|
99
|
+
for (const pattern of SENSITIVE_TEXT_PATTERNS) {
|
|
100
|
+
sanitized = sanitized.replace(pattern, REDACTED);
|
|
101
|
+
}
|
|
102
|
+
sanitized = sanitized.replace(/\s+/g, ' ').trim();
|
|
103
|
+
const safeLimit = Math.max(0, limit);
|
|
104
|
+
return sanitized.length > safeLimit ? `${sanitized.slice(0, Math.max(0, safeLimit - 3))}...` : sanitized;
|
|
105
|
+
}
|
|
106
|
+
function normalizePraxisAssistantAttachmentSummary(attachment) {
|
|
107
|
+
if (!isRecord(attachment))
|
|
108
|
+
return null;
|
|
109
|
+
const id = sanitizePraxisAssistantText(attachment['id'] ?? attachment['name'], 96);
|
|
110
|
+
const name = sanitizePraxisAssistantText(attachment['name'] ?? id, 120);
|
|
111
|
+
if (!id || !name)
|
|
112
|
+
return null;
|
|
113
|
+
return {
|
|
114
|
+
id,
|
|
115
|
+
name,
|
|
116
|
+
kind: sanitizePraxisAssistantText(attachment['kind'] ?? 'file', 32) || 'file',
|
|
117
|
+
mimeType: sanitizePraxisAssistantText(attachment['mimeType'], 96) || undefined,
|
|
118
|
+
sizeBytes: normalizeNonNegativeNumber(attachment['sizeBytes']),
|
|
119
|
+
source: sanitizePraxisAssistantText(attachment['source'], 64) || undefined,
|
|
120
|
+
hasPreview: Boolean(attachment['hasPreview']),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function normalizePraxisAssistantContextSnapshot(value) {
|
|
124
|
+
if (!isRecord(value)) {
|
|
125
|
+
throw new Error('Praxis assistant context snapshot requires an object.');
|
|
126
|
+
}
|
|
127
|
+
const identity = normalizeIdentity(value['identity']);
|
|
128
|
+
const contextItems = normalizeContextItems(value['contextItems']);
|
|
129
|
+
return {
|
|
130
|
+
identity,
|
|
131
|
+
target: normalizeTargetRef(value['target']) ?? undefined,
|
|
132
|
+
contextItems,
|
|
133
|
+
mode: normalizeMode(value['mode']),
|
|
134
|
+
authoringManifestRef: normalizeManifestRef(value['authoringManifestRef']) ?? undefined,
|
|
135
|
+
resourcePath: sanitizePraxisAssistantText(value['resourcePath'], 160) || undefined,
|
|
136
|
+
schemaFields: normalizeStringList(value['schemaFields'], PRAXIS_ASSISTANT_CONTEXT_SCHEMA_FIELD_LIMIT, 96),
|
|
137
|
+
dataProfileDigest: normalizeDigest(value['dataProfileDigest']) ?? undefined,
|
|
138
|
+
runtimeStateDigest: normalizeDigest(value['runtimeStateDigest']) ?? undefined,
|
|
139
|
+
capabilityRefs: normalizeCapabilityRefs(value['capabilityRefs']),
|
|
140
|
+
governanceHints: normalizeGovernanceHints(value['governanceHints']),
|
|
141
|
+
riskHints: normalizeGovernanceHints(value['riskHints']),
|
|
142
|
+
attachmentSummaries: normalizeAttachmentSummaries(value['attachmentSummaries']),
|
|
143
|
+
actions: normalizeActions(value['actions']),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function normalizeIdentity(value) {
|
|
147
|
+
if (!isRecord(value)) {
|
|
148
|
+
throw new Error('Praxis assistant context snapshot requires identity.');
|
|
149
|
+
}
|
|
150
|
+
const sessionId = sanitizePraxisAssistantText(value['sessionId'], 120);
|
|
151
|
+
const ownerId = sanitizePraxisAssistantText(value['ownerId'], 120);
|
|
152
|
+
const ownerType = sanitizePraxisAssistantText(value['ownerType'], 64);
|
|
153
|
+
if (!sessionId || !ownerId || !ownerType) {
|
|
154
|
+
throw new Error('Praxis assistant identity requires sessionId, ownerId and ownerType.');
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
sessionId,
|
|
158
|
+
ownerId,
|
|
159
|
+
ownerType,
|
|
160
|
+
componentId: sanitizePraxisAssistantText(value['componentId'], 120) || undefined,
|
|
161
|
+
componentType: sanitizePraxisAssistantText(value['componentType'], 64) || undefined,
|
|
162
|
+
routeKey: sanitizePraxisAssistantText(value['routeKey'], 160) || undefined,
|
|
163
|
+
tenantId: sanitizePraxisAssistantText(value['tenantId'], 96) || undefined,
|
|
164
|
+
env: sanitizePraxisAssistantText(value['env'], 48) || undefined,
|
|
165
|
+
userId: sanitizePraxisAssistantText(value['userId'], 96) || undefined,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function normalizeTargetRef(value) {
|
|
169
|
+
if (!isRecord(value))
|
|
170
|
+
return null;
|
|
171
|
+
const kind = sanitizePraxisAssistantText(value['kind'], 48);
|
|
172
|
+
const id = sanitizePraxisAssistantText(value['id'], 120);
|
|
173
|
+
if (!kind || !id)
|
|
174
|
+
return null;
|
|
175
|
+
return {
|
|
176
|
+
kind,
|
|
177
|
+
id,
|
|
178
|
+
label: sanitizePraxisAssistantText(value['label'], 120) || undefined,
|
|
179
|
+
path: sanitizePraxisAssistantText(value['path'], 160) || undefined,
|
|
180
|
+
schemaPath: sanitizePraxisAssistantText(value['schemaPath'], 160) || undefined,
|
|
181
|
+
metadata: normalizeMetadata(value['metadata']),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function normalizeContextItems(value) {
|
|
185
|
+
if (!Array.isArray(value))
|
|
186
|
+
return [];
|
|
187
|
+
return value
|
|
188
|
+
.slice(0, PRAXIS_ASSISTANT_CONTEXT_ITEM_LIMIT)
|
|
189
|
+
.map((item) => {
|
|
190
|
+
if (!isRecord(item))
|
|
191
|
+
return null;
|
|
192
|
+
const id = sanitizePraxisAssistantText(item['id'], 80);
|
|
193
|
+
const label = sanitizePraxisAssistantText(item['label'], 80);
|
|
194
|
+
const itemValue = sanitizePraxisAssistantText(item['value'], 160);
|
|
195
|
+
if (!id || !label || !itemValue)
|
|
196
|
+
return null;
|
|
197
|
+
return {
|
|
198
|
+
id,
|
|
199
|
+
label,
|
|
200
|
+
value: itemValue,
|
|
201
|
+
kind: sanitizePraxisAssistantText(item['kind'], 48) || undefined,
|
|
202
|
+
tone: normalizeTone(item['tone']),
|
|
203
|
+
};
|
|
204
|
+
})
|
|
205
|
+
.filter(isDefined);
|
|
206
|
+
}
|
|
207
|
+
function normalizeManifestRef(value) {
|
|
208
|
+
if (!isRecord(value))
|
|
209
|
+
return null;
|
|
210
|
+
const componentId = sanitizePraxisAssistantText(value['componentId'], 120);
|
|
211
|
+
if (!componentId)
|
|
212
|
+
return null;
|
|
213
|
+
return {
|
|
214
|
+
componentId,
|
|
215
|
+
version: sanitizePraxisAssistantText(value['version'], 64) || undefined,
|
|
216
|
+
source: sanitizePraxisAssistantText(value['source'], 160) || undefined,
|
|
217
|
+
hash: sanitizePraxisAssistantText(value['hash'], 96) || undefined,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function normalizeDigest(value) {
|
|
221
|
+
if (!isRecord(value))
|
|
222
|
+
return null;
|
|
223
|
+
return {
|
|
224
|
+
label: sanitizePraxisAssistantText(value['label'], 80) || undefined,
|
|
225
|
+
summary: sanitizePraxisAssistantText(value['summary'], 240) || undefined,
|
|
226
|
+
hash: sanitizePraxisAssistantText(value['hash'], 96) || undefined,
|
|
227
|
+
source: sanitizePraxisAssistantText(value['source'], 160) || undefined,
|
|
228
|
+
fields: normalizeStringList(value['fields'], 20, 96),
|
|
229
|
+
counts: normalizeNumberMap(value['counts']),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function normalizeCapabilityRefs(value) {
|
|
233
|
+
if (!Array.isArray(value))
|
|
234
|
+
return undefined;
|
|
235
|
+
const refs = value.slice(0, 20).map((item) => {
|
|
236
|
+
if (!isRecord(item))
|
|
237
|
+
return null;
|
|
238
|
+
const id = sanitizePraxisAssistantText(item['id'], 120);
|
|
239
|
+
if (!id)
|
|
240
|
+
return null;
|
|
241
|
+
return {
|
|
242
|
+
id,
|
|
243
|
+
label: sanitizePraxisAssistantText(item['label'], 120) || undefined,
|
|
244
|
+
source: sanitizePraxisAssistantText(item['source'], 160) || undefined,
|
|
245
|
+
risk: normalizeRisk(item['risk']),
|
|
246
|
+
};
|
|
247
|
+
}).filter(isDefined);
|
|
248
|
+
return refs.length ? refs : undefined;
|
|
249
|
+
}
|
|
250
|
+
function normalizeGovernanceHints(value) {
|
|
251
|
+
if (!Array.isArray(value))
|
|
252
|
+
return undefined;
|
|
253
|
+
const hints = value.slice(0, 12).map((item) => {
|
|
254
|
+
if (!isRecord(item))
|
|
255
|
+
return null;
|
|
256
|
+
const kind = sanitizePraxisAssistantText(item['kind'], 80);
|
|
257
|
+
if (!kind)
|
|
258
|
+
return null;
|
|
259
|
+
return {
|
|
260
|
+
kind,
|
|
261
|
+
label: sanitizePraxisAssistantText(item['label'], 120) || undefined,
|
|
262
|
+
reason: sanitizePraxisAssistantText(item['reason'], 200) || undefined,
|
|
263
|
+
target: sanitizePraxisAssistantText(item['target'], 160) || undefined,
|
|
264
|
+
risk: normalizeRisk(item['risk']),
|
|
265
|
+
};
|
|
266
|
+
}).filter(isDefined);
|
|
267
|
+
return hints.length ? hints : undefined;
|
|
268
|
+
}
|
|
269
|
+
function normalizeAttachmentSummaries(value) {
|
|
270
|
+
if (!Array.isArray(value))
|
|
271
|
+
return undefined;
|
|
272
|
+
const summaries = value
|
|
273
|
+
.slice(0, PRAXIS_ASSISTANT_CONTEXT_ATTACHMENT_LIMIT)
|
|
274
|
+
.map(normalizePraxisAssistantAttachmentSummary)
|
|
275
|
+
.filter((item) => !!item);
|
|
276
|
+
return summaries.length ? summaries : undefined;
|
|
277
|
+
}
|
|
278
|
+
function normalizeActions(value) {
|
|
279
|
+
if (!Array.isArray(value))
|
|
280
|
+
return undefined;
|
|
281
|
+
const actions = value.slice(0, 20).map((item) => {
|
|
282
|
+
if (!isRecord(item))
|
|
283
|
+
return null;
|
|
284
|
+
const id = sanitizePraxisAssistantText(item['id'], 120);
|
|
285
|
+
const kind = sanitizePraxisAssistantText(item['kind'], 80);
|
|
286
|
+
if (!id || !kind)
|
|
287
|
+
return null;
|
|
288
|
+
return {
|
|
289
|
+
id,
|
|
290
|
+
kind,
|
|
291
|
+
label: sanitizePraxisAssistantText(item['label'], 120) || undefined,
|
|
292
|
+
target: normalizeTargetRef(item['target']) ?? undefined,
|
|
293
|
+
capabilityRef: sanitizePraxisAssistantText(item['capabilityRef'], 120) || undefined,
|
|
294
|
+
risk: normalizeRisk(item['risk']),
|
|
295
|
+
handoffEndpoint: sanitizePraxisAssistantText(item['handoffEndpoint'], 180) || undefined,
|
|
296
|
+
description: sanitizePraxisAssistantText(item['description'], 200) || undefined,
|
|
297
|
+
};
|
|
298
|
+
}).filter(isDefined);
|
|
299
|
+
return actions.length ? actions : undefined;
|
|
300
|
+
}
|
|
301
|
+
function normalizeMetadata(value) {
|
|
302
|
+
if (!isRecord(value))
|
|
303
|
+
return undefined;
|
|
304
|
+
const metadata = {};
|
|
305
|
+
for (const [key, rawValue] of Object.entries(value)) {
|
|
306
|
+
if (PROHIBITED_CONTEXT_KEYS.has(key))
|
|
307
|
+
continue;
|
|
308
|
+
const safeKey = sanitizePraxisAssistantText(key, 64);
|
|
309
|
+
if (!safeKey)
|
|
310
|
+
continue;
|
|
311
|
+
if (typeof rawValue === 'string')
|
|
312
|
+
metadata[safeKey] = sanitizePraxisAssistantText(rawValue, 120);
|
|
313
|
+
if (typeof rawValue === 'number' && Number.isFinite(rawValue))
|
|
314
|
+
metadata[safeKey] = rawValue;
|
|
315
|
+
if (typeof rawValue === 'boolean')
|
|
316
|
+
metadata[safeKey] = rawValue;
|
|
317
|
+
}
|
|
318
|
+
return Object.keys(metadata).length ? metadata : undefined;
|
|
319
|
+
}
|
|
320
|
+
function normalizeStringList(value, limit, textLimit) {
|
|
321
|
+
if (!Array.isArray(value))
|
|
322
|
+
return undefined;
|
|
323
|
+
const list = value
|
|
324
|
+
.slice(0, limit)
|
|
325
|
+
.map((item) => sanitizePraxisAssistantText(item, textLimit))
|
|
326
|
+
.filter(Boolean);
|
|
327
|
+
return list.length ? list : undefined;
|
|
328
|
+
}
|
|
329
|
+
function normalizeNumberMap(value) {
|
|
330
|
+
if (!isRecord(value))
|
|
331
|
+
return undefined;
|
|
332
|
+
const counts = {};
|
|
333
|
+
for (const [key, rawValue] of Object.entries(value)) {
|
|
334
|
+
const safeKey = sanitizePraxisAssistantText(key, 64);
|
|
335
|
+
if (!safeKey || typeof rawValue !== 'number' || !Number.isFinite(rawValue))
|
|
336
|
+
continue;
|
|
337
|
+
counts[safeKey] = rawValue;
|
|
338
|
+
}
|
|
339
|
+
return Object.keys(counts).length ? counts : undefined;
|
|
340
|
+
}
|
|
341
|
+
function normalizeMode(value) {
|
|
342
|
+
const mode = sanitizePraxisAssistantText(value, 48);
|
|
343
|
+
if (mode === 'config'
|
|
344
|
+
|| mode === 'agentic-authoring'
|
|
345
|
+
|| mode === 'chat'
|
|
346
|
+
|| mode === 'diagnostic'
|
|
347
|
+
|| mode === 'review'
|
|
348
|
+
|| mode === 'inline-help') {
|
|
349
|
+
return mode;
|
|
350
|
+
}
|
|
351
|
+
return 'chat';
|
|
352
|
+
}
|
|
353
|
+
function normalizeRisk(value) {
|
|
354
|
+
const risk = sanitizePraxisAssistantText(value, 24);
|
|
355
|
+
return risk === 'low' || risk === 'medium' || risk === 'high' || risk === 'blocked'
|
|
356
|
+
? risk
|
|
357
|
+
: undefined;
|
|
358
|
+
}
|
|
359
|
+
function normalizeTone(value) {
|
|
360
|
+
const tone = sanitizePraxisAssistantText(value, 24);
|
|
361
|
+
return tone === 'neutral' || tone === 'info' || tone === 'success' || tone === 'warning' || tone === 'danger'
|
|
362
|
+
? tone
|
|
363
|
+
: undefined;
|
|
364
|
+
}
|
|
365
|
+
function normalizeNonNegativeNumber(value) {
|
|
366
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
367
|
+
}
|
|
368
|
+
function isRecord(value) {
|
|
369
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
370
|
+
}
|
|
371
|
+
function isDefined(value) {
|
|
372
|
+
return value !== null && value !== undefined;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function toPraxisAssistantConversationMessages(messages, limit = 12) {
|
|
376
|
+
return messages
|
|
377
|
+
.filter((message) => !!message.text?.trim() && isPraxisAssistantConversationMessageRole(message.role))
|
|
378
|
+
.slice(-Math.max(0, limit))
|
|
379
|
+
.map((message) => ({
|
|
380
|
+
id: message.id,
|
|
381
|
+
role: toPraxisAssistantConversationMessageRole(message.role),
|
|
382
|
+
text: message.text,
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
function isPraxisAssistantConversationMessageRole(role) {
|
|
386
|
+
return role === 'user' || role === 'assistant' || role === 'system' || role === 'status';
|
|
387
|
+
}
|
|
388
|
+
function toPraxisAssistantConversationMessageRole(role) {
|
|
389
|
+
switch (role) {
|
|
390
|
+
case 'user':
|
|
391
|
+
case 'assistant':
|
|
392
|
+
case 'system':
|
|
393
|
+
return role;
|
|
394
|
+
case 'status':
|
|
395
|
+
return 'assistant';
|
|
396
|
+
default:
|
|
397
|
+
return 'assistant';
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
63
401
|
/**
|
|
64
402
|
* Generated from praxis-config-starter/docs/ai/contracts/praxis-ai-api-contract-v1.1.openapi.yaml.
|
|
65
403
|
* Do not edit manually. Run praxis-config-starter/tools/contracts/generate-ai-contract-bindings.js.
|
|
66
404
|
*/
|
|
67
405
|
const AI_CONTRACT_VERSION = 'v1.1';
|
|
68
|
-
const AI_CONTRACT_SCHEMA_HASH = '
|
|
406
|
+
const AI_CONTRACT_SCHEMA_HASH = '33c545b58f49404695bf845d2094a5b2858538a54200745f1ecb3ca2d0628f01';
|
|
69
407
|
const AI_STREAM_EVENT_SCHEMA_VERSION = 'v1';
|
|
408
|
+
const AI_DOMAIN_CATALOG_CONTEXT_HINT_SCHEMA_VERSION = 'praxis.ai.context-hints.domain-catalog/v0.2';
|
|
70
409
|
const AI_STREAM_EVENT_TYPES = ['status', 'thought.step', 'heartbeat', 'result', 'error', 'cancelled'];
|
|
71
410
|
|
|
72
411
|
/**
|
|
@@ -113,6 +452,26 @@ class BaseAiAdapter {
|
|
|
113
452
|
}
|
|
114
453
|
}
|
|
115
454
|
|
|
455
|
+
/**
|
|
456
|
+
* @deprecated Component assistants must not infer governed authoring from user
|
|
457
|
+
* wording. Route through the backend semantic resolver and use this only for
|
|
458
|
+
* explicit, structured context provided by a canonical backend source.
|
|
459
|
+
*/
|
|
460
|
+
function shouldRoutePromptToGovernedDecision(prompt, contextHints, options = {}) {
|
|
461
|
+
void prompt;
|
|
462
|
+
void options;
|
|
463
|
+
const recommendedFlow = toRecord(contextHints?.['domainCatalog'])?.['recommendedAuthoringFlow'];
|
|
464
|
+
return recommendedFlow === 'shared_rule_authoring';
|
|
465
|
+
}
|
|
466
|
+
function normalizeAuthoringPrompt(prompt) {
|
|
467
|
+
return prompt.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
|
468
|
+
}
|
|
469
|
+
function toRecord(value) {
|
|
470
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
471
|
+
? value
|
|
472
|
+
: null;
|
|
473
|
+
}
|
|
474
|
+
|
|
116
475
|
class SchemaMinifierService {
|
|
117
476
|
/**
|
|
118
477
|
* Converts complete FieldSchemas into a minified version for AI context (Token optimized)
|
|
@@ -526,15 +885,6 @@ class PraxisAiService {
|
|
|
526
885
|
if (!this.isGeminiProvider()) {
|
|
527
886
|
return throwError(() => new Error('LLM provider not supported in browser mode.'));
|
|
528
887
|
}
|
|
529
|
-
const intentOnly = this.extractUserIntent(prompt);
|
|
530
|
-
// Only check for mock triggers in the specific user intent, not the full prompt template
|
|
531
|
-
if (intentOnly) {
|
|
532
|
-
const mockPatch = this.getMockPatch(intentOnly);
|
|
533
|
-
if (mockPatch) {
|
|
534
|
-
console.warn('PraxisAI: Using mock response for prompt intent:', intentOnly);
|
|
535
|
-
return of(mockPatch);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
888
|
if (!this.genAI)
|
|
539
889
|
return throwError(() => new Error('API Key not configured'));
|
|
540
890
|
const generationConfig = {
|
|
@@ -580,50 +930,6 @@ class PraxisAiService {
|
|
|
580
930
|
isMockMode() {
|
|
581
931
|
return !this.genAI;
|
|
582
932
|
}
|
|
583
|
-
// -------- Helpers --------
|
|
584
|
-
extractUserIntent(prompt) {
|
|
585
|
-
// Tenta capturar o conteúdo entre aspas após os marcadores conhecidos
|
|
586
|
-
const patterns = [
|
|
587
|
-
/ENTRADA DO USUÁRIO:\s*"([^"]+)"/i,
|
|
588
|
-
/PEDIDO DO USUÁRIO:\s*"([^"]+)"/i,
|
|
589
|
-
/USER INTENT:\s*"([^"]+)"/i
|
|
590
|
-
];
|
|
591
|
-
for (const regex of patterns) {
|
|
592
|
-
const match = prompt.match(regex);
|
|
593
|
-
if (match && match[1]) {
|
|
594
|
-
return match[1].trim();
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
return null;
|
|
598
|
-
}
|
|
599
|
-
getMockPatch(prompt) {
|
|
600
|
-
if (!prompt)
|
|
601
|
-
return null;
|
|
602
|
-
const p = prompt.toLowerCase();
|
|
603
|
-
// ... rest of the function
|
|
604
|
-
if (p.includes('compacta') && p.includes('bordas')) {
|
|
605
|
-
return {
|
|
606
|
-
patch: { appearance: { density: 'compact', borders: { showRowBorders: false } } },
|
|
607
|
-
explanation: 'Ajustei a tabela para o modo compacto e removi as bordas das linhas (MOCK).',
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
if (p.includes('seleção múltipla') && p.includes('filtros no cabeçalho')) {
|
|
611
|
-
return {
|
|
612
|
-
patch: {
|
|
613
|
-
behavior: {
|
|
614
|
-
selection: { enabled: true, type: 'multiple', mode: 'checkbox' },
|
|
615
|
-
filtering: { enabled: true, columnFilters: { enabled: true, position: 'header' } },
|
|
616
|
-
sorting: { enabled: true },
|
|
617
|
-
},
|
|
618
|
-
},
|
|
619
|
-
explanation: 'Ativei seleção múltipla, filtros no cabeçalho e ordenação (MOCK Power User).',
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
if (p.includes('paginação') || p.includes('paginacao')) {
|
|
623
|
-
return { patch: { behavior: { pagination: { enabled: false } } }, explanation: 'Desativei a paginação da tabela (MOCK).' };
|
|
624
|
-
}
|
|
625
|
-
return null;
|
|
626
|
-
}
|
|
627
933
|
listModels(apiKey) {
|
|
628
934
|
if (!this.isGeminiProvider()) {
|
|
629
935
|
return throwError(() => new Error('Model listing only available for Gemini.'));
|
|
@@ -690,6 +996,7 @@ class AiPatchStreamConnectionError extends Error {
|
|
|
690
996
|
}
|
|
691
997
|
const AI_BACKEND_CONFIG_STORE = new InjectionToken('AI_BACKEND_CONFIG_STORE');
|
|
692
998
|
const AI_BACKEND_STORAGE_OPTIONS = new InjectionToken('AI_BACKEND_STORAGE_OPTIONS');
|
|
999
|
+
const AI_BACKEND_ENDPOINTS = new InjectionToken('AI_BACKEND_ENDPOINTS');
|
|
693
1000
|
const DEFAULT_USER_ID_STORAGE_KEY = 'praxis.demoUserId';
|
|
694
1001
|
function buildDemoUserId() {
|
|
695
1002
|
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
@@ -723,18 +1030,20 @@ class AiBackendApiService {
|
|
|
723
1030
|
http = inject(HttpClient);
|
|
724
1031
|
globalConfigStore = inject(AI_BACKEND_CONFIG_STORE, { optional: true });
|
|
725
1032
|
storageOpts = inject(AI_BACKEND_STORAGE_OPTIONS, { optional: true });
|
|
726
|
-
|
|
727
|
-
|
|
1033
|
+
endpointOpts = inject(AI_BACKEND_ENDPOINTS, { optional: true });
|
|
1034
|
+
apiUrlConfig = inject(API_URL, { optional: true });
|
|
1035
|
+
fallbackAiBaseUrl = '/api/praxis/config/ai';
|
|
1036
|
+
fallbackAiContextBaseUrl = '/api/praxis/config/ai-context';
|
|
728
1037
|
getSuggestions(request) {
|
|
729
|
-
return this.http.post(`${this.
|
|
1038
|
+
return this.http.post(`${this.aiBaseUrl()}/suggestions`, request, { headers: this.buildHeaders() });
|
|
730
1039
|
}
|
|
731
1040
|
getPatch(request) {
|
|
732
1041
|
const normalizedRequest = {
|
|
733
|
-
...request,
|
|
1042
|
+
...this.normalizeLegacyPatchConversationIds(request),
|
|
734
1043
|
contractVersion: this.normalizeContractVersion(request.contractVersion),
|
|
735
1044
|
schemaHash: this.normalizeContractSchemaHash(request.schemaHash),
|
|
736
1045
|
};
|
|
737
|
-
return this.http.post(`${this.
|
|
1046
|
+
return this.http.post(`${this.aiBaseUrl()}/patch`, normalizedRequest, {
|
|
738
1047
|
headers: this.buildHeaders({
|
|
739
1048
|
[AI_CONTRACT_VERSION_HEADER]: normalizedRequest.contractVersion,
|
|
740
1049
|
[AI_CONTRACT_SCHEMA_HASH_HEADER]: normalizedRequest.schemaHash,
|
|
@@ -743,11 +1052,11 @@ class AiBackendApiService {
|
|
|
743
1052
|
}
|
|
744
1053
|
startPatchStream(request) {
|
|
745
1054
|
const normalizedRequest = {
|
|
746
|
-
...request,
|
|
1055
|
+
...this.normalizeLegacyPatchConversationIds(request),
|
|
747
1056
|
contractVersion: this.normalizeContractVersion(request.contractVersion),
|
|
748
1057
|
schemaHash: this.normalizeContractSchemaHash(request.schemaHash),
|
|
749
1058
|
};
|
|
750
|
-
return this.http.post(`${this.
|
|
1059
|
+
return this.http.post(`${this.aiBaseUrl()}/patch/stream/start`, normalizedRequest, {
|
|
751
1060
|
headers: this.buildHeaders({
|
|
752
1061
|
[AI_CONTRACT_VERSION_HEADER]: normalizedRequest.contractVersion,
|
|
753
1062
|
[AI_CONTRACT_SCHEMA_HASH_HEADER]: normalizedRequest.schemaHash,
|
|
@@ -764,7 +1073,7 @@ class AiBackendApiService {
|
|
|
764
1073
|
queryParams.push(`accessToken=${encodeURIComponent(accessToken.trim())}`);
|
|
765
1074
|
}
|
|
766
1075
|
const query = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
|
|
767
|
-
const streamPath = `${this.
|
|
1076
|
+
const streamPath = `${this.aiBaseUrl()}/patch/stream/${encodeURIComponent(streamId)}`;
|
|
768
1077
|
const endpoint = `${streamPath}${query}`;
|
|
769
1078
|
const probeEndpoint = `${streamPath}/probe${accessToken?.trim()
|
|
770
1079
|
? `?accessToken=${encodeURIComponent(accessToken.trim())}`
|
|
@@ -831,7 +1140,95 @@ class AiBackendApiService {
|
|
|
831
1140
|
};
|
|
832
1141
|
}
|
|
833
1142
|
cancelPatchStream(streamId, accessToken) {
|
|
834
|
-
const endpoint = `${this.
|
|
1143
|
+
const endpoint = `${this.aiBaseUrl()}/patch/stream/${encodeURIComponent(streamId)}/cancel${accessToken?.trim()
|
|
1144
|
+
? `?accessToken=${encodeURIComponent(accessToken.trim())}`
|
|
1145
|
+
: ''}`;
|
|
1146
|
+
return this.http.post(endpoint, {}, {
|
|
1147
|
+
headers: this.buildHeaders(),
|
|
1148
|
+
withCredentials: true,
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
startAgenticAuthoringTurnStream(request) {
|
|
1152
|
+
return this.http.post(`${this.aiBaseUrl()}/authoring/turn/stream/start`, request, {
|
|
1153
|
+
headers: this.buildHeaders(),
|
|
1154
|
+
withCredentials: true,
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
connectAgenticAuthoringTurnStream(streamId, lastEventId, accessToken) {
|
|
1158
|
+
const queryParams = [];
|
|
1159
|
+
if (lastEventId?.trim()) {
|
|
1160
|
+
queryParams.push(`lastEventId=${encodeURIComponent(lastEventId.trim())}`);
|
|
1161
|
+
}
|
|
1162
|
+
if (accessToken?.trim()) {
|
|
1163
|
+
queryParams.push(`accessToken=${encodeURIComponent(accessToken.trim())}`);
|
|
1164
|
+
}
|
|
1165
|
+
const query = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
|
|
1166
|
+
const streamPath = `${this.aiBaseUrl()}/authoring/turn/stream/${encodeURIComponent(streamId)}`;
|
|
1167
|
+
const endpoint = `${streamPath}${query}`;
|
|
1168
|
+
const probeEndpoint = `${streamPath}/probe${accessToken?.trim()
|
|
1169
|
+
? `?accessToken=${encodeURIComponent(accessToken.trim())}`
|
|
1170
|
+
: ''}`;
|
|
1171
|
+
let source = null;
|
|
1172
|
+
const events$ = new Observable((subscriber) => {
|
|
1173
|
+
let disposed = false;
|
|
1174
|
+
const openStream = async () => {
|
|
1175
|
+
if (typeof EventSource === 'undefined') {
|
|
1176
|
+
subscriber.error(new AiPatchStreamConnectionError('unsupported', 'EventSource is not supported in this environment.'));
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
const probeStatus = await this.probePatchStreamEndpoint(probeEndpoint);
|
|
1180
|
+
if (disposed) {
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
if (typeof probeStatus === 'number' && probeStatus >= 400) {
|
|
1184
|
+
subscriber.error(new AiPatchStreamConnectionError('http_status', `Agentic authoring stream probe failed with status ${probeStatus}.`, probeStatus));
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
source = new EventSource(endpoint, { withCredentials: true });
|
|
1188
|
+
source.onmessage = (messageEvent) => {
|
|
1189
|
+
try {
|
|
1190
|
+
const parsed = this.parsePatchStreamEnvelope(messageEvent.data);
|
|
1191
|
+
subscriber.next(parsed);
|
|
1192
|
+
}
|
|
1193
|
+
catch (error) {
|
|
1194
|
+
if (error instanceof AiPatchStreamConnectionError) {
|
|
1195
|
+
subscriber.error(error);
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
subscriber.error(new AiPatchStreamConnectionError('parse', 'Failed to parse agentic authoring stream event payload.'));
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
source.onerror = () => {
|
|
1202
|
+
if (!source) {
|
|
1203
|
+
subscriber.error(new AiPatchStreamConnectionError('transport', 'Agentic authoring stream connection error.'));
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
if (source.readyState === EventSource.CLOSED) {
|
|
1207
|
+
subscriber.error(new AiPatchStreamConnectionError('transport', 'Agentic authoring stream connection closed unexpectedly.'));
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
};
|
|
1211
|
+
void openStream();
|
|
1212
|
+
return () => {
|
|
1213
|
+
disposed = true;
|
|
1214
|
+
if (source) {
|
|
1215
|
+
source.close();
|
|
1216
|
+
source = null;
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
});
|
|
1220
|
+
return {
|
|
1221
|
+
events$,
|
|
1222
|
+
close: () => {
|
|
1223
|
+
if (source) {
|
|
1224
|
+
source.close();
|
|
1225
|
+
source = null;
|
|
1226
|
+
}
|
|
1227
|
+
},
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
cancelAgenticAuthoringTurnStream(streamId, accessToken) {
|
|
1231
|
+
const endpoint = `${this.aiBaseUrl()}/authoring/turn/stream/${encodeURIComponent(streamId)}/cancel${accessToken?.trim()
|
|
835
1232
|
? `?accessToken=${encodeURIComponent(accessToken.trim())}`
|
|
836
1233
|
: ''}`;
|
|
837
1234
|
return this.http.post(endpoint, {}, {
|
|
@@ -840,20 +1237,38 @@ class AiBackendApiService {
|
|
|
840
1237
|
});
|
|
841
1238
|
}
|
|
842
1239
|
listModels(request) {
|
|
843
|
-
return this.http.post(`${this.
|
|
1240
|
+
return this.http.post(`${this.aiBaseUrl()}/providers/models`, request, { headers: this.buildHeaders() });
|
|
844
1241
|
}
|
|
845
1242
|
listProviderCatalog() {
|
|
846
|
-
return this.http.get(`${this.
|
|
1243
|
+
return this.http.get(`${this.aiBaseUrl()}/providers/catalog`, { headers: this.buildHeaders() });
|
|
847
1244
|
}
|
|
848
1245
|
testProvider(request) {
|
|
849
|
-
return this.http.post(`${this.
|
|
1246
|
+
return this.http.post(`${this.aiBaseUrl()}/providers/test`, request, { headers: this.buildHeaders() });
|
|
850
1247
|
}
|
|
851
1248
|
getAiStatus() {
|
|
852
|
-
return this.http.get(`${this.
|
|
1249
|
+
return this.http.get(`${this.aiBaseUrl()}/status`, { headers: this.buildHeaders() });
|
|
853
1250
|
}
|
|
854
1251
|
getAiContext(componentId, componentType) {
|
|
855
1252
|
const params = new HttpParams({ fromObject: { componentType } });
|
|
856
|
-
return this.http.get(`${this.
|
|
1253
|
+
return this.http.get(`${this.aiContextBaseUrl()}/${componentId}`, { headers: this.buildHeaders(), params });
|
|
1254
|
+
}
|
|
1255
|
+
getAgenticAuthoringManifest(componentId) {
|
|
1256
|
+
return this.http.get(`${this.authoringManifestUrl(componentId)}`, { headers: this.buildHeaders() });
|
|
1257
|
+
}
|
|
1258
|
+
listAgenticAuthoringManifestTargets(componentId) {
|
|
1259
|
+
return this.http.get(`${this.authoringManifestUrl(componentId)}/editable-targets`, { headers: this.buildHeaders() });
|
|
1260
|
+
}
|
|
1261
|
+
listAgenticAuthoringManifestOperations(componentId) {
|
|
1262
|
+
return this.http.get(`${this.authoringManifestUrl(componentId)}/operations`, { headers: this.buildHeaders() });
|
|
1263
|
+
}
|
|
1264
|
+
resolveAgenticAuthoringManifestTarget(componentId, request) {
|
|
1265
|
+
return this.http.post(`${this.authoringManifestUrl(componentId)}/resolve-target`, request, { headers: this.buildHeaders() });
|
|
1266
|
+
}
|
|
1267
|
+
validateAgenticAuthoringManifestPlan(componentId, request) {
|
|
1268
|
+
return this.http.post(`${this.authoringManifestUrl(componentId)}/validate-plan`, request, { headers: this.buildHeaders() });
|
|
1269
|
+
}
|
|
1270
|
+
compileAgenticAuthoringManifestPatch(componentId, request) {
|
|
1271
|
+
return this.http.post(`${this.authoringManifestUrl(componentId)}/compile-patch`, request, { headers: this.buildHeaders() });
|
|
857
1272
|
}
|
|
858
1273
|
loadGlobalAiConfig() {
|
|
859
1274
|
const snapshot = this.globalConfigStore?.getAiConfigSnapshot?.();
|
|
@@ -895,6 +1310,69 @@ class AiBackendApiService {
|
|
|
895
1310
|
}
|
|
896
1311
|
return AI_INTENT_CONTRACT_SCHEMA_HASH;
|
|
897
1312
|
}
|
|
1313
|
+
normalizeLegacyPatchConversationIds(request) {
|
|
1314
|
+
const normalized = { ...request };
|
|
1315
|
+
if (!this.isUuid(normalized.sessionId)) {
|
|
1316
|
+
delete normalized.sessionId;
|
|
1317
|
+
}
|
|
1318
|
+
if (!this.isUuid(normalized.clientTurnId)) {
|
|
1319
|
+
delete normalized.clientTurnId;
|
|
1320
|
+
}
|
|
1321
|
+
return normalized;
|
|
1322
|
+
}
|
|
1323
|
+
isUuid(value) {
|
|
1324
|
+
return typeof value === 'string'
|
|
1325
|
+
&& /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value.trim());
|
|
1326
|
+
}
|
|
1327
|
+
authoringManifestUrl(componentId) {
|
|
1328
|
+
return `${this.aiBaseUrl()}/authoring/manifests/${encodeURIComponent(componentId)}`;
|
|
1329
|
+
}
|
|
1330
|
+
aiBaseUrl() {
|
|
1331
|
+
return this.resolveBaseUrl(this.endpointOpts?.aiBaseUrl, 'praxis/config/ai', this.fallbackAiBaseUrl);
|
|
1332
|
+
}
|
|
1333
|
+
aiContextBaseUrl() {
|
|
1334
|
+
return this.resolveBaseUrl(this.endpointOpts?.aiContextBaseUrl, 'praxis/config/ai-context', this.fallbackAiContextBaseUrl);
|
|
1335
|
+
}
|
|
1336
|
+
resolveBaseUrl(explicitUrl, apiRelativePath, fallbackUrl) {
|
|
1337
|
+
const explicit = this.normalizeBaseUrl(explicitUrl);
|
|
1338
|
+
if (explicit) {
|
|
1339
|
+
return explicit;
|
|
1340
|
+
}
|
|
1341
|
+
const apiBaseUrl = this.resolveApiBaseUrl();
|
|
1342
|
+
if (apiBaseUrl) {
|
|
1343
|
+
return this.joinUrl(apiBaseUrl, apiRelativePath);
|
|
1344
|
+
}
|
|
1345
|
+
return fallbackUrl;
|
|
1346
|
+
}
|
|
1347
|
+
resolveApiBaseUrl() {
|
|
1348
|
+
const defaultConfig = this.apiUrlConfig?.['default'];
|
|
1349
|
+
if (!defaultConfig) {
|
|
1350
|
+
return '';
|
|
1351
|
+
}
|
|
1352
|
+
return this.normalizeBaseUrl(this.buildApiUrl(defaultConfig));
|
|
1353
|
+
}
|
|
1354
|
+
joinUrl(baseUrl, relativePath) {
|
|
1355
|
+
const normalizedBase = this.normalizeBaseUrl(baseUrl);
|
|
1356
|
+
const normalizedPath = relativePath.replace(/^\/+/, '');
|
|
1357
|
+
return normalizedBase ? `${normalizedBase}/${normalizedPath}` : normalizedPath;
|
|
1358
|
+
}
|
|
1359
|
+
normalizeBaseUrl(value) {
|
|
1360
|
+
const normalized = value?.trim().replace(/\/+$/, '') ?? '';
|
|
1361
|
+
return normalized;
|
|
1362
|
+
}
|
|
1363
|
+
buildApiUrl(entry) {
|
|
1364
|
+
if (entry.fullUrl?.trim()) {
|
|
1365
|
+
return entry.fullUrl.trim();
|
|
1366
|
+
}
|
|
1367
|
+
let url = entry.baseUrl?.trim().replace(/\/+$/, '') ?? '';
|
|
1368
|
+
if (entry.path?.trim()) {
|
|
1369
|
+
url = this.joinUrl(url, entry.path.trim());
|
|
1370
|
+
}
|
|
1371
|
+
if (entry.version?.trim()) {
|
|
1372
|
+
url = this.joinUrl(url, entry.version.trim());
|
|
1373
|
+
}
|
|
1374
|
+
return url;
|
|
1375
|
+
}
|
|
898
1376
|
resolveHeaderMap(extra) {
|
|
899
1377
|
const allowLocalIdentityFallback = !!this.storageOpts?.allowLocalIdentityFallback;
|
|
900
1378
|
const defaults = this.storageOpts?.defaultHeaders
|
|
@@ -1077,7 +1555,7 @@ class AiResponseValidatorService {
|
|
|
1077
1555
|
code: 'MISSING_RULE_NAME'
|
|
1078
1556
|
});
|
|
1079
1557
|
}
|
|
1080
|
-
if (!response.targetType || !['field', 'section', 'action', 'row', 'column'].includes(response.targetType)) {
|
|
1558
|
+
if (!response.targetType || !['field', 'section', 'action', 'row', 'column', 'visualBlock'].includes(response.targetType)) {
|
|
1081
1559
|
errors.push({
|
|
1082
1560
|
field: 'targetType',
|
|
1083
1561
|
message: 'targetType inválido',
|
|
@@ -1211,6 +1689,10 @@ class AiResponseValidatorService {
|
|
|
1211
1689
|
return;
|
|
1212
1690
|
}
|
|
1213
1691
|
const args = Array.isArray(rawArgs) ? rawArgs : [rawArgs];
|
|
1692
|
+
const arityIssue = this.validateJsonLogicOperatorArity(operator, args.length);
|
|
1693
|
+
if (arityIssue) {
|
|
1694
|
+
issues.push({ message: arityIssue });
|
|
1695
|
+
}
|
|
1214
1696
|
args.forEach((arg) => this.walkJsonLogicNode(arg, false, issues));
|
|
1215
1697
|
return;
|
|
1216
1698
|
}
|
|
@@ -1247,6 +1729,49 @@ class AiResponseValidatorService {
|
|
|
1247
1729
|
'max',
|
|
1248
1730
|
].includes(operator);
|
|
1249
1731
|
}
|
|
1732
|
+
validateJsonLogicOperatorArity(operator, argumentCount) {
|
|
1733
|
+
const exactArity = {
|
|
1734
|
+
'==': 2,
|
|
1735
|
+
'===': 2,
|
|
1736
|
+
'!=': 2,
|
|
1737
|
+
'!==': 2,
|
|
1738
|
+
'>': 2,
|
|
1739
|
+
'>=': 2,
|
|
1740
|
+
'<': 2,
|
|
1741
|
+
'<=': 2,
|
|
1742
|
+
'!': 1,
|
|
1743
|
+
'!!': 1,
|
|
1744
|
+
in: 2,
|
|
1745
|
+
contains: 2,
|
|
1746
|
+
startsWith: 2,
|
|
1747
|
+
endsWith: 2,
|
|
1748
|
+
'-': 2,
|
|
1749
|
+
'/': 2,
|
|
1750
|
+
'%': 2,
|
|
1751
|
+
};
|
|
1752
|
+
const expected = exactArity[operator];
|
|
1753
|
+
if (expected !== undefined && argumentCount !== expected) {
|
|
1754
|
+
return `Operator ${operator} requires exactly ${expected} argument(s).`;
|
|
1755
|
+
}
|
|
1756
|
+
if ((operator === 'and' || operator === 'or') && argumentCount < 2) {
|
|
1757
|
+
return `Operator ${operator} requires at least 2 arguments.`;
|
|
1758
|
+
}
|
|
1759
|
+
if (operator === 'if' && argumentCount < 3) {
|
|
1760
|
+
return 'Operator if requires at least 3 arguments.';
|
|
1761
|
+
}
|
|
1762
|
+
if ((operator === 'cat'
|
|
1763
|
+
|| operator === '+'
|
|
1764
|
+
|| operator === '*'
|
|
1765
|
+
|| operator === 'min'
|
|
1766
|
+
|| operator === 'max')
|
|
1767
|
+
&& argumentCount < 1) {
|
|
1768
|
+
return `Operator ${operator} requires at least 1 argument.`;
|
|
1769
|
+
}
|
|
1770
|
+
if (operator === 'substr' && (argumentCount < 2 || argumentCount > 3)) {
|
|
1771
|
+
return 'Operator substr requires 2 or 3 arguments.';
|
|
1772
|
+
}
|
|
1773
|
+
return null;
|
|
1774
|
+
}
|
|
1250
1775
|
collectVarPaths(value, collector) {
|
|
1251
1776
|
if (Array.isArray(value)) {
|
|
1252
1777
|
value.forEach((item) => this.collectVarPaths(item, collector));
|
|
@@ -1460,7 +1985,10 @@ class PraxisAssistantTurnController {
|
|
|
1460
1985
|
const current = this.stateSubject.value;
|
|
1461
1986
|
const effectiveAction = this.resolveSubmitAction(action, current);
|
|
1462
1987
|
const clientTurnId = this.createId('turn');
|
|
1463
|
-
const
|
|
1988
|
+
const displayPrompt = typeof effectiveAction.displayPrompt === 'string'
|
|
1989
|
+
? effectiveAction.displayPrompt.trim()
|
|
1990
|
+
: '';
|
|
1991
|
+
const userMessage = this.buildMessage('user', displayPrompt || normalized, true);
|
|
1464
1992
|
this.patchState({
|
|
1465
1993
|
state: 'processing',
|
|
1466
1994
|
phase: 'contextualize',
|
|
@@ -1542,7 +2070,18 @@ class PraxisAssistantTurnController {
|
|
|
1542
2070
|
if (!handler) {
|
|
1543
2071
|
return of(this.snapshot());
|
|
1544
2072
|
}
|
|
1545
|
-
|
|
2073
|
+
let flowResult;
|
|
2074
|
+
try {
|
|
2075
|
+
flowResult = handler.call(this.flow, request);
|
|
2076
|
+
}
|
|
2077
|
+
catch (error) {
|
|
2078
|
+
this.applyResult(this.buildFlowErrorResult(error));
|
|
2079
|
+
return of(this.snapshot());
|
|
2080
|
+
}
|
|
2081
|
+
return this.toObservable(flowResult).pipe(tap((result) => this.applyResult(result)), map$1(() => this.snapshot()), catchError$1((error) => {
|
|
2082
|
+
this.applyResult(this.buildFlowErrorResult(error));
|
|
2083
|
+
return of(this.snapshot());
|
|
2084
|
+
}));
|
|
1546
2085
|
}
|
|
1547
2086
|
buildRequest(partial) {
|
|
1548
2087
|
const current = this.stateSubject.value;
|
|
@@ -1653,7 +2192,6 @@ class PraxisAssistantTurnController {
|
|
|
1653
2192
|
}
|
|
1654
2193
|
return {
|
|
1655
2194
|
...action,
|
|
1656
|
-
kind: 'clarify',
|
|
1657
2195
|
contextHints: {
|
|
1658
2196
|
...(action.contextHints ?? {}),
|
|
1659
2197
|
pendingClarification: current.pendingClarification,
|
|
@@ -1681,7 +2219,7 @@ class PraxisAssistantTurnController {
|
|
|
1681
2219
|
return lastUserMessage?.text ?? '';
|
|
1682
2220
|
}
|
|
1683
2221
|
resolveMessageRole(result) {
|
|
1684
|
-
return result.state === 'error' ? 'error' :
|
|
2222
|
+
return result.state === 'error' ? 'error' : 'assistant';
|
|
1685
2223
|
}
|
|
1686
2224
|
resolvePhase(result) {
|
|
1687
2225
|
if (result.state === 'clarification')
|
|
@@ -1737,7 +2275,212 @@ class PraxisAssistantTurnController {
|
|
|
1737
2275
|
toObservable(value) {
|
|
1738
2276
|
return isObservable(value) ? value : from(value);
|
|
1739
2277
|
}
|
|
2278
|
+
buildFlowErrorResult(error) {
|
|
2279
|
+
const detail = this.describeFlowError(error);
|
|
2280
|
+
const message = detail
|
|
2281
|
+
? `Nao consegui concluir este pedido. ${detail}`
|
|
2282
|
+
: 'Nao consegui concluir este pedido. Tente novamente ou revise o contexto ativo.';
|
|
2283
|
+
return {
|
|
2284
|
+
state: 'error',
|
|
2285
|
+
phase: this.stateSubject.value.phase,
|
|
2286
|
+
assistantMessage: message,
|
|
2287
|
+
errorText: message,
|
|
2288
|
+
canApply: this.stateSubject.value.canApply,
|
|
2289
|
+
diagnostics: {
|
|
2290
|
+
error: this.serializeFlowError(error),
|
|
2291
|
+
},
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
describeFlowError(error) {
|
|
2295
|
+
const record = this.toRecord(error);
|
|
2296
|
+
const status = this.toNumber(record?.['status']);
|
|
2297
|
+
const statusText = this.toString(record?.['statusText']);
|
|
2298
|
+
const message = this.toString(record?.['message']);
|
|
2299
|
+
if (status || statusText) {
|
|
2300
|
+
return `A chamada falhou${status ? ` com HTTP ${status}` : ''}${statusText ? ` (${statusText})` : ''}.`;
|
|
2301
|
+
}
|
|
2302
|
+
if (message) {
|
|
2303
|
+
return message;
|
|
2304
|
+
}
|
|
2305
|
+
return '';
|
|
2306
|
+
}
|
|
2307
|
+
serializeFlowError(error) {
|
|
2308
|
+
const record = this.toRecord(error);
|
|
2309
|
+
if (!record) {
|
|
2310
|
+
return { message: String(error ?? 'unknown') };
|
|
2311
|
+
}
|
|
2312
|
+
return {
|
|
2313
|
+
name: this.toString(record['name']) || undefined,
|
|
2314
|
+
message: this.toString(record['message']) || undefined,
|
|
2315
|
+
status: this.toNumber(record['status']) ?? undefined,
|
|
2316
|
+
statusText: this.toString(record['statusText']) || undefined,
|
|
2317
|
+
url: this.toString(record['url']) || undefined,
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
toRecord(value) {
|
|
2321
|
+
return value && typeof value === 'object'
|
|
2322
|
+
? value
|
|
2323
|
+
: null;
|
|
2324
|
+
}
|
|
2325
|
+
toString(value) {
|
|
2326
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
2327
|
+
}
|
|
2328
|
+
toNumber(value) {
|
|
2329
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
class PraxisAssistantSessionRegistryService {
|
|
2334
|
+
sessionsState = signal([], ...(ngDevMode ? [{ debugName: "sessionsState" }] : []));
|
|
2335
|
+
sessions = this.sessionsState.asReadonly();
|
|
2336
|
+
activeSession = computed(() => this.sessions().find((session) => session.visibility === 'active') ?? null, ...(ngDevMode ? [{ debugName: "activeSession" }] : []));
|
|
2337
|
+
minimizedSessions = computed(() => this.sessions().filter((session) => session.visibility === 'minimized'), ...(ngDevMode ? [{ debugName: "minimizedSessions" }] : []));
|
|
2338
|
+
upsertSession(descriptor) {
|
|
2339
|
+
const normalized = this.normalizeDescriptor(descriptor);
|
|
2340
|
+
const existing = this.sessionsState().find((session) => session.id === normalized.id);
|
|
2341
|
+
const now = normalized.updatedAt || new Date().toISOString();
|
|
2342
|
+
const next = {
|
|
2343
|
+
...existing,
|
|
2344
|
+
id: normalized.id,
|
|
2345
|
+
ownerId: normalized.ownerId,
|
|
2346
|
+
ownerType: normalized.ownerType,
|
|
2347
|
+
title: normalized.title,
|
|
2348
|
+
summary: normalized.summary,
|
|
2349
|
+
mode: normalized.mode,
|
|
2350
|
+
state: normalized.state,
|
|
2351
|
+
visibility: normalized.visibility,
|
|
2352
|
+
contextItems: normalized.contextItems,
|
|
2353
|
+
contextSnapshot: normalized.contextSnapshot,
|
|
2354
|
+
badge: normalized.badge,
|
|
2355
|
+
icon: normalized.icon,
|
|
2356
|
+
createdAt: existing?.createdAt ?? now,
|
|
2357
|
+
updatedAt: now,
|
|
2358
|
+
};
|
|
2359
|
+
this.sessionsState.update((sessions) => this.sortSessions([
|
|
2360
|
+
...sessions.filter((session) => session.id !== next.id),
|
|
2361
|
+
next,
|
|
2362
|
+
], next.visibility === 'active' ? next.id : null));
|
|
2363
|
+
return next;
|
|
2364
|
+
}
|
|
2365
|
+
upsertContextSession(contextSnapshot, descriptor = {}) {
|
|
2366
|
+
const normalizedContext = normalizePraxisAssistantContextSnapshot(contextSnapshot);
|
|
2367
|
+
const identity = normalizedContext.identity;
|
|
2368
|
+
return this.upsertSession({
|
|
2369
|
+
...descriptor,
|
|
2370
|
+
id: identity.sessionId,
|
|
2371
|
+
ownerId: identity.ownerId,
|
|
2372
|
+
ownerType: identity.ownerType,
|
|
2373
|
+
title: descriptor.title?.trim()
|
|
2374
|
+
|| normalizedContext.target?.label
|
|
2375
|
+
|| identity.componentId
|
|
2376
|
+
|| identity.ownerType
|
|
2377
|
+
|| 'Praxis assistant',
|
|
2378
|
+
mode: descriptor.mode || normalizedContext.mode,
|
|
2379
|
+
contextSnapshot: normalizedContext,
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
openSession(sessionId) {
|
|
2383
|
+
return this.setVisibility(sessionId, 'active');
|
|
2384
|
+
}
|
|
2385
|
+
openContextSession(identity) {
|
|
2386
|
+
return this.openSession(this.resolveSessionId(identity));
|
|
2387
|
+
}
|
|
2388
|
+
minimizeSession(sessionId) {
|
|
2389
|
+
return this.setVisibility(sessionId, 'minimized');
|
|
2390
|
+
}
|
|
2391
|
+
minimizeContextSession(identity) {
|
|
2392
|
+
return this.minimizeSession(this.resolveSessionId(identity));
|
|
2393
|
+
}
|
|
2394
|
+
removeSession(sessionId) {
|
|
2395
|
+
this.sessionsState.update((sessions) => sessions.filter((session) => session.id !== sessionId));
|
|
2396
|
+
}
|
|
2397
|
+
removeContextSession(identity) {
|
|
2398
|
+
this.removeSession(this.resolveSessionId(identity));
|
|
2399
|
+
}
|
|
2400
|
+
getSession(sessionId) {
|
|
2401
|
+
return this.sessionsState().find((session) => session.id === sessionId) ?? null;
|
|
2402
|
+
}
|
|
2403
|
+
getContextSession(identity) {
|
|
2404
|
+
return this.getSession(this.resolveSessionId(identity));
|
|
2405
|
+
}
|
|
2406
|
+
clear() {
|
|
2407
|
+
this.sessionsState.set([]);
|
|
2408
|
+
}
|
|
2409
|
+
setVisibility(sessionId, visibility) {
|
|
2410
|
+
const session = this.getSession(sessionId);
|
|
2411
|
+
if (!session)
|
|
2412
|
+
return null;
|
|
2413
|
+
return this.upsertSession({ ...session, visibility });
|
|
2414
|
+
}
|
|
2415
|
+
resolveSessionId(identity) {
|
|
2416
|
+
if (typeof identity === 'string')
|
|
2417
|
+
return identity;
|
|
2418
|
+
if ('identity' in identity)
|
|
2419
|
+
return identity.identity.sessionId;
|
|
2420
|
+
return identity.sessionId;
|
|
2421
|
+
}
|
|
2422
|
+
normalizeDescriptor(descriptor) {
|
|
2423
|
+
const id = descriptor.id?.trim();
|
|
2424
|
+
const ownerId = descriptor.ownerId?.trim();
|
|
2425
|
+
const ownerType = descriptor.ownerType?.trim();
|
|
2426
|
+
if (!id || !ownerId || !ownerType) {
|
|
2427
|
+
throw new Error('Praxis assistant sessions require id, ownerId and ownerType.');
|
|
2428
|
+
}
|
|
2429
|
+
const contextSnapshot = descriptor.contextSnapshot
|
|
2430
|
+
? normalizePraxisAssistantContextSnapshot(descriptor.contextSnapshot)
|
|
2431
|
+
: null;
|
|
2432
|
+
if (contextSnapshot) {
|
|
2433
|
+
this.assertContextIdentity(id, ownerId, ownerType, contextSnapshot);
|
|
2434
|
+
}
|
|
2435
|
+
const contextItems = descriptor.contextItems
|
|
2436
|
+
? [...descriptor.contextItems]
|
|
2437
|
+
: this.toShellContextItems(contextSnapshot);
|
|
2438
|
+
return {
|
|
2439
|
+
id,
|
|
2440
|
+
ownerId,
|
|
2441
|
+
ownerType,
|
|
2442
|
+
title: descriptor.title?.trim() || 'Praxis assistant',
|
|
2443
|
+
summary: descriptor.summary?.trim() || '',
|
|
2444
|
+
mode: descriptor.mode || 'chat',
|
|
2445
|
+
state: descriptor.state || 'idle',
|
|
2446
|
+
visibility: descriptor.visibility || 'minimized',
|
|
2447
|
+
contextItems,
|
|
2448
|
+
contextSnapshot,
|
|
2449
|
+
badge: descriptor.badge?.trim() || '',
|
|
2450
|
+
icon: descriptor.icon?.trim() || '',
|
|
2451
|
+
updatedAt: descriptor.updatedAt?.trim() || null,
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
assertContextIdentity(id, ownerId, ownerType, contextSnapshot) {
|
|
2455
|
+
const identity = contextSnapshot.identity;
|
|
2456
|
+
if (identity.sessionId !== id || identity.ownerId !== ownerId || identity.ownerType !== ownerType) {
|
|
2457
|
+
throw new Error('Praxis assistant session context identity must match id, ownerId and ownerType.');
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
toShellContextItems(contextSnapshot) {
|
|
2461
|
+
if (!contextSnapshot)
|
|
2462
|
+
return [];
|
|
2463
|
+
return contextSnapshot.contextItems.map((item) => ({
|
|
2464
|
+
id: item.id,
|
|
2465
|
+
label: item.label,
|
|
2466
|
+
value: item.value,
|
|
2467
|
+
kind: item.kind,
|
|
2468
|
+
}));
|
|
2469
|
+
}
|
|
2470
|
+
sortSessions(sessions, activeSessionId) {
|
|
2471
|
+
return sessions
|
|
2472
|
+
.map((session) => activeSessionId && session.id !== activeSessionId
|
|
2473
|
+
? { ...session, visibility: 'minimized' }
|
|
2474
|
+
: session)
|
|
2475
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
2476
|
+
}
|
|
2477
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAssistantSessionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2478
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAssistantSessionRegistryService, providedIn: 'root' });
|
|
1740
2479
|
}
|
|
2480
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAssistantSessionRegistryService, decorators: [{
|
|
2481
|
+
type: Injectable,
|
|
2482
|
+
args: [{ providedIn: 'root' }]
|
|
2483
|
+
}] });
|
|
1741
2484
|
|
|
1742
2485
|
const HISTORY_INDEX_PREFIX = 'praxis.ai.history.index';
|
|
1743
2486
|
const HISTORY_SESSION_PREFIX = 'praxis.ai.history.session';
|
|
@@ -2436,7 +3179,7 @@ class PraxisAiAssistantComponent {
|
|
|
2436
3179
|
: undefined;
|
|
2437
3180
|
const normalizedRuntimeState = runtimeState !== undefined ? this.toAiJsonObject(runtimeState) : undefined;
|
|
2438
3181
|
const normalizedSuggestedPatch = suggestion?.patch ? this.toAiJsonObject(suggestion.patch) : undefined;
|
|
2439
|
-
const normalizedContextHints = this.
|
|
3182
|
+
const normalizedContextHints = this.enrichDomainCatalogAuthoringHints(mergedContextHints);
|
|
2440
3183
|
const patchRequest = {
|
|
2441
3184
|
componentId,
|
|
2442
3185
|
componentType,
|
|
@@ -4337,8 +5080,12 @@ class PraxisAiAssistantComponent {
|
|
|
4337
5080
|
return null;
|
|
4338
5081
|
}
|
|
4339
5082
|
if (event.type === 'heartbeat') {
|
|
5083
|
+
if (message) {
|
|
5084
|
+
this.aiExplanation = message;
|
|
5085
|
+
}
|
|
4340
5086
|
this.streamTerminalState = 'in_progress';
|
|
4341
5087
|
this.processingInfoVisible = true;
|
|
5088
|
+
this.setState('processing');
|
|
4342
5089
|
return null;
|
|
4343
5090
|
}
|
|
4344
5091
|
if (event.type === 'result') {
|
|
@@ -4433,6 +5180,10 @@ class PraxisAiAssistantComponent {
|
|
|
4433
5180
|
if (typeof direct === 'string' && direct.trim()) {
|
|
4434
5181
|
return direct.trim();
|
|
4435
5182
|
}
|
|
5183
|
+
const summary = payload['summary'];
|
|
5184
|
+
if (typeof summary === 'string' && summary.trim()) {
|
|
5185
|
+
return summary.trim();
|
|
5186
|
+
}
|
|
4436
5187
|
const nested = this.asRecord(payload['error']);
|
|
4437
5188
|
const nestedMsg = nested?.['message'];
|
|
4438
5189
|
if (typeof nestedMsg === 'string' && nestedMsg.trim()) {
|
|
@@ -5138,15 +5889,15 @@ class PraxisAiAssistantComponent {
|
|
|
5138
5889
|
.filter((value) => value.length > 0);
|
|
5139
5890
|
}
|
|
5140
5891
|
resolveBadgeContextHints(contextHints) {
|
|
5141
|
-
|
|
5892
|
+
const candidate = this.toAiJsonObject(contextHints);
|
|
5893
|
+
if (!Object.keys(candidate).length)
|
|
5142
5894
|
return null;
|
|
5143
|
-
const badge = this.asRecord(
|
|
5895
|
+
const badge = this.asRecord(candidate['badge']);
|
|
5144
5896
|
if (badge)
|
|
5145
5897
|
return this.toAiJsonObject(badge);
|
|
5146
|
-
const candidate = contextHints;
|
|
5147
5898
|
const hasBadgeKeys = ['field', 'values', 'valueColorMap', 'palette', 'inferredType', 'explicitType']
|
|
5148
5899
|
.some((key) => Object.prototype.hasOwnProperty.call(candidate, key));
|
|
5149
|
-
return hasBadgeKeys ? candidate : null;
|
|
5900
|
+
return hasBadgeKeys ? this.toAiJsonObject(candidate) : null;
|
|
5150
5901
|
}
|
|
5151
5902
|
hasBadgeValues(badgeHints) {
|
|
5152
5903
|
if (!badgeHints)
|
|
@@ -5190,6 +5941,25 @@ class PraxisAiAssistantComponent {
|
|
|
5190
5941
|
}
|
|
5191
5942
|
return this.toClarificationContextHints(merged) ?? null;
|
|
5192
5943
|
}
|
|
5944
|
+
enrichDomainCatalogAuthoringHints(value) {
|
|
5945
|
+
const normalized = this.toClarificationContextHints(value);
|
|
5946
|
+
if (!normalized)
|
|
5947
|
+
return undefined;
|
|
5948
|
+
const domainCatalog = this.asRecord(normalized['domainCatalog']);
|
|
5949
|
+
const recommendedAuthoringFlow = domainCatalog?.['recommendedAuthoringFlow'];
|
|
5950
|
+
if (typeof recommendedAuthoringFlow !== 'string' || !recommendedAuthoringFlow.trim()) {
|
|
5951
|
+
return normalized;
|
|
5952
|
+
}
|
|
5953
|
+
const flowId = recommendedAuthoringFlow.trim();
|
|
5954
|
+
const enriched = this.toAiJsonObject(normalized);
|
|
5955
|
+
enriched['authoringFlow'] = this.toAiJsonObject({
|
|
5956
|
+
flowId,
|
|
5957
|
+
source: 'domainCatalog.recommendedAuthoringFlow',
|
|
5958
|
+
reviewRequired: true,
|
|
5959
|
+
materializeOnlyAfterReview: true,
|
|
5960
|
+
});
|
|
5961
|
+
return this.toClarificationContextHints(enriched);
|
|
5962
|
+
}
|
|
5193
5963
|
setResourcePathHint(resourcePath) {
|
|
5194
5964
|
const raw = (resourcePath || '').trim();
|
|
5195
5965
|
const normalized = this.normalizeResourcePath(raw);
|
|
@@ -5865,14 +6635,15 @@ const DEFAULT_LAYOUT = {
|
|
|
5865
6635
|
const DEFAULT_LABELS = {
|
|
5866
6636
|
title: 'Assistente de IA',
|
|
5867
6637
|
subtitle: 'Revise o resultado gerado antes de aplicar.',
|
|
5868
|
-
close: '
|
|
6638
|
+
close: 'Minimizar assistente',
|
|
5869
6639
|
prompt: 'Prompt',
|
|
5870
6640
|
promptPlaceholder: 'Descreva o que você quer criar ou alterar.',
|
|
5871
6641
|
emptyConversation: 'Diga o que você quer criar ou alterar.',
|
|
5872
|
-
submit: '
|
|
5873
|
-
apply: 'Aplicar',
|
|
6642
|
+
submit: 'Interpretar pedido',
|
|
6643
|
+
apply: 'Aplicar ajuste',
|
|
5874
6644
|
conversationAria: 'Conversa com IA',
|
|
5875
6645
|
quickRepliesAria: 'Respostas rápidas',
|
|
6646
|
+
quickReplyDetails: 'Detalhes técnicos',
|
|
5876
6647
|
dragHandleAria: 'Mover assistente de IA',
|
|
5877
6648
|
resizeHandleAria: 'Redimensionar assistente de IA',
|
|
5878
6649
|
contextAria: 'Contexto ativo',
|
|
@@ -5911,10 +6682,15 @@ class PraxisAiAssistantShellComponent {
|
|
|
5911
6682
|
panelTestId = '';
|
|
5912
6683
|
submitTestId = '';
|
|
5913
6684
|
applyTestId = '';
|
|
6685
|
+
primaryAction = null;
|
|
6686
|
+
secondaryActions = [];
|
|
6687
|
+
governanceActions = [];
|
|
5914
6688
|
busy = false;
|
|
5915
6689
|
canSubmit = true;
|
|
5916
6690
|
canApply = false;
|
|
5917
6691
|
submitOnEnter = true;
|
|
6692
|
+
showAttachAction = true;
|
|
6693
|
+
enablePastedAttachments = true;
|
|
5918
6694
|
enableFileAttachments = false;
|
|
5919
6695
|
attachmentAccept = '';
|
|
5920
6696
|
attachmentMultiple = true;
|
|
@@ -5927,6 +6703,9 @@ class PraxisAiAssistantShellComponent {
|
|
|
5927
6703
|
promptChange = new EventEmitter();
|
|
5928
6704
|
submitPrompt = new EventEmitter();
|
|
5929
6705
|
apply = new EventEmitter();
|
|
6706
|
+
retryTurn = new EventEmitter();
|
|
6707
|
+
cancelTurn = new EventEmitter();
|
|
6708
|
+
shellAction = new EventEmitter();
|
|
5930
6709
|
close = new EventEmitter();
|
|
5931
6710
|
attach = new EventEmitter();
|
|
5932
6711
|
attachmentsPasted = new EventEmitter();
|
|
@@ -5943,6 +6722,7 @@ class PraxisAiAssistantShellComponent {
|
|
|
5943
6722
|
currentPrompt = '';
|
|
5944
6723
|
resolvedLabels = DEFAULT_LABELS;
|
|
5945
6724
|
currentLayout = { ...DEFAULT_LAYOUT };
|
|
6725
|
+
resizeHandles = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];
|
|
5946
6726
|
pointerSession = null;
|
|
5947
6727
|
ownedPreviewUrls = new Set();
|
|
5948
6728
|
onWindowPointerMove = (event) => this.handlePointerMove(event);
|
|
@@ -5989,6 +6769,9 @@ class PraxisAiAssistantShellComponent {
|
|
|
5989
6769
|
this.onSubmit();
|
|
5990
6770
|
}
|
|
5991
6771
|
onPromptPaste(event) {
|
|
6772
|
+
if (!this.enablePastedAttachments) {
|
|
6773
|
+
return;
|
|
6774
|
+
}
|
|
5992
6775
|
const files = Array.from(event.clipboardData?.files ?? [])
|
|
5993
6776
|
.filter((file) => file.type.startsWith('image/'));
|
|
5994
6777
|
if (this.busy || !files.length) {
|
|
@@ -6023,11 +6806,732 @@ class PraxisAiAssistantShellComponent {
|
|
|
6023
6806
|
return;
|
|
6024
6807
|
this.apply.emit();
|
|
6025
6808
|
}
|
|
6809
|
+
renderMessageText(text) {
|
|
6810
|
+
const lines = (text ?? '').replace(/\r\n?/g, '\n').split('\n');
|
|
6811
|
+
const html = [];
|
|
6812
|
+
let listOpen = false;
|
|
6813
|
+
const closeList = () => {
|
|
6814
|
+
if (listOpen) {
|
|
6815
|
+
html.push('</ul>');
|
|
6816
|
+
listOpen = false;
|
|
6817
|
+
}
|
|
6818
|
+
};
|
|
6819
|
+
for (const rawLine of lines) {
|
|
6820
|
+
const line = rawLine.trim();
|
|
6821
|
+
if (!line) {
|
|
6822
|
+
closeList();
|
|
6823
|
+
continue;
|
|
6824
|
+
}
|
|
6825
|
+
const heading = line.match(/^(#{1,3})\s+(.+)$/);
|
|
6826
|
+
if (heading) {
|
|
6827
|
+
closeList();
|
|
6828
|
+
const level = Math.min(heading[1].length + 2, 5);
|
|
6829
|
+
html.push(`<h${level}>${this.renderInlineMarkdown(heading[2])}</h${level}>`);
|
|
6830
|
+
continue;
|
|
6831
|
+
}
|
|
6832
|
+
const bullet = line.match(/^[-*]\s+(.+)$/);
|
|
6833
|
+
if (bullet) {
|
|
6834
|
+
if (!listOpen) {
|
|
6835
|
+
html.push('<ul>');
|
|
6836
|
+
listOpen = true;
|
|
6837
|
+
}
|
|
6838
|
+
html.push(`<li>${this.renderInlineMarkdown(bullet[1])}</li>`);
|
|
6839
|
+
continue;
|
|
6840
|
+
}
|
|
6841
|
+
closeList();
|
|
6842
|
+
html.push(`<p>${this.renderInlineMarkdown(line)}</p>`);
|
|
6843
|
+
}
|
|
6844
|
+
closeList();
|
|
6845
|
+
return html.join('');
|
|
6846
|
+
}
|
|
6847
|
+
renderInlineMarkdown(value) {
|
|
6848
|
+
return this.escapeHtml(value)
|
|
6849
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
6850
|
+
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
6851
|
+
.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
6852
|
+
}
|
|
6853
|
+
escapeHtml(value) {
|
|
6854
|
+
return value
|
|
6855
|
+
.replace(/&/g, '&')
|
|
6856
|
+
.replace(/</g, '<')
|
|
6857
|
+
.replace(/>/g, '>')
|
|
6858
|
+
.replace(/"/g, '"')
|
|
6859
|
+
.replace(/'/g, ''');
|
|
6860
|
+
}
|
|
6861
|
+
shouldShowStatusText() {
|
|
6862
|
+
return this.shouldShowAuxiliaryText(this.statusText, false);
|
|
6863
|
+
}
|
|
6864
|
+
shouldShowErrorText() {
|
|
6865
|
+
return this.shouldShowAuxiliaryText(this.errorText, true);
|
|
6866
|
+
}
|
|
6867
|
+
onShellAction(action) {
|
|
6868
|
+
if (this.isShellActionDisabled(action))
|
|
6869
|
+
return;
|
|
6870
|
+
this.shellAction.emit(action);
|
|
6871
|
+
switch (action.kind) {
|
|
6872
|
+
case 'submit-prompt':
|
|
6873
|
+
this.onSubmit();
|
|
6874
|
+
return;
|
|
6875
|
+
case 'apply':
|
|
6876
|
+
this.onApply();
|
|
6877
|
+
return;
|
|
6878
|
+
case 'retry':
|
|
6879
|
+
this.retryTurn.emit();
|
|
6880
|
+
return;
|
|
6881
|
+
case 'cancel':
|
|
6882
|
+
this.cancelTurn.emit();
|
|
6883
|
+
return;
|
|
6884
|
+
default:
|
|
6885
|
+
return;
|
|
6886
|
+
}
|
|
6887
|
+
}
|
|
6888
|
+
getPrimaryAction() {
|
|
6889
|
+
return this.normalizeShellAction(this.primaryAction, this.getDefaultPrimaryAction());
|
|
6890
|
+
}
|
|
6891
|
+
getSecondaryActions() {
|
|
6892
|
+
const actions = [
|
|
6893
|
+
...this.secondaryActions,
|
|
6894
|
+
...this.governanceActions,
|
|
6895
|
+
];
|
|
6896
|
+
if (this.canApply) {
|
|
6897
|
+
actions.push({
|
|
6898
|
+
id: 'apply',
|
|
6899
|
+
kind: 'apply',
|
|
6900
|
+
label: this.resolvedLabels.apply,
|
|
6901
|
+
icon: 'check_circle',
|
|
6902
|
+
tone: 'governance',
|
|
6903
|
+
disabled: !this.canApply,
|
|
6904
|
+
testId: this.applyTestId || `${this.testIdPrefix}-apply`,
|
|
6905
|
+
});
|
|
6906
|
+
}
|
|
6907
|
+
if (this.state === 'error') {
|
|
6908
|
+
actions.push({
|
|
6909
|
+
id: 'retry',
|
|
6910
|
+
kind: 'retry',
|
|
6911
|
+
label: 'Tentar novamente',
|
|
6912
|
+
icon: 'replay',
|
|
6913
|
+
tone: 'warning',
|
|
6914
|
+
testId: `${this.testIdPrefix}-retry`,
|
|
6915
|
+
});
|
|
6916
|
+
}
|
|
6917
|
+
if (this.hasRecoverableTurn()) {
|
|
6918
|
+
actions.push({
|
|
6919
|
+
id: 'cancel',
|
|
6920
|
+
kind: 'cancel',
|
|
6921
|
+
label: 'Cancelar pedido',
|
|
6922
|
+
icon: 'close',
|
|
6923
|
+
tone: 'neutral',
|
|
6924
|
+
testId: `${this.testIdPrefix}-cancel-turn`,
|
|
6925
|
+
});
|
|
6926
|
+
}
|
|
6927
|
+
return actions.map((action) => this.normalizeShellAction(action));
|
|
6928
|
+
}
|
|
6929
|
+
getPrimaryActionTooltip(action) {
|
|
6930
|
+
return action.iconOnly ? action.ariaLabel || action.label : '';
|
|
6931
|
+
}
|
|
6932
|
+
isShellActionDisabled(action) {
|
|
6933
|
+
return Boolean(this.busy
|
|
6934
|
+
|| action.disabled
|
|
6935
|
+
|| (action.requiresPrompt && !this.currentPrompt.trim())
|
|
6936
|
+
|| (action.kind === 'submit-prompt' && !this.canSubmit)
|
|
6937
|
+
|| (action.kind === 'apply' && !this.canApply));
|
|
6938
|
+
}
|
|
6939
|
+
getShellActionTone(action) {
|
|
6940
|
+
const tone = (action.tone || (action.kind === 'apply' ? 'governance' : 'secondary')).toLowerCase();
|
|
6941
|
+
switch (tone) {
|
|
6942
|
+
case 'primary':
|
|
6943
|
+
case 'secondary':
|
|
6944
|
+
case 'governance':
|
|
6945
|
+
case 'success':
|
|
6946
|
+
case 'warning':
|
|
6947
|
+
case 'danger':
|
|
6948
|
+
case 'neutral':
|
|
6949
|
+
return tone;
|
|
6950
|
+
default:
|
|
6951
|
+
return 'secondary';
|
|
6952
|
+
}
|
|
6953
|
+
}
|
|
6954
|
+
trackShellAction(_index, action) {
|
|
6955
|
+
return action.id;
|
|
6956
|
+
}
|
|
6957
|
+
hasRecoverableTurn() {
|
|
6958
|
+
if (this.busy || this.state === 'idle' || this.state === 'listening') {
|
|
6959
|
+
return false;
|
|
6960
|
+
}
|
|
6961
|
+
return this.messages.length > 0
|
|
6962
|
+
|| this.quickReplies.length > 0
|
|
6963
|
+
|| this.canApply
|
|
6964
|
+
|| this.state === 'clarification'
|
|
6965
|
+
|| this.state === 'review'
|
|
6966
|
+
|| this.state === 'error';
|
|
6967
|
+
}
|
|
6968
|
+
shouldShowAuxiliaryText(text, errorOnly) {
|
|
6969
|
+
const normalized = this.normalizeMessageText(text);
|
|
6970
|
+
if (!normalized)
|
|
6971
|
+
return false;
|
|
6972
|
+
const lastMessage = [...this.messages]
|
|
6973
|
+
.reverse()
|
|
6974
|
+
.find((message) => {
|
|
6975
|
+
if (!message.text?.trim())
|
|
6976
|
+
return false;
|
|
6977
|
+
if (errorOnly)
|
|
6978
|
+
return message.role === 'error';
|
|
6979
|
+
return message.role === 'assistant' || message.role === 'status';
|
|
6980
|
+
});
|
|
6981
|
+
return normalized !== this.normalizeMessageText(lastMessage?.text);
|
|
6982
|
+
}
|
|
6983
|
+
normalizeMessageText(text) {
|
|
6984
|
+
return (text ?? '').replace(/\s+/g, ' ').trim();
|
|
6985
|
+
}
|
|
6986
|
+
getDefaultPrimaryAction() {
|
|
6987
|
+
const base = {
|
|
6988
|
+
id: 'submit',
|
|
6989
|
+
kind: 'submit-prompt',
|
|
6990
|
+
label: this.resolvedLabels.submit,
|
|
6991
|
+
icon: 'auto_awesome',
|
|
6992
|
+
tone: 'primary',
|
|
6993
|
+
requiresPrompt: true,
|
|
6994
|
+
testId: this.submitTestId || `${this.testIdPrefix}-submit`,
|
|
6995
|
+
};
|
|
6996
|
+
switch (this.state) {
|
|
6997
|
+
case 'processing':
|
|
6998
|
+
return {
|
|
6999
|
+
...base,
|
|
7000
|
+
label: 'Interpretando...',
|
|
7001
|
+
icon: 'hourglass_top',
|
|
7002
|
+
disabled: true,
|
|
7003
|
+
};
|
|
7004
|
+
case 'clarification':
|
|
7005
|
+
return {
|
|
7006
|
+
...base,
|
|
7007
|
+
label: 'Responder',
|
|
7008
|
+
icon: 'question_answer',
|
|
7009
|
+
};
|
|
7010
|
+
case 'review':
|
|
7011
|
+
return {
|
|
7012
|
+
...base,
|
|
7013
|
+
label: 'Refinar pedido',
|
|
7014
|
+
icon: 'tune',
|
|
7015
|
+
};
|
|
7016
|
+
case 'applying':
|
|
7017
|
+
return {
|
|
7018
|
+
...base,
|
|
7019
|
+
label: 'Aplicando...',
|
|
7020
|
+
icon: 'sync',
|
|
7021
|
+
disabled: true,
|
|
7022
|
+
};
|
|
7023
|
+
case 'error':
|
|
7024
|
+
return {
|
|
7025
|
+
...base,
|
|
7026
|
+
label: 'Corrigir pedido',
|
|
7027
|
+
icon: 'edit_note',
|
|
7028
|
+
tone: 'warning',
|
|
7029
|
+
};
|
|
7030
|
+
case 'success':
|
|
7031
|
+
return {
|
|
7032
|
+
...base,
|
|
7033
|
+
label: 'Novo pedido',
|
|
7034
|
+
icon: 'add_comment',
|
|
7035
|
+
};
|
|
7036
|
+
case 'idle':
|
|
7037
|
+
case 'listening':
|
|
7038
|
+
default:
|
|
7039
|
+
return base;
|
|
7040
|
+
}
|
|
7041
|
+
}
|
|
6026
7042
|
onQuickReply(reply) {
|
|
6027
7043
|
if (this.busy)
|
|
6028
7044
|
return;
|
|
6029
7045
|
this.quickReply.emit(reply);
|
|
6030
7046
|
}
|
|
7047
|
+
getQuickReplyAriaLabel(reply) {
|
|
7048
|
+
const label = reply.label?.trim() ?? '';
|
|
7049
|
+
const description = this.getQuickReplyDescription(reply);
|
|
7050
|
+
const presentation = this.getQuickReplyPresentationItems(reply)
|
|
7051
|
+
.map((item) => `${item.label}: ${item.value}`)
|
|
7052
|
+
.join('. ');
|
|
7053
|
+
return [label, description, presentation]
|
|
7054
|
+
.filter((segment) => typeof segment === 'string' && segment.length > 0)
|
|
7055
|
+
.map((segment) => this.trimSentencePunctuation(segment))
|
|
7056
|
+
.join('. ');
|
|
7057
|
+
}
|
|
7058
|
+
getQuickReplyTechnicalDetails(reply) {
|
|
7059
|
+
const explicit = this.quickReplyPresentation(reply)?.technicalDetails?.trim();
|
|
7060
|
+
if (explicit)
|
|
7061
|
+
return explicit;
|
|
7062
|
+
if (this.isFieldDiscoveryQuickReply(reply)
|
|
7063
|
+
|| this.isGuidedActionQuickReply(reply)
|
|
7064
|
+
|| this.isContextualPreviewActionQuickReply(reply))
|
|
7065
|
+
return '';
|
|
7066
|
+
const hints = reply.contextHints;
|
|
7067
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7068
|
+
if (!details)
|
|
7069
|
+
return '';
|
|
7070
|
+
const submitMethod = this.quickReplyHint(details, hints, 'submitMethod') || this.quickReplyHint(details, hints, 'operation');
|
|
7071
|
+
const submitUrl = this.quickReplyHint(details, hints, 'submitUrl');
|
|
7072
|
+
const resourcePath = this.quickReplyHint(details, hints, 'resourcePath');
|
|
7073
|
+
const schemaUrl = this.quickReplyHint(details, hints, 'schemaUrl');
|
|
7074
|
+
return [
|
|
7075
|
+
submitMethod && submitUrl ? `${submitMethod.toUpperCase()} ${submitUrl}` : '',
|
|
7076
|
+
resourcePath && resourcePath !== submitUrl ? `Recurso: ${resourcePath}` : '',
|
|
7077
|
+
schemaUrl ? `Schema: ${schemaUrl}` : '',
|
|
7078
|
+
].filter(Boolean).join('\n');
|
|
7079
|
+
}
|
|
7080
|
+
isRichQuickReply(reply) {
|
|
7081
|
+
return Boolean(this.getQuickReplyDescription(reply)
|
|
7082
|
+
|| this.getQuickReplyPresentationItems(reply).length
|
|
7083
|
+
|| this.getQuickReplyContextChips(reply).length);
|
|
7084
|
+
}
|
|
7085
|
+
getQuickReplyDescription(reply) {
|
|
7086
|
+
const authored = this.quickReplyPresentation(reply)?.description?.trim();
|
|
7087
|
+
if (authored)
|
|
7088
|
+
return authored;
|
|
7089
|
+
const explicit = reply.description?.trim() ?? '';
|
|
7090
|
+
if (!this.isContextualPreviewActionQuickReply(reply)) {
|
|
7091
|
+
return explicit;
|
|
7092
|
+
}
|
|
7093
|
+
if (explicit && !this.isGenericContextualActionDescription(explicit)) {
|
|
7094
|
+
return explicit;
|
|
7095
|
+
}
|
|
7096
|
+
const hints = reply.contextHints;
|
|
7097
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7098
|
+
const changeKind = details ? this.quickReplyHint(details, hints, 'changeKind') : '';
|
|
7099
|
+
switch (changeKind) {
|
|
7100
|
+
case 'set_chart_type':
|
|
7101
|
+
return 'Altera apenas a apresentação do gráfico selecionado e mantém a fonte de dados atual.';
|
|
7102
|
+
case 'enable_chart_drilldown':
|
|
7103
|
+
return 'Adiciona uma superfície de detalhe a partir da seleção do gráfico, preservando o contexto do dado.';
|
|
7104
|
+
case 'configure_export':
|
|
7105
|
+
return 'Configura uma ação operacional para exportar a seleção atual sem mudar a consulta base.';
|
|
7106
|
+
default:
|
|
7107
|
+
return 'Prepara um ajuste compatível com as capacidades confirmadas do componente selecionado.';
|
|
7108
|
+
}
|
|
7109
|
+
}
|
|
7110
|
+
getQuickReplyContextChips(reply) {
|
|
7111
|
+
if (this.isFieldDiscoveryQuickReply(reply) || this.isGuidedActionQuickReply(reply))
|
|
7112
|
+
return [];
|
|
7113
|
+
const evidence = this.quickReplyPresentation(reply)?.evidence;
|
|
7114
|
+
if (evidence?.length) {
|
|
7115
|
+
return evidence
|
|
7116
|
+
.filter((item) => !!item.value?.trim())
|
|
7117
|
+
.map((item) => this.presentationEvidenceToChip(item))
|
|
7118
|
+
.slice(0, 4);
|
|
7119
|
+
}
|
|
7120
|
+
if (this.isContextualPreviewActionQuickReply(reply)) {
|
|
7121
|
+
return this.getContextualActionChips(reply);
|
|
7122
|
+
}
|
|
7123
|
+
const hints = reply.contextHints;
|
|
7124
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7125
|
+
if (!details)
|
|
7126
|
+
return [];
|
|
7127
|
+
const submitMethod = this.quickReplyHint(details, hints, 'submitMethod') || this.quickReplyHint(details, hints, 'operation');
|
|
7128
|
+
const submitUrl = this.quickReplyHint(details, hints, 'submitUrl');
|
|
7129
|
+
const resourcePath = this.quickReplyHint(details, hints, 'resourcePath');
|
|
7130
|
+
const schemaUrl = this.quickReplyHint(details, hints, 'schemaUrl');
|
|
7131
|
+
const chips = [];
|
|
7132
|
+
if (submitMethod) {
|
|
7133
|
+
chips.push({
|
|
7134
|
+
icon: 'bolt',
|
|
7135
|
+
value: submitMethod.toUpperCase(),
|
|
7136
|
+
ariaLabel: `Operação ${submitMethod.toUpperCase()}`,
|
|
7137
|
+
});
|
|
7138
|
+
}
|
|
7139
|
+
const resourceLabel = this.shortPathLabel(resourcePath || submitUrl);
|
|
7140
|
+
if (resourceLabel) {
|
|
7141
|
+
chips.push({
|
|
7142
|
+
icon: 'dataset',
|
|
7143
|
+
value: resourceLabel,
|
|
7144
|
+
ariaLabel: `Recurso ${resourceLabel}`,
|
|
7145
|
+
});
|
|
7146
|
+
}
|
|
7147
|
+
if (schemaUrl) {
|
|
7148
|
+
chips.push({
|
|
7149
|
+
icon: 'schema',
|
|
7150
|
+
value: 'schema',
|
|
7151
|
+
ariaLabel: 'Schema disponível',
|
|
7152
|
+
});
|
|
7153
|
+
}
|
|
7154
|
+
return chips;
|
|
7155
|
+
}
|
|
7156
|
+
getQuickReplyPresentationItems(reply) {
|
|
7157
|
+
if (this.isFieldDiscoveryQuickReply(reply)
|
|
7158
|
+
|| this.isGuidedActionQuickReply(reply)
|
|
7159
|
+
|| this.isContextualPreviewActionQuickReply(reply))
|
|
7160
|
+
return [];
|
|
7161
|
+
const authoredItems = this.quickReplyPresentation(reply)?.items
|
|
7162
|
+
?.filter((item) => !!item.label?.trim() && !!item.value?.trim())
|
|
7163
|
+
.map((item) => ({
|
|
7164
|
+
key: item.key ?? item.label,
|
|
7165
|
+
label: item.label.trim(),
|
|
7166
|
+
icon: item.icon?.trim() || 'info',
|
|
7167
|
+
value: item.value.trim(),
|
|
7168
|
+
}));
|
|
7169
|
+
if (authoredItems?.length) {
|
|
7170
|
+
return authoredItems;
|
|
7171
|
+
}
|
|
7172
|
+
const hints = reply.contextHints;
|
|
7173
|
+
const presentation = this.asRecord(hints?.['presentation']);
|
|
7174
|
+
const resolvedPresentation = presentation ?? this.defaultQuickReplyPresentation(reply);
|
|
7175
|
+
if (!resolvedPresentation)
|
|
7176
|
+
return [];
|
|
7177
|
+
const items = [
|
|
7178
|
+
{
|
|
7179
|
+
key: 'bestFor',
|
|
7180
|
+
label: 'Indicado para',
|
|
7181
|
+
icon: 'ads_click',
|
|
7182
|
+
value: this.stringHint(resolvedPresentation, 'bestFor'),
|
|
7183
|
+
},
|
|
7184
|
+
{
|
|
7185
|
+
key: 'returns',
|
|
7186
|
+
label: 'Retorna',
|
|
7187
|
+
icon: 'stacked_line_chart',
|
|
7188
|
+
value: this.stringHint(resolvedPresentation, 'returns'),
|
|
7189
|
+
},
|
|
7190
|
+
{
|
|
7191
|
+
key: 'nextStep',
|
|
7192
|
+
label: 'Próximo passo',
|
|
7193
|
+
icon: 'arrow_forward',
|
|
7194
|
+
value: this.stringHint(resolvedPresentation, 'nextStep'),
|
|
7195
|
+
},
|
|
7196
|
+
];
|
|
7197
|
+
return items.filter((item) => item.value.length > 0);
|
|
7198
|
+
}
|
|
7199
|
+
defaultQuickReplyPresentation(reply) {
|
|
7200
|
+
const kind = (reply.kind || reply.tone || '').toLowerCase();
|
|
7201
|
+
switch (kind) {
|
|
7202
|
+
case 'confirm':
|
|
7203
|
+
case 'primary':
|
|
7204
|
+
case 'analytics':
|
|
7205
|
+
return {
|
|
7206
|
+
bestFor: 'Validar a recomendação antes de salvar ou materializar a página.',
|
|
7207
|
+
returns: 'Uma prévia governada com layout, widgets e sinais de decisão.',
|
|
7208
|
+
nextStep: 'Abra a prévia e revise se a composição atende ao objetivo.',
|
|
7209
|
+
};
|
|
7210
|
+
case 'resource':
|
|
7211
|
+
case 'suggestion':
|
|
7212
|
+
if (!this.hasQuickReplyResourceContext(reply))
|
|
7213
|
+
return null;
|
|
7214
|
+
return {
|
|
7215
|
+
bestFor: 'Explorar uma fonte de dados candidata para gráficos e indicadores.',
|
|
7216
|
+
returns: 'Campos, métricas prováveis e caminhos de drill-down disponíveis.',
|
|
7217
|
+
nextStep: 'Clique para usar esta fonte como contexto da próxima decisão.',
|
|
7218
|
+
};
|
|
7219
|
+
case 'revise':
|
|
7220
|
+
case 'warning':
|
|
7221
|
+
return null;
|
|
7222
|
+
default:
|
|
7223
|
+
return null;
|
|
7224
|
+
}
|
|
7225
|
+
}
|
|
7226
|
+
getQuickReplyCategoryLabel(reply) {
|
|
7227
|
+
const categoryLabel = this.quickReplyPresentation(reply)?.categoryLabel?.trim();
|
|
7228
|
+
if (categoryLabel)
|
|
7229
|
+
return categoryLabel;
|
|
7230
|
+
const kind = (reply.kind || '').toLowerCase();
|
|
7231
|
+
const tone = (reply.tone || '').toLowerCase();
|
|
7232
|
+
if (this.isContextualPreviewActionQuickReply(reply)) {
|
|
7233
|
+
return 'Ação sugerida';
|
|
7234
|
+
}
|
|
7235
|
+
if ((kind === 'resource' || kind === 'suggestion') && !this.hasQuickReplyResourceContext(reply)) {
|
|
7236
|
+
if (tone === 'primary' || tone === 'analytics' || tone === 'confirm')
|
|
7237
|
+
return 'Recomendado';
|
|
7238
|
+
return 'Opção guiada';
|
|
7239
|
+
}
|
|
7240
|
+
const normalizedKind = (kind || tone).toLowerCase();
|
|
7241
|
+
switch (normalizedKind) {
|
|
7242
|
+
case 'confirm':
|
|
7243
|
+
case 'primary':
|
|
7244
|
+
case 'analytics':
|
|
7245
|
+
return 'Recomendado';
|
|
7246
|
+
case 'revise':
|
|
7247
|
+
case 'warning':
|
|
7248
|
+
return 'Ajustar antes';
|
|
7249
|
+
case 'resource':
|
|
7250
|
+
case 'suggestion':
|
|
7251
|
+
return 'Fonte candidata';
|
|
7252
|
+
case 'success':
|
|
7253
|
+
return 'Pronto para usar';
|
|
7254
|
+
case 'cancel':
|
|
7255
|
+
case 'danger':
|
|
7256
|
+
return 'Encerrar';
|
|
7257
|
+
default:
|
|
7258
|
+
return 'Opção guiada';
|
|
7259
|
+
}
|
|
7260
|
+
}
|
|
7261
|
+
getQuickReplyIcon(reply) {
|
|
7262
|
+
const presentationIcon = this.quickReplyPresentation(reply)?.icon?.trim();
|
|
7263
|
+
if (presentationIcon)
|
|
7264
|
+
return presentationIcon;
|
|
7265
|
+
const explicitIcon = reply.icon?.trim();
|
|
7266
|
+
if (explicitIcon)
|
|
7267
|
+
return explicitIcon;
|
|
7268
|
+
const kind = (reply.kind || reply.tone || '').toLowerCase();
|
|
7269
|
+
switch (kind) {
|
|
7270
|
+
case 'confirm':
|
|
7271
|
+
case 'primary':
|
|
7272
|
+
return 'auto_awesome';
|
|
7273
|
+
case 'analytics':
|
|
7274
|
+
return 'query_stats';
|
|
7275
|
+
case 'resource':
|
|
7276
|
+
case 'suggestion':
|
|
7277
|
+
return 'dataset';
|
|
7278
|
+
case 'revise':
|
|
7279
|
+
case 'warning':
|
|
7280
|
+
return 'tune';
|
|
7281
|
+
case 'cancel':
|
|
7282
|
+
case 'danger':
|
|
7283
|
+
return 'close';
|
|
7284
|
+
case 'success':
|
|
7285
|
+
return 'check_circle';
|
|
7286
|
+
default:
|
|
7287
|
+
return 'touch_app';
|
|
7288
|
+
}
|
|
7289
|
+
}
|
|
7290
|
+
getQuickReplyCtaLabel(reply) {
|
|
7291
|
+
const ctaLabel = this.quickReplyPresentation(reply)?.ctaLabel?.trim();
|
|
7292
|
+
if (ctaLabel)
|
|
7293
|
+
return ctaLabel;
|
|
7294
|
+
const kind = (reply.kind || '').toLowerCase();
|
|
7295
|
+
if (this.isContextualPreviewActionQuickReply(reply)) {
|
|
7296
|
+
return 'Pré-visualizar ajuste';
|
|
7297
|
+
}
|
|
7298
|
+
if ((kind === 'resource' || kind === 'suggestion') && !this.hasQuickReplyResourceContext(reply)) {
|
|
7299
|
+
return 'Usar esta opção';
|
|
7300
|
+
}
|
|
7301
|
+
const normalizedKind = (kind || reply.tone || '').toLowerCase();
|
|
7302
|
+
switch (normalizedKind) {
|
|
7303
|
+
case 'revise':
|
|
7304
|
+
case 'warning':
|
|
7305
|
+
return 'Refinar';
|
|
7306
|
+
case 'cancel':
|
|
7307
|
+
case 'danger':
|
|
7308
|
+
return 'Cancelar';
|
|
7309
|
+
case 'resource':
|
|
7310
|
+
case 'suggestion':
|
|
7311
|
+
return 'Explorar';
|
|
7312
|
+
default:
|
|
7313
|
+
return 'Usar esta opção';
|
|
7314
|
+
}
|
|
7315
|
+
}
|
|
7316
|
+
getQuickReplyTone(reply) {
|
|
7317
|
+
const presentationTone = this.quickReplyPresentation(reply)?.tone?.trim().toLowerCase();
|
|
7318
|
+
if (presentationTone)
|
|
7319
|
+
return this.normalizeQuickReplyTone(presentationTone);
|
|
7320
|
+
if (this.isContextualPreviewActionQuickReply(reply)) {
|
|
7321
|
+
return 'analytics';
|
|
7322
|
+
}
|
|
7323
|
+
const tone = (reply.tone || reply.kind || 'neutral').toLowerCase();
|
|
7324
|
+
switch (tone) {
|
|
7325
|
+
case 'primary':
|
|
7326
|
+
case 'analytics':
|
|
7327
|
+
case 'resource':
|
|
7328
|
+
case 'warning':
|
|
7329
|
+
case 'neutral':
|
|
7330
|
+
case 'success':
|
|
7331
|
+
case 'danger':
|
|
7332
|
+
return tone;
|
|
7333
|
+
case 'confirm':
|
|
7334
|
+
return 'primary';
|
|
7335
|
+
case 'suggestion':
|
|
7336
|
+
return 'resource';
|
|
7337
|
+
case 'revise':
|
|
7338
|
+
return 'warning';
|
|
7339
|
+
case 'cancel':
|
|
7340
|
+
default:
|
|
7341
|
+
return 'neutral';
|
|
7342
|
+
}
|
|
7343
|
+
}
|
|
7344
|
+
normalizeQuickReplyTone(tone) {
|
|
7345
|
+
switch (tone) {
|
|
7346
|
+
case 'primary':
|
|
7347
|
+
case 'analytics':
|
|
7348
|
+
case 'resource':
|
|
7349
|
+
case 'warning':
|
|
7350
|
+
case 'neutral':
|
|
7351
|
+
case 'success':
|
|
7352
|
+
case 'danger':
|
|
7353
|
+
return tone;
|
|
7354
|
+
case 'confirm':
|
|
7355
|
+
return 'primary';
|
|
7356
|
+
case 'suggestion':
|
|
7357
|
+
return 'resource';
|
|
7358
|
+
case 'revise':
|
|
7359
|
+
return 'warning';
|
|
7360
|
+
case 'cancel':
|
|
7361
|
+
default:
|
|
7362
|
+
return 'neutral';
|
|
7363
|
+
}
|
|
7364
|
+
}
|
|
7365
|
+
asRecord(value) {
|
|
7366
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
7367
|
+
? value
|
|
7368
|
+
: null;
|
|
7369
|
+
}
|
|
7370
|
+
stringHint(source, key) {
|
|
7371
|
+
const value = source[key];
|
|
7372
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
7373
|
+
}
|
|
7374
|
+
quickReplyHint(primary, fallback, key) {
|
|
7375
|
+
return this.stringHint(primary, key) || (fallback ? this.stringHint(fallback, key) : '');
|
|
7376
|
+
}
|
|
7377
|
+
hasQuickReplyResourceContext(reply) {
|
|
7378
|
+
if (this.isFieldDiscoveryQuickReply(reply) || this.isContextualPreviewActionQuickReply(reply))
|
|
7379
|
+
return false;
|
|
7380
|
+
const hints = reply.contextHints;
|
|
7381
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7382
|
+
if (!details)
|
|
7383
|
+
return false;
|
|
7384
|
+
return Boolean(this.quickReplyHint(details, hints, 'resourcePath')
|
|
7385
|
+
|| this.quickReplyHint(details, hints, 'submitUrl')
|
|
7386
|
+
|| this.quickReplyHint(details, hints, 'schemaUrl'));
|
|
7387
|
+
}
|
|
7388
|
+
isFieldDiscoveryQuickReply(reply) {
|
|
7389
|
+
const hints = reply.contextHints;
|
|
7390
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7391
|
+
const questionKind = details
|
|
7392
|
+
? this.quickReplyHint(details, hints, 'questionKind').toLowerCase()
|
|
7393
|
+
: this.stringHint(hints ?? {}, 'questionKind').toLowerCase();
|
|
7394
|
+
if (questionKind === 'field_discovery')
|
|
7395
|
+
return true;
|
|
7396
|
+
const id = (reply.id || '').toLowerCase();
|
|
7397
|
+
const label = (reply.label || '').toLocaleLowerCase('pt-BR');
|
|
7398
|
+
const prompt = (reply.prompt || '').toLocaleLowerCase('pt-BR');
|
|
7399
|
+
return id.includes('fields')
|
|
7400
|
+
|| label.includes('ver campos')
|
|
7401
|
+
|| prompt.includes('quais campos');
|
|
7402
|
+
}
|
|
7403
|
+
isContextualPreviewActionQuickReply(reply) {
|
|
7404
|
+
if (this.quickReplyPresentationKind(reply) === 'contextual-action') {
|
|
7405
|
+
return true;
|
|
7406
|
+
}
|
|
7407
|
+
const hints = reply.contextHints;
|
|
7408
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7409
|
+
const source = details ? this.quickReplyHint(details, hints, 'source') : this.stringHint(hints ?? {}, 'source');
|
|
7410
|
+
const kind = details ? this.quickReplyHint(details, hints, 'kind') : this.stringHint(hints ?? {}, 'kind');
|
|
7411
|
+
const id = (reply.id || '').trim().toLowerCase();
|
|
7412
|
+
return source === 'component-capability-catalog'
|
|
7413
|
+
|| kind === 'contextual-preview-action'
|
|
7414
|
+
|| id.startsWith('chart-')
|
|
7415
|
+
|| id.startsWith('table-export-');
|
|
7416
|
+
}
|
|
7417
|
+
isGuidedActionQuickReply(reply) {
|
|
7418
|
+
const presentationKind = this.quickReplyPresentationKind(reply);
|
|
7419
|
+
if (presentationKind === 'guided-option' || presentationKind === 'quick-action') {
|
|
7420
|
+
return true;
|
|
7421
|
+
}
|
|
7422
|
+
const kind = (reply.kind || '').trim().toLowerCase();
|
|
7423
|
+
const hasActionPresentation = Boolean(this.quickReplyPresentation(reply)?.ctaLabel?.trim()
|
|
7424
|
+
|| this.quickReplyPresentation(reply)?.icon?.trim()
|
|
7425
|
+
|| this.quickReplyPresentation(reply)?.description?.trim());
|
|
7426
|
+
return kind === 'clarification-option' && hasActionPresentation;
|
|
7427
|
+
}
|
|
7428
|
+
quickReplyPresentation(reply) {
|
|
7429
|
+
const presentation = reply.presentation;
|
|
7430
|
+
return presentation && typeof presentation === 'object' && !Array.isArray(presentation)
|
|
7431
|
+
? presentation
|
|
7432
|
+
: null;
|
|
7433
|
+
}
|
|
7434
|
+
quickReplyPresentationKind(reply) {
|
|
7435
|
+
return this.quickReplyPresentation(reply)?.kind?.trim().toLowerCase() ?? '';
|
|
7436
|
+
}
|
|
7437
|
+
presentationEvidenceToChip(item) {
|
|
7438
|
+
const value = item.value.trim();
|
|
7439
|
+
return {
|
|
7440
|
+
icon: item.icon?.trim() || 'verified',
|
|
7441
|
+
value,
|
|
7442
|
+
ariaLabel: item.ariaLabel?.trim() || value,
|
|
7443
|
+
};
|
|
7444
|
+
}
|
|
7445
|
+
getContextualActionChips(reply) {
|
|
7446
|
+
const hints = reply.contextHints;
|
|
7447
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7448
|
+
if (!details)
|
|
7449
|
+
return [];
|
|
7450
|
+
const chips = [];
|
|
7451
|
+
const targetComponentId = this.quickReplyHint(details, hints, 'targetComponentId')
|
|
7452
|
+
|| this.quickReplyHint(details, hints, 'selectedComponentId');
|
|
7453
|
+
const changeKind = this.quickReplyHint(details, hints, 'changeKind');
|
|
7454
|
+
const capabilityId = this.quickReplyHint(details, hints, 'capabilityId');
|
|
7455
|
+
const selectedWidgetKey = this.quickReplyHint(details, hints, 'selectedWidgetKey');
|
|
7456
|
+
if (targetComponentId) {
|
|
7457
|
+
chips.push({
|
|
7458
|
+
icon: 'widgets',
|
|
7459
|
+
value: this.shortTechnicalLabel(targetComponentId),
|
|
7460
|
+
ariaLabel: `Componente ${targetComponentId}`,
|
|
7461
|
+
});
|
|
7462
|
+
}
|
|
7463
|
+
if (changeKind) {
|
|
7464
|
+
chips.push({
|
|
7465
|
+
icon: 'rule',
|
|
7466
|
+
value: this.shortTechnicalLabel(changeKind),
|
|
7467
|
+
ariaLabel: `Mudança ${changeKind}`,
|
|
7468
|
+
});
|
|
7469
|
+
}
|
|
7470
|
+
if (capabilityId) {
|
|
7471
|
+
chips.push({
|
|
7472
|
+
icon: 'verified',
|
|
7473
|
+
value: 'capability',
|
|
7474
|
+
ariaLabel: `Capability ${capabilityId}`,
|
|
7475
|
+
});
|
|
7476
|
+
}
|
|
7477
|
+
if (!capabilityId && selectedWidgetKey) {
|
|
7478
|
+
chips.push({
|
|
7479
|
+
icon: 'ads_click',
|
|
7480
|
+
value: 'seleção atual',
|
|
7481
|
+
ariaLabel: `Widget selecionado ${selectedWidgetKey}`,
|
|
7482
|
+
});
|
|
7483
|
+
}
|
|
7484
|
+
return chips.slice(0, 3);
|
|
7485
|
+
}
|
|
7486
|
+
isGenericContextualActionDescription(value) {
|
|
7487
|
+
const normalized = value
|
|
7488
|
+
.normalize('NFD')
|
|
7489
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
7490
|
+
.toLocaleLowerCase('pt-BR');
|
|
7491
|
+
return normalized.includes('acao sugerida')
|
|
7492
|
+
&& normalized.includes('capacidades confirmadas');
|
|
7493
|
+
}
|
|
7494
|
+
trimSentencePunctuation(value) {
|
|
7495
|
+
return value.trim().replace(/[.!?]+$/u, '');
|
|
7496
|
+
}
|
|
7497
|
+
shortPathLabel(value) {
|
|
7498
|
+
const normalized = value.trim().replace(/[?#].*$/u, '').replace(/\/+$/u, '');
|
|
7499
|
+
if (!normalized)
|
|
7500
|
+
return '';
|
|
7501
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
7502
|
+
const last = segments.length > 0 ? segments[segments.length - 1] : normalized;
|
|
7503
|
+
return last
|
|
7504
|
+
.replace(/^vw-/u, '')
|
|
7505
|
+
.replace(/-/gu, ' ')
|
|
7506
|
+
.trim();
|
|
7507
|
+
}
|
|
7508
|
+
shortTechnicalLabel(value) {
|
|
7509
|
+
return value
|
|
7510
|
+
.trim()
|
|
7511
|
+
.replace(/^praxis-/u, '')
|
|
7512
|
+
.replace(/@.*$/u, '')
|
|
7513
|
+
.replace(/[_-]+/gu, ' ')
|
|
7514
|
+
.trim();
|
|
7515
|
+
}
|
|
7516
|
+
getCloseIcon() {
|
|
7517
|
+
const label = this.resolvedLabels.close.toLocaleLowerCase('pt-BR');
|
|
7518
|
+
return label.includes('minimiz') ? 'remove' : 'close';
|
|
7519
|
+
}
|
|
7520
|
+
normalizeShellAction(action, fallback) {
|
|
7521
|
+
const source = action ?? fallback;
|
|
7522
|
+
return {
|
|
7523
|
+
id: source?.id || fallback?.id || 'action',
|
|
7524
|
+
label: source?.label || fallback?.label || '',
|
|
7525
|
+
kind: source?.kind ?? fallback?.kind ?? 'custom',
|
|
7526
|
+
icon: source?.icon ?? fallback?.icon ?? null,
|
|
7527
|
+
tone: source?.tone ?? fallback?.tone ?? null,
|
|
7528
|
+
disabled: source?.disabled ?? fallback?.disabled ?? false,
|
|
7529
|
+
requiresPrompt: source?.requiresPrompt ?? fallback?.requiresPrompt ?? false,
|
|
7530
|
+
testId: source?.testId ?? fallback?.testId ?? null,
|
|
7531
|
+
ariaLabel: source?.ariaLabel ?? fallback?.ariaLabel ?? null,
|
|
7532
|
+
iconOnly: source?.iconOnly ?? fallback?.iconOnly ?? false,
|
|
7533
|
+
};
|
|
7534
|
+
}
|
|
6031
7535
|
onRemoveAttachment(attachment) {
|
|
6032
7536
|
if (this.busy)
|
|
6033
7537
|
return;
|
|
@@ -6045,6 +7549,26 @@ class PraxisAiAssistantShellComponent {
|
|
|
6045
7549
|
this.resendMessage.emit(message);
|
|
6046
7550
|
}
|
|
6047
7551
|
}
|
|
7552
|
+
getMessageActionIcon(action) {
|
|
7553
|
+
if (action.icon)
|
|
7554
|
+
return action.icon;
|
|
7555
|
+
switch (action.kind) {
|
|
7556
|
+
case 'edit':
|
|
7557
|
+
return 'edit';
|
|
7558
|
+
case 'resend':
|
|
7559
|
+
return 'replay';
|
|
7560
|
+
case 'copy':
|
|
7561
|
+
return 'content_copy';
|
|
7562
|
+
default:
|
|
7563
|
+
return '';
|
|
7564
|
+
}
|
|
7565
|
+
}
|
|
7566
|
+
getMessageActionLabel(action) {
|
|
7567
|
+
return action.ariaLabel || action.label;
|
|
7568
|
+
}
|
|
7569
|
+
isMessageActionIconOnly(action) {
|
|
7570
|
+
return action.iconOnly ?? !!this.getMessageActionIcon(action);
|
|
7571
|
+
}
|
|
6048
7572
|
getModeLabel() {
|
|
6049
7573
|
switch (this.mode) {
|
|
6050
7574
|
case 'config':
|
|
@@ -6088,10 +7612,13 @@ class PraxisAiAssistantShellComponent {
|
|
|
6088
7612
|
return;
|
|
6089
7613
|
this.startPointerSession('drag', event);
|
|
6090
7614
|
}
|
|
6091
|
-
startResize(event) {
|
|
7615
|
+
startResize(direction, event) {
|
|
6092
7616
|
if (!this.resizable || event.button !== 0)
|
|
6093
7617
|
return;
|
|
6094
|
-
this.startPointerSession('resize', event);
|
|
7618
|
+
this.startPointerSession('resize', event, direction);
|
|
7619
|
+
}
|
|
7620
|
+
trackResizeHandle(_index, direction) {
|
|
7621
|
+
return direction;
|
|
6095
7622
|
}
|
|
6096
7623
|
trackMessage(_index, message) {
|
|
6097
7624
|
return message.id;
|
|
@@ -6108,15 +7635,16 @@ class PraxisAiAssistantShellComponent {
|
|
|
6108
7635
|
trackAttachment(_index, attachment) {
|
|
6109
7636
|
return attachment.id;
|
|
6110
7637
|
}
|
|
6111
|
-
startPointerSession(mode, event) {
|
|
7638
|
+
startPointerSession(mode, event, resizeDirection) {
|
|
6112
7639
|
event.preventDefault();
|
|
6113
7640
|
event.stopPropagation();
|
|
6114
7641
|
const panel = this.panel?.nativeElement;
|
|
6115
7642
|
if (!panel)
|
|
6116
7643
|
return;
|
|
6117
|
-
const bounds = this.
|
|
7644
|
+
const bounds = this.resolveViewportBounds();
|
|
6118
7645
|
this.pointerSession = {
|
|
6119
7646
|
mode,
|
|
7647
|
+
resizeDirection,
|
|
6120
7648
|
pointerId: event.pointerId,
|
|
6121
7649
|
startX: event.clientX,
|
|
6122
7650
|
startY: event.clientY,
|
|
@@ -6146,15 +7674,40 @@ class PraxisAiAssistantShellComponent {
|
|
|
6146
7674
|
left: session.startLayout.left + deltaX,
|
|
6147
7675
|
top: session.startLayout.top + deltaY,
|
|
6148
7676
|
}
|
|
6149
|
-
:
|
|
6150
|
-
...session.startLayout,
|
|
6151
|
-
width: session.startLayout.width + deltaX,
|
|
6152
|
-
height: session.startLayout.height + deltaY,
|
|
6153
|
-
};
|
|
7677
|
+
: this.resizeLayout(session, deltaX, deltaY);
|
|
6154
7678
|
this.currentLayout = this.clampLayout(next, session.boundsWidth, session.boundsHeight);
|
|
6155
7679
|
this.layoutChange.emit(this.currentLayout);
|
|
6156
7680
|
this.cdr.markForCheck();
|
|
6157
7681
|
}
|
|
7682
|
+
resizeLayout(session, deltaX, deltaY) {
|
|
7683
|
+
const direction = session.resizeDirection ?? 'se';
|
|
7684
|
+
const start = session.startLayout;
|
|
7685
|
+
const right = start.left + start.width;
|
|
7686
|
+
const bottom = start.top + start.height;
|
|
7687
|
+
let left = start.left;
|
|
7688
|
+
let top = start.top;
|
|
7689
|
+
let width = start.width;
|
|
7690
|
+
let height = start.height;
|
|
7691
|
+
if (direction.includes('e')) {
|
|
7692
|
+
const maxWidth = Math.max(this.minWidth, session.boundsWidth - start.left - this.margin);
|
|
7693
|
+
width = this.clamp(start.width + deltaX, this.minWidth, maxWidth);
|
|
7694
|
+
}
|
|
7695
|
+
if (direction.includes('s')) {
|
|
7696
|
+
const maxHeight = Math.max(this.minHeight, session.boundsHeight - start.top - this.margin);
|
|
7697
|
+
height = this.clamp(start.height + deltaY, this.minHeight, maxHeight);
|
|
7698
|
+
}
|
|
7699
|
+
if (direction.includes('w')) {
|
|
7700
|
+
const nextLeft = this.clamp(start.left + deltaX, this.margin, right - this.minWidth);
|
|
7701
|
+
left = nextLeft;
|
|
7702
|
+
width = right - nextLeft;
|
|
7703
|
+
}
|
|
7704
|
+
if (direction.includes('n')) {
|
|
7705
|
+
const nextTop = this.clamp(start.top + deltaY, this.margin, bottom - this.minHeight);
|
|
7706
|
+
top = nextTop;
|
|
7707
|
+
height = bottom - nextTop;
|
|
7708
|
+
}
|
|
7709
|
+
return { left, top, width, height };
|
|
7710
|
+
}
|
|
6158
7711
|
finishPointerSession(event) {
|
|
6159
7712
|
if (this.pointerSession && event.pointerId === this.pointerSession.pointerId) {
|
|
6160
7713
|
try {
|
|
@@ -6172,10 +7725,9 @@ class PraxisAiAssistantShellComponent {
|
|
|
6172
7725
|
window.removeEventListener('pointerup', this.onWindowPointerUp);
|
|
6173
7726
|
window.removeEventListener('pointercancel', this.onWindowPointerUp);
|
|
6174
7727
|
}
|
|
6175
|
-
|
|
6176
|
-
const
|
|
6177
|
-
const
|
|
6178
|
-
const height = hostBounds?.height || (typeof window !== 'undefined' ? window.innerHeight : 768);
|
|
7728
|
+
resolveViewportBounds() {
|
|
7729
|
+
const width = typeof window !== 'undefined' ? window.innerWidth : 1024;
|
|
7730
|
+
const height = typeof window !== 'undefined' ? window.innerHeight : 768;
|
|
6179
7731
|
return {
|
|
6180
7732
|
width: Math.max(width, this.minWidth + this.margin * 2),
|
|
6181
7733
|
height: Math.max(height, this.minHeight + this.margin * 2),
|
|
@@ -6268,7 +7820,7 @@ class PraxisAiAssistantShellComponent {
|
|
|
6268
7820
|
this.ownedPreviewUrls.delete(previewUrl);
|
|
6269
7821
|
}
|
|
6270
7822
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6271
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisAiAssistantShellComponent, isStandalone: true, selector: "praxis-ai-assistant-shell", inputs: { labels: "labels", mode: "mode", state: "state", contextItems: "contextItems", attachments: "attachments", messages: "messages", quickReplies: "quickReplies", prompt: "prompt", statusText: "statusText", errorText: "errorText", testIdPrefix: "testIdPrefix", panelTestId: "panelTestId", submitTestId: "submitTestId", applyTestId: "applyTestId", busy: "busy", canSubmit: "canSubmit", canApply: "canApply", submitOnEnter: "submitOnEnter", enableFileAttachments: "enableFileAttachments", attachmentAccept: "attachmentAccept", attachmentMultiple: "attachmentMultiple", draggable: "draggable", resizable: "resizable", minWidth: "minWidth", minHeight: "minHeight", margin: "margin", layout: "layout" }, outputs: { promptChange: "promptChange", submitPrompt: "submitPrompt", apply: "apply", close: "close", attach: "attach", attachmentsPasted: "attachmentsPasted", attachmentsSelected: "attachmentsSelected", removeAttachment: "removeAttachment", messageAction: "messageAction", editMessage: "editMessage", resendMessage: "resendMessage", quickReply: "quickReply", layoutChange: "layoutChange" }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, static: true }, { propertyName: "conversation", first: true, predicate: ["conversation"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<section\n #panel\n class=\"praxis-ai-assistant-shell\"\n role=\"dialog\"\n [attr.aria-label]=\"resolvedLabels.title\"\n [attr.aria-busy]=\"busy ? 'true' : null\"\n [style.left.px]=\"currentLayout.left\"\n [style.top.px]=\"currentLayout.top\"\n [style.width.px]=\"currentLayout.width\"\n [style.height.px]=\"currentLayout.height\"\n [attr.data-testid]=\"panelTestId || testIdPrefix\"\n>\n <header\n class=\"praxis-ai-assistant-shell__header\"\n [attr.data-testid]=\"testIdPrefix + '-drag-handle'\"\n [attr.aria-label]=\"resolvedLabels.dragHandleAria\"\n (pointerdown)=\"startDrag($event)\"\n >\n <div class=\"praxis-ai-assistant-shell__identity\" aria-hidden=\"true\">\n <mat-icon>auto_awesome</mat-icon>\n </div>\n <div class=\"praxis-ai-assistant-shell__title-group\">\n <strong>{{ resolvedLabels.title }}</strong>\n <p *ngIf=\"resolvedLabels.subtitle\">{{ resolvedLabels.subtitle }}</p>\n </div>\n <div class=\"praxis-ai-assistant-shell__badges\" aria-hidden=\"true\">\n <span class=\"praxis-ai-assistant-shell__badge\">{{ getModeLabel() }}</span>\n <span class=\"praxis-ai-assistant-shell__badge\" [class.praxis-ai-assistant-shell__badge--error]=\"state === 'error'\">\n {{ getStateLabel() }}\n </span>\n </div>\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"resolvedLabels.close\"\n [attr.aria-label]=\"resolvedLabels.close\"\n (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"close.emit()\"\n [attr.data-testid]=\"testIdPrefix + '-close'\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </header>\n\n <div class=\"praxis-ai-assistant-shell__body\">\n <div\n *ngIf=\"contextItems.length\"\n class=\"praxis-ai-assistant-shell__context\"\n [attr.aria-label]=\"resolvedLabels.contextAria\"\n [attr.data-testid]=\"testIdPrefix + '-context'\"\n >\n <span\n *ngFor=\"let item of contextItems; trackBy: trackContextItem\"\n class=\"praxis-ai-assistant-shell__context-item\"\n [attr.data-testid]=\"testIdPrefix + '-context-' + item.id\"\n >\n <mat-icon *ngIf=\"item.icon\" aria-hidden=\"true\">{{ item.icon }}</mat-icon>\n <span class=\"praxis-ai-assistant-shell__context-label\">{{ item.label }}</span>\n <span *ngIf=\"item.value\" class=\"praxis-ai-assistant-shell__context-value\">{{ item.value }}</span>\n </span>\n </div>\n\n <div\n #conversation\n class=\"praxis-ai-assistant-shell__conversation\"\n [attr.data-testid]=\"testIdPrefix + '-conversation'\"\n [attr.aria-label]=\"resolvedLabels.conversationAria\"\n >\n <article\n *ngIf=\"!messages.length\"\n class=\"praxis-ai-assistant-shell__message praxis-ai-assistant-shell__message--assistant\"\n [attr.data-testid]=\"testIdPrefix + '-message-assistant-empty'\"\n >\n {{ resolvedLabels.emptyConversation }}\n </article>\n <article\n *ngFor=\"let message of messages; trackBy: trackMessage\"\n class=\"praxis-ai-assistant-shell__message\"\n [class.praxis-ai-assistant-shell__message--user]=\"message.role === 'user'\"\n [class.praxis-ai-assistant-shell__message--assistant]=\"message.role === 'assistant'\"\n [class.praxis-ai-assistant-shell__message--status]=\"message.role === 'status'\"\n [class.praxis-ai-assistant-shell__message--error]=\"message.role === 'error'\"\n [attr.data-testid]=\"testIdPrefix + '-message-' + message.role\"\n >\n {{ message.text }}\n <div\n *ngIf=\"message.actions?.length || message.editable || message.resendable\"\n class=\"praxis-ai-assistant-shell__message-actions\"\n >\n <button\n *ngIf=\"message.editable\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy\"\n [attr.data-testid]=\"testIdPrefix + '-message-edit-' + message.id\"\n (click)=\"onMessageAction(message, { id: 'edit', label: resolvedLabels.editMessage, kind: 'edit' })\"\n >\n {{ resolvedLabels.editMessage }}\n </button>\n <button\n *ngIf=\"message.resendable\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy\"\n [attr.data-testid]=\"testIdPrefix + '-message-resend-' + message.id\"\n (click)=\"onMessageAction(message, { id: 'resend', label: resolvedLabels.resendMessage, kind: 'resend' })\"\n >\n {{ resolvedLabels.resendMessage }}\n </button>\n <button\n *ngFor=\"let action of message.actions || []; trackBy: trackMessageAction\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy || action.disabled\"\n [attr.data-testid]=\"testIdPrefix + '-message-action-' + message.id + '-' + action.id\"\n (click)=\"onMessageAction(message, action)\"\n >\n {{ action.label }}\n </button>\n </div>\n </article>\n </div>\n\n <div\n *ngIf=\"attachments.length\"\n class=\"praxis-ai-assistant-shell__attachments\"\n [attr.aria-label]=\"resolvedLabels.attachmentsAria\"\n [attr.data-testid]=\"testIdPrefix + '-attachments'\"\n >\n <div\n *ngFor=\"let attachment of attachments; trackBy: trackAttachment\"\n class=\"praxis-ai-assistant-shell__attachment\"\n [class.praxis-ai-assistant-shell__attachment--error]=\"attachment.status === 'error'\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-' + attachment.id\"\n >\n <img\n *ngIf=\"attachment.previewUrl && attachment.kind === 'image'\"\n class=\"praxis-ai-assistant-shell__attachment-preview\"\n [src]=\"attachment.previewUrl\"\n [alt]=\"attachment.name\"\n >\n <span class=\"praxis-ai-assistant-shell__attachment-name\">{{ attachment.name }}</span>\n <span class=\"praxis-ai-assistant-shell__attachment-kind\">{{ attachment.kind }}</span>\n <button\n mat-icon-button\n type=\"button\"\n [disabled]=\"busy\"\n [matTooltip]=\"resolvedLabels.removeAttachment\"\n [attr.aria-label]=\"resolvedLabels.removeAttachment + ': ' + attachment.name\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-remove-' + attachment.id\"\n (click)=\"onRemoveAttachment(attachment)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n\n <div\n *ngIf=\"quickReplies.length\"\n class=\"praxis-ai-assistant-shell__quick-replies\"\n [attr.data-testid]=\"testIdPrefix + '-quick-replies'\"\n [attr.aria-label]=\"resolvedLabels.quickRepliesAria\"\n >\n <button\n *ngFor=\"let reply of quickReplies; trackBy: trackQuickReply\"\n mat-stroked-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__quick-reply\"\n [attr.data-testid]=\"testIdPrefix + '-quick-reply-' + (reply.id || reply.kind)\"\n [disabled]=\"busy\"\n (click)=\"onQuickReply(reply)\"\n >\n {{ reply.label }}\n </button>\n </div>\n <p\n *ngIf=\"statusText\"\n class=\"praxis-ai-assistant-shell__status\"\n [attr.data-testid]=\"testIdPrefix + '-status'\"\n >\n {{ statusText }}\n </p>\n <p\n *ngIf=\"errorText\"\n class=\"praxis-ai-assistant-shell__error\"\n [attr.data-testid]=\"testIdPrefix + '-error'\"\n >\n {{ errorText }}\n </p>\n </div>\n\n <footer class=\"praxis-ai-assistant-shell__footer\">\n <label class=\"praxis-ai-assistant-shell__label\" for=\"praxis-ai-assistant-shell-prompt\">\n {{ resolvedLabels.prompt }}\n </label>\n <div class=\"praxis-ai-assistant-shell__composer\">\n <textarea\n id=\"praxis-ai-assistant-shell-prompt\"\n class=\"praxis-ai-assistant-shell__prompt\"\n [attr.data-testid]=\"testIdPrefix + '-prompt'\"\n [placeholder]=\"resolvedLabels.promptPlaceholder\"\n [ngModel]=\"currentPrompt\"\n [disabled]=\"busy\"\n (ngModelChange)=\"onPromptInput($event)\"\n (keydown)=\"onPromptKeydown($event)\"\n (paste)=\"onPromptPaste($event)\"\n ></textarea>\n <div class=\"praxis-ai-assistant-shell__composer-actions\">\n <input\n #attachmentInput\n type=\"file\"\n hidden\n [attr.accept]=\"attachmentAccept || null\"\n [attr.multiple]=\"attachmentMultiple ? '' : null\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-input'\"\n (change)=\"onAttachmentFilesSelected($event)\"\n >\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"busy\"\n (click)=\"onAttachClick(attachmentInput)\"\n [attr.data-testid]=\"testIdPrefix + '-attach'\"\n >\n <mat-icon>attach_file</mat-icon>\n {{ resolvedLabels.attach }}\n </button>\n <button\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n [disabled]=\"busy || !canSubmit || !currentPrompt.trim()\"\n (click)=\"onSubmit()\"\n [attr.data-testid]=\"submitTestId || (testIdPrefix + '-submit')\"\n >\n {{ resolvedLabels.submit }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"busy || !canApply\"\n (click)=\"onApply()\"\n [attr.data-testid]=\"applyTestId || (testIdPrefix + '-apply')\"\n >\n {{ resolvedLabels.apply }}\n </button>\n <mat-spinner *ngIf=\"busy\" diameter=\"20\" [attr.data-testid]=\"testIdPrefix + '-spinner'\"></mat-spinner>\n </div>\n </div>\n </footer>\n\n <button\n *ngIf=\"resizable\"\n type=\"button\"\n class=\"praxis-ai-assistant-shell__resize-handle\"\n [attr.data-testid]=\"testIdPrefix + '-resize-handle'\"\n [attr.aria-label]=\"resolvedLabels.resizeHandleAria\"\n [matTooltip]=\"resolvedLabels.resizeHandleAria\"\n (pointerdown)=\"startResize($event)\"\n ></button>\n</section>\n", styles: [":host{display:block}.praxis-ai-assistant-shell{position:absolute;box-sizing:border-box;min-width:360px;min-height:360px;display:flex;flex-direction:column;overflow:hidden;color:var(--md-sys-color-on-surface, #f8fafc);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface, #0f172a);box-shadow:0 24px 60px #0006;z-index:10}.praxis-ai-assistant-shell__header{flex:0 0 auto;display:flex;align-items:center;gap:10px;padding:12px;border-color:color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 72%,transparent);background:radial-gradient(circle at top left,rgba(96,165,250,.22),transparent 34%),var(--md-sys-color-surface-container, #172033)}.praxis-ai-assistant-shell__header{justify-content:flex-start;border-bottom:1px solid;cursor:move;touch-action:none}.praxis-ai-assistant-shell__identity{flex:0 0 auto;display:grid;place-items:center;width:34px;height:34px;border-radius:8px;background:var(--md-sys-color-primary, #60a5fa);color:var(--md-sys-color-on-primary, #020617);box-shadow:0 8px 20px #60a5fa4d}.praxis-ai-assistant-shell__identity mat-icon{width:20px;height:20px;font-size:20px}.praxis-ai-assistant-shell__title-group{min-width:0;flex:1 1 auto;display:grid;gap:3px}.praxis-ai-assistant-shell__badges{flex:0 1 auto;display:flex;align-items:center;justify-content:flex-end;gap:6px;min-width:0;flex-wrap:wrap}.praxis-ai-assistant-shell__badge{display:inline-flex;align-items:center;min-height:22px;max-width:140px;padding:2px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 80%,transparent);border-radius:8px;color:var(--md-sys-color-on-surface-variant, #cbd5e1);background:var(--md-sys-color-surface-container-high, #263244);font-size:11px;line-height:1.2;overflow-wrap:anywhere}.praxis-ai-assistant-shell__badge--error{color:var(--md-sys-color-error, #ff6b6b);border-color:color-mix(in srgb,var(--md-sys-color-error, #ff6b6b) 60%,transparent)}.praxis-ai-assistant-shell__title-group strong,.praxis-ai-assistant-shell__title-group p{min-width:0;margin:0;overflow-wrap:anywhere}.praxis-ai-assistant-shell__title-group p{color:var(--md-sys-color-on-surface-variant, #cbd5e1);font-size:12px;line-height:1.35}.praxis-ai-assistant-shell__body{min-height:0;flex:1 1 auto;display:flex;flex-direction:column;gap:12px;padding:14px 14px 8px;overflow:auto;background:linear-gradient(180deg,var(--md-sys-color-surface-container-low, #111827),var(--md-sys-color-surface, #0f172a))}.praxis-ai-assistant-shell__context,.praxis-ai-assistant-shell__attachments{flex:0 0 auto;display:flex;align-items:center;gap:8px;overflow-x:auto}.praxis-ai-assistant-shell__context-item{flex:0 0 auto;display:inline-flex;align-items:center;gap:5px;max-width:240px;padding:5px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface-container-high, #263244);font-size:12px;line-height:1.25}.praxis-ai-assistant-shell__context-item mat-icon{width:16px;height:16px;font-size:16px}.praxis-ai-assistant-shell__context-label,.praxis-ai-assistant-shell__context-value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-shell__context-label{color:var(--md-sys-color-on-surface, #f8fafc)}.praxis-ai-assistant-shell__context-value{color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.praxis-ai-assistant-shell__label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.praxis-ai-assistant-shell__prompt{box-sizing:border-box;width:100%;min-height:56px;max-height:128px;resize:none;border:0;padding:12px;color:var(--md-sys-color-on-surface, #f8fafc);background:transparent;font:inherit;line-height:1.45;outline:none}.praxis-ai-assistant-shell__conversation{min-height:0;flex:1 1 auto;display:flex;flex-direction:column;gap:8px;overflow:auto;padding:2px}.praxis-ai-assistant-shell__message{max-width:86%;align-self:flex-start;padding:9px 11px;border-radius:8px;background:var(--md-sys-color-surface-container-high, #263244);color:var(--md-sys-color-on-surface, #f8fafc);font-size:13px;line-height:1.35;overflow-wrap:anywhere}.praxis-ai-assistant-shell__message-actions{display:flex;align-items:center;gap:4px;flex-wrap:wrap;margin-top:8px}.praxis-ai-assistant-shell__message-action{min-height:28px;padding:0 8px;border-radius:8px;font-size:12px}.praxis-ai-assistant-shell__message--assistant{border-bottom-left-radius:2px}.praxis-ai-assistant-shell__message--user{align-self:flex-end;border-bottom-right-radius:2px;background:var(--md-sys-color-primary-container, #17375f);color:var(--md-sys-color-on-primary-container, #f8fafc)}.praxis-ai-assistant-shell__message--status{color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.praxis-ai-assistant-shell__message--error,.praxis-ai-assistant-shell__error{color:var(--md-sys-color-error, #ff6b6b)}.praxis-ai-assistant-shell__quick-replies{display:flex;flex-wrap:wrap;gap:8px}.praxis-ai-assistant-shell__attachment{flex:0 0 auto;display:inline-flex;align-items:center;gap:7px;max-width:260px;min-height:34px;padding:4px 4px 4px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface-container-high, #263244)}.praxis-ai-assistant-shell__attachment--error{border-color:color-mix(in srgb,var(--md-sys-color-error, #ff6b6b) 60%,transparent)}.praxis-ai-assistant-shell__attachment-preview{flex:0 0 auto;width:28px;height:28px;border-radius:6px;object-fit:cover}.praxis-ai-assistant-shell__attachment-name,.praxis-ai-assistant-shell__attachment-kind{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.praxis-ai-assistant-shell__attachment-name{color:var(--md-sys-color-on-surface, #f8fafc)}.praxis-ai-assistant-shell__attachment-kind{color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.praxis-ai-assistant-shell__quick-reply{max-width:100%;border-radius:999px}.praxis-ai-assistant-shell__status,.praxis-ai-assistant-shell__error{margin:0;font-size:13px;line-height:1.35;overflow-wrap:anywhere}.praxis-ai-assistant-shell__footer{flex:0 0 auto;padding:10px 12px 12px;border-top:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 72%,transparent);background:var(--md-sys-color-surface-container-low, #111827)}.praxis-ai-assistant-shell__composer{display:grid;gap:8px;border:1px solid var(--md-sys-color-outline-variant, #334155);border-radius:8px;background:var(--md-sys-color-surface-container-lowest, #020617);box-shadow:inset 0 1px #ffffff0a}.praxis-ai-assistant-shell__composer:focus-within{border-color:var(--md-sys-color-primary, #60a5fa);box-shadow:0 0 0 2px #60a5fa29}.praxis-ai-assistant-shell__composer-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:0 8px 8px;flex-wrap:wrap}.praxis-ai-assistant-shell__resize-handle{position:absolute;right:0;bottom:0;width:22px;height:22px;border:0;background:transparent;cursor:nwse-resize;touch-action:none}.praxis-ai-assistant-shell__resize-handle:after{content:\"\";position:absolute;right:6px;bottom:6px;width:10px;height:10px;border-right:2px solid var(--md-sys-color-outline, #94a3b8);border-bottom:2px solid var(--md-sys-color-outline, #94a3b8)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.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: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i8.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i9.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7823
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisAiAssistantShellComponent, isStandalone: true, selector: "praxis-ai-assistant-shell", inputs: { labels: "labels", mode: "mode", state: "state", contextItems: "contextItems", attachments: "attachments", messages: "messages", quickReplies: "quickReplies", prompt: "prompt", statusText: "statusText", errorText: "errorText", testIdPrefix: "testIdPrefix", panelTestId: "panelTestId", submitTestId: "submitTestId", applyTestId: "applyTestId", primaryAction: "primaryAction", secondaryActions: "secondaryActions", governanceActions: "governanceActions", busy: "busy", canSubmit: "canSubmit", canApply: "canApply", submitOnEnter: "submitOnEnter", showAttachAction: "showAttachAction", enablePastedAttachments: "enablePastedAttachments", enableFileAttachments: "enableFileAttachments", attachmentAccept: "attachmentAccept", attachmentMultiple: "attachmentMultiple", draggable: "draggable", resizable: "resizable", minWidth: "minWidth", minHeight: "minHeight", margin: "margin", layout: "layout" }, outputs: { promptChange: "promptChange", submitPrompt: "submitPrompt", apply: "apply", retryTurn: "retryTurn", cancelTurn: "cancelTurn", shellAction: "shellAction", close: "close", attach: "attach", attachmentsPasted: "attachmentsPasted", attachmentsSelected: "attachmentsSelected", removeAttachment: "removeAttachment", messageAction: "messageAction", editMessage: "editMessage", resendMessage: "resendMessage", quickReply: "quickReply", layoutChange: "layoutChange" }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, static: true }, { propertyName: "conversation", first: true, predicate: ["conversation"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<section\n #panel\n class=\"praxis-ai-assistant-shell\"\n role=\"dialog\"\n [attr.aria-label]=\"resolvedLabels.title\"\n [attr.aria-busy]=\"busy ? 'true' : null\"\n [style.left.px]=\"currentLayout.left\"\n [style.top.px]=\"currentLayout.top\"\n [style.width.px]=\"currentLayout.width\"\n [style.height.px]=\"currentLayout.height\"\n [attr.data-testid]=\"panelTestId || testIdPrefix\"\n>\n <header\n class=\"praxis-ai-assistant-shell__header\"\n [attr.data-testid]=\"testIdPrefix + '-drag-handle'\"\n [attr.aria-label]=\"resolvedLabels.dragHandleAria\"\n (pointerdown)=\"startDrag($event)\"\n >\n <div class=\"praxis-ai-assistant-shell__identity\" aria-hidden=\"true\">\n <mat-icon>auto_awesome</mat-icon>\n </div>\n <div class=\"praxis-ai-assistant-shell__title-group\">\n <div class=\"praxis-ai-assistant-shell__title-row\">\n <strong>{{ resolvedLabels.title }}</strong>\n <div class=\"praxis-ai-assistant-shell__badges\" aria-hidden=\"true\">\n <span class=\"praxis-ai-assistant-shell__badge praxis-ai-assistant-shell__badge--context\">\n {{ getModeLabel() }}\n </span>\n <span\n class=\"praxis-ai-assistant-shell__badge praxis-ai-assistant-shell__badge--state\"\n [class.praxis-ai-assistant-shell__badge--error]=\"state === 'error'\"\n >\n <span class=\"praxis-ai-assistant-shell__state-dot\" aria-hidden=\"true\"></span>\n {{ getStateLabel() }}\n </span>\n </div>\n </div>\n <p *ngIf=\"resolvedLabels.subtitle\">{{ resolvedLabels.subtitle }}</p>\n </div>\n <div class=\"praxis-ai-assistant-shell__header-actions\">\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"resolvedLabels.close\"\n [attr.aria-label]=\"resolvedLabels.close\"\n (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"close.emit()\"\n [attr.data-testid]=\"testIdPrefix + '-close'\"\n >\n <mat-icon>{{ getCloseIcon() }}</mat-icon>\n </button>\n </div>\n </header>\n\n <div class=\"praxis-ai-assistant-shell__body\">\n <div\n *ngIf=\"contextItems.length\"\n class=\"praxis-ai-assistant-shell__context\"\n [attr.aria-label]=\"resolvedLabels.contextAria\"\n [attr.data-testid]=\"testIdPrefix + '-context'\"\n >\n <span\n *ngFor=\"let item of contextItems; trackBy: trackContextItem\"\n class=\"praxis-ai-assistant-shell__context-item\"\n [attr.data-testid]=\"testIdPrefix + '-context-' + item.id\"\n >\n <mat-icon *ngIf=\"item.icon\" aria-hidden=\"true\">{{ item.icon }}</mat-icon>\n <span class=\"praxis-ai-assistant-shell__context-label\">{{ item.label }}</span>\n <span *ngIf=\"item.value\" class=\"praxis-ai-assistant-shell__context-value\">{{ item.value }}</span>\n </span>\n </div>\n\n <div\n #conversation\n class=\"praxis-ai-assistant-shell__conversation\"\n [attr.data-testid]=\"testIdPrefix + '-conversation'\"\n [attr.aria-label]=\"resolvedLabels.conversationAria\"\n >\n <article\n *ngIf=\"!messages.length\"\n class=\"praxis-ai-assistant-shell__message praxis-ai-assistant-shell__message--assistant\"\n [attr.data-testid]=\"testIdPrefix + '-message-assistant-empty'\"\n >\n {{ resolvedLabels.emptyConversation }}\n </article>\n <article\n *ngFor=\"let message of messages; trackBy: trackMessage\"\n class=\"praxis-ai-assistant-shell__message\"\n [class.praxis-ai-assistant-shell__message--user]=\"message.role === 'user'\"\n [class.praxis-ai-assistant-shell__message--assistant]=\"message.role === 'assistant'\"\n [class.praxis-ai-assistant-shell__message--status]=\"message.role === 'status'\"\n [class.praxis-ai-assistant-shell__message--error]=\"message.role === 'error'\"\n [attr.data-testid]=\"testIdPrefix + '-message-' + message.role\"\n >\n <div\n class=\"praxis-ai-assistant-shell__message-content\"\n [innerHTML]=\"renderMessageText(message.text)\"\n ></div>\n <div\n *ngIf=\"message.actions?.length || message.editable || message.resendable\"\n class=\"praxis-ai-assistant-shell__message-actions\"\n >\n <button\n *ngIf=\"message.editable\"\n mat-icon-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action praxis-ai-assistant-shell__message-action--icon\"\n [disabled]=\"busy\"\n [matTooltip]=\"resolvedLabels.editMessage\"\n [attr.aria-label]=\"resolvedLabels.editMessage\"\n [attr.data-testid]=\"testIdPrefix + '-message-edit-' + message.id\"\n (click)=\"onMessageAction(message, { id: 'edit', label: resolvedLabels.editMessage, kind: 'edit' })\"\n >\n <mat-icon aria-hidden=\"true\">edit</mat-icon>\n </button>\n <button\n *ngIf=\"message.resendable\"\n mat-icon-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action praxis-ai-assistant-shell__message-action--icon\"\n [disabled]=\"busy\"\n [matTooltip]=\"resolvedLabels.resendMessage\"\n [attr.aria-label]=\"resolvedLabels.resendMessage\"\n [attr.data-testid]=\"testIdPrefix + '-message-resend-' + message.id\"\n (click)=\"onMessageAction(message, { id: 'resend', label: resolvedLabels.resendMessage, kind: 'resend' })\"\n >\n <mat-icon aria-hidden=\"true\">replay</mat-icon>\n </button>\n <ng-container *ngFor=\"let action of message.actions || []; trackBy: trackMessageAction\">\n <button\n *ngIf=\"isMessageActionIconOnly(action); else textualMessageAction\"\n mat-icon-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action praxis-ai-assistant-shell__message-action--icon\"\n [disabled]=\"busy || action.disabled\"\n [matTooltip]=\"getMessageActionLabel(action)\"\n [attr.aria-label]=\"getMessageActionLabel(action)\"\n [attr.data-testid]=\"testIdPrefix + '-message-action-' + message.id + '-' + action.id\"\n (click)=\"onMessageAction(message, action)\"\n >\n <mat-icon aria-hidden=\"true\">{{ getMessageActionIcon(action) }}</mat-icon>\n </button>\n <ng-template #textualMessageAction>\n <button\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy || action.disabled\"\n [attr.aria-label]=\"getMessageActionLabel(action)\"\n [attr.data-testid]=\"testIdPrefix + '-message-action-' + message.id + '-' + action.id\"\n (click)=\"onMessageAction(message, action)\"\n >\n {{ action.label }}\n </button>\n </ng-template>\n </ng-container>\n </div>\n </article>\n </div>\n\n <div\n *ngIf=\"attachments.length\"\n class=\"praxis-ai-assistant-shell__attachments\"\n [attr.aria-label]=\"resolvedLabels.attachmentsAria\"\n [attr.data-testid]=\"testIdPrefix + '-attachments'\"\n >\n <div\n *ngFor=\"let attachment of attachments; trackBy: trackAttachment\"\n class=\"praxis-ai-assistant-shell__attachment\"\n [class.praxis-ai-assistant-shell__attachment--error]=\"attachment.status === 'error'\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-' + attachment.id\"\n >\n <img\n *ngIf=\"attachment.previewUrl && attachment.kind === 'image'\"\n class=\"praxis-ai-assistant-shell__attachment-preview\"\n [src]=\"attachment.previewUrl\"\n [alt]=\"attachment.name\"\n >\n <span class=\"praxis-ai-assistant-shell__attachment-name\">{{ attachment.name }}</span>\n <span class=\"praxis-ai-assistant-shell__attachment-kind\">{{ attachment.kind }}</span>\n <button\n mat-icon-button\n type=\"button\"\n [disabled]=\"busy\"\n [matTooltip]=\"resolvedLabels.removeAttachment\"\n [attr.aria-label]=\"resolvedLabels.removeAttachment + ': ' + attachment.name\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-remove-' + attachment.id\"\n (click)=\"onRemoveAttachment(attachment)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n\n <div\n *ngIf=\"quickReplies.length\"\n class=\"praxis-ai-assistant-shell__quick-replies\"\n [attr.data-testid]=\"testIdPrefix + '-quick-replies'\"\n [attr.aria-label]=\"resolvedLabels.quickRepliesAria\"\n >\n <button\n *ngFor=\"let reply of quickReplies; trackBy: trackQuickReply\"\n mat-stroked-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__quick-reply\"\n [class.praxis-ai-assistant-shell__quick-reply--compact]=\"!isRichQuickReply(reply)\"\n [class.praxis-ai-assistant-shell__quick-reply--guided-action]=\"isGuidedActionQuickReply(reply)\"\n [class.praxis-ai-assistant-shell__quick-reply--contextual-action]=\"isContextualPreviewActionQuickReply(reply)\"\n [ngClass]=\"'praxis-ai-assistant-shell__quick-reply--tone-' + getQuickReplyTone(reply)\"\n [attr.data-testid]=\"testIdPrefix + '-quick-reply-' + (reply.id || reply.kind)\"\n [attr.aria-label]=\"getQuickReplyAriaLabel(reply)\"\n [disabled]=\"busy\"\n (click)=\"onQuickReply(reply)\"\n >\n <span class=\"praxis-ai-assistant-shell__quick-reply-ambient\" aria-hidden=\"true\"></span>\n <span class=\"praxis-ai-assistant-shell__quick-reply-icon-frame\" aria-hidden=\"true\">\n <mat-icon\n class=\"praxis-ai-assistant-shell__quick-reply-icon\"\n >\n {{ getQuickReplyIcon(reply) }}\n </mat-icon>\n </span>\n <span class=\"praxis-ai-assistant-shell__quick-reply-copy\">\n <span class=\"praxis-ai-assistant-shell__quick-reply-header\">\n <span class=\"praxis-ai-assistant-shell__quick-reply-heading\">\n <span class=\"praxis-ai-assistant-shell__quick-reply-label\">{{ reply.label }}</span>\n <span\n *ngIf=\"getQuickReplyDescription(reply)\"\n class=\"praxis-ai-assistant-shell__quick-reply-description\"\n >\n {{ getQuickReplyDescription(reply) }}\n </span>\n </span>\n <span class=\"praxis-ai-assistant-shell__quick-reply-actions\">\n <span class=\"praxis-ai-assistant-shell__quick-reply-badge\">\n {{ getQuickReplyCategoryLabel(reply) }}\n </span>\n <mat-icon\n *ngIf=\"getQuickReplyTechnicalDetails(reply)\"\n class=\"praxis-ai-assistant-shell__quick-reply-details\"\n [matTooltip]=\"getQuickReplyTechnicalDetails(reply)\"\n [attr.aria-label]=\"resolvedLabels.quickReplyDetails\"\n >\n info\n </mat-icon>\n </span>\n </span>\n <span\n *ngIf=\"getQuickReplyContextChips(reply).length\"\n class=\"praxis-ai-assistant-shell__quick-reply-context\"\n >\n <span\n *ngFor=\"let chip of getQuickReplyContextChips(reply)\"\n class=\"praxis-ai-assistant-shell__quick-reply-context-chip\"\n [attr.aria-label]=\"chip.ariaLabel\"\n >\n <mat-icon aria-hidden=\"true\">{{ chip.icon }}</mat-icon>\n <span>{{ chip.value }}</span>\n </span>\n </span>\n <span\n *ngIf=\"getQuickReplyPresentationItems(reply).length\"\n class=\"praxis-ai-assistant-shell__quick-reply-insights\"\n >\n <span\n *ngFor=\"let item of getQuickReplyPresentationItems(reply)\"\n class=\"praxis-ai-assistant-shell__quick-reply-insight\"\n >\n <mat-icon aria-hidden=\"true\">{{ item.icon }}</mat-icon>\n <span class=\"praxis-ai-assistant-shell__quick-reply-insight-copy\">\n <span class=\"praxis-ai-assistant-shell__quick-reply-insight-label\">\n {{ item.label }}\n </span>\n <span class=\"praxis-ai-assistant-shell__quick-reply-insight-value\">\n {{ item.value }}\n </span>\n </span>\n </span>\n </span>\n <span class=\"praxis-ai-assistant-shell__quick-reply-cta\">\n {{ getQuickReplyCtaLabel(reply) }}\n <mat-icon aria-hidden=\"true\">arrow_forward</mat-icon>\n </span>\n </span>\n </button>\n </div>\n <p\n *ngIf=\"shouldShowStatusText()\"\n class=\"praxis-ai-assistant-shell__status\"\n [attr.data-testid]=\"testIdPrefix + '-status'\"\n >\n {{ statusText }}\n </p>\n <p\n *ngIf=\"shouldShowErrorText()\"\n class=\"praxis-ai-assistant-shell__error\"\n [attr.data-testid]=\"testIdPrefix + '-error'\"\n >\n {{ errorText }}\n </p>\n </div>\n\n <footer class=\"praxis-ai-assistant-shell__footer\">\n <label class=\"praxis-ai-assistant-shell__label\" for=\"praxis-ai-assistant-shell-prompt\">\n {{ resolvedLabels.prompt }}\n </label>\n <div class=\"praxis-ai-assistant-shell__composer\">\n <textarea\n id=\"praxis-ai-assistant-shell-prompt\"\n class=\"praxis-ai-assistant-shell__prompt\"\n [attr.data-testid]=\"testIdPrefix + '-prompt'\"\n [placeholder]=\"resolvedLabels.promptPlaceholder\"\n [ngModel]=\"currentPrompt\"\n [disabled]=\"busy\"\n (ngModelChange)=\"onPromptInput($event)\"\n (keydown)=\"onPromptKeydown($event)\"\n (paste)=\"onPromptPaste($event)\"\n ></textarea>\n <div class=\"praxis-ai-assistant-shell__composer-actions\">\n <ng-container *ngIf=\"showAttachAction\">\n <input\n #attachmentInput\n type=\"file\"\n hidden\n [attr.accept]=\"attachmentAccept || null\"\n [attr.multiple]=\"attachmentMultiple ? '' : null\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-input'\"\n (change)=\"onAttachmentFilesSelected($event)\"\n >\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"busy\"\n (click)=\"onAttachClick(attachmentInput)\"\n [attr.data-testid]=\"testIdPrefix + '-attach'\"\n >\n <mat-icon>attach_file</mat-icon>\n {{ resolvedLabels.attach }}\n </button>\n </ng-container>\n <button\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n class=\"praxis-ai-assistant-shell__action praxis-ai-assistant-shell__action--primary\"\n [class.praxis-ai-assistant-shell__action--icon-only]=\"getPrimaryAction().iconOnly\"\n [matTooltip]=\"getPrimaryActionTooltip(getPrimaryAction())\"\n [ngClass]=\"'praxis-ai-assistant-shell__action--tone-' + getShellActionTone(getPrimaryAction())\"\n [disabled]=\"isShellActionDisabled(getPrimaryAction())\"\n (click)=\"onShellAction(getPrimaryAction())\"\n [attr.data-testid]=\"getPrimaryAction().testId || (submitTestId || (testIdPrefix + '-submit'))\"\n [attr.aria-label]=\"getPrimaryAction().ariaLabel || getPrimaryAction().label\"\n >\n <mat-icon *ngIf=\"getPrimaryAction().icon\" aria-hidden=\"true\">{{ getPrimaryAction().icon }}</mat-icon>\n <span *ngIf=\"!getPrimaryAction().iconOnly\">{{ getPrimaryAction().label }}</span>\n </button>\n <button\n *ngFor=\"let action of getSecondaryActions(); trackBy: trackShellAction\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__action praxis-ai-assistant-shell__action--secondary\"\n [ngClass]=\"'praxis-ai-assistant-shell__action--tone-' + getShellActionTone(action)\"\n [disabled]=\"isShellActionDisabled(action)\"\n (click)=\"onShellAction(action)\"\n [attr.data-testid]=\"action.testId || (testIdPrefix + '-action-' + action.id)\"\n [attr.aria-label]=\"action.ariaLabel || action.label\"\n >\n <mat-icon *ngIf=\"action.icon\" aria-hidden=\"true\">{{ action.icon }}</mat-icon>\n {{ action.label }}\n </button>\n <mat-spinner *ngIf=\"busy\" diameter=\"20\" [attr.data-testid]=\"testIdPrefix + '-spinner'\"></mat-spinner>\n </div>\n </div>\n </footer>\n\n <ng-container *ngIf=\"resizable\">\n <span\n *ngFor=\"let direction of resizeHandles; trackBy: trackResizeHandle\"\n class=\"praxis-ai-assistant-shell__resize-handle praxis-ai-assistant-shell__resize-handle--{{ direction }}\"\n [attr.data-testid]=\"direction === 'se' ? testIdPrefix + '-resize-handle' : testIdPrefix + '-resize-handle-' + direction\"\n aria-hidden=\"true\"\n role=\"presentation\"\n (pointerdown)=\"startResize(direction, $event)\"\n ></span>\n </ng-container>\n</section>\n", styles: [":host{display:block}.praxis-ai-assistant-shell{--praxis-ai-assistant-shell-shadow-color: var(--md-sys-color-shadow);--praxis-ai-assistant-shell-highlight-color: var(--md-sys-color-on-surface);--praxis-ai-assistant-shell-tone-analytics: var(--md-sys-color-primary);--praxis-ai-assistant-shell-tone-resource: var(--md-sys-color-tertiary);--praxis-ai-assistant-shell-tone-success: var(--md-sys-color-tertiary);--praxis-ai-assistant-shell-tone-warning: var(--md-sys-color-secondary);--praxis-ai-assistant-shell-tone-danger: var(--md-sys-color-error);--praxis-ai-assistant-shell-tone-neutral: var(--md-sys-color-outline);position:fixed;box-sizing:border-box;min-width:360px;min-height:360px;display:flex;flex-direction:column;overflow:hidden;color:var(--md-sys-color-on-surface);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface);box-shadow:0 24px 60px color-mix(in srgb,var(--praxis-ai-assistant-shell-shadow-color) 40%,transparent);z-index:var(--praxis-ai-assistant-shell-z-index, 1200)}.praxis-ai-assistant-shell__header{flex:0 0 auto;display:flex;align-items:flex-start;gap:9px;padding:10px 10px 9px;border-color:color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent);background:var(--md-sys-color-surface-container)}.praxis-ai-assistant-shell__header{justify-content:flex-start;border-bottom:1px solid;cursor:move;touch-action:none}.praxis-ai-assistant-shell__identity{flex:0 0 auto;display:grid;place-items:center;width:30px;height:30px;border-radius:8px;background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);box-shadow:0 6px 16px color-mix(in srgb,var(--md-sys-color-primary) 22%,transparent)}.praxis-ai-assistant-shell__identity mat-icon{width:18px;height:18px;font-size:18px}.praxis-ai-assistant-shell__title-group{min-width:0;flex:1 1 auto;display:grid;gap:4px}.praxis-ai-assistant-shell__title-row{min-width:0;display:flex;align-items:center;gap:8px}.praxis-ai-assistant-shell__badges{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:4px;min-width:0;flex-wrap:wrap}.praxis-ai-assistant-shell__badge{display:inline-flex;align-items:center;gap:5px;min-height:20px;max-width:120px;padding:2px 7px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 80%,transparent);border-radius:8px;color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);font-size:11px;line-height:1.2;overflow-wrap:anywhere}.praxis-ai-assistant-shell__badge--context{color:var(--md-sys-color-on-surface);background:color-mix(in srgb,var(--md-sys-color-primary) 9%,var(--md-sys-color-surface-container-high))}.praxis-ai-assistant-shell__badge--state{border-color:transparent;background:transparent;color:var(--md-sys-color-on-surface-variant);padding-inline:3px}.praxis-ai-assistant-shell__badge--error{color:var(--md-sys-color-error);border-color:color-mix(in srgb,var(--md-sys-color-error) 60%,transparent)}.praxis-ai-assistant-shell__state-dot{flex:0 0 auto;width:6px;height:6px;border-radius:999px;background:var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.praxis-ai-assistant-shell__badge--error .praxis-ai-assistant-shell__state-dot{background:var(--md-sys-color-error);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-error) 16%,transparent)}.praxis-ai-assistant-shell__title-group strong,.praxis-ai-assistant-shell__title-group p{min-width:0;margin:0;overflow-wrap:anywhere}.praxis-ai-assistant-shell__title-group strong{flex:1 1 auto;font-size:13px;line-height:1.2}.praxis-ai-assistant-shell__title-group p{color:var(--md-sys-color-on-surface-variant);font-size:11.5px;line-height:1.3}.praxis-ai-assistant-shell__header-actions{flex:0 0 auto;display:flex;align-items:center;gap:2px;margin-top:-3px}.praxis-ai-assistant-shell__header-actions button{width:34px;height:34px;color:var(--md-sys-color-on-surface-variant);opacity:.78}.praxis-ai-assistant-shell__header-actions button:hover,.praxis-ai-assistant-shell__header-actions button:focus-visible{opacity:1}.praxis-ai-assistant-shell__header-actions mat-icon{width:18px;height:18px;font-size:18px}.praxis-ai-assistant-shell__body{min-height:0;flex:1 1 auto;display:flex;flex-direction:column;gap:10px;padding:12px 12px 10px;overflow:auto;background:linear-gradient(180deg,var(--md-sys-color-surface-container-low),var(--md-sys-color-surface))}.praxis-ai-assistant-shell__context,.praxis-ai-assistant-shell__attachments{flex:0 0 auto;display:flex;align-items:center;gap:8px;overflow-x:auto}.praxis-ai-assistant-shell__context-item{flex:0 0 auto;display:inline-flex;align-items:center;gap:5px;max-width:240px;padding:5px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface-container-high);font-size:12px;line-height:1.25}.praxis-ai-assistant-shell__context-item mat-icon{width:16px;height:16px;font-size:16px}.praxis-ai-assistant-shell__context-label,.praxis-ai-assistant-shell__context-value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-shell__context-label{color:var(--md-sys-color-on-surface)}.praxis-ai-assistant-shell__context-value{color:var(--md-sys-color-on-surface-variant)}.praxis-ai-assistant-shell__label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.praxis-ai-assistant-shell__prompt{box-sizing:border-box;width:100%;min-height:46px;max-height:96px;resize:none;border:0;padding:10px 12px 8px;color:var(--md-sys-color-on-surface);background:transparent;font:inherit;line-height:1.45;outline:none}.praxis-ai-assistant-shell__conversation{min-height:0;flex:1 1 auto;display:flex;flex-direction:column;gap:8px;overflow:auto;padding:2px}.praxis-ai-assistant-shell__message{max-width:86%;align-self:flex-start;padding:9px 11px;border-radius:8px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface);font-size:13px;line-height:1.35;overflow-wrap:anywhere}.praxis-ai-assistant-shell__message-content{white-space:normal}.praxis-ai-assistant-shell__message-content :where(p,ul,h3,h4,h5){margin:0}.praxis-ai-assistant-shell__message-content :where(p,ul,h3,h4,h5)+:where(p,ul,h3,h4,h5){margin-top:8px}.praxis-ai-assistant-shell__message-content ul{padding-left:18px}.praxis-ai-assistant-shell__message-content li+li{margin-top:4px}.praxis-ai-assistant-shell__message-content code{padding:1px 4px;border-radius:4px;background:color-mix(in srgb,var(--md-sys-color-on-surface) 10%,transparent);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.94em}.praxis-ai-assistant-shell__message-actions{display:flex;align-items:center;gap:4px;flex-wrap:wrap;margin-top:8px}.praxis-ai-assistant-shell__message-action{min-height:28px;padding:0 8px;border-radius:8px;font-size:12px}.praxis-ai-assistant-shell__message-action--icon{width:30px;min-width:30px;height:30px;padding:0;color:var(--md-sys-color-on-surface-variant)}.praxis-ai-assistant-shell__message-action--icon mat-icon{width:17px;height:17px;font-size:17px}.praxis-ai-assistant-shell__message--assistant{border-bottom-left-radius:2px}.praxis-ai-assistant-shell__message--user{align-self:flex-end;border-bottom-right-radius:2px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.praxis-ai-assistant-shell__message--status{color:var(--md-sys-color-on-surface-variant)}.praxis-ai-assistant-shell__message--error,.praxis-ai-assistant-shell__error{color:var(--md-sys-color-error)}.praxis-ai-assistant-shell__quick-replies{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,520px),1fr));gap:8px;align-items:stretch;padding-bottom:4px}.praxis-ai-assistant-shell__attachment{flex:0 0 auto;display:inline-flex;align-items:center;gap:7px;max-width:260px;min-height:34px;padding:4px 4px 4px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface-container-high)}.praxis-ai-assistant-shell__attachment--error{border-color:color-mix(in srgb,var(--md-sys-color-error) 60%,transparent)}.praxis-ai-assistant-shell__attachment-preview{flex:0 0 auto;width:28px;height:28px;border-radius:6px;object-fit:cover}.praxis-ai-assistant-shell__attachment-name,.praxis-ai-assistant-shell__attachment-kind{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.praxis-ai-assistant-shell__attachment-name{color:var(--md-sys-color-on-surface)}.praxis-ai-assistant-shell__attachment-kind{color:var(--md-sys-color-on-surface-variant)}.praxis-ai-assistant-shell__quick-reply{--praxis-ai-assistant-shell-quick-reply-accent: var(--md-sys-color-primary);--praxis-ai-assistant-shell-quick-reply-background: color-mix( in srgb, var(--praxis-ai-assistant-shell-quick-reply-accent) 7%, var(--md-sys-color-surface-container-high) );--praxis-ai-assistant-shell-quick-reply-foreground: var(--md-sys-color-on-surface);width:100%;max-width:100%;height:auto;min-height:0;position:relative;overflow:hidden;padding:15px 16px;align-items:stretch;border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 36%,transparent);border-radius:22px;color:var(--praxis-ai-assistant-shell-quick-reply-foreground);background:linear-gradient(135deg,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 16%,transparent),transparent 46%),radial-gradient(circle at 92% 10%,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 22%,transparent),transparent 32%),var(--praxis-ai-assistant-shell-quick-reply-background);box-shadow:0 18px 42px color-mix(in srgb,var(--praxis-ai-assistant-shell-shadow-color) 22%,transparent),inset 0 1px color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 16%,transparent);letter-spacing:normal;white-space:normal;text-align:left;text-transform:none;-webkit-user-select:none;user-select:none;transition:border-color .16s ease,box-shadow .16s ease,transform .16s ease,background .16s ease;--mdc-outlined-button-container-height: auto;--mat-outlined-button-horizontal-padding: 0}.praxis-ai-assistant-shell__quick-reply:hover:not(:disabled),.praxis-ai-assistant-shell__quick-reply:focus-visible{border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 74%,transparent);box-shadow:0 18px 42px color-mix(in srgb,var(--praxis-ai-assistant-shell-shadow-color) 24%,transparent),0 0 0 3px color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 18%,transparent),inset 0 1px color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 18%,transparent);transform:translateY(-1px)}.praxis-ai-assistant-shell__quick-reply--contextual-action{padding:10px 12px;border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 34%,transparent);border-radius:8px;background:linear-gradient(90deg,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 15%,transparent),color-mix(in srgb,var(--md-sys-color-surface-container-high) 92%,transparent)),var(--md-sys-color-surface-container-high);box-shadow:none}.praxis-ai-assistant-shell__quick-reply--guided-action{padding:10px 12px;border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 30%,transparent);border-radius:8px;background:linear-gradient(90deg,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 10%,transparent),color-mix(in srgb,var(--md-sys-color-surface-container-high) 96%,transparent)),var(--md-sys-color-surface-container-high);box-shadow:none}.praxis-ai-assistant-shell__quick-reply--contextual-action:hover:not(:disabled),.praxis-ai-assistant-shell__quick-reply--contextual-action:focus-visible,.praxis-ai-assistant-shell__quick-reply--guided-action:hover:not(:disabled),.praxis-ai-assistant-shell__quick-reply--guided-action:focus-visible{border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 62%,transparent);box-shadow:0 0 0 2px color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 15%,transparent);transform:none}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-ambient,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-ambient{display:none}.praxis-ai-assistant-shell__quick-reply--contextual-action ::ng-deep .mdc-button__label,.praxis-ai-assistant-shell__quick-reply--guided-action ::ng-deep .mdc-button__label{gap:10px}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-icon-frame,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-icon-frame{width:34px;height:34px;border-radius:8px;box-shadow:none}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-icon,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-icon{width:19px;height:19px;font-size:19px}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-copy,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-copy{gap:6px}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-label,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-label{font-size:13.5px;font-weight:760;letter-spacing:0}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-description,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-description{display:-webkit-box;overflow:hidden;font-size:12px;line-height:1.34;-webkit-box-orient:vertical;-webkit-line-clamp:2}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-badge,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-badge{border-radius:8px;font-size:10px;letter-spacing:0}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-context-chip{border-radius:8px;padding:4px 7px;font-size:10.5px;font-weight:650}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-cta,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-cta{font-size:11.5px}.praxis-ai-assistant-shell__quick-reply--compact{justify-self:start;width:fit-content;min-width:min(100%,210px);max-width:min(100%,320px);padding:9px 11px;border-radius:16px;background:linear-gradient(135deg,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 10%,transparent),transparent 55%),var(--md-sys-color-surface-container-high);box-shadow:0 8px 18px color-mix(in srgb,var(--praxis-ai-assistant-shell-shadow-color) 16%,transparent),inset 0 1px color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 10%,transparent)}.praxis-ai-assistant-shell__quick-reply.praxis-ai-assistant-shell__quick-reply--compact ::ng-deep .mdc-button__label{align-items:center;gap:10px}.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-icon-frame{width:34px;height:34px;border-radius:12px}.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-icon{width:19px;height:19px;font-size:19px}.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-copy{gap:4px}.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-header{align-items:center}.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-label{font-size:13px;line-height:1.2}.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-badge,.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-cta{display:none}.praxis-ai-assistant-shell__quick-reply ::ng-deep .mdc-button__label{position:relative;z-index:1;min-width:0;display:grid;grid-template-columns:auto minmax(0,1fr);align-items:flex-start;gap:14px;width:100%;height:auto;line-height:normal}.praxis-ai-assistant-shell__quick-reply-ambient{position:absolute;inset:0;pointer-events:none}.praxis-ai-assistant-shell__quick-reply-ambient:before{content:\"\";position:absolute;inset:0 18px auto;height:1px;border-radius:999px;background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 90%,var(--praxis-ai-assistant-shell-highlight-color)),transparent);opacity:.58}.praxis-ai-assistant-shell__quick-reply-ambient:after{content:\"\";position:absolute;top:-34px;right:-44px;width:150px;height:150px;border-radius:999px;background:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 13%,transparent);filter:blur(18px)}.praxis-ai-assistant-shell__quick-reply ::ng-deep .mat-mdc-button-touch-target{height:100%;min-height:48px}.praxis-ai-assistant-shell__quick-reply-icon-frame{flex:0 0 auto;width:46px;height:46px;display:inline-grid;place-items:center;border:1px solid color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 34%,transparent);border-radius:17px;color:var(--praxis-ai-assistant-shell-quick-reply-accent);background:linear-gradient(145deg,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 24%,transparent),color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 6%,transparent));box-shadow:inset 0 1px color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 18%,transparent),0 10px 24px color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 12%,transparent)}.praxis-ai-assistant-shell__quick-reply-icon{width:24px;height:24px;font-size:24px}.praxis-ai-assistant-shell__quick-reply-details{flex:0 0 auto;width:24px;height:24px;display:inline-grid;place-items:center;border:1px solid color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 14%,transparent);border-radius:50%;color:var(--md-sys-color-on-surface-variant);background:color-mix(in srgb,var(--md-sys-color-outline) 14%,transparent);font-size:17px;opacity:.9}.praxis-ai-assistant-shell__quick-reply-copy{min-width:0;display:grid;gap:10px;flex:1 1 auto}.praxis-ai-assistant-shell__quick-reply-header{min-width:0;display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.praxis-ai-assistant-shell__quick-reply-heading{min-width:0;display:grid;gap:5px}.praxis-ai-assistant-shell__quick-reply-actions{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:flex-end;gap:7px;max-width:46%}.praxis-ai-assistant-shell__quick-reply-label,.praxis-ai-assistant-shell__quick-reply-description{min-width:0;overflow-wrap:anywhere}.praxis-ai-assistant-shell__quick-reply-label{color:var(--md-sys-color-on-surface);font-size:17px;font-weight:760;letter-spacing:.005em;line-height:1.18;text-transform:none}.praxis-ai-assistant-shell__quick-reply-badge{flex:0 0 auto;max-width:100%;padding:4px 8px;border:1px solid color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 28%,transparent);border-radius:999px;color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 86%,var(--praxis-ai-assistant-shell-highlight-color));background:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 11%,transparent);font-size:10.5px;font-weight:700;letter-spacing:.02em;line-height:1.1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-shell__quick-reply-description{color:color-mix(in srgb,var(--md-sys-color-on-surface) 80%,transparent);font-size:13px;line-height:1.46}.praxis-ai-assistant-shell__quick-reply-context{display:flex;flex-wrap:wrap;gap:7px;align-items:center}.praxis-ai-assistant-shell__quick-reply-context-chip{min-width:0;display:inline-flex;align-items:center;gap:4px;max-width:100%;padding:5px 8px;border:1px solid color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 22%,transparent);border-radius:999px;color:color-mix(in srgb,var(--md-sys-color-on-surface) 82%,transparent);background:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 9%,transparent);font-size:11px;font-weight:650;line-height:1.15}.praxis-ai-assistant-shell__quick-reply-context-chip mat-icon{width:14px;height:14px;color:var(--praxis-ai-assistant-shell-quick-reply-accent);font-size:14px}.praxis-ai-assistant-shell__quick-reply-context-chip span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-shell__quick-reply-insights{display:grid;grid-template-columns:repeat(auto-fit,minmax(145px,1fr));gap:8px;padding:8px;border:1px solid color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 16%,transparent);border-radius:16px;background:linear-gradient(180deg,color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 5%,transparent),color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 26%,transparent))}.praxis-ai-assistant-shell__quick-reply-insight{min-width:0;display:grid;grid-template-columns:20px minmax(0,1fr);gap:8px;align-items:start;padding:8px;border:1px solid color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 10%,transparent);border-radius:13px;color:color-mix(in srgb,var(--md-sys-color-on-surface) 86%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 44%,transparent);font-size:12px;line-height:1.35}.praxis-ai-assistant-shell__quick-reply-insight mat-icon{width:17px;height:17px;color:var(--praxis-ai-assistant-shell-quick-reply-accent);font-size:17px;opacity:.9}.praxis-ai-assistant-shell__quick-reply-insight-copy{min-width:0;display:grid;gap:1px}.praxis-ai-assistant-shell__quick-reply-insight-label{color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 82%,var(--praxis-ai-assistant-shell-highlight-color));font-size:10.5px;font-weight:760;letter-spacing:.045em;line-height:1.2;text-transform:uppercase}.praxis-ai-assistant-shell__quick-reply-insight-value{min-width:0;overflow-wrap:anywhere}.praxis-ai-assistant-shell__quick-reply-cta{display:inline-flex;align-items:center;justify-self:start;gap:5px;padding:3px 0;color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 84%,var(--praxis-ai-assistant-shell-highlight-color));font-size:12px;font-weight:760;letter-spacing:.01em;line-height:1.2}.praxis-ai-assistant-shell__quick-reply-cta mat-icon{width:15px;height:15px;font-size:15px;transition:transform .16s ease}.praxis-ai-assistant-shell__quick-reply:hover:not(:disabled) .praxis-ai-assistant-shell__quick-reply-cta mat-icon,.praxis-ai-assistant-shell__quick-reply:focus-visible .praxis-ai-assistant-shell__quick-reply-cta mat-icon{transform:translate(2px)}.praxis-ai-assistant-shell__quick-reply--tone-analytics{--praxis-ai-assistant-shell-quick-reply-accent: var(--praxis-ai-assistant-shell-tone-analytics)}.praxis-ai-assistant-shell__quick-reply--tone-resource{--praxis-ai-assistant-shell-quick-reply-accent: var(--praxis-ai-assistant-shell-tone-resource)}.praxis-ai-assistant-shell__quick-reply--tone-warning{--praxis-ai-assistant-shell-quick-reply-accent: var(--praxis-ai-assistant-shell-tone-warning)}.praxis-ai-assistant-shell__quick-reply--tone-success{--praxis-ai-assistant-shell-quick-reply-accent: var(--praxis-ai-assistant-shell-tone-success)}.praxis-ai-assistant-shell__quick-reply--tone-danger{--praxis-ai-assistant-shell-quick-reply-accent: var(--praxis-ai-assistant-shell-tone-danger)}.praxis-ai-assistant-shell__quick-reply--tone-neutral{--praxis-ai-assistant-shell-quick-reply-accent: var(--praxis-ai-assistant-shell-tone-neutral)}@media(max-width:640px){.praxis-ai-assistant-shell__quick-reply{padding:13px;border-radius:19px}.praxis-ai-assistant-shell__quick-reply ::ng-deep .mdc-button__label{grid-template-columns:minmax(0,1fr);gap:10px}.praxis-ai-assistant-shell__quick-reply-icon-frame{width:38px;height:38px;border-radius:14px}.praxis-ai-assistant-shell__quick-reply-header{display:grid}.praxis-ai-assistant-shell__quick-reply-actions{justify-content:flex-start;max-width:100%}.praxis-ai-assistant-shell__quick-reply-insights{grid-template-columns:minmax(0,1fr)}}.praxis-ai-assistant-shell__status,.praxis-ai-assistant-shell__error{margin:0;font-size:13px;line-height:1.35;overflow-wrap:anywhere}.praxis-ai-assistant-shell__footer{flex:0 0 auto;padding:8px 10px 10px;border-top:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent);background:var(--md-sys-color-surface-container-low)}.praxis-ai-assistant-shell__composer{display:grid;gap:8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container-lowest);box-shadow:inset 0 1px color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 4%,transparent)}.praxis-ai-assistant-shell__composer:focus-within{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.praxis-ai-assistant-shell__composer-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:0 8px 7px;flex-wrap:wrap}.praxis-ai-assistant-shell__action{min-height:36px;border-radius:10px;font-weight:650}.praxis-ai-assistant-shell__action mat-icon{width:18px;height:18px;margin-right:6px;font-size:18px}.praxis-ai-assistant-shell__action--icon-only{width:40px;min-width:40px;height:40px;padding-inline:0;border-radius:50%}.praxis-ai-assistant-shell__action--icon-only mat-icon{margin-right:0}.praxis-ai-assistant-shell__action--secondary{color:var(--md-sys-color-on-surface-variant)}.praxis-ai-assistant-shell__action--tone-governance{color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent)}.praxis-ai-assistant-shell__action--tone-success{color:var(--praxis-ai-assistant-shell-tone-success);background:color-mix(in srgb,var(--praxis-ai-assistant-shell-tone-success) 10%,transparent)}.praxis-ai-assistant-shell__action--tone-warning{color:var(--praxis-ai-assistant-shell-tone-warning);background:color-mix(in srgb,var(--praxis-ai-assistant-shell-tone-warning) 10%,transparent)}.praxis-ai-assistant-shell__action--tone-danger{color:var(--md-sys-color-error);background:color-mix(in srgb,var(--md-sys-color-error) 10%,transparent)}.praxis-ai-assistant-shell__resize-handle{position:absolute;z-index:2;border:0;background:transparent;touch-action:none}.praxis-ai-assistant-shell__resize-handle--n,.praxis-ai-assistant-shell__resize-handle--s{left:16px;right:16px;height:10px;cursor:ns-resize}.praxis-ai-assistant-shell__resize-handle--n{top:-5px}.praxis-ai-assistant-shell__resize-handle--s{bottom:-5px}.praxis-ai-assistant-shell__resize-handle--e,.praxis-ai-assistant-shell__resize-handle--w{top:16px;bottom:16px;width:10px;cursor:ew-resize}.praxis-ai-assistant-shell__resize-handle--e{right:-5px}.praxis-ai-assistant-shell__resize-handle--w{left:-5px}.praxis-ai-assistant-shell__resize-handle--ne,.praxis-ai-assistant-shell__resize-handle--nw,.praxis-ai-assistant-shell__resize-handle--se,.praxis-ai-assistant-shell__resize-handle--sw{width:22px;height:22px}.praxis-ai-assistant-shell__resize-handle--ne{top:-5px;right:-5px;cursor:nesw-resize}.praxis-ai-assistant-shell__resize-handle--nw{top:-5px;left:-5px;cursor:nwse-resize}.praxis-ai-assistant-shell__resize-handle--se{right:-5px;bottom:-5px;cursor:nwse-resize}.praxis-ai-assistant-shell__resize-handle--sw{bottom:-5px;left:-5px;cursor:nesw-resize}.praxis-ai-assistant-shell__resize-handle--se:after{content:\"\";position:absolute;right:8px;bottom:8px;width:10px;height:10px;border-right:2px solid var(--md-sys-color-outline);border-bottom:2px solid var(--md-sys-color-outline)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.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: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i8.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i9.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6272
7824
|
}
|
|
6273
7825
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantShellComponent, decorators: [{
|
|
6274
7826
|
type: Component,
|
|
@@ -6279,7 +7831,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
6279
7831
|
MatIconModule,
|
|
6280
7832
|
MatProgressSpinnerModule,
|
|
6281
7833
|
MatTooltipModule,
|
|
6282
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section\n #panel\n class=\"praxis-ai-assistant-shell\"\n role=\"dialog\"\n [attr.aria-label]=\"resolvedLabels.title\"\n [attr.aria-busy]=\"busy ? 'true' : null\"\n [style.left.px]=\"currentLayout.left\"\n [style.top.px]=\"currentLayout.top\"\n [style.width.px]=\"currentLayout.width\"\n [style.height.px]=\"currentLayout.height\"\n [attr.data-testid]=\"panelTestId || testIdPrefix\"\n>\n <header\n class=\"praxis-ai-assistant-shell__header\"\n [attr.data-testid]=\"testIdPrefix + '-drag-handle'\"\n [attr.aria-label]=\"resolvedLabels.dragHandleAria\"\n (pointerdown)=\"startDrag($event)\"\n >\n <div class=\"praxis-ai-assistant-shell__identity\" aria-hidden=\"true\">\n <mat-icon>auto_awesome</mat-icon>\n </div>\n <div class=\"praxis-ai-assistant-shell__title-group\">\n <strong>{{ resolvedLabels.title }}</strong>\n <p *ngIf=\"resolvedLabels.subtitle\">{{ resolvedLabels.subtitle }}</p>\n </div>\n <div class=\"praxis-ai-assistant-shell__badges\" aria-hidden=\"true\">\n <span class=\"praxis-ai-assistant-shell__badge\">{{ getModeLabel() }}</span>\n <span class=\"praxis-ai-assistant-shell__badge\" [class.praxis-ai-assistant-shell__badge--error]=\"state === 'error'\">\n {{ getStateLabel() }}\n </span>\n </div>\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"resolvedLabels.close\"\n [attr.aria-label]=\"resolvedLabels.close\"\n (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"close.emit()\"\n [attr.data-testid]=\"testIdPrefix + '-close'\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </header>\n\n <div class=\"praxis-ai-assistant-shell__body\">\n <div\n *ngIf=\"contextItems.length\"\n class=\"praxis-ai-assistant-shell__context\"\n [attr.aria-label]=\"resolvedLabels.contextAria\"\n [attr.data-testid]=\"testIdPrefix + '-context'\"\n >\n <span\n *ngFor=\"let item of contextItems; trackBy: trackContextItem\"\n class=\"praxis-ai-assistant-shell__context-item\"\n [attr.data-testid]=\"testIdPrefix + '-context-' + item.id\"\n >\n <mat-icon *ngIf=\"item.icon\" aria-hidden=\"true\">{{ item.icon }}</mat-icon>\n <span class=\"praxis-ai-assistant-shell__context-label\">{{ item.label }}</span>\n <span *ngIf=\"item.value\" class=\"praxis-ai-assistant-shell__context-value\">{{ item.value }}</span>\n </span>\n </div>\n\n <div\n #conversation\n class=\"praxis-ai-assistant-shell__conversation\"\n [attr.data-testid]=\"testIdPrefix + '-conversation'\"\n [attr.aria-label]=\"resolvedLabels.conversationAria\"\n >\n <article\n *ngIf=\"!messages.length\"\n class=\"praxis-ai-assistant-shell__message praxis-ai-assistant-shell__message--assistant\"\n [attr.data-testid]=\"testIdPrefix + '-message-assistant-empty'\"\n >\n {{ resolvedLabels.emptyConversation }}\n </article>\n <article\n *ngFor=\"let message of messages; trackBy: trackMessage\"\n class=\"praxis-ai-assistant-shell__message\"\n [class.praxis-ai-assistant-shell__message--user]=\"message.role === 'user'\"\n [class.praxis-ai-assistant-shell__message--assistant]=\"message.role === 'assistant'\"\n [class.praxis-ai-assistant-shell__message--status]=\"message.role === 'status'\"\n [class.praxis-ai-assistant-shell__message--error]=\"message.role === 'error'\"\n [attr.data-testid]=\"testIdPrefix + '-message-' + message.role\"\n >\n {{ message.text }}\n <div\n *ngIf=\"message.actions?.length || message.editable || message.resendable\"\n class=\"praxis-ai-assistant-shell__message-actions\"\n >\n <button\n *ngIf=\"message.editable\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy\"\n [attr.data-testid]=\"testIdPrefix + '-message-edit-' + message.id\"\n (click)=\"onMessageAction(message, { id: 'edit', label: resolvedLabels.editMessage, kind: 'edit' })\"\n >\n {{ resolvedLabels.editMessage }}\n </button>\n <button\n *ngIf=\"message.resendable\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy\"\n [attr.data-testid]=\"testIdPrefix + '-message-resend-' + message.id\"\n (click)=\"onMessageAction(message, { id: 'resend', label: resolvedLabels.resendMessage, kind: 'resend' })\"\n >\n {{ resolvedLabels.resendMessage }}\n </button>\n <button\n *ngFor=\"let action of message.actions || []; trackBy: trackMessageAction\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy || action.disabled\"\n [attr.data-testid]=\"testIdPrefix + '-message-action-' + message.id + '-' + action.id\"\n (click)=\"onMessageAction(message, action)\"\n >\n {{ action.label }}\n </button>\n </div>\n </article>\n </div>\n\n <div\n *ngIf=\"attachments.length\"\n class=\"praxis-ai-assistant-shell__attachments\"\n [attr.aria-label]=\"resolvedLabels.attachmentsAria\"\n [attr.data-testid]=\"testIdPrefix + '-attachments'\"\n >\n <div\n *ngFor=\"let attachment of attachments; trackBy: trackAttachment\"\n class=\"praxis-ai-assistant-shell__attachment\"\n [class.praxis-ai-assistant-shell__attachment--error]=\"attachment.status === 'error'\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-' + attachment.id\"\n >\n <img\n *ngIf=\"attachment.previewUrl && attachment.kind === 'image'\"\n class=\"praxis-ai-assistant-shell__attachment-preview\"\n [src]=\"attachment.previewUrl\"\n [alt]=\"attachment.name\"\n >\n <span class=\"praxis-ai-assistant-shell__attachment-name\">{{ attachment.name }}</span>\n <span class=\"praxis-ai-assistant-shell__attachment-kind\">{{ attachment.kind }}</span>\n <button\n mat-icon-button\n type=\"button\"\n [disabled]=\"busy\"\n [matTooltip]=\"resolvedLabels.removeAttachment\"\n [attr.aria-label]=\"resolvedLabels.removeAttachment + ': ' + attachment.name\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-remove-' + attachment.id\"\n (click)=\"onRemoveAttachment(attachment)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n\n <div\n *ngIf=\"quickReplies.length\"\n class=\"praxis-ai-assistant-shell__quick-replies\"\n [attr.data-testid]=\"testIdPrefix + '-quick-replies'\"\n [attr.aria-label]=\"resolvedLabels.quickRepliesAria\"\n >\n <button\n *ngFor=\"let reply of quickReplies; trackBy: trackQuickReply\"\n mat-stroked-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__quick-reply\"\n [attr.data-testid]=\"testIdPrefix + '-quick-reply-' + (reply.id || reply.kind)\"\n [disabled]=\"busy\"\n (click)=\"onQuickReply(reply)\"\n >\n {{ reply.label }}\n </button>\n </div>\n <p\n *ngIf=\"statusText\"\n class=\"praxis-ai-assistant-shell__status\"\n [attr.data-testid]=\"testIdPrefix + '-status'\"\n >\n {{ statusText }}\n </p>\n <p\n *ngIf=\"errorText\"\n class=\"praxis-ai-assistant-shell__error\"\n [attr.data-testid]=\"testIdPrefix + '-error'\"\n >\n {{ errorText }}\n </p>\n </div>\n\n <footer class=\"praxis-ai-assistant-shell__footer\">\n <label class=\"praxis-ai-assistant-shell__label\" for=\"praxis-ai-assistant-shell-prompt\">\n {{ resolvedLabels.prompt }}\n </label>\n <div class=\"praxis-ai-assistant-shell__composer\">\n <textarea\n id=\"praxis-ai-assistant-shell-prompt\"\n class=\"praxis-ai-assistant-shell__prompt\"\n [attr.data-testid]=\"testIdPrefix + '-prompt'\"\n [placeholder]=\"resolvedLabels.promptPlaceholder\"\n [ngModel]=\"currentPrompt\"\n [disabled]=\"busy\"\n (ngModelChange)=\"onPromptInput($event)\"\n (keydown)=\"onPromptKeydown($event)\"\n (paste)=\"onPromptPaste($event)\"\n ></textarea>\n <div class=\"praxis-ai-assistant-shell__composer-actions\">\n <input\n #attachmentInput\n type=\"file\"\n hidden\n [attr.accept]=\"attachmentAccept || null\"\n [attr.multiple]=\"attachmentMultiple ? '' : null\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-input'\"\n (change)=\"onAttachmentFilesSelected($event)\"\n >\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"busy\"\n (click)=\"onAttachClick(attachmentInput)\"\n [attr.data-testid]=\"testIdPrefix + '-attach'\"\n >\n <mat-icon>attach_file</mat-icon>\n {{ resolvedLabels.attach }}\n </button>\n <button\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n [disabled]=\"busy || !canSubmit || !currentPrompt.trim()\"\n (click)=\"onSubmit()\"\n [attr.data-testid]=\"submitTestId || (testIdPrefix + '-submit')\"\n >\n {{ resolvedLabels.submit }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"busy || !canApply\"\n (click)=\"onApply()\"\n [attr.data-testid]=\"applyTestId || (testIdPrefix + '-apply')\"\n >\n {{ resolvedLabels.apply }}\n </button>\n <mat-spinner *ngIf=\"busy\" diameter=\"20\" [attr.data-testid]=\"testIdPrefix + '-spinner'\"></mat-spinner>\n </div>\n </div>\n </footer>\n\n <button\n *ngIf=\"resizable\"\n type=\"button\"\n class=\"praxis-ai-assistant-shell__resize-handle\"\n [attr.data-testid]=\"testIdPrefix + '-resize-handle'\"\n [attr.aria-label]=\"resolvedLabels.resizeHandleAria\"\n [matTooltip]=\"resolvedLabels.resizeHandleAria\"\n (pointerdown)=\"startResize($event)\"\n ></button>\n</section>\n", styles: [":host{display:block}.praxis-ai-assistant-shell{position:absolute;box-sizing:border-box;min-width:360px;min-height:360px;display:flex;flex-direction:column;overflow:hidden;color:var(--md-sys-color-on-surface, #f8fafc);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface, #0f172a);box-shadow:0 24px 60px #0006;z-index:10}.praxis-ai-assistant-shell__header{flex:0 0 auto;display:flex;align-items:center;gap:10px;padding:12px;border-color:color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 72%,transparent);background:radial-gradient(circle at top left,rgba(96,165,250,.22),transparent 34%),var(--md-sys-color-surface-container, #172033)}.praxis-ai-assistant-shell__header{justify-content:flex-start;border-bottom:1px solid;cursor:move;touch-action:none}.praxis-ai-assistant-shell__identity{flex:0 0 auto;display:grid;place-items:center;width:34px;height:34px;border-radius:8px;background:var(--md-sys-color-primary, #60a5fa);color:var(--md-sys-color-on-primary, #020617);box-shadow:0 8px 20px #60a5fa4d}.praxis-ai-assistant-shell__identity mat-icon{width:20px;height:20px;font-size:20px}.praxis-ai-assistant-shell__title-group{min-width:0;flex:1 1 auto;display:grid;gap:3px}.praxis-ai-assistant-shell__badges{flex:0 1 auto;display:flex;align-items:center;justify-content:flex-end;gap:6px;min-width:0;flex-wrap:wrap}.praxis-ai-assistant-shell__badge{display:inline-flex;align-items:center;min-height:22px;max-width:140px;padding:2px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 80%,transparent);border-radius:8px;color:var(--md-sys-color-on-surface-variant, #cbd5e1);background:var(--md-sys-color-surface-container-high, #263244);font-size:11px;line-height:1.2;overflow-wrap:anywhere}.praxis-ai-assistant-shell__badge--error{color:var(--md-sys-color-error, #ff6b6b);border-color:color-mix(in srgb,var(--md-sys-color-error, #ff6b6b) 60%,transparent)}.praxis-ai-assistant-shell__title-group strong,.praxis-ai-assistant-shell__title-group p{min-width:0;margin:0;overflow-wrap:anywhere}.praxis-ai-assistant-shell__title-group p{color:var(--md-sys-color-on-surface-variant, #cbd5e1);font-size:12px;line-height:1.35}.praxis-ai-assistant-shell__body{min-height:0;flex:1 1 auto;display:flex;flex-direction:column;gap:12px;padding:14px 14px 8px;overflow:auto;background:linear-gradient(180deg,var(--md-sys-color-surface-container-low, #111827),var(--md-sys-color-surface, #0f172a))}.praxis-ai-assistant-shell__context,.praxis-ai-assistant-shell__attachments{flex:0 0 auto;display:flex;align-items:center;gap:8px;overflow-x:auto}.praxis-ai-assistant-shell__context-item{flex:0 0 auto;display:inline-flex;align-items:center;gap:5px;max-width:240px;padding:5px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface-container-high, #263244);font-size:12px;line-height:1.25}.praxis-ai-assistant-shell__context-item mat-icon{width:16px;height:16px;font-size:16px}.praxis-ai-assistant-shell__context-label,.praxis-ai-assistant-shell__context-value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-shell__context-label{color:var(--md-sys-color-on-surface, #f8fafc)}.praxis-ai-assistant-shell__context-value{color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.praxis-ai-assistant-shell__label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.praxis-ai-assistant-shell__prompt{box-sizing:border-box;width:100%;min-height:56px;max-height:128px;resize:none;border:0;padding:12px;color:var(--md-sys-color-on-surface, #f8fafc);background:transparent;font:inherit;line-height:1.45;outline:none}.praxis-ai-assistant-shell__conversation{min-height:0;flex:1 1 auto;display:flex;flex-direction:column;gap:8px;overflow:auto;padding:2px}.praxis-ai-assistant-shell__message{max-width:86%;align-self:flex-start;padding:9px 11px;border-radius:8px;background:var(--md-sys-color-surface-container-high, #263244);color:var(--md-sys-color-on-surface, #f8fafc);font-size:13px;line-height:1.35;overflow-wrap:anywhere}.praxis-ai-assistant-shell__message-actions{display:flex;align-items:center;gap:4px;flex-wrap:wrap;margin-top:8px}.praxis-ai-assistant-shell__message-action{min-height:28px;padding:0 8px;border-radius:8px;font-size:12px}.praxis-ai-assistant-shell__message--assistant{border-bottom-left-radius:2px}.praxis-ai-assistant-shell__message--user{align-self:flex-end;border-bottom-right-radius:2px;background:var(--md-sys-color-primary-container, #17375f);color:var(--md-sys-color-on-primary-container, #f8fafc)}.praxis-ai-assistant-shell__message--status{color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.praxis-ai-assistant-shell__message--error,.praxis-ai-assistant-shell__error{color:var(--md-sys-color-error, #ff6b6b)}.praxis-ai-assistant-shell__quick-replies{display:flex;flex-wrap:wrap;gap:8px}.praxis-ai-assistant-shell__attachment{flex:0 0 auto;display:inline-flex;align-items:center;gap:7px;max-width:260px;min-height:34px;padding:4px 4px 4px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface-container-high, #263244)}.praxis-ai-assistant-shell__attachment--error{border-color:color-mix(in srgb,var(--md-sys-color-error, #ff6b6b) 60%,transparent)}.praxis-ai-assistant-shell__attachment-preview{flex:0 0 auto;width:28px;height:28px;border-radius:6px;object-fit:cover}.praxis-ai-assistant-shell__attachment-name,.praxis-ai-assistant-shell__attachment-kind{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.praxis-ai-assistant-shell__attachment-name{color:var(--md-sys-color-on-surface, #f8fafc)}.praxis-ai-assistant-shell__attachment-kind{color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.praxis-ai-assistant-shell__quick-reply{max-width:100%;border-radius:999px}.praxis-ai-assistant-shell__status,.praxis-ai-assistant-shell__error{margin:0;font-size:13px;line-height:1.35;overflow-wrap:anywhere}.praxis-ai-assistant-shell__footer{flex:0 0 auto;padding:10px 12px 12px;border-top:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 72%,transparent);background:var(--md-sys-color-surface-container-low, #111827)}.praxis-ai-assistant-shell__composer{display:grid;gap:8px;border:1px solid var(--md-sys-color-outline-variant, #334155);border-radius:8px;background:var(--md-sys-color-surface-container-lowest, #020617);box-shadow:inset 0 1px #ffffff0a}.praxis-ai-assistant-shell__composer:focus-within{border-color:var(--md-sys-color-primary, #60a5fa);box-shadow:0 0 0 2px #60a5fa29}.praxis-ai-assistant-shell__composer-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:0 8px 8px;flex-wrap:wrap}.praxis-ai-assistant-shell__resize-handle{position:absolute;right:0;bottom:0;width:22px;height:22px;border:0;background:transparent;cursor:nwse-resize;touch-action:none}.praxis-ai-assistant-shell__resize-handle:after{content:\"\";position:absolute;right:6px;bottom:6px;width:10px;height:10px;border-right:2px solid var(--md-sys-color-outline, #94a3b8);border-bottom:2px solid var(--md-sys-color-outline, #94a3b8)}\n"] }]
|
|
7834
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section\n #panel\n class=\"praxis-ai-assistant-shell\"\n role=\"dialog\"\n [attr.aria-label]=\"resolvedLabels.title\"\n [attr.aria-busy]=\"busy ? 'true' : null\"\n [style.left.px]=\"currentLayout.left\"\n [style.top.px]=\"currentLayout.top\"\n [style.width.px]=\"currentLayout.width\"\n [style.height.px]=\"currentLayout.height\"\n [attr.data-testid]=\"panelTestId || testIdPrefix\"\n>\n <header\n class=\"praxis-ai-assistant-shell__header\"\n [attr.data-testid]=\"testIdPrefix + '-drag-handle'\"\n [attr.aria-label]=\"resolvedLabels.dragHandleAria\"\n (pointerdown)=\"startDrag($event)\"\n >\n <div class=\"praxis-ai-assistant-shell__identity\" aria-hidden=\"true\">\n <mat-icon>auto_awesome</mat-icon>\n </div>\n <div class=\"praxis-ai-assistant-shell__title-group\">\n <div class=\"praxis-ai-assistant-shell__title-row\">\n <strong>{{ resolvedLabels.title }}</strong>\n <div class=\"praxis-ai-assistant-shell__badges\" aria-hidden=\"true\">\n <span class=\"praxis-ai-assistant-shell__badge praxis-ai-assistant-shell__badge--context\">\n {{ getModeLabel() }}\n </span>\n <span\n class=\"praxis-ai-assistant-shell__badge praxis-ai-assistant-shell__badge--state\"\n [class.praxis-ai-assistant-shell__badge--error]=\"state === 'error'\"\n >\n <span class=\"praxis-ai-assistant-shell__state-dot\" aria-hidden=\"true\"></span>\n {{ getStateLabel() }}\n </span>\n </div>\n </div>\n <p *ngIf=\"resolvedLabels.subtitle\">{{ resolvedLabels.subtitle }}</p>\n </div>\n <div class=\"praxis-ai-assistant-shell__header-actions\">\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"resolvedLabels.close\"\n [attr.aria-label]=\"resolvedLabels.close\"\n (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"close.emit()\"\n [attr.data-testid]=\"testIdPrefix + '-close'\"\n >\n <mat-icon>{{ getCloseIcon() }}</mat-icon>\n </button>\n </div>\n </header>\n\n <div class=\"praxis-ai-assistant-shell__body\">\n <div\n *ngIf=\"contextItems.length\"\n class=\"praxis-ai-assistant-shell__context\"\n [attr.aria-label]=\"resolvedLabels.contextAria\"\n [attr.data-testid]=\"testIdPrefix + '-context'\"\n >\n <span\n *ngFor=\"let item of contextItems; trackBy: trackContextItem\"\n class=\"praxis-ai-assistant-shell__context-item\"\n [attr.data-testid]=\"testIdPrefix + '-context-' + item.id\"\n >\n <mat-icon *ngIf=\"item.icon\" aria-hidden=\"true\">{{ item.icon }}</mat-icon>\n <span class=\"praxis-ai-assistant-shell__context-label\">{{ item.label }}</span>\n <span *ngIf=\"item.value\" class=\"praxis-ai-assistant-shell__context-value\">{{ item.value }}</span>\n </span>\n </div>\n\n <div\n #conversation\n class=\"praxis-ai-assistant-shell__conversation\"\n [attr.data-testid]=\"testIdPrefix + '-conversation'\"\n [attr.aria-label]=\"resolvedLabels.conversationAria\"\n >\n <article\n *ngIf=\"!messages.length\"\n class=\"praxis-ai-assistant-shell__message praxis-ai-assistant-shell__message--assistant\"\n [attr.data-testid]=\"testIdPrefix + '-message-assistant-empty'\"\n >\n {{ resolvedLabels.emptyConversation }}\n </article>\n <article\n *ngFor=\"let message of messages; trackBy: trackMessage\"\n class=\"praxis-ai-assistant-shell__message\"\n [class.praxis-ai-assistant-shell__message--user]=\"message.role === 'user'\"\n [class.praxis-ai-assistant-shell__message--assistant]=\"message.role === 'assistant'\"\n [class.praxis-ai-assistant-shell__message--status]=\"message.role === 'status'\"\n [class.praxis-ai-assistant-shell__message--error]=\"message.role === 'error'\"\n [attr.data-testid]=\"testIdPrefix + '-message-' + message.role\"\n >\n <div\n class=\"praxis-ai-assistant-shell__message-content\"\n [innerHTML]=\"renderMessageText(message.text)\"\n ></div>\n <div\n *ngIf=\"message.actions?.length || message.editable || message.resendable\"\n class=\"praxis-ai-assistant-shell__message-actions\"\n >\n <button\n *ngIf=\"message.editable\"\n mat-icon-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action praxis-ai-assistant-shell__message-action--icon\"\n [disabled]=\"busy\"\n [matTooltip]=\"resolvedLabels.editMessage\"\n [attr.aria-label]=\"resolvedLabels.editMessage\"\n [attr.data-testid]=\"testIdPrefix + '-message-edit-' + message.id\"\n (click)=\"onMessageAction(message, { id: 'edit', label: resolvedLabels.editMessage, kind: 'edit' })\"\n >\n <mat-icon aria-hidden=\"true\">edit</mat-icon>\n </button>\n <button\n *ngIf=\"message.resendable\"\n mat-icon-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action praxis-ai-assistant-shell__message-action--icon\"\n [disabled]=\"busy\"\n [matTooltip]=\"resolvedLabels.resendMessage\"\n [attr.aria-label]=\"resolvedLabels.resendMessage\"\n [attr.data-testid]=\"testIdPrefix + '-message-resend-' + message.id\"\n (click)=\"onMessageAction(message, { id: 'resend', label: resolvedLabels.resendMessage, kind: 'resend' })\"\n >\n <mat-icon aria-hidden=\"true\">replay</mat-icon>\n </button>\n <ng-container *ngFor=\"let action of message.actions || []; trackBy: trackMessageAction\">\n <button\n *ngIf=\"isMessageActionIconOnly(action); else textualMessageAction\"\n mat-icon-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action praxis-ai-assistant-shell__message-action--icon\"\n [disabled]=\"busy || action.disabled\"\n [matTooltip]=\"getMessageActionLabel(action)\"\n [attr.aria-label]=\"getMessageActionLabel(action)\"\n [attr.data-testid]=\"testIdPrefix + '-message-action-' + message.id + '-' + action.id\"\n (click)=\"onMessageAction(message, action)\"\n >\n <mat-icon aria-hidden=\"true\">{{ getMessageActionIcon(action) }}</mat-icon>\n </button>\n <ng-template #textualMessageAction>\n <button\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy || action.disabled\"\n [attr.aria-label]=\"getMessageActionLabel(action)\"\n [attr.data-testid]=\"testIdPrefix + '-message-action-' + message.id + '-' + action.id\"\n (click)=\"onMessageAction(message, action)\"\n >\n {{ action.label }}\n </button>\n </ng-template>\n </ng-container>\n </div>\n </article>\n </div>\n\n <div\n *ngIf=\"attachments.length\"\n class=\"praxis-ai-assistant-shell__attachments\"\n [attr.aria-label]=\"resolvedLabels.attachmentsAria\"\n [attr.data-testid]=\"testIdPrefix + '-attachments'\"\n >\n <div\n *ngFor=\"let attachment of attachments; trackBy: trackAttachment\"\n class=\"praxis-ai-assistant-shell__attachment\"\n [class.praxis-ai-assistant-shell__attachment--error]=\"attachment.status === 'error'\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-' + attachment.id\"\n >\n <img\n *ngIf=\"attachment.previewUrl && attachment.kind === 'image'\"\n class=\"praxis-ai-assistant-shell__attachment-preview\"\n [src]=\"attachment.previewUrl\"\n [alt]=\"attachment.name\"\n >\n <span class=\"praxis-ai-assistant-shell__attachment-name\">{{ attachment.name }}</span>\n <span class=\"praxis-ai-assistant-shell__attachment-kind\">{{ attachment.kind }}</span>\n <button\n mat-icon-button\n type=\"button\"\n [disabled]=\"busy\"\n [matTooltip]=\"resolvedLabels.removeAttachment\"\n [attr.aria-label]=\"resolvedLabels.removeAttachment + ': ' + attachment.name\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-remove-' + attachment.id\"\n (click)=\"onRemoveAttachment(attachment)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n\n <div\n *ngIf=\"quickReplies.length\"\n class=\"praxis-ai-assistant-shell__quick-replies\"\n [attr.data-testid]=\"testIdPrefix + '-quick-replies'\"\n [attr.aria-label]=\"resolvedLabels.quickRepliesAria\"\n >\n <button\n *ngFor=\"let reply of quickReplies; trackBy: trackQuickReply\"\n mat-stroked-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__quick-reply\"\n [class.praxis-ai-assistant-shell__quick-reply--compact]=\"!isRichQuickReply(reply)\"\n [class.praxis-ai-assistant-shell__quick-reply--guided-action]=\"isGuidedActionQuickReply(reply)\"\n [class.praxis-ai-assistant-shell__quick-reply--contextual-action]=\"isContextualPreviewActionQuickReply(reply)\"\n [ngClass]=\"'praxis-ai-assistant-shell__quick-reply--tone-' + getQuickReplyTone(reply)\"\n [attr.data-testid]=\"testIdPrefix + '-quick-reply-' + (reply.id || reply.kind)\"\n [attr.aria-label]=\"getQuickReplyAriaLabel(reply)\"\n [disabled]=\"busy\"\n (click)=\"onQuickReply(reply)\"\n >\n <span class=\"praxis-ai-assistant-shell__quick-reply-ambient\" aria-hidden=\"true\"></span>\n <span class=\"praxis-ai-assistant-shell__quick-reply-icon-frame\" aria-hidden=\"true\">\n <mat-icon\n class=\"praxis-ai-assistant-shell__quick-reply-icon\"\n >\n {{ getQuickReplyIcon(reply) }}\n </mat-icon>\n </span>\n <span class=\"praxis-ai-assistant-shell__quick-reply-copy\">\n <span class=\"praxis-ai-assistant-shell__quick-reply-header\">\n <span class=\"praxis-ai-assistant-shell__quick-reply-heading\">\n <span class=\"praxis-ai-assistant-shell__quick-reply-label\">{{ reply.label }}</span>\n <span\n *ngIf=\"getQuickReplyDescription(reply)\"\n class=\"praxis-ai-assistant-shell__quick-reply-description\"\n >\n {{ getQuickReplyDescription(reply) }}\n </span>\n </span>\n <span class=\"praxis-ai-assistant-shell__quick-reply-actions\">\n <span class=\"praxis-ai-assistant-shell__quick-reply-badge\">\n {{ getQuickReplyCategoryLabel(reply) }}\n </span>\n <mat-icon\n *ngIf=\"getQuickReplyTechnicalDetails(reply)\"\n class=\"praxis-ai-assistant-shell__quick-reply-details\"\n [matTooltip]=\"getQuickReplyTechnicalDetails(reply)\"\n [attr.aria-label]=\"resolvedLabels.quickReplyDetails\"\n >\n info\n </mat-icon>\n </span>\n </span>\n <span\n *ngIf=\"getQuickReplyContextChips(reply).length\"\n class=\"praxis-ai-assistant-shell__quick-reply-context\"\n >\n <span\n *ngFor=\"let chip of getQuickReplyContextChips(reply)\"\n class=\"praxis-ai-assistant-shell__quick-reply-context-chip\"\n [attr.aria-label]=\"chip.ariaLabel\"\n >\n <mat-icon aria-hidden=\"true\">{{ chip.icon }}</mat-icon>\n <span>{{ chip.value }}</span>\n </span>\n </span>\n <span\n *ngIf=\"getQuickReplyPresentationItems(reply).length\"\n class=\"praxis-ai-assistant-shell__quick-reply-insights\"\n >\n <span\n *ngFor=\"let item of getQuickReplyPresentationItems(reply)\"\n class=\"praxis-ai-assistant-shell__quick-reply-insight\"\n >\n <mat-icon aria-hidden=\"true\">{{ item.icon }}</mat-icon>\n <span class=\"praxis-ai-assistant-shell__quick-reply-insight-copy\">\n <span class=\"praxis-ai-assistant-shell__quick-reply-insight-label\">\n {{ item.label }}\n </span>\n <span class=\"praxis-ai-assistant-shell__quick-reply-insight-value\">\n {{ item.value }}\n </span>\n </span>\n </span>\n </span>\n <span class=\"praxis-ai-assistant-shell__quick-reply-cta\">\n {{ getQuickReplyCtaLabel(reply) }}\n <mat-icon aria-hidden=\"true\">arrow_forward</mat-icon>\n </span>\n </span>\n </button>\n </div>\n <p\n *ngIf=\"shouldShowStatusText()\"\n class=\"praxis-ai-assistant-shell__status\"\n [attr.data-testid]=\"testIdPrefix + '-status'\"\n >\n {{ statusText }}\n </p>\n <p\n *ngIf=\"shouldShowErrorText()\"\n class=\"praxis-ai-assistant-shell__error\"\n [attr.data-testid]=\"testIdPrefix + '-error'\"\n >\n {{ errorText }}\n </p>\n </div>\n\n <footer class=\"praxis-ai-assistant-shell__footer\">\n <label class=\"praxis-ai-assistant-shell__label\" for=\"praxis-ai-assistant-shell-prompt\">\n {{ resolvedLabels.prompt }}\n </label>\n <div class=\"praxis-ai-assistant-shell__composer\">\n <textarea\n id=\"praxis-ai-assistant-shell-prompt\"\n class=\"praxis-ai-assistant-shell__prompt\"\n [attr.data-testid]=\"testIdPrefix + '-prompt'\"\n [placeholder]=\"resolvedLabels.promptPlaceholder\"\n [ngModel]=\"currentPrompt\"\n [disabled]=\"busy\"\n (ngModelChange)=\"onPromptInput($event)\"\n (keydown)=\"onPromptKeydown($event)\"\n (paste)=\"onPromptPaste($event)\"\n ></textarea>\n <div class=\"praxis-ai-assistant-shell__composer-actions\">\n <ng-container *ngIf=\"showAttachAction\">\n <input\n #attachmentInput\n type=\"file\"\n hidden\n [attr.accept]=\"attachmentAccept || null\"\n [attr.multiple]=\"attachmentMultiple ? '' : null\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-input'\"\n (change)=\"onAttachmentFilesSelected($event)\"\n >\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"busy\"\n (click)=\"onAttachClick(attachmentInput)\"\n [attr.data-testid]=\"testIdPrefix + '-attach'\"\n >\n <mat-icon>attach_file</mat-icon>\n {{ resolvedLabels.attach }}\n </button>\n </ng-container>\n <button\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n class=\"praxis-ai-assistant-shell__action praxis-ai-assistant-shell__action--primary\"\n [class.praxis-ai-assistant-shell__action--icon-only]=\"getPrimaryAction().iconOnly\"\n [matTooltip]=\"getPrimaryActionTooltip(getPrimaryAction())\"\n [ngClass]=\"'praxis-ai-assistant-shell__action--tone-' + getShellActionTone(getPrimaryAction())\"\n [disabled]=\"isShellActionDisabled(getPrimaryAction())\"\n (click)=\"onShellAction(getPrimaryAction())\"\n [attr.data-testid]=\"getPrimaryAction().testId || (submitTestId || (testIdPrefix + '-submit'))\"\n [attr.aria-label]=\"getPrimaryAction().ariaLabel || getPrimaryAction().label\"\n >\n <mat-icon *ngIf=\"getPrimaryAction().icon\" aria-hidden=\"true\">{{ getPrimaryAction().icon }}</mat-icon>\n <span *ngIf=\"!getPrimaryAction().iconOnly\">{{ getPrimaryAction().label }}</span>\n </button>\n <button\n *ngFor=\"let action of getSecondaryActions(); trackBy: trackShellAction\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__action praxis-ai-assistant-shell__action--secondary\"\n [ngClass]=\"'praxis-ai-assistant-shell__action--tone-' + getShellActionTone(action)\"\n [disabled]=\"isShellActionDisabled(action)\"\n (click)=\"onShellAction(action)\"\n [attr.data-testid]=\"action.testId || (testIdPrefix + '-action-' + action.id)\"\n [attr.aria-label]=\"action.ariaLabel || action.label\"\n >\n <mat-icon *ngIf=\"action.icon\" aria-hidden=\"true\">{{ action.icon }}</mat-icon>\n {{ action.label }}\n </button>\n <mat-spinner *ngIf=\"busy\" diameter=\"20\" [attr.data-testid]=\"testIdPrefix + '-spinner'\"></mat-spinner>\n </div>\n </div>\n </footer>\n\n <ng-container *ngIf=\"resizable\">\n <span\n *ngFor=\"let direction of resizeHandles; trackBy: trackResizeHandle\"\n class=\"praxis-ai-assistant-shell__resize-handle praxis-ai-assistant-shell__resize-handle--{{ direction }}\"\n [attr.data-testid]=\"direction === 'se' ? testIdPrefix + '-resize-handle' : testIdPrefix + '-resize-handle-' + direction\"\n aria-hidden=\"true\"\n role=\"presentation\"\n (pointerdown)=\"startResize(direction, $event)\"\n ></span>\n </ng-container>\n</section>\n", styles: [":host{display:block}.praxis-ai-assistant-shell{--praxis-ai-assistant-shell-shadow-color: var(--md-sys-color-shadow);--praxis-ai-assistant-shell-highlight-color: var(--md-sys-color-on-surface);--praxis-ai-assistant-shell-tone-analytics: var(--md-sys-color-primary);--praxis-ai-assistant-shell-tone-resource: var(--md-sys-color-tertiary);--praxis-ai-assistant-shell-tone-success: var(--md-sys-color-tertiary);--praxis-ai-assistant-shell-tone-warning: var(--md-sys-color-secondary);--praxis-ai-assistant-shell-tone-danger: var(--md-sys-color-error);--praxis-ai-assistant-shell-tone-neutral: var(--md-sys-color-outline);position:fixed;box-sizing:border-box;min-width:360px;min-height:360px;display:flex;flex-direction:column;overflow:hidden;color:var(--md-sys-color-on-surface);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface);box-shadow:0 24px 60px color-mix(in srgb,var(--praxis-ai-assistant-shell-shadow-color) 40%,transparent);z-index:var(--praxis-ai-assistant-shell-z-index, 1200)}.praxis-ai-assistant-shell__header{flex:0 0 auto;display:flex;align-items:flex-start;gap:9px;padding:10px 10px 9px;border-color:color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent);background:var(--md-sys-color-surface-container)}.praxis-ai-assistant-shell__header{justify-content:flex-start;border-bottom:1px solid;cursor:move;touch-action:none}.praxis-ai-assistant-shell__identity{flex:0 0 auto;display:grid;place-items:center;width:30px;height:30px;border-radius:8px;background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);box-shadow:0 6px 16px color-mix(in srgb,var(--md-sys-color-primary) 22%,transparent)}.praxis-ai-assistant-shell__identity mat-icon{width:18px;height:18px;font-size:18px}.praxis-ai-assistant-shell__title-group{min-width:0;flex:1 1 auto;display:grid;gap:4px}.praxis-ai-assistant-shell__title-row{min-width:0;display:flex;align-items:center;gap:8px}.praxis-ai-assistant-shell__badges{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:4px;min-width:0;flex-wrap:wrap}.praxis-ai-assistant-shell__badge{display:inline-flex;align-items:center;gap:5px;min-height:20px;max-width:120px;padding:2px 7px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 80%,transparent);border-radius:8px;color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);font-size:11px;line-height:1.2;overflow-wrap:anywhere}.praxis-ai-assistant-shell__badge--context{color:var(--md-sys-color-on-surface);background:color-mix(in srgb,var(--md-sys-color-primary) 9%,var(--md-sys-color-surface-container-high))}.praxis-ai-assistant-shell__badge--state{border-color:transparent;background:transparent;color:var(--md-sys-color-on-surface-variant);padding-inline:3px}.praxis-ai-assistant-shell__badge--error{color:var(--md-sys-color-error);border-color:color-mix(in srgb,var(--md-sys-color-error) 60%,transparent)}.praxis-ai-assistant-shell__state-dot{flex:0 0 auto;width:6px;height:6px;border-radius:999px;background:var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.praxis-ai-assistant-shell__badge--error .praxis-ai-assistant-shell__state-dot{background:var(--md-sys-color-error);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-error) 16%,transparent)}.praxis-ai-assistant-shell__title-group strong,.praxis-ai-assistant-shell__title-group p{min-width:0;margin:0;overflow-wrap:anywhere}.praxis-ai-assistant-shell__title-group strong{flex:1 1 auto;font-size:13px;line-height:1.2}.praxis-ai-assistant-shell__title-group p{color:var(--md-sys-color-on-surface-variant);font-size:11.5px;line-height:1.3}.praxis-ai-assistant-shell__header-actions{flex:0 0 auto;display:flex;align-items:center;gap:2px;margin-top:-3px}.praxis-ai-assistant-shell__header-actions button{width:34px;height:34px;color:var(--md-sys-color-on-surface-variant);opacity:.78}.praxis-ai-assistant-shell__header-actions button:hover,.praxis-ai-assistant-shell__header-actions button:focus-visible{opacity:1}.praxis-ai-assistant-shell__header-actions mat-icon{width:18px;height:18px;font-size:18px}.praxis-ai-assistant-shell__body{min-height:0;flex:1 1 auto;display:flex;flex-direction:column;gap:10px;padding:12px 12px 10px;overflow:auto;background:linear-gradient(180deg,var(--md-sys-color-surface-container-low),var(--md-sys-color-surface))}.praxis-ai-assistant-shell__context,.praxis-ai-assistant-shell__attachments{flex:0 0 auto;display:flex;align-items:center;gap:8px;overflow-x:auto}.praxis-ai-assistant-shell__context-item{flex:0 0 auto;display:inline-flex;align-items:center;gap:5px;max-width:240px;padding:5px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface-container-high);font-size:12px;line-height:1.25}.praxis-ai-assistant-shell__context-item mat-icon{width:16px;height:16px;font-size:16px}.praxis-ai-assistant-shell__context-label,.praxis-ai-assistant-shell__context-value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-shell__context-label{color:var(--md-sys-color-on-surface)}.praxis-ai-assistant-shell__context-value{color:var(--md-sys-color-on-surface-variant)}.praxis-ai-assistant-shell__label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.praxis-ai-assistant-shell__prompt{box-sizing:border-box;width:100%;min-height:46px;max-height:96px;resize:none;border:0;padding:10px 12px 8px;color:var(--md-sys-color-on-surface);background:transparent;font:inherit;line-height:1.45;outline:none}.praxis-ai-assistant-shell__conversation{min-height:0;flex:1 1 auto;display:flex;flex-direction:column;gap:8px;overflow:auto;padding:2px}.praxis-ai-assistant-shell__message{max-width:86%;align-self:flex-start;padding:9px 11px;border-radius:8px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface);font-size:13px;line-height:1.35;overflow-wrap:anywhere}.praxis-ai-assistant-shell__message-content{white-space:normal}.praxis-ai-assistant-shell__message-content :where(p,ul,h3,h4,h5){margin:0}.praxis-ai-assistant-shell__message-content :where(p,ul,h3,h4,h5)+:where(p,ul,h3,h4,h5){margin-top:8px}.praxis-ai-assistant-shell__message-content ul{padding-left:18px}.praxis-ai-assistant-shell__message-content li+li{margin-top:4px}.praxis-ai-assistant-shell__message-content code{padding:1px 4px;border-radius:4px;background:color-mix(in srgb,var(--md-sys-color-on-surface) 10%,transparent);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.94em}.praxis-ai-assistant-shell__message-actions{display:flex;align-items:center;gap:4px;flex-wrap:wrap;margin-top:8px}.praxis-ai-assistant-shell__message-action{min-height:28px;padding:0 8px;border-radius:8px;font-size:12px}.praxis-ai-assistant-shell__message-action--icon{width:30px;min-width:30px;height:30px;padding:0;color:var(--md-sys-color-on-surface-variant)}.praxis-ai-assistant-shell__message-action--icon mat-icon{width:17px;height:17px;font-size:17px}.praxis-ai-assistant-shell__message--assistant{border-bottom-left-radius:2px}.praxis-ai-assistant-shell__message--user{align-self:flex-end;border-bottom-right-radius:2px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.praxis-ai-assistant-shell__message--status{color:var(--md-sys-color-on-surface-variant)}.praxis-ai-assistant-shell__message--error,.praxis-ai-assistant-shell__error{color:var(--md-sys-color-error)}.praxis-ai-assistant-shell__quick-replies{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,520px),1fr));gap:8px;align-items:stretch;padding-bottom:4px}.praxis-ai-assistant-shell__attachment{flex:0 0 auto;display:inline-flex;align-items:center;gap:7px;max-width:260px;min-height:34px;padding:4px 4px 4px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface-container-high)}.praxis-ai-assistant-shell__attachment--error{border-color:color-mix(in srgb,var(--md-sys-color-error) 60%,transparent)}.praxis-ai-assistant-shell__attachment-preview{flex:0 0 auto;width:28px;height:28px;border-radius:6px;object-fit:cover}.praxis-ai-assistant-shell__attachment-name,.praxis-ai-assistant-shell__attachment-kind{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.praxis-ai-assistant-shell__attachment-name{color:var(--md-sys-color-on-surface)}.praxis-ai-assistant-shell__attachment-kind{color:var(--md-sys-color-on-surface-variant)}.praxis-ai-assistant-shell__quick-reply{--praxis-ai-assistant-shell-quick-reply-accent: var(--md-sys-color-primary);--praxis-ai-assistant-shell-quick-reply-background: color-mix( in srgb, var(--praxis-ai-assistant-shell-quick-reply-accent) 7%, var(--md-sys-color-surface-container-high) );--praxis-ai-assistant-shell-quick-reply-foreground: var(--md-sys-color-on-surface);width:100%;max-width:100%;height:auto;min-height:0;position:relative;overflow:hidden;padding:15px 16px;align-items:stretch;border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 36%,transparent);border-radius:22px;color:var(--praxis-ai-assistant-shell-quick-reply-foreground);background:linear-gradient(135deg,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 16%,transparent),transparent 46%),radial-gradient(circle at 92% 10%,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 22%,transparent),transparent 32%),var(--praxis-ai-assistant-shell-quick-reply-background);box-shadow:0 18px 42px color-mix(in srgb,var(--praxis-ai-assistant-shell-shadow-color) 22%,transparent),inset 0 1px color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 16%,transparent);letter-spacing:normal;white-space:normal;text-align:left;text-transform:none;-webkit-user-select:none;user-select:none;transition:border-color .16s ease,box-shadow .16s ease,transform .16s ease,background .16s ease;--mdc-outlined-button-container-height: auto;--mat-outlined-button-horizontal-padding: 0}.praxis-ai-assistant-shell__quick-reply:hover:not(:disabled),.praxis-ai-assistant-shell__quick-reply:focus-visible{border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 74%,transparent);box-shadow:0 18px 42px color-mix(in srgb,var(--praxis-ai-assistant-shell-shadow-color) 24%,transparent),0 0 0 3px color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 18%,transparent),inset 0 1px color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 18%,transparent);transform:translateY(-1px)}.praxis-ai-assistant-shell__quick-reply--contextual-action{padding:10px 12px;border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 34%,transparent);border-radius:8px;background:linear-gradient(90deg,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 15%,transparent),color-mix(in srgb,var(--md-sys-color-surface-container-high) 92%,transparent)),var(--md-sys-color-surface-container-high);box-shadow:none}.praxis-ai-assistant-shell__quick-reply--guided-action{padding:10px 12px;border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 30%,transparent);border-radius:8px;background:linear-gradient(90deg,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 10%,transparent),color-mix(in srgb,var(--md-sys-color-surface-container-high) 96%,transparent)),var(--md-sys-color-surface-container-high);box-shadow:none}.praxis-ai-assistant-shell__quick-reply--contextual-action:hover:not(:disabled),.praxis-ai-assistant-shell__quick-reply--contextual-action:focus-visible,.praxis-ai-assistant-shell__quick-reply--guided-action:hover:not(:disabled),.praxis-ai-assistant-shell__quick-reply--guided-action:focus-visible{border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 62%,transparent);box-shadow:0 0 0 2px color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 15%,transparent);transform:none}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-ambient,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-ambient{display:none}.praxis-ai-assistant-shell__quick-reply--contextual-action ::ng-deep .mdc-button__label,.praxis-ai-assistant-shell__quick-reply--guided-action ::ng-deep .mdc-button__label{gap:10px}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-icon-frame,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-icon-frame{width:34px;height:34px;border-radius:8px;box-shadow:none}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-icon,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-icon{width:19px;height:19px;font-size:19px}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-copy,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-copy{gap:6px}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-label,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-label{font-size:13.5px;font-weight:760;letter-spacing:0}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-description,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-description{display:-webkit-box;overflow:hidden;font-size:12px;line-height:1.34;-webkit-box-orient:vertical;-webkit-line-clamp:2}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-badge,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-badge{border-radius:8px;font-size:10px;letter-spacing:0}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-context-chip{border-radius:8px;padding:4px 7px;font-size:10.5px;font-weight:650}.praxis-ai-assistant-shell__quick-reply--contextual-action .praxis-ai-assistant-shell__quick-reply-cta,.praxis-ai-assistant-shell__quick-reply--guided-action .praxis-ai-assistant-shell__quick-reply-cta{font-size:11.5px}.praxis-ai-assistant-shell__quick-reply--compact{justify-self:start;width:fit-content;min-width:min(100%,210px);max-width:min(100%,320px);padding:9px 11px;border-radius:16px;background:linear-gradient(135deg,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 10%,transparent),transparent 55%),var(--md-sys-color-surface-container-high);box-shadow:0 8px 18px color-mix(in srgb,var(--praxis-ai-assistant-shell-shadow-color) 16%,transparent),inset 0 1px color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 10%,transparent)}.praxis-ai-assistant-shell__quick-reply.praxis-ai-assistant-shell__quick-reply--compact ::ng-deep .mdc-button__label{align-items:center;gap:10px}.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-icon-frame{width:34px;height:34px;border-radius:12px}.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-icon{width:19px;height:19px;font-size:19px}.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-copy{gap:4px}.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-header{align-items:center}.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-label{font-size:13px;line-height:1.2}.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-badge,.praxis-ai-assistant-shell__quick-reply--compact .praxis-ai-assistant-shell__quick-reply-cta{display:none}.praxis-ai-assistant-shell__quick-reply ::ng-deep .mdc-button__label{position:relative;z-index:1;min-width:0;display:grid;grid-template-columns:auto minmax(0,1fr);align-items:flex-start;gap:14px;width:100%;height:auto;line-height:normal}.praxis-ai-assistant-shell__quick-reply-ambient{position:absolute;inset:0;pointer-events:none}.praxis-ai-assistant-shell__quick-reply-ambient:before{content:\"\";position:absolute;inset:0 18px auto;height:1px;border-radius:999px;background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 90%,var(--praxis-ai-assistant-shell-highlight-color)),transparent);opacity:.58}.praxis-ai-assistant-shell__quick-reply-ambient:after{content:\"\";position:absolute;top:-34px;right:-44px;width:150px;height:150px;border-radius:999px;background:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 13%,transparent);filter:blur(18px)}.praxis-ai-assistant-shell__quick-reply ::ng-deep .mat-mdc-button-touch-target{height:100%;min-height:48px}.praxis-ai-assistant-shell__quick-reply-icon-frame{flex:0 0 auto;width:46px;height:46px;display:inline-grid;place-items:center;border:1px solid color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 34%,transparent);border-radius:17px;color:var(--praxis-ai-assistant-shell-quick-reply-accent);background:linear-gradient(145deg,color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 24%,transparent),color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 6%,transparent));box-shadow:inset 0 1px color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 18%,transparent),0 10px 24px color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 12%,transparent)}.praxis-ai-assistant-shell__quick-reply-icon{width:24px;height:24px;font-size:24px}.praxis-ai-assistant-shell__quick-reply-details{flex:0 0 auto;width:24px;height:24px;display:inline-grid;place-items:center;border:1px solid color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 14%,transparent);border-radius:50%;color:var(--md-sys-color-on-surface-variant);background:color-mix(in srgb,var(--md-sys-color-outline) 14%,transparent);font-size:17px;opacity:.9}.praxis-ai-assistant-shell__quick-reply-copy{min-width:0;display:grid;gap:10px;flex:1 1 auto}.praxis-ai-assistant-shell__quick-reply-header{min-width:0;display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.praxis-ai-assistant-shell__quick-reply-heading{min-width:0;display:grid;gap:5px}.praxis-ai-assistant-shell__quick-reply-actions{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:flex-end;gap:7px;max-width:46%}.praxis-ai-assistant-shell__quick-reply-label,.praxis-ai-assistant-shell__quick-reply-description{min-width:0;overflow-wrap:anywhere}.praxis-ai-assistant-shell__quick-reply-label{color:var(--md-sys-color-on-surface);font-size:17px;font-weight:760;letter-spacing:.005em;line-height:1.18;text-transform:none}.praxis-ai-assistant-shell__quick-reply-badge{flex:0 0 auto;max-width:100%;padding:4px 8px;border:1px solid color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 28%,transparent);border-radius:999px;color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 86%,var(--praxis-ai-assistant-shell-highlight-color));background:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 11%,transparent);font-size:10.5px;font-weight:700;letter-spacing:.02em;line-height:1.1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-shell__quick-reply-description{color:color-mix(in srgb,var(--md-sys-color-on-surface) 80%,transparent);font-size:13px;line-height:1.46}.praxis-ai-assistant-shell__quick-reply-context{display:flex;flex-wrap:wrap;gap:7px;align-items:center}.praxis-ai-assistant-shell__quick-reply-context-chip{min-width:0;display:inline-flex;align-items:center;gap:4px;max-width:100%;padding:5px 8px;border:1px solid color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 22%,transparent);border-radius:999px;color:color-mix(in srgb,var(--md-sys-color-on-surface) 82%,transparent);background:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 9%,transparent);font-size:11px;font-weight:650;line-height:1.15}.praxis-ai-assistant-shell__quick-reply-context-chip mat-icon{width:14px;height:14px;color:var(--praxis-ai-assistant-shell-quick-reply-accent);font-size:14px}.praxis-ai-assistant-shell__quick-reply-context-chip span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-shell__quick-reply-insights{display:grid;grid-template-columns:repeat(auto-fit,minmax(145px,1fr));gap:8px;padding:8px;border:1px solid color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 16%,transparent);border-radius:16px;background:linear-gradient(180deg,color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 5%,transparent),color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 26%,transparent))}.praxis-ai-assistant-shell__quick-reply-insight{min-width:0;display:grid;grid-template-columns:20px minmax(0,1fr);gap:8px;align-items:start;padding:8px;border:1px solid color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 10%,transparent);border-radius:13px;color:color-mix(in srgb,var(--md-sys-color-on-surface) 86%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 44%,transparent);font-size:12px;line-height:1.35}.praxis-ai-assistant-shell__quick-reply-insight mat-icon{width:17px;height:17px;color:var(--praxis-ai-assistant-shell-quick-reply-accent);font-size:17px;opacity:.9}.praxis-ai-assistant-shell__quick-reply-insight-copy{min-width:0;display:grid;gap:1px}.praxis-ai-assistant-shell__quick-reply-insight-label{color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 82%,var(--praxis-ai-assistant-shell-highlight-color));font-size:10.5px;font-weight:760;letter-spacing:.045em;line-height:1.2;text-transform:uppercase}.praxis-ai-assistant-shell__quick-reply-insight-value{min-width:0;overflow-wrap:anywhere}.praxis-ai-assistant-shell__quick-reply-cta{display:inline-flex;align-items:center;justify-self:start;gap:5px;padding:3px 0;color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 84%,var(--praxis-ai-assistant-shell-highlight-color));font-size:12px;font-weight:760;letter-spacing:.01em;line-height:1.2}.praxis-ai-assistant-shell__quick-reply-cta mat-icon{width:15px;height:15px;font-size:15px;transition:transform .16s ease}.praxis-ai-assistant-shell__quick-reply:hover:not(:disabled) .praxis-ai-assistant-shell__quick-reply-cta mat-icon,.praxis-ai-assistant-shell__quick-reply:focus-visible .praxis-ai-assistant-shell__quick-reply-cta mat-icon{transform:translate(2px)}.praxis-ai-assistant-shell__quick-reply--tone-analytics{--praxis-ai-assistant-shell-quick-reply-accent: var(--praxis-ai-assistant-shell-tone-analytics)}.praxis-ai-assistant-shell__quick-reply--tone-resource{--praxis-ai-assistant-shell-quick-reply-accent: var(--praxis-ai-assistant-shell-tone-resource)}.praxis-ai-assistant-shell__quick-reply--tone-warning{--praxis-ai-assistant-shell-quick-reply-accent: var(--praxis-ai-assistant-shell-tone-warning)}.praxis-ai-assistant-shell__quick-reply--tone-success{--praxis-ai-assistant-shell-quick-reply-accent: var(--praxis-ai-assistant-shell-tone-success)}.praxis-ai-assistant-shell__quick-reply--tone-danger{--praxis-ai-assistant-shell-quick-reply-accent: var(--praxis-ai-assistant-shell-tone-danger)}.praxis-ai-assistant-shell__quick-reply--tone-neutral{--praxis-ai-assistant-shell-quick-reply-accent: var(--praxis-ai-assistant-shell-tone-neutral)}@media(max-width:640px){.praxis-ai-assistant-shell__quick-reply{padding:13px;border-radius:19px}.praxis-ai-assistant-shell__quick-reply ::ng-deep .mdc-button__label{grid-template-columns:minmax(0,1fr);gap:10px}.praxis-ai-assistant-shell__quick-reply-icon-frame{width:38px;height:38px;border-radius:14px}.praxis-ai-assistant-shell__quick-reply-header{display:grid}.praxis-ai-assistant-shell__quick-reply-actions{justify-content:flex-start;max-width:100%}.praxis-ai-assistant-shell__quick-reply-insights{grid-template-columns:minmax(0,1fr)}}.praxis-ai-assistant-shell__status,.praxis-ai-assistant-shell__error{margin:0;font-size:13px;line-height:1.35;overflow-wrap:anywhere}.praxis-ai-assistant-shell__footer{flex:0 0 auto;padding:8px 10px 10px;border-top:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent);background:var(--md-sys-color-surface-container-low)}.praxis-ai-assistant-shell__composer{display:grid;gap:8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container-lowest);box-shadow:inset 0 1px color-mix(in srgb,var(--praxis-ai-assistant-shell-highlight-color) 4%,transparent)}.praxis-ai-assistant-shell__composer:focus-within{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.praxis-ai-assistant-shell__composer-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:0 8px 7px;flex-wrap:wrap}.praxis-ai-assistant-shell__action{min-height:36px;border-radius:10px;font-weight:650}.praxis-ai-assistant-shell__action mat-icon{width:18px;height:18px;margin-right:6px;font-size:18px}.praxis-ai-assistant-shell__action--icon-only{width:40px;min-width:40px;height:40px;padding-inline:0;border-radius:50%}.praxis-ai-assistant-shell__action--icon-only mat-icon{margin-right:0}.praxis-ai-assistant-shell__action--secondary{color:var(--md-sys-color-on-surface-variant)}.praxis-ai-assistant-shell__action--tone-governance{color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent)}.praxis-ai-assistant-shell__action--tone-success{color:var(--praxis-ai-assistant-shell-tone-success);background:color-mix(in srgb,var(--praxis-ai-assistant-shell-tone-success) 10%,transparent)}.praxis-ai-assistant-shell__action--tone-warning{color:var(--praxis-ai-assistant-shell-tone-warning);background:color-mix(in srgb,var(--praxis-ai-assistant-shell-tone-warning) 10%,transparent)}.praxis-ai-assistant-shell__action--tone-danger{color:var(--md-sys-color-error);background:color-mix(in srgb,var(--md-sys-color-error) 10%,transparent)}.praxis-ai-assistant-shell__resize-handle{position:absolute;z-index:2;border:0;background:transparent;touch-action:none}.praxis-ai-assistant-shell__resize-handle--n,.praxis-ai-assistant-shell__resize-handle--s{left:16px;right:16px;height:10px;cursor:ns-resize}.praxis-ai-assistant-shell__resize-handle--n{top:-5px}.praxis-ai-assistant-shell__resize-handle--s{bottom:-5px}.praxis-ai-assistant-shell__resize-handle--e,.praxis-ai-assistant-shell__resize-handle--w{top:16px;bottom:16px;width:10px;cursor:ew-resize}.praxis-ai-assistant-shell__resize-handle--e{right:-5px}.praxis-ai-assistant-shell__resize-handle--w{left:-5px}.praxis-ai-assistant-shell__resize-handle--ne,.praxis-ai-assistant-shell__resize-handle--nw,.praxis-ai-assistant-shell__resize-handle--se,.praxis-ai-assistant-shell__resize-handle--sw{width:22px;height:22px}.praxis-ai-assistant-shell__resize-handle--ne{top:-5px;right:-5px;cursor:nesw-resize}.praxis-ai-assistant-shell__resize-handle--nw{top:-5px;left:-5px;cursor:nwse-resize}.praxis-ai-assistant-shell__resize-handle--se{right:-5px;bottom:-5px;cursor:nwse-resize}.praxis-ai-assistant-shell__resize-handle--sw{bottom:-5px;left:-5px;cursor:nesw-resize}.praxis-ai-assistant-shell__resize-handle--se:after{content:\"\";position:absolute;right:8px;bottom:8px;width:10px;height:10px;border-right:2px solid var(--md-sys-color-outline);border-bottom:2px solid var(--md-sys-color-outline)}\n"] }]
|
|
6283
7835
|
}], propDecorators: { labels: [{
|
|
6284
7836
|
type: Input
|
|
6285
7837
|
}], mode: [{
|
|
@@ -6308,6 +7860,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
6308
7860
|
type: Input
|
|
6309
7861
|
}], applyTestId: [{
|
|
6310
7862
|
type: Input
|
|
7863
|
+
}], primaryAction: [{
|
|
7864
|
+
type: Input
|
|
7865
|
+
}], secondaryActions: [{
|
|
7866
|
+
type: Input
|
|
7867
|
+
}], governanceActions: [{
|
|
7868
|
+
type: Input
|
|
6311
7869
|
}], busy: [{
|
|
6312
7870
|
type: Input
|
|
6313
7871
|
}], canSubmit: [{
|
|
@@ -6316,6 +7874,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
6316
7874
|
type: Input
|
|
6317
7875
|
}], submitOnEnter: [{
|
|
6318
7876
|
type: Input
|
|
7877
|
+
}], showAttachAction: [{
|
|
7878
|
+
type: Input
|
|
7879
|
+
}], enablePastedAttachments: [{
|
|
7880
|
+
type: Input
|
|
6319
7881
|
}], enableFileAttachments: [{
|
|
6320
7882
|
type: Input
|
|
6321
7883
|
}], attachmentAccept: [{
|
|
@@ -6340,6 +7902,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
6340
7902
|
type: Output
|
|
6341
7903
|
}], apply: [{
|
|
6342
7904
|
type: Output
|
|
7905
|
+
}], retryTurn: [{
|
|
7906
|
+
type: Output
|
|
7907
|
+
}], cancelTurn: [{
|
|
7908
|
+
type: Output
|
|
7909
|
+
}], shellAction: [{
|
|
7910
|
+
type: Output
|
|
6343
7911
|
}], close: [{
|
|
6344
7912
|
type: Output
|
|
6345
7913
|
}], attach: [{
|
|
@@ -6368,6 +7936,302 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
6368
7936
|
args: ['conversation']
|
|
6369
7937
|
}] } });
|
|
6370
7938
|
|
|
7939
|
+
function createPraxisAssistantViewportLayout(options = {}) {
|
|
7940
|
+
const top = normalizePositiveNumber(options.top, 88);
|
|
7941
|
+
const margin = normalizePositiveNumber(options.margin, 32);
|
|
7942
|
+
const viewportWidth = resolveViewportDimension('width', 1280);
|
|
7943
|
+
const viewportHeight = resolveViewportDimension('height', 900);
|
|
7944
|
+
const width = clampPositiveNumber(normalizePositiveNumber(options.width, 560), 360, Math.max(360, viewportWidth - margin * 2));
|
|
7945
|
+
const height = clampPositiveNumber(normalizePositiveNumber(options.height, 620), 360, Math.max(360, viewportHeight - top - margin));
|
|
7946
|
+
return {
|
|
7947
|
+
left: Math.max(margin, viewportWidth - width - margin),
|
|
7948
|
+
top,
|
|
7949
|
+
width,
|
|
7950
|
+
height,
|
|
7951
|
+
};
|
|
7952
|
+
}
|
|
7953
|
+
function normalizePositiveNumber(value, fallback) {
|
|
7954
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
7955
|
+
}
|
|
7956
|
+
function clampPositiveNumber(value, min, max) {
|
|
7957
|
+
return Math.min(Math.max(value, min), Math.max(min, max));
|
|
7958
|
+
}
|
|
7959
|
+
function resolveViewportDimension(axis, fallback) {
|
|
7960
|
+
if (typeof document !== 'undefined') {
|
|
7961
|
+
const documentElementValue = axis === 'width'
|
|
7962
|
+
? document.documentElement?.clientWidth
|
|
7963
|
+
: document.documentElement?.clientHeight;
|
|
7964
|
+
if (typeof documentElementValue === 'number' && documentElementValue > 0) {
|
|
7965
|
+
return documentElementValue;
|
|
7966
|
+
}
|
|
7967
|
+
}
|
|
7968
|
+
if (typeof window !== 'undefined') {
|
|
7969
|
+
const windowValue = axis === 'width' ? window.innerWidth : window.innerHeight;
|
|
7970
|
+
if (typeof windowValue === 'number' && windowValue > 0) {
|
|
7971
|
+
return windowValue;
|
|
7972
|
+
}
|
|
7973
|
+
}
|
|
7974
|
+
return fallback;
|
|
7975
|
+
}
|
|
7976
|
+
|
|
7977
|
+
class PraxisAiAssistantDockComponent {
|
|
7978
|
+
title = 'Praxis copilot active';
|
|
7979
|
+
summary = 'Conversation preserved. Continue where you stopped.';
|
|
7980
|
+
badge = '';
|
|
7981
|
+
icon = '';
|
|
7982
|
+
state = null;
|
|
7983
|
+
tone = null;
|
|
7984
|
+
ariaLabel = '';
|
|
7985
|
+
openAriaLabel = '';
|
|
7986
|
+
openTooltip = '';
|
|
7987
|
+
testId = 'praxis-ai-assistant-dock';
|
|
7988
|
+
openTestId = 'praxis-ai-assistant-dock-open';
|
|
7989
|
+
open = new EventEmitter();
|
|
7990
|
+
resolvedTone() {
|
|
7991
|
+
if (this.tone)
|
|
7992
|
+
return this.tone;
|
|
7993
|
+
if (this.state === 'error')
|
|
7994
|
+
return 'error';
|
|
7995
|
+
if (this.state === 'processing' || this.state === 'applying')
|
|
7996
|
+
return 'working';
|
|
7997
|
+
if (this.state === 'review')
|
|
7998
|
+
return 'review';
|
|
7999
|
+
if (this.state === 'clarification')
|
|
8000
|
+
return 'governed';
|
|
8001
|
+
return 'ready';
|
|
8002
|
+
}
|
|
8003
|
+
resolvedIcon() {
|
|
8004
|
+
if (this.icon)
|
|
8005
|
+
return this.icon;
|
|
8006
|
+
const tone = this.resolvedTone();
|
|
8007
|
+
if (tone === 'error')
|
|
8008
|
+
return 'error';
|
|
8009
|
+
if (tone === 'working')
|
|
8010
|
+
return 'sync';
|
|
8011
|
+
if (tone === 'review')
|
|
8012
|
+
return 'rate_review';
|
|
8013
|
+
if (tone === 'governed')
|
|
8014
|
+
return 'rule';
|
|
8015
|
+
return 'auto_awesome';
|
|
8016
|
+
}
|
|
8017
|
+
resolvedBadge() {
|
|
8018
|
+
if (this.badge)
|
|
8019
|
+
return this.badge;
|
|
8020
|
+
const tone = this.resolvedTone();
|
|
8021
|
+
if (tone === 'error')
|
|
8022
|
+
return 'Attention';
|
|
8023
|
+
if (tone === 'working')
|
|
8024
|
+
return 'Working';
|
|
8025
|
+
if (tone === 'review')
|
|
8026
|
+
return 'Review';
|
|
8027
|
+
if (tone === 'governed')
|
|
8028
|
+
return 'Governed';
|
|
8029
|
+
return 'Ready';
|
|
8030
|
+
}
|
|
8031
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantDockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8032
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisAiAssistantDockComponent, isStandalone: true, selector: "praxis-ai-assistant-dock", inputs: { title: "title", summary: "summary", badge: "badge", icon: "icon", state: "state", tone: "tone", ariaLabel: "ariaLabel", openAriaLabel: "openAriaLabel", openTooltip: "openTooltip", testId: "testId", openTestId: "openTestId" }, outputs: { open: "open" }, ngImport: i0, template: `
|
|
8033
|
+
<section
|
|
8034
|
+
class="praxis-ai-assistant-dock"
|
|
8035
|
+
[class.praxis-ai-assistant-dock--working]="resolvedTone() === 'working'"
|
|
8036
|
+
[class.praxis-ai-assistant-dock--review]="resolvedTone() === 'review'"
|
|
8037
|
+
[class.praxis-ai-assistant-dock--governed]="resolvedTone() === 'governed'"
|
|
8038
|
+
[class.praxis-ai-assistant-dock--error]="resolvedTone() === 'error'"
|
|
8039
|
+
[attr.data-testid]="testId"
|
|
8040
|
+
role="status"
|
|
8041
|
+
[attr.aria-label]="ariaLabel || title"
|
|
8042
|
+
>
|
|
8043
|
+
<button
|
|
8044
|
+
class="praxis-ai-assistant-dock__main"
|
|
8045
|
+
type="button"
|
|
8046
|
+
[attr.data-testid]="openTestId"
|
|
8047
|
+
[attr.aria-label]="openAriaLabel || title"
|
|
8048
|
+
[matTooltip]="openTooltip || title"
|
|
8049
|
+
(click)="open.emit()"
|
|
8050
|
+
>
|
|
8051
|
+
<span class="praxis-ai-assistant-dock__orb" aria-hidden="true">
|
|
8052
|
+
<mat-icon>{{ resolvedIcon() }}</mat-icon>
|
|
8053
|
+
</span>
|
|
8054
|
+
<span class="praxis-ai-assistant-dock__copy">
|
|
8055
|
+
<strong>{{ title }}</strong>
|
|
8056
|
+
<span>{{ summary }}</span>
|
|
8057
|
+
</span>
|
|
8058
|
+
<span class="praxis-ai-assistant-dock__badge">{{ resolvedBadge() }}</span>
|
|
8059
|
+
</button>
|
|
8060
|
+
</section>
|
|
8061
|
+
`, isInline: true, styles: [":host{display:block;position:var(--praxis-ai-assistant-dock-position, absolute);z-index:var(--praxis-ai-assistant-dock-z-index, 135);right:var(--praxis-ai-assistant-dock-right, 16px);bottom:var(--praxis-ai-assistant-dock-bottom, 88px);width:min(var(--praxis-ai-assistant-dock-width, 560px),calc(100% - 32px));pointer-events:auto}.praxis-ai-assistant-dock__main{appearance:none;width:100%;min-height:76px;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:12px;padding:12px 14px;border:1px solid rgba(56,189,248,.34);border-radius:18px;background:radial-gradient(circle at 8% 18%,rgba(14,165,233,.24),transparent 34%),linear-gradient(135deg,#0f172af0,#1e293beb 48%,#082f49e6);color:#e0f2fe;box-shadow:0 22px 58px #02061757,inset 0 1px #ffffff1f;cursor:pointer;text-align:left}.praxis-ai-assistant-dock__main:hover,.praxis-ai-assistant-dock__main:focus-visible{border-color:#7dd3fc9e;box-shadow:0 26px 72px #0206176b,0 0 0 3px #0ea5e92e;outline:none}.praxis-ai-assistant-dock__orb{width:46px;height:46px;display:inline-grid;place-items:center;border-radius:16px;background:linear-gradient(135deg,#38bdf8,#22c55e);color:#082f49;box-shadow:0 12px 28px #22c55e3d}.praxis-ai-assistant-dock--working .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#38bdf8,#a78bfa)}.praxis-ai-assistant-dock--review .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#facc15,#38bdf8)}.praxis-ai-assistant-dock--governed .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#22c55e,#14b8a6)}.praxis-ai-assistant-dock--error .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#f97316,#ef4444);color:#fff7ed}.praxis-ai-assistant-dock__copy{min-width:0;display:grid;gap:3px}.praxis-ai-assistant-dock__copy strong{color:#f8fafc;font-size:14px;line-height:1.2}.praxis-ai-assistant-dock__copy span{color:#bae6fd;font-size:12px;line-height:1.35;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-dock__badge{max-width:132px;padding:5px 9px;border:1px solid rgba(186,230,253,.24);border-radius:999px;background:#0f172a7a;color:#f0f9ff;font-size:11px;font-weight:800;letter-spacing:.02em;overflow:hidden;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap}@media(max-width:720px){:host{right:var(--praxis-ai-assistant-dock-mobile-right, 12px);bottom:var(--praxis-ai-assistant-dock-mobile-bottom, 84px);width:calc(100% - 24px)}.praxis-ai-assistant-dock__main{grid-template-columns:auto minmax(0,1fr)}.praxis-ai-assistant-dock__badge{grid-column:2;justify-self:start}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i9.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8062
|
+
}
|
|
8063
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantDockComponent, decorators: [{
|
|
8064
|
+
type: Component,
|
|
8065
|
+
args: [{ selector: 'praxis-ai-assistant-dock', standalone: true, imports: [CommonModule, MatIconModule, MatTooltipModule], template: `
|
|
8066
|
+
<section
|
|
8067
|
+
class="praxis-ai-assistant-dock"
|
|
8068
|
+
[class.praxis-ai-assistant-dock--working]="resolvedTone() === 'working'"
|
|
8069
|
+
[class.praxis-ai-assistant-dock--review]="resolvedTone() === 'review'"
|
|
8070
|
+
[class.praxis-ai-assistant-dock--governed]="resolvedTone() === 'governed'"
|
|
8071
|
+
[class.praxis-ai-assistant-dock--error]="resolvedTone() === 'error'"
|
|
8072
|
+
[attr.data-testid]="testId"
|
|
8073
|
+
role="status"
|
|
8074
|
+
[attr.aria-label]="ariaLabel || title"
|
|
8075
|
+
>
|
|
8076
|
+
<button
|
|
8077
|
+
class="praxis-ai-assistant-dock__main"
|
|
8078
|
+
type="button"
|
|
8079
|
+
[attr.data-testid]="openTestId"
|
|
8080
|
+
[attr.aria-label]="openAriaLabel || title"
|
|
8081
|
+
[matTooltip]="openTooltip || title"
|
|
8082
|
+
(click)="open.emit()"
|
|
8083
|
+
>
|
|
8084
|
+
<span class="praxis-ai-assistant-dock__orb" aria-hidden="true">
|
|
8085
|
+
<mat-icon>{{ resolvedIcon() }}</mat-icon>
|
|
8086
|
+
</span>
|
|
8087
|
+
<span class="praxis-ai-assistant-dock__copy">
|
|
8088
|
+
<strong>{{ title }}</strong>
|
|
8089
|
+
<span>{{ summary }}</span>
|
|
8090
|
+
</span>
|
|
8091
|
+
<span class="praxis-ai-assistant-dock__badge">{{ resolvedBadge() }}</span>
|
|
8092
|
+
</button>
|
|
8093
|
+
</section>
|
|
8094
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:var(--praxis-ai-assistant-dock-position, absolute);z-index:var(--praxis-ai-assistant-dock-z-index, 135);right:var(--praxis-ai-assistant-dock-right, 16px);bottom:var(--praxis-ai-assistant-dock-bottom, 88px);width:min(var(--praxis-ai-assistant-dock-width, 560px),calc(100% - 32px));pointer-events:auto}.praxis-ai-assistant-dock__main{appearance:none;width:100%;min-height:76px;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:12px;padding:12px 14px;border:1px solid rgba(56,189,248,.34);border-radius:18px;background:radial-gradient(circle at 8% 18%,rgba(14,165,233,.24),transparent 34%),linear-gradient(135deg,#0f172af0,#1e293beb 48%,#082f49e6);color:#e0f2fe;box-shadow:0 22px 58px #02061757,inset 0 1px #ffffff1f;cursor:pointer;text-align:left}.praxis-ai-assistant-dock__main:hover,.praxis-ai-assistant-dock__main:focus-visible{border-color:#7dd3fc9e;box-shadow:0 26px 72px #0206176b,0 0 0 3px #0ea5e92e;outline:none}.praxis-ai-assistant-dock__orb{width:46px;height:46px;display:inline-grid;place-items:center;border-radius:16px;background:linear-gradient(135deg,#38bdf8,#22c55e);color:#082f49;box-shadow:0 12px 28px #22c55e3d}.praxis-ai-assistant-dock--working .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#38bdf8,#a78bfa)}.praxis-ai-assistant-dock--review .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#facc15,#38bdf8)}.praxis-ai-assistant-dock--governed .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#22c55e,#14b8a6)}.praxis-ai-assistant-dock--error .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#f97316,#ef4444);color:#fff7ed}.praxis-ai-assistant-dock__copy{min-width:0;display:grid;gap:3px}.praxis-ai-assistant-dock__copy strong{color:#f8fafc;font-size:14px;line-height:1.2}.praxis-ai-assistant-dock__copy span{color:#bae6fd;font-size:12px;line-height:1.35;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-dock__badge{max-width:132px;padding:5px 9px;border:1px solid rgba(186,230,253,.24);border-radius:999px;background:#0f172a7a;color:#f0f9ff;font-size:11px;font-weight:800;letter-spacing:.02em;overflow:hidden;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap}@media(max-width:720px){:host{right:var(--praxis-ai-assistant-dock-mobile-right, 12px);bottom:var(--praxis-ai-assistant-dock-mobile-bottom, 84px);width:calc(100% - 24px)}.praxis-ai-assistant-dock__main{grid-template-columns:auto minmax(0,1fr)}.praxis-ai-assistant-dock__badge{grid-column:2;justify-self:start}}\n"] }]
|
|
8095
|
+
}], propDecorators: { title: [{
|
|
8096
|
+
type: Input
|
|
8097
|
+
}], summary: [{
|
|
8098
|
+
type: Input
|
|
8099
|
+
}], badge: [{
|
|
8100
|
+
type: Input
|
|
8101
|
+
}], icon: [{
|
|
8102
|
+
type: Input
|
|
8103
|
+
}], state: [{
|
|
8104
|
+
type: Input
|
|
8105
|
+
}], tone: [{
|
|
8106
|
+
type: Input
|
|
8107
|
+
}], ariaLabel: [{
|
|
8108
|
+
type: Input
|
|
8109
|
+
}], openAriaLabel: [{
|
|
8110
|
+
type: Input
|
|
8111
|
+
}], openTooltip: [{
|
|
8112
|
+
type: Input
|
|
8113
|
+
}], testId: [{
|
|
8114
|
+
type: Input
|
|
8115
|
+
}], openTestId: [{
|
|
8116
|
+
type: Input
|
|
8117
|
+
}], open: [{
|
|
8118
|
+
type: Output
|
|
8119
|
+
}] } });
|
|
8120
|
+
|
|
8121
|
+
class PraxisAiAssistantSessionHostComponent {
|
|
8122
|
+
registry = inject(PraxisAssistantSessionRegistryService);
|
|
8123
|
+
testId = 'praxis-ai-assistant-session-host';
|
|
8124
|
+
dockTestIdPrefix = 'praxis-ai-assistant-session';
|
|
8125
|
+
ariaLabel = 'Active Praxis assistant sessions';
|
|
8126
|
+
openAriaLabel = 'Open assistant session';
|
|
8127
|
+
openTooltip = 'Open assistant session';
|
|
8128
|
+
ownerType = null;
|
|
8129
|
+
ownerId = null;
|
|
8130
|
+
visibility = 'minimized';
|
|
8131
|
+
sessionOpen = new EventEmitter();
|
|
8132
|
+
visibleSessions() {
|
|
8133
|
+
return this.registry.sessions().filter((session) => {
|
|
8134
|
+
if (this.visibility !== 'all' && session.visibility !== this.visibility)
|
|
8135
|
+
return false;
|
|
8136
|
+
if (this.ownerType && session.ownerType !== this.ownerType)
|
|
8137
|
+
return false;
|
|
8138
|
+
if (this.ownerId && session.ownerId !== this.ownerId)
|
|
8139
|
+
return false;
|
|
8140
|
+
return true;
|
|
8141
|
+
});
|
|
8142
|
+
}
|
|
8143
|
+
openSession(sessionId) {
|
|
8144
|
+
const session = this.registry.openSession(sessionId);
|
|
8145
|
+
if (session) {
|
|
8146
|
+
this.sessionOpen.emit(session);
|
|
8147
|
+
}
|
|
8148
|
+
}
|
|
8149
|
+
sessionAriaLabel(session) {
|
|
8150
|
+
const summary = session.summary ? `: ${session.summary}` : '';
|
|
8151
|
+
return `${session.title}${summary}`;
|
|
8152
|
+
}
|
|
8153
|
+
dockTestId(session, first) {
|
|
8154
|
+
return first ? `${this.dockTestIdPrefix}-dock` : `${this.dockTestIdPrefix}-dock-${this.safeId(session.id)}`;
|
|
8155
|
+
}
|
|
8156
|
+
dockOpenTestId(session, first) {
|
|
8157
|
+
return first ? `${this.dockTestIdPrefix}-dock-open` : `${this.dockTestIdPrefix}-dock-open-${this.safeId(session.id)}`;
|
|
8158
|
+
}
|
|
8159
|
+
trackSession(_index, session) {
|
|
8160
|
+
return session.id;
|
|
8161
|
+
}
|
|
8162
|
+
safeId(value) {
|
|
8163
|
+
return value.replace(/[^a-zA-Z0-9_-]+/g, '-');
|
|
8164
|
+
}
|
|
8165
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantSessionHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8166
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisAiAssistantSessionHostComponent, isStandalone: true, selector: "praxis-ai-assistant-session-host", inputs: { testId: "testId", dockTestIdPrefix: "dockTestIdPrefix", ariaLabel: "ariaLabel", openAriaLabel: "openAriaLabel", openTooltip: "openTooltip", ownerType: "ownerType", ownerId: "ownerId", visibility: "visibility" }, outputs: { sessionOpen: "sessionOpen" }, ngImport: i0, template: `
|
|
8167
|
+
<section
|
|
8168
|
+
*ngIf="visibleSessions().length"
|
|
8169
|
+
class="praxis-ai-assistant-session-host"
|
|
8170
|
+
[attr.data-testid]="testId"
|
|
8171
|
+
[attr.aria-label]="ariaLabel"
|
|
8172
|
+
>
|
|
8173
|
+
<praxis-ai-assistant-dock
|
|
8174
|
+
*ngFor="let session of visibleSessions(); let first = first; trackBy: trackSession"
|
|
8175
|
+
[title]="session.title"
|
|
8176
|
+
[summary]="session.summary"
|
|
8177
|
+
[badge]="session.badge"
|
|
8178
|
+
[icon]="session.icon"
|
|
8179
|
+
[state]="session.state"
|
|
8180
|
+
[ariaLabel]="sessionAriaLabel(session)"
|
|
8181
|
+
[openAriaLabel]="openAriaLabel"
|
|
8182
|
+
[openTooltip]="openTooltip"
|
|
8183
|
+
[testId]="dockTestId(session, first)"
|
|
8184
|
+
[openTestId]="dockOpenTestId(session, first)"
|
|
8185
|
+
(open)="openSession(session.id)"
|
|
8186
|
+
/>
|
|
8187
|
+
</section>
|
|
8188
|
+
`, isInline: true, styles: [":host{display:block;position:absolute;z-index:var(--praxis-ai-assistant-session-host-z-index, 136);right:var(--praxis-ai-assistant-session-host-right, 16px);bottom:var(--praxis-ai-assistant-session-host-bottom, 88px);width:min(var(--praxis-ai-assistant-session-host-width, 560px),calc(100% - 32px));pointer-events:none}.praxis-ai-assistant-session-host{display:grid;gap:10px;pointer-events:auto}praxis-ai-assistant-dock{--praxis-ai-assistant-dock-position: static;--praxis-ai-assistant-dock-width: 100%}@media(max-width:720px){:host{right:var(--praxis-ai-assistant-session-host-mobile-right, 12px);bottom:var(--praxis-ai-assistant-session-host-mobile-bottom, 84px);width:calc(100% - 24px)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: PraxisAiAssistantDockComponent, selector: "praxis-ai-assistant-dock", inputs: ["title", "summary", "badge", "icon", "state", "tone", "ariaLabel", "openAriaLabel", "openTooltip", "testId", "openTestId"], outputs: ["open"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8189
|
+
}
|
|
8190
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantSessionHostComponent, decorators: [{
|
|
8191
|
+
type: Component,
|
|
8192
|
+
args: [{ selector: 'praxis-ai-assistant-session-host', standalone: true, imports: [CommonModule, PraxisAiAssistantDockComponent], template: `
|
|
8193
|
+
<section
|
|
8194
|
+
*ngIf="visibleSessions().length"
|
|
8195
|
+
class="praxis-ai-assistant-session-host"
|
|
8196
|
+
[attr.data-testid]="testId"
|
|
8197
|
+
[attr.aria-label]="ariaLabel"
|
|
8198
|
+
>
|
|
8199
|
+
<praxis-ai-assistant-dock
|
|
8200
|
+
*ngFor="let session of visibleSessions(); let first = first; trackBy: trackSession"
|
|
8201
|
+
[title]="session.title"
|
|
8202
|
+
[summary]="session.summary"
|
|
8203
|
+
[badge]="session.badge"
|
|
8204
|
+
[icon]="session.icon"
|
|
8205
|
+
[state]="session.state"
|
|
8206
|
+
[ariaLabel]="sessionAriaLabel(session)"
|
|
8207
|
+
[openAriaLabel]="openAriaLabel"
|
|
8208
|
+
[openTooltip]="openTooltip"
|
|
8209
|
+
[testId]="dockTestId(session, first)"
|
|
8210
|
+
[openTestId]="dockOpenTestId(session, first)"
|
|
8211
|
+
(open)="openSession(session.id)"
|
|
8212
|
+
/>
|
|
8213
|
+
</section>
|
|
8214
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:absolute;z-index:var(--praxis-ai-assistant-session-host-z-index, 136);right:var(--praxis-ai-assistant-session-host-right, 16px);bottom:var(--praxis-ai-assistant-session-host-bottom, 88px);width:min(var(--praxis-ai-assistant-session-host-width, 560px),calc(100% - 32px));pointer-events:none}.praxis-ai-assistant-session-host{display:grid;gap:10px;pointer-events:auto}praxis-ai-assistant-dock{--praxis-ai-assistant-dock-position: static;--praxis-ai-assistant-dock-width: 100%}@media(max-width:720px){:host{right:var(--praxis-ai-assistant-session-host-mobile-right, 12px);bottom:var(--praxis-ai-assistant-session-host-mobile-bottom, 84px);width:calc(100% - 24px)}}\n"] }]
|
|
8215
|
+
}], propDecorators: { testId: [{
|
|
8216
|
+
type: Input
|
|
8217
|
+
}], dockTestIdPrefix: [{
|
|
8218
|
+
type: Input
|
|
8219
|
+
}], ariaLabel: [{
|
|
8220
|
+
type: Input
|
|
8221
|
+
}], openAriaLabel: [{
|
|
8222
|
+
type: Input
|
|
8223
|
+
}], openTooltip: [{
|
|
8224
|
+
type: Input
|
|
8225
|
+
}], ownerType: [{
|
|
8226
|
+
type: Input
|
|
8227
|
+
}], ownerId: [{
|
|
8228
|
+
type: Input
|
|
8229
|
+
}], visibility: [{
|
|
8230
|
+
type: Input
|
|
8231
|
+
}], sessionOpen: [{
|
|
8232
|
+
type: Output
|
|
8233
|
+
}] } });
|
|
8234
|
+
|
|
6371
8235
|
class StreamingFeedbackComponent {
|
|
6372
8236
|
title = 'Processando...';
|
|
6373
8237
|
displayText = '';
|
|
@@ -7165,4 +9029,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
7165
9029
|
* Generated bundle index. Do not edit.
|
|
7166
9030
|
*/
|
|
7167
9031
|
|
|
7168
|
-
export { AI_BACKEND_CONFIG_STORE, AI_BACKEND_STORAGE_OPTIONS, AI_CONTRACT_SCHEMA_HASH, AI_CONTRACT_VERSION, AI_INTENT_CONTRACT_SCHEMA_HASH, AI_INTENT_CONTRACT_VERSION, AI_STREAM_EVENT_SCHEMA_VERSION, AI_STREAM_EVENT_TYPES, AiBackendApiService, AiPatchStreamConnectionError, AiResponseValidatorService, AiRuleWizardDialogComponent, BaseAiAdapter, PraxisAi, PraxisAiAssistantComponent, PraxisAiAssistantShellComponent, PraxisAiService, PraxisAssistantTurnController, PraxisAssistantTurnOrchestratorService, SchemaMinifierService };
|
|
9032
|
+
export { AI_BACKEND_CONFIG_STORE, AI_BACKEND_ENDPOINTS, AI_BACKEND_STORAGE_OPTIONS, AI_CONTRACT_SCHEMA_HASH, AI_CONTRACT_VERSION, AI_INTENT_CONTRACT_SCHEMA_HASH, AI_INTENT_CONTRACT_VERSION, AI_STREAM_EVENT_SCHEMA_VERSION, AI_STREAM_EVENT_TYPES, AiBackendApiService, AiPatchStreamConnectionError, AiResponseValidatorService, AiRuleWizardDialogComponent, BaseAiAdapter, PRAXIS_ASSISTANT_CONTEXT_ATTACHMENT_LIMIT, PRAXIS_ASSISTANT_CONTEXT_ITEM_LIMIT, PRAXIS_ASSISTANT_CONTEXT_SCHEMA_FIELD_LIMIT, PRAXIS_ASSISTANT_CONTEXT_TEXT_LIMIT, PraxisAi, PraxisAiAssistantComponent, PraxisAiAssistantDockComponent, PraxisAiAssistantSessionHostComponent, PraxisAiAssistantShellComponent, PraxisAiService, PraxisAssistantSessionRegistryService, PraxisAssistantTurnController, PraxisAssistantTurnOrchestratorService, SchemaMinifierService, createPraxisAssistantViewportLayout, normalizeAuthoringPrompt, normalizePraxisAssistantAttachmentSummary, normalizePraxisAssistantContextSnapshot, sanitizePraxisAssistantText, shouldRoutePromptToGovernedDecision, toPraxisAssistantConversationMessages };
|