@praxisui/ai 8.0.0-beta.3 → 8.0.0-beta.30

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