@praxisui/ai 8.0.0-beta.3 → 8.0.0-beta.31
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 +67 -4
- package/fesm2022/praxisui-ai.mjs +2065 -110
- package/index.d.ts +727 -26
- 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, booleanAttribute } 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,15 +61,398 @@ 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
|
|
|
411
|
+
function createComponentAuthoringContext(authoringContract) {
|
|
412
|
+
return {
|
|
413
|
+
authoringContract: toAiJsonObject(authoringContract, 'authoringContract'),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function toAiJsonObject(value, path = 'value') {
|
|
417
|
+
const normalized = toAiJsonValue(value, path);
|
|
418
|
+
if (!normalized || Array.isArray(normalized) || typeof normalized !== 'object') {
|
|
419
|
+
throw new Error(`${path} must be a JSON object.`);
|
|
420
|
+
}
|
|
421
|
+
return normalized;
|
|
422
|
+
}
|
|
423
|
+
function toAiJsonValue(value, path) {
|
|
424
|
+
if (value === undefined)
|
|
425
|
+
return undefined;
|
|
426
|
+
if (value === null) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
if (typeof value === 'string' || typeof value === 'boolean') {
|
|
430
|
+
return value;
|
|
431
|
+
}
|
|
432
|
+
if (typeof value === 'number') {
|
|
433
|
+
if (!Number.isFinite(value)) {
|
|
434
|
+
throw new Error(`${path} must be a finite JSON number.`);
|
|
435
|
+
}
|
|
436
|
+
return value;
|
|
437
|
+
}
|
|
438
|
+
if (Array.isArray(value)) {
|
|
439
|
+
return value
|
|
440
|
+
.map((item, index) => toAiJsonValue(item, `${path}[${index}]`))
|
|
441
|
+
.filter((item) => item !== undefined);
|
|
442
|
+
}
|
|
443
|
+
if (typeof value === 'object') {
|
|
444
|
+
const result = {};
|
|
445
|
+
for (const [key, item] of Object.entries(value)) {
|
|
446
|
+
const normalized = toAiJsonValue(item, `${path}.${key}`);
|
|
447
|
+
if (normalized !== undefined) {
|
|
448
|
+
result[key] = normalized;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return result;
|
|
452
|
+
}
|
|
453
|
+
throw new Error(`${path} contains a non-JSON value.`);
|
|
454
|
+
}
|
|
455
|
+
|
|
72
456
|
/**
|
|
73
457
|
* Base implementation for AI Adapters.
|
|
74
458
|
* Provides common validation logic against the Capabilities Catalog.
|
|
@@ -113,6 +497,26 @@ class BaseAiAdapter {
|
|
|
113
497
|
}
|
|
114
498
|
}
|
|
115
499
|
|
|
500
|
+
/**
|
|
501
|
+
* @deprecated Component assistants must not infer governed authoring from user
|
|
502
|
+
* wording. Route through the backend semantic resolver and use this only for
|
|
503
|
+
* explicit, structured context provided by a canonical backend source.
|
|
504
|
+
*/
|
|
505
|
+
function shouldRoutePromptToGovernedDecision(prompt, contextHints, options = {}) {
|
|
506
|
+
void prompt;
|
|
507
|
+
void options;
|
|
508
|
+
const recommendedFlow = toRecord(contextHints?.['domainCatalog'])?.['recommendedAuthoringFlow'];
|
|
509
|
+
return recommendedFlow === 'shared_rule_authoring';
|
|
510
|
+
}
|
|
511
|
+
function normalizeAuthoringPrompt(prompt) {
|
|
512
|
+
return prompt.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
|
513
|
+
}
|
|
514
|
+
function toRecord(value) {
|
|
515
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
516
|
+
? value
|
|
517
|
+
: null;
|
|
518
|
+
}
|
|
519
|
+
|
|
116
520
|
class SchemaMinifierService {
|
|
117
521
|
/**
|
|
118
522
|
* Converts complete FieldSchemas into a minified version for AI context (Token optimized)
|
|
@@ -526,15 +930,6 @@ class PraxisAiService {
|
|
|
526
930
|
if (!this.isGeminiProvider()) {
|
|
527
931
|
return throwError(() => new Error('LLM provider not supported in browser mode.'));
|
|
528
932
|
}
|
|
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
933
|
if (!this.genAI)
|
|
539
934
|
return throwError(() => new Error('API Key not configured'));
|
|
540
935
|
const generationConfig = {
|
|
@@ -580,50 +975,6 @@ class PraxisAiService {
|
|
|
580
975
|
isMockMode() {
|
|
581
976
|
return !this.genAI;
|
|
582
977
|
}
|
|
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
978
|
listModels(apiKey) {
|
|
628
979
|
if (!this.isGeminiProvider()) {
|
|
629
980
|
return throwError(() => new Error('Model listing only available for Gemini.'));
|
|
@@ -690,6 +1041,7 @@ class AiPatchStreamConnectionError extends Error {
|
|
|
690
1041
|
}
|
|
691
1042
|
const AI_BACKEND_CONFIG_STORE = new InjectionToken('AI_BACKEND_CONFIG_STORE');
|
|
692
1043
|
const AI_BACKEND_STORAGE_OPTIONS = new InjectionToken('AI_BACKEND_STORAGE_OPTIONS');
|
|
1044
|
+
const AI_BACKEND_ENDPOINTS = new InjectionToken('AI_BACKEND_ENDPOINTS');
|
|
693
1045
|
const DEFAULT_USER_ID_STORAGE_KEY = 'praxis.demoUserId';
|
|
694
1046
|
function buildDemoUserId() {
|
|
695
1047
|
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
@@ -723,18 +1075,20 @@ class AiBackendApiService {
|
|
|
723
1075
|
http = inject(HttpClient);
|
|
724
1076
|
globalConfigStore = inject(AI_BACKEND_CONFIG_STORE, { optional: true });
|
|
725
1077
|
storageOpts = inject(AI_BACKEND_STORAGE_OPTIONS, { optional: true });
|
|
726
|
-
|
|
727
|
-
|
|
1078
|
+
endpointOpts = inject(AI_BACKEND_ENDPOINTS, { optional: true });
|
|
1079
|
+
apiUrlConfig = inject(API_URL, { optional: true });
|
|
1080
|
+
fallbackAiBaseUrl = '/api/praxis/config/ai';
|
|
1081
|
+
fallbackAiContextBaseUrl = '/api/praxis/config/ai-context';
|
|
728
1082
|
getSuggestions(request) {
|
|
729
|
-
return this.http.post(`${this.
|
|
1083
|
+
return this.http.post(`${this.aiBaseUrl()}/suggestions`, request, { headers: this.buildHeaders() });
|
|
730
1084
|
}
|
|
731
1085
|
getPatch(request) {
|
|
732
1086
|
const normalizedRequest = {
|
|
733
|
-
...request,
|
|
1087
|
+
...this.normalizeLegacyPatchConversationIds(request),
|
|
734
1088
|
contractVersion: this.normalizeContractVersion(request.contractVersion),
|
|
735
1089
|
schemaHash: this.normalizeContractSchemaHash(request.schemaHash),
|
|
736
1090
|
};
|
|
737
|
-
return this.http.post(`${this.
|
|
1091
|
+
return this.http.post(`${this.aiBaseUrl()}/patch`, normalizedRequest, {
|
|
738
1092
|
headers: this.buildHeaders({
|
|
739
1093
|
[AI_CONTRACT_VERSION_HEADER]: normalizedRequest.contractVersion,
|
|
740
1094
|
[AI_CONTRACT_SCHEMA_HASH_HEADER]: normalizedRequest.schemaHash,
|
|
@@ -743,11 +1097,11 @@ class AiBackendApiService {
|
|
|
743
1097
|
}
|
|
744
1098
|
startPatchStream(request) {
|
|
745
1099
|
const normalizedRequest = {
|
|
746
|
-
...request,
|
|
1100
|
+
...this.normalizeLegacyPatchConversationIds(request),
|
|
747
1101
|
contractVersion: this.normalizeContractVersion(request.contractVersion),
|
|
748
1102
|
schemaHash: this.normalizeContractSchemaHash(request.schemaHash),
|
|
749
1103
|
};
|
|
750
|
-
return this.http.post(`${this.
|
|
1104
|
+
return this.http.post(`${this.aiBaseUrl()}/patch/stream/start`, normalizedRequest, {
|
|
751
1105
|
headers: this.buildHeaders({
|
|
752
1106
|
[AI_CONTRACT_VERSION_HEADER]: normalizedRequest.contractVersion,
|
|
753
1107
|
[AI_CONTRACT_SCHEMA_HASH_HEADER]: normalizedRequest.schemaHash,
|
|
@@ -764,7 +1118,7 @@ class AiBackendApiService {
|
|
|
764
1118
|
queryParams.push(`accessToken=${encodeURIComponent(accessToken.trim())}`);
|
|
765
1119
|
}
|
|
766
1120
|
const query = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
|
|
767
|
-
const streamPath = `${this.
|
|
1121
|
+
const streamPath = `${this.aiBaseUrl()}/patch/stream/${encodeURIComponent(streamId)}`;
|
|
768
1122
|
const endpoint = `${streamPath}${query}`;
|
|
769
1123
|
const probeEndpoint = `${streamPath}/probe${accessToken?.trim()
|
|
770
1124
|
? `?accessToken=${encodeURIComponent(accessToken.trim())}`
|
|
@@ -772,6 +1126,12 @@ class AiBackendApiService {
|
|
|
772
1126
|
let source = null;
|
|
773
1127
|
const events$ = new Observable((subscriber) => {
|
|
774
1128
|
let disposed = false;
|
|
1129
|
+
const closeSource = () => {
|
|
1130
|
+
if (source) {
|
|
1131
|
+
source.close();
|
|
1132
|
+
source = null;
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
775
1135
|
const openStream = async () => {
|
|
776
1136
|
if (typeof EventSource === 'undefined') {
|
|
777
1137
|
subscriber.error(new AiPatchStreamConnectionError('unsupported', 'EventSource is not supported in this environment.'));
|
|
@@ -786,10 +1146,14 @@ class AiBackendApiService {
|
|
|
786
1146
|
return;
|
|
787
1147
|
}
|
|
788
1148
|
source = new EventSource(endpoint, { withCredentials: true });
|
|
789
|
-
|
|
1149
|
+
const handleMessage = (messageEvent) => {
|
|
790
1150
|
try {
|
|
791
1151
|
const parsed = this.parsePatchStreamEnvelope(messageEvent.data);
|
|
792
1152
|
subscriber.next(parsed);
|
|
1153
|
+
if (this.isTerminalStreamEventType(parsed.type)) {
|
|
1154
|
+
subscriber.complete();
|
|
1155
|
+
closeSource();
|
|
1156
|
+
}
|
|
793
1157
|
}
|
|
794
1158
|
catch (error) {
|
|
795
1159
|
if (error instanceof AiPatchStreamConnectionError) {
|
|
@@ -799,6 +1163,8 @@ class AiBackendApiService {
|
|
|
799
1163
|
subscriber.error(new AiPatchStreamConnectionError('parse', 'Failed to parse stream event payload.'));
|
|
800
1164
|
}
|
|
801
1165
|
};
|
|
1166
|
+
source.onmessage = handleMessage;
|
|
1167
|
+
this.registerNamedStreamEventListeners(source, handleMessage);
|
|
802
1168
|
source.onerror = () => {
|
|
803
1169
|
if (!source) {
|
|
804
1170
|
subscriber.error(new AiPatchStreamConnectionError('transport', 'Patch stream connection error.'));
|
|
@@ -814,11 +1180,105 @@ class AiBackendApiService {
|
|
|
814
1180
|
void openStream();
|
|
815
1181
|
return () => {
|
|
816
1182
|
disposed = true;
|
|
1183
|
+
closeSource();
|
|
1184
|
+
};
|
|
1185
|
+
});
|
|
1186
|
+
return {
|
|
1187
|
+
events$,
|
|
1188
|
+
close: () => {
|
|
1189
|
+
if (source) {
|
|
1190
|
+
source.close();
|
|
1191
|
+
source = null;
|
|
1192
|
+
}
|
|
1193
|
+
},
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
cancelPatchStream(streamId, accessToken) {
|
|
1197
|
+
const endpoint = `${this.aiBaseUrl()}/patch/stream/${encodeURIComponent(streamId)}/cancel${accessToken?.trim()
|
|
1198
|
+
? `?accessToken=${encodeURIComponent(accessToken.trim())}`
|
|
1199
|
+
: ''}`;
|
|
1200
|
+
return this.http.post(endpoint, {}, {
|
|
1201
|
+
headers: this.buildHeaders(),
|
|
1202
|
+
withCredentials: true,
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
startAgenticAuthoringTurnStream(request) {
|
|
1206
|
+
return this.http.post(`${this.aiBaseUrl()}/authoring/turn/stream/start`, request, {
|
|
1207
|
+
headers: this.buildHeaders(),
|
|
1208
|
+
withCredentials: true,
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
connectAgenticAuthoringTurnStream(streamId, lastEventId, accessToken) {
|
|
1212
|
+
const queryParams = [];
|
|
1213
|
+
if (lastEventId?.trim()) {
|
|
1214
|
+
queryParams.push(`lastEventId=${encodeURIComponent(lastEventId.trim())}`);
|
|
1215
|
+
}
|
|
1216
|
+
if (accessToken?.trim()) {
|
|
1217
|
+
queryParams.push(`accessToken=${encodeURIComponent(accessToken.trim())}`);
|
|
1218
|
+
}
|
|
1219
|
+
const query = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
|
|
1220
|
+
const streamPath = `${this.aiBaseUrl()}/authoring/turn/stream/${encodeURIComponent(streamId)}`;
|
|
1221
|
+
const endpoint = `${streamPath}${query}`;
|
|
1222
|
+
const probeEndpoint = `${streamPath}/probe${accessToken?.trim()
|
|
1223
|
+
? `?accessToken=${encodeURIComponent(accessToken.trim())}`
|
|
1224
|
+
: ''}`;
|
|
1225
|
+
let source = null;
|
|
1226
|
+
const events$ = new Observable((subscriber) => {
|
|
1227
|
+
let disposed = false;
|
|
1228
|
+
const closeSource = () => {
|
|
817
1229
|
if (source) {
|
|
818
1230
|
source.close();
|
|
819
1231
|
source = null;
|
|
820
1232
|
}
|
|
821
1233
|
};
|
|
1234
|
+
const openStream = async () => {
|
|
1235
|
+
if (typeof EventSource === 'undefined') {
|
|
1236
|
+
subscriber.error(new AiPatchStreamConnectionError('unsupported', 'EventSource is not supported in this environment.'));
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
const probeStatus = await this.probePatchStreamEndpoint(probeEndpoint);
|
|
1240
|
+
if (disposed) {
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (typeof probeStatus === 'number' && probeStatus >= 400) {
|
|
1244
|
+
subscriber.error(new AiPatchStreamConnectionError('http_status', `Agentic authoring stream probe failed with status ${probeStatus}.`, probeStatus));
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
source = new EventSource(endpoint, { withCredentials: true });
|
|
1248
|
+
const handleMessage = (messageEvent) => {
|
|
1249
|
+
try {
|
|
1250
|
+
const parsed = this.parsePatchStreamEnvelope(messageEvent.data);
|
|
1251
|
+
subscriber.next(parsed);
|
|
1252
|
+
if (this.isTerminalStreamEventType(parsed.type)) {
|
|
1253
|
+
subscriber.complete();
|
|
1254
|
+
closeSource();
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
catch (error) {
|
|
1258
|
+
if (error instanceof AiPatchStreamConnectionError) {
|
|
1259
|
+
subscriber.error(error);
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
subscriber.error(new AiPatchStreamConnectionError('parse', 'Failed to parse agentic authoring stream event payload.'));
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
source.onmessage = handleMessage;
|
|
1266
|
+
this.registerNamedStreamEventListeners(source, handleMessage);
|
|
1267
|
+
source.onerror = () => {
|
|
1268
|
+
if (!source) {
|
|
1269
|
+
subscriber.error(new AiPatchStreamConnectionError('transport', 'Agentic authoring stream connection error.'));
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
if (source.readyState === EventSource.CLOSED) {
|
|
1273
|
+
subscriber.error(new AiPatchStreamConnectionError('transport', 'Agentic authoring stream connection closed unexpectedly.'));
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
};
|
|
1277
|
+
void openStream();
|
|
1278
|
+
return () => {
|
|
1279
|
+
disposed = true;
|
|
1280
|
+
closeSource();
|
|
1281
|
+
};
|
|
822
1282
|
});
|
|
823
1283
|
return {
|
|
824
1284
|
events$,
|
|
@@ -830,8 +1290,8 @@ class AiBackendApiService {
|
|
|
830
1290
|
},
|
|
831
1291
|
};
|
|
832
1292
|
}
|
|
833
|
-
|
|
834
|
-
const endpoint = `${this.
|
|
1293
|
+
cancelAgenticAuthoringTurnStream(streamId, accessToken) {
|
|
1294
|
+
const endpoint = `${this.aiBaseUrl()}/authoring/turn/stream/${encodeURIComponent(streamId)}/cancel${accessToken?.trim()
|
|
835
1295
|
? `?accessToken=${encodeURIComponent(accessToken.trim())}`
|
|
836
1296
|
: ''}`;
|
|
837
1297
|
return this.http.post(endpoint, {}, {
|
|
@@ -840,20 +1300,38 @@ class AiBackendApiService {
|
|
|
840
1300
|
});
|
|
841
1301
|
}
|
|
842
1302
|
listModels(request) {
|
|
843
|
-
return this.http.post(`${this.
|
|
1303
|
+
return this.http.post(`${this.aiBaseUrl()}/providers/models`, request, { headers: this.buildHeaders() });
|
|
844
1304
|
}
|
|
845
1305
|
listProviderCatalog() {
|
|
846
|
-
return this.http.get(`${this.
|
|
1306
|
+
return this.http.get(`${this.aiBaseUrl()}/providers/catalog`, { headers: this.buildHeaders() });
|
|
847
1307
|
}
|
|
848
1308
|
testProvider(request) {
|
|
849
|
-
return this.http.post(`${this.
|
|
1309
|
+
return this.http.post(`${this.aiBaseUrl()}/providers/test`, request, { headers: this.buildHeaders() });
|
|
850
1310
|
}
|
|
851
1311
|
getAiStatus() {
|
|
852
|
-
return this.http.get(`${this.
|
|
1312
|
+
return this.http.get(`${this.aiBaseUrl()}/status`, { headers: this.buildHeaders() });
|
|
853
1313
|
}
|
|
854
1314
|
getAiContext(componentId, componentType) {
|
|
855
1315
|
const params = new HttpParams({ fromObject: { componentType } });
|
|
856
|
-
return this.http.get(`${this.
|
|
1316
|
+
return this.http.get(`${this.aiContextBaseUrl()}/${componentId}`, { headers: this.buildHeaders(), params });
|
|
1317
|
+
}
|
|
1318
|
+
getAgenticAuthoringManifest(componentId) {
|
|
1319
|
+
return this.http.get(`${this.authoringManifestUrl(componentId)}`, { headers: this.buildHeaders() });
|
|
1320
|
+
}
|
|
1321
|
+
listAgenticAuthoringManifestTargets(componentId) {
|
|
1322
|
+
return this.http.get(`${this.authoringManifestUrl(componentId)}/editable-targets`, { headers: this.buildHeaders() });
|
|
1323
|
+
}
|
|
1324
|
+
listAgenticAuthoringManifestOperations(componentId) {
|
|
1325
|
+
return this.http.get(`${this.authoringManifestUrl(componentId)}/operations`, { headers: this.buildHeaders() });
|
|
1326
|
+
}
|
|
1327
|
+
resolveAgenticAuthoringManifestTarget(componentId, request) {
|
|
1328
|
+
return this.http.post(`${this.authoringManifestUrl(componentId)}/resolve-target`, request, { headers: this.buildHeaders() });
|
|
1329
|
+
}
|
|
1330
|
+
validateAgenticAuthoringManifestPlan(componentId, request) {
|
|
1331
|
+
return this.http.post(`${this.authoringManifestUrl(componentId)}/validate-plan`, request, { headers: this.buildHeaders() });
|
|
1332
|
+
}
|
|
1333
|
+
compileAgenticAuthoringManifestPatch(componentId, request) {
|
|
1334
|
+
return this.http.post(`${this.authoringManifestUrl(componentId)}/compile-patch`, request, { headers: this.buildHeaders() });
|
|
857
1335
|
}
|
|
858
1336
|
loadGlobalAiConfig() {
|
|
859
1337
|
const snapshot = this.globalConfigStore?.getAiConfigSnapshot?.();
|
|
@@ -895,6 +1373,69 @@ class AiBackendApiService {
|
|
|
895
1373
|
}
|
|
896
1374
|
return AI_INTENT_CONTRACT_SCHEMA_HASH;
|
|
897
1375
|
}
|
|
1376
|
+
normalizeLegacyPatchConversationIds(request) {
|
|
1377
|
+
const normalized = { ...request };
|
|
1378
|
+
if (!this.isUuid(normalized.sessionId)) {
|
|
1379
|
+
delete normalized.sessionId;
|
|
1380
|
+
}
|
|
1381
|
+
if (!this.isUuid(normalized.clientTurnId)) {
|
|
1382
|
+
delete normalized.clientTurnId;
|
|
1383
|
+
}
|
|
1384
|
+
return normalized;
|
|
1385
|
+
}
|
|
1386
|
+
isUuid(value) {
|
|
1387
|
+
return typeof value === 'string'
|
|
1388
|
+
&& /^[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());
|
|
1389
|
+
}
|
|
1390
|
+
authoringManifestUrl(componentId) {
|
|
1391
|
+
return `${this.aiBaseUrl()}/authoring/manifests/${encodeURIComponent(componentId)}`;
|
|
1392
|
+
}
|
|
1393
|
+
aiBaseUrl() {
|
|
1394
|
+
return this.resolveBaseUrl(this.endpointOpts?.aiBaseUrl, 'praxis/config/ai', this.fallbackAiBaseUrl);
|
|
1395
|
+
}
|
|
1396
|
+
aiContextBaseUrl() {
|
|
1397
|
+
return this.resolveBaseUrl(this.endpointOpts?.aiContextBaseUrl, 'praxis/config/ai-context', this.fallbackAiContextBaseUrl);
|
|
1398
|
+
}
|
|
1399
|
+
resolveBaseUrl(explicitUrl, apiRelativePath, fallbackUrl) {
|
|
1400
|
+
const explicit = this.normalizeBaseUrl(explicitUrl);
|
|
1401
|
+
if (explicit) {
|
|
1402
|
+
return explicit;
|
|
1403
|
+
}
|
|
1404
|
+
const apiBaseUrl = this.resolveApiBaseUrl();
|
|
1405
|
+
if (apiBaseUrl) {
|
|
1406
|
+
return this.joinUrl(apiBaseUrl, apiRelativePath);
|
|
1407
|
+
}
|
|
1408
|
+
return fallbackUrl;
|
|
1409
|
+
}
|
|
1410
|
+
resolveApiBaseUrl() {
|
|
1411
|
+
const defaultConfig = this.apiUrlConfig?.['default'];
|
|
1412
|
+
if (!defaultConfig) {
|
|
1413
|
+
return '';
|
|
1414
|
+
}
|
|
1415
|
+
return this.normalizeBaseUrl(this.buildApiUrl(defaultConfig));
|
|
1416
|
+
}
|
|
1417
|
+
joinUrl(baseUrl, relativePath) {
|
|
1418
|
+
const normalizedBase = this.normalizeBaseUrl(baseUrl);
|
|
1419
|
+
const normalizedPath = relativePath.replace(/^\/+/, '');
|
|
1420
|
+
return normalizedBase ? `${normalizedBase}/${normalizedPath}` : normalizedPath;
|
|
1421
|
+
}
|
|
1422
|
+
normalizeBaseUrl(value) {
|
|
1423
|
+
const normalized = value?.trim().replace(/\/+$/, '') ?? '';
|
|
1424
|
+
return normalized;
|
|
1425
|
+
}
|
|
1426
|
+
buildApiUrl(entry) {
|
|
1427
|
+
if (entry.fullUrl?.trim()) {
|
|
1428
|
+
return entry.fullUrl.trim();
|
|
1429
|
+
}
|
|
1430
|
+
let url = entry.baseUrl?.trim().replace(/\/+$/, '') ?? '';
|
|
1431
|
+
if (entry.path?.trim()) {
|
|
1432
|
+
url = this.joinUrl(url, entry.path.trim());
|
|
1433
|
+
}
|
|
1434
|
+
if (entry.version?.trim()) {
|
|
1435
|
+
url = this.joinUrl(url, entry.version.trim());
|
|
1436
|
+
}
|
|
1437
|
+
return url;
|
|
1438
|
+
}
|
|
898
1439
|
resolveHeaderMap(extra) {
|
|
899
1440
|
const allowLocalIdentityFallback = !!this.storageOpts?.allowLocalIdentityFallback;
|
|
900
1441
|
const defaults = this.storageOpts?.defaultHeaders
|
|
@@ -969,6 +1510,17 @@ class AiBackendApiService {
|
|
|
969
1510
|
isJsonObject(value) {
|
|
970
1511
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
971
1512
|
}
|
|
1513
|
+
registerNamedStreamEventListeners(source, handler) {
|
|
1514
|
+
if (typeof source.addEventListener !== 'function') {
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
for (const eventType of AI_STREAM_EVENT_TYPES) {
|
|
1518
|
+
source.addEventListener(eventType, handler);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
isTerminalStreamEventType(type) {
|
|
1522
|
+
return type === 'result' || type === 'error' || type === 'cancelled';
|
|
1523
|
+
}
|
|
972
1524
|
async probePatchStreamEndpoint(endpoint) {
|
|
973
1525
|
if (typeof fetch === 'undefined') {
|
|
974
1526
|
return null;
|
|
@@ -1077,7 +1629,7 @@ class AiResponseValidatorService {
|
|
|
1077
1629
|
code: 'MISSING_RULE_NAME'
|
|
1078
1630
|
});
|
|
1079
1631
|
}
|
|
1080
|
-
if (!response.targetType || !['field', 'section', 'action', 'row', 'column'].includes(response.targetType)) {
|
|
1632
|
+
if (!response.targetType || !['field', 'section', 'action', 'row', 'column', 'visualBlock'].includes(response.targetType)) {
|
|
1081
1633
|
errors.push({
|
|
1082
1634
|
field: 'targetType',
|
|
1083
1635
|
message: 'targetType inválido',
|
|
@@ -1211,6 +1763,10 @@ class AiResponseValidatorService {
|
|
|
1211
1763
|
return;
|
|
1212
1764
|
}
|
|
1213
1765
|
const args = Array.isArray(rawArgs) ? rawArgs : [rawArgs];
|
|
1766
|
+
const arityIssue = this.validateJsonLogicOperatorArity(operator, args.length);
|
|
1767
|
+
if (arityIssue) {
|
|
1768
|
+
issues.push({ message: arityIssue });
|
|
1769
|
+
}
|
|
1214
1770
|
args.forEach((arg) => this.walkJsonLogicNode(arg, false, issues));
|
|
1215
1771
|
return;
|
|
1216
1772
|
}
|
|
@@ -1247,6 +1803,49 @@ class AiResponseValidatorService {
|
|
|
1247
1803
|
'max',
|
|
1248
1804
|
].includes(operator);
|
|
1249
1805
|
}
|
|
1806
|
+
validateJsonLogicOperatorArity(operator, argumentCount) {
|
|
1807
|
+
const exactArity = {
|
|
1808
|
+
'==': 2,
|
|
1809
|
+
'===': 2,
|
|
1810
|
+
'!=': 2,
|
|
1811
|
+
'!==': 2,
|
|
1812
|
+
'>': 2,
|
|
1813
|
+
'>=': 2,
|
|
1814
|
+
'<': 2,
|
|
1815
|
+
'<=': 2,
|
|
1816
|
+
'!': 1,
|
|
1817
|
+
'!!': 1,
|
|
1818
|
+
in: 2,
|
|
1819
|
+
contains: 2,
|
|
1820
|
+
startsWith: 2,
|
|
1821
|
+
endsWith: 2,
|
|
1822
|
+
'-': 2,
|
|
1823
|
+
'/': 2,
|
|
1824
|
+
'%': 2,
|
|
1825
|
+
};
|
|
1826
|
+
const expected = exactArity[operator];
|
|
1827
|
+
if (expected !== undefined && argumentCount !== expected) {
|
|
1828
|
+
return `Operator ${operator} requires exactly ${expected} argument(s).`;
|
|
1829
|
+
}
|
|
1830
|
+
if ((operator === 'and' || operator === 'or') && argumentCount < 2) {
|
|
1831
|
+
return `Operator ${operator} requires at least 2 arguments.`;
|
|
1832
|
+
}
|
|
1833
|
+
if (operator === 'if' && argumentCount < 3) {
|
|
1834
|
+
return 'Operator if requires at least 3 arguments.';
|
|
1835
|
+
}
|
|
1836
|
+
if ((operator === 'cat'
|
|
1837
|
+
|| operator === '+'
|
|
1838
|
+
|| operator === '*'
|
|
1839
|
+
|| operator === 'min'
|
|
1840
|
+
|| operator === 'max')
|
|
1841
|
+
&& argumentCount < 1) {
|
|
1842
|
+
return `Operator ${operator} requires at least 1 argument.`;
|
|
1843
|
+
}
|
|
1844
|
+
if (operator === 'substr' && (argumentCount < 2 || argumentCount > 3)) {
|
|
1845
|
+
return 'Operator substr requires 2 or 3 arguments.';
|
|
1846
|
+
}
|
|
1847
|
+
return null;
|
|
1848
|
+
}
|
|
1250
1849
|
collectVarPaths(value, collector) {
|
|
1251
1850
|
if (Array.isArray(value)) {
|
|
1252
1851
|
value.forEach((item) => this.collectVarPaths(item, collector));
|
|
@@ -1460,7 +2059,10 @@ class PraxisAssistantTurnController {
|
|
|
1460
2059
|
const current = this.stateSubject.value;
|
|
1461
2060
|
const effectiveAction = this.resolveSubmitAction(action, current);
|
|
1462
2061
|
const clientTurnId = this.createId('turn');
|
|
1463
|
-
const
|
|
2062
|
+
const displayPrompt = typeof effectiveAction.displayPrompt === 'string'
|
|
2063
|
+
? effectiveAction.displayPrompt.trim()
|
|
2064
|
+
: '';
|
|
2065
|
+
const userMessage = this.buildMessage('user', displayPrompt || normalized, true);
|
|
1464
2066
|
this.patchState({
|
|
1465
2067
|
state: 'processing',
|
|
1466
2068
|
phase: 'contextualize',
|
|
@@ -1542,7 +2144,18 @@ class PraxisAssistantTurnController {
|
|
|
1542
2144
|
if (!handler) {
|
|
1543
2145
|
return of(this.snapshot());
|
|
1544
2146
|
}
|
|
1545
|
-
|
|
2147
|
+
let flowResult;
|
|
2148
|
+
try {
|
|
2149
|
+
flowResult = handler.call(this.flow, request);
|
|
2150
|
+
}
|
|
2151
|
+
catch (error) {
|
|
2152
|
+
this.applyResult(this.buildFlowErrorResult(error));
|
|
2153
|
+
return of(this.snapshot());
|
|
2154
|
+
}
|
|
2155
|
+
return this.toObservable(flowResult).pipe(tap((result) => this.applyResult(result)), map$1(() => this.snapshot()), catchError$1((error) => {
|
|
2156
|
+
this.applyResult(this.buildFlowErrorResult(error));
|
|
2157
|
+
return of(this.snapshot());
|
|
2158
|
+
}));
|
|
1546
2159
|
}
|
|
1547
2160
|
buildRequest(partial) {
|
|
1548
2161
|
const current = this.stateSubject.value;
|
|
@@ -1653,7 +2266,6 @@ class PraxisAssistantTurnController {
|
|
|
1653
2266
|
}
|
|
1654
2267
|
return {
|
|
1655
2268
|
...action,
|
|
1656
|
-
kind: 'clarify',
|
|
1657
2269
|
contextHints: {
|
|
1658
2270
|
...(action.contextHints ?? {}),
|
|
1659
2271
|
pendingClarification: current.pendingClarification,
|
|
@@ -1681,7 +2293,7 @@ class PraxisAssistantTurnController {
|
|
|
1681
2293
|
return lastUserMessage?.text ?? '';
|
|
1682
2294
|
}
|
|
1683
2295
|
resolveMessageRole(result) {
|
|
1684
|
-
return result.state === 'error' ? 'error' :
|
|
2296
|
+
return result.state === 'error' ? 'error' : 'assistant';
|
|
1685
2297
|
}
|
|
1686
2298
|
resolvePhase(result) {
|
|
1687
2299
|
if (result.state === 'clarification')
|
|
@@ -1737,8 +2349,215 @@ class PraxisAssistantTurnController {
|
|
|
1737
2349
|
toObservable(value) {
|
|
1738
2350
|
return isObservable(value) ? value : from(value);
|
|
1739
2351
|
}
|
|
2352
|
+
buildFlowErrorResult(error) {
|
|
2353
|
+
const detail = this.describeFlowError(error);
|
|
2354
|
+
const message = detail
|
|
2355
|
+
? `Nao consegui concluir este pedido. ${detail}`
|
|
2356
|
+
: 'Nao consegui concluir este pedido. Tente novamente ou revise o contexto ativo.';
|
|
2357
|
+
return {
|
|
2358
|
+
state: 'error',
|
|
2359
|
+
phase: this.stateSubject.value.phase,
|
|
2360
|
+
assistantMessage: message,
|
|
2361
|
+
errorText: message,
|
|
2362
|
+
canApply: this.stateSubject.value.canApply,
|
|
2363
|
+
diagnostics: {
|
|
2364
|
+
error: this.serializeFlowError(error),
|
|
2365
|
+
},
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
describeFlowError(error) {
|
|
2369
|
+
const record = this.toRecord(error);
|
|
2370
|
+
const status = this.toNumber(record?.['status']);
|
|
2371
|
+
const statusText = this.toString(record?.['statusText']);
|
|
2372
|
+
const message = this.toString(record?.['message']);
|
|
2373
|
+
if (status || statusText) {
|
|
2374
|
+
return `A chamada falhou${status ? ` com HTTP ${status}` : ''}${statusText ? ` (${statusText})` : ''}.`;
|
|
2375
|
+
}
|
|
2376
|
+
if (message) {
|
|
2377
|
+
return message;
|
|
2378
|
+
}
|
|
2379
|
+
return '';
|
|
2380
|
+
}
|
|
2381
|
+
serializeFlowError(error) {
|
|
2382
|
+
const record = this.toRecord(error);
|
|
2383
|
+
if (!record) {
|
|
2384
|
+
return { message: String(error ?? 'unknown') };
|
|
2385
|
+
}
|
|
2386
|
+
return {
|
|
2387
|
+
name: this.toString(record['name']) || undefined,
|
|
2388
|
+
message: this.toString(record['message']) || undefined,
|
|
2389
|
+
status: this.toNumber(record['status']) ?? undefined,
|
|
2390
|
+
statusText: this.toString(record['statusText']) || undefined,
|
|
2391
|
+
url: this.toString(record['url']) || undefined,
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
toRecord(value) {
|
|
2395
|
+
return value && typeof value === 'object'
|
|
2396
|
+
? value
|
|
2397
|
+
: null;
|
|
2398
|
+
}
|
|
2399
|
+
toString(value) {
|
|
2400
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
2401
|
+
}
|
|
2402
|
+
toNumber(value) {
|
|
2403
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
2404
|
+
}
|
|
1740
2405
|
}
|
|
1741
2406
|
|
|
2407
|
+
class PraxisAssistantSessionRegistryService {
|
|
2408
|
+
sessionsState = signal([], ...(ngDevMode ? [{ debugName: "sessionsState" }] : []));
|
|
2409
|
+
sessions = this.sessionsState.asReadonly();
|
|
2410
|
+
activeSession = computed(() => this.sessions().find((session) => session.visibility === 'active') ?? null, ...(ngDevMode ? [{ debugName: "activeSession" }] : []));
|
|
2411
|
+
minimizedSessions = computed(() => this.sessions().filter((session) => session.visibility === 'minimized'), ...(ngDevMode ? [{ debugName: "minimizedSessions" }] : []));
|
|
2412
|
+
upsertSession(descriptor) {
|
|
2413
|
+
const normalized = this.normalizeDescriptor(descriptor);
|
|
2414
|
+
const existing = this.sessionsState().find((session) => session.id === normalized.id);
|
|
2415
|
+
const now = normalized.updatedAt || new Date().toISOString();
|
|
2416
|
+
const next = {
|
|
2417
|
+
...existing,
|
|
2418
|
+
id: normalized.id,
|
|
2419
|
+
ownerId: normalized.ownerId,
|
|
2420
|
+
ownerType: normalized.ownerType,
|
|
2421
|
+
title: normalized.title,
|
|
2422
|
+
summary: normalized.summary,
|
|
2423
|
+
mode: normalized.mode,
|
|
2424
|
+
state: normalized.state,
|
|
2425
|
+
visibility: normalized.visibility,
|
|
2426
|
+
contextItems: normalized.contextItems,
|
|
2427
|
+
contextSnapshot: normalized.contextSnapshot,
|
|
2428
|
+
badge: normalized.badge,
|
|
2429
|
+
icon: normalized.icon,
|
|
2430
|
+
presence: normalized.presence,
|
|
2431
|
+
createdAt: existing?.createdAt ?? now,
|
|
2432
|
+
updatedAt: now,
|
|
2433
|
+
};
|
|
2434
|
+
this.sessionsState.update((sessions) => this.sortSessions([
|
|
2435
|
+
...sessions.filter((session) => session.id !== next.id),
|
|
2436
|
+
next,
|
|
2437
|
+
], next.visibility === 'active' ? next.id : null));
|
|
2438
|
+
return next;
|
|
2439
|
+
}
|
|
2440
|
+
upsertContextSession(contextSnapshot, descriptor = {}) {
|
|
2441
|
+
const normalizedContext = normalizePraxisAssistantContextSnapshot(contextSnapshot);
|
|
2442
|
+
const identity = normalizedContext.identity;
|
|
2443
|
+
return this.upsertSession({
|
|
2444
|
+
...descriptor,
|
|
2445
|
+
id: identity.sessionId,
|
|
2446
|
+
ownerId: identity.ownerId,
|
|
2447
|
+
ownerType: identity.ownerType,
|
|
2448
|
+
title: descriptor.title?.trim()
|
|
2449
|
+
|| normalizedContext.target?.label
|
|
2450
|
+
|| identity.componentId
|
|
2451
|
+
|| identity.ownerType
|
|
2452
|
+
|| 'Praxis assistant',
|
|
2453
|
+
mode: descriptor.mode || normalizedContext.mode,
|
|
2454
|
+
contextSnapshot: normalizedContext,
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
openSession(sessionId) {
|
|
2458
|
+
return this.setVisibility(sessionId, 'active');
|
|
2459
|
+
}
|
|
2460
|
+
openContextSession(identity) {
|
|
2461
|
+
return this.openSession(this.resolveSessionId(identity));
|
|
2462
|
+
}
|
|
2463
|
+
minimizeSession(sessionId) {
|
|
2464
|
+
return this.setVisibility(sessionId, 'minimized');
|
|
2465
|
+
}
|
|
2466
|
+
minimizeContextSession(identity) {
|
|
2467
|
+
return this.minimizeSession(this.resolveSessionId(identity));
|
|
2468
|
+
}
|
|
2469
|
+
removeSession(sessionId) {
|
|
2470
|
+
this.sessionsState.update((sessions) => sessions.filter((session) => session.id !== sessionId));
|
|
2471
|
+
}
|
|
2472
|
+
removeContextSession(identity) {
|
|
2473
|
+
this.removeSession(this.resolveSessionId(identity));
|
|
2474
|
+
}
|
|
2475
|
+
getSession(sessionId) {
|
|
2476
|
+
return this.sessionsState().find((session) => session.id === sessionId) ?? null;
|
|
2477
|
+
}
|
|
2478
|
+
getContextSession(identity) {
|
|
2479
|
+
return this.getSession(this.resolveSessionId(identity));
|
|
2480
|
+
}
|
|
2481
|
+
clear() {
|
|
2482
|
+
this.sessionsState.set([]);
|
|
2483
|
+
}
|
|
2484
|
+
setVisibility(sessionId, visibility) {
|
|
2485
|
+
const session = this.getSession(sessionId);
|
|
2486
|
+
if (!session)
|
|
2487
|
+
return null;
|
|
2488
|
+
return this.upsertSession({ ...session, visibility });
|
|
2489
|
+
}
|
|
2490
|
+
resolveSessionId(identity) {
|
|
2491
|
+
if (typeof identity === 'string')
|
|
2492
|
+
return identity;
|
|
2493
|
+
if ('identity' in identity)
|
|
2494
|
+
return identity.identity.sessionId;
|
|
2495
|
+
return identity.sessionId;
|
|
2496
|
+
}
|
|
2497
|
+
normalizeDescriptor(descriptor) {
|
|
2498
|
+
const id = descriptor.id?.trim();
|
|
2499
|
+
const ownerId = descriptor.ownerId?.trim();
|
|
2500
|
+
const ownerType = descriptor.ownerType?.trim();
|
|
2501
|
+
if (!id || !ownerId || !ownerType) {
|
|
2502
|
+
throw new Error('Praxis assistant sessions require id, ownerId and ownerType.');
|
|
2503
|
+
}
|
|
2504
|
+
const contextSnapshot = descriptor.contextSnapshot
|
|
2505
|
+
? normalizePraxisAssistantContextSnapshot(descriptor.contextSnapshot)
|
|
2506
|
+
: null;
|
|
2507
|
+
if (contextSnapshot) {
|
|
2508
|
+
this.assertContextIdentity(id, ownerId, ownerType, contextSnapshot);
|
|
2509
|
+
}
|
|
2510
|
+
const contextItems = descriptor.contextItems
|
|
2511
|
+
? [...descriptor.contextItems]
|
|
2512
|
+
: this.toShellContextItems(contextSnapshot);
|
|
2513
|
+
return {
|
|
2514
|
+
id,
|
|
2515
|
+
ownerId,
|
|
2516
|
+
ownerType,
|
|
2517
|
+
title: descriptor.title?.trim() || 'Praxis assistant',
|
|
2518
|
+
summary: descriptor.summary?.trim() || '',
|
|
2519
|
+
mode: descriptor.mode || 'chat',
|
|
2520
|
+
state: descriptor.state || 'idle',
|
|
2521
|
+
visibility: descriptor.visibility || 'minimized',
|
|
2522
|
+
contextItems,
|
|
2523
|
+
contextSnapshot,
|
|
2524
|
+
badge: descriptor.badge?.trim() || '',
|
|
2525
|
+
icon: descriptor.icon?.trim() || '',
|
|
2526
|
+
presence: descriptor.presence || 'global-dock',
|
|
2527
|
+
updatedAt: descriptor.updatedAt?.trim() || null,
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
assertContextIdentity(id, ownerId, ownerType, contextSnapshot) {
|
|
2531
|
+
const identity = contextSnapshot.identity;
|
|
2532
|
+
if (identity.sessionId !== id || identity.ownerId !== ownerId || identity.ownerType !== ownerType) {
|
|
2533
|
+
throw new Error('Praxis assistant session context identity must match id, ownerId and ownerType.');
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
toShellContextItems(contextSnapshot) {
|
|
2537
|
+
if (!contextSnapshot)
|
|
2538
|
+
return [];
|
|
2539
|
+
return contextSnapshot.contextItems.map((item) => ({
|
|
2540
|
+
id: item.id,
|
|
2541
|
+
label: item.label,
|
|
2542
|
+
value: item.value,
|
|
2543
|
+
kind: item.kind,
|
|
2544
|
+
}));
|
|
2545
|
+
}
|
|
2546
|
+
sortSessions(sessions, activeSessionId) {
|
|
2547
|
+
return sessions
|
|
2548
|
+
.map((session) => activeSessionId && session.id !== activeSessionId
|
|
2549
|
+
? { ...session, visibility: 'minimized' }
|
|
2550
|
+
: session)
|
|
2551
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
2552
|
+
}
|
|
2553
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAssistantSessionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2554
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAssistantSessionRegistryService, providedIn: 'root' });
|
|
2555
|
+
}
|
|
2556
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAssistantSessionRegistryService, decorators: [{
|
|
2557
|
+
type: Injectable,
|
|
2558
|
+
args: [{ providedIn: 'root' }]
|
|
2559
|
+
}] });
|
|
2560
|
+
|
|
1742
2561
|
const HISTORY_INDEX_PREFIX = 'praxis.ai.history.index';
|
|
1743
2562
|
const HISTORY_SESSION_PREFIX = 'praxis.ai.history.session';
|
|
1744
2563
|
const DEFAULT_SESSION_TITLE = 'Nova sessão';
|
|
@@ -2120,6 +2939,7 @@ class PraxisAiAssistantComponent {
|
|
|
2120
2939
|
adapter;
|
|
2121
2940
|
riskPolicy = null;
|
|
2122
2941
|
allowManualPatchEdit = false;
|
|
2942
|
+
hasBackdrop = false;
|
|
2123
2943
|
overlayPositions = [
|
|
2124
2944
|
{
|
|
2125
2945
|
originX: 'end',
|
|
@@ -2406,6 +3226,7 @@ class PraxisAiAssistantComponent {
|
|
|
2406
3226
|
const componentType = this.resolveComponentType();
|
|
2407
3227
|
this.currentComponentId = componentId || null;
|
|
2408
3228
|
this.currentComponentType = componentType || null;
|
|
3229
|
+
await this.adapter.prepareAuthoringContext?.();
|
|
2409
3230
|
const configRoot = this.resolveConfigRoot(this.adapter.getCurrentConfig?.());
|
|
2410
3231
|
const dataProfile = this.adapter.getDataProfile ? this.adapter.getDataProfile() : null;
|
|
2411
3232
|
const schemaFields = this.adapter.getSchemaFields ? this.adapter.getSchemaFields() : undefined;
|
|
@@ -2436,7 +3257,7 @@ class PraxisAiAssistantComponent {
|
|
|
2436
3257
|
: undefined;
|
|
2437
3258
|
const normalizedRuntimeState = runtimeState !== undefined ? this.toAiJsonObject(runtimeState) : undefined;
|
|
2438
3259
|
const normalizedSuggestedPatch = suggestion?.patch ? this.toAiJsonObject(suggestion.patch) : undefined;
|
|
2439
|
-
const normalizedContextHints = this.
|
|
3260
|
+
const normalizedContextHints = this.enrichDomainCatalogAuthoringHints(mergedContextHints);
|
|
2440
3261
|
const patchRequest = {
|
|
2441
3262
|
componentId,
|
|
2442
3263
|
componentType,
|
|
@@ -2468,7 +3289,7 @@ class PraxisAiAssistantComponent {
|
|
|
2468
3289
|
}
|
|
2469
3290
|
result = this.compileAdapterResponse(result);
|
|
2470
3291
|
keepContextHints = result?.type === 'clarification';
|
|
2471
|
-
const handled = this.handlePatchResponse(result, configRoot, componentId, componentType);
|
|
3292
|
+
const handled = this.handlePatchResponse(result, configRoot, componentId, componentType, prompt);
|
|
2472
3293
|
if (handled) {
|
|
2473
3294
|
this.appendHistoryAssistantResponse(result, componentId, componentType);
|
|
2474
3295
|
return;
|
|
@@ -4337,14 +5158,18 @@ class PraxisAiAssistantComponent {
|
|
|
4337
5158
|
return null;
|
|
4338
5159
|
}
|
|
4339
5160
|
if (event.type === 'heartbeat') {
|
|
5161
|
+
if (message) {
|
|
5162
|
+
this.aiExplanation = message;
|
|
5163
|
+
}
|
|
4340
5164
|
this.streamTerminalState = 'in_progress';
|
|
4341
5165
|
this.processingInfoVisible = true;
|
|
5166
|
+
this.setState('processing');
|
|
4342
5167
|
return null;
|
|
4343
5168
|
}
|
|
4344
5169
|
if (event.type === 'result') {
|
|
4345
5170
|
this.streamTerminalState = 'completed';
|
|
4346
5171
|
this.processingInfoVisible = false;
|
|
4347
|
-
const response = this.asRecord(payload?.['response']);
|
|
5172
|
+
const response = this.asRecord(payload?.['response']) ?? payload;
|
|
4348
5173
|
if (!response) {
|
|
4349
5174
|
return {
|
|
4350
5175
|
type: 'error',
|
|
@@ -4433,6 +5258,10 @@ class PraxisAiAssistantComponent {
|
|
|
4433
5258
|
if (typeof direct === 'string' && direct.trim()) {
|
|
4434
5259
|
return direct.trim();
|
|
4435
5260
|
}
|
|
5261
|
+
const summary = payload['summary'];
|
|
5262
|
+
if (typeof summary === 'string' && summary.trim()) {
|
|
5263
|
+
return summary.trim();
|
|
5264
|
+
}
|
|
4436
5265
|
const nested = this.asRecord(payload['error']);
|
|
4437
5266
|
const nestedMsg = nested?.['message'];
|
|
4438
5267
|
if (typeof nestedMsg === 'string' && nestedMsg.trim()) {
|
|
@@ -4524,7 +5353,7 @@ class PraxisAiAssistantComponent {
|
|
|
4524
5353
|
warnings: warnings.length ? warnings : undefined,
|
|
4525
5354
|
};
|
|
4526
5355
|
}
|
|
4527
|
-
handlePatchResponse(result, configRoot, componentId, componentType) {
|
|
5356
|
+
handlePatchResponse(result, configRoot, componentId, componentType, requestPrompt) {
|
|
4528
5357
|
if (!result) {
|
|
4529
5358
|
this.streamTerminalState = 'failed';
|
|
4530
5359
|
this.errorMsg = 'Resposta vazia da IA.';
|
|
@@ -4546,12 +5375,12 @@ class PraxisAiAssistantComponent {
|
|
|
4546
5375
|
const questions = result.questions?.filter((q) => !!q && q.trim().length > 0) || [];
|
|
4547
5376
|
if (questions.length && (!result.options || result.options.length === 0)
|
|
4548
5377
|
&& (!result.optionPayloads || result.optionPayloads.length === 0)) {
|
|
4549
|
-
this.enterClarificationWithQuestions(result.message, questions, configRoot, componentId, componentType);
|
|
5378
|
+
this.enterClarificationWithQuestions(result.message, questions, configRoot, componentId, componentType, requestPrompt);
|
|
4550
5379
|
this.applyClarificationUi(result);
|
|
4551
5380
|
return true;
|
|
4552
5381
|
}
|
|
4553
5382
|
const enriched = this.enrichClarification(result, configRoot, componentId, componentType);
|
|
4554
|
-
this.enterClarification(enriched.message, enriched.options, configRoot, componentId, componentType,
|
|
5383
|
+
this.enterClarification(enriched.message, enriched.options, configRoot, componentId, componentType, requestPrompt, enriched.optionPayloads);
|
|
4555
5384
|
this.applyClarificationUi(enriched);
|
|
4556
5385
|
return true;
|
|
4557
5386
|
}
|
|
@@ -5024,10 +5853,10 @@ class PraxisAiAssistantComponent {
|
|
|
5024
5853
|
}
|
|
5025
5854
|
this.cdr.markForCheck();
|
|
5026
5855
|
}
|
|
5027
|
-
enterClarificationWithQuestions(message, questions, configRoot, componentId, componentType) {
|
|
5856
|
+
enterClarificationWithQuestions(message, questions, configRoot, componentId, componentType, basePrompt) {
|
|
5028
5857
|
this.setState('clarification');
|
|
5029
5858
|
this.clarificationMessage = message || 'Preciso de mais detalhes para continuar.';
|
|
5030
|
-
const base = (this.userPrompt
|
|
5859
|
+
const base = (basePrompt ?? this.userPrompt).trim();
|
|
5031
5860
|
this.clarificationBasePrompt = base || null;
|
|
5032
5861
|
this.userPrompt = '';
|
|
5033
5862
|
this.activeContextHints = null;
|
|
@@ -5138,15 +5967,15 @@ class PraxisAiAssistantComponent {
|
|
|
5138
5967
|
.filter((value) => value.length > 0);
|
|
5139
5968
|
}
|
|
5140
5969
|
resolveBadgeContextHints(contextHints) {
|
|
5141
|
-
|
|
5970
|
+
const candidate = this.toAiJsonObject(contextHints);
|
|
5971
|
+
if (!Object.keys(candidate).length)
|
|
5142
5972
|
return null;
|
|
5143
|
-
const badge = this.asRecord(
|
|
5973
|
+
const badge = this.asRecord(candidate['badge']);
|
|
5144
5974
|
if (badge)
|
|
5145
5975
|
return this.toAiJsonObject(badge);
|
|
5146
|
-
const candidate = contextHints;
|
|
5147
5976
|
const hasBadgeKeys = ['field', 'values', 'valueColorMap', 'palette', 'inferredType', 'explicitType']
|
|
5148
5977
|
.some((key) => Object.prototype.hasOwnProperty.call(candidate, key));
|
|
5149
|
-
return hasBadgeKeys ? candidate : null;
|
|
5978
|
+
return hasBadgeKeys ? this.toAiJsonObject(candidate) : null;
|
|
5150
5979
|
}
|
|
5151
5980
|
hasBadgeValues(badgeHints) {
|
|
5152
5981
|
if (!badgeHints)
|
|
@@ -5190,6 +6019,25 @@ class PraxisAiAssistantComponent {
|
|
|
5190
6019
|
}
|
|
5191
6020
|
return this.toClarificationContextHints(merged) ?? null;
|
|
5192
6021
|
}
|
|
6022
|
+
enrichDomainCatalogAuthoringHints(value) {
|
|
6023
|
+
const normalized = this.toClarificationContextHints(value);
|
|
6024
|
+
if (!normalized)
|
|
6025
|
+
return undefined;
|
|
6026
|
+
const domainCatalog = this.asRecord(normalized['domainCatalog']);
|
|
6027
|
+
const recommendedAuthoringFlow = domainCatalog?.['recommendedAuthoringFlow'];
|
|
6028
|
+
if (typeof recommendedAuthoringFlow !== 'string' || !recommendedAuthoringFlow.trim()) {
|
|
6029
|
+
return normalized;
|
|
6030
|
+
}
|
|
6031
|
+
const flowId = recommendedAuthoringFlow.trim();
|
|
6032
|
+
const enriched = this.toAiJsonObject(normalized);
|
|
6033
|
+
enriched['authoringFlow'] = this.toAiJsonObject({
|
|
6034
|
+
flowId,
|
|
6035
|
+
source: 'domainCatalog.recommendedAuthoringFlow',
|
|
6036
|
+
reviewRequired: true,
|
|
6037
|
+
materializeOnlyAfterReview: true,
|
|
6038
|
+
});
|
|
6039
|
+
return this.toClarificationContextHints(enriched);
|
|
6040
|
+
}
|
|
5193
6041
|
setResourcePathHint(resourcePath) {
|
|
5194
6042
|
const raw = (resourcePath || '').trim();
|
|
5195
6043
|
const normalized = this.normalizeResourcePath(raw);
|
|
@@ -5821,7 +6669,7 @@ class PraxisAiAssistantComponent {
|
|
|
5821
6669
|
return 'Erro ao comunicar com a IA. Tente novamente.';
|
|
5822
6670
|
}
|
|
5823
6671
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5824
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisAiAssistantComponent, isStandalone: true, selector: "praxis-ai-assistant", inputs: { adapter: "adapter", riskPolicy: "riskPolicy", allowManualPatchEdit: "allowManualPatchEdit" }, viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: CdkOverlayOrigin, descendants: true }, { propertyName: "triggerButton", first: true, predicate: ["triggerBtn"], descendants: true, read: ElementRef }, { propertyName: "inputElement", first: true, predicate: ["inputEl"], descendants: true }], ngImport: i0, template: "<!-- Trigger Button -->\n<button \n mat-icon-button \n cdkOverlayOrigin \n #trigger=\"cdkOverlayOrigin\"\n #triggerBtn\n (click)=\"open()\"\n [disabled]=\"isOpen\"\n class=\"ai-trigger-btn\"\n matTooltip=\"Assistente de Configura\u00E7\u00E3o\"\n aria-label=\"Abrir Assistente IA\">\n <mat-icon>auto_awesome</mat-icon>\n</button>\n\n<!-- Overlay Template -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayViewportMargin]=\"12\"\n cdkConnectedOverlayPanelClass=\"ai-assistant-overlay-pane\"\n cdkConnectedOverlayBackdropClass=\"ai-assistant-backdrop\"\n (backdropClick)=\"close()\"\n (detach)=\"close()\">\n\n <div\n class=\"ai-assistant-panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Assistente de Configura\u00E7\u00E3o\"\n [attr.aria-busy]=\"isBusyState() ? 'true' : null\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n (keydown)=\"onKeydown($event)\"\n >\n \n <!-- HEADER -->\n <div class=\"assistant-header\">\n <div class=\"assistant-header__left\">\n <mat-icon class=\"magic-icon\">auto_awesome</mat-icon>\n <div class=\"assistant-title-group\">\n <div class=\"assistant-title\">Assistente de Configura\u00E7\u00E3o</div>\n <div class=\"assistant-subtitle\">Copiloto contextual para ajustes guiados</div>\n <div class=\"assistant-header-chips\">\n <span\n class=\"mode-chip\"\n [class.mock]=\"mockMode\"\n [matTooltip]=\"mockMode ? 'Sem chave de API: respostas de demonstra\u00E7\u00E3o' : 'Conectado ao assistente configurado'\"\n >\n {{ mockMode ? 'Mock' : 'Conectado' }}\n </span>\n <span\n class=\"policy-chip\"\n [class.strict]=\"isStrictRiskPolicy()\"\n [matTooltip]=\"getRiskPolicyTooltip()\"\n >\n {{ getRiskPolicyLabel() }}\n </span>\n </div>\n </div>\n </div>\n <div class=\"assistant-header__right\">\n <button\n mat-icon-button\n type=\"button\"\n class=\"assistant-close-btn\"\n (click)=\"close()\"\n aria-label=\"Fechar assistente\"\n matTooltip=\"Fechar assistente\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n <div\n class=\"assistant-status\"\n [class.processing]=\"isBusyState()\"\n [class.pending]=\"state === 'clarification'\"\n [class.warning]=\"state === 'error'\"\n [class.success]=\"state === 'review' || state === 'success'\"\n [class.compact]=\"!shouldShowSystemStatusDetail()\"\n role=\"status\"\n [attr.aria-live]=\"getSystemStatusAriaLive()\"\n aria-atomic=\"true\"\n >\n <span class=\"assistant-status-dot\" aria-hidden=\"true\"></span>\n <div class=\"assistant-status-content\">\n <div class=\"assistant-status-label\">\n <span class=\"assistant-status-label-prefix\">Status:</span>\n <span>{{ getSystemStatusLabel() }}</span>\n <span *ngIf=\"shouldShowSnapshotFallbackBadge()\" class=\"assistant-status-mode\">Snapshot</span>\n </div>\n <div *ngIf=\"shouldShowSystemStatusDetail()\" class=\"assistant-status-detail\">{{ getSystemStatusDetail() }}</div>\n </div>\n </div>\n <div class=\"assistant-flow\" *ngIf=\"shouldShowTaskFlow()\" role=\"list\" aria-label=\"Fluxo da proposta\">\n <div\n class=\"flow-step\"\n role=\"listitem\"\n *ngFor=\"let step of flowSteps\"\n [class.active]=\"getFlowStepState(step.step) === 'active'\"\n [class.done]=\"getFlowStepState(step.step) === 'done'\"\n >\n <span class=\"flow-step-index\">{{ step.step }}</span>\n <span class=\"flow-step-content\">\n <span class=\"flow-step-label\">{{ step.label }}</span>\n <span class=\"flow-step-detail\">{{ getFlowStepDetail(step.step) }}</span>\n </span>\n </div>\n </div>\n <div class=\"assistant-nav\">\n <div\n class=\"assistant-tabs\"\n role=\"tablist\"\n aria-label=\"Se\u00E7\u00F5es do assistente\"\n (keydown)=\"onTabsKeydown($event)\"\n >\n <button\n class=\"assistant-tab\"\n type=\"button\"\n *ngIf=\"isTaskMode()\"\n id=\"assistant-tab-task\"\n role=\"tab\"\n aria-label=\"Proposta atual\"\n [attr.aria-selected]=\"isActiveTab('task')\"\n aria-controls=\"assistant-panel-task\"\n [attr.tabindex]=\"isActiveTab('task') ? 0 : -1\"\n [class.active]=\"isActiveTab('task')\"\n (click)=\"setActiveTab('task')\"\n >\n Proposta\n <span *ngIf=\"hasPendingClarification() && !isActiveTab('task')\" class=\"assistant-tab-badge\">1</span>\n </button>\n <button\n class=\"assistant-tab\"\n type=\"button\"\n id=\"assistant-tab-chat\"\n role=\"tab\"\n aria-label=\"Hist\u00F3rico\"\n [attr.aria-selected]=\"isActiveTab('chat')\"\n aria-controls=\"assistant-panel-chat\"\n [attr.tabindex]=\"isActiveTab('chat') ? 0 : -1\"\n [class.active]=\"isActiveTab('chat')\"\n (click)=\"setActiveTab('chat')\"\n >\n Hist\u00F3rico\n </button>\n <button\n class=\"assistant-tab\"\n type=\"button\"\n id=\"assistant-tab-suggestions\"\n role=\"tab\"\n aria-label=\"Sugest\u00F5es de melhoria\"\n [attr.aria-selected]=\"isActiveTab('suggestions')\"\n aria-controls=\"assistant-panel-suggestions\"\n [attr.tabindex]=\"isActiveTab('suggestions') ? 0 : -1\"\n [class.active]=\"isActiveTab('suggestions')\"\n (click)=\"setActiveTab('suggestions')\"\n >\n Sugest\u00F5es\n </button>\n </div>\n </div>\n\n <!-- BODY: Dynamic Content based on State -->\n <div class=\"assistant-body\">\n <div class=\"assistant-thought assistant-section\" *ngIf=\"shouldShowThoughtCard()\">\n <div class=\"assistant-thought-meta\">\n <mat-icon>psychology</mat-icon>\n <span>{{ getThoughtTimingLabel() }}</span>\n </div>\n <div class=\"assistant-thought-summary\">{{ getThoughtSummary() }}</div>\n <div class=\"assistant-thought-plan\">\n <div class=\"assistant-thought-plan-title\">{{ getThoughtPlanTitle() }}</div>\n <div class=\"assistant-thought-plan-actions\">\n <button\n mat-stroked-button\n type=\"button\"\n class=\"thought-action-details\"\n (click)=\"openThoughtDetails()\"\n [attr.aria-label]=\"getThoughtDetailsLabel()\"\n [matTooltip]=\"getThoughtDetailsTooltip()\"\n >\n {{ getThoughtDetailsLabel() }}\n </button>\n <button\n *ngIf=\"shouldShowThoughtPreviewAction()\"\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n class=\"thought-action-preview\"\n (click)=\"openThoughtPreview()\"\n [matTooltip]=\"getThoughtPreviewTooltip()\"\n >\n Pr\u00E9via\n </button>\n </div>\n <div class=\"assistant-thought-plan-hint\">{{ getThoughtActionHint() }}</div>\n <ng-container *ngIf=\"getThoughtChecklist() as thoughtChecklist\">\n <div class=\"assistant-thought-checklist\" *ngIf=\"thoughtChecklist.length\">\n <div *ngFor=\"let item of thoughtChecklist\" class=\"assistant-thought-checklist-item\">\n <mat-icon>radio_button_unchecked</mat-icon>\n <span>{{ item }}</span>\n </div>\n </div>\n </ng-container>\n </div>\n </div>\n <div *ngIf=\"processingInfoVisible\" class=\"processing-banner\">\n <mat-spinner diameter=\"16\"></mat-spinner>\n <span>{{ aiExplanation || 'Analisando solicita\u00E7\u00E3o e preparando proposta...' }}</span>\n <button mat-button type=\"button\" class=\"processing-retry\" (click)=\"retryProcessing()\">Tentar novamente</button>\n </div>\n <div\n class=\"assistant-history assistant-section\"\n *ngIf=\"historyContext && isActiveTab('chat')\"\n id=\"assistant-panel-chat\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-chat\"\n >\n <div class=\"section-header\">\n <div class=\"section-heading\">\n <div class=\"section-title\">Hist\u00F3rico</div>\n <div class=\"section-subtitle\">Sess\u00F5es recentes, pedidos reaproveit\u00E1veis e desfazer r\u00E1pido.</div>\n </div>\n <div class=\"history-actions\" role=\"group\" aria-label=\"A\u00E7\u00F5es do hist\u00F3rico\">\n <button\n mat-stroked-button\n type=\"button\"\n class=\"history-action-btn\"\n (click)=\"historyExpanded = !historyExpanded\"\n [matTooltip]=\"historyExpanded ? 'Recolher hist\u00F3rico' : 'Expandir hist\u00F3rico'\"\n [attr.aria-label]=\"historyExpanded ? 'Recolher hist\u00F3rico' : 'Expandir hist\u00F3rico'\"\n [disabled]=\"!historyWarnings.length && !historySessions.length && !activeHistoryMessages.length\"\n >\n <mat-icon>{{ historyExpanded ? 'expand_less' : 'expand_more' }}</mat-icon>\n <span>{{ historyExpanded ? 'Recolher' : 'Expandir' }}</span>\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n class=\"history-action-btn\"\n (click)=\"startNewSession()\"\n matTooltip=\"Nova conversa\"\n aria-label=\"Nova conversa\"\n [disabled]=\"isBusyState()\"\n >\n <mat-icon>add_comment</mat-icon>\n <span>Nova conversa</span>\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"clearHistory()\"\n matTooltip=\"Limpar hist\u00F3rico local\"\n aria-label=\"Limpar hist\u00F3rico local\"\n class=\"history-action-btn history-action-btn--danger\"\n [disabled]=\"!historySessions.length\"\n >\n <mat-icon>delete_outline</mat-icon>\n <span>Limpar</span>\n </button>\n </div>\n </div>\n <div *ngIf=\"historyUndoDeleteSession\" class=\"history-undo\">\n <span>Sess\u00E3o removida.</span>\n <button mat-button type=\"button\" (click)=\"undoRemoveHistorySession()\">\n Desfazer\n </button>\n </div>\n\n <div *ngIf=\"historyExpanded && historyWarnings.length\" class=\"history-warnings\">\n <mat-icon>info</mat-icon>\n <div class=\"history-warnings-list\">\n <div *ngFor=\"let warning of historyWarnings\">{{ warning }}</div>\n <div class=\"history-warnings-hint\">\n Configure headers via API_CONFIG_STORAGE_OPTIONS no host.\n </div>\n </div>\n </div>\n\n <div *ngIf=\"historyExpanded && historySessions.length\" class=\"history-sessions\">\n <div\n class=\"history-session\"\n *ngFor=\"let session of historySessions\"\n role=\"button\"\n tabindex=\"0\"\n (click)=\"selectHistorySession(session.id)\"\n (keydown)=\"onHistorySessionCardKeydown($event, session.id)\"\n [class.active]=\"session.id === activeHistorySession?.id\"\n [matTooltip]=\"getHistorySessionTooltip(session)\"\n [matTooltipDisabled]=\"!session.componentType && !session.componentId\"\n >\n <div class=\"history-session-main\">\n <span class=\"history-session-title\">{{ session.title }}</span>\n <div class=\"history-session-main-right\">\n <span class=\"history-session-time\">{{ session.updatedAt | date:'short' }}</span>\n <div class=\"history-session-tools\">\n <button\n mat-icon-button\n type=\"button\"\n class=\"history-session-tool\"\n (click)=\"reuseHistorySessionPrompt(session.id, $event)\"\n matTooltip=\"Reusar \u00FAltimo pedido\"\n aria-label=\"Reusar \u00FAltimo pedido\"\n >\n <mat-icon>edit_note</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n class=\"history-session-tool history-session-tool--danger\"\n (click)=\"removeHistorySession(session.id, $event)\"\n matTooltip=\"Excluir sess\u00E3o\"\n aria-label=\"Excluir sess\u00E3o\"\n >\n <mat-icon>delete_outline</mat-icon>\n </button>\n </div>\n </div>\n </div>\n <div class=\"history-session-meta\">\n <span *ngIf=\"session.componentType\" class=\"history-chip\">{{ session.componentType }}</span>\n <span *ngIf=\"session.componentId\" class=\"history-chip\">{{ session.componentId }}</span>\n </div>\n </div>\n </div>\n <div *ngIf=\"historyExpanded && !historySessions.length\" class=\"history-empty\">\n Nenhuma sess\u00E3o salva ainda.\n </div>\n\n <div *ngIf=\"historyExpanded && activeHistoryMessages.length\" class=\"history-messages\">\n <div\n *ngIf=\"activeHistoryTotalMessages > activeHistoryMessages.length\"\n class=\"history-messages-hint\"\n >\n Mostrando \u00FAltimas {{ activeHistoryMessages.length }} de {{ activeHistoryTotalMessages }} mensagens.\n </div>\n <div\n *ngFor=\"let msg of activeHistoryMessages\"\n class=\"history-message\"\n [class.user]=\"msg.role === 'user'\"\n [class.assistant]=\"msg.role === 'assistant'\"\n >\n <div class=\"history-message-header\">\n <span class=\"history-message-role\">{{ msg.role === 'user' ? 'Voc\u00EA' : 'Assistente' }}</span>\n <span class=\"history-message-time\">{{ msg.createdAt | date:'shortTime' }}</span>\n <span\n *ngIf=\"msg.context?.usedRag\"\n class=\"history-rag\"\n matTooltip=\"Resposta baseada em contexto recuperado (RAG)\"\n >RAG</span>\n </div>\n <div class=\"history-message-text\">{{ msg.text }}</div>\n </div>\n </div>\n <div *ngIf=\"historyExpanded && historySessions.length && !activeHistoryMessages.length\" class=\"history-empty history-empty--panel\">\n Selecione uma sess\u00E3o para visualizar as mensagens.\n </div>\n <div class=\"history-helper\" *ngIf=\"historyExpanded\">\n O hist\u00F3rico \u00E9 local ao usu\u00E1rio e ao componente atual.\n </div>\n </div>\n\n <div\n class=\"loading-suggestions assistant-section\"\n *ngIf=\"loadingSuggestions && isActiveTab('suggestions')\"\n id=\"assistant-panel-suggestions\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-suggestions\"\n >\n <mat-spinner diameter=\"20\"></mat-spinner>\n <span>Carregando sugest\u00F5es de melhoria...</span>\n </div>\n \n <!-- STATE: LISTENING (Suggestions) -->\n <div\n *ngIf=\"state === 'listening' && isActiveTab('suggestions')\"\n class=\"suggestions-area assistant-section\"\n id=\"assistant-panel-suggestions\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-suggestions\"\n >\n <div class=\"section-header\">\n <div class=\"section-title\">Sugest\u00F5es de melhoria</div>\n <div class=\"suggestions-actions\">\n <button mat-icon-button type=\"button\" (click)=\"refreshSuggestions()\" [disabled]=\"loadingSuggestions\" matTooltip=\"Atualizar sugest\u00F5es\">\n <mat-icon>refresh</mat-icon>\n </button>\n </div>\n </div>\n <div class=\"suggestions-hero\" *ngIf=\"!loadingSuggestions\">\n <div class=\"suggestions-hero__label\">Contexto ativo</div>\n <div class=\"suggestions-hero__title\">{{ adapter.componentName || 'Componente atual' }}</div>\n <div class=\"suggestions-hero__detail\">{{ getSystemStatusDetail() }}</div>\n </div>\n <div *ngIf=\"suggestionsWarnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of suggestionsWarnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div class=\"suggestions-content\" *ngIf=\"!loadingSuggestions && (richSuggestions.length || hasDismissedSuggestions())\">\n <div class=\"suggestions-filter\" *ngIf=\"hasDismissedSuggestions()\">\n <span>{{ getDismissedSuggestionCount() }} oculta(s)</span>\n <button mat-button type=\"button\" (click)=\"restoreDismissedSuggestions()\">\n Restaurar\n </button>\n </div>\n <div class=\"suggestions-list\" *ngIf=\"getVisibleSuggestions().length; else allSuggestionsHidden\">\n <div\n class=\"suggestion-item\"\n *ngFor=\"let sug of getVisibleSuggestions()\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'Selecionar sugest\u00E3o: ' + sug.label\"\n (click)=\"selectSuggestion(sug)\"\n (keydown)=\"onSuggestionCardKeydown($event, sug)\"\n >\n <div class=\"suggestion-copy\">\n <div class=\"suggestion-main\">\n <mat-icon *ngIf=\"sug.icon\" class=\"suggestion-icon\">{{ sug.icon }}</mat-icon>\n <span class=\"suggestion-label\">{{ sug.label }}</span>\n <span *ngIf=\"sug.group\" class=\"suggestion-group\">{{ sug.group }}</span>\n </div>\n <div *ngIf=\"sug.description\" class=\"suggestion-desc\">{{ sug.description }}</div>\n </div>\n <div class=\"suggestion-actions\">\n <span class=\"suggestion-arrow\" aria-hidden=\"true\">\n <mat-icon>chevron_right</mat-icon>\n </span>\n <button\n mat-icon-button\n type=\"button\"\n class=\"suggestion-action-btn\"\n (click)=\"prepareSuggestionPrompt(sug, $event)\"\n matTooltip=\"Refinar pedido\"\n aria-label=\"Refinar pedido\"\n >\n <mat-icon>edit</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n class=\"suggestion-action-btn suggestion-action-btn--danger\"\n (click)=\"dismissSuggestion(sug, $event)\"\n matTooltip=\"Ocultar sugest\u00E3o\"\n aria-label=\"Ocultar sugest\u00E3o\"\n >\n <mat-icon>visibility_off</mat-icon>\n </button>\n </div>\n </div>\n </div>\n <ng-template #allSuggestionsHidden>\n <div class=\"suggestions-empty suggestions-empty--inline\">\n Todas as sugest\u00F5es foram ocultadas.\n </div>\n </ng-template>\n </div>\n <div class=\"suggestions-empty\" *ngIf=\"!loadingSuggestions && !richSuggestions.length\">\n Nenhuma sugest\u00E3o dispon\u00EDvel no momento.\n </div>\n <div class=\"suggestions-helper\" *ngIf=\"!loadingSuggestions && !richSuggestions.length\">\n Selecione uma sugest\u00E3o acima ou descreva uma altera\u00E7\u00E3o no campo inferior.\n </div>\n </div>\n\n <!-- STATE: CLARIFICATION (Two-Step Flow) -->\n <div\n *ngIf=\"state === 'clarification' && isActiveTab('task')\"\n class=\"clarification-area assistant-card\"\n id=\"assistant-panel-task\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-task\"\n >\n <div class=\"task-header\" *ngIf=\"!shouldShowThoughtCard()\">\n <div class=\"task-title\">{{ getTaskTitle() }}</div>\n <div class=\"task-subtitle\">{{ getTaskSubtitle() }}</div>\n <div *ngIf=\"clarificationOptions.length > 1 && getTaskSelectionSummary() as selectionSummary\" class=\"task-meta\">\n <span class=\"task-meta-label\">Selecionado:</span>\n <span class=\"task-meta-value\">{{ selectionSummary }}</span>\n </div>\n <div *ngIf=\"shouldShowTaskSteps()\" class=\"task-steps\">\n <span class=\"task-step active\">1 Dados</span>\n <span class=\"task-step\">2 Layout</span>\n <span class=\"task-step\">3 Revis\u00E3o</span>\n </div>\n </div>\n <div *ngIf=\"clarificationResponseType === 'context'\" class=\"context-only\">\n <mat-spinner diameter=\"24\"></mat-spinner>\n <div class=\"context-only-hint\">Buscando contexto adicional...</div>\n </div>\n <ng-template #clarificationOptionContent let-opt let-index=\"index\" let-compact=\"compact\">\n <div class=\"clarification-decision-head\" *ngIf=\"!compact\">\n <span class=\"clarification-decision-index\">{{ index + 1 }}</span>\n <span class=\"clarification-decision-type\">{{ getClarificationOptionKindLabel(opt) }}</span>\n <span class=\"spacer\"></span>\n <span class=\"clarification-decision-state\" *ngIf=\"isClarificationSelected(opt)\">Selecionado</span>\n </div>\n <ng-container [ngSwitch]=\"getClarificationOptionLayout(opt)\">\n <div *ngSwitchCase=\"'endpoint'\" class=\"clarification-card\">\n <div class=\"clarification-card-header\">\n <mat-icon class=\"endpoint-icon\">{{ getEndpointIcon(opt) }}</mat-icon>\n <ng-container *ngIf=\"getEndpointMethod(opt) as method\">\n <span class=\"endpoint-method\" [attr.data-method]=\"method\">{{ method }}</span>\n </ng-container>\n <span class=\"endpoint-label\">{{ opt.label }}</span>\n <span class=\"spacer\"></span>\n <mat-icon class=\"select-indicator\">\n {{ isClarificationSelected(opt) ? 'check_circle' : 'radio_button_unchecked' }}\n </mat-icon>\n </div>\n <div class=\"clarification-card-body\">\n <ng-container *ngIf=\"getEndpointPath(opt) as path\">\n <div class=\"endpoint-path\">{{ path }}</div>\n </ng-container>\n <div\n *ngIf=\"opt.contextHints?.description\"\n class=\"endpoint-description\"\n [matTooltip]=\"getDescriptionTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowDescriptionTooltip(opt)\"\n >\n {{ opt.contextHints?.description }}\n </div>\n </div>\n </div>\n <div *ngSwitchCase=\"'color'\" class=\"clarification-color\">\n <span\n class=\"color-swatch\"\n [style.background]=\"getSafeHexColor(opt) || 'var(--md-sys-color-surface-container-highest)'\"\n ></span>\n <div class=\"color-meta\">\n <span class=\"color-label\">{{ opt.label }}</span>\n <span *ngIf=\"opt.contextHints?.hexColor\" class=\"color-value\">{{ opt.contextHints?.hexColor }}</span>\n </div>\n </div>\n <div *ngSwitchCase=\"'description'\" class=\"clarification-description\">\n <span class=\"clarification-label\">{{ opt.label }}</span>\n <span\n *ngIf=\"opt.contextHints?.description\"\n class=\"clarification-subtitle\"\n [matTooltip]=\"getDescriptionTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowDescriptionTooltip(opt)\"\n >\n {{ opt.contextHints?.description }}\n </span>\n </div>\n <span *ngSwitchDefault class=\"clarification-plain-label\">{{ opt.label }}</span>\n </ng-container>\n </ng-template>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div\n *ngIf=\"clarificationQuestions.length\"\n class=\"clarification-questions\"\n [class.attention-highlight]=\"highlightClarificationDetails && !clarificationOptions.length\"\n >\n <div *ngFor=\"let question of clarificationQuestions; let i = index\" class=\"clarification-question\">\n <div class=\"clarification-question-label\">{{ question }}</div>\n <input\n type=\"text\"\n [(ngModel)]=\"clarificationAnswers[i]\"\n [placeholder]=\"'Resposta ' + (i + 1)\"\n autocomplete=\"off\">\n </div>\n </div>\n <div\n *ngIf=\"clarificationOptions.length\"\n class=\"clarification-options-block\"\n [class.attention-highlight]=\"highlightClarificationDetails\"\n >\n <div class=\"clarification-options-title\">\n {{ clarificationOptions.length === 1 ? 'Decis\u00E3o sugerida' : 'Decis\u00F5es sugeridas' }}\n </div>\n <div *ngIf=\"clarificationOptions.length === 1\" class=\"clarification-options-hint\">\n Etapa 2 de 4: confirme a melhor op\u00E7\u00E3o para continuar.\n </div>\n <div *ngIf=\"clarificationOptions.length > 1\" class=\"clarification-options-hint\">\n Etapa 2 de 4: selecione a alternativa mais aderente para gerar a proposta.\n </div>\n <div\n *ngIf=\"clarificationResponseType === 'confirm'\"\n class=\"clarification-buttons\"\n [class.list]=\"clarificationPresentation === 'list'\"\n >\n <button\n mat-button\n type=\"button\"\n *ngFor=\"let opt of clarificationOptions; let i = index\"\n (click)=\"onClarificationOptionClick(opt)\"\n class=\"clarification-option\"\n [class.selected]=\"isClarificationSelected(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n </div>\n <div\n *ngIf=\"clarificationSelectionMode === 'multiple'\"\n class=\"clarification-buttons\"\n [class.list]=\"clarificationPresentation === 'list'\"\n >\n <button\n mat-button\n type=\"button\"\n *ngFor=\"let opt of clarificationOptions; let i = index\"\n (click)=\"onClarificationOptionClick(opt)\"\n [class.selected]=\"isClarificationSelected(opt)\"\n class=\"clarification-option\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n </div>\n <div\n *ngIf=\"clarificationResponseType !== 'confirm' && clarificationSelectionMode === 'single' && clarificationPresentation !== 'chips'\"\n class=\"clarification-buttons\"\n [class.list]=\"clarificationPresentation === 'list'\"\n >\n <button\n mat-button\n type=\"button\"\n *ngFor=\"let opt of clarificationOptions; let i = index\"\n (click)=\"onClarificationOptionClick(opt)\"\n class=\"clarification-option\"\n [class.selected]=\"isClarificationSelected(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n </div>\n <div\n *ngIf=\"clarificationResponseType !== 'confirm' && clarificationSelectionMode === 'single' && clarificationPresentation === 'chips'\"\n class=\"clarification-chips\"\n >\n <ng-container *ngFor=\"let opt of clarificationOptions; let i = index\">\n <button\n *ngIf=\"isEndpointOption(opt); else chipOption\"\n mat-button\n type=\"button\"\n class=\"clarification-option clarification-card-button\"\n (click)=\"onClarificationOptionClick(opt)\"\n [class.selected]=\"isClarificationSelected(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n <ng-template #chipOption>\n <mat-chip\n (click)=\"onClarificationOptionClick(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt)\"\n class=\"clarification-chip\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: true }\"></ng-container>\n </mat-chip>\n </ng-template>\n </ng-container>\n </div>\n </div>\n <div\n *ngIf=\"clarificationAllowCustom && clarificationResponseType !== 'context'\"\n class=\"clarification-manual-toggle\"\n >\n <span class=\"clarification-manual-label\">N\u00E3o encontrou o recurso?</span>\n <button mat-button type=\"button\" (click)=\"toggleManualInput()\">\n {{ showManualInput ? 'Ocultar resposta manual' : 'Responder manualmente' }}\n </button>\n </div>\n <div\n *ngIf=\"clarificationAllowCustom && showManualInput && clarificationResponseType !== 'context'\"\n class=\"clarification-free\"\n >\n <input\n type=\"text\"\n [(ngModel)]=\"clarificationFreeText\"\n placeholder=\"Digite sua resposta\u2026\"\n autocomplete=\"off\"\n (keydown.enter)=\"confirmTaskAction()\"\n />\n </div>\n <div\n *ngIf=\"clarificationAllowCustom && showManualInput && clarificationResponseType !== 'context'\"\n class=\"clarification-free-hint\"\n >\n Pressione Enter para enviar.\n </div>\n </div>\n\n <!-- STATE: REVIEW (Diff/Explanation) -->\n <div\n *ngIf=\"state === 'review' && isActiveTab('task')\"\n class=\"review-area assistant-card\"\n id=\"assistant-panel-task\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-task\"\n >\n <div class=\"task-header\" *ngIf=\"!shouldShowThoughtCard()\">\n <div class=\"task-title\">{{ getTaskTitle() }}</div>\n <div class=\"task-subtitle\">{{ getTaskSubtitle() }}</div>\n <div *ngIf=\"clarificationOptions.length > 1 && getTaskSelectionSummary() as selectionSummary\" class=\"task-meta\">\n <span class=\"task-meta-label\">Selecionado:</span>\n <span class=\"task-meta-value\">{{ selectionSummary }}</span>\n </div>\n <div *ngIf=\"shouldShowTaskSteps()\" class=\"task-steps\">\n <span class=\"task-step\">1 Dados</span>\n <span class=\"task-step\">2 Layout</span>\n <span class=\"task-step active\">3 Revis\u00E3o</span>\n </div>\n </div>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div class=\"review-trust\">\n <span class=\"trust-chip\" [matTooltip]=\"getScopeTooltip()\">{{ getScopeLabel() }}</span>\n <span class=\"trust-chip\" [matTooltip]=\"getConfidenceTooltip()\">{{ getConfidenceLabel() }}</span>\n <span class=\"trust-chip risk-chip\" [class.medium]=\"getReviewRiskLevel() === 'm\u00E9dio'\" [class.high]=\"getReviewRiskLevel() === 'alto'\">\n Risco {{ getReviewRiskLevel() }}\n </span>\n </div>\n <div class=\"review-summary\" [class.attention-highlight]=\"highlightReviewDetails\">\n <div class=\"review-summary-title\">Resumo da proposta</div>\n <div class=\"review-summary-line\">{{ getReviewSummary() }}</div>\n </div>\n <div class=\"review-diff\">\n <div class=\"review-diff-title\">Pr\u00E9via de mudan\u00E7as</div>\n <ng-container *ngIf=\"pendingDiff.length; else noDiffPreview\">\n <div class=\"review-diff-summary\">\n <div *ngFor=\"let line of getDiffSummaryLines()\" class=\"review-diff-line\">{{ line }}</div>\n <div *ngIf=\"pendingDiff.length > 3\" class=\"review-diff-more\">\u2026 +{{ pendingDiff.length - 3 }} mudan\u00E7as</div>\n </div>\n <button mat-stroked-button type=\"button\" class=\"review-diff-toggle\" (click)=\"toggleFullDiff()\">\n {{ getDiffToggleLabel() }}\n </button>\n </ng-container>\n <ng-template #noDiffPreview>\n <div class=\"review-diff-empty\">\n N\u00E3o foi poss\u00EDvel gerar um diff estruturado. Revise o resumo e aplique com cautela.\n </div>\n </ng-template>\n <div *ngIf=\"showFullDiff && pendingDiff.length\" class=\"review-diff-full\">\n <div *ngFor=\"let diff of pendingDiff\" class=\"review-diff-block\">\n <div class=\"review-diff-path\">{{ diff.path }}</div>\n <div class=\"review-diff-label\">Antes:</div>\n <pre>{{ diff.before | json }}</pre>\n <div class=\"review-diff-label\">Depois:</div>\n <pre>{{ diff.after | json }}</pre>\n </div>\n </div>\n </div>\n <div class=\"ai-explanation\" *ngIf=\"aiExplanation.trim()\">\n {{ aiExplanation }}\n </div>\n </div>\n\n <!-- STATE: ERROR -->\n <div\n *ngIf=\"state === 'error' && (isActiveTab('task') || (!isTaskMode() && isActiveTab('suggestions')))\"\n class=\"error-area assistant-card\"\n [attr.id]=\"isTaskMode() ? 'assistant-panel-task' : 'assistant-panel-suggestions'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"isTaskMode() ? 'assistant-tab-task' : 'assistant-tab-suggestions'\"\n >\n <div class=\"error-msg\">\n <mat-icon color=\"warn\">error_outline</mat-icon>\n <span>{{ errorMsg }}</span>\n </div>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div *ngIf=\"shouldShowApplyDetails()\" class=\"apply-details\">\n <div class=\"apply-details-header\">\n <div class=\"apply-details-title\">Detalhes da aplica\u00E7\u00E3o</div>\n <div class=\"apply-details-actions\" *ngIf=\"allowManualPatchEdit\">\n <button mat-button type=\"button\" (click)=\"togglePatchPathEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchPathEditorExpanded ? 'Ocultar paths' : 'Editar por path' }}\n </button>\n <button mat-button type=\"button\" (click)=\"togglePatchEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchEditorExpanded ? 'Ocultar editor' : 'Editar patch' }}\n </button>\n </div>\n </div>\n <div class=\"apply-details-meta\" *ngIf=\"lastApplyAt\">\n \u00DAltima tentativa: {{ lastApplyAt | date:'short' }}\n </div>\n <div class=\"apply-paths\" *ngIf=\"getApplyPaths().length\">\n <span class=\"apply-path-chip\" *ngFor=\"let path of getApplyPaths()\">{{ path }}</span>\n </div>\n <div class=\"apply-warnings\" *ngIf=\"applyWarnings.length\">\n <div class=\"apply-warnings-title\">Avisos da aplica\u00E7\u00E3o</div>\n <div class=\"apply-warnings-list\">\n <div *ngFor=\"let warning of applyWarnings\">{{ warning }}</div>\n </div>\n </div>\n <div class=\"patch-path-editor\" *ngIf=\"allowManualPatchEdit && patchPathEditorExpanded\">\n <div class=\"patch-editor-label\">Editar por path</div>\n <div class=\"patch-editor-hint\">Texto simples vira string; para for\u00E7ar texto em true/false/n\u00FAmero, use aspas.</div>\n <div class=\"patch-path-editor-empty\" *ngIf=\"!getEditablePatchPathEdits().length\">\n Nenhum path edit\u00E1vel dispon\u00EDvel para este patch.\n </div>\n <div class=\"patch-path-rows\" *ngIf=\"getEditablePatchPathEdits().length\">\n <div class=\"patch-path-row\" *ngFor=\"let edit of getEditablePatchPathEdits(); let i = index\">\n <div class=\"patch-path-label\">{{ edit.path }}</div>\n <input\n type=\"text\"\n [(ngModel)]=\"patchPathEdits[i].valueText\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n />\n <div *ngIf=\"patchPathEdits[i].error\" class=\"patch-editor-error\">{{ patchPathEdits[i].error }}</div>\n </div>\n </div>\n <div *ngIf=\"patchPathEditorError\" class=\"patch-editor-error\">{{ patchPathEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchPathEdits()\">\n Restaurar valores\n </button>\n <button mat-button type=\"button\" (click)=\"applyPathEditsToPatch()\">\n Aplicar no patch\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyPathEdits()\">\n Reaplicar por path\n </button>\n </div>\n </div>\n <div class=\"patch-editor\" *ngIf=\"allowManualPatchEdit && patchEditorExpanded\">\n <label class=\"patch-editor-label\">Patch JSON</label>\n <textarea\n [(ngModel)]=\"patchEditorText\"\n spellcheck=\"false\"\n ></textarea>\n <div *ngIf=\"patchEditorError\" class=\"patch-editor-error\">{{ patchEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchEditor()\">\n Restaurar original\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyEditedPatch()\">\n Reaplicar patch\n </button>\n </div>\n </div>\n </div>\n <div class=\"review-actions\">\n <button mat-button (click)=\"close()\">Fechar</button>\n <button mat-stroked-button (click)=\"retry()\">Tentar Novamente</button>\n </div>\n </div>\n\n <!-- STATE: SUCCESS -->\n <div\n *ngIf=\"state === 'success' && (isActiveTab('task') || (!isTaskMode() && isActiveTab('suggestions')))\"\n class=\"success-area assistant-card\"\n [attr.id]=\"isTaskMode() ? 'assistant-panel-task' : 'assistant-panel-suggestions'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"isTaskMode() ? 'assistant-tab-task' : 'assistant-tab-suggestions'\"\n >\n <div class=\"success-msg\">\n <mat-icon class=\"success-icon\">check_circle</mat-icon>\n <span>{{ aiExplanation || 'Configura\u00E7\u00E3o atualizada.' }}</span>\n </div>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div *ngIf=\"shouldShowApplyDetails()\" class=\"apply-details\">\n <div class=\"apply-details-header\">\n <div class=\"apply-details-title\">Detalhes da aplica\u00E7\u00E3o</div>\n <div class=\"apply-details-actions\" *ngIf=\"allowManualPatchEdit\">\n <button mat-button type=\"button\" (click)=\"togglePatchPathEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchPathEditorExpanded ? 'Ocultar paths' : 'Editar por path' }}\n </button>\n <button mat-button type=\"button\" (click)=\"togglePatchEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchEditorExpanded ? 'Ocultar editor' : 'Editar e reaplicar' }}\n </button>\n </div>\n </div>\n <div class=\"apply-details-meta\" *ngIf=\"lastApplyAt\">\n Aplicado em {{ lastApplyAt | date:'short' }}\n </div>\n <div class=\"apply-paths\" *ngIf=\"getApplyPaths().length\">\n <span class=\"apply-path-chip\" *ngFor=\"let path of getApplyPaths()\">{{ path }}</span>\n </div>\n <div class=\"apply-warnings\" *ngIf=\"applyWarnings.length\">\n <div class=\"apply-warnings-title\">Avisos da aplica\u00E7\u00E3o</div>\n <div class=\"apply-warnings-list\">\n <div *ngFor=\"let warning of applyWarnings\">{{ warning }}</div>\n </div>\n </div>\n <div class=\"patch-path-editor\" *ngIf=\"allowManualPatchEdit && patchPathEditorExpanded\">\n <div class=\"patch-editor-label\">Editar por path</div>\n <div class=\"patch-editor-hint\">Texto simples vira string; para for\u00E7ar texto em true/false/n\u00FAmero, use aspas.</div>\n <div class=\"patch-path-editor-empty\" *ngIf=\"!getEditablePatchPathEdits().length\">\n Nenhum path edit\u00E1vel dispon\u00EDvel para este patch.\n </div>\n <div class=\"patch-path-rows\" *ngIf=\"getEditablePatchPathEdits().length\">\n <div class=\"patch-path-row\" *ngFor=\"let edit of getEditablePatchPathEdits(); let i = index\">\n <div class=\"patch-path-label\">{{ edit.path }}</div>\n <input\n type=\"text\"\n [(ngModel)]=\"patchPathEdits[i].valueText\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n />\n <div *ngIf=\"patchPathEdits[i].error\" class=\"patch-editor-error\">{{ patchPathEdits[i].error }}</div>\n </div>\n </div>\n <div *ngIf=\"patchPathEditorError\" class=\"patch-editor-error\">{{ patchPathEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchPathEdits()\">\n Restaurar valores\n </button>\n <button mat-button type=\"button\" (click)=\"applyPathEditsToPatch()\">\n Aplicar no patch\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyPathEdits()\">\n Reaplicar por path\n </button>\n </div>\n </div>\n <div class=\"patch-editor\" *ngIf=\"allowManualPatchEdit && patchEditorExpanded\">\n <label class=\"patch-editor-label\">Patch JSON</label>\n <textarea\n [(ngModel)]=\"patchEditorText\"\n spellcheck=\"false\"\n ></textarea>\n <div *ngIf=\"patchEditorError\" class=\"patch-editor-error\">{{ patchEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchEditor()\">\n Restaurar original\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyEditedPatch()\">\n Reaplicar patch\n </button>\n </div>\n </div>\n </div>\n <button mat-button color=\"warn\" (click)=\"undoLastChange()\">\n <mat-icon>undo</mat-icon> Desfazer\n </button>\n </div>\n\n </div>\n\n <div class=\"assistant-footer\">\n <ng-container *ngIf=\"!isTaskMode(); else taskFooter\">\n <button\n mat-icon-button\n type=\"button\"\n class=\"composer-leading composer-leading-btn\"\n [matMenuTriggerFor]=\"assistantQuickMenu\"\n aria-label=\"Abrir a\u00E7\u00F5es r\u00E1pidas\"\n matTooltip=\"A\u00E7\u00F5es r\u00E1pidas\"\n >\n <mat-icon>add</mat-icon>\n </button>\n <input\n #inputEl\n type=\"text\"\n [(ngModel)]=\"userPrompt\"\n [disabled]=\"state === 'processing' || state === 'applying'\"\n placeholder=\"Descreva a altera\u00E7\u00E3o que deseja aplicar\u2026\"\n autocomplete=\"off\"\n />\n <div class=\"send-actions\">\n <button\n mat-icon-button\n class=\"send-btn\"\n *ngIf=\"state === 'listening'\"\n (click)=\"submitPrompt()\"\n [disabled]=\"!userPrompt.trim()\"\n [class.ready]=\"!!userPrompt.trim()\"\n >\n <mat-icon>arrow_upward</mat-icon>\n </button>\n <mat-spinner diameter=\"20\" *ngIf=\"state === 'processing' || state === 'applying'\"></mat-spinner>\n </div>\n </ng-container>\n <ng-template #taskFooter>\n <div class=\"task-footer\">\n <div class=\"task-footer-left\">\n <button mat-button type=\"button\" (click)=\"handleTaskSecondary()\">{{ getTaskCancelLabel() }}</button>\n <button\n *ngIf=\"state === 'review'\"\n mat-stroked-button\n type=\"button\"\n (click)=\"retry()\"\n >\n {{ getTaskSecondaryLabel() }}\n </button>\n </div>\n <div class=\"task-footer-right\">\n <button\n class=\"task-primary-btn\"\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n (click)=\"confirmTaskAction()\"\n [disabled]=\"isTaskPrimaryDisabled()\"\n >\n {{ getTaskPrimaryLabel() }}\n </button>\n <mat-spinner diameter=\"20\" *ngIf=\"state === 'processing' || state === 'applying'\"></mat-spinner>\n </div>\n </div>\n </ng-template>\n </div>\n\n <mat-menu #assistantQuickMenu=\"matMenu\" panelClass=\"assistant-quick-menu-panel\">\n <button mat-menu-item type=\"button\" (click)=\"setActiveTab('chat')\">\n <mat-icon>history</mat-icon>\n <span>Hist\u00F3rico</span>\n </button>\n <button mat-menu-item type=\"button\" (click)=\"setActiveTab('suggestions')\">\n <mat-icon>lightbulb</mat-icon>\n <span>Sugest\u00F5es</span>\n </button>\n <button mat-menu-item type=\"button\" (click)=\"startNewSession()\">\n <mat-icon>add_comment</mat-icon>\n <span>Nova conversa</span>\n </button>\n <button mat-menu-item type=\"button\" (click)=\"refreshSuggestions()\" [disabled]=\"loadingSuggestions\">\n <mat-icon>refresh</mat-icon>\n <span>Atualizar sugest\u00F5es</span>\n </button>\n <button\n mat-menu-item\n type=\"button\"\n (click)=\"restoreDismissedSuggestions()\"\n [disabled]=\"!hasDismissedSuggestions()\"\n >\n <mat-icon>visibility</mat-icon>\n <span>Restaurar sugest\u00F5es ocultas</span>\n </button>\n <button\n mat-menu-item\n type=\"button\"\n class=\"assistant-quick-menu-danger\"\n (click)=\"clearHistory()\"\n [disabled]=\"!historySessions.length\"\n >\n <mat-icon>delete_outline</mat-icon>\n <span>Limpar hist\u00F3rico local</span>\n </button>\n </mat-menu>\n </div>\n\n</ng-template>\n", styles: ["@keyframes assistantPremiumEnter{0%{opacity:0;transform:translate(14px) scale(.99)}to{opacity:1;transform:translate(0) scale(1)}}:host ::ng-deep .ai-assistant-backdrop{background:color-mix(in srgb,var(--md-sys-color-scrim, var(--md-sys-color-shadow, var(--md-sys-color-on-surface))) 42%,transparent);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}::ng-deep .ai-assistant-overlay-pane{max-width:calc(100vw - 24px);max-height:calc(100vh - 24px)}.ai-assistant-panel{box-sizing:border-box;width:min(604px,100vw - 18px);min-height:min(620px,100vh - 18px);max-height:min(820px,100vh - 18px);display:flex;flex-direction:column;overflow:hidden;color:var(--md-sys-color-on-surface);border-left:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));border-top:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));box-shadow:0 18px 48px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 42%,transparent),inset 0 1px color-mix(in srgb,var(--md-sys-color-on-surface) 10%,transparent);background:radial-gradient(circle at 14% 8%,color-mix(in srgb,var(--md-sys-color-primary-container) 38%,transparent) 0%,transparent 44%),linear-gradient(165deg,color-mix(in srgb,var(--md-sys-color-surface-container-highest) 88%,var(--md-sys-color-surface)),var(--md-sys-color-surface));animation:assistantPremiumEnter .24s cubic-bezier(.22,1,.36,1)}.assistant-header{position:relative;display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:12px 16px 10px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));background:linear-gradient(130deg,color-mix(in srgb,var(--md-sys-color-primary-container) 28%,transparent) 0%,transparent 52%),var(--md-sys-color-surface-container-low)}.assistant-header:after{content:\"\";position:absolute;left:16px;right:16px;bottom:-1px;height:1px;background:linear-gradient(90deg,color-mix(in srgb,var(--md-sys-color-primary) 55%,transparent),transparent);pointer-events:none}.assistant-header .assistant-header__left{min-width:0;display:flex;align-items:flex-start;gap:12px}.assistant-header .magic-icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:30px;height:30px;font-size:18px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent);box-shadow:0 4px 12px color-mix(in srgb,var(--md-sys-color-primary) 26%,transparent)}.assistant-header .assistant-title-group{min-width:0;display:flex;flex-direction:column;gap:4px}.assistant-header .assistant-title{font-size:15px;font-weight:700;letter-spacing:.01em;overflow-wrap:anywhere}.assistant-header .assistant-subtitle{display:block;font-size:11px;line-height:1.25;color:color-mix(in srgb,var(--md-sys-color-on-surface) 70%,var(--md-sys-color-on-surface-variant));overflow-wrap:anywhere}.assistant-header-chips{display:inline-flex;align-items:center;flex-wrap:wrap;gap:6px}.assistant-header .mode-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 28%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 64%,var(--md-sys-color-surface-container));color:color-mix(in srgb,var(--md-sys-color-on-primary-container) 85%,var(--md-sys-color-on-surface));font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.assistant-header .mode-chip:before{content:\"\";width:6px;height:6px;border-radius:999px;background:currentColor;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 16%,transparent)}.assistant-header .mode-chip.mock{border-color:color-mix(in srgb,var(--md-sys-color-error) 34%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-error-container) 72%,var(--md-sys-color-surface-container));color:color-mix(in srgb,var(--md-sys-color-on-error-container) 85%,var(--md-sys-color-error))}.assistant-header .policy-chip{padding:2px 8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 84%,transparent);background:var(--md-sys-color-surface-container-high);font-size:10px;letter-spacing:.02em;font-weight:600}.assistant-header .policy-chip.strict{border-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 56%,var(--md-sys-color-surface-container-high))}.assistant-status{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));border-radius:0;background:linear-gradient(160deg,color-mix(in srgb,var(--md-sys-color-primary-container) 24%,transparent),transparent 48%),var(--md-sys-color-surface-container-low)}.assistant-status-dot{width:8px;height:8px;margin-top:6px;border-radius:999px;flex:0 0 auto;background:var(--md-sys-color-primary);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.assistant-status.warning .assistant-status-dot{background:var(--md-sys-color-error);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-error) 16%,transparent)}.assistant-status.success .assistant-status-dot{background:color-mix(in srgb,var(--md-sys-color-primary) 74%,var(--md-sys-color-tertiary, var(--md-sys-color-primary)))}.assistant-status-content{min-width:0;display:flex;flex-direction:column;gap:2px}.assistant-status-label{display:flex;align-items:center;flex-wrap:wrap;gap:4px;font-size:12px;font-weight:700;line-height:1.35}.assistant-status-detail{font-size:11px;line-height:1.4;color:var(--md-sys-color-on-surface-variant);overflow-wrap:anywhere}.assistant-status-mode{padding:1px 6px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary-container) 72%,transparent);color:var(--md-sys-color-on-primary-container);font-size:10px;font-weight:700}.assistant-nav{padding:8px 16px 0}.assistant-nav .assistant-tabs{display:flex;align-items:center;gap:4px;border-radius:12px;padding:5px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:linear-gradient(155deg,color-mix(in srgb,var(--md-sys-color-primary-container) 14%,transparent),transparent 58%),var(--md-sys-color-surface-container-low)}.assistant-nav .assistant-tab{appearance:none;min-width:0;min-height:30px;border:0;border-radius:8px;padding:0 10px;background:transparent;color:var(--md-sys-color-on-surface-variant);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;flex:1 1 0;font-size:11px;font-weight:700;letter-spacing:.01em}.assistant-nav .assistant-tab.active{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 34%,transparent),0 6px 12px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}.assistant-section,.assistant-card{border-radius:14px;border-color:color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant));background:linear-gradient(160deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 50%),var(--md-sys-color-surface-container-lowest);box-shadow:0 6px 18px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 12%,transparent),inset 0 1px color-mix(in srgb,var(--md-sys-color-on-surface) 7%,transparent)}.suggestions-hero{margin:2px 0 4px;padding:10px 12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:linear-gradient(120deg,color-mix(in srgb,var(--md-sys-color-primary-container) 34%,transparent) 0%,transparent 52%),var(--md-sys-color-surface-container-low)}.suggestions-hero__label{font-size:10px;letter-spacing:.08em;text-transform:uppercase;font-weight:700;color:var(--md-sys-color-on-surface-variant);opacity:.88}.suggestions-hero__title{margin-top:2px;font-size:13px;font-weight:700;color:var(--md-sys-color-on-surface)}.suggestions-hero__detail{margin-top:4px;font-size:12px;line-height:1.4;color:color-mix(in srgb,var(--md-sys-color-on-surface) 78%,var(--md-sys-color-on-surface-variant))}.suggestions-content .suggestion-item{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;padding:12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));border-color:color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));background:linear-gradient(140deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 56%),var(--md-sys-color-surface-container-lowest)}.suggestions-list{display:flex;flex-direction:column;gap:10px}.suggestion-copy{min-width:0;display:grid;gap:4px}.suggestion-main{min-width:0;display:flex;align-items:center;flex-wrap:wrap;gap:8px}.suggestion-icon{width:18px;height:18px;font-size:18px;flex:0 0 auto;color:var(--md-sys-color-primary)}.suggestion-label{min-width:0;font-size:13px;font-weight:700;line-height:1.35;overflow-wrap:anywhere}.suggestion-group{padding:2px 6px;border-radius:999px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:10px;font-weight:700;line-height:1.2}.suggestion-desc{font-size:12px;line-height:1.35;color:var(--md-sys-color-on-surface-variant);overflow-wrap:anywhere}.suggestion-actions{display:inline-flex;align-items:center;justify-content:flex-end;gap:4px}.suggestion-action-btn,.suggestion-arrow{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;color:var(--md-sys-color-on-surface-variant)}.suggestions-content .suggestion-item:hover,.suggestions-content .suggestion-item:focus-visible{border-color:color-mix(in srgb,var(--md-sys-color-primary) 58%,var(--md-sys-color-outline-variant));box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 28%,transparent)}.assistant-footer{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px;padding:10px 16px;border-top-color:color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant));background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 56%),var(--md-sys-color-surface-container-low)}.assistant-footer .task-footer{grid-column:1/-1}.composer-leading{width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 30%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 70%,var(--md-sys-color-surface-container-low));color:var(--md-sys-color-primary);box-shadow:0 4px 10px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.composer-leading mat-icon{width:16px;height:16px;font-size:16px}.assistant-footer input{box-sizing:border-box;width:100%;min-width:0;height:34px;border:1px solid;border-radius:8px;padding:0 12px;color:var(--md-sys-color-on-surface);border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface);box-shadow:inset 0 1px 2px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 12%,transparent)}.send-actions{min-width:36px;display:inline-flex;align-items:center;justify-content:center}.assistant-footer .send-btn{--mdc-icon-button-icon-size: 20px;--mdc-icon-button-state-layer-size: 36px;--mat-icon-button-state-layer-size: 36px;width:36px;height:36px;padding:8px;line-height:1;display:inline-flex;align-items:center;justify-content:center;border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface)}.assistant-footer .send-btn mat-icon{width:20px;height:20px;margin:0;font-size:20px;line-height:20px;display:inline-flex;align-items:center;justify-content:center}.assistant-footer .send-btn.ready{background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary-container) 84%,var(--md-sys-color-primary)),var(--md-sys-color-primary));color:var(--md-sys-color-on-primary);box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}.task-primary-btn{border-radius:12px;box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 24%,transparent)}@media(max-width:959px){.assistant-header{padding:12px 12px 10px}.assistant-section,.assistant-card{margin:4px 12px 10px}.assistant-header:after{left:12px;right:12px}.assistant-header .assistant-title{font-size:14px}.assistant-header .assistant-subtitle{font-size:10px}.assistant-footer{grid-template-columns:minmax(0,1fr) auto}.composer-leading{display:none}}.assistant-thought{margin-top:0;gap:10px;border-color:color-mix(in srgb,var(--md-sys-color-primary) 26%,var(--md-sys-color-outline-variant));background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 28%,transparent),transparent 58%),var(--md-sys-color-surface-container-low)}.assistant-thought-meta{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.assistant-thought-meta mat-icon{width:15px;height:15px;font-size:15px;color:var(--md-sys-color-primary)}.assistant-thought-summary{font-size:13px;line-height:1.45;color:var(--md-sys-color-on-surface)}.assistant-thought-plan{border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 32%,var(--md-sys-color-outline-variant));border-radius:12px;padding:10px;background:var(--md-sys-color-surface-container-lowest);display:flex;flex-direction:column;gap:8px}.assistant-thought-plan-title{font-size:13px;font-weight:700;color:var(--md-sys-color-on-surface)}.assistant-thought-plan-actions{display:grid;grid-template-columns:repeat(auto-fit,minmax(0,1fr));gap:8px}.assistant-thought-plan-actions .mdc-button{min-width:0}.assistant-thought-plan-hint{font-size:11px;color:var(--md-sys-color-on-surface-variant);line-height:1.35}.assistant-thought-checklist{display:flex;flex-direction:column;gap:5px}.assistant-thought-checklist-item{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.assistant-thought-checklist-item mat-icon{width:14px;height:14px;font-size:14px}.assistant-flow{grid-template-columns:1fr;gap:6px}.flow-step{display:flex;align-items:flex-start;gap:8px;text-align:left;padding:7px 8px;border-radius:10px}.flow-step-index{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);flex:0 0 auto}.flow-step-content{min-width:0;display:flex;flex-direction:column;gap:1px}.flow-step-label{font-size:11px;font-weight:700;color:var(--md-sys-color-on-surface)}.flow-step-detail{font-size:11px;color:var(--md-sys-color-on-surface-variant);line-height:1.3}.flow-step.active .flow-step-index{border-color:color-mix(in srgb,var(--md-sys-color-primary) 60%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 85%,var(--md-sys-color-surface-container-highest));color:var(--md-sys-color-on-primary-container)}.flow-step.done .flow-step-index{border-color:transparent;background:color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-surface-container-highest))}.task-timeline{margin-top:4px;display:grid;gap:6px}.task-timeline-item{display:flex;align-items:flex-start;gap:8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;padding:6px 8px;background:var(--md-sys-color-surface-container-low);opacity:.76}.task-timeline-item.active{opacity:1;border-color:color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 34%,var(--md-sys-color-surface-container-low))}.task-timeline-item.done{opacity:.9;border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant))}.task-timeline-dot{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);flex:0 0 auto}.task-timeline-copy{min-width:0;display:flex;flex-direction:column;gap:1px}.task-timeline-title{font-size:11px;font-weight:700;color:var(--md-sys-color-on-surface)}.task-timeline-detail{font-size:11px;line-height:1.3;color:var(--md-sys-color-on-surface-variant)}.clarification-decision-head{display:inline-flex;align-items:center;width:100%;gap:6px;padding:8px 12px 0;box-sizing:border-box}.clarification-decision-index{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 35%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 68%,transparent)}.clarification-decision-type{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant);opacity:.92}.clarification-decision-state{font-size:10px;font-weight:700;color:var(--md-sys-color-primary)}.clarification-option{border-color:color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant));background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 55%),var(--md-sys-color-surface-container-low)}.clarification-option.selected{border-color:color-mix(in srgb,var(--md-sys-color-primary) 56%,var(--md-sys-color-outline-variant));background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 44%,transparent),transparent 62%),var(--md-sys-color-surface-container);box-shadow:0 8px 16px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}.clarification-options-block{overflow:visible;padding-right:4px}@media(max-width:959px){.assistant-thought-plan-actions{grid-template-columns:1fr}}.ai-assistant-panel{width:min(604px,100vw - 18px)}.assistant-section,.assistant-card{margin:0;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant))}.assistant-section{min-height:0;padding:14px 16px;overflow:visible}.assistant-card{min-height:0;padding:14px 16px 16px;overflow:visible}.suggestions-area,.assistant-history,.loading-suggestions{overflow-y:auto;overflow-x:hidden}.section-header{min-width:0;display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}.section-title{min-width:0;font-size:14px;font-weight:700;line-height:1.35;overflow-wrap:anywhere}.suggestions-actions{flex:0 0 auto;display:inline-flex;align-items:center}.review-area,.error-area,.success-area,.clarification-area{padding:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:auto}.review-trust{margin:2px 0 4px}.trust-chip{max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.warnings-area,.review-summary,.review-diff,.apply-details,.clarification-options-block,.clarification-manual-toggle{width:100%;max-width:100%;min-width:0}.review-diff{padding:12px 14px}.review-diff-full{padding:10px;overflow-x:auto}.review-diff-block{min-width:0}.review-diff-block pre{overflow-wrap:anywhere;word-break:break-word}.clarification-options-block{padding:4px 2px 10px}.clarification-option{line-height:normal}:host ::ng-deep .clarification-option .mdc-button__label{line-height:1.35!important}.clarification-plain-label{padding:6px 12px 12px;font-size:14px;line-height:1.35}.clarification-manual-toggle{margin-top:10px;padding:10px 2px 0;flex-wrap:wrap;row-gap:6px}.suggestions-content .suggestion-actions{gap:6px}.suggestions-content .suggestion-arrow,.suggestions-content .suggestion-action-btn mat-icon,.suggestions-content .suggestion-arrow mat-icon{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep .assistant-quick-menu-panel{min-width:244px;max-width:min(90vw,320px);border-radius:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 26%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface-container-high);box-shadow:0 12px 28px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 32%,transparent),inset 0 1px color-mix(in srgb,var(--md-sys-color-on-surface) 6%,transparent);overflow:hidden;padding:6px}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item{min-height:36px;border-radius:10px;color:var(--md-sys-color-on-surface);font-size:12px;font-weight:500;margin:1px 0}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item .mat-icon{color:color-mix(in srgb,var(--md-sys-color-on-surface) 82%,var(--md-sys-color-on-surface-variant))}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item:hover:not([disabled]){background:color-mix(in srgb,var(--md-sys-color-primary-container) 45%,transparent)}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:-2px}:host ::ng-deep .assistant-quick-menu-panel .assistant-quick-menu-danger{color:var(--md-sys-color-error)}:host ::ng-deep .assistant-quick-menu-panel .assistant-quick-menu-danger .mat-icon{color:var(--md-sys-color-error)}@media(max-width:959px){::ng-deep .ai-assistant-overlay-pane{top:12px!important;left:12px!important;width:calc(100vw - 24px)!important;height:calc(100vh - 24px)!important;max-width:calc(100vw - 24px);max-height:calc(100vh - 24px);transform:none!important}.ai-assistant-panel{width:calc(100vw - 48px);min-height:min(620px,100vh - 48px);max-height:calc(100vh - 48px)}.assistant-section,.assistant-card{margin:0}.assistant-card,.assistant-section{padding:12px}}.ai-trigger-btn{--mdc-icon-button-state-layer-size: 36px;width:36px;height:36px;border-radius:8px;background:color-mix(in srgb,var(--md-sys-color-primary) 9%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 22%,transparent);color:var(--md-sys-color-primary);opacity:1}.ai-trigger-btn:hover:not(:disabled){background:color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent);border-color:color-mix(in srgb,var(--md-sys-color-primary) 36%,transparent);color:var(--md-sys-color-on-primary-container)}.ai-trigger-btn:focus-visible,.assistant-close-btn:focus-visible,.assistant-tab:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.assistant-close-btn{width:28px;height:28px;color:var(--md-sys-color-on-surface-variant)}.assistant-close-btn:hover:not(:disabled){background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface)}.assistant-body{padding:12px 16px;overflow:hidden;min-height:0;flex:1;display:flex;flex-direction:column;gap:12px}.assistant-body>*{min-height:0}@media(max-width:959px){.assistant-nav{padding:8px 12px 0}.assistant-body,.assistant-footer{padding:10px 12px}.suggestions-content .suggestion-item{grid-template-columns:1fr;align-items:start}.suggestion-actions{justify-content:flex-start}}\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: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i3.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i3.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i3.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { 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: OverlayModule }, { kind: "directive", type: i5.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i5.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i6.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "ngmodule", type: MatDialogModule }, { 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"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i16.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i11.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i11.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i11.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "pipe", type: i3.JsonPipe, name: "json" }, { kind: "pipe", type: i3.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6672
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisAiAssistantComponent, isStandalone: true, selector: "praxis-ai-assistant", inputs: { adapter: "adapter", riskPolicy: "riskPolicy", allowManualPatchEdit: "allowManualPatchEdit", hasBackdrop: "hasBackdrop" }, viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: CdkOverlayOrigin, descendants: true }, { propertyName: "triggerButton", first: true, predicate: ["triggerBtn"], descendants: true, read: ElementRef }, { propertyName: "inputElement", first: true, predicate: ["inputEl"], descendants: true }], ngImport: i0, template: "<!-- Trigger Button -->\n<button \n mat-icon-button \n cdkOverlayOrigin \n #trigger=\"cdkOverlayOrigin\"\n #triggerBtn\n (click)=\"open()\"\n [disabled]=\"isOpen\"\n class=\"ai-trigger-btn\"\n matTooltip=\"Assistente de Configura\u00E7\u00E3o\"\n aria-label=\"Abrir Assistente IA\">\n <mat-icon>auto_awesome</mat-icon>\n</button>\n\n<!-- Overlay Template -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayHasBackdrop]=\"hasBackdrop\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayViewportMargin]=\"12\"\n cdkConnectedOverlayPanelClass=\"ai-assistant-overlay-pane\"\n cdkConnectedOverlayBackdropClass=\"ai-assistant-backdrop\"\n (backdropClick)=\"close()\"\n (overlayOutsideClick)=\"close()\"\n (detach)=\"close()\">\n\n <div\n class=\"ai-assistant-panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Assistente de Configura\u00E7\u00E3o\"\n [attr.aria-busy]=\"isBusyState() ? 'true' : null\"\n [cdkTrapFocus]=\"hasBackdrop\"\n [cdkTrapFocusAutoCapture]=\"hasBackdrop\"\n (keydown)=\"onKeydown($event)\"\n >\n \n <!-- HEADER -->\n <div class=\"assistant-header\">\n <div class=\"assistant-header__left\">\n <mat-icon class=\"magic-icon\">auto_awesome</mat-icon>\n <div class=\"assistant-title-group\">\n <div class=\"assistant-title\">Assistente de Configura\u00E7\u00E3o</div>\n <div class=\"assistant-subtitle\">Copiloto contextual para ajustes guiados</div>\n <div class=\"assistant-header-chips\">\n <span\n class=\"mode-chip\"\n [class.mock]=\"mockMode\"\n [matTooltip]=\"mockMode ? 'Sem chave de API: respostas de demonstra\u00E7\u00E3o' : 'Conectado ao assistente configurado'\"\n >\n {{ mockMode ? 'Mock' : 'Conectado' }}\n </span>\n <span\n class=\"policy-chip\"\n [class.strict]=\"isStrictRiskPolicy()\"\n [matTooltip]=\"getRiskPolicyTooltip()\"\n >\n {{ getRiskPolicyLabel() }}\n </span>\n </div>\n </div>\n </div>\n <div class=\"assistant-header__right\">\n <button\n mat-icon-button\n type=\"button\"\n class=\"assistant-close-btn\"\n (click)=\"close()\"\n aria-label=\"Fechar assistente\"\n matTooltip=\"Fechar assistente\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n <div\n class=\"assistant-status\"\n [class.processing]=\"isBusyState()\"\n [class.pending]=\"state === 'clarification'\"\n [class.warning]=\"state === 'error'\"\n [class.success]=\"state === 'review' || state === 'success'\"\n [class.compact]=\"!shouldShowSystemStatusDetail()\"\n role=\"status\"\n [attr.aria-live]=\"getSystemStatusAriaLive()\"\n aria-atomic=\"true\"\n >\n <span class=\"assistant-status-dot\" aria-hidden=\"true\"></span>\n <div class=\"assistant-status-content\">\n <div class=\"assistant-status-label\">\n <span class=\"assistant-status-label-prefix\">Status:</span>\n <span>{{ getSystemStatusLabel() }}</span>\n <span *ngIf=\"shouldShowSnapshotFallbackBadge()\" class=\"assistant-status-mode\">Snapshot</span>\n </div>\n <div *ngIf=\"shouldShowSystemStatusDetail()\" class=\"assistant-status-detail\">{{ getSystemStatusDetail() }}</div>\n </div>\n </div>\n <div class=\"assistant-flow\" *ngIf=\"shouldShowTaskFlow()\" role=\"list\" aria-label=\"Fluxo da proposta\">\n <div\n class=\"flow-step\"\n role=\"listitem\"\n *ngFor=\"let step of flowSteps\"\n [class.active]=\"getFlowStepState(step.step) === 'active'\"\n [class.done]=\"getFlowStepState(step.step) === 'done'\"\n >\n <span class=\"flow-step-index\">{{ step.step }}</span>\n <span class=\"flow-step-content\">\n <span class=\"flow-step-label\">{{ step.label }}</span>\n <span class=\"flow-step-detail\">{{ getFlowStepDetail(step.step) }}</span>\n </span>\n </div>\n </div>\n <div class=\"assistant-nav\">\n <div\n class=\"assistant-tabs\"\n role=\"tablist\"\n aria-label=\"Se\u00E7\u00F5es do assistente\"\n (keydown)=\"onTabsKeydown($event)\"\n >\n <button\n class=\"assistant-tab\"\n type=\"button\"\n *ngIf=\"isTaskMode()\"\n id=\"assistant-tab-task\"\n role=\"tab\"\n aria-label=\"Proposta atual\"\n [attr.aria-selected]=\"isActiveTab('task')\"\n aria-controls=\"assistant-panel-task\"\n [attr.tabindex]=\"isActiveTab('task') ? 0 : -1\"\n [class.active]=\"isActiveTab('task')\"\n (click)=\"setActiveTab('task')\"\n >\n Proposta\n <span *ngIf=\"hasPendingClarification() && !isActiveTab('task')\" class=\"assistant-tab-badge\">1</span>\n </button>\n <button\n class=\"assistant-tab\"\n type=\"button\"\n id=\"assistant-tab-chat\"\n role=\"tab\"\n aria-label=\"Hist\u00F3rico\"\n [attr.aria-selected]=\"isActiveTab('chat')\"\n aria-controls=\"assistant-panel-chat\"\n [attr.tabindex]=\"isActiveTab('chat') ? 0 : -1\"\n [class.active]=\"isActiveTab('chat')\"\n (click)=\"setActiveTab('chat')\"\n >\n Hist\u00F3rico\n </button>\n <button\n class=\"assistant-tab\"\n type=\"button\"\n id=\"assistant-tab-suggestions\"\n role=\"tab\"\n aria-label=\"Sugest\u00F5es de melhoria\"\n [attr.aria-selected]=\"isActiveTab('suggestions')\"\n aria-controls=\"assistant-panel-suggestions\"\n [attr.tabindex]=\"isActiveTab('suggestions') ? 0 : -1\"\n [class.active]=\"isActiveTab('suggestions')\"\n (click)=\"setActiveTab('suggestions')\"\n >\n Sugest\u00F5es\n </button>\n </div>\n </div>\n\n <!-- BODY: Dynamic Content based on State -->\n <div class=\"assistant-body\">\n <div class=\"assistant-thought assistant-section\" *ngIf=\"shouldShowThoughtCard()\">\n <div class=\"assistant-thought-meta\">\n <mat-icon>psychology</mat-icon>\n <span>{{ getThoughtTimingLabel() }}</span>\n </div>\n <div class=\"assistant-thought-summary\">{{ getThoughtSummary() }}</div>\n <div class=\"assistant-thought-plan\">\n <div class=\"assistant-thought-plan-title\">{{ getThoughtPlanTitle() }}</div>\n <div class=\"assistant-thought-plan-actions\">\n <button\n mat-stroked-button\n type=\"button\"\n class=\"thought-action-details\"\n (click)=\"openThoughtDetails()\"\n [attr.aria-label]=\"getThoughtDetailsLabel()\"\n [matTooltip]=\"getThoughtDetailsTooltip()\"\n >\n {{ getThoughtDetailsLabel() }}\n </button>\n <button\n *ngIf=\"shouldShowThoughtPreviewAction()\"\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n class=\"thought-action-preview\"\n (click)=\"openThoughtPreview()\"\n [matTooltip]=\"getThoughtPreviewTooltip()\"\n >\n Pr\u00E9via\n </button>\n </div>\n <div class=\"assistant-thought-plan-hint\">{{ getThoughtActionHint() }}</div>\n <ng-container *ngIf=\"getThoughtChecklist() as thoughtChecklist\">\n <div class=\"assistant-thought-checklist\" *ngIf=\"thoughtChecklist.length\">\n <div *ngFor=\"let item of thoughtChecklist\" class=\"assistant-thought-checklist-item\">\n <mat-icon>radio_button_unchecked</mat-icon>\n <span>{{ item }}</span>\n </div>\n </div>\n <div class=\"assistant-thought-shimmer\" *ngIf=\"isBusyState() && !thoughtChecklist.length\">\n <div class=\"shimmer-line shimmer-line-1\"></div>\n <div class=\"shimmer-line shimmer-line-2\"></div>\n <div class=\"shimmer-line shimmer-line-3\"></div>\n </div>\n </ng-container>\n </div>\n </div>\n <div *ngIf=\"processingInfoVisible\" class=\"processing-banner\">\n <mat-spinner diameter=\"16\"></mat-spinner>\n <span>{{ aiExplanation || 'Analisando solicita\u00E7\u00E3o e preparando proposta...' }}</span>\n <button mat-button type=\"button\" class=\"processing-retry\" (click)=\"retryProcessing()\">Tentar novamente</button>\n </div>\n <div\n class=\"assistant-history assistant-section\"\n *ngIf=\"historyContext && isActiveTab('chat')\"\n id=\"assistant-panel-chat\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-chat\"\n >\n <div class=\"section-header\">\n <div class=\"section-heading\">\n <div class=\"section-title\">Hist\u00F3rico</div>\n <div class=\"section-subtitle\">Sess\u00F5es recentes, pedidos reaproveit\u00E1veis e desfazer r\u00E1pido.</div>\n </div>\n <div class=\"history-actions\" role=\"group\" aria-label=\"A\u00E7\u00F5es do hist\u00F3rico\">\n <button\n mat-stroked-button\n type=\"button\"\n class=\"history-action-btn\"\n (click)=\"historyExpanded = !historyExpanded\"\n [matTooltip]=\"historyExpanded ? 'Recolher hist\u00F3rico' : 'Expandir hist\u00F3rico'\"\n [attr.aria-label]=\"historyExpanded ? 'Recolher hist\u00F3rico' : 'Expandir hist\u00F3rico'\"\n [disabled]=\"!historyWarnings.length && !historySessions.length && !activeHistoryMessages.length\"\n >\n <mat-icon>{{ historyExpanded ? 'expand_less' : 'expand_more' }}</mat-icon>\n <span>{{ historyExpanded ? 'Recolher' : 'Expandir' }}</span>\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n class=\"history-action-btn\"\n (click)=\"startNewSession()\"\n matTooltip=\"Nova conversa\"\n aria-label=\"Nova conversa\"\n [disabled]=\"isBusyState()\"\n >\n <mat-icon>add_comment</mat-icon>\n <span>Nova conversa</span>\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"clearHistory()\"\n matTooltip=\"Limpar hist\u00F3rico local\"\n aria-label=\"Limpar hist\u00F3rico local\"\n class=\"history-action-btn history-action-btn--danger\"\n [disabled]=\"!historySessions.length\"\n >\n <mat-icon>delete_outline</mat-icon>\n <span>Limpar</span>\n </button>\n </div>\n </div>\n <div *ngIf=\"historyUndoDeleteSession\" class=\"history-undo\">\n <span>Sess\u00E3o removida.</span>\n <button mat-button type=\"button\" (click)=\"undoRemoveHistorySession()\">\n Desfazer\n </button>\n </div>\n\n <div *ngIf=\"historyExpanded && historyWarnings.length\" class=\"history-warnings\">\n <mat-icon>info</mat-icon>\n <div class=\"history-warnings-list\">\n <div *ngFor=\"let warning of historyWarnings\">{{ warning }}</div>\n <div class=\"history-warnings-hint\">\n Configure headers via API_CONFIG_STORAGE_OPTIONS no host.\n </div>\n </div>\n </div>\n\n <div *ngIf=\"historyExpanded && historySessions.length\" class=\"history-sessions\">\n <div\n class=\"history-session\"\n *ngFor=\"let session of historySessions\"\n role=\"button\"\n tabindex=\"0\"\n (click)=\"selectHistorySession(session.id)\"\n (keydown)=\"onHistorySessionCardKeydown($event, session.id)\"\n [class.active]=\"session.id === activeHistorySession?.id\"\n [matTooltip]=\"getHistorySessionTooltip(session)\"\n [matTooltipDisabled]=\"!session.componentType && !session.componentId\"\n >\n <div class=\"history-session-main\">\n <span class=\"history-session-title\">{{ session.title }}</span>\n <div class=\"history-session-main-right\">\n <span class=\"history-session-time\">{{ session.updatedAt | date:'short' }}</span>\n <div class=\"history-session-tools\">\n <button\n mat-icon-button\n type=\"button\"\n class=\"history-session-tool\"\n (click)=\"reuseHistorySessionPrompt(session.id, $event)\"\n matTooltip=\"Reusar \u00FAltimo pedido\"\n aria-label=\"Reusar \u00FAltimo pedido\"\n >\n <mat-icon>edit_note</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n class=\"history-session-tool history-session-tool--danger\"\n (click)=\"removeHistorySession(session.id, $event)\"\n matTooltip=\"Excluir sess\u00E3o\"\n aria-label=\"Excluir sess\u00E3o\"\n >\n <mat-icon>delete_outline</mat-icon>\n </button>\n </div>\n </div>\n </div>\n <div class=\"history-session-meta\">\n <span *ngIf=\"session.componentType\" class=\"history-chip\">{{ session.componentType }}</span>\n <span *ngIf=\"session.componentId\" class=\"history-chip\">{{ session.componentId }}</span>\n </div>\n </div>\n </div>\n <div *ngIf=\"historyExpanded && !historySessions.length\" class=\"history-empty\">\n Nenhuma sess\u00E3o salva ainda.\n </div>\n\n <div *ngIf=\"historyExpanded && activeHistoryMessages.length\" class=\"history-messages\">\n <div\n *ngIf=\"activeHistoryTotalMessages > activeHistoryMessages.length\"\n class=\"history-messages-hint\"\n >\n Mostrando \u00FAltimas {{ activeHistoryMessages.length }} de {{ activeHistoryTotalMessages }} mensagens.\n </div>\n <div\n *ngFor=\"let msg of activeHistoryMessages\"\n class=\"history-message\"\n [class.user]=\"msg.role === 'user'\"\n [class.assistant]=\"msg.role === 'assistant'\"\n >\n <div class=\"history-message-header\">\n <span class=\"history-message-role\">{{ msg.role === 'user' ? 'Voc\u00EA' : 'Assistente' }}</span>\n <span class=\"history-message-time\">{{ msg.createdAt | date:'shortTime' }}</span>\n <span\n *ngIf=\"msg.context?.usedRag\"\n class=\"history-rag\"\n matTooltip=\"Resposta baseada em contexto recuperado (RAG)\"\n >RAG</span>\n </div>\n <div class=\"history-message-text\">{{ msg.text }}</div>\n </div>\n </div>\n <div *ngIf=\"historyExpanded && historySessions.length && !activeHistoryMessages.length\" class=\"history-empty history-empty--panel\">\n Selecione uma sess\u00E3o para visualizar as mensagens.\n </div>\n <div class=\"history-helper\" *ngIf=\"historyExpanded\">\n O hist\u00F3rico \u00E9 local ao usu\u00E1rio e ao componente atual.\n </div>\n </div>\n\n <div\n class=\"loading-suggestions assistant-section\"\n *ngIf=\"loadingSuggestions && isActiveTab('suggestions')\"\n id=\"assistant-panel-suggestions\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-suggestions\"\n >\n <mat-spinner diameter=\"20\"></mat-spinner>\n <span>Carregando sugest\u00F5es de melhoria...</span>\n </div>\n \n <!-- STATE: LISTENING (Suggestions) -->\n <div\n *ngIf=\"state === 'listening' && isActiveTab('suggestions')\"\n class=\"suggestions-area assistant-section\"\n id=\"assistant-panel-suggestions\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-suggestions\"\n >\n <div class=\"section-header\">\n <div class=\"section-title\">Sugest\u00F5es de melhoria</div>\n <div class=\"suggestions-actions\">\n <button mat-icon-button type=\"button\" (click)=\"refreshSuggestions()\" [disabled]=\"loadingSuggestions\" matTooltip=\"Atualizar sugest\u00F5es\">\n <mat-icon>refresh</mat-icon>\n </button>\n </div>\n </div>\n <div class=\"suggestions-hero\" *ngIf=\"!loadingSuggestions\">\n <div class=\"suggestions-hero__label\">Contexto ativo</div>\n <div class=\"suggestions-hero__title\">{{ adapter.componentName || 'Componente atual' }}</div>\n <div class=\"suggestions-hero__detail\">{{ getSystemStatusDetail() }}</div>\n </div>\n <div *ngIf=\"suggestionsWarnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of suggestionsWarnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div class=\"suggestions-content\" *ngIf=\"!loadingSuggestions && (richSuggestions.length || hasDismissedSuggestions())\">\n <div class=\"suggestions-filter\" *ngIf=\"hasDismissedSuggestions()\">\n <span>{{ getDismissedSuggestionCount() }} oculta(s)</span>\n <button mat-button type=\"button\" (click)=\"restoreDismissedSuggestions()\">\n Restaurar\n </button>\n </div>\n <div class=\"suggestions-list\" *ngIf=\"getVisibleSuggestions().length; else allSuggestionsHidden\">\n <div\n class=\"suggestion-item\"\n *ngFor=\"let sug of getVisibleSuggestions()\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'Selecionar sugest\u00E3o: ' + sug.label\"\n (click)=\"selectSuggestion(sug)\"\n (keydown)=\"onSuggestionCardKeydown($event, sug)\"\n >\n <div class=\"suggestion-copy\">\n <div class=\"suggestion-main\">\n <mat-icon *ngIf=\"sug.icon\" class=\"suggestion-icon\">{{ sug.icon }}</mat-icon>\n <span class=\"suggestion-label\">{{ sug.label }}</span>\n <span *ngIf=\"sug.group\" class=\"suggestion-group\">{{ sug.group }}</span>\n </div>\n <div *ngIf=\"sug.description\" class=\"suggestion-desc\">{{ sug.description }}</div>\n </div>\n <div class=\"suggestion-actions\">\n <span class=\"suggestion-arrow\" aria-hidden=\"true\">\n <mat-icon>chevron_right</mat-icon>\n </span>\n <button\n mat-icon-button\n type=\"button\"\n class=\"suggestion-action-btn\"\n (click)=\"prepareSuggestionPrompt(sug, $event)\"\n matTooltip=\"Refinar pedido\"\n aria-label=\"Refinar pedido\"\n >\n <mat-icon>edit</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n class=\"suggestion-action-btn suggestion-action-btn--danger\"\n (click)=\"dismissSuggestion(sug, $event)\"\n matTooltip=\"Ocultar sugest\u00E3o\"\n aria-label=\"Ocultar sugest\u00E3o\"\n >\n <mat-icon>visibility_off</mat-icon>\n </button>\n </div>\n </div>\n </div>\n <ng-template #allSuggestionsHidden>\n <div class=\"suggestions-empty suggestions-empty--inline\">\n Todas as sugest\u00F5es foram ocultadas.\n </div>\n </ng-template>\n </div>\n <div class=\"suggestions-empty\" *ngIf=\"!loadingSuggestions && !richSuggestions.length\">\n Nenhuma sugest\u00E3o dispon\u00EDvel no momento.\n </div>\n <div class=\"suggestions-helper\" *ngIf=\"!loadingSuggestions && !richSuggestions.length\">\n Selecione uma sugest\u00E3o acima ou descreva uma altera\u00E7\u00E3o no campo inferior.\n </div>\n </div>\n\n <!-- STATE: CLARIFICATION (Two-Step Flow) -->\n <div\n *ngIf=\"state === 'clarification' && isActiveTab('task')\"\n class=\"clarification-area assistant-card\"\n id=\"assistant-panel-task\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-task\"\n >\n <div class=\"task-header\" *ngIf=\"!shouldShowThoughtCard()\">\n <div class=\"task-title\">{{ getTaskTitle() }}</div>\n <div class=\"task-subtitle\">{{ getTaskSubtitle() }}</div>\n <div *ngIf=\"clarificationOptions.length > 1 && getTaskSelectionSummary() as selectionSummary\" class=\"task-meta\">\n <span class=\"task-meta-label\">Selecionado:</span>\n <span class=\"task-meta-value\">{{ selectionSummary }}</span>\n </div>\n <div *ngIf=\"shouldShowTaskSteps()\" class=\"task-steps\">\n <span class=\"task-step active\">1 Dados</span>\n <span class=\"task-step\">2 Layout</span>\n <span class=\"task-step\">3 Revis\u00E3o</span>\n </div>\n </div>\n <div *ngIf=\"clarificationResponseType === 'context'\" class=\"context-only\">\n <mat-spinner diameter=\"24\"></mat-spinner>\n <div class=\"context-only-hint\">Buscando contexto adicional...</div>\n </div>\n <ng-template #clarificationOptionContent let-opt let-index=\"index\" let-compact=\"compact\">\n <div class=\"clarification-decision-head\" *ngIf=\"!compact\">\n <span class=\"clarification-decision-index\">{{ index + 1 }}</span>\n <span class=\"clarification-decision-type\">{{ getClarificationOptionKindLabel(opt) }}</span>\n <span class=\"spacer\"></span>\n <span class=\"clarification-decision-state\" *ngIf=\"isClarificationSelected(opt)\">Selecionado</span>\n </div>\n <ng-container [ngSwitch]=\"getClarificationOptionLayout(opt)\">\n <div *ngSwitchCase=\"'endpoint'\" class=\"clarification-card\">\n <div class=\"clarification-card-header\">\n <mat-icon class=\"endpoint-icon\">{{ getEndpointIcon(opt) }}</mat-icon>\n <ng-container *ngIf=\"getEndpointMethod(opt) as method\">\n <span class=\"endpoint-method\" [attr.data-method]=\"method\">{{ method }}</span>\n </ng-container>\n <span class=\"endpoint-label\">{{ opt.label }}</span>\n <span class=\"spacer\"></span>\n <mat-icon class=\"select-indicator\">\n {{ isClarificationSelected(opt) ? 'check_circle' : 'radio_button_unchecked' }}\n </mat-icon>\n </div>\n <div class=\"clarification-card-body\">\n <ng-container *ngIf=\"getEndpointPath(opt) as path\">\n <div class=\"endpoint-path\">{{ path }}</div>\n </ng-container>\n <div\n *ngIf=\"opt.contextHints?.description\"\n class=\"endpoint-description\"\n [matTooltip]=\"getDescriptionTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowDescriptionTooltip(opt)\"\n >\n {{ opt.contextHints?.description }}\n </div>\n </div>\n </div>\n <div *ngSwitchCase=\"'color'\" class=\"clarification-color\">\n <span\n class=\"color-swatch\"\n [style.background]=\"getSafeHexColor(opt) || 'var(--md-sys-color-surface-container-highest)'\"\n ></span>\n <div class=\"color-meta\">\n <span class=\"color-label\">{{ opt.label }}</span>\n <span *ngIf=\"opt.contextHints?.hexColor\" class=\"color-value\">{{ opt.contextHints?.hexColor }}</span>\n </div>\n </div>\n <div *ngSwitchCase=\"'description'\" class=\"clarification-description\">\n <span class=\"clarification-label\">{{ opt.label }}</span>\n <span\n *ngIf=\"opt.contextHints?.description\"\n class=\"clarification-subtitle\"\n [matTooltip]=\"getDescriptionTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowDescriptionTooltip(opt)\"\n >\n {{ opt.contextHints?.description }}\n </span>\n </div>\n <span *ngSwitchDefault class=\"clarification-plain-label\">{{ opt.label }}</span>\n </ng-container>\n </ng-template>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div\n *ngIf=\"clarificationQuestions.length\"\n class=\"clarification-questions\"\n [class.attention-highlight]=\"highlightClarificationDetails && !clarificationOptions.length\"\n >\n <div *ngFor=\"let question of clarificationQuestions; let i = index\" class=\"clarification-question\">\n <div class=\"clarification-question-label\">{{ question }}</div>\n <input\n type=\"text\"\n [(ngModel)]=\"clarificationAnswers[i]\"\n [placeholder]=\"'Resposta ' + (i + 1)\"\n autocomplete=\"off\">\n </div>\n </div>\n <div\n *ngIf=\"clarificationOptions.length\"\n class=\"clarification-options-block\"\n [class.attention-highlight]=\"highlightClarificationDetails\"\n >\n <div class=\"clarification-options-title\">\n {{ clarificationOptions.length === 1 ? 'Decis\u00E3o sugerida' : 'Decis\u00F5es sugeridas' }}\n </div>\n <div *ngIf=\"clarificationOptions.length === 1\" class=\"clarification-options-hint\">\n Etapa 2 de 4: confirme a melhor op\u00E7\u00E3o para continuar.\n </div>\n <div *ngIf=\"clarificationOptions.length > 1\" class=\"clarification-options-hint\">\n Etapa 2 de 4: selecione a alternativa mais aderente para gerar a proposta.\n </div>\n <div\n *ngIf=\"clarificationResponseType === 'confirm'\"\n class=\"clarification-buttons\"\n [class.list]=\"clarificationPresentation === 'list'\"\n >\n <button\n mat-button\n type=\"button\"\n *ngFor=\"let opt of clarificationOptions; let i = index\"\n (click)=\"onClarificationOptionClick(opt)\"\n class=\"clarification-option\"\n [class.selected]=\"isClarificationSelected(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n </div>\n <div\n *ngIf=\"clarificationSelectionMode === 'multiple'\"\n class=\"clarification-buttons\"\n [class.list]=\"clarificationPresentation === 'list'\"\n >\n <button\n mat-button\n type=\"button\"\n *ngFor=\"let opt of clarificationOptions; let i = index\"\n (click)=\"onClarificationOptionClick(opt)\"\n [class.selected]=\"isClarificationSelected(opt)\"\n class=\"clarification-option\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n </div>\n <div\n *ngIf=\"clarificationResponseType !== 'confirm' && clarificationSelectionMode === 'single' && clarificationPresentation !== 'chips'\"\n class=\"clarification-buttons\"\n [class.list]=\"clarificationPresentation === 'list'\"\n >\n <button\n mat-button\n type=\"button\"\n *ngFor=\"let opt of clarificationOptions; let i = index\"\n (click)=\"onClarificationOptionClick(opt)\"\n class=\"clarification-option\"\n [class.selected]=\"isClarificationSelected(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n </div>\n <div\n *ngIf=\"clarificationResponseType !== 'confirm' && clarificationSelectionMode === 'single' && clarificationPresentation === 'chips'\"\n class=\"clarification-chips\"\n >\n <ng-container *ngFor=\"let opt of clarificationOptions; let i = index\">\n <button\n *ngIf=\"isEndpointOption(opt); else chipOption\"\n mat-button\n type=\"button\"\n class=\"clarification-option clarification-card-button\"\n (click)=\"onClarificationOptionClick(opt)\"\n [class.selected]=\"isClarificationSelected(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n <ng-template #chipOption>\n <mat-chip\n (click)=\"onClarificationOptionClick(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt)\"\n class=\"clarification-chip\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: true }\"></ng-container>\n </mat-chip>\n </ng-template>\n </ng-container>\n </div>\n </div>\n <div\n *ngIf=\"clarificationAllowCustom && clarificationResponseType !== 'context'\"\n class=\"clarification-manual-toggle\"\n >\n <span class=\"clarification-manual-label\">N\u00E3o encontrou o recurso?</span>\n <button mat-button type=\"button\" (click)=\"toggleManualInput()\">\n {{ showManualInput ? 'Ocultar resposta manual' : 'Responder manualmente' }}\n </button>\n </div>\n <div\n *ngIf=\"clarificationAllowCustom && showManualInput && clarificationResponseType !== 'context'\"\n class=\"clarification-free\"\n >\n <input\n type=\"text\"\n [(ngModel)]=\"clarificationFreeText\"\n placeholder=\"Digite sua resposta\u2026\"\n autocomplete=\"off\"\n (keydown.enter)=\"confirmTaskAction()\"\n />\n </div>\n <div\n *ngIf=\"clarificationAllowCustom && showManualInput && clarificationResponseType !== 'context'\"\n class=\"clarification-free-hint\"\n >\n Pressione Enter para enviar.\n </div>\n </div>\n\n <!-- STATE: REVIEW (Diff/Explanation) -->\n <div\n *ngIf=\"state === 'review' && isActiveTab('task')\"\n class=\"review-area assistant-card\"\n id=\"assistant-panel-task\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-task\"\n >\n <div class=\"task-header\" *ngIf=\"!shouldShowThoughtCard()\">\n <div class=\"task-title\">{{ getTaskTitle() }}</div>\n <div class=\"task-subtitle\">{{ getTaskSubtitle() }}</div>\n <div *ngIf=\"clarificationOptions.length > 1 && getTaskSelectionSummary() as selectionSummary\" class=\"task-meta\">\n <span class=\"task-meta-label\">Selecionado:</span>\n <span class=\"task-meta-value\">{{ selectionSummary }}</span>\n </div>\n <div *ngIf=\"shouldShowTaskSteps()\" class=\"task-steps\">\n <span class=\"task-step\">1 Dados</span>\n <span class=\"task-step\">2 Layout</span>\n <span class=\"task-step active\">3 Revis\u00E3o</span>\n </div>\n </div>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div class=\"review-trust\">\n <span class=\"trust-chip\" [matTooltip]=\"getScopeTooltip()\">{{ getScopeLabel() }}</span>\n <span class=\"trust-chip\" [matTooltip]=\"getConfidenceTooltip()\">{{ getConfidenceLabel() }}</span>\n <span class=\"trust-chip risk-chip\" [class.medium]=\"getReviewRiskLevel() === 'm\u00E9dio'\" [class.high]=\"getReviewRiskLevel() === 'alto'\">\n Risco {{ getReviewRiskLevel() }}\n </span>\n </div>\n <div class=\"review-summary\" [class.attention-highlight]=\"highlightReviewDetails\">\n <div class=\"review-summary-title\">Resumo da proposta</div>\n <div class=\"review-summary-line\">{{ getReviewSummary() }}</div>\n </div>\n <div class=\"review-diff\">\n <div class=\"review-diff-title\">Pr\u00E9via de mudan\u00E7as</div>\n <ng-container *ngIf=\"pendingDiff.length; else noDiffPreview\">\n <div class=\"review-diff-summary\">\n <div *ngFor=\"let line of getDiffSummaryLines()\" class=\"review-diff-line\">{{ line }}</div>\n <div *ngIf=\"pendingDiff.length > 3\" class=\"review-diff-more\">\u2026 +{{ pendingDiff.length - 3 }} mudan\u00E7as</div>\n </div>\n <button mat-stroked-button type=\"button\" class=\"review-diff-toggle\" (click)=\"toggleFullDiff()\">\n {{ getDiffToggleLabel() }}\n </button>\n </ng-container>\n <ng-template #noDiffPreview>\n <div class=\"review-diff-empty\">\n N\u00E3o foi poss\u00EDvel gerar um diff estruturado. Revise o resumo e aplique com cautela.\n </div>\n </ng-template>\n <div *ngIf=\"showFullDiff && pendingDiff.length\" class=\"review-diff-full\">\n <div *ngFor=\"let diff of pendingDiff\" class=\"review-diff-block\">\n <div class=\"review-diff-path\">{{ diff.path }}</div>\n <div class=\"review-diff-label\">Antes:</div>\n <pre>{{ diff.before | json }}</pre>\n <div class=\"review-diff-label\">Depois:</div>\n <pre>{{ diff.after | json }}</pre>\n </div>\n </div>\n </div>\n <div class=\"ai-explanation\" *ngIf=\"aiExplanation.trim()\">\n {{ aiExplanation }}\n </div>\n </div>\n\n <!-- STATE: ERROR -->\n <div\n *ngIf=\"state === 'error' && (isActiveTab('task') || (!isTaskMode() && isActiveTab('suggestions')))\"\n class=\"error-area assistant-card\"\n [attr.id]=\"isTaskMode() ? 'assistant-panel-task' : 'assistant-panel-suggestions'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"isTaskMode() ? 'assistant-tab-task' : 'assistant-tab-suggestions'\"\n >\n <div class=\"error-msg\">\n <mat-icon color=\"warn\">error_outline</mat-icon>\n <span>{{ errorMsg }}</span>\n </div>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div *ngIf=\"shouldShowApplyDetails()\" class=\"apply-details\">\n <div class=\"apply-details-header\">\n <div class=\"apply-details-title\">Detalhes da aplica\u00E7\u00E3o</div>\n <div class=\"apply-details-actions\" *ngIf=\"allowManualPatchEdit\">\n <button mat-button type=\"button\" (click)=\"togglePatchPathEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchPathEditorExpanded ? 'Ocultar paths' : 'Editar por path' }}\n </button>\n <button mat-button type=\"button\" (click)=\"togglePatchEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchEditorExpanded ? 'Ocultar editor' : 'Editar patch' }}\n </button>\n </div>\n </div>\n <div class=\"apply-details-meta\" *ngIf=\"lastApplyAt\">\n \u00DAltima tentativa: {{ lastApplyAt | date:'short' }}\n </div>\n <div class=\"apply-paths\" *ngIf=\"getApplyPaths().length\">\n <span class=\"apply-path-chip\" *ngFor=\"let path of getApplyPaths()\">{{ path }}</span>\n </div>\n <div class=\"apply-warnings\" *ngIf=\"applyWarnings.length\">\n <div class=\"apply-warnings-title\">Avisos da aplica\u00E7\u00E3o</div>\n <div class=\"apply-warnings-list\">\n <div *ngFor=\"let warning of applyWarnings\">{{ warning }}</div>\n </div>\n </div>\n <div class=\"patch-path-editor\" *ngIf=\"allowManualPatchEdit && patchPathEditorExpanded\">\n <div class=\"patch-editor-label\">Editar por path</div>\n <div class=\"patch-editor-hint\">Texto simples vira string; para for\u00E7ar texto em true/false/n\u00FAmero, use aspas.</div>\n <div class=\"patch-path-editor-empty\" *ngIf=\"!getEditablePatchPathEdits().length\">\n Nenhum path edit\u00E1vel dispon\u00EDvel para este patch.\n </div>\n <div class=\"patch-path-rows\" *ngIf=\"getEditablePatchPathEdits().length\">\n <div class=\"patch-path-row\" *ngFor=\"let edit of getEditablePatchPathEdits(); let i = index\">\n <div class=\"patch-path-label\">{{ edit.path }}</div>\n <input\n type=\"text\"\n [(ngModel)]=\"patchPathEdits[i].valueText\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n />\n <div *ngIf=\"patchPathEdits[i].error\" class=\"patch-editor-error\">{{ patchPathEdits[i].error }}</div>\n </div>\n </div>\n <div *ngIf=\"patchPathEditorError\" class=\"patch-editor-error\">{{ patchPathEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchPathEdits()\">\n Restaurar valores\n </button>\n <button mat-button type=\"button\" (click)=\"applyPathEditsToPatch()\">\n Aplicar no patch\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyPathEdits()\">\n Reaplicar por path\n </button>\n </div>\n </div>\n <div class=\"patch-editor\" *ngIf=\"allowManualPatchEdit && patchEditorExpanded\">\n <label class=\"patch-editor-label\">Patch JSON</label>\n <textarea\n [(ngModel)]=\"patchEditorText\"\n spellcheck=\"false\"\n ></textarea>\n <div *ngIf=\"patchEditorError\" class=\"patch-editor-error\">{{ patchEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchEditor()\">\n Restaurar original\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyEditedPatch()\">\n Reaplicar patch\n </button>\n </div>\n </div>\n </div>\n <div class=\"review-actions\">\n <button mat-button (click)=\"close()\">Fechar</button>\n <button mat-stroked-button (click)=\"retry()\">Tentar Novamente</button>\n </div>\n </div>\n\n <!-- STATE: SUCCESS -->\n <div\n *ngIf=\"state === 'success' && (isActiveTab('task') || (!isTaskMode() && isActiveTab('suggestions')))\"\n class=\"success-area assistant-card\"\n [attr.id]=\"isTaskMode() ? 'assistant-panel-task' : 'assistant-panel-suggestions'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"isTaskMode() ? 'assistant-tab-task' : 'assistant-tab-suggestions'\"\n >\n <div class=\"success-msg\">\n <mat-icon class=\"success-icon\">check_circle</mat-icon>\n <span>{{ aiExplanation || 'Configura\u00E7\u00E3o atualizada.' }}</span>\n </div>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div *ngIf=\"shouldShowApplyDetails()\" class=\"apply-details\">\n <div class=\"apply-details-header\">\n <div class=\"apply-details-title\">Detalhes da aplica\u00E7\u00E3o</div>\n <div class=\"apply-details-actions\" *ngIf=\"allowManualPatchEdit\">\n <button mat-button type=\"button\" (click)=\"togglePatchPathEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchPathEditorExpanded ? 'Ocultar paths' : 'Editar por path' }}\n </button>\n <button mat-button type=\"button\" (click)=\"togglePatchEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchEditorExpanded ? 'Ocultar editor' : 'Editar e reaplicar' }}\n </button>\n </div>\n </div>\n <div class=\"apply-details-meta\" *ngIf=\"lastApplyAt\">\n Aplicado em {{ lastApplyAt | date:'short' }}\n </div>\n <div class=\"apply-paths\" *ngIf=\"getApplyPaths().length\">\n <span class=\"apply-path-chip\" *ngFor=\"let path of getApplyPaths()\">{{ path }}</span>\n </div>\n <div class=\"apply-warnings\" *ngIf=\"applyWarnings.length\">\n <div class=\"apply-warnings-title\">Avisos da aplica\u00E7\u00E3o</div>\n <div class=\"apply-warnings-list\">\n <div *ngFor=\"let warning of applyWarnings\">{{ warning }}</div>\n </div>\n </div>\n <div class=\"patch-path-editor\" *ngIf=\"allowManualPatchEdit && patchPathEditorExpanded\">\n <div class=\"patch-editor-label\">Editar por path</div>\n <div class=\"patch-editor-hint\">Texto simples vira string; para for\u00E7ar texto em true/false/n\u00FAmero, use aspas.</div>\n <div class=\"patch-path-editor-empty\" *ngIf=\"!getEditablePatchPathEdits().length\">\n Nenhum path edit\u00E1vel dispon\u00EDvel para este patch.\n </div>\n <div class=\"patch-path-rows\" *ngIf=\"getEditablePatchPathEdits().length\">\n <div class=\"patch-path-row\" *ngFor=\"let edit of getEditablePatchPathEdits(); let i = index\">\n <div class=\"patch-path-label\">{{ edit.path }}</div>\n <input\n type=\"text\"\n [(ngModel)]=\"patchPathEdits[i].valueText\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n />\n <div *ngIf=\"patchPathEdits[i].error\" class=\"patch-editor-error\">{{ patchPathEdits[i].error }}</div>\n </div>\n </div>\n <div *ngIf=\"patchPathEditorError\" class=\"patch-editor-error\">{{ patchPathEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchPathEdits()\">\n Restaurar valores\n </button>\n <button mat-button type=\"button\" (click)=\"applyPathEditsToPatch()\">\n Aplicar no patch\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyPathEdits()\">\n Reaplicar por path\n </button>\n </div>\n </div>\n <div class=\"patch-editor\" *ngIf=\"allowManualPatchEdit && patchEditorExpanded\">\n <label class=\"patch-editor-label\">Patch JSON</label>\n <textarea\n [(ngModel)]=\"patchEditorText\"\n spellcheck=\"false\"\n ></textarea>\n <div *ngIf=\"patchEditorError\" class=\"patch-editor-error\">{{ patchEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchEditor()\">\n Restaurar original\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyEditedPatch()\">\n Reaplicar patch\n </button>\n </div>\n </div>\n </div>\n <button mat-button color=\"warn\" (click)=\"undoLastChange()\">\n <mat-icon>undo</mat-icon> Desfazer\n </button>\n </div>\n\n </div>\n\n <div class=\"assistant-footer\">\n <ng-container *ngIf=\"!isTaskMode(); else taskFooter\">\n <button\n mat-icon-button\n type=\"button\"\n class=\"composer-leading composer-leading-btn\"\n [matMenuTriggerFor]=\"assistantQuickMenu\"\n aria-label=\"Abrir a\u00E7\u00F5es r\u00E1pidas\"\n matTooltip=\"A\u00E7\u00F5es r\u00E1pidas\"\n >\n <mat-icon>add</mat-icon>\n </button>\n <input\n #inputEl\n type=\"text\"\n [(ngModel)]=\"userPrompt\"\n [disabled]=\"state === 'processing' || state === 'applying'\"\n placeholder=\"Descreva a altera\u00E7\u00E3o que deseja aplicar\u2026\"\n autocomplete=\"off\"\n />\n <div class=\"send-actions\">\n <button\n mat-icon-button\n class=\"send-btn\"\n *ngIf=\"state === 'listening'\"\n (click)=\"submitPrompt()\"\n [disabled]=\"!userPrompt.trim()\"\n [class.ready]=\"!!userPrompt.trim()\"\n >\n <mat-icon>arrow_upward</mat-icon>\n </button>\n <mat-spinner diameter=\"20\" *ngIf=\"state === 'processing' || state === 'applying'\"></mat-spinner>\n </div>\n </ng-container>\n <ng-template #taskFooter>\n <div class=\"task-footer\">\n <div class=\"task-footer-left\">\n <button mat-button type=\"button\" (click)=\"handleTaskSecondary()\">{{ getTaskCancelLabel() }}</button>\n <button\n *ngIf=\"state === 'review'\"\n mat-stroked-button\n type=\"button\"\n (click)=\"retry()\"\n >\n {{ getTaskSecondaryLabel() }}\n </button>\n </div>\n <div class=\"task-footer-right\">\n <button\n class=\"task-primary-btn\"\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n (click)=\"confirmTaskAction()\"\n [disabled]=\"isTaskPrimaryDisabled()\"\n >\n {{ getTaskPrimaryLabel() }}\n </button>\n <mat-spinner diameter=\"20\" *ngIf=\"state === 'processing' || state === 'applying'\"></mat-spinner>\n </div>\n </div>\n </ng-template>\n </div>\n\n <mat-menu #assistantQuickMenu=\"matMenu\" panelClass=\"assistant-quick-menu-panel\">\n <button mat-menu-item type=\"button\" (click)=\"setActiveTab('chat')\">\n <mat-icon>history</mat-icon>\n <span>Hist\u00F3rico</span>\n </button>\n <button mat-menu-item type=\"button\" (click)=\"setActiveTab('suggestions')\">\n <mat-icon>lightbulb</mat-icon>\n <span>Sugest\u00F5es</span>\n </button>\n <button mat-menu-item type=\"button\" (click)=\"startNewSession()\">\n <mat-icon>add_comment</mat-icon>\n <span>Nova conversa</span>\n </button>\n <button mat-menu-item type=\"button\" (click)=\"refreshSuggestions()\" [disabled]=\"loadingSuggestions\">\n <mat-icon>refresh</mat-icon>\n <span>Atualizar sugest\u00F5es</span>\n </button>\n <button\n mat-menu-item\n type=\"button\"\n (click)=\"restoreDismissedSuggestions()\"\n [disabled]=\"!hasDismissedSuggestions()\"\n >\n <mat-icon>visibility</mat-icon>\n <span>Restaurar sugest\u00F5es ocultas</span>\n </button>\n <button\n mat-menu-item\n type=\"button\"\n class=\"assistant-quick-menu-danger\"\n (click)=\"clearHistory()\"\n [disabled]=\"!historySessions.length\"\n >\n <mat-icon>delete_outline</mat-icon>\n <span>Limpar hist\u00F3rico local</span>\n </button>\n </mat-menu>\n </div>\n\n</ng-template>\n", styles: ["@keyframes assistantPremiumEnter{0%{opacity:0;transform:translate(14px) scale(.99)}to{opacity:1;transform:translate(0) scale(1)}}:host ::ng-deep .ai-assistant-backdrop{background:color-mix(in srgb,var(--md-sys-color-scrim, var(--md-sys-color-shadow, var(--md-sys-color-on-surface))) 42%,transparent);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}::ng-deep .ai-assistant-overlay-pane{max-width:calc(100vw - 24px);max-height:calc(100vh - 24px)}.ai-assistant-panel{box-sizing:border-box;width:min(604px,100vw - 18px);min-height:min(620px,100vh - 18px);max-height:min(820px,100vh - 18px);display:flex;flex-direction:column;overflow:hidden;color:var(--md-sys-color-on-surface);border-left:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));border-top:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));box-shadow:0 18px 48px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 42%,transparent),inset 0 1px color-mix(in srgb,var(--md-sys-color-on-surface) 10%,transparent);background:radial-gradient(circle at 14% 8%,color-mix(in srgb,var(--md-sys-color-primary-container) 38%,transparent) 0%,transparent 44%),linear-gradient(165deg,color-mix(in srgb,var(--md-sys-color-surface-container-highest) 88%,var(--md-sys-color-surface)),var(--md-sys-color-surface));animation:assistantPremiumEnter .24s cubic-bezier(.22,1,.36,1)}.assistant-header{position:relative;display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:12px 16px 10px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));background:linear-gradient(130deg,color-mix(in srgb,var(--md-sys-color-primary-container) 28%,transparent) 0%,transparent 52%),var(--md-sys-color-surface-container-low)}.assistant-header:after{content:\"\";position:absolute;left:16px;right:16px;bottom:-1px;height:1px;background:linear-gradient(90deg,color-mix(in srgb,var(--md-sys-color-primary) 55%,transparent),transparent);pointer-events:none}.assistant-header .assistant-header__left{min-width:0;display:flex;align-items:flex-start;gap:12px}.assistant-header .magic-icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:30px;height:30px;font-size:18px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent);box-shadow:0 4px 12px color-mix(in srgb,var(--md-sys-color-primary) 26%,transparent)}.assistant-header .assistant-title-group{min-width:0;display:flex;flex-direction:column;gap:4px}.assistant-header .assistant-title{font-size:15px;font-weight:700;letter-spacing:.01em;overflow-wrap:anywhere}.assistant-header .assistant-subtitle{display:block;font-size:11px;line-height:1.25;color:color-mix(in srgb,var(--md-sys-color-on-surface) 70%,var(--md-sys-color-on-surface-variant));overflow-wrap:anywhere}.assistant-header-chips{display:inline-flex;align-items:center;flex-wrap:wrap;gap:6px}.assistant-header .mode-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 28%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 64%,var(--md-sys-color-surface-container));color:color-mix(in srgb,var(--md-sys-color-on-primary-container) 85%,var(--md-sys-color-on-surface));font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.assistant-header .mode-chip:before{content:\"\";width:6px;height:6px;border-radius:999px;background:currentColor;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 16%,transparent)}.assistant-header .mode-chip.mock{border-color:color-mix(in srgb,var(--md-sys-color-error) 34%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-error-container) 72%,var(--md-sys-color-surface-container));color:color-mix(in srgb,var(--md-sys-color-on-error-container) 85%,var(--md-sys-color-error))}.assistant-header .policy-chip{padding:2px 8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 84%,transparent);background:var(--md-sys-color-surface-container-high);font-size:10px;letter-spacing:.02em;font-weight:600}.assistant-header .policy-chip.strict{border-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 56%,var(--md-sys-color-surface-container-high))}.assistant-status{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));border-radius:0;background:linear-gradient(160deg,color-mix(in srgb,var(--md-sys-color-primary-container) 24%,transparent),transparent 48%),var(--md-sys-color-surface-container-low)}.assistant-status-dot{width:8px;height:8px;margin-top:6px;border-radius:999px;flex:0 0 auto;background:var(--md-sys-color-primary);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.assistant-status.warning .assistant-status-dot{background:var(--md-sys-color-error);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-error) 16%,transparent)}.assistant-status.success .assistant-status-dot{background:color-mix(in srgb,var(--md-sys-color-primary) 74%,var(--md-sys-color-tertiary, var(--md-sys-color-primary)))}.assistant-status-content{min-width:0;display:flex;flex-direction:column;gap:2px}.assistant-status-label{display:flex;align-items:center;flex-wrap:wrap;gap:4px;font-size:12px;font-weight:700;line-height:1.35}.assistant-status-detail{font-size:11px;line-height:1.4;color:var(--md-sys-color-on-surface-variant);overflow-wrap:anywhere}.assistant-status-mode{padding:1px 6px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary-container) 72%,transparent);color:var(--md-sys-color-on-primary-container);font-size:10px;font-weight:700}.assistant-nav{padding:8px 16px 0}.assistant-nav .assistant-tabs{display:flex;align-items:center;gap:4px;border-radius:12px;padding:5px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:linear-gradient(155deg,color-mix(in srgb,var(--md-sys-color-primary-container) 14%,transparent),transparent 58%),var(--md-sys-color-surface-container-low)}.assistant-nav .assistant-tab{appearance:none;min-width:0;min-height:30px;border:0;border-radius:8px;padding:0 10px;background:transparent;color:var(--md-sys-color-on-surface-variant);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;flex:1 1 0;font-size:11px;font-weight:700;letter-spacing:.01em}.assistant-nav .assistant-tab.active{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 34%,transparent),0 6px 12px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}.assistant-section,.assistant-card{border-radius:14px;border-color:color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant));background:linear-gradient(160deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 50%),var(--md-sys-color-surface-container-lowest);box-shadow:0 6px 18px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 12%,transparent),inset 0 1px color-mix(in srgb,var(--md-sys-color-on-surface) 7%,transparent)}.suggestions-hero{margin:2px 0 4px;padding:10px 12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:linear-gradient(120deg,color-mix(in srgb,var(--md-sys-color-primary-container) 34%,transparent) 0%,transparent 52%),var(--md-sys-color-surface-container-low)}.suggestions-hero__label{font-size:10px;letter-spacing:.08em;text-transform:uppercase;font-weight:700;color:var(--md-sys-color-on-surface-variant);opacity:.88}.suggestions-hero__title{margin-top:2px;font-size:13px;font-weight:700;color:var(--md-sys-color-on-surface)}.suggestions-hero__detail{margin-top:4px;font-size:12px;line-height:1.4;color:color-mix(in srgb,var(--md-sys-color-on-surface) 78%,var(--md-sys-color-on-surface-variant))}.suggestions-content .suggestion-item{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;padding:12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));border-color:color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));background:linear-gradient(140deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 56%),var(--md-sys-color-surface-container-lowest);transition:transform .2s cubic-bezier(.25,.8,.25,1),border-color .2s ease,box-shadow .2s ease}.suggestions-list{display:flex;flex-direction:column;gap:10px}.suggestion-copy{min-width:0;display:grid;gap:4px}.suggestion-main{min-width:0;display:flex;align-items:center;flex-wrap:wrap;gap:8px}.suggestion-icon{width:18px;height:18px;font-size:18px;flex:0 0 auto;color:var(--md-sys-color-primary)}.suggestion-label{min-width:0;font-size:13px;font-weight:700;line-height:1.35;overflow-wrap:anywhere}.suggestion-group{padding:2px 6px;border-radius:999px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:10px;font-weight:700;line-height:1.2}.suggestion-desc{font-size:12px;line-height:1.35;color:var(--md-sys-color-on-surface-variant);overflow-wrap:anywhere}.suggestion-actions{display:inline-flex;align-items:center;justify-content:flex-end;gap:4px}.suggestion-action-btn,.suggestion-arrow{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;color:var(--md-sys-color-on-surface-variant)}.suggestions-content .suggestion-item:hover,.suggestions-content .suggestion-item:focus-visible{border-color:color-mix(in srgb,var(--md-sys-color-primary) 58%,var(--md-sys-color-outline-variant));box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 28%,transparent);transform:translateY(-2px) scale(1.01)}.assistant-footer{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px;padding:10px 16px;border-top-color:color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant));background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 56%),var(--md-sys-color-surface-container-low)}.assistant-footer .task-footer{grid-column:1/-1}.composer-leading{width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 30%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 70%,var(--md-sys-color-surface-container-low));color:var(--md-sys-color-primary);box-shadow:0 4px 10px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.composer-leading mat-icon{width:16px;height:16px;font-size:16px}.assistant-footer input{box-sizing:border-box;width:100%;min-width:0;height:34px;border:1px solid;border-radius:8px;padding:0 12px;color:var(--md-sys-color-on-surface);border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface);box-shadow:inset 0 1px 2px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 12%,transparent)}.send-actions{min-width:36px;display:inline-flex;align-items:center;justify-content:center}.assistant-footer .send-btn{--mdc-icon-button-icon-size: 20px;--mdc-icon-button-state-layer-size: 36px;--mat-icon-button-state-layer-size: 36px;width:36px;height:36px;padding:8px;line-height:1;display:inline-flex;align-items:center;justify-content:center;border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface)}.assistant-footer .send-btn mat-icon{width:20px;height:20px;margin:0;font-size:20px;line-height:20px;display:inline-flex;align-items:center;justify-content:center}.assistant-footer .send-btn.ready{background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary-container) 84%,var(--md-sys-color-primary)),var(--md-sys-color-primary));color:var(--md-sys-color-on-primary);box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}.task-primary-btn{border-radius:12px;box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 24%,transparent)}@media(max-width:959px){.assistant-header{padding:12px 12px 10px}.assistant-section,.assistant-card{margin:4px 12px 10px}.assistant-header:after{left:12px;right:12px}.assistant-header .assistant-title{font-size:14px}.assistant-header .assistant-subtitle{font-size:10px}.assistant-footer{grid-template-columns:minmax(0,1fr) auto}.composer-leading{display:none}}.assistant-thought{margin-top:0;gap:10px;border-color:color-mix(in srgb,var(--md-sys-color-primary) 26%,var(--md-sys-color-outline-variant));background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 28%,transparent),transparent 58%),var(--md-sys-color-surface-container-low)}.assistant-thought-meta{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.assistant-thought-meta mat-icon{width:15px;height:15px;font-size:15px;color:var(--md-sys-color-primary)}.assistant-thought-summary{font-size:13px;line-height:1.45;color:var(--md-sys-color-on-surface)}.assistant-thought-plan{border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 32%,var(--md-sys-color-outline-variant));border-radius:12px;padding:10px;background:var(--md-sys-color-surface-container-lowest);display:flex;flex-direction:column;gap:8px}.assistant-thought-plan-title{font-size:13px;font-weight:700;color:var(--md-sys-color-on-surface)}.assistant-thought-plan-actions{display:grid;grid-template-columns:repeat(auto-fit,minmax(0,1fr));gap:8px}.assistant-thought-plan-actions .mdc-button{min-width:0}.assistant-thought-plan-hint{font-size:11px;color:var(--md-sys-color-on-surface-variant);line-height:1.35}.assistant-thought-checklist{display:flex;flex-direction:column;gap:5px}.assistant-thought-checklist-item{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.assistant-thought-checklist-item mat-icon{width:14px;height:14px;font-size:14px}.assistant-thought-shimmer{display:flex;flex-direction:column;gap:8px;margin-top:4px}.assistant-thought-shimmer .shimmer-line{height:12px;border-radius:4px;position:relative;overflow:hidden;background:var(--md-sys-color-surface-container-highest)}.assistant-thought-shimmer .shimmer-line:after{content:\"\";position:absolute;inset:0;transform:translate(-100%);background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent),transparent);animation:assistantShimmerEffect 1.5s infinite}.assistant-thought-shimmer .shimmer-line-1{width:85%}.assistant-thought-shimmer .shimmer-line-2{width:92%}.assistant-thought-shimmer .shimmer-line-3{width:64%}@keyframes assistantShimmerEffect{to{transform:translate(100%)}}.assistant-flow{grid-template-columns:1fr;gap:6px}.flow-step{display:flex;align-items:flex-start;gap:8px;text-align:left;padding:7px 8px;border-radius:10px}.flow-step-index{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);flex:0 0 auto}.flow-step-content{min-width:0;display:flex;flex-direction:column;gap:1px}.flow-step-label{font-size:11px;font-weight:700;color:var(--md-sys-color-on-surface)}.flow-step-detail{font-size:11px;color:var(--md-sys-color-on-surface-variant);line-height:1.3}.flow-step.active .flow-step-index{border-color:color-mix(in srgb,var(--md-sys-color-primary) 60%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 85%,var(--md-sys-color-surface-container-highest));color:var(--md-sys-color-on-primary-container)}.flow-step.done .flow-step-index{border-color:transparent;background:color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-surface-container-highest))}.task-timeline{margin-top:4px;display:grid;gap:6px}.task-timeline-item{display:flex;align-items:flex-start;gap:8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;padding:6px 8px;background:var(--md-sys-color-surface-container-low);opacity:.76}.task-timeline-item.active{opacity:1;border-color:color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 34%,var(--md-sys-color-surface-container-low))}.task-timeline-item.done{opacity:.9;border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant))}.task-timeline-dot{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);flex:0 0 auto}.task-timeline-copy{min-width:0;display:flex;flex-direction:column;gap:1px}.task-timeline-title{font-size:11px;font-weight:700;color:var(--md-sys-color-on-surface)}.task-timeline-detail{font-size:11px;line-height:1.3;color:var(--md-sys-color-on-surface-variant)}.clarification-decision-head{display:inline-flex;align-items:center;width:100%;gap:6px;padding:8px 12px 0;box-sizing:border-box}.clarification-decision-index{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 35%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 68%,transparent)}.clarification-decision-type{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant);opacity:.92}.clarification-decision-state{font-size:10px;font-weight:700;color:var(--md-sys-color-primary)}.clarification-option{border-color:color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant));background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 55%),var(--md-sys-color-surface-container-low)}.clarification-option.selected{border-color:color-mix(in srgb,var(--md-sys-color-primary) 56%,var(--md-sys-color-outline-variant));background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 44%,transparent),transparent 62%),var(--md-sys-color-surface-container);box-shadow:0 8px 16px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}.clarification-options-block{overflow:visible;padding-right:4px}@media(max-width:959px){.assistant-thought-plan-actions{grid-template-columns:1fr}}.ai-assistant-panel{width:min(604px,100vw - 18px)}.assistant-section,.assistant-card{margin:0;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant))}.assistant-section{min-height:0;padding:14px 16px;overflow:visible}.assistant-card{min-height:0;padding:14px 16px 16px;overflow:visible}.suggestions-area,.assistant-history,.loading-suggestions{overflow-y:auto;overflow-x:hidden}.section-header{min-width:0;display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}.section-title{min-width:0;font-size:14px;font-weight:700;line-height:1.35;overflow-wrap:anywhere}.suggestions-actions{flex:0 0 auto;display:inline-flex;align-items:center}.review-area,.error-area,.success-area,.clarification-area{padding:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:auto}.review-trust{margin:2px 0 4px}.trust-chip{max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.warnings-area,.review-summary,.review-diff,.apply-details,.clarification-options-block,.clarification-manual-toggle{width:100%;max-width:100%;min-width:0}.review-diff{padding:12px 14px}.review-diff-full{padding:10px;overflow-x:auto}.review-diff-block{min-width:0}.review-diff-block pre{overflow-wrap:anywhere;word-break:break-word}.clarification-options-block{padding:4px 2px 10px}.clarification-option{line-height:normal}:host ::ng-deep .clarification-option .mdc-button__label{line-height:1.35!important}.clarification-plain-label{padding:6px 12px 12px;font-size:14px;line-height:1.35}.clarification-manual-toggle{margin-top:10px;padding:10px 2px 0;flex-wrap:wrap;row-gap:6px}.suggestions-content .suggestion-actions{gap:6px}.suggestions-content .suggestion-arrow,.suggestions-content .suggestion-action-btn mat-icon,.suggestions-content .suggestion-arrow mat-icon{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep .assistant-quick-menu-panel{min-width:244px;max-width:min(90vw,320px);border-radius:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 26%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface-container-high);box-shadow:0 12px 28px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 32%,transparent),inset 0 1px color-mix(in srgb,var(--md-sys-color-on-surface) 6%,transparent);overflow:hidden;padding:6px}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item{min-height:36px;border-radius:10px;color:var(--md-sys-color-on-surface);font-size:12px;font-weight:500;margin:1px 0}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item .mat-icon{color:color-mix(in srgb,var(--md-sys-color-on-surface) 82%,var(--md-sys-color-on-surface-variant))}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item:hover:not([disabled]){background:color-mix(in srgb,var(--md-sys-color-primary-container) 45%,transparent)}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:-2px}:host ::ng-deep .assistant-quick-menu-panel .assistant-quick-menu-danger{color:var(--md-sys-color-error)}:host ::ng-deep .assistant-quick-menu-panel .assistant-quick-menu-danger .mat-icon{color:var(--md-sys-color-error)}@media(max-width:959px){::ng-deep .ai-assistant-overlay-pane{top:12px!important;left:12px!important;width:calc(100vw - 24px)!important;height:calc(100vh - 24px)!important;max-width:calc(100vw - 24px);max-height:calc(100vh - 24px);transform:none!important}.ai-assistant-panel{width:calc(100vw - 48px);min-height:min(620px,100vh - 48px);max-height:calc(100vh - 48px)}.assistant-section,.assistant-card{margin:0}.assistant-card,.assistant-section{padding:12px}}.ai-trigger-btn{--mdc-icon-button-state-layer-size: 36px;width:36px;height:36px;border-radius:8px;background:color-mix(in srgb,var(--md-sys-color-primary) 9%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 22%,transparent);color:var(--md-sys-color-primary);opacity:1}.ai-trigger-btn:hover:not(:disabled){background:color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent);border-color:color-mix(in srgb,var(--md-sys-color-primary) 36%,transparent);color:var(--md-sys-color-on-primary-container)}.ai-trigger-btn:focus-visible,.assistant-close-btn:focus-visible,.assistant-tab:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.assistant-close-btn{width:28px;height:28px;color:var(--md-sys-color-on-surface-variant)}.assistant-close-btn:hover:not(:disabled){background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface)}.assistant-body{padding:12px 16px;overflow:hidden;min-height:0;flex:1;display:flex;flex-direction:column;gap:12px}.assistant-body>*{min-height:0}@media(max-width:959px){.assistant-nav{padding:8px 12px 0}.assistant-body,.assistant-footer{padding:10px 12px}.suggestions-content .suggestion-item{grid-template-columns:1fr;align-items:start}.suggestion-actions{justify-content:flex-start}}\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: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i3.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i3.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i3.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { 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: OverlayModule }, { kind: "directive", type: i5.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i5.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i6.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "ngmodule", type: MatDialogModule }, { 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"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i16.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i11.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i11.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i11.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "pipe", type: i3.JsonPipe, name: "json" }, { kind: "pipe", type: i3.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5825
6673
|
}
|
|
5826
6674
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantComponent, decorators: [{
|
|
5827
6675
|
type: Component,
|
|
@@ -5837,7 +6685,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
5837
6685
|
MatTooltipModule,
|
|
5838
6686
|
MatChipsModule,
|
|
5839
6687
|
MatMenuModule,
|
|
5840
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Trigger Button -->\n<button \n mat-icon-button \n cdkOverlayOrigin \n #trigger=\"cdkOverlayOrigin\"\n #triggerBtn\n (click)=\"open()\"\n [disabled]=\"isOpen\"\n class=\"ai-trigger-btn\"\n matTooltip=\"Assistente de Configura\u00E7\u00E3o\"\n aria-label=\"Abrir Assistente IA\">\n <mat-icon>auto_awesome</mat-icon>\n</button>\n\n<!-- Overlay Template -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayViewportMargin]=\"12\"\n cdkConnectedOverlayPanelClass=\"ai-assistant-overlay-pane\"\n cdkConnectedOverlayBackdropClass=\"ai-assistant-backdrop\"\n (backdropClick)=\"close()\"\n (detach)=\"close()\">\n\n <div\n class=\"ai-assistant-panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Assistente de Configura\u00E7\u00E3o\"\n [attr.aria-busy]=\"isBusyState() ? 'true' : null\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n (keydown)=\"onKeydown($event)\"\n >\n \n <!-- HEADER -->\n <div class=\"assistant-header\">\n <div class=\"assistant-header__left\">\n <mat-icon class=\"magic-icon\">auto_awesome</mat-icon>\n <div class=\"assistant-title-group\">\n <div class=\"assistant-title\">Assistente de Configura\u00E7\u00E3o</div>\n <div class=\"assistant-subtitle\">Copiloto contextual para ajustes guiados</div>\n <div class=\"assistant-header-chips\">\n <span\n class=\"mode-chip\"\n [class.mock]=\"mockMode\"\n [matTooltip]=\"mockMode ? 'Sem chave de API: respostas de demonstra\u00E7\u00E3o' : 'Conectado ao assistente configurado'\"\n >\n {{ mockMode ? 'Mock' : 'Conectado' }}\n </span>\n <span\n class=\"policy-chip\"\n [class.strict]=\"isStrictRiskPolicy()\"\n [matTooltip]=\"getRiskPolicyTooltip()\"\n >\n {{ getRiskPolicyLabel() }}\n </span>\n </div>\n </div>\n </div>\n <div class=\"assistant-header__right\">\n <button\n mat-icon-button\n type=\"button\"\n class=\"assistant-close-btn\"\n (click)=\"close()\"\n aria-label=\"Fechar assistente\"\n matTooltip=\"Fechar assistente\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n <div\n class=\"assistant-status\"\n [class.processing]=\"isBusyState()\"\n [class.pending]=\"state === 'clarification'\"\n [class.warning]=\"state === 'error'\"\n [class.success]=\"state === 'review' || state === 'success'\"\n [class.compact]=\"!shouldShowSystemStatusDetail()\"\n role=\"status\"\n [attr.aria-live]=\"getSystemStatusAriaLive()\"\n aria-atomic=\"true\"\n >\n <span class=\"assistant-status-dot\" aria-hidden=\"true\"></span>\n <div class=\"assistant-status-content\">\n <div class=\"assistant-status-label\">\n <span class=\"assistant-status-label-prefix\">Status:</span>\n <span>{{ getSystemStatusLabel() }}</span>\n <span *ngIf=\"shouldShowSnapshotFallbackBadge()\" class=\"assistant-status-mode\">Snapshot</span>\n </div>\n <div *ngIf=\"shouldShowSystemStatusDetail()\" class=\"assistant-status-detail\">{{ getSystemStatusDetail() }}</div>\n </div>\n </div>\n <div class=\"assistant-flow\" *ngIf=\"shouldShowTaskFlow()\" role=\"list\" aria-label=\"Fluxo da proposta\">\n <div\n class=\"flow-step\"\n role=\"listitem\"\n *ngFor=\"let step of flowSteps\"\n [class.active]=\"getFlowStepState(step.step) === 'active'\"\n [class.done]=\"getFlowStepState(step.step) === 'done'\"\n >\n <span class=\"flow-step-index\">{{ step.step }}</span>\n <span class=\"flow-step-content\">\n <span class=\"flow-step-label\">{{ step.label }}</span>\n <span class=\"flow-step-detail\">{{ getFlowStepDetail(step.step) }}</span>\n </span>\n </div>\n </div>\n <div class=\"assistant-nav\">\n <div\n class=\"assistant-tabs\"\n role=\"tablist\"\n aria-label=\"Se\u00E7\u00F5es do assistente\"\n (keydown)=\"onTabsKeydown($event)\"\n >\n <button\n class=\"assistant-tab\"\n type=\"button\"\n *ngIf=\"isTaskMode()\"\n id=\"assistant-tab-task\"\n role=\"tab\"\n aria-label=\"Proposta atual\"\n [attr.aria-selected]=\"isActiveTab('task')\"\n aria-controls=\"assistant-panel-task\"\n [attr.tabindex]=\"isActiveTab('task') ? 0 : -1\"\n [class.active]=\"isActiveTab('task')\"\n (click)=\"setActiveTab('task')\"\n >\n Proposta\n <span *ngIf=\"hasPendingClarification() && !isActiveTab('task')\" class=\"assistant-tab-badge\">1</span>\n </button>\n <button\n class=\"assistant-tab\"\n type=\"button\"\n id=\"assistant-tab-chat\"\n role=\"tab\"\n aria-label=\"Hist\u00F3rico\"\n [attr.aria-selected]=\"isActiveTab('chat')\"\n aria-controls=\"assistant-panel-chat\"\n [attr.tabindex]=\"isActiveTab('chat') ? 0 : -1\"\n [class.active]=\"isActiveTab('chat')\"\n (click)=\"setActiveTab('chat')\"\n >\n Hist\u00F3rico\n </button>\n <button\n class=\"assistant-tab\"\n type=\"button\"\n id=\"assistant-tab-suggestions\"\n role=\"tab\"\n aria-label=\"Sugest\u00F5es de melhoria\"\n [attr.aria-selected]=\"isActiveTab('suggestions')\"\n aria-controls=\"assistant-panel-suggestions\"\n [attr.tabindex]=\"isActiveTab('suggestions') ? 0 : -1\"\n [class.active]=\"isActiveTab('suggestions')\"\n (click)=\"setActiveTab('suggestions')\"\n >\n Sugest\u00F5es\n </button>\n </div>\n </div>\n\n <!-- BODY: Dynamic Content based on State -->\n <div class=\"assistant-body\">\n <div class=\"assistant-thought assistant-section\" *ngIf=\"shouldShowThoughtCard()\">\n <div class=\"assistant-thought-meta\">\n <mat-icon>psychology</mat-icon>\n <span>{{ getThoughtTimingLabel() }}</span>\n </div>\n <div class=\"assistant-thought-summary\">{{ getThoughtSummary() }}</div>\n <div class=\"assistant-thought-plan\">\n <div class=\"assistant-thought-plan-title\">{{ getThoughtPlanTitle() }}</div>\n <div class=\"assistant-thought-plan-actions\">\n <button\n mat-stroked-button\n type=\"button\"\n class=\"thought-action-details\"\n (click)=\"openThoughtDetails()\"\n [attr.aria-label]=\"getThoughtDetailsLabel()\"\n [matTooltip]=\"getThoughtDetailsTooltip()\"\n >\n {{ getThoughtDetailsLabel() }}\n </button>\n <button\n *ngIf=\"shouldShowThoughtPreviewAction()\"\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n class=\"thought-action-preview\"\n (click)=\"openThoughtPreview()\"\n [matTooltip]=\"getThoughtPreviewTooltip()\"\n >\n Pr\u00E9via\n </button>\n </div>\n <div class=\"assistant-thought-plan-hint\">{{ getThoughtActionHint() }}</div>\n <ng-container *ngIf=\"getThoughtChecklist() as thoughtChecklist\">\n <div class=\"assistant-thought-checklist\" *ngIf=\"thoughtChecklist.length\">\n <div *ngFor=\"let item of thoughtChecklist\" class=\"assistant-thought-checklist-item\">\n <mat-icon>radio_button_unchecked</mat-icon>\n <span>{{ item }}</span>\n </div>\n </div>\n </ng-container>\n </div>\n </div>\n <div *ngIf=\"processingInfoVisible\" class=\"processing-banner\">\n <mat-spinner diameter=\"16\"></mat-spinner>\n <span>{{ aiExplanation || 'Analisando solicita\u00E7\u00E3o e preparando proposta...' }}</span>\n <button mat-button type=\"button\" class=\"processing-retry\" (click)=\"retryProcessing()\">Tentar novamente</button>\n </div>\n <div\n class=\"assistant-history assistant-section\"\n *ngIf=\"historyContext && isActiveTab('chat')\"\n id=\"assistant-panel-chat\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-chat\"\n >\n <div class=\"section-header\">\n <div class=\"section-heading\">\n <div class=\"section-title\">Hist\u00F3rico</div>\n <div class=\"section-subtitle\">Sess\u00F5es recentes, pedidos reaproveit\u00E1veis e desfazer r\u00E1pido.</div>\n </div>\n <div class=\"history-actions\" role=\"group\" aria-label=\"A\u00E7\u00F5es do hist\u00F3rico\">\n <button\n mat-stroked-button\n type=\"button\"\n class=\"history-action-btn\"\n (click)=\"historyExpanded = !historyExpanded\"\n [matTooltip]=\"historyExpanded ? 'Recolher hist\u00F3rico' : 'Expandir hist\u00F3rico'\"\n [attr.aria-label]=\"historyExpanded ? 'Recolher hist\u00F3rico' : 'Expandir hist\u00F3rico'\"\n [disabled]=\"!historyWarnings.length && !historySessions.length && !activeHistoryMessages.length\"\n >\n <mat-icon>{{ historyExpanded ? 'expand_less' : 'expand_more' }}</mat-icon>\n <span>{{ historyExpanded ? 'Recolher' : 'Expandir' }}</span>\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n class=\"history-action-btn\"\n (click)=\"startNewSession()\"\n matTooltip=\"Nova conversa\"\n aria-label=\"Nova conversa\"\n [disabled]=\"isBusyState()\"\n >\n <mat-icon>add_comment</mat-icon>\n <span>Nova conversa</span>\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"clearHistory()\"\n matTooltip=\"Limpar hist\u00F3rico local\"\n aria-label=\"Limpar hist\u00F3rico local\"\n class=\"history-action-btn history-action-btn--danger\"\n [disabled]=\"!historySessions.length\"\n >\n <mat-icon>delete_outline</mat-icon>\n <span>Limpar</span>\n </button>\n </div>\n </div>\n <div *ngIf=\"historyUndoDeleteSession\" class=\"history-undo\">\n <span>Sess\u00E3o removida.</span>\n <button mat-button type=\"button\" (click)=\"undoRemoveHistorySession()\">\n Desfazer\n </button>\n </div>\n\n <div *ngIf=\"historyExpanded && historyWarnings.length\" class=\"history-warnings\">\n <mat-icon>info</mat-icon>\n <div class=\"history-warnings-list\">\n <div *ngFor=\"let warning of historyWarnings\">{{ warning }}</div>\n <div class=\"history-warnings-hint\">\n Configure headers via API_CONFIG_STORAGE_OPTIONS no host.\n </div>\n </div>\n </div>\n\n <div *ngIf=\"historyExpanded && historySessions.length\" class=\"history-sessions\">\n <div\n class=\"history-session\"\n *ngFor=\"let session of historySessions\"\n role=\"button\"\n tabindex=\"0\"\n (click)=\"selectHistorySession(session.id)\"\n (keydown)=\"onHistorySessionCardKeydown($event, session.id)\"\n [class.active]=\"session.id === activeHistorySession?.id\"\n [matTooltip]=\"getHistorySessionTooltip(session)\"\n [matTooltipDisabled]=\"!session.componentType && !session.componentId\"\n >\n <div class=\"history-session-main\">\n <span class=\"history-session-title\">{{ session.title }}</span>\n <div class=\"history-session-main-right\">\n <span class=\"history-session-time\">{{ session.updatedAt | date:'short' }}</span>\n <div class=\"history-session-tools\">\n <button\n mat-icon-button\n type=\"button\"\n class=\"history-session-tool\"\n (click)=\"reuseHistorySessionPrompt(session.id, $event)\"\n matTooltip=\"Reusar \u00FAltimo pedido\"\n aria-label=\"Reusar \u00FAltimo pedido\"\n >\n <mat-icon>edit_note</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n class=\"history-session-tool history-session-tool--danger\"\n (click)=\"removeHistorySession(session.id, $event)\"\n matTooltip=\"Excluir sess\u00E3o\"\n aria-label=\"Excluir sess\u00E3o\"\n >\n <mat-icon>delete_outline</mat-icon>\n </button>\n </div>\n </div>\n </div>\n <div class=\"history-session-meta\">\n <span *ngIf=\"session.componentType\" class=\"history-chip\">{{ session.componentType }}</span>\n <span *ngIf=\"session.componentId\" class=\"history-chip\">{{ session.componentId }}</span>\n </div>\n </div>\n </div>\n <div *ngIf=\"historyExpanded && !historySessions.length\" class=\"history-empty\">\n Nenhuma sess\u00E3o salva ainda.\n </div>\n\n <div *ngIf=\"historyExpanded && activeHistoryMessages.length\" class=\"history-messages\">\n <div\n *ngIf=\"activeHistoryTotalMessages > activeHistoryMessages.length\"\n class=\"history-messages-hint\"\n >\n Mostrando \u00FAltimas {{ activeHistoryMessages.length }} de {{ activeHistoryTotalMessages }} mensagens.\n </div>\n <div\n *ngFor=\"let msg of activeHistoryMessages\"\n class=\"history-message\"\n [class.user]=\"msg.role === 'user'\"\n [class.assistant]=\"msg.role === 'assistant'\"\n >\n <div class=\"history-message-header\">\n <span class=\"history-message-role\">{{ msg.role === 'user' ? 'Voc\u00EA' : 'Assistente' }}</span>\n <span class=\"history-message-time\">{{ msg.createdAt | date:'shortTime' }}</span>\n <span\n *ngIf=\"msg.context?.usedRag\"\n class=\"history-rag\"\n matTooltip=\"Resposta baseada em contexto recuperado (RAG)\"\n >RAG</span>\n </div>\n <div class=\"history-message-text\">{{ msg.text }}</div>\n </div>\n </div>\n <div *ngIf=\"historyExpanded && historySessions.length && !activeHistoryMessages.length\" class=\"history-empty history-empty--panel\">\n Selecione uma sess\u00E3o para visualizar as mensagens.\n </div>\n <div class=\"history-helper\" *ngIf=\"historyExpanded\">\n O hist\u00F3rico \u00E9 local ao usu\u00E1rio e ao componente atual.\n </div>\n </div>\n\n <div\n class=\"loading-suggestions assistant-section\"\n *ngIf=\"loadingSuggestions && isActiveTab('suggestions')\"\n id=\"assistant-panel-suggestions\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-suggestions\"\n >\n <mat-spinner diameter=\"20\"></mat-spinner>\n <span>Carregando sugest\u00F5es de melhoria...</span>\n </div>\n \n <!-- STATE: LISTENING (Suggestions) -->\n <div\n *ngIf=\"state === 'listening' && isActiveTab('suggestions')\"\n class=\"suggestions-area assistant-section\"\n id=\"assistant-panel-suggestions\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-suggestions\"\n >\n <div class=\"section-header\">\n <div class=\"section-title\">Sugest\u00F5es de melhoria</div>\n <div class=\"suggestions-actions\">\n <button mat-icon-button type=\"button\" (click)=\"refreshSuggestions()\" [disabled]=\"loadingSuggestions\" matTooltip=\"Atualizar sugest\u00F5es\">\n <mat-icon>refresh</mat-icon>\n </button>\n </div>\n </div>\n <div class=\"suggestions-hero\" *ngIf=\"!loadingSuggestions\">\n <div class=\"suggestions-hero__label\">Contexto ativo</div>\n <div class=\"suggestions-hero__title\">{{ adapter.componentName || 'Componente atual' }}</div>\n <div class=\"suggestions-hero__detail\">{{ getSystemStatusDetail() }}</div>\n </div>\n <div *ngIf=\"suggestionsWarnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of suggestionsWarnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div class=\"suggestions-content\" *ngIf=\"!loadingSuggestions && (richSuggestions.length || hasDismissedSuggestions())\">\n <div class=\"suggestions-filter\" *ngIf=\"hasDismissedSuggestions()\">\n <span>{{ getDismissedSuggestionCount() }} oculta(s)</span>\n <button mat-button type=\"button\" (click)=\"restoreDismissedSuggestions()\">\n Restaurar\n </button>\n </div>\n <div class=\"suggestions-list\" *ngIf=\"getVisibleSuggestions().length; else allSuggestionsHidden\">\n <div\n class=\"suggestion-item\"\n *ngFor=\"let sug of getVisibleSuggestions()\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'Selecionar sugest\u00E3o: ' + sug.label\"\n (click)=\"selectSuggestion(sug)\"\n (keydown)=\"onSuggestionCardKeydown($event, sug)\"\n >\n <div class=\"suggestion-copy\">\n <div class=\"suggestion-main\">\n <mat-icon *ngIf=\"sug.icon\" class=\"suggestion-icon\">{{ sug.icon }}</mat-icon>\n <span class=\"suggestion-label\">{{ sug.label }}</span>\n <span *ngIf=\"sug.group\" class=\"suggestion-group\">{{ sug.group }}</span>\n </div>\n <div *ngIf=\"sug.description\" class=\"suggestion-desc\">{{ sug.description }}</div>\n </div>\n <div class=\"suggestion-actions\">\n <span class=\"suggestion-arrow\" aria-hidden=\"true\">\n <mat-icon>chevron_right</mat-icon>\n </span>\n <button\n mat-icon-button\n type=\"button\"\n class=\"suggestion-action-btn\"\n (click)=\"prepareSuggestionPrompt(sug, $event)\"\n matTooltip=\"Refinar pedido\"\n aria-label=\"Refinar pedido\"\n >\n <mat-icon>edit</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n class=\"suggestion-action-btn suggestion-action-btn--danger\"\n (click)=\"dismissSuggestion(sug, $event)\"\n matTooltip=\"Ocultar sugest\u00E3o\"\n aria-label=\"Ocultar sugest\u00E3o\"\n >\n <mat-icon>visibility_off</mat-icon>\n </button>\n </div>\n </div>\n </div>\n <ng-template #allSuggestionsHidden>\n <div class=\"suggestions-empty suggestions-empty--inline\">\n Todas as sugest\u00F5es foram ocultadas.\n </div>\n </ng-template>\n </div>\n <div class=\"suggestions-empty\" *ngIf=\"!loadingSuggestions && !richSuggestions.length\">\n Nenhuma sugest\u00E3o dispon\u00EDvel no momento.\n </div>\n <div class=\"suggestions-helper\" *ngIf=\"!loadingSuggestions && !richSuggestions.length\">\n Selecione uma sugest\u00E3o acima ou descreva uma altera\u00E7\u00E3o no campo inferior.\n </div>\n </div>\n\n <!-- STATE: CLARIFICATION (Two-Step Flow) -->\n <div\n *ngIf=\"state === 'clarification' && isActiveTab('task')\"\n class=\"clarification-area assistant-card\"\n id=\"assistant-panel-task\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-task\"\n >\n <div class=\"task-header\" *ngIf=\"!shouldShowThoughtCard()\">\n <div class=\"task-title\">{{ getTaskTitle() }}</div>\n <div class=\"task-subtitle\">{{ getTaskSubtitle() }}</div>\n <div *ngIf=\"clarificationOptions.length > 1 && getTaskSelectionSummary() as selectionSummary\" class=\"task-meta\">\n <span class=\"task-meta-label\">Selecionado:</span>\n <span class=\"task-meta-value\">{{ selectionSummary }}</span>\n </div>\n <div *ngIf=\"shouldShowTaskSteps()\" class=\"task-steps\">\n <span class=\"task-step active\">1 Dados</span>\n <span class=\"task-step\">2 Layout</span>\n <span class=\"task-step\">3 Revis\u00E3o</span>\n </div>\n </div>\n <div *ngIf=\"clarificationResponseType === 'context'\" class=\"context-only\">\n <mat-spinner diameter=\"24\"></mat-spinner>\n <div class=\"context-only-hint\">Buscando contexto adicional...</div>\n </div>\n <ng-template #clarificationOptionContent let-opt let-index=\"index\" let-compact=\"compact\">\n <div class=\"clarification-decision-head\" *ngIf=\"!compact\">\n <span class=\"clarification-decision-index\">{{ index + 1 }}</span>\n <span class=\"clarification-decision-type\">{{ getClarificationOptionKindLabel(opt) }}</span>\n <span class=\"spacer\"></span>\n <span class=\"clarification-decision-state\" *ngIf=\"isClarificationSelected(opt)\">Selecionado</span>\n </div>\n <ng-container [ngSwitch]=\"getClarificationOptionLayout(opt)\">\n <div *ngSwitchCase=\"'endpoint'\" class=\"clarification-card\">\n <div class=\"clarification-card-header\">\n <mat-icon class=\"endpoint-icon\">{{ getEndpointIcon(opt) }}</mat-icon>\n <ng-container *ngIf=\"getEndpointMethod(opt) as method\">\n <span class=\"endpoint-method\" [attr.data-method]=\"method\">{{ method }}</span>\n </ng-container>\n <span class=\"endpoint-label\">{{ opt.label }}</span>\n <span class=\"spacer\"></span>\n <mat-icon class=\"select-indicator\">\n {{ isClarificationSelected(opt) ? 'check_circle' : 'radio_button_unchecked' }}\n </mat-icon>\n </div>\n <div class=\"clarification-card-body\">\n <ng-container *ngIf=\"getEndpointPath(opt) as path\">\n <div class=\"endpoint-path\">{{ path }}</div>\n </ng-container>\n <div\n *ngIf=\"opt.contextHints?.description\"\n class=\"endpoint-description\"\n [matTooltip]=\"getDescriptionTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowDescriptionTooltip(opt)\"\n >\n {{ opt.contextHints?.description }}\n </div>\n </div>\n </div>\n <div *ngSwitchCase=\"'color'\" class=\"clarification-color\">\n <span\n class=\"color-swatch\"\n [style.background]=\"getSafeHexColor(opt) || 'var(--md-sys-color-surface-container-highest)'\"\n ></span>\n <div class=\"color-meta\">\n <span class=\"color-label\">{{ opt.label }}</span>\n <span *ngIf=\"opt.contextHints?.hexColor\" class=\"color-value\">{{ opt.contextHints?.hexColor }}</span>\n </div>\n </div>\n <div *ngSwitchCase=\"'description'\" class=\"clarification-description\">\n <span class=\"clarification-label\">{{ opt.label }}</span>\n <span\n *ngIf=\"opt.contextHints?.description\"\n class=\"clarification-subtitle\"\n [matTooltip]=\"getDescriptionTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowDescriptionTooltip(opt)\"\n >\n {{ opt.contextHints?.description }}\n </span>\n </div>\n <span *ngSwitchDefault class=\"clarification-plain-label\">{{ opt.label }}</span>\n </ng-container>\n </ng-template>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div\n *ngIf=\"clarificationQuestions.length\"\n class=\"clarification-questions\"\n [class.attention-highlight]=\"highlightClarificationDetails && !clarificationOptions.length\"\n >\n <div *ngFor=\"let question of clarificationQuestions; let i = index\" class=\"clarification-question\">\n <div class=\"clarification-question-label\">{{ question }}</div>\n <input\n type=\"text\"\n [(ngModel)]=\"clarificationAnswers[i]\"\n [placeholder]=\"'Resposta ' + (i + 1)\"\n autocomplete=\"off\">\n </div>\n </div>\n <div\n *ngIf=\"clarificationOptions.length\"\n class=\"clarification-options-block\"\n [class.attention-highlight]=\"highlightClarificationDetails\"\n >\n <div class=\"clarification-options-title\">\n {{ clarificationOptions.length === 1 ? 'Decis\u00E3o sugerida' : 'Decis\u00F5es sugeridas' }}\n </div>\n <div *ngIf=\"clarificationOptions.length === 1\" class=\"clarification-options-hint\">\n Etapa 2 de 4: confirme a melhor op\u00E7\u00E3o para continuar.\n </div>\n <div *ngIf=\"clarificationOptions.length > 1\" class=\"clarification-options-hint\">\n Etapa 2 de 4: selecione a alternativa mais aderente para gerar a proposta.\n </div>\n <div\n *ngIf=\"clarificationResponseType === 'confirm'\"\n class=\"clarification-buttons\"\n [class.list]=\"clarificationPresentation === 'list'\"\n >\n <button\n mat-button\n type=\"button\"\n *ngFor=\"let opt of clarificationOptions; let i = index\"\n (click)=\"onClarificationOptionClick(opt)\"\n class=\"clarification-option\"\n [class.selected]=\"isClarificationSelected(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n </div>\n <div\n *ngIf=\"clarificationSelectionMode === 'multiple'\"\n class=\"clarification-buttons\"\n [class.list]=\"clarificationPresentation === 'list'\"\n >\n <button\n mat-button\n type=\"button\"\n *ngFor=\"let opt of clarificationOptions; let i = index\"\n (click)=\"onClarificationOptionClick(opt)\"\n [class.selected]=\"isClarificationSelected(opt)\"\n class=\"clarification-option\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n </div>\n <div\n *ngIf=\"clarificationResponseType !== 'confirm' && clarificationSelectionMode === 'single' && clarificationPresentation !== 'chips'\"\n class=\"clarification-buttons\"\n [class.list]=\"clarificationPresentation === 'list'\"\n >\n <button\n mat-button\n type=\"button\"\n *ngFor=\"let opt of clarificationOptions; let i = index\"\n (click)=\"onClarificationOptionClick(opt)\"\n class=\"clarification-option\"\n [class.selected]=\"isClarificationSelected(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n </div>\n <div\n *ngIf=\"clarificationResponseType !== 'confirm' && clarificationSelectionMode === 'single' && clarificationPresentation === 'chips'\"\n class=\"clarification-chips\"\n >\n <ng-container *ngFor=\"let opt of clarificationOptions; let i = index\">\n <button\n *ngIf=\"isEndpointOption(opt); else chipOption\"\n mat-button\n type=\"button\"\n class=\"clarification-option clarification-card-button\"\n (click)=\"onClarificationOptionClick(opt)\"\n [class.selected]=\"isClarificationSelected(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n <ng-template #chipOption>\n <mat-chip\n (click)=\"onClarificationOptionClick(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt)\"\n class=\"clarification-chip\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: true }\"></ng-container>\n </mat-chip>\n </ng-template>\n </ng-container>\n </div>\n </div>\n <div\n *ngIf=\"clarificationAllowCustom && clarificationResponseType !== 'context'\"\n class=\"clarification-manual-toggle\"\n >\n <span class=\"clarification-manual-label\">N\u00E3o encontrou o recurso?</span>\n <button mat-button type=\"button\" (click)=\"toggleManualInput()\">\n {{ showManualInput ? 'Ocultar resposta manual' : 'Responder manualmente' }}\n </button>\n </div>\n <div\n *ngIf=\"clarificationAllowCustom && showManualInput && clarificationResponseType !== 'context'\"\n class=\"clarification-free\"\n >\n <input\n type=\"text\"\n [(ngModel)]=\"clarificationFreeText\"\n placeholder=\"Digite sua resposta\u2026\"\n autocomplete=\"off\"\n (keydown.enter)=\"confirmTaskAction()\"\n />\n </div>\n <div\n *ngIf=\"clarificationAllowCustom && showManualInput && clarificationResponseType !== 'context'\"\n class=\"clarification-free-hint\"\n >\n Pressione Enter para enviar.\n </div>\n </div>\n\n <!-- STATE: REVIEW (Diff/Explanation) -->\n <div\n *ngIf=\"state === 'review' && isActiveTab('task')\"\n class=\"review-area assistant-card\"\n id=\"assistant-panel-task\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-task\"\n >\n <div class=\"task-header\" *ngIf=\"!shouldShowThoughtCard()\">\n <div class=\"task-title\">{{ getTaskTitle() }}</div>\n <div class=\"task-subtitle\">{{ getTaskSubtitle() }}</div>\n <div *ngIf=\"clarificationOptions.length > 1 && getTaskSelectionSummary() as selectionSummary\" class=\"task-meta\">\n <span class=\"task-meta-label\">Selecionado:</span>\n <span class=\"task-meta-value\">{{ selectionSummary }}</span>\n </div>\n <div *ngIf=\"shouldShowTaskSteps()\" class=\"task-steps\">\n <span class=\"task-step\">1 Dados</span>\n <span class=\"task-step\">2 Layout</span>\n <span class=\"task-step active\">3 Revis\u00E3o</span>\n </div>\n </div>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div class=\"review-trust\">\n <span class=\"trust-chip\" [matTooltip]=\"getScopeTooltip()\">{{ getScopeLabel() }}</span>\n <span class=\"trust-chip\" [matTooltip]=\"getConfidenceTooltip()\">{{ getConfidenceLabel() }}</span>\n <span class=\"trust-chip risk-chip\" [class.medium]=\"getReviewRiskLevel() === 'm\u00E9dio'\" [class.high]=\"getReviewRiskLevel() === 'alto'\">\n Risco {{ getReviewRiskLevel() }}\n </span>\n </div>\n <div class=\"review-summary\" [class.attention-highlight]=\"highlightReviewDetails\">\n <div class=\"review-summary-title\">Resumo da proposta</div>\n <div class=\"review-summary-line\">{{ getReviewSummary() }}</div>\n </div>\n <div class=\"review-diff\">\n <div class=\"review-diff-title\">Pr\u00E9via de mudan\u00E7as</div>\n <ng-container *ngIf=\"pendingDiff.length; else noDiffPreview\">\n <div class=\"review-diff-summary\">\n <div *ngFor=\"let line of getDiffSummaryLines()\" class=\"review-diff-line\">{{ line }}</div>\n <div *ngIf=\"pendingDiff.length > 3\" class=\"review-diff-more\">\u2026 +{{ pendingDiff.length - 3 }} mudan\u00E7as</div>\n </div>\n <button mat-stroked-button type=\"button\" class=\"review-diff-toggle\" (click)=\"toggleFullDiff()\">\n {{ getDiffToggleLabel() }}\n </button>\n </ng-container>\n <ng-template #noDiffPreview>\n <div class=\"review-diff-empty\">\n N\u00E3o foi poss\u00EDvel gerar um diff estruturado. Revise o resumo e aplique com cautela.\n </div>\n </ng-template>\n <div *ngIf=\"showFullDiff && pendingDiff.length\" class=\"review-diff-full\">\n <div *ngFor=\"let diff of pendingDiff\" class=\"review-diff-block\">\n <div class=\"review-diff-path\">{{ diff.path }}</div>\n <div class=\"review-diff-label\">Antes:</div>\n <pre>{{ diff.before | json }}</pre>\n <div class=\"review-diff-label\">Depois:</div>\n <pre>{{ diff.after | json }}</pre>\n </div>\n </div>\n </div>\n <div class=\"ai-explanation\" *ngIf=\"aiExplanation.trim()\">\n {{ aiExplanation }}\n </div>\n </div>\n\n <!-- STATE: ERROR -->\n <div\n *ngIf=\"state === 'error' && (isActiveTab('task') || (!isTaskMode() && isActiveTab('suggestions')))\"\n class=\"error-area assistant-card\"\n [attr.id]=\"isTaskMode() ? 'assistant-panel-task' : 'assistant-panel-suggestions'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"isTaskMode() ? 'assistant-tab-task' : 'assistant-tab-suggestions'\"\n >\n <div class=\"error-msg\">\n <mat-icon color=\"warn\">error_outline</mat-icon>\n <span>{{ errorMsg }}</span>\n </div>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div *ngIf=\"shouldShowApplyDetails()\" class=\"apply-details\">\n <div class=\"apply-details-header\">\n <div class=\"apply-details-title\">Detalhes da aplica\u00E7\u00E3o</div>\n <div class=\"apply-details-actions\" *ngIf=\"allowManualPatchEdit\">\n <button mat-button type=\"button\" (click)=\"togglePatchPathEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchPathEditorExpanded ? 'Ocultar paths' : 'Editar por path' }}\n </button>\n <button mat-button type=\"button\" (click)=\"togglePatchEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchEditorExpanded ? 'Ocultar editor' : 'Editar patch' }}\n </button>\n </div>\n </div>\n <div class=\"apply-details-meta\" *ngIf=\"lastApplyAt\">\n \u00DAltima tentativa: {{ lastApplyAt | date:'short' }}\n </div>\n <div class=\"apply-paths\" *ngIf=\"getApplyPaths().length\">\n <span class=\"apply-path-chip\" *ngFor=\"let path of getApplyPaths()\">{{ path }}</span>\n </div>\n <div class=\"apply-warnings\" *ngIf=\"applyWarnings.length\">\n <div class=\"apply-warnings-title\">Avisos da aplica\u00E7\u00E3o</div>\n <div class=\"apply-warnings-list\">\n <div *ngFor=\"let warning of applyWarnings\">{{ warning }}</div>\n </div>\n </div>\n <div class=\"patch-path-editor\" *ngIf=\"allowManualPatchEdit && patchPathEditorExpanded\">\n <div class=\"patch-editor-label\">Editar por path</div>\n <div class=\"patch-editor-hint\">Texto simples vira string; para for\u00E7ar texto em true/false/n\u00FAmero, use aspas.</div>\n <div class=\"patch-path-editor-empty\" *ngIf=\"!getEditablePatchPathEdits().length\">\n Nenhum path edit\u00E1vel dispon\u00EDvel para este patch.\n </div>\n <div class=\"patch-path-rows\" *ngIf=\"getEditablePatchPathEdits().length\">\n <div class=\"patch-path-row\" *ngFor=\"let edit of getEditablePatchPathEdits(); let i = index\">\n <div class=\"patch-path-label\">{{ edit.path }}</div>\n <input\n type=\"text\"\n [(ngModel)]=\"patchPathEdits[i].valueText\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n />\n <div *ngIf=\"patchPathEdits[i].error\" class=\"patch-editor-error\">{{ patchPathEdits[i].error }}</div>\n </div>\n </div>\n <div *ngIf=\"patchPathEditorError\" class=\"patch-editor-error\">{{ patchPathEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchPathEdits()\">\n Restaurar valores\n </button>\n <button mat-button type=\"button\" (click)=\"applyPathEditsToPatch()\">\n Aplicar no patch\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyPathEdits()\">\n Reaplicar por path\n </button>\n </div>\n </div>\n <div class=\"patch-editor\" *ngIf=\"allowManualPatchEdit && patchEditorExpanded\">\n <label class=\"patch-editor-label\">Patch JSON</label>\n <textarea\n [(ngModel)]=\"patchEditorText\"\n spellcheck=\"false\"\n ></textarea>\n <div *ngIf=\"patchEditorError\" class=\"patch-editor-error\">{{ patchEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchEditor()\">\n Restaurar original\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyEditedPatch()\">\n Reaplicar patch\n </button>\n </div>\n </div>\n </div>\n <div class=\"review-actions\">\n <button mat-button (click)=\"close()\">Fechar</button>\n <button mat-stroked-button (click)=\"retry()\">Tentar Novamente</button>\n </div>\n </div>\n\n <!-- STATE: SUCCESS -->\n <div\n *ngIf=\"state === 'success' && (isActiveTab('task') || (!isTaskMode() && isActiveTab('suggestions')))\"\n class=\"success-area assistant-card\"\n [attr.id]=\"isTaskMode() ? 'assistant-panel-task' : 'assistant-panel-suggestions'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"isTaskMode() ? 'assistant-tab-task' : 'assistant-tab-suggestions'\"\n >\n <div class=\"success-msg\">\n <mat-icon class=\"success-icon\">check_circle</mat-icon>\n <span>{{ aiExplanation || 'Configura\u00E7\u00E3o atualizada.' }}</span>\n </div>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div *ngIf=\"shouldShowApplyDetails()\" class=\"apply-details\">\n <div class=\"apply-details-header\">\n <div class=\"apply-details-title\">Detalhes da aplica\u00E7\u00E3o</div>\n <div class=\"apply-details-actions\" *ngIf=\"allowManualPatchEdit\">\n <button mat-button type=\"button\" (click)=\"togglePatchPathEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchPathEditorExpanded ? 'Ocultar paths' : 'Editar por path' }}\n </button>\n <button mat-button type=\"button\" (click)=\"togglePatchEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchEditorExpanded ? 'Ocultar editor' : 'Editar e reaplicar' }}\n </button>\n </div>\n </div>\n <div class=\"apply-details-meta\" *ngIf=\"lastApplyAt\">\n Aplicado em {{ lastApplyAt | date:'short' }}\n </div>\n <div class=\"apply-paths\" *ngIf=\"getApplyPaths().length\">\n <span class=\"apply-path-chip\" *ngFor=\"let path of getApplyPaths()\">{{ path }}</span>\n </div>\n <div class=\"apply-warnings\" *ngIf=\"applyWarnings.length\">\n <div class=\"apply-warnings-title\">Avisos da aplica\u00E7\u00E3o</div>\n <div class=\"apply-warnings-list\">\n <div *ngFor=\"let warning of applyWarnings\">{{ warning }}</div>\n </div>\n </div>\n <div class=\"patch-path-editor\" *ngIf=\"allowManualPatchEdit && patchPathEditorExpanded\">\n <div class=\"patch-editor-label\">Editar por path</div>\n <div class=\"patch-editor-hint\">Texto simples vira string; para for\u00E7ar texto em true/false/n\u00FAmero, use aspas.</div>\n <div class=\"patch-path-editor-empty\" *ngIf=\"!getEditablePatchPathEdits().length\">\n Nenhum path edit\u00E1vel dispon\u00EDvel para este patch.\n </div>\n <div class=\"patch-path-rows\" *ngIf=\"getEditablePatchPathEdits().length\">\n <div class=\"patch-path-row\" *ngFor=\"let edit of getEditablePatchPathEdits(); let i = index\">\n <div class=\"patch-path-label\">{{ edit.path }}</div>\n <input\n type=\"text\"\n [(ngModel)]=\"patchPathEdits[i].valueText\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n />\n <div *ngIf=\"patchPathEdits[i].error\" class=\"patch-editor-error\">{{ patchPathEdits[i].error }}</div>\n </div>\n </div>\n <div *ngIf=\"patchPathEditorError\" class=\"patch-editor-error\">{{ patchPathEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchPathEdits()\">\n Restaurar valores\n </button>\n <button mat-button type=\"button\" (click)=\"applyPathEditsToPatch()\">\n Aplicar no patch\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyPathEdits()\">\n Reaplicar por path\n </button>\n </div>\n </div>\n <div class=\"patch-editor\" *ngIf=\"allowManualPatchEdit && patchEditorExpanded\">\n <label class=\"patch-editor-label\">Patch JSON</label>\n <textarea\n [(ngModel)]=\"patchEditorText\"\n spellcheck=\"false\"\n ></textarea>\n <div *ngIf=\"patchEditorError\" class=\"patch-editor-error\">{{ patchEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchEditor()\">\n Restaurar original\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyEditedPatch()\">\n Reaplicar patch\n </button>\n </div>\n </div>\n </div>\n <button mat-button color=\"warn\" (click)=\"undoLastChange()\">\n <mat-icon>undo</mat-icon> Desfazer\n </button>\n </div>\n\n </div>\n\n <div class=\"assistant-footer\">\n <ng-container *ngIf=\"!isTaskMode(); else taskFooter\">\n <button\n mat-icon-button\n type=\"button\"\n class=\"composer-leading composer-leading-btn\"\n [matMenuTriggerFor]=\"assistantQuickMenu\"\n aria-label=\"Abrir a\u00E7\u00F5es r\u00E1pidas\"\n matTooltip=\"A\u00E7\u00F5es r\u00E1pidas\"\n >\n <mat-icon>add</mat-icon>\n </button>\n <input\n #inputEl\n type=\"text\"\n [(ngModel)]=\"userPrompt\"\n [disabled]=\"state === 'processing' || state === 'applying'\"\n placeholder=\"Descreva a altera\u00E7\u00E3o que deseja aplicar\u2026\"\n autocomplete=\"off\"\n />\n <div class=\"send-actions\">\n <button\n mat-icon-button\n class=\"send-btn\"\n *ngIf=\"state === 'listening'\"\n (click)=\"submitPrompt()\"\n [disabled]=\"!userPrompt.trim()\"\n [class.ready]=\"!!userPrompt.trim()\"\n >\n <mat-icon>arrow_upward</mat-icon>\n </button>\n <mat-spinner diameter=\"20\" *ngIf=\"state === 'processing' || state === 'applying'\"></mat-spinner>\n </div>\n </ng-container>\n <ng-template #taskFooter>\n <div class=\"task-footer\">\n <div class=\"task-footer-left\">\n <button mat-button type=\"button\" (click)=\"handleTaskSecondary()\">{{ getTaskCancelLabel() }}</button>\n <button\n *ngIf=\"state === 'review'\"\n mat-stroked-button\n type=\"button\"\n (click)=\"retry()\"\n >\n {{ getTaskSecondaryLabel() }}\n </button>\n </div>\n <div class=\"task-footer-right\">\n <button\n class=\"task-primary-btn\"\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n (click)=\"confirmTaskAction()\"\n [disabled]=\"isTaskPrimaryDisabled()\"\n >\n {{ getTaskPrimaryLabel() }}\n </button>\n <mat-spinner diameter=\"20\" *ngIf=\"state === 'processing' || state === 'applying'\"></mat-spinner>\n </div>\n </div>\n </ng-template>\n </div>\n\n <mat-menu #assistantQuickMenu=\"matMenu\" panelClass=\"assistant-quick-menu-panel\">\n <button mat-menu-item type=\"button\" (click)=\"setActiveTab('chat')\">\n <mat-icon>history</mat-icon>\n <span>Hist\u00F3rico</span>\n </button>\n <button mat-menu-item type=\"button\" (click)=\"setActiveTab('suggestions')\">\n <mat-icon>lightbulb</mat-icon>\n <span>Sugest\u00F5es</span>\n </button>\n <button mat-menu-item type=\"button\" (click)=\"startNewSession()\">\n <mat-icon>add_comment</mat-icon>\n <span>Nova conversa</span>\n </button>\n <button mat-menu-item type=\"button\" (click)=\"refreshSuggestions()\" [disabled]=\"loadingSuggestions\">\n <mat-icon>refresh</mat-icon>\n <span>Atualizar sugest\u00F5es</span>\n </button>\n <button\n mat-menu-item\n type=\"button\"\n (click)=\"restoreDismissedSuggestions()\"\n [disabled]=\"!hasDismissedSuggestions()\"\n >\n <mat-icon>visibility</mat-icon>\n <span>Restaurar sugest\u00F5es ocultas</span>\n </button>\n <button\n mat-menu-item\n type=\"button\"\n class=\"assistant-quick-menu-danger\"\n (click)=\"clearHistory()\"\n [disabled]=\"!historySessions.length\"\n >\n <mat-icon>delete_outline</mat-icon>\n <span>Limpar hist\u00F3rico local</span>\n </button>\n </mat-menu>\n </div>\n\n</ng-template>\n", styles: ["@keyframes assistantPremiumEnter{0%{opacity:0;transform:translate(14px) scale(.99)}to{opacity:1;transform:translate(0) scale(1)}}:host ::ng-deep .ai-assistant-backdrop{background:color-mix(in srgb,var(--md-sys-color-scrim, var(--md-sys-color-shadow, var(--md-sys-color-on-surface))) 42%,transparent);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}::ng-deep .ai-assistant-overlay-pane{max-width:calc(100vw - 24px);max-height:calc(100vh - 24px)}.ai-assistant-panel{box-sizing:border-box;width:min(604px,100vw - 18px);min-height:min(620px,100vh - 18px);max-height:min(820px,100vh - 18px);display:flex;flex-direction:column;overflow:hidden;color:var(--md-sys-color-on-surface);border-left:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));border-top:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));box-shadow:0 18px 48px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 42%,transparent),inset 0 1px color-mix(in srgb,var(--md-sys-color-on-surface) 10%,transparent);background:radial-gradient(circle at 14% 8%,color-mix(in srgb,var(--md-sys-color-primary-container) 38%,transparent) 0%,transparent 44%),linear-gradient(165deg,color-mix(in srgb,var(--md-sys-color-surface-container-highest) 88%,var(--md-sys-color-surface)),var(--md-sys-color-surface));animation:assistantPremiumEnter .24s cubic-bezier(.22,1,.36,1)}.assistant-header{position:relative;display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:12px 16px 10px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));background:linear-gradient(130deg,color-mix(in srgb,var(--md-sys-color-primary-container) 28%,transparent) 0%,transparent 52%),var(--md-sys-color-surface-container-low)}.assistant-header:after{content:\"\";position:absolute;left:16px;right:16px;bottom:-1px;height:1px;background:linear-gradient(90deg,color-mix(in srgb,var(--md-sys-color-primary) 55%,transparent),transparent);pointer-events:none}.assistant-header .assistant-header__left{min-width:0;display:flex;align-items:flex-start;gap:12px}.assistant-header .magic-icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:30px;height:30px;font-size:18px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent);box-shadow:0 4px 12px color-mix(in srgb,var(--md-sys-color-primary) 26%,transparent)}.assistant-header .assistant-title-group{min-width:0;display:flex;flex-direction:column;gap:4px}.assistant-header .assistant-title{font-size:15px;font-weight:700;letter-spacing:.01em;overflow-wrap:anywhere}.assistant-header .assistant-subtitle{display:block;font-size:11px;line-height:1.25;color:color-mix(in srgb,var(--md-sys-color-on-surface) 70%,var(--md-sys-color-on-surface-variant));overflow-wrap:anywhere}.assistant-header-chips{display:inline-flex;align-items:center;flex-wrap:wrap;gap:6px}.assistant-header .mode-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 28%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 64%,var(--md-sys-color-surface-container));color:color-mix(in srgb,var(--md-sys-color-on-primary-container) 85%,var(--md-sys-color-on-surface));font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.assistant-header .mode-chip:before{content:\"\";width:6px;height:6px;border-radius:999px;background:currentColor;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 16%,transparent)}.assistant-header .mode-chip.mock{border-color:color-mix(in srgb,var(--md-sys-color-error) 34%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-error-container) 72%,var(--md-sys-color-surface-container));color:color-mix(in srgb,var(--md-sys-color-on-error-container) 85%,var(--md-sys-color-error))}.assistant-header .policy-chip{padding:2px 8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 84%,transparent);background:var(--md-sys-color-surface-container-high);font-size:10px;letter-spacing:.02em;font-weight:600}.assistant-header .policy-chip.strict{border-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 56%,var(--md-sys-color-surface-container-high))}.assistant-status{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));border-radius:0;background:linear-gradient(160deg,color-mix(in srgb,var(--md-sys-color-primary-container) 24%,transparent),transparent 48%),var(--md-sys-color-surface-container-low)}.assistant-status-dot{width:8px;height:8px;margin-top:6px;border-radius:999px;flex:0 0 auto;background:var(--md-sys-color-primary);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.assistant-status.warning .assistant-status-dot{background:var(--md-sys-color-error);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-error) 16%,transparent)}.assistant-status.success .assistant-status-dot{background:color-mix(in srgb,var(--md-sys-color-primary) 74%,var(--md-sys-color-tertiary, var(--md-sys-color-primary)))}.assistant-status-content{min-width:0;display:flex;flex-direction:column;gap:2px}.assistant-status-label{display:flex;align-items:center;flex-wrap:wrap;gap:4px;font-size:12px;font-weight:700;line-height:1.35}.assistant-status-detail{font-size:11px;line-height:1.4;color:var(--md-sys-color-on-surface-variant);overflow-wrap:anywhere}.assistant-status-mode{padding:1px 6px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary-container) 72%,transparent);color:var(--md-sys-color-on-primary-container);font-size:10px;font-weight:700}.assistant-nav{padding:8px 16px 0}.assistant-nav .assistant-tabs{display:flex;align-items:center;gap:4px;border-radius:12px;padding:5px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:linear-gradient(155deg,color-mix(in srgb,var(--md-sys-color-primary-container) 14%,transparent),transparent 58%),var(--md-sys-color-surface-container-low)}.assistant-nav .assistant-tab{appearance:none;min-width:0;min-height:30px;border:0;border-radius:8px;padding:0 10px;background:transparent;color:var(--md-sys-color-on-surface-variant);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;flex:1 1 0;font-size:11px;font-weight:700;letter-spacing:.01em}.assistant-nav .assistant-tab.active{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 34%,transparent),0 6px 12px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}.assistant-section,.assistant-card{border-radius:14px;border-color:color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant));background:linear-gradient(160deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 50%),var(--md-sys-color-surface-container-lowest);box-shadow:0 6px 18px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 12%,transparent),inset 0 1px color-mix(in srgb,var(--md-sys-color-on-surface) 7%,transparent)}.suggestions-hero{margin:2px 0 4px;padding:10px 12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:linear-gradient(120deg,color-mix(in srgb,var(--md-sys-color-primary-container) 34%,transparent) 0%,transparent 52%),var(--md-sys-color-surface-container-low)}.suggestions-hero__label{font-size:10px;letter-spacing:.08em;text-transform:uppercase;font-weight:700;color:var(--md-sys-color-on-surface-variant);opacity:.88}.suggestions-hero__title{margin-top:2px;font-size:13px;font-weight:700;color:var(--md-sys-color-on-surface)}.suggestions-hero__detail{margin-top:4px;font-size:12px;line-height:1.4;color:color-mix(in srgb,var(--md-sys-color-on-surface) 78%,var(--md-sys-color-on-surface-variant))}.suggestions-content .suggestion-item{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;padding:12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));border-color:color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));background:linear-gradient(140deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 56%),var(--md-sys-color-surface-container-lowest)}.suggestions-list{display:flex;flex-direction:column;gap:10px}.suggestion-copy{min-width:0;display:grid;gap:4px}.suggestion-main{min-width:0;display:flex;align-items:center;flex-wrap:wrap;gap:8px}.suggestion-icon{width:18px;height:18px;font-size:18px;flex:0 0 auto;color:var(--md-sys-color-primary)}.suggestion-label{min-width:0;font-size:13px;font-weight:700;line-height:1.35;overflow-wrap:anywhere}.suggestion-group{padding:2px 6px;border-radius:999px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:10px;font-weight:700;line-height:1.2}.suggestion-desc{font-size:12px;line-height:1.35;color:var(--md-sys-color-on-surface-variant);overflow-wrap:anywhere}.suggestion-actions{display:inline-flex;align-items:center;justify-content:flex-end;gap:4px}.suggestion-action-btn,.suggestion-arrow{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;color:var(--md-sys-color-on-surface-variant)}.suggestions-content .suggestion-item:hover,.suggestions-content .suggestion-item:focus-visible{border-color:color-mix(in srgb,var(--md-sys-color-primary) 58%,var(--md-sys-color-outline-variant));box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 28%,transparent)}.assistant-footer{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px;padding:10px 16px;border-top-color:color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant));background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 56%),var(--md-sys-color-surface-container-low)}.assistant-footer .task-footer{grid-column:1/-1}.composer-leading{width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 30%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 70%,var(--md-sys-color-surface-container-low));color:var(--md-sys-color-primary);box-shadow:0 4px 10px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.composer-leading mat-icon{width:16px;height:16px;font-size:16px}.assistant-footer input{box-sizing:border-box;width:100%;min-width:0;height:34px;border:1px solid;border-radius:8px;padding:0 12px;color:var(--md-sys-color-on-surface);border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface);box-shadow:inset 0 1px 2px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 12%,transparent)}.send-actions{min-width:36px;display:inline-flex;align-items:center;justify-content:center}.assistant-footer .send-btn{--mdc-icon-button-icon-size: 20px;--mdc-icon-button-state-layer-size: 36px;--mat-icon-button-state-layer-size: 36px;width:36px;height:36px;padding:8px;line-height:1;display:inline-flex;align-items:center;justify-content:center;border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface)}.assistant-footer .send-btn mat-icon{width:20px;height:20px;margin:0;font-size:20px;line-height:20px;display:inline-flex;align-items:center;justify-content:center}.assistant-footer .send-btn.ready{background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary-container) 84%,var(--md-sys-color-primary)),var(--md-sys-color-primary));color:var(--md-sys-color-on-primary);box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}.task-primary-btn{border-radius:12px;box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 24%,transparent)}@media(max-width:959px){.assistant-header{padding:12px 12px 10px}.assistant-section,.assistant-card{margin:4px 12px 10px}.assistant-header:after{left:12px;right:12px}.assistant-header .assistant-title{font-size:14px}.assistant-header .assistant-subtitle{font-size:10px}.assistant-footer{grid-template-columns:minmax(0,1fr) auto}.composer-leading{display:none}}.assistant-thought{margin-top:0;gap:10px;border-color:color-mix(in srgb,var(--md-sys-color-primary) 26%,var(--md-sys-color-outline-variant));background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 28%,transparent),transparent 58%),var(--md-sys-color-surface-container-low)}.assistant-thought-meta{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.assistant-thought-meta mat-icon{width:15px;height:15px;font-size:15px;color:var(--md-sys-color-primary)}.assistant-thought-summary{font-size:13px;line-height:1.45;color:var(--md-sys-color-on-surface)}.assistant-thought-plan{border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 32%,var(--md-sys-color-outline-variant));border-radius:12px;padding:10px;background:var(--md-sys-color-surface-container-lowest);display:flex;flex-direction:column;gap:8px}.assistant-thought-plan-title{font-size:13px;font-weight:700;color:var(--md-sys-color-on-surface)}.assistant-thought-plan-actions{display:grid;grid-template-columns:repeat(auto-fit,minmax(0,1fr));gap:8px}.assistant-thought-plan-actions .mdc-button{min-width:0}.assistant-thought-plan-hint{font-size:11px;color:var(--md-sys-color-on-surface-variant);line-height:1.35}.assistant-thought-checklist{display:flex;flex-direction:column;gap:5px}.assistant-thought-checklist-item{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.assistant-thought-checklist-item mat-icon{width:14px;height:14px;font-size:14px}.assistant-flow{grid-template-columns:1fr;gap:6px}.flow-step{display:flex;align-items:flex-start;gap:8px;text-align:left;padding:7px 8px;border-radius:10px}.flow-step-index{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);flex:0 0 auto}.flow-step-content{min-width:0;display:flex;flex-direction:column;gap:1px}.flow-step-label{font-size:11px;font-weight:700;color:var(--md-sys-color-on-surface)}.flow-step-detail{font-size:11px;color:var(--md-sys-color-on-surface-variant);line-height:1.3}.flow-step.active .flow-step-index{border-color:color-mix(in srgb,var(--md-sys-color-primary) 60%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 85%,var(--md-sys-color-surface-container-highest));color:var(--md-sys-color-on-primary-container)}.flow-step.done .flow-step-index{border-color:transparent;background:color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-surface-container-highest))}.task-timeline{margin-top:4px;display:grid;gap:6px}.task-timeline-item{display:flex;align-items:flex-start;gap:8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;padding:6px 8px;background:var(--md-sys-color-surface-container-low);opacity:.76}.task-timeline-item.active{opacity:1;border-color:color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 34%,var(--md-sys-color-surface-container-low))}.task-timeline-item.done{opacity:.9;border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant))}.task-timeline-dot{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);flex:0 0 auto}.task-timeline-copy{min-width:0;display:flex;flex-direction:column;gap:1px}.task-timeline-title{font-size:11px;font-weight:700;color:var(--md-sys-color-on-surface)}.task-timeline-detail{font-size:11px;line-height:1.3;color:var(--md-sys-color-on-surface-variant)}.clarification-decision-head{display:inline-flex;align-items:center;width:100%;gap:6px;padding:8px 12px 0;box-sizing:border-box}.clarification-decision-index{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 35%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 68%,transparent)}.clarification-decision-type{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant);opacity:.92}.clarification-decision-state{font-size:10px;font-weight:700;color:var(--md-sys-color-primary)}.clarification-option{border-color:color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant));background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 55%),var(--md-sys-color-surface-container-low)}.clarification-option.selected{border-color:color-mix(in srgb,var(--md-sys-color-primary) 56%,var(--md-sys-color-outline-variant));background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 44%,transparent),transparent 62%),var(--md-sys-color-surface-container);box-shadow:0 8px 16px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}.clarification-options-block{overflow:visible;padding-right:4px}@media(max-width:959px){.assistant-thought-plan-actions{grid-template-columns:1fr}}.ai-assistant-panel{width:min(604px,100vw - 18px)}.assistant-section,.assistant-card{margin:0;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant))}.assistant-section{min-height:0;padding:14px 16px;overflow:visible}.assistant-card{min-height:0;padding:14px 16px 16px;overflow:visible}.suggestions-area,.assistant-history,.loading-suggestions{overflow-y:auto;overflow-x:hidden}.section-header{min-width:0;display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}.section-title{min-width:0;font-size:14px;font-weight:700;line-height:1.35;overflow-wrap:anywhere}.suggestions-actions{flex:0 0 auto;display:inline-flex;align-items:center}.review-area,.error-area,.success-area,.clarification-area{padding:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:auto}.review-trust{margin:2px 0 4px}.trust-chip{max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.warnings-area,.review-summary,.review-diff,.apply-details,.clarification-options-block,.clarification-manual-toggle{width:100%;max-width:100%;min-width:0}.review-diff{padding:12px 14px}.review-diff-full{padding:10px;overflow-x:auto}.review-diff-block{min-width:0}.review-diff-block pre{overflow-wrap:anywhere;word-break:break-word}.clarification-options-block{padding:4px 2px 10px}.clarification-option{line-height:normal}:host ::ng-deep .clarification-option .mdc-button__label{line-height:1.35!important}.clarification-plain-label{padding:6px 12px 12px;font-size:14px;line-height:1.35}.clarification-manual-toggle{margin-top:10px;padding:10px 2px 0;flex-wrap:wrap;row-gap:6px}.suggestions-content .suggestion-actions{gap:6px}.suggestions-content .suggestion-arrow,.suggestions-content .suggestion-action-btn mat-icon,.suggestions-content .suggestion-arrow mat-icon{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep .assistant-quick-menu-panel{min-width:244px;max-width:min(90vw,320px);border-radius:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 26%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface-container-high);box-shadow:0 12px 28px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 32%,transparent),inset 0 1px color-mix(in srgb,var(--md-sys-color-on-surface) 6%,transparent);overflow:hidden;padding:6px}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item{min-height:36px;border-radius:10px;color:var(--md-sys-color-on-surface);font-size:12px;font-weight:500;margin:1px 0}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item .mat-icon{color:color-mix(in srgb,var(--md-sys-color-on-surface) 82%,var(--md-sys-color-on-surface-variant))}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item:hover:not([disabled]){background:color-mix(in srgb,var(--md-sys-color-primary-container) 45%,transparent)}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:-2px}:host ::ng-deep .assistant-quick-menu-panel .assistant-quick-menu-danger{color:var(--md-sys-color-error)}:host ::ng-deep .assistant-quick-menu-panel .assistant-quick-menu-danger .mat-icon{color:var(--md-sys-color-error)}@media(max-width:959px){::ng-deep .ai-assistant-overlay-pane{top:12px!important;left:12px!important;width:calc(100vw - 24px)!important;height:calc(100vh - 24px)!important;max-width:calc(100vw - 24px);max-height:calc(100vh - 24px);transform:none!important}.ai-assistant-panel{width:calc(100vw - 48px);min-height:min(620px,100vh - 48px);max-height:calc(100vh - 48px)}.assistant-section,.assistant-card{margin:0}.assistant-card,.assistant-section{padding:12px}}.ai-trigger-btn{--mdc-icon-button-state-layer-size: 36px;width:36px;height:36px;border-radius:8px;background:color-mix(in srgb,var(--md-sys-color-primary) 9%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 22%,transparent);color:var(--md-sys-color-primary);opacity:1}.ai-trigger-btn:hover:not(:disabled){background:color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent);border-color:color-mix(in srgb,var(--md-sys-color-primary) 36%,transparent);color:var(--md-sys-color-on-primary-container)}.ai-trigger-btn:focus-visible,.assistant-close-btn:focus-visible,.assistant-tab:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.assistant-close-btn{width:28px;height:28px;color:var(--md-sys-color-on-surface-variant)}.assistant-close-btn:hover:not(:disabled){background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface)}.assistant-body{padding:12px 16px;overflow:hidden;min-height:0;flex:1;display:flex;flex-direction:column;gap:12px}.assistant-body>*{min-height:0}@media(max-width:959px){.assistant-nav{padding:8px 12px 0}.assistant-body,.assistant-footer{padding:10px 12px}.suggestions-content .suggestion-item{grid-template-columns:1fr;align-items:start}.suggestion-actions{justify-content:flex-start}}\n"] }]
|
|
6688
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Trigger Button -->\n<button \n mat-icon-button \n cdkOverlayOrigin \n #trigger=\"cdkOverlayOrigin\"\n #triggerBtn\n (click)=\"open()\"\n [disabled]=\"isOpen\"\n class=\"ai-trigger-btn\"\n matTooltip=\"Assistente de Configura\u00E7\u00E3o\"\n aria-label=\"Abrir Assistente IA\">\n <mat-icon>auto_awesome</mat-icon>\n</button>\n\n<!-- Overlay Template -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayHasBackdrop]=\"hasBackdrop\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayViewportMargin]=\"12\"\n cdkConnectedOverlayPanelClass=\"ai-assistant-overlay-pane\"\n cdkConnectedOverlayBackdropClass=\"ai-assistant-backdrop\"\n (backdropClick)=\"close()\"\n (overlayOutsideClick)=\"close()\"\n (detach)=\"close()\">\n\n <div\n class=\"ai-assistant-panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Assistente de Configura\u00E7\u00E3o\"\n [attr.aria-busy]=\"isBusyState() ? 'true' : null\"\n [cdkTrapFocus]=\"hasBackdrop\"\n [cdkTrapFocusAutoCapture]=\"hasBackdrop\"\n (keydown)=\"onKeydown($event)\"\n >\n \n <!-- HEADER -->\n <div class=\"assistant-header\">\n <div class=\"assistant-header__left\">\n <mat-icon class=\"magic-icon\">auto_awesome</mat-icon>\n <div class=\"assistant-title-group\">\n <div class=\"assistant-title\">Assistente de Configura\u00E7\u00E3o</div>\n <div class=\"assistant-subtitle\">Copiloto contextual para ajustes guiados</div>\n <div class=\"assistant-header-chips\">\n <span\n class=\"mode-chip\"\n [class.mock]=\"mockMode\"\n [matTooltip]=\"mockMode ? 'Sem chave de API: respostas de demonstra\u00E7\u00E3o' : 'Conectado ao assistente configurado'\"\n >\n {{ mockMode ? 'Mock' : 'Conectado' }}\n </span>\n <span\n class=\"policy-chip\"\n [class.strict]=\"isStrictRiskPolicy()\"\n [matTooltip]=\"getRiskPolicyTooltip()\"\n >\n {{ getRiskPolicyLabel() }}\n </span>\n </div>\n </div>\n </div>\n <div class=\"assistant-header__right\">\n <button\n mat-icon-button\n type=\"button\"\n class=\"assistant-close-btn\"\n (click)=\"close()\"\n aria-label=\"Fechar assistente\"\n matTooltip=\"Fechar assistente\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n <div\n class=\"assistant-status\"\n [class.processing]=\"isBusyState()\"\n [class.pending]=\"state === 'clarification'\"\n [class.warning]=\"state === 'error'\"\n [class.success]=\"state === 'review' || state === 'success'\"\n [class.compact]=\"!shouldShowSystemStatusDetail()\"\n role=\"status\"\n [attr.aria-live]=\"getSystemStatusAriaLive()\"\n aria-atomic=\"true\"\n >\n <span class=\"assistant-status-dot\" aria-hidden=\"true\"></span>\n <div class=\"assistant-status-content\">\n <div class=\"assistant-status-label\">\n <span class=\"assistant-status-label-prefix\">Status:</span>\n <span>{{ getSystemStatusLabel() }}</span>\n <span *ngIf=\"shouldShowSnapshotFallbackBadge()\" class=\"assistant-status-mode\">Snapshot</span>\n </div>\n <div *ngIf=\"shouldShowSystemStatusDetail()\" class=\"assistant-status-detail\">{{ getSystemStatusDetail() }}</div>\n </div>\n </div>\n <div class=\"assistant-flow\" *ngIf=\"shouldShowTaskFlow()\" role=\"list\" aria-label=\"Fluxo da proposta\">\n <div\n class=\"flow-step\"\n role=\"listitem\"\n *ngFor=\"let step of flowSteps\"\n [class.active]=\"getFlowStepState(step.step) === 'active'\"\n [class.done]=\"getFlowStepState(step.step) === 'done'\"\n >\n <span class=\"flow-step-index\">{{ step.step }}</span>\n <span class=\"flow-step-content\">\n <span class=\"flow-step-label\">{{ step.label }}</span>\n <span class=\"flow-step-detail\">{{ getFlowStepDetail(step.step) }}</span>\n </span>\n </div>\n </div>\n <div class=\"assistant-nav\">\n <div\n class=\"assistant-tabs\"\n role=\"tablist\"\n aria-label=\"Se\u00E7\u00F5es do assistente\"\n (keydown)=\"onTabsKeydown($event)\"\n >\n <button\n class=\"assistant-tab\"\n type=\"button\"\n *ngIf=\"isTaskMode()\"\n id=\"assistant-tab-task\"\n role=\"tab\"\n aria-label=\"Proposta atual\"\n [attr.aria-selected]=\"isActiveTab('task')\"\n aria-controls=\"assistant-panel-task\"\n [attr.tabindex]=\"isActiveTab('task') ? 0 : -1\"\n [class.active]=\"isActiveTab('task')\"\n (click)=\"setActiveTab('task')\"\n >\n Proposta\n <span *ngIf=\"hasPendingClarification() && !isActiveTab('task')\" class=\"assistant-tab-badge\">1</span>\n </button>\n <button\n class=\"assistant-tab\"\n type=\"button\"\n id=\"assistant-tab-chat\"\n role=\"tab\"\n aria-label=\"Hist\u00F3rico\"\n [attr.aria-selected]=\"isActiveTab('chat')\"\n aria-controls=\"assistant-panel-chat\"\n [attr.tabindex]=\"isActiveTab('chat') ? 0 : -1\"\n [class.active]=\"isActiveTab('chat')\"\n (click)=\"setActiveTab('chat')\"\n >\n Hist\u00F3rico\n </button>\n <button\n class=\"assistant-tab\"\n type=\"button\"\n id=\"assistant-tab-suggestions\"\n role=\"tab\"\n aria-label=\"Sugest\u00F5es de melhoria\"\n [attr.aria-selected]=\"isActiveTab('suggestions')\"\n aria-controls=\"assistant-panel-suggestions\"\n [attr.tabindex]=\"isActiveTab('suggestions') ? 0 : -1\"\n [class.active]=\"isActiveTab('suggestions')\"\n (click)=\"setActiveTab('suggestions')\"\n >\n Sugest\u00F5es\n </button>\n </div>\n </div>\n\n <!-- BODY: Dynamic Content based on State -->\n <div class=\"assistant-body\">\n <div class=\"assistant-thought assistant-section\" *ngIf=\"shouldShowThoughtCard()\">\n <div class=\"assistant-thought-meta\">\n <mat-icon>psychology</mat-icon>\n <span>{{ getThoughtTimingLabel() }}</span>\n </div>\n <div class=\"assistant-thought-summary\">{{ getThoughtSummary() }}</div>\n <div class=\"assistant-thought-plan\">\n <div class=\"assistant-thought-plan-title\">{{ getThoughtPlanTitle() }}</div>\n <div class=\"assistant-thought-plan-actions\">\n <button\n mat-stroked-button\n type=\"button\"\n class=\"thought-action-details\"\n (click)=\"openThoughtDetails()\"\n [attr.aria-label]=\"getThoughtDetailsLabel()\"\n [matTooltip]=\"getThoughtDetailsTooltip()\"\n >\n {{ getThoughtDetailsLabel() }}\n </button>\n <button\n *ngIf=\"shouldShowThoughtPreviewAction()\"\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n class=\"thought-action-preview\"\n (click)=\"openThoughtPreview()\"\n [matTooltip]=\"getThoughtPreviewTooltip()\"\n >\n Pr\u00E9via\n </button>\n </div>\n <div class=\"assistant-thought-plan-hint\">{{ getThoughtActionHint() }}</div>\n <ng-container *ngIf=\"getThoughtChecklist() as thoughtChecklist\">\n <div class=\"assistant-thought-checklist\" *ngIf=\"thoughtChecklist.length\">\n <div *ngFor=\"let item of thoughtChecklist\" class=\"assistant-thought-checklist-item\">\n <mat-icon>radio_button_unchecked</mat-icon>\n <span>{{ item }}</span>\n </div>\n </div>\n <div class=\"assistant-thought-shimmer\" *ngIf=\"isBusyState() && !thoughtChecklist.length\">\n <div class=\"shimmer-line shimmer-line-1\"></div>\n <div class=\"shimmer-line shimmer-line-2\"></div>\n <div class=\"shimmer-line shimmer-line-3\"></div>\n </div>\n </ng-container>\n </div>\n </div>\n <div *ngIf=\"processingInfoVisible\" class=\"processing-banner\">\n <mat-spinner diameter=\"16\"></mat-spinner>\n <span>{{ aiExplanation || 'Analisando solicita\u00E7\u00E3o e preparando proposta...' }}</span>\n <button mat-button type=\"button\" class=\"processing-retry\" (click)=\"retryProcessing()\">Tentar novamente</button>\n </div>\n <div\n class=\"assistant-history assistant-section\"\n *ngIf=\"historyContext && isActiveTab('chat')\"\n id=\"assistant-panel-chat\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-chat\"\n >\n <div class=\"section-header\">\n <div class=\"section-heading\">\n <div class=\"section-title\">Hist\u00F3rico</div>\n <div class=\"section-subtitle\">Sess\u00F5es recentes, pedidos reaproveit\u00E1veis e desfazer r\u00E1pido.</div>\n </div>\n <div class=\"history-actions\" role=\"group\" aria-label=\"A\u00E7\u00F5es do hist\u00F3rico\">\n <button\n mat-stroked-button\n type=\"button\"\n class=\"history-action-btn\"\n (click)=\"historyExpanded = !historyExpanded\"\n [matTooltip]=\"historyExpanded ? 'Recolher hist\u00F3rico' : 'Expandir hist\u00F3rico'\"\n [attr.aria-label]=\"historyExpanded ? 'Recolher hist\u00F3rico' : 'Expandir hist\u00F3rico'\"\n [disabled]=\"!historyWarnings.length && !historySessions.length && !activeHistoryMessages.length\"\n >\n <mat-icon>{{ historyExpanded ? 'expand_less' : 'expand_more' }}</mat-icon>\n <span>{{ historyExpanded ? 'Recolher' : 'Expandir' }}</span>\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n class=\"history-action-btn\"\n (click)=\"startNewSession()\"\n matTooltip=\"Nova conversa\"\n aria-label=\"Nova conversa\"\n [disabled]=\"isBusyState()\"\n >\n <mat-icon>add_comment</mat-icon>\n <span>Nova conversa</span>\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"clearHistory()\"\n matTooltip=\"Limpar hist\u00F3rico local\"\n aria-label=\"Limpar hist\u00F3rico local\"\n class=\"history-action-btn history-action-btn--danger\"\n [disabled]=\"!historySessions.length\"\n >\n <mat-icon>delete_outline</mat-icon>\n <span>Limpar</span>\n </button>\n </div>\n </div>\n <div *ngIf=\"historyUndoDeleteSession\" class=\"history-undo\">\n <span>Sess\u00E3o removida.</span>\n <button mat-button type=\"button\" (click)=\"undoRemoveHistorySession()\">\n Desfazer\n </button>\n </div>\n\n <div *ngIf=\"historyExpanded && historyWarnings.length\" class=\"history-warnings\">\n <mat-icon>info</mat-icon>\n <div class=\"history-warnings-list\">\n <div *ngFor=\"let warning of historyWarnings\">{{ warning }}</div>\n <div class=\"history-warnings-hint\">\n Configure headers via API_CONFIG_STORAGE_OPTIONS no host.\n </div>\n </div>\n </div>\n\n <div *ngIf=\"historyExpanded && historySessions.length\" class=\"history-sessions\">\n <div\n class=\"history-session\"\n *ngFor=\"let session of historySessions\"\n role=\"button\"\n tabindex=\"0\"\n (click)=\"selectHistorySession(session.id)\"\n (keydown)=\"onHistorySessionCardKeydown($event, session.id)\"\n [class.active]=\"session.id === activeHistorySession?.id\"\n [matTooltip]=\"getHistorySessionTooltip(session)\"\n [matTooltipDisabled]=\"!session.componentType && !session.componentId\"\n >\n <div class=\"history-session-main\">\n <span class=\"history-session-title\">{{ session.title }}</span>\n <div class=\"history-session-main-right\">\n <span class=\"history-session-time\">{{ session.updatedAt | date:'short' }}</span>\n <div class=\"history-session-tools\">\n <button\n mat-icon-button\n type=\"button\"\n class=\"history-session-tool\"\n (click)=\"reuseHistorySessionPrompt(session.id, $event)\"\n matTooltip=\"Reusar \u00FAltimo pedido\"\n aria-label=\"Reusar \u00FAltimo pedido\"\n >\n <mat-icon>edit_note</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n class=\"history-session-tool history-session-tool--danger\"\n (click)=\"removeHistorySession(session.id, $event)\"\n matTooltip=\"Excluir sess\u00E3o\"\n aria-label=\"Excluir sess\u00E3o\"\n >\n <mat-icon>delete_outline</mat-icon>\n </button>\n </div>\n </div>\n </div>\n <div class=\"history-session-meta\">\n <span *ngIf=\"session.componentType\" class=\"history-chip\">{{ session.componentType }}</span>\n <span *ngIf=\"session.componentId\" class=\"history-chip\">{{ session.componentId }}</span>\n </div>\n </div>\n </div>\n <div *ngIf=\"historyExpanded && !historySessions.length\" class=\"history-empty\">\n Nenhuma sess\u00E3o salva ainda.\n </div>\n\n <div *ngIf=\"historyExpanded && activeHistoryMessages.length\" class=\"history-messages\">\n <div\n *ngIf=\"activeHistoryTotalMessages > activeHistoryMessages.length\"\n class=\"history-messages-hint\"\n >\n Mostrando \u00FAltimas {{ activeHistoryMessages.length }} de {{ activeHistoryTotalMessages }} mensagens.\n </div>\n <div\n *ngFor=\"let msg of activeHistoryMessages\"\n class=\"history-message\"\n [class.user]=\"msg.role === 'user'\"\n [class.assistant]=\"msg.role === 'assistant'\"\n >\n <div class=\"history-message-header\">\n <span class=\"history-message-role\">{{ msg.role === 'user' ? 'Voc\u00EA' : 'Assistente' }}</span>\n <span class=\"history-message-time\">{{ msg.createdAt | date:'shortTime' }}</span>\n <span\n *ngIf=\"msg.context?.usedRag\"\n class=\"history-rag\"\n matTooltip=\"Resposta baseada em contexto recuperado (RAG)\"\n >RAG</span>\n </div>\n <div class=\"history-message-text\">{{ msg.text }}</div>\n </div>\n </div>\n <div *ngIf=\"historyExpanded && historySessions.length && !activeHistoryMessages.length\" class=\"history-empty history-empty--panel\">\n Selecione uma sess\u00E3o para visualizar as mensagens.\n </div>\n <div class=\"history-helper\" *ngIf=\"historyExpanded\">\n O hist\u00F3rico \u00E9 local ao usu\u00E1rio e ao componente atual.\n </div>\n </div>\n\n <div\n class=\"loading-suggestions assistant-section\"\n *ngIf=\"loadingSuggestions && isActiveTab('suggestions')\"\n id=\"assistant-panel-suggestions\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-suggestions\"\n >\n <mat-spinner diameter=\"20\"></mat-spinner>\n <span>Carregando sugest\u00F5es de melhoria...</span>\n </div>\n \n <!-- STATE: LISTENING (Suggestions) -->\n <div\n *ngIf=\"state === 'listening' && isActiveTab('suggestions')\"\n class=\"suggestions-area assistant-section\"\n id=\"assistant-panel-suggestions\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-suggestions\"\n >\n <div class=\"section-header\">\n <div class=\"section-title\">Sugest\u00F5es de melhoria</div>\n <div class=\"suggestions-actions\">\n <button mat-icon-button type=\"button\" (click)=\"refreshSuggestions()\" [disabled]=\"loadingSuggestions\" matTooltip=\"Atualizar sugest\u00F5es\">\n <mat-icon>refresh</mat-icon>\n </button>\n </div>\n </div>\n <div class=\"suggestions-hero\" *ngIf=\"!loadingSuggestions\">\n <div class=\"suggestions-hero__label\">Contexto ativo</div>\n <div class=\"suggestions-hero__title\">{{ adapter.componentName || 'Componente atual' }}</div>\n <div class=\"suggestions-hero__detail\">{{ getSystemStatusDetail() }}</div>\n </div>\n <div *ngIf=\"suggestionsWarnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of suggestionsWarnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div class=\"suggestions-content\" *ngIf=\"!loadingSuggestions && (richSuggestions.length || hasDismissedSuggestions())\">\n <div class=\"suggestions-filter\" *ngIf=\"hasDismissedSuggestions()\">\n <span>{{ getDismissedSuggestionCount() }} oculta(s)</span>\n <button mat-button type=\"button\" (click)=\"restoreDismissedSuggestions()\">\n Restaurar\n </button>\n </div>\n <div class=\"suggestions-list\" *ngIf=\"getVisibleSuggestions().length; else allSuggestionsHidden\">\n <div\n class=\"suggestion-item\"\n *ngFor=\"let sug of getVisibleSuggestions()\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'Selecionar sugest\u00E3o: ' + sug.label\"\n (click)=\"selectSuggestion(sug)\"\n (keydown)=\"onSuggestionCardKeydown($event, sug)\"\n >\n <div class=\"suggestion-copy\">\n <div class=\"suggestion-main\">\n <mat-icon *ngIf=\"sug.icon\" class=\"suggestion-icon\">{{ sug.icon }}</mat-icon>\n <span class=\"suggestion-label\">{{ sug.label }}</span>\n <span *ngIf=\"sug.group\" class=\"suggestion-group\">{{ sug.group }}</span>\n </div>\n <div *ngIf=\"sug.description\" class=\"suggestion-desc\">{{ sug.description }}</div>\n </div>\n <div class=\"suggestion-actions\">\n <span class=\"suggestion-arrow\" aria-hidden=\"true\">\n <mat-icon>chevron_right</mat-icon>\n </span>\n <button\n mat-icon-button\n type=\"button\"\n class=\"suggestion-action-btn\"\n (click)=\"prepareSuggestionPrompt(sug, $event)\"\n matTooltip=\"Refinar pedido\"\n aria-label=\"Refinar pedido\"\n >\n <mat-icon>edit</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n class=\"suggestion-action-btn suggestion-action-btn--danger\"\n (click)=\"dismissSuggestion(sug, $event)\"\n matTooltip=\"Ocultar sugest\u00E3o\"\n aria-label=\"Ocultar sugest\u00E3o\"\n >\n <mat-icon>visibility_off</mat-icon>\n </button>\n </div>\n </div>\n </div>\n <ng-template #allSuggestionsHidden>\n <div class=\"suggestions-empty suggestions-empty--inline\">\n Todas as sugest\u00F5es foram ocultadas.\n </div>\n </ng-template>\n </div>\n <div class=\"suggestions-empty\" *ngIf=\"!loadingSuggestions && !richSuggestions.length\">\n Nenhuma sugest\u00E3o dispon\u00EDvel no momento.\n </div>\n <div class=\"suggestions-helper\" *ngIf=\"!loadingSuggestions && !richSuggestions.length\">\n Selecione uma sugest\u00E3o acima ou descreva uma altera\u00E7\u00E3o no campo inferior.\n </div>\n </div>\n\n <!-- STATE: CLARIFICATION (Two-Step Flow) -->\n <div\n *ngIf=\"state === 'clarification' && isActiveTab('task')\"\n class=\"clarification-area assistant-card\"\n id=\"assistant-panel-task\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-task\"\n >\n <div class=\"task-header\" *ngIf=\"!shouldShowThoughtCard()\">\n <div class=\"task-title\">{{ getTaskTitle() }}</div>\n <div class=\"task-subtitle\">{{ getTaskSubtitle() }}</div>\n <div *ngIf=\"clarificationOptions.length > 1 && getTaskSelectionSummary() as selectionSummary\" class=\"task-meta\">\n <span class=\"task-meta-label\">Selecionado:</span>\n <span class=\"task-meta-value\">{{ selectionSummary }}</span>\n </div>\n <div *ngIf=\"shouldShowTaskSteps()\" class=\"task-steps\">\n <span class=\"task-step active\">1 Dados</span>\n <span class=\"task-step\">2 Layout</span>\n <span class=\"task-step\">3 Revis\u00E3o</span>\n </div>\n </div>\n <div *ngIf=\"clarificationResponseType === 'context'\" class=\"context-only\">\n <mat-spinner diameter=\"24\"></mat-spinner>\n <div class=\"context-only-hint\">Buscando contexto adicional...</div>\n </div>\n <ng-template #clarificationOptionContent let-opt let-index=\"index\" let-compact=\"compact\">\n <div class=\"clarification-decision-head\" *ngIf=\"!compact\">\n <span class=\"clarification-decision-index\">{{ index + 1 }}</span>\n <span class=\"clarification-decision-type\">{{ getClarificationOptionKindLabel(opt) }}</span>\n <span class=\"spacer\"></span>\n <span class=\"clarification-decision-state\" *ngIf=\"isClarificationSelected(opt)\">Selecionado</span>\n </div>\n <ng-container [ngSwitch]=\"getClarificationOptionLayout(opt)\">\n <div *ngSwitchCase=\"'endpoint'\" class=\"clarification-card\">\n <div class=\"clarification-card-header\">\n <mat-icon class=\"endpoint-icon\">{{ getEndpointIcon(opt) }}</mat-icon>\n <ng-container *ngIf=\"getEndpointMethod(opt) as method\">\n <span class=\"endpoint-method\" [attr.data-method]=\"method\">{{ method }}</span>\n </ng-container>\n <span class=\"endpoint-label\">{{ opt.label }}</span>\n <span class=\"spacer\"></span>\n <mat-icon class=\"select-indicator\">\n {{ isClarificationSelected(opt) ? 'check_circle' : 'radio_button_unchecked' }}\n </mat-icon>\n </div>\n <div class=\"clarification-card-body\">\n <ng-container *ngIf=\"getEndpointPath(opt) as path\">\n <div class=\"endpoint-path\">{{ path }}</div>\n </ng-container>\n <div\n *ngIf=\"opt.contextHints?.description\"\n class=\"endpoint-description\"\n [matTooltip]=\"getDescriptionTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowDescriptionTooltip(opt)\"\n >\n {{ opt.contextHints?.description }}\n </div>\n </div>\n </div>\n <div *ngSwitchCase=\"'color'\" class=\"clarification-color\">\n <span\n class=\"color-swatch\"\n [style.background]=\"getSafeHexColor(opt) || 'var(--md-sys-color-surface-container-highest)'\"\n ></span>\n <div class=\"color-meta\">\n <span class=\"color-label\">{{ opt.label }}</span>\n <span *ngIf=\"opt.contextHints?.hexColor\" class=\"color-value\">{{ opt.contextHints?.hexColor }}</span>\n </div>\n </div>\n <div *ngSwitchCase=\"'description'\" class=\"clarification-description\">\n <span class=\"clarification-label\">{{ opt.label }}</span>\n <span\n *ngIf=\"opt.contextHints?.description\"\n class=\"clarification-subtitle\"\n [matTooltip]=\"getDescriptionTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowDescriptionTooltip(opt)\"\n >\n {{ opt.contextHints?.description }}\n </span>\n </div>\n <span *ngSwitchDefault class=\"clarification-plain-label\">{{ opt.label }}</span>\n </ng-container>\n </ng-template>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div\n *ngIf=\"clarificationQuestions.length\"\n class=\"clarification-questions\"\n [class.attention-highlight]=\"highlightClarificationDetails && !clarificationOptions.length\"\n >\n <div *ngFor=\"let question of clarificationQuestions; let i = index\" class=\"clarification-question\">\n <div class=\"clarification-question-label\">{{ question }}</div>\n <input\n type=\"text\"\n [(ngModel)]=\"clarificationAnswers[i]\"\n [placeholder]=\"'Resposta ' + (i + 1)\"\n autocomplete=\"off\">\n </div>\n </div>\n <div\n *ngIf=\"clarificationOptions.length\"\n class=\"clarification-options-block\"\n [class.attention-highlight]=\"highlightClarificationDetails\"\n >\n <div class=\"clarification-options-title\">\n {{ clarificationOptions.length === 1 ? 'Decis\u00E3o sugerida' : 'Decis\u00F5es sugeridas' }}\n </div>\n <div *ngIf=\"clarificationOptions.length === 1\" class=\"clarification-options-hint\">\n Etapa 2 de 4: confirme a melhor op\u00E7\u00E3o para continuar.\n </div>\n <div *ngIf=\"clarificationOptions.length > 1\" class=\"clarification-options-hint\">\n Etapa 2 de 4: selecione a alternativa mais aderente para gerar a proposta.\n </div>\n <div\n *ngIf=\"clarificationResponseType === 'confirm'\"\n class=\"clarification-buttons\"\n [class.list]=\"clarificationPresentation === 'list'\"\n >\n <button\n mat-button\n type=\"button\"\n *ngFor=\"let opt of clarificationOptions; let i = index\"\n (click)=\"onClarificationOptionClick(opt)\"\n class=\"clarification-option\"\n [class.selected]=\"isClarificationSelected(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n </div>\n <div\n *ngIf=\"clarificationSelectionMode === 'multiple'\"\n class=\"clarification-buttons\"\n [class.list]=\"clarificationPresentation === 'list'\"\n >\n <button\n mat-button\n type=\"button\"\n *ngFor=\"let opt of clarificationOptions; let i = index\"\n (click)=\"onClarificationOptionClick(opt)\"\n [class.selected]=\"isClarificationSelected(opt)\"\n class=\"clarification-option\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n </div>\n <div\n *ngIf=\"clarificationResponseType !== 'confirm' && clarificationSelectionMode === 'single' && clarificationPresentation !== 'chips'\"\n class=\"clarification-buttons\"\n [class.list]=\"clarificationPresentation === 'list'\"\n >\n <button\n mat-button\n type=\"button\"\n *ngFor=\"let opt of clarificationOptions; let i = index\"\n (click)=\"onClarificationOptionClick(opt)\"\n class=\"clarification-option\"\n [class.selected]=\"isClarificationSelected(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n </div>\n <div\n *ngIf=\"clarificationResponseType !== 'confirm' && clarificationSelectionMode === 'single' && clarificationPresentation === 'chips'\"\n class=\"clarification-chips\"\n >\n <ng-container *ngFor=\"let opt of clarificationOptions; let i = index\">\n <button\n *ngIf=\"isEndpointOption(opt); else chipOption\"\n mat-button\n type=\"button\"\n class=\"clarification-option clarification-card-button\"\n (click)=\"onClarificationOptionClick(opt)\"\n [class.selected]=\"isClarificationSelected(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt) || isEndpointOption(opt)\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: false }\"></ng-container>\n </button>\n <ng-template #chipOption>\n <mat-chip\n (click)=\"onClarificationOptionClick(opt)\"\n [matTooltip]=\"getClarificationTooltip(opt)\"\n [matTooltipDisabled]=\"!shouldShowClarificationTooltip(opt)\"\n class=\"clarification-chip\"\n >\n <ng-container *ngTemplateOutlet=\"clarificationOptionContent; context: { $implicit: opt, index: i, compact: true }\"></ng-container>\n </mat-chip>\n </ng-template>\n </ng-container>\n </div>\n </div>\n <div\n *ngIf=\"clarificationAllowCustom && clarificationResponseType !== 'context'\"\n class=\"clarification-manual-toggle\"\n >\n <span class=\"clarification-manual-label\">N\u00E3o encontrou o recurso?</span>\n <button mat-button type=\"button\" (click)=\"toggleManualInput()\">\n {{ showManualInput ? 'Ocultar resposta manual' : 'Responder manualmente' }}\n </button>\n </div>\n <div\n *ngIf=\"clarificationAllowCustom && showManualInput && clarificationResponseType !== 'context'\"\n class=\"clarification-free\"\n >\n <input\n type=\"text\"\n [(ngModel)]=\"clarificationFreeText\"\n placeholder=\"Digite sua resposta\u2026\"\n autocomplete=\"off\"\n (keydown.enter)=\"confirmTaskAction()\"\n />\n </div>\n <div\n *ngIf=\"clarificationAllowCustom && showManualInput && clarificationResponseType !== 'context'\"\n class=\"clarification-free-hint\"\n >\n Pressione Enter para enviar.\n </div>\n </div>\n\n <!-- STATE: REVIEW (Diff/Explanation) -->\n <div\n *ngIf=\"state === 'review' && isActiveTab('task')\"\n class=\"review-area assistant-card\"\n id=\"assistant-panel-task\"\n role=\"tabpanel\"\n aria-labelledby=\"assistant-tab-task\"\n >\n <div class=\"task-header\" *ngIf=\"!shouldShowThoughtCard()\">\n <div class=\"task-title\">{{ getTaskTitle() }}</div>\n <div class=\"task-subtitle\">{{ getTaskSubtitle() }}</div>\n <div *ngIf=\"clarificationOptions.length > 1 && getTaskSelectionSummary() as selectionSummary\" class=\"task-meta\">\n <span class=\"task-meta-label\">Selecionado:</span>\n <span class=\"task-meta-value\">{{ selectionSummary }}</span>\n </div>\n <div *ngIf=\"shouldShowTaskSteps()\" class=\"task-steps\">\n <span class=\"task-step\">1 Dados</span>\n <span class=\"task-step\">2 Layout</span>\n <span class=\"task-step active\">3 Revis\u00E3o</span>\n </div>\n </div>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div class=\"review-trust\">\n <span class=\"trust-chip\" [matTooltip]=\"getScopeTooltip()\">{{ getScopeLabel() }}</span>\n <span class=\"trust-chip\" [matTooltip]=\"getConfidenceTooltip()\">{{ getConfidenceLabel() }}</span>\n <span class=\"trust-chip risk-chip\" [class.medium]=\"getReviewRiskLevel() === 'm\u00E9dio'\" [class.high]=\"getReviewRiskLevel() === 'alto'\">\n Risco {{ getReviewRiskLevel() }}\n </span>\n </div>\n <div class=\"review-summary\" [class.attention-highlight]=\"highlightReviewDetails\">\n <div class=\"review-summary-title\">Resumo da proposta</div>\n <div class=\"review-summary-line\">{{ getReviewSummary() }}</div>\n </div>\n <div class=\"review-diff\">\n <div class=\"review-diff-title\">Pr\u00E9via de mudan\u00E7as</div>\n <ng-container *ngIf=\"pendingDiff.length; else noDiffPreview\">\n <div class=\"review-diff-summary\">\n <div *ngFor=\"let line of getDiffSummaryLines()\" class=\"review-diff-line\">{{ line }}</div>\n <div *ngIf=\"pendingDiff.length > 3\" class=\"review-diff-more\">\u2026 +{{ pendingDiff.length - 3 }} mudan\u00E7as</div>\n </div>\n <button mat-stroked-button type=\"button\" class=\"review-diff-toggle\" (click)=\"toggleFullDiff()\">\n {{ getDiffToggleLabel() }}\n </button>\n </ng-container>\n <ng-template #noDiffPreview>\n <div class=\"review-diff-empty\">\n N\u00E3o foi poss\u00EDvel gerar um diff estruturado. Revise o resumo e aplique com cautela.\n </div>\n </ng-template>\n <div *ngIf=\"showFullDiff && pendingDiff.length\" class=\"review-diff-full\">\n <div *ngFor=\"let diff of pendingDiff\" class=\"review-diff-block\">\n <div class=\"review-diff-path\">{{ diff.path }}</div>\n <div class=\"review-diff-label\">Antes:</div>\n <pre>{{ diff.before | json }}</pre>\n <div class=\"review-diff-label\">Depois:</div>\n <pre>{{ diff.after | json }}</pre>\n </div>\n </div>\n </div>\n <div class=\"ai-explanation\" *ngIf=\"aiExplanation.trim()\">\n {{ aiExplanation }}\n </div>\n </div>\n\n <!-- STATE: ERROR -->\n <div\n *ngIf=\"state === 'error' && (isActiveTab('task') || (!isTaskMode() && isActiveTab('suggestions')))\"\n class=\"error-area assistant-card\"\n [attr.id]=\"isTaskMode() ? 'assistant-panel-task' : 'assistant-panel-suggestions'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"isTaskMode() ? 'assistant-tab-task' : 'assistant-tab-suggestions'\"\n >\n <div class=\"error-msg\">\n <mat-icon color=\"warn\">error_outline</mat-icon>\n <span>{{ errorMsg }}</span>\n </div>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div *ngIf=\"shouldShowApplyDetails()\" class=\"apply-details\">\n <div class=\"apply-details-header\">\n <div class=\"apply-details-title\">Detalhes da aplica\u00E7\u00E3o</div>\n <div class=\"apply-details-actions\" *ngIf=\"allowManualPatchEdit\">\n <button mat-button type=\"button\" (click)=\"togglePatchPathEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchPathEditorExpanded ? 'Ocultar paths' : 'Editar por path' }}\n </button>\n <button mat-button type=\"button\" (click)=\"togglePatchEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchEditorExpanded ? 'Ocultar editor' : 'Editar patch' }}\n </button>\n </div>\n </div>\n <div class=\"apply-details-meta\" *ngIf=\"lastApplyAt\">\n \u00DAltima tentativa: {{ lastApplyAt | date:'short' }}\n </div>\n <div class=\"apply-paths\" *ngIf=\"getApplyPaths().length\">\n <span class=\"apply-path-chip\" *ngFor=\"let path of getApplyPaths()\">{{ path }}</span>\n </div>\n <div class=\"apply-warnings\" *ngIf=\"applyWarnings.length\">\n <div class=\"apply-warnings-title\">Avisos da aplica\u00E7\u00E3o</div>\n <div class=\"apply-warnings-list\">\n <div *ngFor=\"let warning of applyWarnings\">{{ warning }}</div>\n </div>\n </div>\n <div class=\"patch-path-editor\" *ngIf=\"allowManualPatchEdit && patchPathEditorExpanded\">\n <div class=\"patch-editor-label\">Editar por path</div>\n <div class=\"patch-editor-hint\">Texto simples vira string; para for\u00E7ar texto em true/false/n\u00FAmero, use aspas.</div>\n <div class=\"patch-path-editor-empty\" *ngIf=\"!getEditablePatchPathEdits().length\">\n Nenhum path edit\u00E1vel dispon\u00EDvel para este patch.\n </div>\n <div class=\"patch-path-rows\" *ngIf=\"getEditablePatchPathEdits().length\">\n <div class=\"patch-path-row\" *ngFor=\"let edit of getEditablePatchPathEdits(); let i = index\">\n <div class=\"patch-path-label\">{{ edit.path }}</div>\n <input\n type=\"text\"\n [(ngModel)]=\"patchPathEdits[i].valueText\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n />\n <div *ngIf=\"patchPathEdits[i].error\" class=\"patch-editor-error\">{{ patchPathEdits[i].error }}</div>\n </div>\n </div>\n <div *ngIf=\"patchPathEditorError\" class=\"patch-editor-error\">{{ patchPathEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchPathEdits()\">\n Restaurar valores\n </button>\n <button mat-button type=\"button\" (click)=\"applyPathEditsToPatch()\">\n Aplicar no patch\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyPathEdits()\">\n Reaplicar por path\n </button>\n </div>\n </div>\n <div class=\"patch-editor\" *ngIf=\"allowManualPatchEdit && patchEditorExpanded\">\n <label class=\"patch-editor-label\">Patch JSON</label>\n <textarea\n [(ngModel)]=\"patchEditorText\"\n spellcheck=\"false\"\n ></textarea>\n <div *ngIf=\"patchEditorError\" class=\"patch-editor-error\">{{ patchEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchEditor()\">\n Restaurar original\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyEditedPatch()\">\n Reaplicar patch\n </button>\n </div>\n </div>\n </div>\n <div class=\"review-actions\">\n <button mat-button (click)=\"close()\">Fechar</button>\n <button mat-stroked-button (click)=\"retry()\">Tentar Novamente</button>\n </div>\n </div>\n\n <!-- STATE: SUCCESS -->\n <div\n *ngIf=\"state === 'success' && (isActiveTab('task') || (!isTaskMode() && isActiveTab('suggestions')))\"\n class=\"success-area assistant-card\"\n [attr.id]=\"isTaskMode() ? 'assistant-panel-task' : 'assistant-panel-suggestions'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"isTaskMode() ? 'assistant-tab-task' : 'assistant-tab-suggestions'\"\n >\n <div class=\"success-msg\">\n <mat-icon class=\"success-icon\">check_circle</mat-icon>\n <span>{{ aiExplanation || 'Configura\u00E7\u00E3o atualizada.' }}</span>\n </div>\n <div *ngIf=\"warnings.length\" class=\"warnings-area\">\n <mat-icon class=\"warnings-icon\">warning_amber</mat-icon>\n <div class=\"warnings-content\">\n <div class=\"warnings-title\">Avisos</div>\n <div class=\"warnings-list\">\n <div *ngFor=\"let warning of warnings\">{{ warning }}</div>\n </div>\n </div>\n </div>\n <div *ngIf=\"shouldShowApplyDetails()\" class=\"apply-details\">\n <div class=\"apply-details-header\">\n <div class=\"apply-details-title\">Detalhes da aplica\u00E7\u00E3o</div>\n <div class=\"apply-details-actions\" *ngIf=\"allowManualPatchEdit\">\n <button mat-button type=\"button\" (click)=\"togglePatchPathEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchPathEditorExpanded ? 'Ocultar paths' : 'Editar por path' }}\n </button>\n <button mat-button type=\"button\" (click)=\"togglePatchEditor()\" [disabled]=\"!pendingPatch\">\n {{ patchEditorExpanded ? 'Ocultar editor' : 'Editar e reaplicar' }}\n </button>\n </div>\n </div>\n <div class=\"apply-details-meta\" *ngIf=\"lastApplyAt\">\n Aplicado em {{ lastApplyAt | date:'short' }}\n </div>\n <div class=\"apply-paths\" *ngIf=\"getApplyPaths().length\">\n <span class=\"apply-path-chip\" *ngFor=\"let path of getApplyPaths()\">{{ path }}</span>\n </div>\n <div class=\"apply-warnings\" *ngIf=\"applyWarnings.length\">\n <div class=\"apply-warnings-title\">Avisos da aplica\u00E7\u00E3o</div>\n <div class=\"apply-warnings-list\">\n <div *ngFor=\"let warning of applyWarnings\">{{ warning }}</div>\n </div>\n </div>\n <div class=\"patch-path-editor\" *ngIf=\"allowManualPatchEdit && patchPathEditorExpanded\">\n <div class=\"patch-editor-label\">Editar por path</div>\n <div class=\"patch-editor-hint\">Texto simples vira string; para for\u00E7ar texto em true/false/n\u00FAmero, use aspas.</div>\n <div class=\"patch-path-editor-empty\" *ngIf=\"!getEditablePatchPathEdits().length\">\n Nenhum path edit\u00E1vel dispon\u00EDvel para este patch.\n </div>\n <div class=\"patch-path-rows\" *ngIf=\"getEditablePatchPathEdits().length\">\n <div class=\"patch-path-row\" *ngFor=\"let edit of getEditablePatchPathEdits(); let i = index\">\n <div class=\"patch-path-label\">{{ edit.path }}</div>\n <input\n type=\"text\"\n [(ngModel)]=\"patchPathEdits[i].valueText\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n />\n <div *ngIf=\"patchPathEdits[i].error\" class=\"patch-editor-error\">{{ patchPathEdits[i].error }}</div>\n </div>\n </div>\n <div *ngIf=\"patchPathEditorError\" class=\"patch-editor-error\">{{ patchPathEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchPathEdits()\">\n Restaurar valores\n </button>\n <button mat-button type=\"button\" (click)=\"applyPathEditsToPatch()\">\n Aplicar no patch\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyPathEdits()\">\n Reaplicar por path\n </button>\n </div>\n </div>\n <div class=\"patch-editor\" *ngIf=\"allowManualPatchEdit && patchEditorExpanded\">\n <label class=\"patch-editor-label\">Patch JSON</label>\n <textarea\n [(ngModel)]=\"patchEditorText\"\n spellcheck=\"false\"\n ></textarea>\n <div *ngIf=\"patchEditorError\" class=\"patch-editor-error\">{{ patchEditorError }}</div>\n <div class=\"patch-editor-actions\">\n <button mat-button type=\"button\" (click)=\"resetPatchEditor()\">\n Restaurar original\n </button>\n <button mat-flat-button color=\"primary\" type=\"button\" (click)=\"reapplyEditedPatch()\">\n Reaplicar patch\n </button>\n </div>\n </div>\n </div>\n <button mat-button color=\"warn\" (click)=\"undoLastChange()\">\n <mat-icon>undo</mat-icon> Desfazer\n </button>\n </div>\n\n </div>\n\n <div class=\"assistant-footer\">\n <ng-container *ngIf=\"!isTaskMode(); else taskFooter\">\n <button\n mat-icon-button\n type=\"button\"\n class=\"composer-leading composer-leading-btn\"\n [matMenuTriggerFor]=\"assistantQuickMenu\"\n aria-label=\"Abrir a\u00E7\u00F5es r\u00E1pidas\"\n matTooltip=\"A\u00E7\u00F5es r\u00E1pidas\"\n >\n <mat-icon>add</mat-icon>\n </button>\n <input\n #inputEl\n type=\"text\"\n [(ngModel)]=\"userPrompt\"\n [disabled]=\"state === 'processing' || state === 'applying'\"\n placeholder=\"Descreva a altera\u00E7\u00E3o que deseja aplicar\u2026\"\n autocomplete=\"off\"\n />\n <div class=\"send-actions\">\n <button\n mat-icon-button\n class=\"send-btn\"\n *ngIf=\"state === 'listening'\"\n (click)=\"submitPrompt()\"\n [disabled]=\"!userPrompt.trim()\"\n [class.ready]=\"!!userPrompt.trim()\"\n >\n <mat-icon>arrow_upward</mat-icon>\n </button>\n <mat-spinner diameter=\"20\" *ngIf=\"state === 'processing' || state === 'applying'\"></mat-spinner>\n </div>\n </ng-container>\n <ng-template #taskFooter>\n <div class=\"task-footer\">\n <div class=\"task-footer-left\">\n <button mat-button type=\"button\" (click)=\"handleTaskSecondary()\">{{ getTaskCancelLabel() }}</button>\n <button\n *ngIf=\"state === 'review'\"\n mat-stroked-button\n type=\"button\"\n (click)=\"retry()\"\n >\n {{ getTaskSecondaryLabel() }}\n </button>\n </div>\n <div class=\"task-footer-right\">\n <button\n class=\"task-primary-btn\"\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n (click)=\"confirmTaskAction()\"\n [disabled]=\"isTaskPrimaryDisabled()\"\n >\n {{ getTaskPrimaryLabel() }}\n </button>\n <mat-spinner diameter=\"20\" *ngIf=\"state === 'processing' || state === 'applying'\"></mat-spinner>\n </div>\n </div>\n </ng-template>\n </div>\n\n <mat-menu #assistantQuickMenu=\"matMenu\" panelClass=\"assistant-quick-menu-panel\">\n <button mat-menu-item type=\"button\" (click)=\"setActiveTab('chat')\">\n <mat-icon>history</mat-icon>\n <span>Hist\u00F3rico</span>\n </button>\n <button mat-menu-item type=\"button\" (click)=\"setActiveTab('suggestions')\">\n <mat-icon>lightbulb</mat-icon>\n <span>Sugest\u00F5es</span>\n </button>\n <button mat-menu-item type=\"button\" (click)=\"startNewSession()\">\n <mat-icon>add_comment</mat-icon>\n <span>Nova conversa</span>\n </button>\n <button mat-menu-item type=\"button\" (click)=\"refreshSuggestions()\" [disabled]=\"loadingSuggestions\">\n <mat-icon>refresh</mat-icon>\n <span>Atualizar sugest\u00F5es</span>\n </button>\n <button\n mat-menu-item\n type=\"button\"\n (click)=\"restoreDismissedSuggestions()\"\n [disabled]=\"!hasDismissedSuggestions()\"\n >\n <mat-icon>visibility</mat-icon>\n <span>Restaurar sugest\u00F5es ocultas</span>\n </button>\n <button\n mat-menu-item\n type=\"button\"\n class=\"assistant-quick-menu-danger\"\n (click)=\"clearHistory()\"\n [disabled]=\"!historySessions.length\"\n >\n <mat-icon>delete_outline</mat-icon>\n <span>Limpar hist\u00F3rico local</span>\n </button>\n </mat-menu>\n </div>\n\n</ng-template>\n", styles: ["@keyframes assistantPremiumEnter{0%{opacity:0;transform:translate(14px) scale(.99)}to{opacity:1;transform:translate(0) scale(1)}}:host ::ng-deep .ai-assistant-backdrop{background:color-mix(in srgb,var(--md-sys-color-scrim, var(--md-sys-color-shadow, var(--md-sys-color-on-surface))) 42%,transparent);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}::ng-deep .ai-assistant-overlay-pane{max-width:calc(100vw - 24px);max-height:calc(100vh - 24px)}.ai-assistant-panel{box-sizing:border-box;width:min(604px,100vw - 18px);min-height:min(620px,100vh - 18px);max-height:min(820px,100vh - 18px);display:flex;flex-direction:column;overflow:hidden;color:var(--md-sys-color-on-surface);border-left:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));border-top:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));box-shadow:0 18px 48px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 42%,transparent),inset 0 1px color-mix(in srgb,var(--md-sys-color-on-surface) 10%,transparent);background:radial-gradient(circle at 14% 8%,color-mix(in srgb,var(--md-sys-color-primary-container) 38%,transparent) 0%,transparent 44%),linear-gradient(165deg,color-mix(in srgb,var(--md-sys-color-surface-container-highest) 88%,var(--md-sys-color-surface)),var(--md-sys-color-surface));animation:assistantPremiumEnter .24s cubic-bezier(.22,1,.36,1)}.assistant-header{position:relative;display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:12px 16px 10px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));background:linear-gradient(130deg,color-mix(in srgb,var(--md-sys-color-primary-container) 28%,transparent) 0%,transparent 52%),var(--md-sys-color-surface-container-low)}.assistant-header:after{content:\"\";position:absolute;left:16px;right:16px;bottom:-1px;height:1px;background:linear-gradient(90deg,color-mix(in srgb,var(--md-sys-color-primary) 55%,transparent),transparent);pointer-events:none}.assistant-header .assistant-header__left{min-width:0;display:flex;align-items:flex-start;gap:12px}.assistant-header .magic-icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:30px;height:30px;font-size:18px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent);box-shadow:0 4px 12px color-mix(in srgb,var(--md-sys-color-primary) 26%,transparent)}.assistant-header .assistant-title-group{min-width:0;display:flex;flex-direction:column;gap:4px}.assistant-header .assistant-title{font-size:15px;font-weight:700;letter-spacing:.01em;overflow-wrap:anywhere}.assistant-header .assistant-subtitle{display:block;font-size:11px;line-height:1.25;color:color-mix(in srgb,var(--md-sys-color-on-surface) 70%,var(--md-sys-color-on-surface-variant));overflow-wrap:anywhere}.assistant-header-chips{display:inline-flex;align-items:center;flex-wrap:wrap;gap:6px}.assistant-header .mode-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 28%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 64%,var(--md-sys-color-surface-container));color:color-mix(in srgb,var(--md-sys-color-on-primary-container) 85%,var(--md-sys-color-on-surface));font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.assistant-header .mode-chip:before{content:\"\";width:6px;height:6px;border-radius:999px;background:currentColor;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 16%,transparent)}.assistant-header .mode-chip.mock{border-color:color-mix(in srgb,var(--md-sys-color-error) 34%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-error-container) 72%,var(--md-sys-color-surface-container));color:color-mix(in srgb,var(--md-sys-color-on-error-container) 85%,var(--md-sys-color-error))}.assistant-header .policy-chip{padding:2px 8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 84%,transparent);background:var(--md-sys-color-surface-container-high);font-size:10px;letter-spacing:.02em;font-weight:600}.assistant-header .policy-chip.strict{border-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 56%,var(--md-sys-color-surface-container-high))}.assistant-status{display:flex;align-items:flex-start;gap:10px;padding:10px 16px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));border-radius:0;background:linear-gradient(160deg,color-mix(in srgb,var(--md-sys-color-primary-container) 24%,transparent),transparent 48%),var(--md-sys-color-surface-container-low)}.assistant-status-dot{width:8px;height:8px;margin-top:6px;border-radius:999px;flex:0 0 auto;background:var(--md-sys-color-primary);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.assistant-status.warning .assistant-status-dot{background:var(--md-sys-color-error);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-error) 16%,transparent)}.assistant-status.success .assistant-status-dot{background:color-mix(in srgb,var(--md-sys-color-primary) 74%,var(--md-sys-color-tertiary, var(--md-sys-color-primary)))}.assistant-status-content{min-width:0;display:flex;flex-direction:column;gap:2px}.assistant-status-label{display:flex;align-items:center;flex-wrap:wrap;gap:4px;font-size:12px;font-weight:700;line-height:1.35}.assistant-status-detail{font-size:11px;line-height:1.4;color:var(--md-sys-color-on-surface-variant);overflow-wrap:anywhere}.assistant-status-mode{padding:1px 6px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary-container) 72%,transparent);color:var(--md-sys-color-on-primary-container);font-size:10px;font-weight:700}.assistant-nav{padding:8px 16px 0}.assistant-nav .assistant-tabs{display:flex;align-items:center;gap:4px;border-radius:12px;padding:5px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:linear-gradient(155deg,color-mix(in srgb,var(--md-sys-color-primary-container) 14%,transparent),transparent 58%),var(--md-sys-color-surface-container-low)}.assistant-nav .assistant-tab{appearance:none;min-width:0;min-height:30px;border:0;border-radius:8px;padding:0 10px;background:transparent;color:var(--md-sys-color-on-surface-variant);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;flex:1 1 0;font-size:11px;font-weight:700;letter-spacing:.01em}.assistant-nav .assistant-tab.active{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 34%,transparent),0 6px 12px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}.assistant-section,.assistant-card{border-radius:14px;border-color:color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant));background:linear-gradient(160deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 50%),var(--md-sys-color-surface-container-lowest);box-shadow:0 6px 18px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 12%,transparent),inset 0 1px color-mix(in srgb,var(--md-sys-color-on-surface) 7%,transparent)}.suggestions-hero{margin:2px 0 4px;padding:10px 12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:linear-gradient(120deg,color-mix(in srgb,var(--md-sys-color-primary-container) 34%,transparent) 0%,transparent 52%),var(--md-sys-color-surface-container-low)}.suggestions-hero__label{font-size:10px;letter-spacing:.08em;text-transform:uppercase;font-weight:700;color:var(--md-sys-color-on-surface-variant);opacity:.88}.suggestions-hero__title{margin-top:2px;font-size:13px;font-weight:700;color:var(--md-sys-color-on-surface)}.suggestions-hero__detail{margin-top:4px;font-size:12px;line-height:1.4;color:color-mix(in srgb,var(--md-sys-color-on-surface) 78%,var(--md-sys-color-on-surface-variant))}.suggestions-content .suggestion-item{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;padding:12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));border-color:color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-outline-variant));background:linear-gradient(140deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 56%),var(--md-sys-color-surface-container-lowest);transition:transform .2s cubic-bezier(.25,.8,.25,1),border-color .2s ease,box-shadow .2s ease}.suggestions-list{display:flex;flex-direction:column;gap:10px}.suggestion-copy{min-width:0;display:grid;gap:4px}.suggestion-main{min-width:0;display:flex;align-items:center;flex-wrap:wrap;gap:8px}.suggestion-icon{width:18px;height:18px;font-size:18px;flex:0 0 auto;color:var(--md-sys-color-primary)}.suggestion-label{min-width:0;font-size:13px;font-weight:700;line-height:1.35;overflow-wrap:anywhere}.suggestion-group{padding:2px 6px;border-radius:999px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:10px;font-weight:700;line-height:1.2}.suggestion-desc{font-size:12px;line-height:1.35;color:var(--md-sys-color-on-surface-variant);overflow-wrap:anywhere}.suggestion-actions{display:inline-flex;align-items:center;justify-content:flex-end;gap:4px}.suggestion-action-btn,.suggestion-arrow{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;color:var(--md-sys-color-on-surface-variant)}.suggestions-content .suggestion-item:hover,.suggestions-content .suggestion-item:focus-visible{border-color:color-mix(in srgb,var(--md-sys-color-primary) 58%,var(--md-sys-color-outline-variant));box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 28%,transparent);transform:translateY(-2px) scale(1.01)}.assistant-footer{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px;padding:10px 16px;border-top-color:color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant));background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 56%),var(--md-sys-color-surface-container-low)}.assistant-footer .task-footer{grid-column:1/-1}.composer-leading{width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 30%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 70%,var(--md-sys-color-surface-container-low));color:var(--md-sys-color-primary);box-shadow:0 4px 10px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}.composer-leading mat-icon{width:16px;height:16px;font-size:16px}.assistant-footer input{box-sizing:border-box;width:100%;min-width:0;height:34px;border:1px solid;border-radius:8px;padding:0 12px;color:var(--md-sys-color-on-surface);border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface);box-shadow:inset 0 1px 2px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 12%,transparent)}.send-actions{min-width:36px;display:inline-flex;align-items:center;justify-content:center}.assistant-footer .send-btn{--mdc-icon-button-icon-size: 20px;--mdc-icon-button-state-layer-size: 36px;--mat-icon-button-state-layer-size: 36px;width:36px;height:36px;padding:8px;line-height:1;display:inline-flex;align-items:center;justify-content:center;border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface)}.assistant-footer .send-btn mat-icon{width:20px;height:20px;margin:0;font-size:20px;line-height:20px;display:inline-flex;align-items:center;justify-content:center}.assistant-footer .send-btn.ready{background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary-container) 84%,var(--md-sys-color-primary)),var(--md-sys-color-primary));color:var(--md-sys-color-on-primary);box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}.task-primary-btn{border-radius:12px;box-shadow:0 8px 18px color-mix(in srgb,var(--md-sys-color-primary) 24%,transparent)}@media(max-width:959px){.assistant-header{padding:12px 12px 10px}.assistant-section,.assistant-card{margin:4px 12px 10px}.assistant-header:after{left:12px;right:12px}.assistant-header .assistant-title{font-size:14px}.assistant-header .assistant-subtitle{font-size:10px}.assistant-footer{grid-template-columns:minmax(0,1fr) auto}.composer-leading{display:none}}.assistant-thought{margin-top:0;gap:10px;border-color:color-mix(in srgb,var(--md-sys-color-primary) 26%,var(--md-sys-color-outline-variant));background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 28%,transparent),transparent 58%),var(--md-sys-color-surface-container-low)}.assistant-thought-meta{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.assistant-thought-meta mat-icon{width:15px;height:15px;font-size:15px;color:var(--md-sys-color-primary)}.assistant-thought-summary{font-size:13px;line-height:1.45;color:var(--md-sys-color-on-surface)}.assistant-thought-plan{border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 32%,var(--md-sys-color-outline-variant));border-radius:12px;padding:10px;background:var(--md-sys-color-surface-container-lowest);display:flex;flex-direction:column;gap:8px}.assistant-thought-plan-title{font-size:13px;font-weight:700;color:var(--md-sys-color-on-surface)}.assistant-thought-plan-actions{display:grid;grid-template-columns:repeat(auto-fit,minmax(0,1fr));gap:8px}.assistant-thought-plan-actions .mdc-button{min-width:0}.assistant-thought-plan-hint{font-size:11px;color:var(--md-sys-color-on-surface-variant);line-height:1.35}.assistant-thought-checklist{display:flex;flex-direction:column;gap:5px}.assistant-thought-checklist-item{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.assistant-thought-checklist-item mat-icon{width:14px;height:14px;font-size:14px}.assistant-thought-shimmer{display:flex;flex-direction:column;gap:8px;margin-top:4px}.assistant-thought-shimmer .shimmer-line{height:12px;border-radius:4px;position:relative;overflow:hidden;background:var(--md-sys-color-surface-container-highest)}.assistant-thought-shimmer .shimmer-line:after{content:\"\";position:absolute;inset:0;transform:translate(-100%);background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent),transparent);animation:assistantShimmerEffect 1.5s infinite}.assistant-thought-shimmer .shimmer-line-1{width:85%}.assistant-thought-shimmer .shimmer-line-2{width:92%}.assistant-thought-shimmer .shimmer-line-3{width:64%}@keyframes assistantShimmerEffect{to{transform:translate(100%)}}.assistant-flow{grid-template-columns:1fr;gap:6px}.flow-step{display:flex;align-items:flex-start;gap:8px;text-align:left;padding:7px 8px;border-radius:10px}.flow-step-index{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);flex:0 0 auto}.flow-step-content{min-width:0;display:flex;flex-direction:column;gap:1px}.flow-step-label{font-size:11px;font-weight:700;color:var(--md-sys-color-on-surface)}.flow-step-detail{font-size:11px;color:var(--md-sys-color-on-surface-variant);line-height:1.3}.flow-step.active .flow-step-index{border-color:color-mix(in srgb,var(--md-sys-color-primary) 60%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 85%,var(--md-sys-color-surface-container-highest));color:var(--md-sys-color-on-primary-container)}.flow-step.done .flow-step-index{border-color:transparent;background:color-mix(in srgb,var(--md-sys-color-primary) 18%,var(--md-sys-color-surface-container-highest))}.task-timeline{margin-top:4px;display:grid;gap:6px}.task-timeline-item{display:flex;align-items:flex-start;gap:8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;padding:6px 8px;background:var(--md-sys-color-surface-container-low);opacity:.76}.task-timeline-item.active{opacity:1;border-color:color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 34%,var(--md-sys-color-surface-container-low))}.task-timeline-item.done{opacity:.9;border-color:color-mix(in srgb,var(--md-sys-color-primary) 24%,var(--md-sys-color-outline-variant))}.task-timeline-dot{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);flex:0 0 auto}.task-timeline-copy{min-width:0;display:flex;flex-direction:column;gap:1px}.task-timeline-title{font-size:11px;font-weight:700;color:var(--md-sys-color-on-surface)}.task-timeline-detail{font-size:11px;line-height:1.3;color:var(--md-sys-color-on-surface-variant)}.clarification-decision-head{display:inline-flex;align-items:center;width:100%;gap:6px;padding:8px 12px 0;box-sizing:border-box}.clarification-decision-index{width:18px;height:18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 35%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 68%,transparent)}.clarification-decision-type{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant);opacity:.92}.clarification-decision-state{font-size:10px;font-weight:700;color:var(--md-sys-color-primary)}.clarification-option{border-color:color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant));background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 16%,transparent),transparent 55%),var(--md-sys-color-surface-container-low)}.clarification-option.selected{border-color:color-mix(in srgb,var(--md-sys-color-primary) 56%,var(--md-sys-color-outline-variant));background:linear-gradient(145deg,color-mix(in srgb,var(--md-sys-color-primary-container) 44%,transparent),transparent 62%),var(--md-sys-color-surface-container);box-shadow:0 8px 16px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}.clarification-options-block{overflow:visible;padding-right:4px}@media(max-width:959px){.assistant-thought-plan-actions{grid-template-columns:1fr}}.ai-assistant-panel{width:min(604px,100vw - 18px)}.assistant-section,.assistant-card{margin:0;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 20%,var(--md-sys-color-outline-variant))}.assistant-section{min-height:0;padding:14px 16px;overflow:visible}.assistant-card{min-height:0;padding:14px 16px 16px;overflow:visible}.suggestions-area,.assistant-history,.loading-suggestions{overflow-y:auto;overflow-x:hidden}.section-header{min-width:0;display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}.section-title{min-width:0;font-size:14px;font-weight:700;line-height:1.35;overflow-wrap:anywhere}.suggestions-actions{flex:0 0 auto;display:inline-flex;align-items:center}.review-area,.error-area,.success-area,.clarification-area{padding:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:auto}.review-trust{margin:2px 0 4px}.trust-chip{max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.warnings-area,.review-summary,.review-diff,.apply-details,.clarification-options-block,.clarification-manual-toggle{width:100%;max-width:100%;min-width:0}.review-diff{padding:12px 14px}.review-diff-full{padding:10px;overflow-x:auto}.review-diff-block{min-width:0}.review-diff-block pre{overflow-wrap:anywhere;word-break:break-word}.clarification-options-block{padding:4px 2px 10px}.clarification-option{line-height:normal}:host ::ng-deep .clarification-option .mdc-button__label{line-height:1.35!important}.clarification-plain-label{padding:6px 12px 12px;font-size:14px;line-height:1.35}.clarification-manual-toggle{margin-top:10px;padding:10px 2px 0;flex-wrap:wrap;row-gap:6px}.suggestions-content .suggestion-actions{gap:6px}.suggestions-content .suggestion-arrow,.suggestions-content .suggestion-action-btn mat-icon,.suggestions-content .suggestion-arrow mat-icon{display:inline-flex;align-items:center;justify-content:center}:host ::ng-deep .assistant-quick-menu-panel{min-width:244px;max-width:min(90vw,320px);border-radius:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 26%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface-container-high);box-shadow:0 12px 28px color-mix(in srgb,var(--md-sys-color-shadow, var(--md-sys-color-on-surface)) 32%,transparent),inset 0 1px color-mix(in srgb,var(--md-sys-color-on-surface) 6%,transparent);overflow:hidden;padding:6px}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item{min-height:36px;border-radius:10px;color:var(--md-sys-color-on-surface);font-size:12px;font-weight:500;margin:1px 0}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item .mat-icon{color:color-mix(in srgb,var(--md-sys-color-on-surface) 82%,var(--md-sys-color-on-surface-variant))}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item:hover:not([disabled]){background:color-mix(in srgb,var(--md-sys-color-primary-container) 45%,transparent)}:host ::ng-deep .assistant-quick-menu-panel .mat-mdc-menu-item:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:-2px}:host ::ng-deep .assistant-quick-menu-panel .assistant-quick-menu-danger{color:var(--md-sys-color-error)}:host ::ng-deep .assistant-quick-menu-panel .assistant-quick-menu-danger .mat-icon{color:var(--md-sys-color-error)}@media(max-width:959px){::ng-deep .ai-assistant-overlay-pane{top:12px!important;left:12px!important;width:calc(100vw - 24px)!important;height:calc(100vh - 24px)!important;max-width:calc(100vw - 24px);max-height:calc(100vh - 24px);transform:none!important}.ai-assistant-panel{width:calc(100vw - 48px);min-height:min(620px,100vh - 48px);max-height:calc(100vh - 48px)}.assistant-section,.assistant-card{margin:0}.assistant-card,.assistant-section{padding:12px}}.ai-trigger-btn{--mdc-icon-button-state-layer-size: 36px;width:36px;height:36px;border-radius:8px;background:color-mix(in srgb,var(--md-sys-color-primary) 9%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 22%,transparent);color:var(--md-sys-color-primary);opacity:1}.ai-trigger-btn:hover:not(:disabled){background:color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent);border-color:color-mix(in srgb,var(--md-sys-color-primary) 36%,transparent);color:var(--md-sys-color-on-primary-container)}.ai-trigger-btn:focus-visible,.assistant-close-btn:focus-visible,.assistant-tab:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.assistant-close-btn{width:28px;height:28px;color:var(--md-sys-color-on-surface-variant)}.assistant-close-btn:hover:not(:disabled){background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface)}.assistant-body{padding:12px 16px;overflow:hidden;min-height:0;flex:1;display:flex;flex-direction:column;gap:12px}.assistant-body>*{min-height:0}@media(max-width:959px){.assistant-nav{padding:8px 12px 0}.assistant-body,.assistant-footer{padding:10px 12px}.suggestions-content .suggestion-item{grid-template-columns:1fr;align-items:start}.suggestion-actions{justify-content:flex-start}}\n"] }]
|
|
5841
6689
|
}], propDecorators: { adapter: [{
|
|
5842
6690
|
type: Input,
|
|
5843
6691
|
args: [{ required: true }]
|
|
@@ -5845,6 +6693,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
5845
6693
|
type: Input
|
|
5846
6694
|
}], allowManualPatchEdit: [{
|
|
5847
6695
|
type: Input
|
|
6696
|
+
}], hasBackdrop: [{
|
|
6697
|
+
type: Input
|
|
5848
6698
|
}], overlayOrigin: [{
|
|
5849
6699
|
type: ViewChild,
|
|
5850
6700
|
args: [CdkOverlayOrigin]
|
|
@@ -5865,14 +6715,15 @@ const DEFAULT_LAYOUT = {
|
|
|
5865
6715
|
const DEFAULT_LABELS = {
|
|
5866
6716
|
title: 'Assistente de IA',
|
|
5867
6717
|
subtitle: 'Revise o resultado gerado antes de aplicar.',
|
|
5868
|
-
close: '
|
|
6718
|
+
close: 'Minimizar assistente',
|
|
5869
6719
|
prompt: 'Prompt',
|
|
5870
6720
|
promptPlaceholder: 'Descreva o que você quer criar ou alterar.',
|
|
5871
6721
|
emptyConversation: 'Diga o que você quer criar ou alterar.',
|
|
5872
|
-
submit: '
|
|
5873
|
-
apply: 'Aplicar',
|
|
6722
|
+
submit: 'Interpretar pedido',
|
|
6723
|
+
apply: 'Aplicar ajuste',
|
|
5874
6724
|
conversationAria: 'Conversa com IA',
|
|
5875
6725
|
quickRepliesAria: 'Respostas rápidas',
|
|
6726
|
+
quickReplyDetails: 'Detalhes técnicos',
|
|
5876
6727
|
dragHandleAria: 'Mover assistente de IA',
|
|
5877
6728
|
resizeHandleAria: 'Redimensionar assistente de IA',
|
|
5878
6729
|
contextAria: 'Contexto ativo',
|
|
@@ -5911,10 +6762,15 @@ class PraxisAiAssistantShellComponent {
|
|
|
5911
6762
|
panelTestId = '';
|
|
5912
6763
|
submitTestId = '';
|
|
5913
6764
|
applyTestId = '';
|
|
6765
|
+
primaryAction = null;
|
|
6766
|
+
secondaryActions = [];
|
|
6767
|
+
governanceActions = [];
|
|
5914
6768
|
busy = false;
|
|
5915
6769
|
canSubmit = true;
|
|
5916
6770
|
canApply = false;
|
|
5917
6771
|
submitOnEnter = true;
|
|
6772
|
+
showAttachAction = false;
|
|
6773
|
+
enablePastedAttachments = false;
|
|
5918
6774
|
enableFileAttachments = false;
|
|
5919
6775
|
attachmentAccept = '';
|
|
5920
6776
|
attachmentMultiple = true;
|
|
@@ -5927,6 +6783,9 @@ class PraxisAiAssistantShellComponent {
|
|
|
5927
6783
|
promptChange = new EventEmitter();
|
|
5928
6784
|
submitPrompt = new EventEmitter();
|
|
5929
6785
|
apply = new EventEmitter();
|
|
6786
|
+
retryTurn = new EventEmitter();
|
|
6787
|
+
cancelTurn = new EventEmitter();
|
|
6788
|
+
shellAction = new EventEmitter();
|
|
5930
6789
|
close = new EventEmitter();
|
|
5931
6790
|
attach = new EventEmitter();
|
|
5932
6791
|
attachmentsPasted = new EventEmitter();
|
|
@@ -5943,6 +6802,7 @@ class PraxisAiAssistantShellComponent {
|
|
|
5943
6802
|
currentPrompt = '';
|
|
5944
6803
|
resolvedLabels = DEFAULT_LABELS;
|
|
5945
6804
|
currentLayout = { ...DEFAULT_LAYOUT };
|
|
6805
|
+
resizeHandles = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];
|
|
5946
6806
|
pointerSession = null;
|
|
5947
6807
|
ownedPreviewUrls = new Set();
|
|
5948
6808
|
onWindowPointerMove = (event) => this.handlePointerMove(event);
|
|
@@ -5989,6 +6849,9 @@ class PraxisAiAssistantShellComponent {
|
|
|
5989
6849
|
this.onSubmit();
|
|
5990
6850
|
}
|
|
5991
6851
|
onPromptPaste(event) {
|
|
6852
|
+
if (!this.enablePastedAttachments) {
|
|
6853
|
+
return;
|
|
6854
|
+
}
|
|
5992
6855
|
const files = Array.from(event.clipboardData?.files ?? [])
|
|
5993
6856
|
.filter((file) => file.type.startsWith('image/'));
|
|
5994
6857
|
if (this.busy || !files.length) {
|
|
@@ -6023,11 +6886,737 @@ class PraxisAiAssistantShellComponent {
|
|
|
6023
6886
|
return;
|
|
6024
6887
|
this.apply.emit();
|
|
6025
6888
|
}
|
|
6889
|
+
renderMessageText(text) {
|
|
6890
|
+
const lines = (text ?? '').replace(/\r\n?/g, '\n').split('\n');
|
|
6891
|
+
const html = [];
|
|
6892
|
+
let listOpen = false;
|
|
6893
|
+
const closeList = () => {
|
|
6894
|
+
if (listOpen) {
|
|
6895
|
+
html.push('</ul>');
|
|
6896
|
+
listOpen = false;
|
|
6897
|
+
}
|
|
6898
|
+
};
|
|
6899
|
+
for (const rawLine of lines) {
|
|
6900
|
+
const line = rawLine.trim();
|
|
6901
|
+
if (!line) {
|
|
6902
|
+
closeList();
|
|
6903
|
+
continue;
|
|
6904
|
+
}
|
|
6905
|
+
const heading = line.match(/^(#{1,3})\s+(.+)$/);
|
|
6906
|
+
if (heading) {
|
|
6907
|
+
closeList();
|
|
6908
|
+
const level = Math.min(heading[1].length + 2, 5);
|
|
6909
|
+
html.push(`<h${level}>${this.renderInlineMarkdown(heading[2])}</h${level}>`);
|
|
6910
|
+
continue;
|
|
6911
|
+
}
|
|
6912
|
+
const bullet = line.match(/^[-*]\s+(.+)$/);
|
|
6913
|
+
if (bullet) {
|
|
6914
|
+
if (!listOpen) {
|
|
6915
|
+
html.push('<ul>');
|
|
6916
|
+
listOpen = true;
|
|
6917
|
+
}
|
|
6918
|
+
html.push(`<li>${this.renderInlineMarkdown(bullet[1])}</li>`);
|
|
6919
|
+
continue;
|
|
6920
|
+
}
|
|
6921
|
+
closeList();
|
|
6922
|
+
html.push(`<p>${this.renderInlineMarkdown(line)}</p>`);
|
|
6923
|
+
}
|
|
6924
|
+
closeList();
|
|
6925
|
+
return html.join('');
|
|
6926
|
+
}
|
|
6927
|
+
renderInlineMarkdown(value) {
|
|
6928
|
+
return this.escapeHtml(value)
|
|
6929
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
6930
|
+
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
6931
|
+
.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
6932
|
+
}
|
|
6933
|
+
escapeHtml(value) {
|
|
6934
|
+
return value
|
|
6935
|
+
.replace(/&/g, '&')
|
|
6936
|
+
.replace(/</g, '<')
|
|
6937
|
+
.replace(/>/g, '>')
|
|
6938
|
+
.replace(/"/g, '"')
|
|
6939
|
+
.replace(/'/g, ''');
|
|
6940
|
+
}
|
|
6941
|
+
shouldShowStatusText() {
|
|
6942
|
+
return this.shouldShowAuxiliaryText(this.statusText, false);
|
|
6943
|
+
}
|
|
6944
|
+
shouldShowErrorText() {
|
|
6945
|
+
return this.shouldShowAuxiliaryText(this.errorText, true);
|
|
6946
|
+
}
|
|
6947
|
+
onShellAction(action) {
|
|
6948
|
+
if (this.isShellActionDisabled(action))
|
|
6949
|
+
return;
|
|
6950
|
+
this.shellAction.emit(action);
|
|
6951
|
+
switch (action.kind) {
|
|
6952
|
+
case 'submit-prompt':
|
|
6953
|
+
this.onSubmit();
|
|
6954
|
+
return;
|
|
6955
|
+
case 'apply':
|
|
6956
|
+
this.onApply();
|
|
6957
|
+
return;
|
|
6958
|
+
case 'retry':
|
|
6959
|
+
this.retryTurn.emit();
|
|
6960
|
+
return;
|
|
6961
|
+
case 'cancel':
|
|
6962
|
+
this.cancelTurn.emit();
|
|
6963
|
+
return;
|
|
6964
|
+
default:
|
|
6965
|
+
return;
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6968
|
+
getPrimaryAction() {
|
|
6969
|
+
return this.normalizeShellAction(this.primaryAction, this.getDefaultPrimaryAction());
|
|
6970
|
+
}
|
|
6971
|
+
getSecondaryActions() {
|
|
6972
|
+
const actions = [
|
|
6973
|
+
...this.secondaryActions,
|
|
6974
|
+
...this.governanceActions,
|
|
6975
|
+
];
|
|
6976
|
+
if (this.canApply) {
|
|
6977
|
+
actions.push({
|
|
6978
|
+
id: 'apply',
|
|
6979
|
+
kind: 'apply',
|
|
6980
|
+
label: this.resolvedLabels.apply,
|
|
6981
|
+
icon: 'check_circle',
|
|
6982
|
+
tone: 'governance',
|
|
6983
|
+
disabled: !this.canApply,
|
|
6984
|
+
testId: this.applyTestId || `${this.testIdPrefix}-apply`,
|
|
6985
|
+
});
|
|
6986
|
+
}
|
|
6987
|
+
if (this.state === 'error') {
|
|
6988
|
+
actions.push({
|
|
6989
|
+
id: 'retry',
|
|
6990
|
+
kind: 'retry',
|
|
6991
|
+
label: 'Tentar novamente',
|
|
6992
|
+
icon: 'replay',
|
|
6993
|
+
tone: 'warning',
|
|
6994
|
+
testId: `${this.testIdPrefix}-retry`,
|
|
6995
|
+
});
|
|
6996
|
+
}
|
|
6997
|
+
if (this.hasRecoverableTurn()) {
|
|
6998
|
+
actions.push({
|
|
6999
|
+
id: 'cancel',
|
|
7000
|
+
kind: 'cancel',
|
|
7001
|
+
label: 'Cancelar pedido',
|
|
7002
|
+
icon: 'close',
|
|
7003
|
+
tone: 'neutral',
|
|
7004
|
+
testId: `${this.testIdPrefix}-cancel-turn`,
|
|
7005
|
+
});
|
|
7006
|
+
}
|
|
7007
|
+
return actions.map((action) => this.normalizeShellAction(action));
|
|
7008
|
+
}
|
|
7009
|
+
getPrimaryActionTooltip(action) {
|
|
7010
|
+
return action.iconOnly ? action.ariaLabel || action.label : '';
|
|
7011
|
+
}
|
|
7012
|
+
isShellActionDisabled(action) {
|
|
7013
|
+
return Boolean(this.busy
|
|
7014
|
+
|| action.disabled
|
|
7015
|
+
|| (action.requiresPrompt && !this.currentPrompt.trim())
|
|
7016
|
+
|| (action.kind === 'submit-prompt' && !this.canSubmit)
|
|
7017
|
+
|| (action.kind === 'apply' && !this.canApply));
|
|
7018
|
+
}
|
|
7019
|
+
getShellActionTone(action) {
|
|
7020
|
+
const tone = (action.tone || (action.kind === 'apply' ? 'governance' : 'secondary')).toLowerCase();
|
|
7021
|
+
switch (tone) {
|
|
7022
|
+
case 'primary':
|
|
7023
|
+
case 'secondary':
|
|
7024
|
+
case 'governance':
|
|
7025
|
+
case 'success':
|
|
7026
|
+
case 'warning':
|
|
7027
|
+
case 'danger':
|
|
7028
|
+
case 'neutral':
|
|
7029
|
+
return tone;
|
|
7030
|
+
default:
|
|
7031
|
+
return 'secondary';
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
7034
|
+
trackShellAction(_index, action) {
|
|
7035
|
+
return action.id;
|
|
7036
|
+
}
|
|
7037
|
+
hasRecoverableTurn() {
|
|
7038
|
+
if (this.busy || this.state === 'idle' || this.state === 'listening') {
|
|
7039
|
+
return false;
|
|
7040
|
+
}
|
|
7041
|
+
return this.messages.length > 0
|
|
7042
|
+
|| this.quickReplies.length > 0
|
|
7043
|
+
|| this.canApply
|
|
7044
|
+
|| this.state === 'clarification'
|
|
7045
|
+
|| this.state === 'review'
|
|
7046
|
+
|| this.state === 'error';
|
|
7047
|
+
}
|
|
7048
|
+
shouldShowAuxiliaryText(text, errorOnly) {
|
|
7049
|
+
const normalized = this.normalizeMessageText(text);
|
|
7050
|
+
if (!normalized)
|
|
7051
|
+
return false;
|
|
7052
|
+
const lastMessage = [...this.messages]
|
|
7053
|
+
.reverse()
|
|
7054
|
+
.find((message) => {
|
|
7055
|
+
if (!message.text?.trim())
|
|
7056
|
+
return false;
|
|
7057
|
+
if (errorOnly)
|
|
7058
|
+
return message.role === 'error';
|
|
7059
|
+
return message.role === 'assistant' || message.role === 'status';
|
|
7060
|
+
});
|
|
7061
|
+
return normalized !== this.normalizeMessageText(lastMessage?.text);
|
|
7062
|
+
}
|
|
7063
|
+
normalizeMessageText(text) {
|
|
7064
|
+
return (text ?? '').replace(/\s+/g, ' ').trim();
|
|
7065
|
+
}
|
|
7066
|
+
getDefaultPrimaryAction() {
|
|
7067
|
+
const base = {
|
|
7068
|
+
id: 'submit',
|
|
7069
|
+
kind: 'submit-prompt',
|
|
7070
|
+
label: this.resolvedLabels.submit,
|
|
7071
|
+
icon: 'auto_awesome',
|
|
7072
|
+
tone: 'primary',
|
|
7073
|
+
requiresPrompt: true,
|
|
7074
|
+
testId: this.submitTestId || `${this.testIdPrefix}-submit`,
|
|
7075
|
+
};
|
|
7076
|
+
switch (this.state) {
|
|
7077
|
+
case 'processing':
|
|
7078
|
+
return {
|
|
7079
|
+
...base,
|
|
7080
|
+
label: 'Interpretando...',
|
|
7081
|
+
icon: 'hourglass_top',
|
|
7082
|
+
disabled: true,
|
|
7083
|
+
};
|
|
7084
|
+
case 'clarification':
|
|
7085
|
+
return {
|
|
7086
|
+
...base,
|
|
7087
|
+
label: 'Responder',
|
|
7088
|
+
icon: 'question_answer',
|
|
7089
|
+
};
|
|
7090
|
+
case 'review':
|
|
7091
|
+
return {
|
|
7092
|
+
...base,
|
|
7093
|
+
label: 'Refinar pedido',
|
|
7094
|
+
icon: 'tune',
|
|
7095
|
+
};
|
|
7096
|
+
case 'applying':
|
|
7097
|
+
return {
|
|
7098
|
+
...base,
|
|
7099
|
+
label: 'Aplicando...',
|
|
7100
|
+
icon: 'sync',
|
|
7101
|
+
disabled: true,
|
|
7102
|
+
};
|
|
7103
|
+
case 'error':
|
|
7104
|
+
return {
|
|
7105
|
+
...base,
|
|
7106
|
+
label: 'Corrigir pedido',
|
|
7107
|
+
icon: 'edit_note',
|
|
7108
|
+
tone: 'warning',
|
|
7109
|
+
};
|
|
7110
|
+
case 'success':
|
|
7111
|
+
return {
|
|
7112
|
+
...base,
|
|
7113
|
+
label: 'Novo pedido',
|
|
7114
|
+
icon: 'add_comment',
|
|
7115
|
+
};
|
|
7116
|
+
case 'idle':
|
|
7117
|
+
case 'listening':
|
|
7118
|
+
default:
|
|
7119
|
+
return base;
|
|
7120
|
+
}
|
|
7121
|
+
}
|
|
6026
7122
|
onQuickReply(reply) {
|
|
6027
7123
|
if (this.busy)
|
|
6028
7124
|
return;
|
|
6029
7125
|
this.quickReply.emit(reply);
|
|
6030
7126
|
}
|
|
7127
|
+
getQuickReplyAriaLabel(reply) {
|
|
7128
|
+
const label = reply.label?.trim() ?? '';
|
|
7129
|
+
const description = this.getQuickReplyDescription(reply);
|
|
7130
|
+
const presentation = this.getQuickReplyPresentationItems(reply)
|
|
7131
|
+
.map((item) => `${item.label}: ${item.value}`)
|
|
7132
|
+
.join('. ');
|
|
7133
|
+
return [label, description, presentation]
|
|
7134
|
+
.filter((segment) => typeof segment === 'string' && segment.length > 0)
|
|
7135
|
+
.map((segment) => this.trimSentencePunctuation(segment))
|
|
7136
|
+
.join('. ');
|
|
7137
|
+
}
|
|
7138
|
+
getQuickReplyTechnicalDetails(reply) {
|
|
7139
|
+
const explicit = this.quickReplyPresentation(reply)?.technicalDetails?.trim();
|
|
7140
|
+
if (explicit)
|
|
7141
|
+
return explicit;
|
|
7142
|
+
if (this.isFieldDiscoveryQuickReply(reply)
|
|
7143
|
+
|| this.isGuidedActionQuickReply(reply)
|
|
7144
|
+
|| this.isContextualPreviewActionQuickReply(reply))
|
|
7145
|
+
return '';
|
|
7146
|
+
const hints = reply.contextHints;
|
|
7147
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7148
|
+
if (!details)
|
|
7149
|
+
return '';
|
|
7150
|
+
const submitMethod = this.quickReplyHint(details, hints, 'submitMethod') || this.quickReplyHint(details, hints, 'operation');
|
|
7151
|
+
const submitUrl = this.quickReplyHint(details, hints, 'submitUrl');
|
|
7152
|
+
const resourcePath = this.quickReplyHint(details, hints, 'resourcePath');
|
|
7153
|
+
const schemaUrl = this.quickReplyHint(details, hints, 'schemaUrl');
|
|
7154
|
+
return [
|
|
7155
|
+
submitMethod && submitUrl ? `${submitMethod.toUpperCase()} ${submitUrl}` : '',
|
|
7156
|
+
resourcePath && resourcePath !== submitUrl ? `Recurso: ${resourcePath}` : '',
|
|
7157
|
+
schemaUrl ? `Schema: ${schemaUrl}` : '',
|
|
7158
|
+
].filter(Boolean).join('\n');
|
|
7159
|
+
}
|
|
7160
|
+
isRichQuickReply(reply) {
|
|
7161
|
+
return Boolean(this.getQuickReplyDescription(reply)
|
|
7162
|
+
|| this.getQuickReplyPresentationItems(reply).length
|
|
7163
|
+
|| this.getQuickReplyContextChips(reply).length);
|
|
7164
|
+
}
|
|
7165
|
+
shouldUseInlineQuickReplies() {
|
|
7166
|
+
if (!this.quickReplies.length)
|
|
7167
|
+
return false;
|
|
7168
|
+
return this.quickReplies.every((reply) => this.isGuidedActionQuickReply(reply) && !this.isRichQuickReply(reply));
|
|
7169
|
+
}
|
|
7170
|
+
getQuickReplyDescription(reply) {
|
|
7171
|
+
const authored = this.quickReplyPresentation(reply)?.description?.trim();
|
|
7172
|
+
if (authored)
|
|
7173
|
+
return authored;
|
|
7174
|
+
const explicit = reply.description?.trim() ?? '';
|
|
7175
|
+
if (!this.isContextualPreviewActionQuickReply(reply)) {
|
|
7176
|
+
return explicit;
|
|
7177
|
+
}
|
|
7178
|
+
if (explicit && !this.isGenericContextualActionDescription(explicit)) {
|
|
7179
|
+
return explicit;
|
|
7180
|
+
}
|
|
7181
|
+
const hints = reply.contextHints;
|
|
7182
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7183
|
+
const changeKind = details ? this.quickReplyHint(details, hints, 'changeKind') : '';
|
|
7184
|
+
switch (changeKind) {
|
|
7185
|
+
case 'set_chart_type':
|
|
7186
|
+
return 'Altera apenas a apresentação do gráfico selecionado e mantém a fonte de dados atual.';
|
|
7187
|
+
case 'enable_chart_drilldown':
|
|
7188
|
+
return 'Adiciona uma superfície de detalhe a partir da seleção do gráfico, preservando o contexto do dado.';
|
|
7189
|
+
case 'configure_export':
|
|
7190
|
+
return 'Configura uma ação operacional para exportar a seleção atual sem mudar a consulta base.';
|
|
7191
|
+
default:
|
|
7192
|
+
return 'Prepara um ajuste compatível com as capacidades confirmadas do componente selecionado.';
|
|
7193
|
+
}
|
|
7194
|
+
}
|
|
7195
|
+
getQuickReplyContextChips(reply) {
|
|
7196
|
+
if (this.isFieldDiscoveryQuickReply(reply) || this.isGuidedActionQuickReply(reply))
|
|
7197
|
+
return [];
|
|
7198
|
+
const evidence = this.quickReplyPresentation(reply)?.evidence;
|
|
7199
|
+
if (evidence?.length) {
|
|
7200
|
+
return evidence
|
|
7201
|
+
.filter((item) => !!item.value?.trim())
|
|
7202
|
+
.map((item) => this.presentationEvidenceToChip(item))
|
|
7203
|
+
.slice(0, 4);
|
|
7204
|
+
}
|
|
7205
|
+
if (this.isContextualPreviewActionQuickReply(reply)) {
|
|
7206
|
+
return this.getContextualActionChips(reply);
|
|
7207
|
+
}
|
|
7208
|
+
const hints = reply.contextHints;
|
|
7209
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7210
|
+
if (!details)
|
|
7211
|
+
return [];
|
|
7212
|
+
const submitMethod = this.quickReplyHint(details, hints, 'submitMethod') || this.quickReplyHint(details, hints, 'operation');
|
|
7213
|
+
const submitUrl = this.quickReplyHint(details, hints, 'submitUrl');
|
|
7214
|
+
const resourcePath = this.quickReplyHint(details, hints, 'resourcePath');
|
|
7215
|
+
const schemaUrl = this.quickReplyHint(details, hints, 'schemaUrl');
|
|
7216
|
+
const chips = [];
|
|
7217
|
+
if (submitMethod) {
|
|
7218
|
+
chips.push({
|
|
7219
|
+
icon: 'bolt',
|
|
7220
|
+
value: submitMethod.toUpperCase(),
|
|
7221
|
+
ariaLabel: `Operação ${submitMethod.toUpperCase()}`,
|
|
7222
|
+
});
|
|
7223
|
+
}
|
|
7224
|
+
const resourceLabel = this.shortPathLabel(resourcePath || submitUrl);
|
|
7225
|
+
if (resourceLabel) {
|
|
7226
|
+
chips.push({
|
|
7227
|
+
icon: 'dataset',
|
|
7228
|
+
value: resourceLabel,
|
|
7229
|
+
ariaLabel: `Recurso ${resourceLabel}`,
|
|
7230
|
+
});
|
|
7231
|
+
}
|
|
7232
|
+
if (schemaUrl) {
|
|
7233
|
+
chips.push({
|
|
7234
|
+
icon: 'schema',
|
|
7235
|
+
value: 'schema',
|
|
7236
|
+
ariaLabel: 'Schema disponível',
|
|
7237
|
+
});
|
|
7238
|
+
}
|
|
7239
|
+
return chips;
|
|
7240
|
+
}
|
|
7241
|
+
getQuickReplyPresentationItems(reply) {
|
|
7242
|
+
if (this.isFieldDiscoveryQuickReply(reply)
|
|
7243
|
+
|| this.isGuidedActionQuickReply(reply)
|
|
7244
|
+
|| this.isContextualPreviewActionQuickReply(reply))
|
|
7245
|
+
return [];
|
|
7246
|
+
const authoredItems = this.quickReplyPresentation(reply)?.items
|
|
7247
|
+
?.filter((item) => !!item.label?.trim() && !!item.value?.trim())
|
|
7248
|
+
.map((item) => ({
|
|
7249
|
+
key: item.key ?? item.label,
|
|
7250
|
+
label: item.label.trim(),
|
|
7251
|
+
icon: item.icon?.trim() || 'info',
|
|
7252
|
+
value: item.value.trim(),
|
|
7253
|
+
}));
|
|
7254
|
+
if (authoredItems?.length) {
|
|
7255
|
+
return authoredItems;
|
|
7256
|
+
}
|
|
7257
|
+
const hints = reply.contextHints;
|
|
7258
|
+
const presentation = this.asRecord(hints?.['presentation']);
|
|
7259
|
+
const resolvedPresentation = presentation ?? this.defaultQuickReplyPresentation(reply);
|
|
7260
|
+
if (!resolvedPresentation)
|
|
7261
|
+
return [];
|
|
7262
|
+
const items = [
|
|
7263
|
+
{
|
|
7264
|
+
key: 'bestFor',
|
|
7265
|
+
label: 'Indicado para',
|
|
7266
|
+
icon: 'ads_click',
|
|
7267
|
+
value: this.stringHint(resolvedPresentation, 'bestFor'),
|
|
7268
|
+
},
|
|
7269
|
+
{
|
|
7270
|
+
key: 'returns',
|
|
7271
|
+
label: 'Retorna',
|
|
7272
|
+
icon: 'stacked_line_chart',
|
|
7273
|
+
value: this.stringHint(resolvedPresentation, 'returns'),
|
|
7274
|
+
},
|
|
7275
|
+
{
|
|
7276
|
+
key: 'nextStep',
|
|
7277
|
+
label: 'Próximo passo',
|
|
7278
|
+
icon: 'arrow_forward',
|
|
7279
|
+
value: this.stringHint(resolvedPresentation, 'nextStep'),
|
|
7280
|
+
},
|
|
7281
|
+
];
|
|
7282
|
+
return items.filter((item) => item.value.length > 0);
|
|
7283
|
+
}
|
|
7284
|
+
defaultQuickReplyPresentation(reply) {
|
|
7285
|
+
const kind = (reply.kind || reply.tone || '').toLowerCase();
|
|
7286
|
+
switch (kind) {
|
|
7287
|
+
case 'confirm':
|
|
7288
|
+
case 'primary':
|
|
7289
|
+
case 'analytics':
|
|
7290
|
+
return {
|
|
7291
|
+
bestFor: 'Validar a recomendação antes de salvar ou materializar a página.',
|
|
7292
|
+
returns: 'Uma prévia governada com layout, widgets e sinais de decisão.',
|
|
7293
|
+
nextStep: 'Abra a prévia e revise se a composição atende ao objetivo.',
|
|
7294
|
+
};
|
|
7295
|
+
case 'resource':
|
|
7296
|
+
case 'suggestion':
|
|
7297
|
+
if (!this.hasQuickReplyResourceContext(reply))
|
|
7298
|
+
return null;
|
|
7299
|
+
return {
|
|
7300
|
+
bestFor: 'Explorar uma fonte de dados candidata para gráficos e indicadores.',
|
|
7301
|
+
returns: 'Campos, métricas prováveis e caminhos de drill-down disponíveis.',
|
|
7302
|
+
nextStep: 'Clique para usar esta fonte como contexto da próxima decisão.',
|
|
7303
|
+
};
|
|
7304
|
+
case 'revise':
|
|
7305
|
+
case 'warning':
|
|
7306
|
+
return null;
|
|
7307
|
+
default:
|
|
7308
|
+
return null;
|
|
7309
|
+
}
|
|
7310
|
+
}
|
|
7311
|
+
getQuickReplyCategoryLabel(reply) {
|
|
7312
|
+
const categoryLabel = this.quickReplyPresentation(reply)?.categoryLabel?.trim();
|
|
7313
|
+
if (categoryLabel)
|
|
7314
|
+
return categoryLabel;
|
|
7315
|
+
const kind = (reply.kind || '').toLowerCase();
|
|
7316
|
+
const tone = (reply.tone || '').toLowerCase();
|
|
7317
|
+
if (this.isContextualPreviewActionQuickReply(reply)) {
|
|
7318
|
+
return 'Ação sugerida';
|
|
7319
|
+
}
|
|
7320
|
+
if ((kind === 'resource' || kind === 'suggestion') && !this.hasQuickReplyResourceContext(reply)) {
|
|
7321
|
+
if (tone === 'primary' || tone === 'analytics' || tone === 'confirm')
|
|
7322
|
+
return 'Recomendado';
|
|
7323
|
+
return 'Opção guiada';
|
|
7324
|
+
}
|
|
7325
|
+
const normalizedKind = (kind || tone).toLowerCase();
|
|
7326
|
+
switch (normalizedKind) {
|
|
7327
|
+
case 'confirm':
|
|
7328
|
+
case 'primary':
|
|
7329
|
+
case 'analytics':
|
|
7330
|
+
return 'Recomendado';
|
|
7331
|
+
case 'revise':
|
|
7332
|
+
case 'warning':
|
|
7333
|
+
return 'Ajustar antes';
|
|
7334
|
+
case 'resource':
|
|
7335
|
+
case 'suggestion':
|
|
7336
|
+
return 'Fonte candidata';
|
|
7337
|
+
case 'success':
|
|
7338
|
+
return 'Pronto para usar';
|
|
7339
|
+
case 'cancel':
|
|
7340
|
+
case 'danger':
|
|
7341
|
+
return 'Encerrar';
|
|
7342
|
+
default:
|
|
7343
|
+
return 'Opção guiada';
|
|
7344
|
+
}
|
|
7345
|
+
}
|
|
7346
|
+
getQuickReplyIcon(reply) {
|
|
7347
|
+
const presentationIcon = this.quickReplyPresentation(reply)?.icon?.trim();
|
|
7348
|
+
if (presentationIcon)
|
|
7349
|
+
return presentationIcon;
|
|
7350
|
+
const explicitIcon = reply.icon?.trim();
|
|
7351
|
+
if (explicitIcon)
|
|
7352
|
+
return explicitIcon;
|
|
7353
|
+
const kind = (reply.kind || reply.tone || '').toLowerCase();
|
|
7354
|
+
switch (kind) {
|
|
7355
|
+
case 'confirm':
|
|
7356
|
+
case 'primary':
|
|
7357
|
+
return 'auto_awesome';
|
|
7358
|
+
case 'analytics':
|
|
7359
|
+
return 'query_stats';
|
|
7360
|
+
case 'resource':
|
|
7361
|
+
case 'suggestion':
|
|
7362
|
+
return 'dataset';
|
|
7363
|
+
case 'revise':
|
|
7364
|
+
case 'warning':
|
|
7365
|
+
return 'tune';
|
|
7366
|
+
case 'cancel':
|
|
7367
|
+
case 'danger':
|
|
7368
|
+
return 'close';
|
|
7369
|
+
case 'success':
|
|
7370
|
+
return 'check_circle';
|
|
7371
|
+
default:
|
|
7372
|
+
return 'touch_app';
|
|
7373
|
+
}
|
|
7374
|
+
}
|
|
7375
|
+
getQuickReplyCtaLabel(reply) {
|
|
7376
|
+
const ctaLabel = this.quickReplyPresentation(reply)?.ctaLabel?.trim();
|
|
7377
|
+
if (ctaLabel)
|
|
7378
|
+
return ctaLabel;
|
|
7379
|
+
const kind = (reply.kind || '').toLowerCase();
|
|
7380
|
+
if (this.isContextualPreviewActionQuickReply(reply)) {
|
|
7381
|
+
return 'Pré-visualizar ajuste';
|
|
7382
|
+
}
|
|
7383
|
+
if ((kind === 'resource' || kind === 'suggestion') && !this.hasQuickReplyResourceContext(reply)) {
|
|
7384
|
+
return 'Usar esta opção';
|
|
7385
|
+
}
|
|
7386
|
+
const normalizedKind = (kind || reply.tone || '').toLowerCase();
|
|
7387
|
+
switch (normalizedKind) {
|
|
7388
|
+
case 'revise':
|
|
7389
|
+
case 'warning':
|
|
7390
|
+
return 'Refinar';
|
|
7391
|
+
case 'cancel':
|
|
7392
|
+
case 'danger':
|
|
7393
|
+
return 'Cancelar';
|
|
7394
|
+
case 'resource':
|
|
7395
|
+
case 'suggestion':
|
|
7396
|
+
return 'Explorar';
|
|
7397
|
+
default:
|
|
7398
|
+
return 'Usar esta opção';
|
|
7399
|
+
}
|
|
7400
|
+
}
|
|
7401
|
+
getQuickReplyTone(reply) {
|
|
7402
|
+
const presentationTone = this.quickReplyPresentation(reply)?.tone?.trim().toLowerCase();
|
|
7403
|
+
if (presentationTone)
|
|
7404
|
+
return this.normalizeQuickReplyTone(presentationTone);
|
|
7405
|
+
if (this.isContextualPreviewActionQuickReply(reply)) {
|
|
7406
|
+
return 'analytics';
|
|
7407
|
+
}
|
|
7408
|
+
const tone = (reply.tone || reply.kind || 'neutral').toLowerCase();
|
|
7409
|
+
switch (tone) {
|
|
7410
|
+
case 'primary':
|
|
7411
|
+
case 'analytics':
|
|
7412
|
+
case 'resource':
|
|
7413
|
+
case 'warning':
|
|
7414
|
+
case 'neutral':
|
|
7415
|
+
case 'success':
|
|
7416
|
+
case 'danger':
|
|
7417
|
+
return tone;
|
|
7418
|
+
case 'confirm':
|
|
7419
|
+
return 'primary';
|
|
7420
|
+
case 'suggestion':
|
|
7421
|
+
return 'resource';
|
|
7422
|
+
case 'revise':
|
|
7423
|
+
return 'warning';
|
|
7424
|
+
case 'cancel':
|
|
7425
|
+
default:
|
|
7426
|
+
return 'neutral';
|
|
7427
|
+
}
|
|
7428
|
+
}
|
|
7429
|
+
normalizeQuickReplyTone(tone) {
|
|
7430
|
+
switch (tone) {
|
|
7431
|
+
case 'primary':
|
|
7432
|
+
case 'analytics':
|
|
7433
|
+
case 'resource':
|
|
7434
|
+
case 'warning':
|
|
7435
|
+
case 'neutral':
|
|
7436
|
+
case 'success':
|
|
7437
|
+
case 'danger':
|
|
7438
|
+
return tone;
|
|
7439
|
+
case 'confirm':
|
|
7440
|
+
return 'primary';
|
|
7441
|
+
case 'suggestion':
|
|
7442
|
+
return 'resource';
|
|
7443
|
+
case 'revise':
|
|
7444
|
+
return 'warning';
|
|
7445
|
+
case 'cancel':
|
|
7446
|
+
default:
|
|
7447
|
+
return 'neutral';
|
|
7448
|
+
}
|
|
7449
|
+
}
|
|
7450
|
+
asRecord(value) {
|
|
7451
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
7452
|
+
? value
|
|
7453
|
+
: null;
|
|
7454
|
+
}
|
|
7455
|
+
stringHint(source, key) {
|
|
7456
|
+
const value = source[key];
|
|
7457
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
7458
|
+
}
|
|
7459
|
+
quickReplyHint(primary, fallback, key) {
|
|
7460
|
+
return this.stringHint(primary, key) || (fallback ? this.stringHint(fallback, key) : '');
|
|
7461
|
+
}
|
|
7462
|
+
hasQuickReplyResourceContext(reply) {
|
|
7463
|
+
if (this.isFieldDiscoveryQuickReply(reply) || this.isContextualPreviewActionQuickReply(reply))
|
|
7464
|
+
return false;
|
|
7465
|
+
const hints = reply.contextHints;
|
|
7466
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7467
|
+
if (!details)
|
|
7468
|
+
return false;
|
|
7469
|
+
return Boolean(this.quickReplyHint(details, hints, 'resourcePath')
|
|
7470
|
+
|| this.quickReplyHint(details, hints, 'submitUrl')
|
|
7471
|
+
|| this.quickReplyHint(details, hints, 'schemaUrl'));
|
|
7472
|
+
}
|
|
7473
|
+
isFieldDiscoveryQuickReply(reply) {
|
|
7474
|
+
const hints = reply.contextHints;
|
|
7475
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7476
|
+
const questionKind = details
|
|
7477
|
+
? this.quickReplyHint(details, hints, 'questionKind').toLowerCase()
|
|
7478
|
+
: this.stringHint(hints ?? {}, 'questionKind').toLowerCase();
|
|
7479
|
+
if (questionKind === 'field_discovery')
|
|
7480
|
+
return true;
|
|
7481
|
+
const id = (reply.id || '').toLowerCase();
|
|
7482
|
+
const label = (reply.label || '').toLocaleLowerCase('pt-BR');
|
|
7483
|
+
const prompt = (reply.prompt || '').toLocaleLowerCase('pt-BR');
|
|
7484
|
+
return id.includes('fields')
|
|
7485
|
+
|| label.includes('ver campos')
|
|
7486
|
+
|| prompt.includes('quais campos');
|
|
7487
|
+
}
|
|
7488
|
+
isContextualPreviewActionQuickReply(reply) {
|
|
7489
|
+
if (this.quickReplyPresentationKind(reply) === 'contextual-action') {
|
|
7490
|
+
return true;
|
|
7491
|
+
}
|
|
7492
|
+
const hints = reply.contextHints;
|
|
7493
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7494
|
+
const source = details ? this.quickReplyHint(details, hints, 'source') : this.stringHint(hints ?? {}, 'source');
|
|
7495
|
+
const kind = details ? this.quickReplyHint(details, hints, 'kind') : this.stringHint(hints ?? {}, 'kind');
|
|
7496
|
+
const id = (reply.id || '').trim().toLowerCase();
|
|
7497
|
+
return source === 'component-capability-catalog'
|
|
7498
|
+
|| kind === 'contextual-preview-action'
|
|
7499
|
+
|| id.startsWith('chart-')
|
|
7500
|
+
|| id.startsWith('table-export-');
|
|
7501
|
+
}
|
|
7502
|
+
isGuidedActionQuickReply(reply) {
|
|
7503
|
+
const presentationKind = this.quickReplyPresentationKind(reply);
|
|
7504
|
+
if (presentationKind === 'guided-option' || presentationKind === 'quick-action') {
|
|
7505
|
+
return true;
|
|
7506
|
+
}
|
|
7507
|
+
const kind = (reply.kind || '').trim().toLowerCase();
|
|
7508
|
+
const hasActionPresentation = Boolean(this.quickReplyPresentation(reply)?.ctaLabel?.trim()
|
|
7509
|
+
|| this.quickReplyPresentation(reply)?.icon?.trim()
|
|
7510
|
+
|| this.quickReplyPresentation(reply)?.description?.trim());
|
|
7511
|
+
return kind === 'clarification-option' && hasActionPresentation;
|
|
7512
|
+
}
|
|
7513
|
+
quickReplyPresentation(reply) {
|
|
7514
|
+
const presentation = reply.presentation;
|
|
7515
|
+
return presentation && typeof presentation === 'object' && !Array.isArray(presentation)
|
|
7516
|
+
? presentation
|
|
7517
|
+
: null;
|
|
7518
|
+
}
|
|
7519
|
+
quickReplyPresentationKind(reply) {
|
|
7520
|
+
return this.quickReplyPresentation(reply)?.kind?.trim().toLowerCase() ?? '';
|
|
7521
|
+
}
|
|
7522
|
+
presentationEvidenceToChip(item) {
|
|
7523
|
+
const value = item.value.trim();
|
|
7524
|
+
return {
|
|
7525
|
+
icon: item.icon?.trim() || 'verified',
|
|
7526
|
+
value,
|
|
7527
|
+
ariaLabel: item.ariaLabel?.trim() || value,
|
|
7528
|
+
};
|
|
7529
|
+
}
|
|
7530
|
+
getContextualActionChips(reply) {
|
|
7531
|
+
const hints = reply.contextHints;
|
|
7532
|
+
const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
|
|
7533
|
+
if (!details)
|
|
7534
|
+
return [];
|
|
7535
|
+
const chips = [];
|
|
7536
|
+
const targetComponentId = this.quickReplyHint(details, hints, 'targetComponentId')
|
|
7537
|
+
|| this.quickReplyHint(details, hints, 'selectedComponentId');
|
|
7538
|
+
const changeKind = this.quickReplyHint(details, hints, 'changeKind');
|
|
7539
|
+
const capabilityId = this.quickReplyHint(details, hints, 'capabilityId');
|
|
7540
|
+
const selectedWidgetKey = this.quickReplyHint(details, hints, 'selectedWidgetKey');
|
|
7541
|
+
if (targetComponentId) {
|
|
7542
|
+
chips.push({
|
|
7543
|
+
icon: 'widgets',
|
|
7544
|
+
value: this.shortTechnicalLabel(targetComponentId),
|
|
7545
|
+
ariaLabel: `Componente ${targetComponentId}`,
|
|
7546
|
+
});
|
|
7547
|
+
}
|
|
7548
|
+
if (changeKind) {
|
|
7549
|
+
chips.push({
|
|
7550
|
+
icon: 'rule',
|
|
7551
|
+
value: this.shortTechnicalLabel(changeKind),
|
|
7552
|
+
ariaLabel: `Mudança ${changeKind}`,
|
|
7553
|
+
});
|
|
7554
|
+
}
|
|
7555
|
+
if (capabilityId) {
|
|
7556
|
+
chips.push({
|
|
7557
|
+
icon: 'verified',
|
|
7558
|
+
value: 'capability',
|
|
7559
|
+
ariaLabel: `Capability ${capabilityId}`,
|
|
7560
|
+
});
|
|
7561
|
+
}
|
|
7562
|
+
if (!capabilityId && selectedWidgetKey) {
|
|
7563
|
+
chips.push({
|
|
7564
|
+
icon: 'ads_click',
|
|
7565
|
+
value: 'seleção atual',
|
|
7566
|
+
ariaLabel: `Widget selecionado ${selectedWidgetKey}`,
|
|
7567
|
+
});
|
|
7568
|
+
}
|
|
7569
|
+
return chips.slice(0, 3);
|
|
7570
|
+
}
|
|
7571
|
+
isGenericContextualActionDescription(value) {
|
|
7572
|
+
const normalized = value
|
|
7573
|
+
.normalize('NFD')
|
|
7574
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
7575
|
+
.toLocaleLowerCase('pt-BR');
|
|
7576
|
+
return normalized.includes('acao sugerida')
|
|
7577
|
+
&& normalized.includes('capacidades confirmadas');
|
|
7578
|
+
}
|
|
7579
|
+
trimSentencePunctuation(value) {
|
|
7580
|
+
return value.trim().replace(/[.!?]+$/u, '');
|
|
7581
|
+
}
|
|
7582
|
+
shortPathLabel(value) {
|
|
7583
|
+
const normalized = value.trim().replace(/[?#].*$/u, '').replace(/\/+$/u, '');
|
|
7584
|
+
if (!normalized)
|
|
7585
|
+
return '';
|
|
7586
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
7587
|
+
const last = segments.length > 0 ? segments[segments.length - 1] : normalized;
|
|
7588
|
+
return last
|
|
7589
|
+
.replace(/^vw-/u, '')
|
|
7590
|
+
.replace(/-/gu, ' ')
|
|
7591
|
+
.trim();
|
|
7592
|
+
}
|
|
7593
|
+
shortTechnicalLabel(value) {
|
|
7594
|
+
return value
|
|
7595
|
+
.trim()
|
|
7596
|
+
.replace(/^praxis-/u, '')
|
|
7597
|
+
.replace(/@.*$/u, '')
|
|
7598
|
+
.replace(/[_-]+/gu, ' ')
|
|
7599
|
+
.trim();
|
|
7600
|
+
}
|
|
7601
|
+
getCloseIcon() {
|
|
7602
|
+
const label = this.resolvedLabels.close.toLocaleLowerCase('pt-BR');
|
|
7603
|
+
return label.includes('minimiz') ? 'horizontal_rule' : 'close';
|
|
7604
|
+
}
|
|
7605
|
+
normalizeShellAction(action, fallback) {
|
|
7606
|
+
const source = action ?? fallback;
|
|
7607
|
+
return {
|
|
7608
|
+
id: source?.id || fallback?.id || 'action',
|
|
7609
|
+
label: source?.label || fallback?.label || '',
|
|
7610
|
+
kind: source?.kind ?? fallback?.kind ?? 'custom',
|
|
7611
|
+
icon: source?.icon ?? fallback?.icon ?? null,
|
|
7612
|
+
tone: source?.tone ?? fallback?.tone ?? null,
|
|
7613
|
+
disabled: source?.disabled ?? fallback?.disabled ?? false,
|
|
7614
|
+
requiresPrompt: source?.requiresPrompt ?? fallback?.requiresPrompt ?? false,
|
|
7615
|
+
testId: source?.testId ?? fallback?.testId ?? null,
|
|
7616
|
+
ariaLabel: source?.ariaLabel ?? fallback?.ariaLabel ?? null,
|
|
7617
|
+
iconOnly: source?.iconOnly ?? fallback?.iconOnly ?? false,
|
|
7618
|
+
};
|
|
7619
|
+
}
|
|
6031
7620
|
onRemoveAttachment(attachment) {
|
|
6032
7621
|
if (this.busy)
|
|
6033
7622
|
return;
|
|
@@ -6045,6 +7634,26 @@ class PraxisAiAssistantShellComponent {
|
|
|
6045
7634
|
this.resendMessage.emit(message);
|
|
6046
7635
|
}
|
|
6047
7636
|
}
|
|
7637
|
+
getMessageActionIcon(action) {
|
|
7638
|
+
if (action.icon)
|
|
7639
|
+
return action.icon;
|
|
7640
|
+
switch (action.kind) {
|
|
7641
|
+
case 'edit':
|
|
7642
|
+
return 'edit';
|
|
7643
|
+
case 'resend':
|
|
7644
|
+
return 'replay';
|
|
7645
|
+
case 'copy':
|
|
7646
|
+
return 'content_copy';
|
|
7647
|
+
default:
|
|
7648
|
+
return '';
|
|
7649
|
+
}
|
|
7650
|
+
}
|
|
7651
|
+
getMessageActionLabel(action) {
|
|
7652
|
+
return action.ariaLabel || action.label;
|
|
7653
|
+
}
|
|
7654
|
+
isMessageActionIconOnly(action) {
|
|
7655
|
+
return action.iconOnly ?? !!this.getMessageActionIcon(action);
|
|
7656
|
+
}
|
|
6048
7657
|
getModeLabel() {
|
|
6049
7658
|
switch (this.mode) {
|
|
6050
7659
|
case 'config':
|
|
@@ -6088,10 +7697,13 @@ class PraxisAiAssistantShellComponent {
|
|
|
6088
7697
|
return;
|
|
6089
7698
|
this.startPointerSession('drag', event);
|
|
6090
7699
|
}
|
|
6091
|
-
startResize(event) {
|
|
7700
|
+
startResize(direction, event) {
|
|
6092
7701
|
if (!this.resizable || event.button !== 0)
|
|
6093
7702
|
return;
|
|
6094
|
-
this.startPointerSession('resize', event);
|
|
7703
|
+
this.startPointerSession('resize', event, direction);
|
|
7704
|
+
}
|
|
7705
|
+
trackResizeHandle(_index, direction) {
|
|
7706
|
+
return direction;
|
|
6095
7707
|
}
|
|
6096
7708
|
trackMessage(_index, message) {
|
|
6097
7709
|
return message.id;
|
|
@@ -6108,15 +7720,16 @@ class PraxisAiAssistantShellComponent {
|
|
|
6108
7720
|
trackAttachment(_index, attachment) {
|
|
6109
7721
|
return attachment.id;
|
|
6110
7722
|
}
|
|
6111
|
-
startPointerSession(mode, event) {
|
|
7723
|
+
startPointerSession(mode, event, resizeDirection) {
|
|
6112
7724
|
event.preventDefault();
|
|
6113
7725
|
event.stopPropagation();
|
|
6114
7726
|
const panel = this.panel?.nativeElement;
|
|
6115
7727
|
if (!panel)
|
|
6116
7728
|
return;
|
|
6117
|
-
const bounds = this.
|
|
7729
|
+
const bounds = this.resolveViewportBounds();
|
|
6118
7730
|
this.pointerSession = {
|
|
6119
7731
|
mode,
|
|
7732
|
+
resizeDirection,
|
|
6120
7733
|
pointerId: event.pointerId,
|
|
6121
7734
|
startX: event.clientX,
|
|
6122
7735
|
startY: event.clientY,
|
|
@@ -6146,15 +7759,40 @@ class PraxisAiAssistantShellComponent {
|
|
|
6146
7759
|
left: session.startLayout.left + deltaX,
|
|
6147
7760
|
top: session.startLayout.top + deltaY,
|
|
6148
7761
|
}
|
|
6149
|
-
:
|
|
6150
|
-
...session.startLayout,
|
|
6151
|
-
width: session.startLayout.width + deltaX,
|
|
6152
|
-
height: session.startLayout.height + deltaY,
|
|
6153
|
-
};
|
|
7762
|
+
: this.resizeLayout(session, deltaX, deltaY);
|
|
6154
7763
|
this.currentLayout = this.clampLayout(next, session.boundsWidth, session.boundsHeight);
|
|
6155
7764
|
this.layoutChange.emit(this.currentLayout);
|
|
6156
7765
|
this.cdr.markForCheck();
|
|
6157
7766
|
}
|
|
7767
|
+
resizeLayout(session, deltaX, deltaY) {
|
|
7768
|
+
const direction = session.resizeDirection ?? 'se';
|
|
7769
|
+
const start = session.startLayout;
|
|
7770
|
+
const right = start.left + start.width;
|
|
7771
|
+
const bottom = start.top + start.height;
|
|
7772
|
+
let left = start.left;
|
|
7773
|
+
let top = start.top;
|
|
7774
|
+
let width = start.width;
|
|
7775
|
+
let height = start.height;
|
|
7776
|
+
if (direction.includes('e')) {
|
|
7777
|
+
const maxWidth = Math.max(this.minWidth, session.boundsWidth - start.left - this.margin);
|
|
7778
|
+
width = this.clamp(start.width + deltaX, this.minWidth, maxWidth);
|
|
7779
|
+
}
|
|
7780
|
+
if (direction.includes('s')) {
|
|
7781
|
+
const maxHeight = Math.max(this.minHeight, session.boundsHeight - start.top - this.margin);
|
|
7782
|
+
height = this.clamp(start.height + deltaY, this.minHeight, maxHeight);
|
|
7783
|
+
}
|
|
7784
|
+
if (direction.includes('w')) {
|
|
7785
|
+
const nextLeft = this.clamp(start.left + deltaX, this.margin, right - this.minWidth);
|
|
7786
|
+
left = nextLeft;
|
|
7787
|
+
width = right - nextLeft;
|
|
7788
|
+
}
|
|
7789
|
+
if (direction.includes('n')) {
|
|
7790
|
+
const nextTop = this.clamp(start.top + deltaY, this.margin, bottom - this.minHeight);
|
|
7791
|
+
top = nextTop;
|
|
7792
|
+
height = bottom - nextTop;
|
|
7793
|
+
}
|
|
7794
|
+
return { left, top, width, height };
|
|
7795
|
+
}
|
|
6158
7796
|
finishPointerSession(event) {
|
|
6159
7797
|
if (this.pointerSession && event.pointerId === this.pointerSession.pointerId) {
|
|
6160
7798
|
try {
|
|
@@ -6172,10 +7810,9 @@ class PraxisAiAssistantShellComponent {
|
|
|
6172
7810
|
window.removeEventListener('pointerup', this.onWindowPointerUp);
|
|
6173
7811
|
window.removeEventListener('pointercancel', this.onWindowPointerUp);
|
|
6174
7812
|
}
|
|
6175
|
-
|
|
6176
|
-
const
|
|
6177
|
-
const
|
|
6178
|
-
const height = hostBounds?.height || (typeof window !== 'undefined' ? window.innerHeight : 768);
|
|
7813
|
+
resolveViewportBounds() {
|
|
7814
|
+
const width = typeof window !== 'undefined' ? window.innerWidth : 1024;
|
|
7815
|
+
const height = typeof window !== 'undefined' ? window.innerHeight : 768;
|
|
6179
7816
|
return {
|
|
6180
7817
|
width: Math.max(width, this.minWidth + this.margin * 2),
|
|
6181
7818
|
height: Math.max(height, this.minHeight + this.margin * 2),
|
|
@@ -6268,7 +7905,7 @@ class PraxisAiAssistantShellComponent {
|
|
|
6268
7905
|
this.ownedPreviewUrls.delete(previewUrl);
|
|
6269
7906
|
}
|
|
6270
7907
|
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 });
|
|
7908
|
+
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 [class.praxis-ai-assistant-shell__quick-replies--inline]=\"shouldUseInlineQuickReplies()\"\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{display:inline-grid;place-items:center;width:34px;height:34px;padding:0;color:var(--md-sys-color-on-surface-variant);line-height:1;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{display:block;width:18px;height:18px;font-size:18px;line-height: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__quick-replies--inline{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.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-replies--inline .praxis-ai-assistant-shell__quick-reply{width:auto;min-width:0;max-width:100%;padding:6px 10px;border-radius:999px;box-shadow:none}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply--guided-action{border-color:color-mix(in srgb,var(--md-sys-color-outline-variant) 76%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 96%,transparent)}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply--guided-action:hover:not(:disabled),.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply--guided-action:focus-visible{border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 52%,transparent);background:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 9%,var(--md-sys-color-surface-container-high));box-shadow:0 0 0 2px color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 12%,transparent)}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply ::ng-deep .mdc-button__label{display:inline-flex;grid-template-columns:none;align-items:center;gap:6px;width:auto}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply ::ng-deep .mat-mdc-button-touch-target{min-height:34px}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-icon-frame{width:18px;height:18px;border:0;border-radius:0;background:transparent;box-shadow:none}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-icon{width:16px;height:16px;color:var(--praxis-ai-assistant-shell-quick-reply-accent);font-size:16px}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-copy,.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-heading,.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-header{display:inline-flex;align-items:center;gap:0}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-label{color:var(--md-sys-color-on-surface);font-size:12.5px;font-weight:650;line-height:1.2;white-space:nowrap}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-description,.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-actions,.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-badge,.praxis-ai-assistant-shell__quick-replies--inline .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
7909
|
}
|
|
6273
7910
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantShellComponent, decorators: [{
|
|
6274
7911
|
type: Component,
|
|
@@ -6279,7 +7916,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
6279
7916
|
MatIconModule,
|
|
6280
7917
|
MatProgressSpinnerModule,
|
|
6281
7918
|
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"] }]
|
|
7919
|
+
], 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 [class.praxis-ai-assistant-shell__quick-replies--inline]=\"shouldUseInlineQuickReplies()\"\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{display:inline-grid;place-items:center;width:34px;height:34px;padding:0;color:var(--md-sys-color-on-surface-variant);line-height:1;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{display:block;width:18px;height:18px;font-size:18px;line-height: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__quick-replies--inline{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.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-replies--inline .praxis-ai-assistant-shell__quick-reply{width:auto;min-width:0;max-width:100%;padding:6px 10px;border-radius:999px;box-shadow:none}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply--guided-action{border-color:color-mix(in srgb,var(--md-sys-color-outline-variant) 76%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 96%,transparent)}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply--guided-action:hover:not(:disabled),.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply--guided-action:focus-visible{border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 52%,transparent);background:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 9%,var(--md-sys-color-surface-container-high));box-shadow:0 0 0 2px color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 12%,transparent)}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply ::ng-deep .mdc-button__label{display:inline-flex;grid-template-columns:none;align-items:center;gap:6px;width:auto}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply ::ng-deep .mat-mdc-button-touch-target{min-height:34px}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-icon-frame{width:18px;height:18px;border:0;border-radius:0;background:transparent;box-shadow:none}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-icon{width:16px;height:16px;color:var(--praxis-ai-assistant-shell-quick-reply-accent);font-size:16px}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-copy,.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-heading,.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-header{display:inline-flex;align-items:center;gap:0}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-label{color:var(--md-sys-color-on-surface);font-size:12.5px;font-weight:650;line-height:1.2;white-space:nowrap}.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-description,.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-actions,.praxis-ai-assistant-shell__quick-replies--inline .praxis-ai-assistant-shell__quick-reply-badge,.praxis-ai-assistant-shell__quick-replies--inline .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
7920
|
}], propDecorators: { labels: [{
|
|
6284
7921
|
type: Input
|
|
6285
7922
|
}], mode: [{
|
|
@@ -6308,6 +7945,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
6308
7945
|
type: Input
|
|
6309
7946
|
}], applyTestId: [{
|
|
6310
7947
|
type: Input
|
|
7948
|
+
}], primaryAction: [{
|
|
7949
|
+
type: Input
|
|
7950
|
+
}], secondaryActions: [{
|
|
7951
|
+
type: Input
|
|
7952
|
+
}], governanceActions: [{
|
|
7953
|
+
type: Input
|
|
6311
7954
|
}], busy: [{
|
|
6312
7955
|
type: Input
|
|
6313
7956
|
}], canSubmit: [{
|
|
@@ -6316,6 +7959,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
6316
7959
|
type: Input
|
|
6317
7960
|
}], submitOnEnter: [{
|
|
6318
7961
|
type: Input
|
|
7962
|
+
}], showAttachAction: [{
|
|
7963
|
+
type: Input
|
|
7964
|
+
}], enablePastedAttachments: [{
|
|
7965
|
+
type: Input
|
|
6319
7966
|
}], enableFileAttachments: [{
|
|
6320
7967
|
type: Input
|
|
6321
7968
|
}], attachmentAccept: [{
|
|
@@ -6340,6 +7987,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
6340
7987
|
type: Output
|
|
6341
7988
|
}], apply: [{
|
|
6342
7989
|
type: Output
|
|
7990
|
+
}], retryTurn: [{
|
|
7991
|
+
type: Output
|
|
7992
|
+
}], cancelTurn: [{
|
|
7993
|
+
type: Output
|
|
7994
|
+
}], shellAction: [{
|
|
7995
|
+
type: Output
|
|
6343
7996
|
}], close: [{
|
|
6344
7997
|
type: Output
|
|
6345
7998
|
}], attach: [{
|
|
@@ -6368,6 +8021,308 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
6368
8021
|
args: ['conversation']
|
|
6369
8022
|
}] } });
|
|
6370
8023
|
|
|
8024
|
+
function createPraxisAssistantViewportLayout(options = {}) {
|
|
8025
|
+
const top = normalizePositiveNumber(options.top, 88);
|
|
8026
|
+
const margin = normalizePositiveNumber(options.margin, 32);
|
|
8027
|
+
const viewportWidth = resolveViewportDimension('width', 1280);
|
|
8028
|
+
const viewportHeight = resolveViewportDimension('height', 900);
|
|
8029
|
+
const width = clampPositiveNumber(normalizePositiveNumber(options.width, 560), 360, Math.max(360, viewportWidth - margin * 2));
|
|
8030
|
+
const height = clampPositiveNumber(normalizePositiveNumber(options.height, 620), 360, Math.max(360, viewportHeight - top - margin));
|
|
8031
|
+
return {
|
|
8032
|
+
left: Math.max(margin, viewportWidth - width - margin),
|
|
8033
|
+
top,
|
|
8034
|
+
width,
|
|
8035
|
+
height,
|
|
8036
|
+
};
|
|
8037
|
+
}
|
|
8038
|
+
function normalizePositiveNumber(value, fallback) {
|
|
8039
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
8040
|
+
}
|
|
8041
|
+
function clampPositiveNumber(value, min, max) {
|
|
8042
|
+
return Math.min(Math.max(value, min), Math.max(min, max));
|
|
8043
|
+
}
|
|
8044
|
+
function resolveViewportDimension(axis, fallback) {
|
|
8045
|
+
if (typeof document !== 'undefined') {
|
|
8046
|
+
const documentElementValue = axis === 'width'
|
|
8047
|
+
? document.documentElement?.clientWidth
|
|
8048
|
+
: document.documentElement?.clientHeight;
|
|
8049
|
+
if (typeof documentElementValue === 'number' && documentElementValue > 0) {
|
|
8050
|
+
return documentElementValue;
|
|
8051
|
+
}
|
|
8052
|
+
}
|
|
8053
|
+
if (typeof window !== 'undefined') {
|
|
8054
|
+
const windowValue = axis === 'width' ? window.innerWidth : window.innerHeight;
|
|
8055
|
+
if (typeof windowValue === 'number' && windowValue > 0) {
|
|
8056
|
+
return windowValue;
|
|
8057
|
+
}
|
|
8058
|
+
}
|
|
8059
|
+
return fallback;
|
|
8060
|
+
}
|
|
8061
|
+
|
|
8062
|
+
class PraxisAiAssistantDockComponent {
|
|
8063
|
+
title = 'Praxis copilot active';
|
|
8064
|
+
summary = 'Conversation preserved. Continue where you stopped.';
|
|
8065
|
+
badge = '';
|
|
8066
|
+
icon = '';
|
|
8067
|
+
state = null;
|
|
8068
|
+
tone = null;
|
|
8069
|
+
ariaLabel = '';
|
|
8070
|
+
openAriaLabel = '';
|
|
8071
|
+
openTooltip = '';
|
|
8072
|
+
testId = 'praxis-ai-assistant-dock';
|
|
8073
|
+
openTestId = 'praxis-ai-assistant-dock-open';
|
|
8074
|
+
open = new EventEmitter();
|
|
8075
|
+
resolvedTone() {
|
|
8076
|
+
if (this.tone)
|
|
8077
|
+
return this.tone;
|
|
8078
|
+
if (this.state === 'error')
|
|
8079
|
+
return 'error';
|
|
8080
|
+
if (this.state === 'processing' || this.state === 'applying')
|
|
8081
|
+
return 'working';
|
|
8082
|
+
if (this.state === 'review')
|
|
8083
|
+
return 'review';
|
|
8084
|
+
if (this.state === 'clarification')
|
|
8085
|
+
return 'governed';
|
|
8086
|
+
return 'ready';
|
|
8087
|
+
}
|
|
8088
|
+
resolvedIcon() {
|
|
8089
|
+
if (this.icon)
|
|
8090
|
+
return this.icon;
|
|
8091
|
+
const tone = this.resolvedTone();
|
|
8092
|
+
if (tone === 'error')
|
|
8093
|
+
return 'error';
|
|
8094
|
+
if (tone === 'working')
|
|
8095
|
+
return 'sync';
|
|
8096
|
+
if (tone === 'review')
|
|
8097
|
+
return 'rate_review';
|
|
8098
|
+
if (tone === 'governed')
|
|
8099
|
+
return 'rule';
|
|
8100
|
+
return 'auto_awesome';
|
|
8101
|
+
}
|
|
8102
|
+
resolvedBadge() {
|
|
8103
|
+
if (this.badge)
|
|
8104
|
+
return this.badge;
|
|
8105
|
+
const tone = this.resolvedTone();
|
|
8106
|
+
if (tone === 'error')
|
|
8107
|
+
return 'Attention';
|
|
8108
|
+
if (tone === 'working')
|
|
8109
|
+
return 'Working';
|
|
8110
|
+
if (tone === 'review')
|
|
8111
|
+
return 'Review';
|
|
8112
|
+
if (tone === 'governed')
|
|
8113
|
+
return 'Governed';
|
|
8114
|
+
return 'Ready';
|
|
8115
|
+
}
|
|
8116
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantDockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8117
|
+
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: `
|
|
8118
|
+
<section
|
|
8119
|
+
class="praxis-ai-assistant-dock"
|
|
8120
|
+
[class.praxis-ai-assistant-dock--working]="resolvedTone() === 'working'"
|
|
8121
|
+
[class.praxis-ai-assistant-dock--review]="resolvedTone() === 'review'"
|
|
8122
|
+
[class.praxis-ai-assistant-dock--governed]="resolvedTone() === 'governed'"
|
|
8123
|
+
[class.praxis-ai-assistant-dock--error]="resolvedTone() === 'error'"
|
|
8124
|
+
[attr.data-testid]="testId"
|
|
8125
|
+
role="status"
|
|
8126
|
+
[attr.aria-label]="ariaLabel || title"
|
|
8127
|
+
>
|
|
8128
|
+
<button
|
|
8129
|
+
class="praxis-ai-assistant-dock__main"
|
|
8130
|
+
type="button"
|
|
8131
|
+
[attr.data-testid]="openTestId"
|
|
8132
|
+
[attr.aria-label]="openAriaLabel || title"
|
|
8133
|
+
[matTooltip]="openTooltip || title"
|
|
8134
|
+
(click)="open.emit()"
|
|
8135
|
+
>
|
|
8136
|
+
<span class="praxis-ai-assistant-dock__orb" aria-hidden="true">
|
|
8137
|
+
<mat-icon>{{ resolvedIcon() }}</mat-icon>
|
|
8138
|
+
</span>
|
|
8139
|
+
<span class="praxis-ai-assistant-dock__copy">
|
|
8140
|
+
<strong>{{ title }}</strong>
|
|
8141
|
+
<span>{{ summary }}</span>
|
|
8142
|
+
</span>
|
|
8143
|
+
<span class="praxis-ai-assistant-dock__badge">{{ resolvedBadge() }}</span>
|
|
8144
|
+
</button>
|
|
8145
|
+
</section>
|
|
8146
|
+
`, 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 });
|
|
8147
|
+
}
|
|
8148
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantDockComponent, decorators: [{
|
|
8149
|
+
type: Component,
|
|
8150
|
+
args: [{ selector: 'praxis-ai-assistant-dock', standalone: true, imports: [CommonModule, MatIconModule, MatTooltipModule], template: `
|
|
8151
|
+
<section
|
|
8152
|
+
class="praxis-ai-assistant-dock"
|
|
8153
|
+
[class.praxis-ai-assistant-dock--working]="resolvedTone() === 'working'"
|
|
8154
|
+
[class.praxis-ai-assistant-dock--review]="resolvedTone() === 'review'"
|
|
8155
|
+
[class.praxis-ai-assistant-dock--governed]="resolvedTone() === 'governed'"
|
|
8156
|
+
[class.praxis-ai-assistant-dock--error]="resolvedTone() === 'error'"
|
|
8157
|
+
[attr.data-testid]="testId"
|
|
8158
|
+
role="status"
|
|
8159
|
+
[attr.aria-label]="ariaLabel || title"
|
|
8160
|
+
>
|
|
8161
|
+
<button
|
|
8162
|
+
class="praxis-ai-assistant-dock__main"
|
|
8163
|
+
type="button"
|
|
8164
|
+
[attr.data-testid]="openTestId"
|
|
8165
|
+
[attr.aria-label]="openAriaLabel || title"
|
|
8166
|
+
[matTooltip]="openTooltip || title"
|
|
8167
|
+
(click)="open.emit()"
|
|
8168
|
+
>
|
|
8169
|
+
<span class="praxis-ai-assistant-dock__orb" aria-hidden="true">
|
|
8170
|
+
<mat-icon>{{ resolvedIcon() }}</mat-icon>
|
|
8171
|
+
</span>
|
|
8172
|
+
<span class="praxis-ai-assistant-dock__copy">
|
|
8173
|
+
<strong>{{ title }}</strong>
|
|
8174
|
+
<span>{{ summary }}</span>
|
|
8175
|
+
</span>
|
|
8176
|
+
<span class="praxis-ai-assistant-dock__badge">{{ resolvedBadge() }}</span>
|
|
8177
|
+
</button>
|
|
8178
|
+
</section>
|
|
8179
|
+
`, 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"] }]
|
|
8180
|
+
}], propDecorators: { title: [{
|
|
8181
|
+
type: Input
|
|
8182
|
+
}], summary: [{
|
|
8183
|
+
type: Input
|
|
8184
|
+
}], badge: [{
|
|
8185
|
+
type: Input
|
|
8186
|
+
}], icon: [{
|
|
8187
|
+
type: Input
|
|
8188
|
+
}], state: [{
|
|
8189
|
+
type: Input
|
|
8190
|
+
}], tone: [{
|
|
8191
|
+
type: Input
|
|
8192
|
+
}], ariaLabel: [{
|
|
8193
|
+
type: Input
|
|
8194
|
+
}], openAriaLabel: [{
|
|
8195
|
+
type: Input
|
|
8196
|
+
}], openTooltip: [{
|
|
8197
|
+
type: Input
|
|
8198
|
+
}], testId: [{
|
|
8199
|
+
type: Input
|
|
8200
|
+
}], openTestId: [{
|
|
8201
|
+
type: Input
|
|
8202
|
+
}], open: [{
|
|
8203
|
+
type: Output
|
|
8204
|
+
}] } });
|
|
8205
|
+
|
|
8206
|
+
class PraxisAiAssistantSessionHostComponent {
|
|
8207
|
+
registry = inject(PraxisAssistantSessionRegistryService);
|
|
8208
|
+
testId = 'praxis-ai-assistant-session-host';
|
|
8209
|
+
dockTestIdPrefix = 'praxis-ai-assistant-session';
|
|
8210
|
+
ariaLabel = 'Active Praxis assistant sessions';
|
|
8211
|
+
openAriaLabel = 'Open assistant session';
|
|
8212
|
+
openTooltip = 'Open assistant session';
|
|
8213
|
+
ownerType = null;
|
|
8214
|
+
ownerId = null;
|
|
8215
|
+
visibility = 'minimized';
|
|
8216
|
+
includeOriginAnchored = false;
|
|
8217
|
+
sessionOpen = new EventEmitter();
|
|
8218
|
+
visibleSessions() {
|
|
8219
|
+
return this.registry.sessions().filter((session) => {
|
|
8220
|
+
if (this.visibility !== 'all' && session.visibility !== this.visibility)
|
|
8221
|
+
return false;
|
|
8222
|
+
if (!this.includeOriginAnchored && session.presence === 'origin-anchor')
|
|
8223
|
+
return false;
|
|
8224
|
+
if (this.ownerType && session.ownerType !== this.ownerType)
|
|
8225
|
+
return false;
|
|
8226
|
+
if (this.ownerId && session.ownerId !== this.ownerId)
|
|
8227
|
+
return false;
|
|
8228
|
+
return true;
|
|
8229
|
+
});
|
|
8230
|
+
}
|
|
8231
|
+
openSession(sessionId) {
|
|
8232
|
+
const session = this.registry.openSession(sessionId);
|
|
8233
|
+
if (session) {
|
|
8234
|
+
this.sessionOpen.emit(session);
|
|
8235
|
+
}
|
|
8236
|
+
}
|
|
8237
|
+
sessionAriaLabel(session) {
|
|
8238
|
+
const summary = session.summary ? `: ${session.summary}` : '';
|
|
8239
|
+
return `${session.title}${summary}`;
|
|
8240
|
+
}
|
|
8241
|
+
dockTestId(session, first) {
|
|
8242
|
+
return first ? `${this.dockTestIdPrefix}-dock` : `${this.dockTestIdPrefix}-dock-${this.safeId(session.id)}`;
|
|
8243
|
+
}
|
|
8244
|
+
dockOpenTestId(session, first) {
|
|
8245
|
+
return first ? `${this.dockTestIdPrefix}-dock-open` : `${this.dockTestIdPrefix}-dock-open-${this.safeId(session.id)}`;
|
|
8246
|
+
}
|
|
8247
|
+
trackSession(_index, session) {
|
|
8248
|
+
return session.id;
|
|
8249
|
+
}
|
|
8250
|
+
safeId(value) {
|
|
8251
|
+
return value.replace(/[^a-zA-Z0-9_-]+/g, '-');
|
|
8252
|
+
}
|
|
8253
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantSessionHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8254
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.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", includeOriginAnchored: ["includeOriginAnchored", "includeOriginAnchored", booleanAttribute] }, outputs: { sessionOpen: "sessionOpen" }, ngImport: i0, template: `
|
|
8255
|
+
<section
|
|
8256
|
+
*ngIf="visibleSessions().length"
|
|
8257
|
+
class="praxis-ai-assistant-session-host"
|
|
8258
|
+
[attr.data-testid]="testId"
|
|
8259
|
+
[attr.aria-label]="ariaLabel"
|
|
8260
|
+
>
|
|
8261
|
+
<praxis-ai-assistant-dock
|
|
8262
|
+
*ngFor="let session of visibleSessions(); let first = first; trackBy: trackSession"
|
|
8263
|
+
[title]="session.title"
|
|
8264
|
+
[summary]="session.summary"
|
|
8265
|
+
[badge]="session.badge"
|
|
8266
|
+
[icon]="session.icon"
|
|
8267
|
+
[state]="session.state"
|
|
8268
|
+
[ariaLabel]="sessionAriaLabel(session)"
|
|
8269
|
+
[openAriaLabel]="openAriaLabel"
|
|
8270
|
+
[openTooltip]="openTooltip"
|
|
8271
|
+
[testId]="dockTestId(session, first)"
|
|
8272
|
+
[openTestId]="dockOpenTestId(session, first)"
|
|
8273
|
+
(open)="openSession(session.id)"
|
|
8274
|
+
/>
|
|
8275
|
+
</section>
|
|
8276
|
+
`, 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 });
|
|
8277
|
+
}
|
|
8278
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantSessionHostComponent, decorators: [{
|
|
8279
|
+
type: Component,
|
|
8280
|
+
args: [{ selector: 'praxis-ai-assistant-session-host', standalone: true, imports: [CommonModule, PraxisAiAssistantDockComponent], template: `
|
|
8281
|
+
<section
|
|
8282
|
+
*ngIf="visibleSessions().length"
|
|
8283
|
+
class="praxis-ai-assistant-session-host"
|
|
8284
|
+
[attr.data-testid]="testId"
|
|
8285
|
+
[attr.aria-label]="ariaLabel"
|
|
8286
|
+
>
|
|
8287
|
+
<praxis-ai-assistant-dock
|
|
8288
|
+
*ngFor="let session of visibleSessions(); let first = first; trackBy: trackSession"
|
|
8289
|
+
[title]="session.title"
|
|
8290
|
+
[summary]="session.summary"
|
|
8291
|
+
[badge]="session.badge"
|
|
8292
|
+
[icon]="session.icon"
|
|
8293
|
+
[state]="session.state"
|
|
8294
|
+
[ariaLabel]="sessionAriaLabel(session)"
|
|
8295
|
+
[openAriaLabel]="openAriaLabel"
|
|
8296
|
+
[openTooltip]="openTooltip"
|
|
8297
|
+
[testId]="dockTestId(session, first)"
|
|
8298
|
+
[openTestId]="dockOpenTestId(session, first)"
|
|
8299
|
+
(open)="openSession(session.id)"
|
|
8300
|
+
/>
|
|
8301
|
+
</section>
|
|
8302
|
+
`, 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"] }]
|
|
8303
|
+
}], propDecorators: { testId: [{
|
|
8304
|
+
type: Input
|
|
8305
|
+
}], dockTestIdPrefix: [{
|
|
8306
|
+
type: Input
|
|
8307
|
+
}], ariaLabel: [{
|
|
8308
|
+
type: Input
|
|
8309
|
+
}], openAriaLabel: [{
|
|
8310
|
+
type: Input
|
|
8311
|
+
}], openTooltip: [{
|
|
8312
|
+
type: Input
|
|
8313
|
+
}], ownerType: [{
|
|
8314
|
+
type: Input
|
|
8315
|
+
}], ownerId: [{
|
|
8316
|
+
type: Input
|
|
8317
|
+
}], visibility: [{
|
|
8318
|
+
type: Input
|
|
8319
|
+
}], includeOriginAnchored: [{
|
|
8320
|
+
type: Input,
|
|
8321
|
+
args: [{ transform: booleanAttribute }]
|
|
8322
|
+
}], sessionOpen: [{
|
|
8323
|
+
type: Output
|
|
8324
|
+
}] } });
|
|
8325
|
+
|
|
6371
8326
|
class StreamingFeedbackComponent {
|
|
6372
8327
|
title = 'Processando...';
|
|
6373
8328
|
displayText = '';
|
|
@@ -7165,4 +9120,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
7165
9120
|
* Generated bundle index. Do not edit.
|
|
7166
9121
|
*/
|
|
7167
9122
|
|
|
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 };
|
|
9123
|
+
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, createComponentAuthoringContext, createPraxisAssistantViewportLayout, normalizeAuthoringPrompt, normalizePraxisAssistantAttachmentSummary, normalizePraxisAssistantContextSnapshot, sanitizePraxisAssistantText, shouldRoutePromptToGovernedDecision, toAiJsonObject, toPraxisAssistantConversationMessages };
|