@praxisui/ai 8.0.0-beta.20 → 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.
@@ -60,6 +60,317 @@ 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
+
63
374
  function toPraxisAssistantConversationMessages(messages, limit = 12) {
64
375
  return messages
65
376
  .filter((message) => !!message.text?.trim() && isPraxisAssistantConversationMessageRole(message.role))
@@ -1590,7 +1901,10 @@ class PraxisAssistantTurnController {
1590
1901
  const current = this.stateSubject.value;
1591
1902
  const effectiveAction = this.resolveSubmitAction(action, current);
1592
1903
  const clientTurnId = this.createId('turn');
1593
- 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);
1594
1908
  this.patchState({
1595
1909
  state: 'processing',
1596
1910
  phase: 'contextualize',
@@ -1889,6 +2203,7 @@ class PraxisAssistantSessionRegistryService {
1889
2203
  state: normalized.state,
1890
2204
  visibility: normalized.visibility,
1891
2205
  contextItems: normalized.contextItems,
2206
+ contextSnapshot: normalized.contextSnapshot,
1892
2207
  badge: normalized.badge,
1893
2208
  icon: normalized.icon,
1894
2209
  createdAt: existing?.createdAt ?? now,
@@ -1900,18 +2215,47 @@ class PraxisAssistantSessionRegistryService {
1900
2215
  ], next.visibility === 'active' ? next.id : null));
1901
2216
  return next;
1902
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
+ }
1903
2235
  openSession(sessionId) {
1904
2236
  return this.setVisibility(sessionId, 'active');
1905
2237
  }
2238
+ openContextSession(identity) {
2239
+ return this.openSession(this.resolveSessionId(identity));
2240
+ }
1906
2241
  minimizeSession(sessionId) {
1907
2242
  return this.setVisibility(sessionId, 'minimized');
1908
2243
  }
2244
+ minimizeContextSession(identity) {
2245
+ return this.minimizeSession(this.resolveSessionId(identity));
2246
+ }
1909
2247
  removeSession(sessionId) {
1910
2248
  this.sessionsState.update((sessions) => sessions.filter((session) => session.id !== sessionId));
1911
2249
  }
2250
+ removeContextSession(identity) {
2251
+ this.removeSession(this.resolveSessionId(identity));
2252
+ }
1912
2253
  getSession(sessionId) {
1913
2254
  return this.sessionsState().find((session) => session.id === sessionId) ?? null;
1914
2255
  }
2256
+ getContextSession(identity) {
2257
+ return this.getSession(this.resolveSessionId(identity));
2258
+ }
1915
2259
  clear() {
1916
2260
  this.sessionsState.set([]);
1917
2261
  }
@@ -1921,6 +2265,13 @@ class PraxisAssistantSessionRegistryService {
1921
2265
  return null;
1922
2266
  return this.upsertSession({ ...session, visibility });
1923
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
+ }
1924
2275
  normalizeDescriptor(descriptor) {
1925
2276
  const id = descriptor.id?.trim();
1926
2277
  const ownerId = descriptor.ownerId?.trim();
@@ -1928,6 +2279,15 @@ class PraxisAssistantSessionRegistryService {
1928
2279
  if (!id || !ownerId || !ownerType) {
1929
2280
  throw new Error('Praxis assistant sessions require id, ownerId and ownerType.');
1930
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);
1931
2291
  return {
1932
2292
  id,
1933
2293
  ownerId,
@@ -1937,12 +2297,29 @@ class PraxisAssistantSessionRegistryService {
1937
2297
  mode: descriptor.mode || 'chat',
1938
2298
  state: descriptor.state || 'idle',
1939
2299
  visibility: descriptor.visibility || 'minimized',
1940
- contextItems: [...(descriptor.contextItems ?? [])],
2300
+ contextItems,
2301
+ contextSnapshot,
1941
2302
  badge: descriptor.badge?.trim() || '',
1942
2303
  icon: descriptor.icon?.trim() || '',
1943
2304
  updatedAt: descriptor.updatedAt?.trim() || null,
1944
2305
  };
1945
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
+ }
1946
2323
  sortSessions(sessions, activeSessionId) {
1947
2324
  return sessions
1948
2325
  .map((session) => activeSessionId && session.id !== activeSessionId
@@ -6103,16 +6480,18 @@ const DEFAULT_LAYOUT = {
6103
6480
  const DEFAULT_LABELS = {
6104
6481
  title: 'Assistente de IA',
6105
6482
  subtitle: 'Revise o resultado gerado antes de aplicar.',
6106
- close: 'Fechar',
6483
+ close: 'Minimizar assistente',
6107
6484
  prompt: 'Prompt',
6108
6485
  promptPlaceholder: 'Descreva o que você quer criar ou alterar.',
6109
6486
  emptyConversation: 'Diga o que você quer criar ou alterar.',
6110
- submit: 'Pré-visualizar',
6111
- apply: 'Aplicar',
6487
+ submit: 'Interpretar pedido',
6488
+ apply: 'Aplicar ajuste',
6112
6489
  conversationAria: 'Conversa com IA',
6113
6490
  quickRepliesAria: 'Respostas rápidas',
6491
+ quickReplyDetails: 'Detalhes técnicos',
6114
6492
  dragHandleAria: 'Mover assistente de IA',
6115
6493
  resizeHandleAria: 'Redimensionar assistente de IA',
6494
+ resetLayout: 'Restaurar posição do assistente',
6116
6495
  contextAria: 'Contexto ativo',
6117
6496
  attachmentsAria: 'Contexto anexado',
6118
6497
  attach: 'Anexar',
@@ -6149,10 +6528,15 @@ class PraxisAiAssistantShellComponent {
6149
6528
  panelTestId = '';
6150
6529
  submitTestId = '';
6151
6530
  applyTestId = '';
6531
+ primaryAction = null;
6532
+ secondaryActions = [];
6533
+ governanceActions = [];
6152
6534
  busy = false;
6153
6535
  canSubmit = true;
6154
6536
  canApply = false;
6155
6537
  submitOnEnter = true;
6538
+ showAttachAction = true;
6539
+ enablePastedAttachments = true;
6156
6540
  enableFileAttachments = false;
6157
6541
  attachmentAccept = '';
6158
6542
  attachmentMultiple = true;
@@ -6165,6 +6549,9 @@ class PraxisAiAssistantShellComponent {
6165
6549
  promptChange = new EventEmitter();
6166
6550
  submitPrompt = new EventEmitter();
6167
6551
  apply = new EventEmitter();
6552
+ retryTurn = new EventEmitter();
6553
+ cancelTurn = new EventEmitter();
6554
+ shellAction = new EventEmitter();
6168
6555
  close = new EventEmitter();
6169
6556
  attach = new EventEmitter();
6170
6557
  attachmentsPasted = new EventEmitter();
@@ -6181,7 +6568,9 @@ class PraxisAiAssistantShellComponent {
6181
6568
  currentPrompt = '';
6182
6569
  resolvedLabels = DEFAULT_LABELS;
6183
6570
  currentLayout = { ...DEFAULT_LAYOUT };
6571
+ resizeHandles = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];
6184
6572
  pointerSession = null;
6573
+ baselineLayout = null;
6185
6574
  ownedPreviewUrls = new Set();
6186
6575
  onWindowPointerMove = (event) => this.handlePointerMove(event);
6187
6576
  onWindowPointerUp = (event) => this.finishPointerSession(event);
@@ -6197,6 +6586,7 @@ class PraxisAiAssistantShellComponent {
6197
6586
  }
6198
6587
  if (changes['layout']) {
6199
6588
  this.currentLayout = this.normalizeLayout(this.layout);
6589
+ this.baselineLayout ??= { ...this.currentLayout };
6200
6590
  }
6201
6591
  if (changes['messages']) {
6202
6592
  this.scheduleConversationScroll();
@@ -6227,6 +6617,9 @@ class PraxisAiAssistantShellComponent {
6227
6617
  this.onSubmit();
6228
6618
  }
6229
6619
  onPromptPaste(event) {
6620
+ if (!this.enablePastedAttachments) {
6621
+ return;
6622
+ }
6230
6623
  const files = Array.from(event.clipboardData?.files ?? [])
6231
6624
  .filter((file) => file.type.startsWith('image/'));
6232
6625
  if (this.busy || !files.length) {
@@ -6261,6 +6654,163 @@ class PraxisAiAssistantShellComponent {
6261
6654
  return;
6262
6655
  this.apply.emit();
6263
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
+ }
6264
6814
  onQuickReply(reply) {
6265
6815
  if (this.busy)
6266
6816
  return;
@@ -6271,6 +6821,21 @@ class PraxisAiAssistantShellComponent {
6271
6821
  const description = reply.description?.trim();
6272
6822
  return description ? `${label}. ${description}` : label;
6273
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
+ }
6274
6839
  getQuickReplyTone(reply) {
6275
6840
  const tone = (reply.tone || reply.kind || 'neutral').toLowerCase();
6276
6841
  switch (tone) {
@@ -6293,6 +6858,44 @@ class PraxisAiAssistantShellComponent {
6293
6858
  return 'neutral';
6294
6859
  }
6295
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
+ }
6296
6899
  onRemoveAttachment(attachment) {
6297
6900
  if (this.busy)
6298
6901
  return;
@@ -6310,6 +6913,26 @@ class PraxisAiAssistantShellComponent {
6310
6913
  this.resendMessage.emit(message);
6311
6914
  }
6312
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
+ }
6313
6936
  getModeLabel() {
6314
6937
  switch (this.mode) {
6315
6938
  case 'config':
@@ -6353,10 +6976,13 @@ class PraxisAiAssistantShellComponent {
6353
6976
  return;
6354
6977
  this.startPointerSession('drag', event);
6355
6978
  }
6356
- startResize(event) {
6979
+ startResize(direction, event) {
6357
6980
  if (!this.resizable || event.button !== 0)
6358
6981
  return;
6359
- this.startPointerSession('resize', event);
6982
+ this.startPointerSession('resize', event, direction);
6983
+ }
6984
+ trackResizeHandle(_index, direction) {
6985
+ return direction;
6360
6986
  }
6361
6987
  trackMessage(_index, message) {
6362
6988
  return message.id;
@@ -6373,15 +6999,16 @@ class PraxisAiAssistantShellComponent {
6373
6999
  trackAttachment(_index, attachment) {
6374
7000
  return attachment.id;
6375
7001
  }
6376
- startPointerSession(mode, event) {
7002
+ startPointerSession(mode, event, resizeDirection) {
6377
7003
  event.preventDefault();
6378
7004
  event.stopPropagation();
6379
7005
  const panel = this.panel?.nativeElement;
6380
7006
  if (!panel)
6381
7007
  return;
6382
- const bounds = this.resolveBounds(panel);
7008
+ const bounds = this.resolveViewportBounds();
6383
7009
  this.pointerSession = {
6384
7010
  mode,
7011
+ resizeDirection,
6385
7012
  pointerId: event.pointerId,
6386
7013
  startX: event.clientX,
6387
7014
  startY: event.clientY,
@@ -6411,15 +7038,40 @@ class PraxisAiAssistantShellComponent {
6411
7038
  left: session.startLayout.left + deltaX,
6412
7039
  top: session.startLayout.top + deltaY,
6413
7040
  }
6414
- : {
6415
- ...session.startLayout,
6416
- width: session.startLayout.width + deltaX,
6417
- height: session.startLayout.height + deltaY,
6418
- };
7041
+ : this.resizeLayout(session, deltaX, deltaY);
6419
7042
  this.currentLayout = this.clampLayout(next, session.boundsWidth, session.boundsHeight);
6420
7043
  this.layoutChange.emit(this.currentLayout);
6421
7044
  this.cdr.markForCheck();
6422
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
+ }
6423
7075
  finishPointerSession(event) {
6424
7076
  if (this.pointerSession && event.pointerId === this.pointerSession.pointerId) {
6425
7077
  try {
@@ -6437,10 +7089,9 @@ class PraxisAiAssistantShellComponent {
6437
7089
  window.removeEventListener('pointerup', this.onWindowPointerUp);
6438
7090
  window.removeEventListener('pointercancel', this.onWindowPointerUp);
6439
7091
  }
6440
- resolveBounds(panel) {
6441
- const hostBounds = panel.parentElement?.getBoundingClientRect();
6442
- const width = hostBounds?.width || (typeof window !== 'undefined' ? window.innerWidth : 1024);
6443
- 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;
6444
7095
  return {
6445
7096
  width: Math.max(width, this.minWidth + this.margin * 2),
6446
7097
  height: Math.max(height, this.minHeight + this.margin * 2),
@@ -6533,7 +7184,7 @@ class PraxisAiAssistantShellComponent {
6533
7184
  this.ownedPreviewUrls.delete(previewUrl);
6534
7185
  }
6535
7186
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6536
- 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 [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 </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{--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-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__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.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 });
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 });
6537
7188
  }
6538
7189
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantShellComponent, decorators: [{
6539
7190
  type: Component,
@@ -6544,7 +7195,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6544
7195
  MatIconModule,
6545
7196
  MatProgressSpinnerModule,
6546
7197
  MatTooltipModule,
6547
- ], 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 [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 </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{--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-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__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"] }]
6548
7199
  }], propDecorators: { labels: [{
6549
7200
  type: Input
6550
7201
  }], mode: [{
@@ -6573,6 +7224,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6573
7224
  type: Input
6574
7225
  }], applyTestId: [{
6575
7226
  type: Input
7227
+ }], primaryAction: [{
7228
+ type: Input
7229
+ }], secondaryActions: [{
7230
+ type: Input
7231
+ }], governanceActions: [{
7232
+ type: Input
6576
7233
  }], busy: [{
6577
7234
  type: Input
6578
7235
  }], canSubmit: [{
@@ -6581,6 +7238,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6581
7238
  type: Input
6582
7239
  }], submitOnEnter: [{
6583
7240
  type: Input
7241
+ }], showAttachAction: [{
7242
+ type: Input
7243
+ }], enablePastedAttachments: [{
7244
+ type: Input
6584
7245
  }], enableFileAttachments: [{
6585
7246
  type: Input
6586
7247
  }], attachmentAccept: [{
@@ -6605,6 +7266,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6605
7266
  type: Output
6606
7267
  }], apply: [{
6607
7268
  type: Output
7269
+ }], retryTurn: [{
7270
+ type: Output
7271
+ }], cancelTurn: [{
7272
+ type: Output
7273
+ }], shellAction: [{
7274
+ type: Output
6608
7275
  }], close: [{
6609
7276
  type: Output
6610
7277
  }], attach: [{
@@ -6633,6 +7300,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6633
7300
  args: ['conversation']
6634
7301
  }] } });
6635
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
+
6636
7320
  class PraxisAiAssistantDockComponent {
6637
7321
  title = 'Praxis copilot active';
6638
7322
  summary = 'Conversation preserved. Continue where you stopped.';
@@ -7688,4 +8372,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7688
8372
  * Generated bundle index. Do not edit.
7689
8373
  */
7690
8374
 
7691
- 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, PraxisAiAssistantDockComponent, PraxisAiAssistantSessionHostComponent, PraxisAiAssistantShellComponent, PraxisAiService, PraxisAssistantSessionRegistryService, PraxisAssistantTurnController, PraxisAssistantTurnOrchestratorService, SchemaMinifierService, toPraxisAssistantConversationMessages };
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 };