@praxisui/ai 8.0.0-beta.2 → 8.0.0-beta.21

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,5 +1,5 @@
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
4
  import { throwError, from, Observable, of, BehaviorSubject, tap, map as map$1, isObservable, firstValueFrom } from 'rxjs';
5
5
  import { map, catchError, filter, take } from 'rxjs/operators';
@@ -60,13 +60,351 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
60
60
  * Models for Praxis AI (Centralized)
61
61
  */
62
62
 
63
+ const PRAXIS_ASSISTANT_CONTEXT_TEXT_LIMIT = 160;
64
+ const PRAXIS_ASSISTANT_CONTEXT_ITEM_LIMIT = 12;
65
+ const PRAXIS_ASSISTANT_CONTEXT_SCHEMA_FIELD_LIMIT = 40;
66
+ const PRAXIS_ASSISTANT_CONTEXT_ATTACHMENT_LIMIT = 8;
67
+ const REDACTED = '[redacted]';
68
+ const PROHIBITED_CONTEXT_KEYS = new Set([
69
+ 'file',
70
+ 'blob',
71
+ 'bytes',
72
+ 'base64',
73
+ 'previewUrl',
74
+ 'currentState',
75
+ 'runtimeState',
76
+ 'formValues',
77
+ 'values',
78
+ 'rows',
79
+ 'rowData',
80
+ 'pendingPatch',
81
+ 'diagnostics',
82
+ 'payload',
83
+ 'config',
84
+ ]);
85
+ const SENSITIVE_TEXT_PATTERNS = [
86
+ /\bBearer\s+[A-Za-z0-9\-._~+/]+=*/gi,
87
+ /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
88
+ /\bsk-[A-Za-z0-9]{20,}\b/g,
89
+ /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi,
90
+ /\b\d{3}\.?\d{3}\.?\d{3}-?\d{2}\b/g,
91
+ /\b(?:\+?55\s?)?(?:\(?\d{2}\)?\s?)?(?:9\s?)?\d{4}[-\s]?\d{4}\b/g,
92
+ /\b(?:api[_-]?key|token|secret|password|senha)\s*[:=]\s*['"]?[^'"\s,;]+/gi,
93
+ ];
94
+ function sanitizePraxisAssistantText(value, limit = PRAXIS_ASSISTANT_CONTEXT_TEXT_LIMIT) {
95
+ if (value === null || value === undefined)
96
+ return '';
97
+ let sanitized = String(value);
98
+ for (const pattern of SENSITIVE_TEXT_PATTERNS) {
99
+ sanitized = sanitized.replace(pattern, REDACTED);
100
+ }
101
+ sanitized = sanitized.replace(/\s+/g, ' ').trim();
102
+ const safeLimit = Math.max(0, limit);
103
+ return sanitized.length > safeLimit ? `${sanitized.slice(0, Math.max(0, safeLimit - 3))}...` : sanitized;
104
+ }
105
+ function normalizePraxisAssistantAttachmentSummary(attachment) {
106
+ if (!isRecord(attachment))
107
+ return null;
108
+ const id = sanitizePraxisAssistantText(attachment['id'] ?? attachment['name'], 96);
109
+ const name = sanitizePraxisAssistantText(attachment['name'] ?? id, 120);
110
+ if (!id || !name)
111
+ return null;
112
+ return {
113
+ id,
114
+ name,
115
+ kind: sanitizePraxisAssistantText(attachment['kind'] ?? 'file', 32) || 'file',
116
+ mimeType: sanitizePraxisAssistantText(attachment['mimeType'], 96) || undefined,
117
+ sizeBytes: normalizeNonNegativeNumber(attachment['sizeBytes']),
118
+ source: sanitizePraxisAssistantText(attachment['source'], 64) || undefined,
119
+ hasPreview: Boolean(attachment['hasPreview']),
120
+ };
121
+ }
122
+ function normalizePraxisAssistantContextSnapshot(value) {
123
+ if (!isRecord(value)) {
124
+ throw new Error('Praxis assistant context snapshot requires an object.');
125
+ }
126
+ const identity = normalizeIdentity(value['identity']);
127
+ const contextItems = normalizeContextItems(value['contextItems']);
128
+ return {
129
+ identity,
130
+ target: normalizeTargetRef(value['target']) ?? undefined,
131
+ contextItems,
132
+ mode: normalizeMode(value['mode']),
133
+ authoringManifestRef: normalizeManifestRef(value['authoringManifestRef']) ?? undefined,
134
+ resourcePath: sanitizePraxisAssistantText(value['resourcePath'], 160) || undefined,
135
+ schemaFields: normalizeStringList(value['schemaFields'], PRAXIS_ASSISTANT_CONTEXT_SCHEMA_FIELD_LIMIT, 96),
136
+ dataProfileDigest: normalizeDigest(value['dataProfileDigest']) ?? undefined,
137
+ runtimeStateDigest: normalizeDigest(value['runtimeStateDigest']) ?? undefined,
138
+ capabilityRefs: normalizeCapabilityRefs(value['capabilityRefs']),
139
+ governanceHints: normalizeGovernanceHints(value['governanceHints']),
140
+ riskHints: normalizeGovernanceHints(value['riskHints']),
141
+ attachmentSummaries: normalizeAttachmentSummaries(value['attachmentSummaries']),
142
+ actions: normalizeActions(value['actions']),
143
+ };
144
+ }
145
+ function normalizeIdentity(value) {
146
+ if (!isRecord(value)) {
147
+ throw new Error('Praxis assistant context snapshot requires identity.');
148
+ }
149
+ const sessionId = sanitizePraxisAssistantText(value['sessionId'], 120);
150
+ const ownerId = sanitizePraxisAssistantText(value['ownerId'], 120);
151
+ const ownerType = sanitizePraxisAssistantText(value['ownerType'], 64);
152
+ if (!sessionId || !ownerId || !ownerType) {
153
+ throw new Error('Praxis assistant identity requires sessionId, ownerId and ownerType.');
154
+ }
155
+ return {
156
+ sessionId,
157
+ ownerId,
158
+ ownerType,
159
+ componentId: sanitizePraxisAssistantText(value['componentId'], 120) || undefined,
160
+ componentType: sanitizePraxisAssistantText(value['componentType'], 64) || undefined,
161
+ routeKey: sanitizePraxisAssistantText(value['routeKey'], 160) || undefined,
162
+ tenantId: sanitizePraxisAssistantText(value['tenantId'], 96) || undefined,
163
+ env: sanitizePraxisAssistantText(value['env'], 48) || undefined,
164
+ userId: sanitizePraxisAssistantText(value['userId'], 96) || undefined,
165
+ };
166
+ }
167
+ function normalizeTargetRef(value) {
168
+ if (!isRecord(value))
169
+ return null;
170
+ const kind = sanitizePraxisAssistantText(value['kind'], 48);
171
+ const id = sanitizePraxisAssistantText(value['id'], 120);
172
+ if (!kind || !id)
173
+ return null;
174
+ return {
175
+ kind,
176
+ id,
177
+ label: sanitizePraxisAssistantText(value['label'], 120) || undefined,
178
+ path: sanitizePraxisAssistantText(value['path'], 160) || undefined,
179
+ schemaPath: sanitizePraxisAssistantText(value['schemaPath'], 160) || undefined,
180
+ metadata: normalizeMetadata(value['metadata']),
181
+ };
182
+ }
183
+ function normalizeContextItems(value) {
184
+ if (!Array.isArray(value))
185
+ return [];
186
+ return value
187
+ .slice(0, PRAXIS_ASSISTANT_CONTEXT_ITEM_LIMIT)
188
+ .map((item) => {
189
+ if (!isRecord(item))
190
+ return null;
191
+ const id = sanitizePraxisAssistantText(item['id'], 80);
192
+ const label = sanitizePraxisAssistantText(item['label'], 80);
193
+ const itemValue = sanitizePraxisAssistantText(item['value'], 160);
194
+ if (!id || !label || !itemValue)
195
+ return null;
196
+ return {
197
+ id,
198
+ label,
199
+ value: itemValue,
200
+ kind: sanitizePraxisAssistantText(item['kind'], 48) || undefined,
201
+ tone: normalizeTone(item['tone']),
202
+ };
203
+ })
204
+ .filter(isDefined);
205
+ }
206
+ function normalizeManifestRef(value) {
207
+ if (!isRecord(value))
208
+ return null;
209
+ const componentId = sanitizePraxisAssistantText(value['componentId'], 120);
210
+ if (!componentId)
211
+ return null;
212
+ return {
213
+ componentId,
214
+ version: sanitizePraxisAssistantText(value['version'], 64) || undefined,
215
+ source: sanitizePraxisAssistantText(value['source'], 160) || undefined,
216
+ hash: sanitizePraxisAssistantText(value['hash'], 96) || undefined,
217
+ };
218
+ }
219
+ function normalizeDigest(value) {
220
+ if (!isRecord(value))
221
+ return null;
222
+ return {
223
+ label: sanitizePraxisAssistantText(value['label'], 80) || undefined,
224
+ summary: sanitizePraxisAssistantText(value['summary'], 240) || undefined,
225
+ hash: sanitizePraxisAssistantText(value['hash'], 96) || undefined,
226
+ source: sanitizePraxisAssistantText(value['source'], 160) || undefined,
227
+ fields: normalizeStringList(value['fields'], 20, 96),
228
+ counts: normalizeNumberMap(value['counts']),
229
+ };
230
+ }
231
+ function normalizeCapabilityRefs(value) {
232
+ if (!Array.isArray(value))
233
+ return undefined;
234
+ const refs = value.slice(0, 20).map((item) => {
235
+ if (!isRecord(item))
236
+ return null;
237
+ const id = sanitizePraxisAssistantText(item['id'], 120);
238
+ if (!id)
239
+ return null;
240
+ return {
241
+ id,
242
+ label: sanitizePraxisAssistantText(item['label'], 120) || undefined,
243
+ source: sanitizePraxisAssistantText(item['source'], 160) || undefined,
244
+ risk: normalizeRisk(item['risk']),
245
+ };
246
+ }).filter(isDefined);
247
+ return refs.length ? refs : undefined;
248
+ }
249
+ function normalizeGovernanceHints(value) {
250
+ if (!Array.isArray(value))
251
+ return undefined;
252
+ const hints = value.slice(0, 12).map((item) => {
253
+ if (!isRecord(item))
254
+ return null;
255
+ const kind = sanitizePraxisAssistantText(item['kind'], 80);
256
+ if (!kind)
257
+ return null;
258
+ return {
259
+ kind,
260
+ label: sanitizePraxisAssistantText(item['label'], 120) || undefined,
261
+ reason: sanitizePraxisAssistantText(item['reason'], 200) || undefined,
262
+ target: sanitizePraxisAssistantText(item['target'], 160) || undefined,
263
+ risk: normalizeRisk(item['risk']),
264
+ };
265
+ }).filter(isDefined);
266
+ return hints.length ? hints : undefined;
267
+ }
268
+ function normalizeAttachmentSummaries(value) {
269
+ if (!Array.isArray(value))
270
+ return undefined;
271
+ const summaries = value
272
+ .slice(0, PRAXIS_ASSISTANT_CONTEXT_ATTACHMENT_LIMIT)
273
+ .map(normalizePraxisAssistantAttachmentSummary)
274
+ .filter((item) => !!item);
275
+ return summaries.length ? summaries : undefined;
276
+ }
277
+ function normalizeActions(value) {
278
+ if (!Array.isArray(value))
279
+ return undefined;
280
+ const actions = value.slice(0, 20).map((item) => {
281
+ if (!isRecord(item))
282
+ return null;
283
+ const id = sanitizePraxisAssistantText(item['id'], 120);
284
+ const kind = sanitizePraxisAssistantText(item['kind'], 80);
285
+ if (!id || !kind)
286
+ return null;
287
+ return {
288
+ id,
289
+ kind,
290
+ label: sanitizePraxisAssistantText(item['label'], 120) || undefined,
291
+ target: normalizeTargetRef(item['target']) ?? undefined,
292
+ capabilityRef: sanitizePraxisAssistantText(item['capabilityRef'], 120) || undefined,
293
+ risk: normalizeRisk(item['risk']),
294
+ handoffEndpoint: sanitizePraxisAssistantText(item['handoffEndpoint'], 180) || undefined,
295
+ description: sanitizePraxisAssistantText(item['description'], 200) || undefined,
296
+ };
297
+ }).filter(isDefined);
298
+ return actions.length ? actions : undefined;
299
+ }
300
+ function normalizeMetadata(value) {
301
+ if (!isRecord(value))
302
+ return undefined;
303
+ const metadata = {};
304
+ for (const [key, rawValue] of Object.entries(value)) {
305
+ if (PROHIBITED_CONTEXT_KEYS.has(key))
306
+ continue;
307
+ const safeKey = sanitizePraxisAssistantText(key, 64);
308
+ if (!safeKey)
309
+ continue;
310
+ if (typeof rawValue === 'string')
311
+ metadata[safeKey] = sanitizePraxisAssistantText(rawValue, 120);
312
+ if (typeof rawValue === 'number' && Number.isFinite(rawValue))
313
+ metadata[safeKey] = rawValue;
314
+ if (typeof rawValue === 'boolean')
315
+ metadata[safeKey] = rawValue;
316
+ }
317
+ return Object.keys(metadata).length ? metadata : undefined;
318
+ }
319
+ function normalizeStringList(value, limit, textLimit) {
320
+ if (!Array.isArray(value))
321
+ return undefined;
322
+ const list = value
323
+ .slice(0, limit)
324
+ .map((item) => sanitizePraxisAssistantText(item, textLimit))
325
+ .filter(Boolean);
326
+ return list.length ? list : undefined;
327
+ }
328
+ function normalizeNumberMap(value) {
329
+ if (!isRecord(value))
330
+ return undefined;
331
+ const counts = {};
332
+ for (const [key, rawValue] of Object.entries(value)) {
333
+ const safeKey = sanitizePraxisAssistantText(key, 64);
334
+ if (!safeKey || typeof rawValue !== 'number' || !Number.isFinite(rawValue))
335
+ continue;
336
+ counts[safeKey] = rawValue;
337
+ }
338
+ return Object.keys(counts).length ? counts : undefined;
339
+ }
340
+ function normalizeMode(value) {
341
+ const mode = sanitizePraxisAssistantText(value, 48);
342
+ if (mode === 'config'
343
+ || mode === 'agentic-authoring'
344
+ || mode === 'chat'
345
+ || mode === 'diagnostic'
346
+ || mode === 'review'
347
+ || mode === 'inline-help') {
348
+ return mode;
349
+ }
350
+ return 'chat';
351
+ }
352
+ function normalizeRisk(value) {
353
+ const risk = sanitizePraxisAssistantText(value, 24);
354
+ return risk === 'low' || risk === 'medium' || risk === 'high' || risk === 'blocked'
355
+ ? risk
356
+ : undefined;
357
+ }
358
+ function normalizeTone(value) {
359
+ const tone = sanitizePraxisAssistantText(value, 24);
360
+ return tone === 'neutral' || tone === 'info' || tone === 'success' || tone === 'warning' || tone === 'danger'
361
+ ? tone
362
+ : undefined;
363
+ }
364
+ function normalizeNonNegativeNumber(value) {
365
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
366
+ }
367
+ function isRecord(value) {
368
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
369
+ }
370
+ function isDefined(value) {
371
+ return value !== null && value !== undefined;
372
+ }
373
+
374
+ function toPraxisAssistantConversationMessages(messages, limit = 12) {
375
+ return messages
376
+ .filter((message) => !!message.text?.trim() && isPraxisAssistantConversationMessageRole(message.role))
377
+ .slice(-Math.max(0, limit))
378
+ .map((message) => ({
379
+ id: message.id,
380
+ role: toPraxisAssistantConversationMessageRole(message.role),
381
+ text: message.text,
382
+ }));
383
+ }
384
+ function isPraxisAssistantConversationMessageRole(role) {
385
+ return role === 'user' || role === 'assistant' || role === 'system' || role === 'status';
386
+ }
387
+ function toPraxisAssistantConversationMessageRole(role) {
388
+ switch (role) {
389
+ case 'user':
390
+ case 'assistant':
391
+ case 'system':
392
+ return role;
393
+ case 'status':
394
+ return 'assistant';
395
+ default:
396
+ return 'assistant';
397
+ }
398
+ }
399
+
63
400
  /**
64
401
  * Generated from praxis-config-starter/docs/ai/contracts/praxis-ai-api-contract-v1.1.openapi.yaml.
65
402
  * Do not edit manually. Run praxis-config-starter/tools/contracts/generate-ai-contract-bindings.js.
66
403
  */
67
404
  const AI_CONTRACT_VERSION = 'v1.1';
68
- const AI_CONTRACT_SCHEMA_HASH = '922e6d48637e64b403562d6d7cb833ed4942ffb0b452ec3573255871f4ec8739';
405
+ const AI_CONTRACT_SCHEMA_HASH = '42af43aec91777def176def87d2f41d30c8dbc8abb6c21dc38b5e181a1a51f4f';
69
406
  const AI_STREAM_EVENT_SCHEMA_VERSION = 'v1';
407
+ const AI_DOMAIN_CATALOG_CONTEXT_HINT_SCHEMA_VERSION = 'praxis.ai.context-hints.domain-catalog/v0.2';
70
408
  const AI_STREAM_EVENT_TYPES = ['status', 'thought.step', 'heartbeat', 'result', 'error', 'cancelled'];
71
409
 
72
410
  /**
@@ -526,15 +864,6 @@ class PraxisAiService {
526
864
  if (!this.isGeminiProvider()) {
527
865
  return throwError(() => new Error('LLM provider not supported in browser mode.'));
528
866
  }
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
867
  if (!this.genAI)
539
868
  return throwError(() => new Error('API Key not configured'));
540
869
  const generationConfig = {
@@ -580,50 +909,6 @@ class PraxisAiService {
580
909
  isMockMode() {
581
910
  return !this.genAI;
582
911
  }
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
912
  listModels(apiKey) {
628
913
  if (!this.isGeminiProvider()) {
629
914
  return throwError(() => new Error('Model listing only available for Gemini.'));
@@ -839,6 +1124,94 @@ class AiBackendApiService {
839
1124
  withCredentials: true,
840
1125
  });
841
1126
  }
1127
+ startAgenticAuthoringTurnStream(request) {
1128
+ return this.http.post(`${this.baseUrl}/authoring/turn/stream/start`, request, {
1129
+ headers: this.buildHeaders(),
1130
+ withCredentials: true,
1131
+ });
1132
+ }
1133
+ connectAgenticAuthoringTurnStream(streamId, lastEventId, accessToken) {
1134
+ const queryParams = [];
1135
+ if (lastEventId?.trim()) {
1136
+ queryParams.push(`lastEventId=${encodeURIComponent(lastEventId.trim())}`);
1137
+ }
1138
+ if (accessToken?.trim()) {
1139
+ queryParams.push(`accessToken=${encodeURIComponent(accessToken.trim())}`);
1140
+ }
1141
+ const query = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
1142
+ const streamPath = `${this.baseUrl}/authoring/turn/stream/${encodeURIComponent(streamId)}`;
1143
+ const endpoint = `${streamPath}${query}`;
1144
+ const probeEndpoint = `${streamPath}/probe${accessToken?.trim()
1145
+ ? `?accessToken=${encodeURIComponent(accessToken.trim())}`
1146
+ : ''}`;
1147
+ let source = null;
1148
+ const events$ = new Observable((subscriber) => {
1149
+ let disposed = false;
1150
+ const openStream = async () => {
1151
+ if (typeof EventSource === 'undefined') {
1152
+ subscriber.error(new AiPatchStreamConnectionError('unsupported', 'EventSource is not supported in this environment.'));
1153
+ return;
1154
+ }
1155
+ const probeStatus = await this.probePatchStreamEndpoint(probeEndpoint);
1156
+ if (disposed) {
1157
+ return;
1158
+ }
1159
+ if (typeof probeStatus === 'number' && probeStatus >= 400) {
1160
+ subscriber.error(new AiPatchStreamConnectionError('http_status', `Agentic authoring stream probe failed with status ${probeStatus}.`, probeStatus));
1161
+ return;
1162
+ }
1163
+ source = new EventSource(endpoint, { withCredentials: true });
1164
+ source.onmessage = (messageEvent) => {
1165
+ try {
1166
+ const parsed = this.parsePatchStreamEnvelope(messageEvent.data);
1167
+ subscriber.next(parsed);
1168
+ }
1169
+ catch (error) {
1170
+ if (error instanceof AiPatchStreamConnectionError) {
1171
+ subscriber.error(error);
1172
+ return;
1173
+ }
1174
+ subscriber.error(new AiPatchStreamConnectionError('parse', 'Failed to parse agentic authoring stream event payload.'));
1175
+ }
1176
+ };
1177
+ source.onerror = () => {
1178
+ if (!source) {
1179
+ subscriber.error(new AiPatchStreamConnectionError('transport', 'Agentic authoring stream connection error.'));
1180
+ return;
1181
+ }
1182
+ if (source.readyState === EventSource.CLOSED) {
1183
+ subscriber.error(new AiPatchStreamConnectionError('transport', 'Agentic authoring stream connection closed unexpectedly.'));
1184
+ }
1185
+ };
1186
+ };
1187
+ void openStream();
1188
+ return () => {
1189
+ disposed = true;
1190
+ if (source) {
1191
+ source.close();
1192
+ source = null;
1193
+ }
1194
+ };
1195
+ });
1196
+ return {
1197
+ events$,
1198
+ close: () => {
1199
+ if (source) {
1200
+ source.close();
1201
+ source = null;
1202
+ }
1203
+ },
1204
+ };
1205
+ }
1206
+ cancelAgenticAuthoringTurnStream(streamId, accessToken) {
1207
+ const endpoint = `${this.baseUrl}/authoring/turn/stream/${encodeURIComponent(streamId)}/cancel${accessToken?.trim()
1208
+ ? `?accessToken=${encodeURIComponent(accessToken.trim())}`
1209
+ : ''}`;
1210
+ return this.http.post(endpoint, {}, {
1211
+ headers: this.buildHeaders(),
1212
+ withCredentials: true,
1213
+ });
1214
+ }
842
1215
  listModels(request) {
843
1216
  return this.http.post(`${this.baseUrl}/providers/models`, request, { headers: this.buildHeaders() });
844
1217
  }
@@ -855,6 +1228,24 @@ class AiBackendApiService {
855
1228
  const params = new HttpParams({ fromObject: { componentType } });
856
1229
  return this.http.get(`${this.contextUrl}/${componentId}`, { headers: this.buildHeaders(), params });
857
1230
  }
1231
+ getAgenticAuthoringManifest(componentId) {
1232
+ return this.http.get(`${this.authoringManifestUrl(componentId)}`, { headers: this.buildHeaders() });
1233
+ }
1234
+ listAgenticAuthoringManifestTargets(componentId) {
1235
+ return this.http.get(`${this.authoringManifestUrl(componentId)}/editable-targets`, { headers: this.buildHeaders() });
1236
+ }
1237
+ listAgenticAuthoringManifestOperations(componentId) {
1238
+ return this.http.get(`${this.authoringManifestUrl(componentId)}/operations`, { headers: this.buildHeaders() });
1239
+ }
1240
+ resolveAgenticAuthoringManifestTarget(componentId, request) {
1241
+ return this.http.post(`${this.authoringManifestUrl(componentId)}/resolve-target`, request, { headers: this.buildHeaders() });
1242
+ }
1243
+ validateAgenticAuthoringManifestPlan(componentId, request) {
1244
+ return this.http.post(`${this.authoringManifestUrl(componentId)}/validate-plan`, request, { headers: this.buildHeaders() });
1245
+ }
1246
+ compileAgenticAuthoringManifestPatch(componentId, request) {
1247
+ return this.http.post(`${this.authoringManifestUrl(componentId)}/compile-patch`, request, { headers: this.buildHeaders() });
1248
+ }
858
1249
  loadGlobalAiConfig() {
859
1250
  const snapshot = this.globalConfigStore?.getAiConfigSnapshot?.();
860
1251
  if (snapshot !== undefined) {
@@ -895,6 +1286,9 @@ class AiBackendApiService {
895
1286
  }
896
1287
  return AI_INTENT_CONTRACT_SCHEMA_HASH;
897
1288
  }
1289
+ authoringManifestUrl(componentId) {
1290
+ return `${this.baseUrl}/authoring/manifests/${encodeURIComponent(componentId)}`;
1291
+ }
898
1292
  resolveHeaderMap(extra) {
899
1293
  const allowLocalIdentityFallback = !!this.storageOpts?.allowLocalIdentityFallback;
900
1294
  const defaults = this.storageOpts?.defaultHeaders
@@ -1077,7 +1471,7 @@ class AiResponseValidatorService {
1077
1471
  code: 'MISSING_RULE_NAME'
1078
1472
  });
1079
1473
  }
1080
- if (!response.targetType || !['field', 'section', 'action', 'row', 'column'].includes(response.targetType)) {
1474
+ if (!response.targetType || !['field', 'section', 'action', 'row', 'column', 'visualBlock'].includes(response.targetType)) {
1081
1475
  errors.push({
1082
1476
  field: 'targetType',
1083
1477
  message: 'targetType inválido',
@@ -1211,6 +1605,10 @@ class AiResponseValidatorService {
1211
1605
  return;
1212
1606
  }
1213
1607
  const args = Array.isArray(rawArgs) ? rawArgs : [rawArgs];
1608
+ const arityIssue = this.validateJsonLogicOperatorArity(operator, args.length);
1609
+ if (arityIssue) {
1610
+ issues.push({ message: arityIssue });
1611
+ }
1214
1612
  args.forEach((arg) => this.walkJsonLogicNode(arg, false, issues));
1215
1613
  return;
1216
1614
  }
@@ -1247,6 +1645,49 @@ class AiResponseValidatorService {
1247
1645
  'max',
1248
1646
  ].includes(operator);
1249
1647
  }
1648
+ validateJsonLogicOperatorArity(operator, argumentCount) {
1649
+ const exactArity = {
1650
+ '==': 2,
1651
+ '===': 2,
1652
+ '!=': 2,
1653
+ '!==': 2,
1654
+ '>': 2,
1655
+ '>=': 2,
1656
+ '<': 2,
1657
+ '<=': 2,
1658
+ '!': 1,
1659
+ '!!': 1,
1660
+ in: 2,
1661
+ contains: 2,
1662
+ startsWith: 2,
1663
+ endsWith: 2,
1664
+ '-': 2,
1665
+ '/': 2,
1666
+ '%': 2,
1667
+ };
1668
+ const expected = exactArity[operator];
1669
+ if (expected !== undefined && argumentCount !== expected) {
1670
+ return `Operator ${operator} requires exactly ${expected} argument(s).`;
1671
+ }
1672
+ if ((operator === 'and' || operator === 'or') && argumentCount < 2) {
1673
+ return `Operator ${operator} requires at least 2 arguments.`;
1674
+ }
1675
+ if (operator === 'if' && argumentCount < 3) {
1676
+ return 'Operator if requires at least 3 arguments.';
1677
+ }
1678
+ if ((operator === 'cat'
1679
+ || operator === '+'
1680
+ || operator === '*'
1681
+ || operator === 'min'
1682
+ || operator === 'max')
1683
+ && argumentCount < 1) {
1684
+ return `Operator ${operator} requires at least 1 argument.`;
1685
+ }
1686
+ if (operator === 'substr' && (argumentCount < 2 || argumentCount > 3)) {
1687
+ return 'Operator substr requires 2 or 3 arguments.';
1688
+ }
1689
+ return null;
1690
+ }
1250
1691
  collectVarPaths(value, collector) {
1251
1692
  if (Array.isArray(value)) {
1252
1693
  value.forEach((item) => this.collectVarPaths(item, collector));
@@ -1460,7 +1901,10 @@ class PraxisAssistantTurnController {
1460
1901
  const current = this.stateSubject.value;
1461
1902
  const effectiveAction = this.resolveSubmitAction(action, current);
1462
1903
  const clientTurnId = this.createId('turn');
1463
- const userMessage = this.buildMessage('user', normalized, true);
1904
+ const displayPrompt = typeof effectiveAction.displayPrompt === 'string'
1905
+ ? effectiveAction.displayPrompt.trim()
1906
+ : '';
1907
+ const userMessage = this.buildMessage('user', displayPrompt || normalized, true);
1464
1908
  this.patchState({
1465
1909
  state: 'processing',
1466
1910
  phase: 'contextualize',
@@ -1542,7 +1986,8 @@ class PraxisAssistantTurnController {
1542
1986
  if (!handler) {
1543
1987
  return of(this.snapshot());
1544
1988
  }
1545
- return this.toObservable(handler.call(this.flow, request)).pipe(tap((result) => this.applyResult(result)), map$1(() => this.snapshot()));
1989
+ const flowResult = handler.call(this.flow, request);
1990
+ return this.toObservable(flowResult).pipe(tap((result) => this.applyResult(result)), map$1(() => this.snapshot()));
1546
1991
  }
1547
1992
  buildRequest(partial) {
1548
1993
  const current = this.stateSubject.value;
@@ -1653,7 +2098,6 @@ class PraxisAssistantTurnController {
1653
2098
  }
1654
2099
  return {
1655
2100
  ...action,
1656
- kind: 'clarify',
1657
2101
  contextHints: {
1658
2102
  ...(action.contextHints ?? {}),
1659
2103
  pendingClarification: current.pendingClarification,
@@ -1739,6 +2183,158 @@ class PraxisAssistantTurnController {
1739
2183
  }
1740
2184
  }
1741
2185
 
2186
+ class PraxisAssistantSessionRegistryService {
2187
+ sessionsState = signal([], ...(ngDevMode ? [{ debugName: "sessionsState" }] : []));
2188
+ sessions = this.sessionsState.asReadonly();
2189
+ activeSession = computed(() => this.sessions().find((session) => session.visibility === 'active') ?? null, ...(ngDevMode ? [{ debugName: "activeSession" }] : []));
2190
+ minimizedSessions = computed(() => this.sessions().filter((session) => session.visibility === 'minimized'), ...(ngDevMode ? [{ debugName: "minimizedSessions" }] : []));
2191
+ upsertSession(descriptor) {
2192
+ const normalized = this.normalizeDescriptor(descriptor);
2193
+ const existing = this.sessionsState().find((session) => session.id === normalized.id);
2194
+ const now = normalized.updatedAt || new Date().toISOString();
2195
+ const next = {
2196
+ ...existing,
2197
+ id: normalized.id,
2198
+ ownerId: normalized.ownerId,
2199
+ ownerType: normalized.ownerType,
2200
+ title: normalized.title,
2201
+ summary: normalized.summary,
2202
+ mode: normalized.mode,
2203
+ state: normalized.state,
2204
+ visibility: normalized.visibility,
2205
+ contextItems: normalized.contextItems,
2206
+ contextSnapshot: normalized.contextSnapshot,
2207
+ badge: normalized.badge,
2208
+ icon: normalized.icon,
2209
+ createdAt: existing?.createdAt ?? now,
2210
+ updatedAt: now,
2211
+ };
2212
+ this.sessionsState.update((sessions) => this.sortSessions([
2213
+ ...sessions.filter((session) => session.id !== next.id),
2214
+ next,
2215
+ ], next.visibility === 'active' ? next.id : null));
2216
+ return next;
2217
+ }
2218
+ upsertContextSession(contextSnapshot, descriptor = {}) {
2219
+ const normalizedContext = normalizePraxisAssistantContextSnapshot(contextSnapshot);
2220
+ const identity = normalizedContext.identity;
2221
+ return this.upsertSession({
2222
+ ...descriptor,
2223
+ id: identity.sessionId,
2224
+ ownerId: identity.ownerId,
2225
+ ownerType: identity.ownerType,
2226
+ title: descriptor.title?.trim()
2227
+ || normalizedContext.target?.label
2228
+ || identity.componentId
2229
+ || identity.ownerType
2230
+ || 'Praxis assistant',
2231
+ mode: descriptor.mode || normalizedContext.mode,
2232
+ contextSnapshot: normalizedContext,
2233
+ });
2234
+ }
2235
+ openSession(sessionId) {
2236
+ return this.setVisibility(sessionId, 'active');
2237
+ }
2238
+ openContextSession(identity) {
2239
+ return this.openSession(this.resolveSessionId(identity));
2240
+ }
2241
+ minimizeSession(sessionId) {
2242
+ return this.setVisibility(sessionId, 'minimized');
2243
+ }
2244
+ minimizeContextSession(identity) {
2245
+ return this.minimizeSession(this.resolveSessionId(identity));
2246
+ }
2247
+ removeSession(sessionId) {
2248
+ this.sessionsState.update((sessions) => sessions.filter((session) => session.id !== sessionId));
2249
+ }
2250
+ removeContextSession(identity) {
2251
+ this.removeSession(this.resolveSessionId(identity));
2252
+ }
2253
+ getSession(sessionId) {
2254
+ return this.sessionsState().find((session) => session.id === sessionId) ?? null;
2255
+ }
2256
+ getContextSession(identity) {
2257
+ return this.getSession(this.resolveSessionId(identity));
2258
+ }
2259
+ clear() {
2260
+ this.sessionsState.set([]);
2261
+ }
2262
+ setVisibility(sessionId, visibility) {
2263
+ const session = this.getSession(sessionId);
2264
+ if (!session)
2265
+ return null;
2266
+ return this.upsertSession({ ...session, visibility });
2267
+ }
2268
+ resolveSessionId(identity) {
2269
+ if (typeof identity === 'string')
2270
+ return identity;
2271
+ if ('identity' in identity)
2272
+ return identity.identity.sessionId;
2273
+ return identity.sessionId;
2274
+ }
2275
+ normalizeDescriptor(descriptor) {
2276
+ const id = descriptor.id?.trim();
2277
+ const ownerId = descriptor.ownerId?.trim();
2278
+ const ownerType = descriptor.ownerType?.trim();
2279
+ if (!id || !ownerId || !ownerType) {
2280
+ throw new Error('Praxis assistant sessions require id, ownerId and ownerType.');
2281
+ }
2282
+ const contextSnapshot = descriptor.contextSnapshot
2283
+ ? normalizePraxisAssistantContextSnapshot(descriptor.contextSnapshot)
2284
+ : null;
2285
+ if (contextSnapshot) {
2286
+ this.assertContextIdentity(id, ownerId, ownerType, contextSnapshot);
2287
+ }
2288
+ const contextItems = descriptor.contextItems
2289
+ ? [...descriptor.contextItems]
2290
+ : this.toShellContextItems(contextSnapshot);
2291
+ return {
2292
+ id,
2293
+ ownerId,
2294
+ ownerType,
2295
+ title: descriptor.title?.trim() || 'Praxis assistant',
2296
+ summary: descriptor.summary?.trim() || '',
2297
+ mode: descriptor.mode || 'chat',
2298
+ state: descriptor.state || 'idle',
2299
+ visibility: descriptor.visibility || 'minimized',
2300
+ contextItems,
2301
+ contextSnapshot,
2302
+ badge: descriptor.badge?.trim() || '',
2303
+ icon: descriptor.icon?.trim() || '',
2304
+ updatedAt: descriptor.updatedAt?.trim() || null,
2305
+ };
2306
+ }
2307
+ assertContextIdentity(id, ownerId, ownerType, contextSnapshot) {
2308
+ const identity = contextSnapshot.identity;
2309
+ if (identity.sessionId !== id || identity.ownerId !== ownerId || identity.ownerType !== ownerType) {
2310
+ throw new Error('Praxis assistant session context identity must match id, ownerId and ownerType.');
2311
+ }
2312
+ }
2313
+ toShellContextItems(contextSnapshot) {
2314
+ if (!contextSnapshot)
2315
+ return [];
2316
+ return contextSnapshot.contextItems.map((item) => ({
2317
+ id: item.id,
2318
+ label: item.label,
2319
+ value: item.value,
2320
+ kind: item.kind,
2321
+ }));
2322
+ }
2323
+ sortSessions(sessions, activeSessionId) {
2324
+ return sessions
2325
+ .map((session) => activeSessionId && session.id !== activeSessionId
2326
+ ? { ...session, visibility: 'minimized' }
2327
+ : session)
2328
+ .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
2329
+ }
2330
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAssistantSessionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2331
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAssistantSessionRegistryService, providedIn: 'root' });
2332
+ }
2333
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAssistantSessionRegistryService, decorators: [{
2334
+ type: Injectable,
2335
+ args: [{ providedIn: 'root' }]
2336
+ }] });
2337
+
1742
2338
  const HISTORY_INDEX_PREFIX = 'praxis.ai.history.index';
1743
2339
  const HISTORY_SESSION_PREFIX = 'praxis.ai.history.session';
1744
2340
  const DEFAULT_SESSION_TITLE = 'Nova sessão';
@@ -2436,7 +3032,7 @@ class PraxisAiAssistantComponent {
2436
3032
  : undefined;
2437
3033
  const normalizedRuntimeState = runtimeState !== undefined ? this.toAiJsonObject(runtimeState) : undefined;
2438
3034
  const normalizedSuggestedPatch = suggestion?.patch ? this.toAiJsonObject(suggestion.patch) : undefined;
2439
- const normalizedContextHints = this.toClarificationContextHints(mergedContextHints);
3035
+ const normalizedContextHints = this.enrichDomainCatalogAuthoringHints(mergedContextHints);
2440
3036
  const patchRequest = {
2441
3037
  componentId,
2442
3038
  componentType,
@@ -5138,15 +5734,15 @@ class PraxisAiAssistantComponent {
5138
5734
  .filter((value) => value.length > 0);
5139
5735
  }
5140
5736
  resolveBadgeContextHints(contextHints) {
5141
- if (!contextHints || typeof contextHints !== 'object')
5737
+ const candidate = this.toAiJsonObject(contextHints);
5738
+ if (!Object.keys(candidate).length)
5142
5739
  return null;
5143
- const badge = this.asRecord(contextHints['badge']);
5740
+ const badge = this.asRecord(candidate['badge']);
5144
5741
  if (badge)
5145
5742
  return this.toAiJsonObject(badge);
5146
- const candidate = contextHints;
5147
5743
  const hasBadgeKeys = ['field', 'values', 'valueColorMap', 'palette', 'inferredType', 'explicitType']
5148
5744
  .some((key) => Object.prototype.hasOwnProperty.call(candidate, key));
5149
- return hasBadgeKeys ? candidate : null;
5745
+ return hasBadgeKeys ? this.toAiJsonObject(candidate) : null;
5150
5746
  }
5151
5747
  hasBadgeValues(badgeHints) {
5152
5748
  if (!badgeHints)
@@ -5190,6 +5786,25 @@ class PraxisAiAssistantComponent {
5190
5786
  }
5191
5787
  return this.toClarificationContextHints(merged) ?? null;
5192
5788
  }
5789
+ enrichDomainCatalogAuthoringHints(value) {
5790
+ const normalized = this.toClarificationContextHints(value);
5791
+ if (!normalized)
5792
+ return undefined;
5793
+ const domainCatalog = this.asRecord(normalized['domainCatalog']);
5794
+ const recommendedAuthoringFlow = domainCatalog?.['recommendedAuthoringFlow'];
5795
+ if (typeof recommendedAuthoringFlow !== 'string' || !recommendedAuthoringFlow.trim()) {
5796
+ return normalized;
5797
+ }
5798
+ const flowId = recommendedAuthoringFlow.trim();
5799
+ const enriched = this.toAiJsonObject(normalized);
5800
+ enriched['authoringFlow'] = this.toAiJsonObject({
5801
+ flowId,
5802
+ source: 'domainCatalog.recommendedAuthoringFlow',
5803
+ reviewRequired: true,
5804
+ materializeOnlyAfterReview: true,
5805
+ });
5806
+ return this.toClarificationContextHints(enriched);
5807
+ }
5193
5808
  setResourcePathHint(resourcePath) {
5194
5809
  const raw = (resourcePath || '').trim();
5195
5810
  const normalized = this.normalizeResourcePath(raw);
@@ -5865,16 +6480,18 @@ const DEFAULT_LAYOUT = {
5865
6480
  const DEFAULT_LABELS = {
5866
6481
  title: 'Assistente de IA',
5867
6482
  subtitle: 'Revise o resultado gerado antes de aplicar.',
5868
- close: 'Fechar',
6483
+ close: 'Minimizar assistente',
5869
6484
  prompt: 'Prompt',
5870
6485
  promptPlaceholder: 'Descreva o que você quer criar ou alterar.',
5871
6486
  emptyConversation: 'Diga o que você quer criar ou alterar.',
5872
- submit: 'Pré-visualizar',
5873
- apply: 'Aplicar',
6487
+ submit: 'Interpretar pedido',
6488
+ apply: 'Aplicar ajuste',
5874
6489
  conversationAria: 'Conversa com IA',
5875
6490
  quickRepliesAria: 'Respostas rápidas',
6491
+ quickReplyDetails: 'Detalhes técnicos',
5876
6492
  dragHandleAria: 'Mover assistente de IA',
5877
6493
  resizeHandleAria: 'Redimensionar assistente de IA',
6494
+ resetLayout: 'Restaurar posição do assistente',
5878
6495
  contextAria: 'Contexto ativo',
5879
6496
  attachmentsAria: 'Contexto anexado',
5880
6497
  attach: 'Anexar',
@@ -5911,10 +6528,15 @@ class PraxisAiAssistantShellComponent {
5911
6528
  panelTestId = '';
5912
6529
  submitTestId = '';
5913
6530
  applyTestId = '';
6531
+ primaryAction = null;
6532
+ secondaryActions = [];
6533
+ governanceActions = [];
5914
6534
  busy = false;
5915
6535
  canSubmit = true;
5916
6536
  canApply = false;
5917
6537
  submitOnEnter = true;
6538
+ showAttachAction = true;
6539
+ enablePastedAttachments = true;
5918
6540
  enableFileAttachments = false;
5919
6541
  attachmentAccept = '';
5920
6542
  attachmentMultiple = true;
@@ -5927,6 +6549,9 @@ class PraxisAiAssistantShellComponent {
5927
6549
  promptChange = new EventEmitter();
5928
6550
  submitPrompt = new EventEmitter();
5929
6551
  apply = new EventEmitter();
6552
+ retryTurn = new EventEmitter();
6553
+ cancelTurn = new EventEmitter();
6554
+ shellAction = new EventEmitter();
5930
6555
  close = new EventEmitter();
5931
6556
  attach = new EventEmitter();
5932
6557
  attachmentsPasted = new EventEmitter();
@@ -5943,7 +6568,9 @@ class PraxisAiAssistantShellComponent {
5943
6568
  currentPrompt = '';
5944
6569
  resolvedLabels = DEFAULT_LABELS;
5945
6570
  currentLayout = { ...DEFAULT_LAYOUT };
6571
+ resizeHandles = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];
5946
6572
  pointerSession = null;
6573
+ baselineLayout = null;
5947
6574
  ownedPreviewUrls = new Set();
5948
6575
  onWindowPointerMove = (event) => this.handlePointerMove(event);
5949
6576
  onWindowPointerUp = (event) => this.finishPointerSession(event);
@@ -5959,6 +6586,7 @@ class PraxisAiAssistantShellComponent {
5959
6586
  }
5960
6587
  if (changes['layout']) {
5961
6588
  this.currentLayout = this.normalizeLayout(this.layout);
6589
+ this.baselineLayout ??= { ...this.currentLayout };
5962
6590
  }
5963
6591
  if (changes['messages']) {
5964
6592
  this.scheduleConversationScroll();
@@ -5989,6 +6617,9 @@ class PraxisAiAssistantShellComponent {
5989
6617
  this.onSubmit();
5990
6618
  }
5991
6619
  onPromptPaste(event) {
6620
+ if (!this.enablePastedAttachments) {
6621
+ return;
6622
+ }
5992
6623
  const files = Array.from(event.clipboardData?.files ?? [])
5993
6624
  .filter((file) => file.type.startsWith('image/'));
5994
6625
  if (this.busy || !files.length) {
@@ -6023,11 +6654,248 @@ class PraxisAiAssistantShellComponent {
6023
6654
  return;
6024
6655
  this.apply.emit();
6025
6656
  }
6657
+ onShellAction(action) {
6658
+ if (this.isShellActionDisabled(action))
6659
+ return;
6660
+ this.shellAction.emit(action);
6661
+ switch (action.kind) {
6662
+ case 'submit-prompt':
6663
+ this.onSubmit();
6664
+ return;
6665
+ case 'apply':
6666
+ this.onApply();
6667
+ return;
6668
+ case 'retry':
6669
+ this.retryTurn.emit();
6670
+ return;
6671
+ case 'cancel':
6672
+ this.cancelTurn.emit();
6673
+ return;
6674
+ default:
6675
+ return;
6676
+ }
6677
+ }
6678
+ getPrimaryAction() {
6679
+ return this.normalizeShellAction(this.primaryAction, this.getDefaultPrimaryAction());
6680
+ }
6681
+ getSecondaryActions() {
6682
+ const actions = [
6683
+ ...this.secondaryActions,
6684
+ ...this.governanceActions,
6685
+ ];
6686
+ if (this.canApply) {
6687
+ actions.push({
6688
+ id: 'apply',
6689
+ kind: 'apply',
6690
+ label: this.resolvedLabels.apply,
6691
+ icon: 'check_circle',
6692
+ tone: 'governance',
6693
+ disabled: !this.canApply,
6694
+ testId: this.applyTestId || `${this.testIdPrefix}-apply`,
6695
+ });
6696
+ }
6697
+ if (this.state === 'error') {
6698
+ actions.push({
6699
+ id: 'retry',
6700
+ kind: 'retry',
6701
+ label: 'Tentar novamente',
6702
+ icon: 'replay',
6703
+ tone: 'warning',
6704
+ testId: `${this.testIdPrefix}-retry`,
6705
+ });
6706
+ }
6707
+ if (this.hasRecoverableTurn()) {
6708
+ actions.push({
6709
+ id: 'cancel',
6710
+ kind: 'cancel',
6711
+ label: 'Cancelar pedido',
6712
+ icon: 'close',
6713
+ tone: 'neutral',
6714
+ testId: `${this.testIdPrefix}-cancel-turn`,
6715
+ });
6716
+ }
6717
+ return actions.map((action) => this.normalizeShellAction(action));
6718
+ }
6719
+ getPrimaryActionTooltip(action) {
6720
+ return action.iconOnly ? action.ariaLabel || action.label : '';
6721
+ }
6722
+ isShellActionDisabled(action) {
6723
+ return Boolean(this.busy
6724
+ || action.disabled
6725
+ || (action.requiresPrompt && !this.currentPrompt.trim())
6726
+ || (action.kind === 'submit-prompt' && !this.canSubmit)
6727
+ || (action.kind === 'apply' && !this.canApply));
6728
+ }
6729
+ getShellActionTone(action) {
6730
+ const tone = (action.tone || (action.kind === 'apply' ? 'governance' : 'secondary')).toLowerCase();
6731
+ switch (tone) {
6732
+ case 'primary':
6733
+ case 'secondary':
6734
+ case 'governance':
6735
+ case 'success':
6736
+ case 'warning':
6737
+ case 'danger':
6738
+ case 'neutral':
6739
+ return tone;
6740
+ default:
6741
+ return 'secondary';
6742
+ }
6743
+ }
6744
+ trackShellAction(_index, action) {
6745
+ return action.id;
6746
+ }
6747
+ hasRecoverableTurn() {
6748
+ if (this.busy || this.state === 'idle' || this.state === 'listening') {
6749
+ return false;
6750
+ }
6751
+ return this.messages.length > 0
6752
+ || this.quickReplies.length > 0
6753
+ || this.canApply
6754
+ || this.state === 'clarification'
6755
+ || this.state === 'review'
6756
+ || this.state === 'error';
6757
+ }
6758
+ getDefaultPrimaryAction() {
6759
+ const base = {
6760
+ id: 'submit',
6761
+ kind: 'submit-prompt',
6762
+ label: this.resolvedLabels.submit,
6763
+ icon: 'auto_awesome',
6764
+ tone: 'primary',
6765
+ requiresPrompt: true,
6766
+ testId: this.submitTestId || `${this.testIdPrefix}-submit`,
6767
+ };
6768
+ switch (this.state) {
6769
+ case 'processing':
6770
+ return {
6771
+ ...base,
6772
+ label: 'Interpretando...',
6773
+ icon: 'hourglass_top',
6774
+ disabled: true,
6775
+ };
6776
+ case 'clarification':
6777
+ return {
6778
+ ...base,
6779
+ label: 'Responder',
6780
+ icon: 'question_answer',
6781
+ };
6782
+ case 'review':
6783
+ return {
6784
+ ...base,
6785
+ label: 'Refinar pedido',
6786
+ icon: 'tune',
6787
+ };
6788
+ case 'applying':
6789
+ return {
6790
+ ...base,
6791
+ label: 'Aplicando...',
6792
+ icon: 'sync',
6793
+ disabled: true,
6794
+ };
6795
+ case 'error':
6796
+ return {
6797
+ ...base,
6798
+ label: 'Corrigir pedido',
6799
+ icon: 'edit_note',
6800
+ tone: 'warning',
6801
+ };
6802
+ case 'success':
6803
+ return {
6804
+ ...base,
6805
+ label: 'Novo pedido',
6806
+ icon: 'add_comment',
6807
+ };
6808
+ case 'idle':
6809
+ case 'listening':
6810
+ default:
6811
+ return base;
6812
+ }
6813
+ }
6026
6814
  onQuickReply(reply) {
6027
6815
  if (this.busy)
6028
6816
  return;
6029
6817
  this.quickReply.emit(reply);
6030
6818
  }
6819
+ getQuickReplyAriaLabel(reply) {
6820
+ const label = reply.label?.trim() ?? '';
6821
+ const description = reply.description?.trim();
6822
+ return description ? `${label}. ${description}` : label;
6823
+ }
6824
+ getQuickReplyTechnicalDetails(reply) {
6825
+ const hints = reply.contextHints;
6826
+ const details = this.asRecord(hints?.['technicalDetails']) ?? hints;
6827
+ if (!details)
6828
+ return '';
6829
+ const submitMethod = this.stringHint(details, 'submitMethod') || this.stringHint(details, 'operation');
6830
+ const submitUrl = this.stringHint(details, 'submitUrl');
6831
+ const resourcePath = this.stringHint(details, 'resourcePath');
6832
+ const schemaUrl = this.stringHint(details, 'schemaUrl');
6833
+ return [
6834
+ submitMethod && submitUrl ? `${submitMethod.toUpperCase()} ${submitUrl}` : '',
6835
+ resourcePath && resourcePath !== submitUrl ? `Recurso: ${resourcePath}` : '',
6836
+ schemaUrl ? `Schema: ${schemaUrl}` : '',
6837
+ ].filter(Boolean).join('\n');
6838
+ }
6839
+ getQuickReplyTone(reply) {
6840
+ const tone = (reply.tone || reply.kind || 'neutral').toLowerCase();
6841
+ switch (tone) {
6842
+ case 'primary':
6843
+ case 'analytics':
6844
+ case 'resource':
6845
+ case 'warning':
6846
+ case 'neutral':
6847
+ case 'success':
6848
+ case 'danger':
6849
+ return tone;
6850
+ case 'confirm':
6851
+ return 'primary';
6852
+ case 'suggestion':
6853
+ return 'resource';
6854
+ case 'revise':
6855
+ return 'warning';
6856
+ case 'cancel':
6857
+ default:
6858
+ return 'neutral';
6859
+ }
6860
+ }
6861
+ asRecord(value) {
6862
+ return value && typeof value === 'object' && !Array.isArray(value)
6863
+ ? value
6864
+ : null;
6865
+ }
6866
+ stringHint(source, key) {
6867
+ const value = source[key];
6868
+ return typeof value === 'string' ? value.trim() : '';
6869
+ }
6870
+ getCloseIcon() {
6871
+ const label = this.resolvedLabels.close.toLocaleLowerCase('pt-BR');
6872
+ return label.includes('minimiz') ? 'remove' : 'close';
6873
+ }
6874
+ getResetLayoutLabel() {
6875
+ return this.resolvedLabels.resetLayout || DEFAULT_LABELS.resetLayout || 'Restaurar posição do assistente';
6876
+ }
6877
+ resetLayout() {
6878
+ const bounds = this.resolveViewportBounds();
6879
+ const layout = this.clampLayout(this.baselineLayout ?? DEFAULT_LAYOUT, bounds.width, bounds.height);
6880
+ this.currentLayout = layout;
6881
+ this.layoutChange.emit(layout);
6882
+ this.cdr.markForCheck();
6883
+ }
6884
+ normalizeShellAction(action, fallback) {
6885
+ const source = action ?? fallback;
6886
+ return {
6887
+ id: source?.id || fallback?.id || 'action',
6888
+ label: source?.label || fallback?.label || '',
6889
+ kind: source?.kind ?? fallback?.kind ?? 'custom',
6890
+ icon: source?.icon ?? fallback?.icon ?? null,
6891
+ tone: source?.tone ?? fallback?.tone ?? null,
6892
+ disabled: source?.disabled ?? fallback?.disabled ?? false,
6893
+ requiresPrompt: source?.requiresPrompt ?? fallback?.requiresPrompt ?? false,
6894
+ testId: source?.testId ?? fallback?.testId ?? null,
6895
+ ariaLabel: source?.ariaLabel ?? fallback?.ariaLabel ?? null,
6896
+ iconOnly: source?.iconOnly ?? fallback?.iconOnly ?? false,
6897
+ };
6898
+ }
6031
6899
  onRemoveAttachment(attachment) {
6032
6900
  if (this.busy)
6033
6901
  return;
@@ -6045,6 +6913,26 @@ class PraxisAiAssistantShellComponent {
6045
6913
  this.resendMessage.emit(message);
6046
6914
  }
6047
6915
  }
6916
+ getMessageActionIcon(action) {
6917
+ if (action.icon)
6918
+ return action.icon;
6919
+ switch (action.kind) {
6920
+ case 'edit':
6921
+ return 'edit';
6922
+ case 'resend':
6923
+ return 'replay';
6924
+ case 'copy':
6925
+ return 'content_copy';
6926
+ default:
6927
+ return '';
6928
+ }
6929
+ }
6930
+ getMessageActionLabel(action) {
6931
+ return action.ariaLabel || action.label;
6932
+ }
6933
+ isMessageActionIconOnly(action) {
6934
+ return action.iconOnly ?? !!this.getMessageActionIcon(action);
6935
+ }
6048
6936
  getModeLabel() {
6049
6937
  switch (this.mode) {
6050
6938
  case 'config':
@@ -6088,10 +6976,13 @@ class PraxisAiAssistantShellComponent {
6088
6976
  return;
6089
6977
  this.startPointerSession('drag', event);
6090
6978
  }
6091
- startResize(event) {
6979
+ startResize(direction, event) {
6092
6980
  if (!this.resizable || event.button !== 0)
6093
6981
  return;
6094
- this.startPointerSession('resize', event);
6982
+ this.startPointerSession('resize', event, direction);
6983
+ }
6984
+ trackResizeHandle(_index, direction) {
6985
+ return direction;
6095
6986
  }
6096
6987
  trackMessage(_index, message) {
6097
6988
  return message.id;
@@ -6108,15 +6999,16 @@ class PraxisAiAssistantShellComponent {
6108
6999
  trackAttachment(_index, attachment) {
6109
7000
  return attachment.id;
6110
7001
  }
6111
- startPointerSession(mode, event) {
7002
+ startPointerSession(mode, event, resizeDirection) {
6112
7003
  event.preventDefault();
6113
7004
  event.stopPropagation();
6114
7005
  const panel = this.panel?.nativeElement;
6115
7006
  if (!panel)
6116
7007
  return;
6117
- const bounds = this.resolveBounds(panel);
7008
+ const bounds = this.resolveViewportBounds();
6118
7009
  this.pointerSession = {
6119
7010
  mode,
7011
+ resizeDirection,
6120
7012
  pointerId: event.pointerId,
6121
7013
  startX: event.clientX,
6122
7014
  startY: event.clientY,
@@ -6146,15 +7038,40 @@ class PraxisAiAssistantShellComponent {
6146
7038
  left: session.startLayout.left + deltaX,
6147
7039
  top: session.startLayout.top + deltaY,
6148
7040
  }
6149
- : {
6150
- ...session.startLayout,
6151
- width: session.startLayout.width + deltaX,
6152
- height: session.startLayout.height + deltaY,
6153
- };
7041
+ : this.resizeLayout(session, deltaX, deltaY);
6154
7042
  this.currentLayout = this.clampLayout(next, session.boundsWidth, session.boundsHeight);
6155
7043
  this.layoutChange.emit(this.currentLayout);
6156
7044
  this.cdr.markForCheck();
6157
7045
  }
7046
+ resizeLayout(session, deltaX, deltaY) {
7047
+ const direction = session.resizeDirection ?? 'se';
7048
+ const start = session.startLayout;
7049
+ const right = start.left + start.width;
7050
+ const bottom = start.top + start.height;
7051
+ let left = start.left;
7052
+ let top = start.top;
7053
+ let width = start.width;
7054
+ let height = start.height;
7055
+ if (direction.includes('e')) {
7056
+ const maxWidth = Math.max(this.minWidth, session.boundsWidth - start.left - this.margin);
7057
+ width = this.clamp(start.width + deltaX, this.minWidth, maxWidth);
7058
+ }
7059
+ if (direction.includes('s')) {
7060
+ const maxHeight = Math.max(this.minHeight, session.boundsHeight - start.top - this.margin);
7061
+ height = this.clamp(start.height + deltaY, this.minHeight, maxHeight);
7062
+ }
7063
+ if (direction.includes('w')) {
7064
+ const nextLeft = this.clamp(start.left + deltaX, this.margin, right - this.minWidth);
7065
+ left = nextLeft;
7066
+ width = right - nextLeft;
7067
+ }
7068
+ if (direction.includes('n')) {
7069
+ const nextTop = this.clamp(start.top + deltaY, this.margin, bottom - this.minHeight);
7070
+ top = nextTop;
7071
+ height = bottom - nextTop;
7072
+ }
7073
+ return { left, top, width, height };
7074
+ }
6158
7075
  finishPointerSession(event) {
6159
7076
  if (this.pointerSession && event.pointerId === this.pointerSession.pointerId) {
6160
7077
  try {
@@ -6172,10 +7089,9 @@ class PraxisAiAssistantShellComponent {
6172
7089
  window.removeEventListener('pointerup', this.onWindowPointerUp);
6173
7090
  window.removeEventListener('pointercancel', this.onWindowPointerUp);
6174
7091
  }
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);
7092
+ resolveViewportBounds() {
7093
+ const width = typeof window !== 'undefined' ? window.innerWidth : 1024;
7094
+ const height = typeof window !== 'undefined' ? window.innerHeight : 768;
6179
7095
  return {
6180
7096
  width: Math.max(width, this.minWidth + this.margin * 2),
6181
7097
  height: Math.max(height, this.minHeight + this.margin * 2),
@@ -6268,7 +7184,7 @@ class PraxisAiAssistantShellComponent {
6268
7184
  this.ownedPreviewUrls.delete(previewUrl);
6269
7185
  }
6270
7186
  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 });
7187
+ 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]=\"getResetLayoutLabel()\"\n [attr.aria-label]=\"getResetLayoutLabel()\"\n (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"resetLayout()\"\n [attr.data-testid]=\"testIdPrefix + '-reset-layout'\"\n >\n <mat-icon>center_focus_strong</mat-icon>\n </button>\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 {{ 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-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 [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 <mat-icon\n *ngIf=\"reply.icon\"\n class=\"praxis-ai-assistant-shell__quick-reply-icon\"\n aria-hidden=\"true\"\n >\n {{ reply.icon }}\n </mat-icon>\n <span class=\"praxis-ai-assistant-shell__quick-reply-copy\">\n <span class=\"praxis-ai-assistant-shell__quick-reply-label\">{{ reply.label }}</span>\n <span\n *ngIf=\"reply.description\"\n class=\"praxis-ai-assistant-shell__quick-reply-description\"\n >\n {{ reply.description }}\n </span>\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 </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 <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{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, #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: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, #334155) 72%,transparent);background: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:30px;height:30px;border-radius:8px;background:var(--md-sys-color-primary, #60a5fa);color:var(--md-sys-color-on-primary, #020617);box-shadow:0 6px 16px #60a5fa38}.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, #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--context{color:var(--md-sys-color-on-surface, #f8fafc);background:color-mix(in srgb,var(--md-sys-color-primary, #60a5fa) 9%,var(--md-sys-color-surface-container-high, #263244))}.praxis-ai-assistant-shell__badge--state{border-color:transparent;background:transparent;color:var(--md-sys-color-on-surface-variant, #cbd5e1);padding-inline:3px}.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__state-dot{flex:0 0 auto;width:6px;height:6px;border-radius:999px;background:var(--md-sys-color-primary, #60a5fa);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary, #60a5fa) 16%,transparent)}.praxis-ai-assistant-shell__badge--error .praxis-ai-assistant-shell__state-dot{background:var(--md-sys-color-error, #ff6b6b);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-error, #ff6b6b) 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, #cbd5e1);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, #cbd5e1);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: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-action--icon{width:30px;min-width:30px;height:30px;padding:0;color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.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, #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{--praxis-ai-assistant-shell-quick-reply-accent: var(--md-sys-color-primary, #60a5fa);--praxis-ai-assistant-shell-quick-reply-background: color-mix( in srgb, var(--praxis-ai-assistant-shell-quick-reply-accent) 12%, var(--md-sys-color-surface-container-high, #263244) );--praxis-ai-assistant-shell-quick-reply-foreground: var(--md-sys-color-on-surface, #f8fafc);max-width:100%;min-height:38px;border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 58%,transparent);border-radius:8px;color:var(--praxis-ai-assistant-shell-quick-reply-foreground);background:var(--praxis-ai-assistant-shell-quick-reply-background);white-space:normal;text-align:left}.praxis-ai-assistant-shell__quick-reply ::ng-deep .mdc-button__label{min-width:0;display:inline-flex;align-items:center;gap:8px;width:100%}.praxis-ai-assistant-shell__quick-reply-icon{flex:0 0 auto;width:18px;height:18px;color:var(--praxis-ai-assistant-shell-quick-reply-accent);font-size:18px}.praxis-ai-assistant-shell__quick-reply-details{flex:0 0 auto;width:16px;height:16px;color:var(--md-sys-color-on-surface-variant, #94a3b8);font-size:16px;opacity:.82}.praxis-ai-assistant-shell__quick-reply-copy{min-width:0;display:grid;gap:2px}.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{font-weight:600;line-height:1.2}.praxis-ai-assistant-shell__quick-reply-description{color:var(--md-sys-color-on-surface-variant, #cbd5e1);font-size:11px;line-height:1.25}.praxis-ai-assistant-shell__quick-reply--tone-analytics{--praxis-ai-assistant-shell-quick-reply-accent: #38bdf8}.praxis-ai-assistant-shell__quick-reply--tone-resource{--praxis-ai-assistant-shell-quick-reply-accent: #34d399}.praxis-ai-assistant-shell__quick-reply--tone-warning{--praxis-ai-assistant-shell-quick-reply-accent: #facc15}.praxis-ai-assistant-shell__quick-reply--tone-success{--praxis-ai-assistant-shell-quick-reply-accent: #22c55e}.praxis-ai-assistant-shell__quick-reply--tone-danger{--praxis-ai-assistant-shell-quick-reply-accent: var(--md-sys-color-error, #ff6b6b)}.praxis-ai-assistant-shell__quick-reply--tone-neutral{--praxis-ai-assistant-shell-quick-reply-accent: var(--md-sys-color-outline, #94a3b8)}.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__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, #cbd5e1)}.praxis-ai-assistant-shell__action--tone-governance{color:var(--md-sys-color-primary, #60a5fa);background:color-mix(in srgb,var(--md-sys-color-primary, #60a5fa) 10%,transparent)}.praxis-ai-assistant-shell__action--tone-success{color:#34d399;background:color-mix(in srgb,#34d399 10%,transparent)}.praxis-ai-assistant-shell__action--tone-warning{color:#facc15;background:color-mix(in srgb,#facc15 10%,transparent)}.praxis-ai-assistant-shell__action--tone-danger{color:var(--md-sys-color-error, #ff6b6b);background:color-mix(in srgb,var(--md-sys-color-error, #ff6b6b) 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, #94a3b8);border-bottom:2px solid var(--md-sys-color-outline, #94a3b8)}\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
7188
  }
6273
7189
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantShellComponent, decorators: [{
6274
7190
  type: Component,
@@ -6279,7 +7195,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6279
7195
  MatIconModule,
6280
7196
  MatProgressSpinnerModule,
6281
7197
  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"] }]
7198
+ ], 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]=\"getResetLayoutLabel()\"\n [attr.aria-label]=\"getResetLayoutLabel()\"\n (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"resetLayout()\"\n [attr.data-testid]=\"testIdPrefix + '-reset-layout'\"\n >\n <mat-icon>center_focus_strong</mat-icon>\n </button>\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 {{ 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-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 [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 <mat-icon\n *ngIf=\"reply.icon\"\n class=\"praxis-ai-assistant-shell__quick-reply-icon\"\n aria-hidden=\"true\"\n >\n {{ reply.icon }}\n </mat-icon>\n <span class=\"praxis-ai-assistant-shell__quick-reply-copy\">\n <span class=\"praxis-ai-assistant-shell__quick-reply-label\">{{ reply.label }}</span>\n <span\n *ngIf=\"reply.description\"\n class=\"praxis-ai-assistant-shell__quick-reply-description\"\n >\n {{ reply.description }}\n </span>\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 </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 <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{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, #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: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, #334155) 72%,transparent);background: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:30px;height:30px;border-radius:8px;background:var(--md-sys-color-primary, #60a5fa);color:var(--md-sys-color-on-primary, #020617);box-shadow:0 6px 16px #60a5fa38}.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, #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--context{color:var(--md-sys-color-on-surface, #f8fafc);background:color-mix(in srgb,var(--md-sys-color-primary, #60a5fa) 9%,var(--md-sys-color-surface-container-high, #263244))}.praxis-ai-assistant-shell__badge--state{border-color:transparent;background:transparent;color:var(--md-sys-color-on-surface-variant, #cbd5e1);padding-inline:3px}.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__state-dot{flex:0 0 auto;width:6px;height:6px;border-radius:999px;background:var(--md-sys-color-primary, #60a5fa);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary, #60a5fa) 16%,transparent)}.praxis-ai-assistant-shell__badge--error .praxis-ai-assistant-shell__state-dot{background:var(--md-sys-color-error, #ff6b6b);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-error, #ff6b6b) 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, #cbd5e1);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, #cbd5e1);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: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-action--icon{width:30px;min-width:30px;height:30px;padding:0;color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.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, #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{--praxis-ai-assistant-shell-quick-reply-accent: var(--md-sys-color-primary, #60a5fa);--praxis-ai-assistant-shell-quick-reply-background: color-mix( in srgb, var(--praxis-ai-assistant-shell-quick-reply-accent) 12%, var(--md-sys-color-surface-container-high, #263244) );--praxis-ai-assistant-shell-quick-reply-foreground: var(--md-sys-color-on-surface, #f8fafc);max-width:100%;min-height:38px;border-color:color-mix(in srgb,var(--praxis-ai-assistant-shell-quick-reply-accent) 58%,transparent);border-radius:8px;color:var(--praxis-ai-assistant-shell-quick-reply-foreground);background:var(--praxis-ai-assistant-shell-quick-reply-background);white-space:normal;text-align:left}.praxis-ai-assistant-shell__quick-reply ::ng-deep .mdc-button__label{min-width:0;display:inline-flex;align-items:center;gap:8px;width:100%}.praxis-ai-assistant-shell__quick-reply-icon{flex:0 0 auto;width:18px;height:18px;color:var(--praxis-ai-assistant-shell-quick-reply-accent);font-size:18px}.praxis-ai-assistant-shell__quick-reply-details{flex:0 0 auto;width:16px;height:16px;color:var(--md-sys-color-on-surface-variant, #94a3b8);font-size:16px;opacity:.82}.praxis-ai-assistant-shell__quick-reply-copy{min-width:0;display:grid;gap:2px}.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{font-weight:600;line-height:1.2}.praxis-ai-assistant-shell__quick-reply-description{color:var(--md-sys-color-on-surface-variant, #cbd5e1);font-size:11px;line-height:1.25}.praxis-ai-assistant-shell__quick-reply--tone-analytics{--praxis-ai-assistant-shell-quick-reply-accent: #38bdf8}.praxis-ai-assistant-shell__quick-reply--tone-resource{--praxis-ai-assistant-shell-quick-reply-accent: #34d399}.praxis-ai-assistant-shell__quick-reply--tone-warning{--praxis-ai-assistant-shell-quick-reply-accent: #facc15}.praxis-ai-assistant-shell__quick-reply--tone-success{--praxis-ai-assistant-shell-quick-reply-accent: #22c55e}.praxis-ai-assistant-shell__quick-reply--tone-danger{--praxis-ai-assistant-shell-quick-reply-accent: var(--md-sys-color-error, #ff6b6b)}.praxis-ai-assistant-shell__quick-reply--tone-neutral{--praxis-ai-assistant-shell-quick-reply-accent: var(--md-sys-color-outline, #94a3b8)}.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__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, #cbd5e1)}.praxis-ai-assistant-shell__action--tone-governance{color:var(--md-sys-color-primary, #60a5fa);background:color-mix(in srgb,var(--md-sys-color-primary, #60a5fa) 10%,transparent)}.praxis-ai-assistant-shell__action--tone-success{color:#34d399;background:color-mix(in srgb,#34d399 10%,transparent)}.praxis-ai-assistant-shell__action--tone-warning{color:#facc15;background:color-mix(in srgb,#facc15 10%,transparent)}.praxis-ai-assistant-shell__action--tone-danger{color:var(--md-sys-color-error, #ff6b6b);background:color-mix(in srgb,var(--md-sys-color-error, #ff6b6b) 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, #94a3b8);border-bottom:2px solid var(--md-sys-color-outline, #94a3b8)}\n"] }]
6283
7199
  }], propDecorators: { labels: [{
6284
7200
  type: Input
6285
7201
  }], mode: [{
@@ -6308,6 +7224,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6308
7224
  type: Input
6309
7225
  }], applyTestId: [{
6310
7226
  type: Input
7227
+ }], primaryAction: [{
7228
+ type: Input
7229
+ }], secondaryActions: [{
7230
+ type: Input
7231
+ }], governanceActions: [{
7232
+ type: Input
6311
7233
  }], busy: [{
6312
7234
  type: Input
6313
7235
  }], canSubmit: [{
@@ -6316,6 +7238,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6316
7238
  type: Input
6317
7239
  }], submitOnEnter: [{
6318
7240
  type: Input
7241
+ }], showAttachAction: [{
7242
+ type: Input
7243
+ }], enablePastedAttachments: [{
7244
+ type: Input
6319
7245
  }], enableFileAttachments: [{
6320
7246
  type: Input
6321
7247
  }], attachmentAccept: [{
@@ -6340,6 +7266,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6340
7266
  type: Output
6341
7267
  }], apply: [{
6342
7268
  type: Output
7269
+ }], retryTurn: [{
7270
+ type: Output
7271
+ }], cancelTurn: [{
7272
+ type: Output
7273
+ }], shellAction: [{
7274
+ type: Output
6343
7275
  }], close: [{
6344
7276
  type: Output
6345
7277
  }], attach: [{
@@ -6368,6 +7300,281 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6368
7300
  args: ['conversation']
6369
7301
  }] } });
6370
7302
 
7303
+ function createPraxisAssistantViewportLayout(options = {}) {
7304
+ const width = normalizePositiveNumber(options.width, 560);
7305
+ const height = normalizePositiveNumber(options.height, 620);
7306
+ const top = normalizePositiveNumber(options.top, 88);
7307
+ const margin = normalizePositiveNumber(options.margin, 32);
7308
+ const viewportWidth = typeof window !== 'undefined' ? window.innerWidth : 1280;
7309
+ return {
7310
+ left: Math.max(margin, viewportWidth - width - margin),
7311
+ top,
7312
+ width,
7313
+ height,
7314
+ };
7315
+ }
7316
+ function normalizePositiveNumber(value, fallback) {
7317
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;
7318
+ }
7319
+
7320
+ class PraxisAiAssistantDockComponent {
7321
+ title = 'Praxis copilot active';
7322
+ summary = 'Conversation preserved. Continue where you stopped.';
7323
+ badge = '';
7324
+ icon = '';
7325
+ state = null;
7326
+ tone = null;
7327
+ ariaLabel = '';
7328
+ openAriaLabel = '';
7329
+ openTooltip = '';
7330
+ testId = 'praxis-ai-assistant-dock';
7331
+ openTestId = 'praxis-ai-assistant-dock-open';
7332
+ open = new EventEmitter();
7333
+ resolvedTone() {
7334
+ if (this.tone)
7335
+ return this.tone;
7336
+ if (this.state === 'error')
7337
+ return 'error';
7338
+ if (this.state === 'processing' || this.state === 'applying')
7339
+ return 'working';
7340
+ if (this.state === 'review')
7341
+ return 'review';
7342
+ if (this.state === 'clarification')
7343
+ return 'governed';
7344
+ return 'ready';
7345
+ }
7346
+ resolvedIcon() {
7347
+ if (this.icon)
7348
+ return this.icon;
7349
+ const tone = this.resolvedTone();
7350
+ if (tone === 'error')
7351
+ return 'error';
7352
+ if (tone === 'working')
7353
+ return 'sync';
7354
+ if (tone === 'review')
7355
+ return 'rate_review';
7356
+ if (tone === 'governed')
7357
+ return 'rule';
7358
+ return 'auto_awesome';
7359
+ }
7360
+ resolvedBadge() {
7361
+ if (this.badge)
7362
+ return this.badge;
7363
+ const tone = this.resolvedTone();
7364
+ if (tone === 'error')
7365
+ return 'Attention';
7366
+ if (tone === 'working')
7367
+ return 'Working';
7368
+ if (tone === 'review')
7369
+ return 'Review';
7370
+ if (tone === 'governed')
7371
+ return 'Governed';
7372
+ return 'Ready';
7373
+ }
7374
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantDockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7375
+ 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: `
7376
+ <section
7377
+ class="praxis-ai-assistant-dock"
7378
+ [class.praxis-ai-assistant-dock--working]="resolvedTone() === 'working'"
7379
+ [class.praxis-ai-assistant-dock--review]="resolvedTone() === 'review'"
7380
+ [class.praxis-ai-assistant-dock--governed]="resolvedTone() === 'governed'"
7381
+ [class.praxis-ai-assistant-dock--error]="resolvedTone() === 'error'"
7382
+ [attr.data-testid]="testId"
7383
+ role="status"
7384
+ [attr.aria-label]="ariaLabel || title"
7385
+ >
7386
+ <button
7387
+ class="praxis-ai-assistant-dock__main"
7388
+ type="button"
7389
+ [attr.data-testid]="openTestId"
7390
+ [attr.aria-label]="openAriaLabel || title"
7391
+ [matTooltip]="openTooltip || title"
7392
+ (click)="open.emit()"
7393
+ >
7394
+ <span class="praxis-ai-assistant-dock__orb" aria-hidden="true">
7395
+ <mat-icon>{{ resolvedIcon() }}</mat-icon>
7396
+ </span>
7397
+ <span class="praxis-ai-assistant-dock__copy">
7398
+ <strong>{{ title }}</strong>
7399
+ <span>{{ summary }}</span>
7400
+ </span>
7401
+ <span class="praxis-ai-assistant-dock__badge">{{ resolvedBadge() }}</span>
7402
+ </button>
7403
+ </section>
7404
+ `, 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 });
7405
+ }
7406
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantDockComponent, decorators: [{
7407
+ type: Component,
7408
+ args: [{ selector: 'praxis-ai-assistant-dock', standalone: true, imports: [CommonModule, MatIconModule, MatTooltipModule], template: `
7409
+ <section
7410
+ class="praxis-ai-assistant-dock"
7411
+ [class.praxis-ai-assistant-dock--working]="resolvedTone() === 'working'"
7412
+ [class.praxis-ai-assistant-dock--review]="resolvedTone() === 'review'"
7413
+ [class.praxis-ai-assistant-dock--governed]="resolvedTone() === 'governed'"
7414
+ [class.praxis-ai-assistant-dock--error]="resolvedTone() === 'error'"
7415
+ [attr.data-testid]="testId"
7416
+ role="status"
7417
+ [attr.aria-label]="ariaLabel || title"
7418
+ >
7419
+ <button
7420
+ class="praxis-ai-assistant-dock__main"
7421
+ type="button"
7422
+ [attr.data-testid]="openTestId"
7423
+ [attr.aria-label]="openAriaLabel || title"
7424
+ [matTooltip]="openTooltip || title"
7425
+ (click)="open.emit()"
7426
+ >
7427
+ <span class="praxis-ai-assistant-dock__orb" aria-hidden="true">
7428
+ <mat-icon>{{ resolvedIcon() }}</mat-icon>
7429
+ </span>
7430
+ <span class="praxis-ai-assistant-dock__copy">
7431
+ <strong>{{ title }}</strong>
7432
+ <span>{{ summary }}</span>
7433
+ </span>
7434
+ <span class="praxis-ai-assistant-dock__badge">{{ resolvedBadge() }}</span>
7435
+ </button>
7436
+ </section>
7437
+ `, 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"] }]
7438
+ }], propDecorators: { title: [{
7439
+ type: Input
7440
+ }], summary: [{
7441
+ type: Input
7442
+ }], badge: [{
7443
+ type: Input
7444
+ }], icon: [{
7445
+ type: Input
7446
+ }], state: [{
7447
+ type: Input
7448
+ }], tone: [{
7449
+ type: Input
7450
+ }], ariaLabel: [{
7451
+ type: Input
7452
+ }], openAriaLabel: [{
7453
+ type: Input
7454
+ }], openTooltip: [{
7455
+ type: Input
7456
+ }], testId: [{
7457
+ type: Input
7458
+ }], openTestId: [{
7459
+ type: Input
7460
+ }], open: [{
7461
+ type: Output
7462
+ }] } });
7463
+
7464
+ class PraxisAiAssistantSessionHostComponent {
7465
+ registry = inject(PraxisAssistantSessionRegistryService);
7466
+ testId = 'praxis-ai-assistant-session-host';
7467
+ dockTestIdPrefix = 'praxis-ai-assistant-session';
7468
+ ariaLabel = 'Active Praxis assistant sessions';
7469
+ openAriaLabel = 'Open assistant session';
7470
+ openTooltip = 'Open assistant session';
7471
+ ownerType = null;
7472
+ ownerId = null;
7473
+ visibility = 'minimized';
7474
+ sessionOpen = new EventEmitter();
7475
+ visibleSessions() {
7476
+ return this.registry.sessions().filter((session) => {
7477
+ if (this.visibility !== 'all' && session.visibility !== this.visibility)
7478
+ return false;
7479
+ if (this.ownerType && session.ownerType !== this.ownerType)
7480
+ return false;
7481
+ if (this.ownerId && session.ownerId !== this.ownerId)
7482
+ return false;
7483
+ return true;
7484
+ });
7485
+ }
7486
+ openSession(sessionId) {
7487
+ const session = this.registry.openSession(sessionId);
7488
+ if (session) {
7489
+ this.sessionOpen.emit(session);
7490
+ }
7491
+ }
7492
+ sessionAriaLabel(session) {
7493
+ const summary = session.summary ? `: ${session.summary}` : '';
7494
+ return `${session.title}${summary}`;
7495
+ }
7496
+ dockTestId(session, first) {
7497
+ return first ? `${this.dockTestIdPrefix}-dock` : `${this.dockTestIdPrefix}-dock-${this.safeId(session.id)}`;
7498
+ }
7499
+ dockOpenTestId(session, first) {
7500
+ return first ? `${this.dockTestIdPrefix}-dock-open` : `${this.dockTestIdPrefix}-dock-open-${this.safeId(session.id)}`;
7501
+ }
7502
+ trackSession(_index, session) {
7503
+ return session.id;
7504
+ }
7505
+ safeId(value) {
7506
+ return value.replace(/[^a-zA-Z0-9_-]+/g, '-');
7507
+ }
7508
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantSessionHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7509
+ 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: `
7510
+ <section
7511
+ *ngIf="visibleSessions().length"
7512
+ class="praxis-ai-assistant-session-host"
7513
+ [attr.data-testid]="testId"
7514
+ [attr.aria-label]="ariaLabel"
7515
+ >
7516
+ <praxis-ai-assistant-dock
7517
+ *ngFor="let session of visibleSessions(); let first = first; trackBy: trackSession"
7518
+ [title]="session.title"
7519
+ [summary]="session.summary"
7520
+ [badge]="session.badge"
7521
+ [icon]="session.icon"
7522
+ [state]="session.state"
7523
+ [ariaLabel]="sessionAriaLabel(session)"
7524
+ [openAriaLabel]="openAriaLabel"
7525
+ [openTooltip]="openTooltip"
7526
+ [testId]="dockTestId(session, first)"
7527
+ [openTestId]="dockOpenTestId(session, first)"
7528
+ (open)="openSession(session.id)"
7529
+ />
7530
+ </section>
7531
+ `, 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 });
7532
+ }
7533
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantSessionHostComponent, decorators: [{
7534
+ type: Component,
7535
+ args: [{ selector: 'praxis-ai-assistant-session-host', standalone: true, imports: [CommonModule, PraxisAiAssistantDockComponent], template: `
7536
+ <section
7537
+ *ngIf="visibleSessions().length"
7538
+ class="praxis-ai-assistant-session-host"
7539
+ [attr.data-testid]="testId"
7540
+ [attr.aria-label]="ariaLabel"
7541
+ >
7542
+ <praxis-ai-assistant-dock
7543
+ *ngFor="let session of visibleSessions(); let first = first; trackBy: trackSession"
7544
+ [title]="session.title"
7545
+ [summary]="session.summary"
7546
+ [badge]="session.badge"
7547
+ [icon]="session.icon"
7548
+ [state]="session.state"
7549
+ [ariaLabel]="sessionAriaLabel(session)"
7550
+ [openAriaLabel]="openAriaLabel"
7551
+ [openTooltip]="openTooltip"
7552
+ [testId]="dockTestId(session, first)"
7553
+ [openTestId]="dockOpenTestId(session, first)"
7554
+ (open)="openSession(session.id)"
7555
+ />
7556
+ </section>
7557
+ `, 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"] }]
7558
+ }], propDecorators: { testId: [{
7559
+ type: Input
7560
+ }], dockTestIdPrefix: [{
7561
+ type: Input
7562
+ }], ariaLabel: [{
7563
+ type: Input
7564
+ }], openAriaLabel: [{
7565
+ type: Input
7566
+ }], openTooltip: [{
7567
+ type: Input
7568
+ }], ownerType: [{
7569
+ type: Input
7570
+ }], ownerId: [{
7571
+ type: Input
7572
+ }], visibility: [{
7573
+ type: Input
7574
+ }], sessionOpen: [{
7575
+ type: Output
7576
+ }] } });
7577
+
6371
7578
  class StreamingFeedbackComponent {
6372
7579
  title = 'Processando...';
6373
7580
  displayText = '';
@@ -7165,4 +8372,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7165
8372
  * Generated bundle index. Do not edit.
7166
8373
  */
7167
8374
 
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 };
8375
+ 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, 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, normalizePraxisAssistantAttachmentSummary, normalizePraxisAssistantContextSnapshot, sanitizePraxisAssistantText, toPraxisAssistantConversationMessages };