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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Component, Injectable, InjectionToken, Optional, Inject, inject, ChangeDetectorRef, ElementRef, ViewChild, Input, ChangeDetectionStrategy, EventEmitter, Output } from '@angular/core';
2
+ import { Component, Injectable, InjectionToken, Optional, Inject, inject, signal, computed, ChangeDetectorRef, ElementRef, ViewChild, Input, ChangeDetectionStrategy, EventEmitter, Output } from '@angular/core';
3
3
  import { SchemaType, GoogleGenerativeAI } from '@google/generative-ai';
4
4
  import { throwError, from, Observable, of, BehaviorSubject, tap, map as map$1, isObservable, firstValueFrom } from 'rxjs';
5
5
  import { map, catchError, filter, take } from 'rxjs/operators';
@@ -60,13 +60,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
60
60
  * Models for Praxis AI (Centralized)
61
61
  */
62
62
 
63
+ function toPraxisAssistantConversationMessages(messages, limit = 12) {
64
+ return messages
65
+ .filter((message) => !!message.text?.trim() && isPraxisAssistantConversationMessageRole(message.role))
66
+ .slice(-Math.max(0, limit))
67
+ .map((message) => ({
68
+ id: message.id,
69
+ role: toPraxisAssistantConversationMessageRole(message.role),
70
+ text: message.text,
71
+ }));
72
+ }
73
+ function isPraxisAssistantConversationMessageRole(role) {
74
+ return role === 'user' || role === 'assistant' || role === 'system' || role === 'status';
75
+ }
76
+ function toPraxisAssistantConversationMessageRole(role) {
77
+ switch (role) {
78
+ case 'user':
79
+ case 'assistant':
80
+ case 'system':
81
+ return role;
82
+ case 'status':
83
+ return 'assistant';
84
+ default:
85
+ return 'assistant';
86
+ }
87
+ }
88
+
63
89
  /**
64
90
  * Generated from praxis-config-starter/docs/ai/contracts/praxis-ai-api-contract-v1.1.openapi.yaml.
65
91
  * Do not edit manually. Run praxis-config-starter/tools/contracts/generate-ai-contract-bindings.js.
66
92
  */
67
93
  const AI_CONTRACT_VERSION = 'v1.1';
68
- const AI_CONTRACT_SCHEMA_HASH = '922e6d48637e64b403562d6d7cb833ed4942ffb0b452ec3573255871f4ec8739';
94
+ const AI_CONTRACT_SCHEMA_HASH = '42af43aec91777def176def87d2f41d30c8dbc8abb6c21dc38b5e181a1a51f4f';
69
95
  const AI_STREAM_EVENT_SCHEMA_VERSION = 'v1';
96
+ const AI_DOMAIN_CATALOG_CONTEXT_HINT_SCHEMA_VERSION = 'praxis.ai.context-hints.domain-catalog/v0.2';
70
97
  const AI_STREAM_EVENT_TYPES = ['status', 'thought.step', 'heartbeat', 'result', 'error', 'cancelled'];
71
98
 
72
99
  /**
@@ -526,15 +553,6 @@ class PraxisAiService {
526
553
  if (!this.isGeminiProvider()) {
527
554
  return throwError(() => new Error('LLM provider not supported in browser mode.'));
528
555
  }
529
- const intentOnly = this.extractUserIntent(prompt);
530
- // Only check for mock triggers in the specific user intent, not the full prompt template
531
- if (intentOnly) {
532
- const mockPatch = this.getMockPatch(intentOnly);
533
- if (mockPatch) {
534
- console.warn('PraxisAI: Using mock response for prompt intent:', intentOnly);
535
- return of(mockPatch);
536
- }
537
- }
538
556
  if (!this.genAI)
539
557
  return throwError(() => new Error('API Key not configured'));
540
558
  const generationConfig = {
@@ -580,50 +598,6 @@ class PraxisAiService {
580
598
  isMockMode() {
581
599
  return !this.genAI;
582
600
  }
583
- // -------- Helpers --------
584
- extractUserIntent(prompt) {
585
- // Tenta capturar o conteúdo entre aspas após os marcadores conhecidos
586
- const patterns = [
587
- /ENTRADA DO USUÁRIO:\s*"([^"]+)"/i,
588
- /PEDIDO DO USUÁRIO:\s*"([^"]+)"/i,
589
- /USER INTENT:\s*"([^"]+)"/i
590
- ];
591
- for (const regex of patterns) {
592
- const match = prompt.match(regex);
593
- if (match && match[1]) {
594
- return match[1].trim();
595
- }
596
- }
597
- return null;
598
- }
599
- getMockPatch(prompt) {
600
- if (!prompt)
601
- return null;
602
- const p = prompt.toLowerCase();
603
- // ... rest of the function
604
- if (p.includes('compacta') && p.includes('bordas')) {
605
- return {
606
- patch: { appearance: { density: 'compact', borders: { showRowBorders: false } } },
607
- explanation: 'Ajustei a tabela para o modo compacto e removi as bordas das linhas (MOCK).',
608
- };
609
- }
610
- if (p.includes('seleção múltipla') && p.includes('filtros no cabeçalho')) {
611
- return {
612
- patch: {
613
- behavior: {
614
- selection: { enabled: true, type: 'multiple', mode: 'checkbox' },
615
- filtering: { enabled: true, columnFilters: { enabled: true, position: 'header' } },
616
- sorting: { enabled: true },
617
- },
618
- },
619
- explanation: 'Ativei seleção múltipla, filtros no cabeçalho e ordenação (MOCK Power User).',
620
- };
621
- }
622
- if (p.includes('paginação') || p.includes('paginacao')) {
623
- return { patch: { behavior: { pagination: { enabled: false } } }, explanation: 'Desativei a paginação da tabela (MOCK).' };
624
- }
625
- return null;
626
- }
627
601
  listModels(apiKey) {
628
602
  if (!this.isGeminiProvider()) {
629
603
  return throwError(() => new Error('Model listing only available for Gemini.'));
@@ -839,6 +813,94 @@ class AiBackendApiService {
839
813
  withCredentials: true,
840
814
  });
841
815
  }
816
+ startAgenticAuthoringTurnStream(request) {
817
+ return this.http.post(`${this.baseUrl}/authoring/turn/stream/start`, request, {
818
+ headers: this.buildHeaders(),
819
+ withCredentials: true,
820
+ });
821
+ }
822
+ connectAgenticAuthoringTurnStream(streamId, lastEventId, accessToken) {
823
+ const queryParams = [];
824
+ if (lastEventId?.trim()) {
825
+ queryParams.push(`lastEventId=${encodeURIComponent(lastEventId.trim())}`);
826
+ }
827
+ if (accessToken?.trim()) {
828
+ queryParams.push(`accessToken=${encodeURIComponent(accessToken.trim())}`);
829
+ }
830
+ const query = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
831
+ const streamPath = `${this.baseUrl}/authoring/turn/stream/${encodeURIComponent(streamId)}`;
832
+ const endpoint = `${streamPath}${query}`;
833
+ const probeEndpoint = `${streamPath}/probe${accessToken?.trim()
834
+ ? `?accessToken=${encodeURIComponent(accessToken.trim())}`
835
+ : ''}`;
836
+ let source = null;
837
+ const events$ = new Observable((subscriber) => {
838
+ let disposed = false;
839
+ const openStream = async () => {
840
+ if (typeof EventSource === 'undefined') {
841
+ subscriber.error(new AiPatchStreamConnectionError('unsupported', 'EventSource is not supported in this environment.'));
842
+ return;
843
+ }
844
+ const probeStatus = await this.probePatchStreamEndpoint(probeEndpoint);
845
+ if (disposed) {
846
+ return;
847
+ }
848
+ if (typeof probeStatus === 'number' && probeStatus >= 400) {
849
+ subscriber.error(new AiPatchStreamConnectionError('http_status', `Agentic authoring stream probe failed with status ${probeStatus}.`, probeStatus));
850
+ return;
851
+ }
852
+ source = new EventSource(endpoint, { withCredentials: true });
853
+ source.onmessage = (messageEvent) => {
854
+ try {
855
+ const parsed = this.parsePatchStreamEnvelope(messageEvent.data);
856
+ subscriber.next(parsed);
857
+ }
858
+ catch (error) {
859
+ if (error instanceof AiPatchStreamConnectionError) {
860
+ subscriber.error(error);
861
+ return;
862
+ }
863
+ subscriber.error(new AiPatchStreamConnectionError('parse', 'Failed to parse agentic authoring stream event payload.'));
864
+ }
865
+ };
866
+ source.onerror = () => {
867
+ if (!source) {
868
+ subscriber.error(new AiPatchStreamConnectionError('transport', 'Agentic authoring stream connection error.'));
869
+ return;
870
+ }
871
+ if (source.readyState === EventSource.CLOSED) {
872
+ subscriber.error(new AiPatchStreamConnectionError('transport', 'Agentic authoring stream connection closed unexpectedly.'));
873
+ }
874
+ };
875
+ };
876
+ void openStream();
877
+ return () => {
878
+ disposed = true;
879
+ if (source) {
880
+ source.close();
881
+ source = null;
882
+ }
883
+ };
884
+ });
885
+ return {
886
+ events$,
887
+ close: () => {
888
+ if (source) {
889
+ source.close();
890
+ source = null;
891
+ }
892
+ },
893
+ };
894
+ }
895
+ cancelAgenticAuthoringTurnStream(streamId, accessToken) {
896
+ const endpoint = `${this.baseUrl}/authoring/turn/stream/${encodeURIComponent(streamId)}/cancel${accessToken?.trim()
897
+ ? `?accessToken=${encodeURIComponent(accessToken.trim())}`
898
+ : ''}`;
899
+ return this.http.post(endpoint, {}, {
900
+ headers: this.buildHeaders(),
901
+ withCredentials: true,
902
+ });
903
+ }
842
904
  listModels(request) {
843
905
  return this.http.post(`${this.baseUrl}/providers/models`, request, { headers: this.buildHeaders() });
844
906
  }
@@ -855,6 +917,24 @@ class AiBackendApiService {
855
917
  const params = new HttpParams({ fromObject: { componentType } });
856
918
  return this.http.get(`${this.contextUrl}/${componentId}`, { headers: this.buildHeaders(), params });
857
919
  }
920
+ getAgenticAuthoringManifest(componentId) {
921
+ return this.http.get(`${this.authoringManifestUrl(componentId)}`, { headers: this.buildHeaders() });
922
+ }
923
+ listAgenticAuthoringManifestTargets(componentId) {
924
+ return this.http.get(`${this.authoringManifestUrl(componentId)}/editable-targets`, { headers: this.buildHeaders() });
925
+ }
926
+ listAgenticAuthoringManifestOperations(componentId) {
927
+ return this.http.get(`${this.authoringManifestUrl(componentId)}/operations`, { headers: this.buildHeaders() });
928
+ }
929
+ resolveAgenticAuthoringManifestTarget(componentId, request) {
930
+ return this.http.post(`${this.authoringManifestUrl(componentId)}/resolve-target`, request, { headers: this.buildHeaders() });
931
+ }
932
+ validateAgenticAuthoringManifestPlan(componentId, request) {
933
+ return this.http.post(`${this.authoringManifestUrl(componentId)}/validate-plan`, request, { headers: this.buildHeaders() });
934
+ }
935
+ compileAgenticAuthoringManifestPatch(componentId, request) {
936
+ return this.http.post(`${this.authoringManifestUrl(componentId)}/compile-patch`, request, { headers: this.buildHeaders() });
937
+ }
858
938
  loadGlobalAiConfig() {
859
939
  const snapshot = this.globalConfigStore?.getAiConfigSnapshot?.();
860
940
  if (snapshot !== undefined) {
@@ -895,6 +975,9 @@ class AiBackendApiService {
895
975
  }
896
976
  return AI_INTENT_CONTRACT_SCHEMA_HASH;
897
977
  }
978
+ authoringManifestUrl(componentId) {
979
+ return `${this.baseUrl}/authoring/manifests/${encodeURIComponent(componentId)}`;
980
+ }
898
981
  resolveHeaderMap(extra) {
899
982
  const allowLocalIdentityFallback = !!this.storageOpts?.allowLocalIdentityFallback;
900
983
  const defaults = this.storageOpts?.defaultHeaders
@@ -1077,7 +1160,7 @@ class AiResponseValidatorService {
1077
1160
  code: 'MISSING_RULE_NAME'
1078
1161
  });
1079
1162
  }
1080
- if (!response.targetType || !['field', 'section', 'action', 'row', 'column'].includes(response.targetType)) {
1163
+ if (!response.targetType || !['field', 'section', 'action', 'row', 'column', 'visualBlock'].includes(response.targetType)) {
1081
1164
  errors.push({
1082
1165
  field: 'targetType',
1083
1166
  message: 'targetType inválido',
@@ -1211,6 +1294,10 @@ class AiResponseValidatorService {
1211
1294
  return;
1212
1295
  }
1213
1296
  const args = Array.isArray(rawArgs) ? rawArgs : [rawArgs];
1297
+ const arityIssue = this.validateJsonLogicOperatorArity(operator, args.length);
1298
+ if (arityIssue) {
1299
+ issues.push({ message: arityIssue });
1300
+ }
1214
1301
  args.forEach((arg) => this.walkJsonLogicNode(arg, false, issues));
1215
1302
  return;
1216
1303
  }
@@ -1247,6 +1334,49 @@ class AiResponseValidatorService {
1247
1334
  'max',
1248
1335
  ].includes(operator);
1249
1336
  }
1337
+ validateJsonLogicOperatorArity(operator, argumentCount) {
1338
+ const exactArity = {
1339
+ '==': 2,
1340
+ '===': 2,
1341
+ '!=': 2,
1342
+ '!==': 2,
1343
+ '>': 2,
1344
+ '>=': 2,
1345
+ '<': 2,
1346
+ '<=': 2,
1347
+ '!': 1,
1348
+ '!!': 1,
1349
+ in: 2,
1350
+ contains: 2,
1351
+ startsWith: 2,
1352
+ endsWith: 2,
1353
+ '-': 2,
1354
+ '/': 2,
1355
+ '%': 2,
1356
+ };
1357
+ const expected = exactArity[operator];
1358
+ if (expected !== undefined && argumentCount !== expected) {
1359
+ return `Operator ${operator} requires exactly ${expected} argument(s).`;
1360
+ }
1361
+ if ((operator === 'and' || operator === 'or') && argumentCount < 2) {
1362
+ return `Operator ${operator} requires at least 2 arguments.`;
1363
+ }
1364
+ if (operator === 'if' && argumentCount < 3) {
1365
+ return 'Operator if requires at least 3 arguments.';
1366
+ }
1367
+ if ((operator === 'cat'
1368
+ || operator === '+'
1369
+ || operator === '*'
1370
+ || operator === 'min'
1371
+ || operator === 'max')
1372
+ && argumentCount < 1) {
1373
+ return `Operator ${operator} requires at least 1 argument.`;
1374
+ }
1375
+ if (operator === 'substr' && (argumentCount < 2 || argumentCount > 3)) {
1376
+ return 'Operator substr requires 2 or 3 arguments.';
1377
+ }
1378
+ return null;
1379
+ }
1250
1380
  collectVarPaths(value, collector) {
1251
1381
  if (Array.isArray(value)) {
1252
1382
  value.forEach((item) => this.collectVarPaths(item, collector));
@@ -1542,7 +1672,8 @@ class PraxisAssistantTurnController {
1542
1672
  if (!handler) {
1543
1673
  return of(this.snapshot());
1544
1674
  }
1545
- return this.toObservable(handler.call(this.flow, request)).pipe(tap((result) => this.applyResult(result)), map$1(() => this.snapshot()));
1675
+ const flowResult = handler.call(this.flow, request);
1676
+ return this.toObservable(flowResult).pipe(tap((result) => this.applyResult(result)), map$1(() => this.snapshot()));
1546
1677
  }
1547
1678
  buildRequest(partial) {
1548
1679
  const current = this.stateSubject.value;
@@ -1653,7 +1784,6 @@ class PraxisAssistantTurnController {
1653
1784
  }
1654
1785
  return {
1655
1786
  ...action,
1656
- kind: 'clarify',
1657
1787
  contextHints: {
1658
1788
  ...(action.contextHints ?? {}),
1659
1789
  pendingClarification: current.pendingClarification,
@@ -1739,6 +1869,95 @@ class PraxisAssistantTurnController {
1739
1869
  }
1740
1870
  }
1741
1871
 
1872
+ class PraxisAssistantSessionRegistryService {
1873
+ sessionsState = signal([], ...(ngDevMode ? [{ debugName: "sessionsState" }] : []));
1874
+ sessions = this.sessionsState.asReadonly();
1875
+ activeSession = computed(() => this.sessions().find((session) => session.visibility === 'active') ?? null, ...(ngDevMode ? [{ debugName: "activeSession" }] : []));
1876
+ minimizedSessions = computed(() => this.sessions().filter((session) => session.visibility === 'minimized'), ...(ngDevMode ? [{ debugName: "minimizedSessions" }] : []));
1877
+ upsertSession(descriptor) {
1878
+ const normalized = this.normalizeDescriptor(descriptor);
1879
+ const existing = this.sessionsState().find((session) => session.id === normalized.id);
1880
+ const now = normalized.updatedAt || new Date().toISOString();
1881
+ const next = {
1882
+ ...existing,
1883
+ id: normalized.id,
1884
+ ownerId: normalized.ownerId,
1885
+ ownerType: normalized.ownerType,
1886
+ title: normalized.title,
1887
+ summary: normalized.summary,
1888
+ mode: normalized.mode,
1889
+ state: normalized.state,
1890
+ visibility: normalized.visibility,
1891
+ contextItems: normalized.contextItems,
1892
+ badge: normalized.badge,
1893
+ icon: normalized.icon,
1894
+ createdAt: existing?.createdAt ?? now,
1895
+ updatedAt: now,
1896
+ };
1897
+ this.sessionsState.update((sessions) => this.sortSessions([
1898
+ ...sessions.filter((session) => session.id !== next.id),
1899
+ next,
1900
+ ], next.visibility === 'active' ? next.id : null));
1901
+ return next;
1902
+ }
1903
+ openSession(sessionId) {
1904
+ return this.setVisibility(sessionId, 'active');
1905
+ }
1906
+ minimizeSession(sessionId) {
1907
+ return this.setVisibility(sessionId, 'minimized');
1908
+ }
1909
+ removeSession(sessionId) {
1910
+ this.sessionsState.update((sessions) => sessions.filter((session) => session.id !== sessionId));
1911
+ }
1912
+ getSession(sessionId) {
1913
+ return this.sessionsState().find((session) => session.id === sessionId) ?? null;
1914
+ }
1915
+ clear() {
1916
+ this.sessionsState.set([]);
1917
+ }
1918
+ setVisibility(sessionId, visibility) {
1919
+ const session = this.getSession(sessionId);
1920
+ if (!session)
1921
+ return null;
1922
+ return this.upsertSession({ ...session, visibility });
1923
+ }
1924
+ normalizeDescriptor(descriptor) {
1925
+ const id = descriptor.id?.trim();
1926
+ const ownerId = descriptor.ownerId?.trim();
1927
+ const ownerType = descriptor.ownerType?.trim();
1928
+ if (!id || !ownerId || !ownerType) {
1929
+ throw new Error('Praxis assistant sessions require id, ownerId and ownerType.');
1930
+ }
1931
+ return {
1932
+ id,
1933
+ ownerId,
1934
+ ownerType,
1935
+ title: descriptor.title?.trim() || 'Praxis assistant',
1936
+ summary: descriptor.summary?.trim() || '',
1937
+ mode: descriptor.mode || 'chat',
1938
+ state: descriptor.state || 'idle',
1939
+ visibility: descriptor.visibility || 'minimized',
1940
+ contextItems: [...(descriptor.contextItems ?? [])],
1941
+ badge: descriptor.badge?.trim() || '',
1942
+ icon: descriptor.icon?.trim() || '',
1943
+ updatedAt: descriptor.updatedAt?.trim() || null,
1944
+ };
1945
+ }
1946
+ sortSessions(sessions, activeSessionId) {
1947
+ return sessions
1948
+ .map((session) => activeSessionId && session.id !== activeSessionId
1949
+ ? { ...session, visibility: 'minimized' }
1950
+ : session)
1951
+ .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
1952
+ }
1953
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAssistantSessionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1954
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAssistantSessionRegistryService, providedIn: 'root' });
1955
+ }
1956
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAssistantSessionRegistryService, decorators: [{
1957
+ type: Injectable,
1958
+ args: [{ providedIn: 'root' }]
1959
+ }] });
1960
+
1742
1961
  const HISTORY_INDEX_PREFIX = 'praxis.ai.history.index';
1743
1962
  const HISTORY_SESSION_PREFIX = 'praxis.ai.history.session';
1744
1963
  const DEFAULT_SESSION_TITLE = 'Nova sessão';
@@ -2436,7 +2655,7 @@ class PraxisAiAssistantComponent {
2436
2655
  : undefined;
2437
2656
  const normalizedRuntimeState = runtimeState !== undefined ? this.toAiJsonObject(runtimeState) : undefined;
2438
2657
  const normalizedSuggestedPatch = suggestion?.patch ? this.toAiJsonObject(suggestion.patch) : undefined;
2439
- const normalizedContextHints = this.toClarificationContextHints(mergedContextHints);
2658
+ const normalizedContextHints = this.enrichDomainCatalogAuthoringHints(mergedContextHints);
2440
2659
  const patchRequest = {
2441
2660
  componentId,
2442
2661
  componentType,
@@ -5138,15 +5357,15 @@ class PraxisAiAssistantComponent {
5138
5357
  .filter((value) => value.length > 0);
5139
5358
  }
5140
5359
  resolveBadgeContextHints(contextHints) {
5141
- if (!contextHints || typeof contextHints !== 'object')
5360
+ const candidate = this.toAiJsonObject(contextHints);
5361
+ if (!Object.keys(candidate).length)
5142
5362
  return null;
5143
- const badge = this.asRecord(contextHints['badge']);
5363
+ const badge = this.asRecord(candidate['badge']);
5144
5364
  if (badge)
5145
5365
  return this.toAiJsonObject(badge);
5146
- const candidate = contextHints;
5147
5366
  const hasBadgeKeys = ['field', 'values', 'valueColorMap', 'palette', 'inferredType', 'explicitType']
5148
5367
  .some((key) => Object.prototype.hasOwnProperty.call(candidate, key));
5149
- return hasBadgeKeys ? candidate : null;
5368
+ return hasBadgeKeys ? this.toAiJsonObject(candidate) : null;
5150
5369
  }
5151
5370
  hasBadgeValues(badgeHints) {
5152
5371
  if (!badgeHints)
@@ -5190,6 +5409,25 @@ class PraxisAiAssistantComponent {
5190
5409
  }
5191
5410
  return this.toClarificationContextHints(merged) ?? null;
5192
5411
  }
5412
+ enrichDomainCatalogAuthoringHints(value) {
5413
+ const normalized = this.toClarificationContextHints(value);
5414
+ if (!normalized)
5415
+ return undefined;
5416
+ const domainCatalog = this.asRecord(normalized['domainCatalog']);
5417
+ const recommendedAuthoringFlow = domainCatalog?.['recommendedAuthoringFlow'];
5418
+ if (typeof recommendedAuthoringFlow !== 'string' || !recommendedAuthoringFlow.trim()) {
5419
+ return normalized;
5420
+ }
5421
+ const flowId = recommendedAuthoringFlow.trim();
5422
+ const enriched = this.toAiJsonObject(normalized);
5423
+ enriched['authoringFlow'] = this.toAiJsonObject({
5424
+ flowId,
5425
+ source: 'domainCatalog.recommendedAuthoringFlow',
5426
+ reviewRequired: true,
5427
+ materializeOnlyAfterReview: true,
5428
+ });
5429
+ return this.toClarificationContextHints(enriched);
5430
+ }
5193
5431
  setResourcePathHint(resourcePath) {
5194
5432
  const raw = (resourcePath || '').trim();
5195
5433
  const normalized = this.normalizeResourcePath(raw);
@@ -6028,6 +6266,33 @@ class PraxisAiAssistantShellComponent {
6028
6266
  return;
6029
6267
  this.quickReply.emit(reply);
6030
6268
  }
6269
+ getQuickReplyAriaLabel(reply) {
6270
+ const label = reply.label?.trim() ?? '';
6271
+ const description = reply.description?.trim();
6272
+ return description ? `${label}. ${description}` : label;
6273
+ }
6274
+ getQuickReplyTone(reply) {
6275
+ const tone = (reply.tone || reply.kind || 'neutral').toLowerCase();
6276
+ switch (tone) {
6277
+ case 'primary':
6278
+ case 'analytics':
6279
+ case 'resource':
6280
+ case 'warning':
6281
+ case 'neutral':
6282
+ case 'success':
6283
+ case 'danger':
6284
+ return tone;
6285
+ case 'confirm':
6286
+ return 'primary';
6287
+ case 'suggestion':
6288
+ return 'resource';
6289
+ case 'revise':
6290
+ return 'warning';
6291
+ case 'cancel':
6292
+ default:
6293
+ return 'neutral';
6294
+ }
6295
+ }
6031
6296
  onRemoveAttachment(attachment) {
6032
6297
  if (this.busy)
6033
6298
  return;
@@ -6268,7 +6533,7 @@ class PraxisAiAssistantShellComponent {
6268
6533
  this.ownedPreviewUrls.delete(previewUrl);
6269
6534
  }
6270
6535
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6271
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisAiAssistantShellComponent, isStandalone: true, selector: "praxis-ai-assistant-shell", inputs: { labels: "labels", mode: "mode", state: "state", contextItems: "contextItems", attachments: "attachments", messages: "messages", quickReplies: "quickReplies", prompt: "prompt", statusText: "statusText", errorText: "errorText", testIdPrefix: "testIdPrefix", panelTestId: "panelTestId", submitTestId: "submitTestId", applyTestId: "applyTestId", busy: "busy", canSubmit: "canSubmit", canApply: "canApply", submitOnEnter: "submitOnEnter", enableFileAttachments: "enableFileAttachments", attachmentAccept: "attachmentAccept", attachmentMultiple: "attachmentMultiple", draggable: "draggable", resizable: "resizable", minWidth: "minWidth", minHeight: "minHeight", margin: "margin", layout: "layout" }, outputs: { promptChange: "promptChange", submitPrompt: "submitPrompt", apply: "apply", close: "close", attach: "attach", attachmentsPasted: "attachmentsPasted", attachmentsSelected: "attachmentsSelected", removeAttachment: "removeAttachment", messageAction: "messageAction", editMessage: "editMessage", resendMessage: "resendMessage", quickReply: "quickReply", layoutChange: "layoutChange" }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, static: true }, { propertyName: "conversation", first: true, predicate: ["conversation"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<section\n #panel\n class=\"praxis-ai-assistant-shell\"\n role=\"dialog\"\n [attr.aria-label]=\"resolvedLabels.title\"\n [attr.aria-busy]=\"busy ? 'true' : null\"\n [style.left.px]=\"currentLayout.left\"\n [style.top.px]=\"currentLayout.top\"\n [style.width.px]=\"currentLayout.width\"\n [style.height.px]=\"currentLayout.height\"\n [attr.data-testid]=\"panelTestId || testIdPrefix\"\n>\n <header\n class=\"praxis-ai-assistant-shell__header\"\n [attr.data-testid]=\"testIdPrefix + '-drag-handle'\"\n [attr.aria-label]=\"resolvedLabels.dragHandleAria\"\n (pointerdown)=\"startDrag($event)\"\n >\n <div class=\"praxis-ai-assistant-shell__identity\" aria-hidden=\"true\">\n <mat-icon>auto_awesome</mat-icon>\n </div>\n <div class=\"praxis-ai-assistant-shell__title-group\">\n <strong>{{ resolvedLabels.title }}</strong>\n <p *ngIf=\"resolvedLabels.subtitle\">{{ resolvedLabels.subtitle }}</p>\n </div>\n <div class=\"praxis-ai-assistant-shell__badges\" aria-hidden=\"true\">\n <span class=\"praxis-ai-assistant-shell__badge\">{{ getModeLabel() }}</span>\n <span class=\"praxis-ai-assistant-shell__badge\" [class.praxis-ai-assistant-shell__badge--error]=\"state === 'error'\">\n {{ getStateLabel() }}\n </span>\n </div>\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"resolvedLabels.close\"\n [attr.aria-label]=\"resolvedLabels.close\"\n (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"close.emit()\"\n [attr.data-testid]=\"testIdPrefix + '-close'\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </header>\n\n <div class=\"praxis-ai-assistant-shell__body\">\n <div\n *ngIf=\"contextItems.length\"\n class=\"praxis-ai-assistant-shell__context\"\n [attr.aria-label]=\"resolvedLabels.contextAria\"\n [attr.data-testid]=\"testIdPrefix + '-context'\"\n >\n <span\n *ngFor=\"let item of contextItems; trackBy: trackContextItem\"\n class=\"praxis-ai-assistant-shell__context-item\"\n [attr.data-testid]=\"testIdPrefix + '-context-' + item.id\"\n >\n <mat-icon *ngIf=\"item.icon\" aria-hidden=\"true\">{{ item.icon }}</mat-icon>\n <span class=\"praxis-ai-assistant-shell__context-label\">{{ item.label }}</span>\n <span *ngIf=\"item.value\" class=\"praxis-ai-assistant-shell__context-value\">{{ item.value }}</span>\n </span>\n </div>\n\n <div\n #conversation\n class=\"praxis-ai-assistant-shell__conversation\"\n [attr.data-testid]=\"testIdPrefix + '-conversation'\"\n [attr.aria-label]=\"resolvedLabels.conversationAria\"\n >\n <article\n *ngIf=\"!messages.length\"\n class=\"praxis-ai-assistant-shell__message praxis-ai-assistant-shell__message--assistant\"\n [attr.data-testid]=\"testIdPrefix + '-message-assistant-empty'\"\n >\n {{ resolvedLabels.emptyConversation }}\n </article>\n <article\n *ngFor=\"let message of messages; trackBy: trackMessage\"\n class=\"praxis-ai-assistant-shell__message\"\n [class.praxis-ai-assistant-shell__message--user]=\"message.role === 'user'\"\n [class.praxis-ai-assistant-shell__message--assistant]=\"message.role === 'assistant'\"\n [class.praxis-ai-assistant-shell__message--status]=\"message.role === 'status'\"\n [class.praxis-ai-assistant-shell__message--error]=\"message.role === 'error'\"\n [attr.data-testid]=\"testIdPrefix + '-message-' + message.role\"\n >\n {{ message.text }}\n <div\n *ngIf=\"message.actions?.length || message.editable || message.resendable\"\n class=\"praxis-ai-assistant-shell__message-actions\"\n >\n <button\n *ngIf=\"message.editable\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy\"\n [attr.data-testid]=\"testIdPrefix + '-message-edit-' + message.id\"\n (click)=\"onMessageAction(message, { id: 'edit', label: resolvedLabels.editMessage, kind: 'edit' })\"\n >\n {{ resolvedLabels.editMessage }}\n </button>\n <button\n *ngIf=\"message.resendable\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy\"\n [attr.data-testid]=\"testIdPrefix + '-message-resend-' + message.id\"\n (click)=\"onMessageAction(message, { id: 'resend', label: resolvedLabels.resendMessage, kind: 'resend' })\"\n >\n {{ resolvedLabels.resendMessage }}\n </button>\n <button\n *ngFor=\"let action of message.actions || []; trackBy: trackMessageAction\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy || action.disabled\"\n [attr.data-testid]=\"testIdPrefix + '-message-action-' + message.id + '-' + action.id\"\n (click)=\"onMessageAction(message, action)\"\n >\n {{ action.label }}\n </button>\n </div>\n </article>\n </div>\n\n <div\n *ngIf=\"attachments.length\"\n class=\"praxis-ai-assistant-shell__attachments\"\n [attr.aria-label]=\"resolvedLabels.attachmentsAria\"\n [attr.data-testid]=\"testIdPrefix + '-attachments'\"\n >\n <div\n *ngFor=\"let attachment of attachments; trackBy: trackAttachment\"\n class=\"praxis-ai-assistant-shell__attachment\"\n [class.praxis-ai-assistant-shell__attachment--error]=\"attachment.status === 'error'\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-' + attachment.id\"\n >\n <img\n *ngIf=\"attachment.previewUrl && attachment.kind === 'image'\"\n class=\"praxis-ai-assistant-shell__attachment-preview\"\n [src]=\"attachment.previewUrl\"\n [alt]=\"attachment.name\"\n >\n <span class=\"praxis-ai-assistant-shell__attachment-name\">{{ attachment.name }}</span>\n <span class=\"praxis-ai-assistant-shell__attachment-kind\">{{ attachment.kind }}</span>\n <button\n mat-icon-button\n type=\"button\"\n [disabled]=\"busy\"\n [matTooltip]=\"resolvedLabels.removeAttachment\"\n [attr.aria-label]=\"resolvedLabels.removeAttachment + ': ' + attachment.name\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-remove-' + attachment.id\"\n (click)=\"onRemoveAttachment(attachment)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n\n <div\n *ngIf=\"quickReplies.length\"\n class=\"praxis-ai-assistant-shell__quick-replies\"\n [attr.data-testid]=\"testIdPrefix + '-quick-replies'\"\n [attr.aria-label]=\"resolvedLabels.quickRepliesAria\"\n >\n <button\n *ngFor=\"let reply of quickReplies; trackBy: trackQuickReply\"\n mat-stroked-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__quick-reply\"\n [attr.data-testid]=\"testIdPrefix + '-quick-reply-' + (reply.id || reply.kind)\"\n [disabled]=\"busy\"\n (click)=\"onQuickReply(reply)\"\n >\n {{ reply.label }}\n </button>\n </div>\n <p\n *ngIf=\"statusText\"\n class=\"praxis-ai-assistant-shell__status\"\n [attr.data-testid]=\"testIdPrefix + '-status'\"\n >\n {{ statusText }}\n </p>\n <p\n *ngIf=\"errorText\"\n class=\"praxis-ai-assistant-shell__error\"\n [attr.data-testid]=\"testIdPrefix + '-error'\"\n >\n {{ errorText }}\n </p>\n </div>\n\n <footer class=\"praxis-ai-assistant-shell__footer\">\n <label class=\"praxis-ai-assistant-shell__label\" for=\"praxis-ai-assistant-shell-prompt\">\n {{ resolvedLabels.prompt }}\n </label>\n <div class=\"praxis-ai-assistant-shell__composer\">\n <textarea\n id=\"praxis-ai-assistant-shell-prompt\"\n class=\"praxis-ai-assistant-shell__prompt\"\n [attr.data-testid]=\"testIdPrefix + '-prompt'\"\n [placeholder]=\"resolvedLabels.promptPlaceholder\"\n [ngModel]=\"currentPrompt\"\n [disabled]=\"busy\"\n (ngModelChange)=\"onPromptInput($event)\"\n (keydown)=\"onPromptKeydown($event)\"\n (paste)=\"onPromptPaste($event)\"\n ></textarea>\n <div class=\"praxis-ai-assistant-shell__composer-actions\">\n <input\n #attachmentInput\n type=\"file\"\n hidden\n [attr.accept]=\"attachmentAccept || null\"\n [attr.multiple]=\"attachmentMultiple ? '' : null\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-input'\"\n (change)=\"onAttachmentFilesSelected($event)\"\n >\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"busy\"\n (click)=\"onAttachClick(attachmentInput)\"\n [attr.data-testid]=\"testIdPrefix + '-attach'\"\n >\n <mat-icon>attach_file</mat-icon>\n {{ resolvedLabels.attach }}\n </button>\n <button\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n [disabled]=\"busy || !canSubmit || !currentPrompt.trim()\"\n (click)=\"onSubmit()\"\n [attr.data-testid]=\"submitTestId || (testIdPrefix + '-submit')\"\n >\n {{ resolvedLabels.submit }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"busy || !canApply\"\n (click)=\"onApply()\"\n [attr.data-testid]=\"applyTestId || (testIdPrefix + '-apply')\"\n >\n {{ resolvedLabels.apply }}\n </button>\n <mat-spinner *ngIf=\"busy\" diameter=\"20\" [attr.data-testid]=\"testIdPrefix + '-spinner'\"></mat-spinner>\n </div>\n </div>\n </footer>\n\n <button\n *ngIf=\"resizable\"\n type=\"button\"\n class=\"praxis-ai-assistant-shell__resize-handle\"\n [attr.data-testid]=\"testIdPrefix + '-resize-handle'\"\n [attr.aria-label]=\"resolvedLabels.resizeHandleAria\"\n [matTooltip]=\"resolvedLabels.resizeHandleAria\"\n (pointerdown)=\"startResize($event)\"\n ></button>\n</section>\n", styles: [":host{display:block}.praxis-ai-assistant-shell{position:absolute;box-sizing:border-box;min-width:360px;min-height:360px;display:flex;flex-direction:column;overflow:hidden;color:var(--md-sys-color-on-surface, #f8fafc);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface, #0f172a);box-shadow:0 24px 60px #0006;z-index:10}.praxis-ai-assistant-shell__header{flex:0 0 auto;display:flex;align-items:center;gap:10px;padding:12px;border-color:color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 72%,transparent);background:radial-gradient(circle at top left,rgba(96,165,250,.22),transparent 34%),var(--md-sys-color-surface-container, #172033)}.praxis-ai-assistant-shell__header{justify-content:flex-start;border-bottom:1px solid;cursor:move;touch-action:none}.praxis-ai-assistant-shell__identity{flex:0 0 auto;display:grid;place-items:center;width:34px;height:34px;border-radius:8px;background:var(--md-sys-color-primary, #60a5fa);color:var(--md-sys-color-on-primary, #020617);box-shadow:0 8px 20px #60a5fa4d}.praxis-ai-assistant-shell__identity mat-icon{width:20px;height:20px;font-size:20px}.praxis-ai-assistant-shell__title-group{min-width:0;flex:1 1 auto;display:grid;gap:3px}.praxis-ai-assistant-shell__badges{flex:0 1 auto;display:flex;align-items:center;justify-content:flex-end;gap:6px;min-width:0;flex-wrap:wrap}.praxis-ai-assistant-shell__badge{display:inline-flex;align-items:center;min-height:22px;max-width:140px;padding:2px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 80%,transparent);border-radius:8px;color:var(--md-sys-color-on-surface-variant, #cbd5e1);background:var(--md-sys-color-surface-container-high, #263244);font-size:11px;line-height:1.2;overflow-wrap:anywhere}.praxis-ai-assistant-shell__badge--error{color:var(--md-sys-color-error, #ff6b6b);border-color:color-mix(in srgb,var(--md-sys-color-error, #ff6b6b) 60%,transparent)}.praxis-ai-assistant-shell__title-group strong,.praxis-ai-assistant-shell__title-group p{min-width:0;margin:0;overflow-wrap:anywhere}.praxis-ai-assistant-shell__title-group p{color:var(--md-sys-color-on-surface-variant, #cbd5e1);font-size:12px;line-height:1.35}.praxis-ai-assistant-shell__body{min-height:0;flex:1 1 auto;display:flex;flex-direction:column;gap:12px;padding:14px 14px 8px;overflow:auto;background:linear-gradient(180deg,var(--md-sys-color-surface-container-low, #111827),var(--md-sys-color-surface, #0f172a))}.praxis-ai-assistant-shell__context,.praxis-ai-assistant-shell__attachments{flex:0 0 auto;display:flex;align-items:center;gap:8px;overflow-x:auto}.praxis-ai-assistant-shell__context-item{flex:0 0 auto;display:inline-flex;align-items:center;gap:5px;max-width:240px;padding:5px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface-container-high, #263244);font-size:12px;line-height:1.25}.praxis-ai-assistant-shell__context-item mat-icon{width:16px;height:16px;font-size:16px}.praxis-ai-assistant-shell__context-label,.praxis-ai-assistant-shell__context-value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-shell__context-label{color:var(--md-sys-color-on-surface, #f8fafc)}.praxis-ai-assistant-shell__context-value{color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.praxis-ai-assistant-shell__label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.praxis-ai-assistant-shell__prompt{box-sizing:border-box;width:100%;min-height:56px;max-height:128px;resize:none;border:0;padding:12px;color:var(--md-sys-color-on-surface, #f8fafc);background:transparent;font:inherit;line-height:1.45;outline:none}.praxis-ai-assistant-shell__conversation{min-height:0;flex:1 1 auto;display:flex;flex-direction:column;gap:8px;overflow:auto;padding:2px}.praxis-ai-assistant-shell__message{max-width:86%;align-self:flex-start;padding:9px 11px;border-radius:8px;background:var(--md-sys-color-surface-container-high, #263244);color:var(--md-sys-color-on-surface, #f8fafc);font-size:13px;line-height:1.35;overflow-wrap:anywhere}.praxis-ai-assistant-shell__message-actions{display:flex;align-items:center;gap:4px;flex-wrap:wrap;margin-top:8px}.praxis-ai-assistant-shell__message-action{min-height:28px;padding:0 8px;border-radius:8px;font-size:12px}.praxis-ai-assistant-shell__message--assistant{border-bottom-left-radius:2px}.praxis-ai-assistant-shell__message--user{align-self:flex-end;border-bottom-right-radius:2px;background:var(--md-sys-color-primary-container, #17375f);color:var(--md-sys-color-on-primary-container, #f8fafc)}.praxis-ai-assistant-shell__message--status{color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.praxis-ai-assistant-shell__message--error,.praxis-ai-assistant-shell__error{color:var(--md-sys-color-error, #ff6b6b)}.praxis-ai-assistant-shell__quick-replies{display:flex;flex-wrap:wrap;gap:8px}.praxis-ai-assistant-shell__attachment{flex:0 0 auto;display:inline-flex;align-items:center;gap:7px;max-width:260px;min-height:34px;padding:4px 4px 4px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface-container-high, #263244)}.praxis-ai-assistant-shell__attachment--error{border-color:color-mix(in srgb,var(--md-sys-color-error, #ff6b6b) 60%,transparent)}.praxis-ai-assistant-shell__attachment-preview{flex:0 0 auto;width:28px;height:28px;border-radius:6px;object-fit:cover}.praxis-ai-assistant-shell__attachment-name,.praxis-ai-assistant-shell__attachment-kind{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.praxis-ai-assistant-shell__attachment-name{color:var(--md-sys-color-on-surface, #f8fafc)}.praxis-ai-assistant-shell__attachment-kind{color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.praxis-ai-assistant-shell__quick-reply{max-width:100%;border-radius:999px}.praxis-ai-assistant-shell__status,.praxis-ai-assistant-shell__error{margin:0;font-size:13px;line-height:1.35;overflow-wrap:anywhere}.praxis-ai-assistant-shell__footer{flex:0 0 auto;padding:10px 12px 12px;border-top:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 72%,transparent);background:var(--md-sys-color-surface-container-low, #111827)}.praxis-ai-assistant-shell__composer{display:grid;gap:8px;border:1px solid var(--md-sys-color-outline-variant, #334155);border-radius:8px;background:var(--md-sys-color-surface-container-lowest, #020617);box-shadow:inset 0 1px #ffffff0a}.praxis-ai-assistant-shell__composer:focus-within{border-color:var(--md-sys-color-primary, #60a5fa);box-shadow:0 0 0 2px #60a5fa29}.praxis-ai-assistant-shell__composer-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:0 8px 8px;flex-wrap:wrap}.praxis-ai-assistant-shell__resize-handle{position:absolute;right:0;bottom:0;width:22px;height:22px;border:0;background:transparent;cursor:nwse-resize;touch-action:none}.praxis-ai-assistant-shell__resize-handle:after{content:\"\";position:absolute;right:6px;bottom:6px;width:10px;height:10px;border-right:2px solid var(--md-sys-color-outline, #94a3b8);border-bottom:2px solid var(--md-sys-color-outline, #94a3b8)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i8.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i9.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
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 });
6272
6537
  }
6273
6538
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantShellComponent, decorators: [{
6274
6539
  type: Component,
@@ -6279,7 +6544,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6279
6544
  MatIconModule,
6280
6545
  MatProgressSpinnerModule,
6281
6546
  MatTooltipModule,
6282
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section\n #panel\n class=\"praxis-ai-assistant-shell\"\n role=\"dialog\"\n [attr.aria-label]=\"resolvedLabels.title\"\n [attr.aria-busy]=\"busy ? 'true' : null\"\n [style.left.px]=\"currentLayout.left\"\n [style.top.px]=\"currentLayout.top\"\n [style.width.px]=\"currentLayout.width\"\n [style.height.px]=\"currentLayout.height\"\n [attr.data-testid]=\"panelTestId || testIdPrefix\"\n>\n <header\n class=\"praxis-ai-assistant-shell__header\"\n [attr.data-testid]=\"testIdPrefix + '-drag-handle'\"\n [attr.aria-label]=\"resolvedLabels.dragHandleAria\"\n (pointerdown)=\"startDrag($event)\"\n >\n <div class=\"praxis-ai-assistant-shell__identity\" aria-hidden=\"true\">\n <mat-icon>auto_awesome</mat-icon>\n </div>\n <div class=\"praxis-ai-assistant-shell__title-group\">\n <strong>{{ resolvedLabels.title }}</strong>\n <p *ngIf=\"resolvedLabels.subtitle\">{{ resolvedLabels.subtitle }}</p>\n </div>\n <div class=\"praxis-ai-assistant-shell__badges\" aria-hidden=\"true\">\n <span class=\"praxis-ai-assistant-shell__badge\">{{ getModeLabel() }}</span>\n <span class=\"praxis-ai-assistant-shell__badge\" [class.praxis-ai-assistant-shell__badge--error]=\"state === 'error'\">\n {{ getStateLabel() }}\n </span>\n </div>\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"resolvedLabels.close\"\n [attr.aria-label]=\"resolvedLabels.close\"\n (pointerdown)=\"$event.stopPropagation()\"\n (click)=\"close.emit()\"\n [attr.data-testid]=\"testIdPrefix + '-close'\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </header>\n\n <div class=\"praxis-ai-assistant-shell__body\">\n <div\n *ngIf=\"contextItems.length\"\n class=\"praxis-ai-assistant-shell__context\"\n [attr.aria-label]=\"resolvedLabels.contextAria\"\n [attr.data-testid]=\"testIdPrefix + '-context'\"\n >\n <span\n *ngFor=\"let item of contextItems; trackBy: trackContextItem\"\n class=\"praxis-ai-assistant-shell__context-item\"\n [attr.data-testid]=\"testIdPrefix + '-context-' + item.id\"\n >\n <mat-icon *ngIf=\"item.icon\" aria-hidden=\"true\">{{ item.icon }}</mat-icon>\n <span class=\"praxis-ai-assistant-shell__context-label\">{{ item.label }}</span>\n <span *ngIf=\"item.value\" class=\"praxis-ai-assistant-shell__context-value\">{{ item.value }}</span>\n </span>\n </div>\n\n <div\n #conversation\n class=\"praxis-ai-assistant-shell__conversation\"\n [attr.data-testid]=\"testIdPrefix + '-conversation'\"\n [attr.aria-label]=\"resolvedLabels.conversationAria\"\n >\n <article\n *ngIf=\"!messages.length\"\n class=\"praxis-ai-assistant-shell__message praxis-ai-assistant-shell__message--assistant\"\n [attr.data-testid]=\"testIdPrefix + '-message-assistant-empty'\"\n >\n {{ resolvedLabels.emptyConversation }}\n </article>\n <article\n *ngFor=\"let message of messages; trackBy: trackMessage\"\n class=\"praxis-ai-assistant-shell__message\"\n [class.praxis-ai-assistant-shell__message--user]=\"message.role === 'user'\"\n [class.praxis-ai-assistant-shell__message--assistant]=\"message.role === 'assistant'\"\n [class.praxis-ai-assistant-shell__message--status]=\"message.role === 'status'\"\n [class.praxis-ai-assistant-shell__message--error]=\"message.role === 'error'\"\n [attr.data-testid]=\"testIdPrefix + '-message-' + message.role\"\n >\n {{ message.text }}\n <div\n *ngIf=\"message.actions?.length || message.editable || message.resendable\"\n class=\"praxis-ai-assistant-shell__message-actions\"\n >\n <button\n *ngIf=\"message.editable\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy\"\n [attr.data-testid]=\"testIdPrefix + '-message-edit-' + message.id\"\n (click)=\"onMessageAction(message, { id: 'edit', label: resolvedLabels.editMessage, kind: 'edit' })\"\n >\n {{ resolvedLabels.editMessage }}\n </button>\n <button\n *ngIf=\"message.resendable\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy\"\n [attr.data-testid]=\"testIdPrefix + '-message-resend-' + message.id\"\n (click)=\"onMessageAction(message, { id: 'resend', label: resolvedLabels.resendMessage, kind: 'resend' })\"\n >\n {{ resolvedLabels.resendMessage }}\n </button>\n <button\n *ngFor=\"let action of message.actions || []; trackBy: trackMessageAction\"\n mat-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__message-action\"\n [disabled]=\"busy || action.disabled\"\n [attr.data-testid]=\"testIdPrefix + '-message-action-' + message.id + '-' + action.id\"\n (click)=\"onMessageAction(message, action)\"\n >\n {{ action.label }}\n </button>\n </div>\n </article>\n </div>\n\n <div\n *ngIf=\"attachments.length\"\n class=\"praxis-ai-assistant-shell__attachments\"\n [attr.aria-label]=\"resolvedLabels.attachmentsAria\"\n [attr.data-testid]=\"testIdPrefix + '-attachments'\"\n >\n <div\n *ngFor=\"let attachment of attachments; trackBy: trackAttachment\"\n class=\"praxis-ai-assistant-shell__attachment\"\n [class.praxis-ai-assistant-shell__attachment--error]=\"attachment.status === 'error'\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-' + attachment.id\"\n >\n <img\n *ngIf=\"attachment.previewUrl && attachment.kind === 'image'\"\n class=\"praxis-ai-assistant-shell__attachment-preview\"\n [src]=\"attachment.previewUrl\"\n [alt]=\"attachment.name\"\n >\n <span class=\"praxis-ai-assistant-shell__attachment-name\">{{ attachment.name }}</span>\n <span class=\"praxis-ai-assistant-shell__attachment-kind\">{{ attachment.kind }}</span>\n <button\n mat-icon-button\n type=\"button\"\n [disabled]=\"busy\"\n [matTooltip]=\"resolvedLabels.removeAttachment\"\n [attr.aria-label]=\"resolvedLabels.removeAttachment + ': ' + attachment.name\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-remove-' + attachment.id\"\n (click)=\"onRemoveAttachment(attachment)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n\n <div\n *ngIf=\"quickReplies.length\"\n class=\"praxis-ai-assistant-shell__quick-replies\"\n [attr.data-testid]=\"testIdPrefix + '-quick-replies'\"\n [attr.aria-label]=\"resolvedLabels.quickRepliesAria\"\n >\n <button\n *ngFor=\"let reply of quickReplies; trackBy: trackQuickReply\"\n mat-stroked-button\n type=\"button\"\n class=\"praxis-ai-assistant-shell__quick-reply\"\n [attr.data-testid]=\"testIdPrefix + '-quick-reply-' + (reply.id || reply.kind)\"\n [disabled]=\"busy\"\n (click)=\"onQuickReply(reply)\"\n >\n {{ reply.label }}\n </button>\n </div>\n <p\n *ngIf=\"statusText\"\n class=\"praxis-ai-assistant-shell__status\"\n [attr.data-testid]=\"testIdPrefix + '-status'\"\n >\n {{ statusText }}\n </p>\n <p\n *ngIf=\"errorText\"\n class=\"praxis-ai-assistant-shell__error\"\n [attr.data-testid]=\"testIdPrefix + '-error'\"\n >\n {{ errorText }}\n </p>\n </div>\n\n <footer class=\"praxis-ai-assistant-shell__footer\">\n <label class=\"praxis-ai-assistant-shell__label\" for=\"praxis-ai-assistant-shell-prompt\">\n {{ resolvedLabels.prompt }}\n </label>\n <div class=\"praxis-ai-assistant-shell__composer\">\n <textarea\n id=\"praxis-ai-assistant-shell-prompt\"\n class=\"praxis-ai-assistant-shell__prompt\"\n [attr.data-testid]=\"testIdPrefix + '-prompt'\"\n [placeholder]=\"resolvedLabels.promptPlaceholder\"\n [ngModel]=\"currentPrompt\"\n [disabled]=\"busy\"\n (ngModelChange)=\"onPromptInput($event)\"\n (keydown)=\"onPromptKeydown($event)\"\n (paste)=\"onPromptPaste($event)\"\n ></textarea>\n <div class=\"praxis-ai-assistant-shell__composer-actions\">\n <input\n #attachmentInput\n type=\"file\"\n hidden\n [attr.accept]=\"attachmentAccept || null\"\n [attr.multiple]=\"attachmentMultiple ? '' : null\"\n [attr.data-testid]=\"testIdPrefix + '-attachment-input'\"\n (change)=\"onAttachmentFilesSelected($event)\"\n >\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"busy\"\n (click)=\"onAttachClick(attachmentInput)\"\n [attr.data-testid]=\"testIdPrefix + '-attach'\"\n >\n <mat-icon>attach_file</mat-icon>\n {{ resolvedLabels.attach }}\n </button>\n <button\n mat-flat-button\n color=\"primary\"\n type=\"button\"\n [disabled]=\"busy || !canSubmit || !currentPrompt.trim()\"\n (click)=\"onSubmit()\"\n [attr.data-testid]=\"submitTestId || (testIdPrefix + '-submit')\"\n >\n {{ resolvedLabels.submit }}\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"busy || !canApply\"\n (click)=\"onApply()\"\n [attr.data-testid]=\"applyTestId || (testIdPrefix + '-apply')\"\n >\n {{ resolvedLabels.apply }}\n </button>\n <mat-spinner *ngIf=\"busy\" diameter=\"20\" [attr.data-testid]=\"testIdPrefix + '-spinner'\"></mat-spinner>\n </div>\n </div>\n </footer>\n\n <button\n *ngIf=\"resizable\"\n type=\"button\"\n class=\"praxis-ai-assistant-shell__resize-handle\"\n [attr.data-testid]=\"testIdPrefix + '-resize-handle'\"\n [attr.aria-label]=\"resolvedLabels.resizeHandleAria\"\n [matTooltip]=\"resolvedLabels.resizeHandleAria\"\n (pointerdown)=\"startResize($event)\"\n ></button>\n</section>\n", styles: [":host{display:block}.praxis-ai-assistant-shell{position:absolute;box-sizing:border-box;min-width:360px;min-height:360px;display:flex;flex-direction:column;overflow:hidden;color:var(--md-sys-color-on-surface, #f8fafc);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface, #0f172a);box-shadow:0 24px 60px #0006;z-index:10}.praxis-ai-assistant-shell__header{flex:0 0 auto;display:flex;align-items:center;gap:10px;padding:12px;border-color:color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 72%,transparent);background:radial-gradient(circle at top left,rgba(96,165,250,.22),transparent 34%),var(--md-sys-color-surface-container, #172033)}.praxis-ai-assistant-shell__header{justify-content:flex-start;border-bottom:1px solid;cursor:move;touch-action:none}.praxis-ai-assistant-shell__identity{flex:0 0 auto;display:grid;place-items:center;width:34px;height:34px;border-radius:8px;background:var(--md-sys-color-primary, #60a5fa);color:var(--md-sys-color-on-primary, #020617);box-shadow:0 8px 20px #60a5fa4d}.praxis-ai-assistant-shell__identity mat-icon{width:20px;height:20px;font-size:20px}.praxis-ai-assistant-shell__title-group{min-width:0;flex:1 1 auto;display:grid;gap:3px}.praxis-ai-assistant-shell__badges{flex:0 1 auto;display:flex;align-items:center;justify-content:flex-end;gap:6px;min-width:0;flex-wrap:wrap}.praxis-ai-assistant-shell__badge{display:inline-flex;align-items:center;min-height:22px;max-width:140px;padding:2px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 80%,transparent);border-radius:8px;color:var(--md-sys-color-on-surface-variant, #cbd5e1);background:var(--md-sys-color-surface-container-high, #263244);font-size:11px;line-height:1.2;overflow-wrap:anywhere}.praxis-ai-assistant-shell__badge--error{color:var(--md-sys-color-error, #ff6b6b);border-color:color-mix(in srgb,var(--md-sys-color-error, #ff6b6b) 60%,transparent)}.praxis-ai-assistant-shell__title-group strong,.praxis-ai-assistant-shell__title-group p{min-width:0;margin:0;overflow-wrap:anywhere}.praxis-ai-assistant-shell__title-group p{color:var(--md-sys-color-on-surface-variant, #cbd5e1);font-size:12px;line-height:1.35}.praxis-ai-assistant-shell__body{min-height:0;flex:1 1 auto;display:flex;flex-direction:column;gap:12px;padding:14px 14px 8px;overflow:auto;background:linear-gradient(180deg,var(--md-sys-color-surface-container-low, #111827),var(--md-sys-color-surface, #0f172a))}.praxis-ai-assistant-shell__context,.praxis-ai-assistant-shell__attachments{flex:0 0 auto;display:flex;align-items:center;gap:8px;overflow-x:auto}.praxis-ai-assistant-shell__context-item{flex:0 0 auto;display:inline-flex;align-items:center;gap:5px;max-width:240px;padding:5px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface-container-high, #263244);font-size:12px;line-height:1.25}.praxis-ai-assistant-shell__context-item mat-icon{width:16px;height:16px;font-size:16px}.praxis-ai-assistant-shell__context-label,.praxis-ai-assistant-shell__context-value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-shell__context-label{color:var(--md-sys-color-on-surface, #f8fafc)}.praxis-ai-assistant-shell__context-value{color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.praxis-ai-assistant-shell__label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.praxis-ai-assistant-shell__prompt{box-sizing:border-box;width:100%;min-height:56px;max-height:128px;resize:none;border:0;padding:12px;color:var(--md-sys-color-on-surface, #f8fafc);background:transparent;font:inherit;line-height:1.45;outline:none}.praxis-ai-assistant-shell__conversation{min-height:0;flex:1 1 auto;display:flex;flex-direction:column;gap:8px;overflow:auto;padding:2px}.praxis-ai-assistant-shell__message{max-width:86%;align-self:flex-start;padding:9px 11px;border-radius:8px;background:var(--md-sys-color-surface-container-high, #263244);color:var(--md-sys-color-on-surface, #f8fafc);font-size:13px;line-height:1.35;overflow-wrap:anywhere}.praxis-ai-assistant-shell__message-actions{display:flex;align-items:center;gap:4px;flex-wrap:wrap;margin-top:8px}.praxis-ai-assistant-shell__message-action{min-height:28px;padding:0 8px;border-radius:8px;font-size:12px}.praxis-ai-assistant-shell__message--assistant{border-bottom-left-radius:2px}.praxis-ai-assistant-shell__message--user{align-self:flex-end;border-bottom-right-radius:2px;background:var(--md-sys-color-primary-container, #17375f);color:var(--md-sys-color-on-primary-container, #f8fafc)}.praxis-ai-assistant-shell__message--status{color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.praxis-ai-assistant-shell__message--error,.praxis-ai-assistant-shell__error{color:var(--md-sys-color-error, #ff6b6b)}.praxis-ai-assistant-shell__quick-replies{display:flex;flex-wrap:wrap;gap:8px}.praxis-ai-assistant-shell__attachment{flex:0 0 auto;display:inline-flex;align-items:center;gap:7px;max-width:260px;min-height:34px;padding:4px 4px 4px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 70%,transparent);border-radius:8px;background:var(--md-sys-color-surface-container-high, #263244)}.praxis-ai-assistant-shell__attachment--error{border-color:color-mix(in srgb,var(--md-sys-color-error, #ff6b6b) 60%,transparent)}.praxis-ai-assistant-shell__attachment-preview{flex:0 0 auto;width:28px;height:28px;border-radius:6px;object-fit:cover}.praxis-ai-assistant-shell__attachment-name,.praxis-ai-assistant-shell__attachment-kind{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.praxis-ai-assistant-shell__attachment-name{color:var(--md-sys-color-on-surface, #f8fafc)}.praxis-ai-assistant-shell__attachment-kind{color:var(--md-sys-color-on-surface-variant, #cbd5e1)}.praxis-ai-assistant-shell__quick-reply{max-width:100%;border-radius:999px}.praxis-ai-assistant-shell__status,.praxis-ai-assistant-shell__error{margin:0;font-size:13px;line-height:1.35;overflow-wrap:anywhere}.praxis-ai-assistant-shell__footer{flex:0 0 auto;padding:10px 12px 12px;border-top:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant, #334155) 72%,transparent);background:var(--md-sys-color-surface-container-low, #111827)}.praxis-ai-assistant-shell__composer{display:grid;gap:8px;border:1px solid var(--md-sys-color-outline-variant, #334155);border-radius:8px;background:var(--md-sys-color-surface-container-lowest, #020617);box-shadow:inset 0 1px #ffffff0a}.praxis-ai-assistant-shell__composer:focus-within{border-color:var(--md-sys-color-primary, #60a5fa);box-shadow:0 0 0 2px #60a5fa29}.praxis-ai-assistant-shell__composer-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:0 8px 8px;flex-wrap:wrap}.praxis-ai-assistant-shell__resize-handle{position:absolute;right:0;bottom:0;width:22px;height:22px;border:0;background:transparent;cursor:nwse-resize;touch-action:none}.praxis-ai-assistant-shell__resize-handle:after{content:\"\";position:absolute;right:6px;bottom:6px;width:10px;height:10px;border-right:2px solid var(--md-sys-color-outline, #94a3b8);border-bottom:2px solid var(--md-sys-color-outline, #94a3b8)}\n"] }]
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"] }]
6283
6548
  }], propDecorators: { labels: [{
6284
6549
  type: Input
6285
6550
  }], mode: [{
@@ -6368,6 +6633,264 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6368
6633
  args: ['conversation']
6369
6634
  }] } });
6370
6635
 
6636
+ class PraxisAiAssistantDockComponent {
6637
+ title = 'Praxis copilot active';
6638
+ summary = 'Conversation preserved. Continue where you stopped.';
6639
+ badge = '';
6640
+ icon = '';
6641
+ state = null;
6642
+ tone = null;
6643
+ ariaLabel = '';
6644
+ openAriaLabel = '';
6645
+ openTooltip = '';
6646
+ testId = 'praxis-ai-assistant-dock';
6647
+ openTestId = 'praxis-ai-assistant-dock-open';
6648
+ open = new EventEmitter();
6649
+ resolvedTone() {
6650
+ if (this.tone)
6651
+ return this.tone;
6652
+ if (this.state === 'error')
6653
+ return 'error';
6654
+ if (this.state === 'processing' || this.state === 'applying')
6655
+ return 'working';
6656
+ if (this.state === 'review')
6657
+ return 'review';
6658
+ if (this.state === 'clarification')
6659
+ return 'governed';
6660
+ return 'ready';
6661
+ }
6662
+ resolvedIcon() {
6663
+ if (this.icon)
6664
+ return this.icon;
6665
+ const tone = this.resolvedTone();
6666
+ if (tone === 'error')
6667
+ return 'error';
6668
+ if (tone === 'working')
6669
+ return 'sync';
6670
+ if (tone === 'review')
6671
+ return 'rate_review';
6672
+ if (tone === 'governed')
6673
+ return 'rule';
6674
+ return 'auto_awesome';
6675
+ }
6676
+ resolvedBadge() {
6677
+ if (this.badge)
6678
+ return this.badge;
6679
+ const tone = this.resolvedTone();
6680
+ if (tone === 'error')
6681
+ return 'Attention';
6682
+ if (tone === 'working')
6683
+ return 'Working';
6684
+ if (tone === 'review')
6685
+ return 'Review';
6686
+ if (tone === 'governed')
6687
+ return 'Governed';
6688
+ return 'Ready';
6689
+ }
6690
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantDockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6691
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisAiAssistantDockComponent, isStandalone: true, selector: "praxis-ai-assistant-dock", inputs: { title: "title", summary: "summary", badge: "badge", icon: "icon", state: "state", tone: "tone", ariaLabel: "ariaLabel", openAriaLabel: "openAriaLabel", openTooltip: "openTooltip", testId: "testId", openTestId: "openTestId" }, outputs: { open: "open" }, ngImport: i0, template: `
6692
+ <section
6693
+ class="praxis-ai-assistant-dock"
6694
+ [class.praxis-ai-assistant-dock--working]="resolvedTone() === 'working'"
6695
+ [class.praxis-ai-assistant-dock--review]="resolvedTone() === 'review'"
6696
+ [class.praxis-ai-assistant-dock--governed]="resolvedTone() === 'governed'"
6697
+ [class.praxis-ai-assistant-dock--error]="resolvedTone() === 'error'"
6698
+ [attr.data-testid]="testId"
6699
+ role="status"
6700
+ [attr.aria-label]="ariaLabel || title"
6701
+ >
6702
+ <button
6703
+ class="praxis-ai-assistant-dock__main"
6704
+ type="button"
6705
+ [attr.data-testid]="openTestId"
6706
+ [attr.aria-label]="openAriaLabel || title"
6707
+ [matTooltip]="openTooltip || title"
6708
+ (click)="open.emit()"
6709
+ >
6710
+ <span class="praxis-ai-assistant-dock__orb" aria-hidden="true">
6711
+ <mat-icon>{{ resolvedIcon() }}</mat-icon>
6712
+ </span>
6713
+ <span class="praxis-ai-assistant-dock__copy">
6714
+ <strong>{{ title }}</strong>
6715
+ <span>{{ summary }}</span>
6716
+ </span>
6717
+ <span class="praxis-ai-assistant-dock__badge">{{ resolvedBadge() }}</span>
6718
+ </button>
6719
+ </section>
6720
+ `, isInline: true, styles: [":host{display:block;position:var(--praxis-ai-assistant-dock-position, absolute);z-index:var(--praxis-ai-assistant-dock-z-index, 135);right:var(--praxis-ai-assistant-dock-right, 16px);bottom:var(--praxis-ai-assistant-dock-bottom, 88px);width:min(var(--praxis-ai-assistant-dock-width, 560px),calc(100% - 32px));pointer-events:auto}.praxis-ai-assistant-dock__main{appearance:none;width:100%;min-height:76px;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:12px;padding:12px 14px;border:1px solid rgba(56,189,248,.34);border-radius:18px;background:radial-gradient(circle at 8% 18%,rgba(14,165,233,.24),transparent 34%),linear-gradient(135deg,#0f172af0,#1e293beb 48%,#082f49e6);color:#e0f2fe;box-shadow:0 22px 58px #02061757,inset 0 1px #ffffff1f;cursor:pointer;text-align:left}.praxis-ai-assistant-dock__main:hover,.praxis-ai-assistant-dock__main:focus-visible{border-color:#7dd3fc9e;box-shadow:0 26px 72px #0206176b,0 0 0 3px #0ea5e92e;outline:none}.praxis-ai-assistant-dock__orb{width:46px;height:46px;display:inline-grid;place-items:center;border-radius:16px;background:linear-gradient(135deg,#38bdf8,#22c55e);color:#082f49;box-shadow:0 12px 28px #22c55e3d}.praxis-ai-assistant-dock--working .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#38bdf8,#a78bfa)}.praxis-ai-assistant-dock--review .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#facc15,#38bdf8)}.praxis-ai-assistant-dock--governed .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#22c55e,#14b8a6)}.praxis-ai-assistant-dock--error .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#f97316,#ef4444);color:#fff7ed}.praxis-ai-assistant-dock__copy{min-width:0;display:grid;gap:3px}.praxis-ai-assistant-dock__copy strong{color:#f8fafc;font-size:14px;line-height:1.2}.praxis-ai-assistant-dock__copy span{color:#bae6fd;font-size:12px;line-height:1.35;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-dock__badge{max-width:132px;padding:5px 9px;border:1px solid rgba(186,230,253,.24);border-radius:999px;background:#0f172a7a;color:#f0f9ff;font-size:11px;font-weight:800;letter-spacing:.02em;overflow:hidden;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap}@media(max-width:720px){:host{right:var(--praxis-ai-assistant-dock-mobile-right, 12px);bottom:var(--praxis-ai-assistant-dock-mobile-bottom, 84px);width:calc(100% - 24px)}.praxis-ai-assistant-dock__main{grid-template-columns:auto minmax(0,1fr)}.praxis-ai-assistant-dock__badge{grid-column:2;justify-self:start}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i9.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6721
+ }
6722
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantDockComponent, decorators: [{
6723
+ type: Component,
6724
+ args: [{ selector: 'praxis-ai-assistant-dock', standalone: true, imports: [CommonModule, MatIconModule, MatTooltipModule], template: `
6725
+ <section
6726
+ class="praxis-ai-assistant-dock"
6727
+ [class.praxis-ai-assistant-dock--working]="resolvedTone() === 'working'"
6728
+ [class.praxis-ai-assistant-dock--review]="resolvedTone() === 'review'"
6729
+ [class.praxis-ai-assistant-dock--governed]="resolvedTone() === 'governed'"
6730
+ [class.praxis-ai-assistant-dock--error]="resolvedTone() === 'error'"
6731
+ [attr.data-testid]="testId"
6732
+ role="status"
6733
+ [attr.aria-label]="ariaLabel || title"
6734
+ >
6735
+ <button
6736
+ class="praxis-ai-assistant-dock__main"
6737
+ type="button"
6738
+ [attr.data-testid]="openTestId"
6739
+ [attr.aria-label]="openAriaLabel || title"
6740
+ [matTooltip]="openTooltip || title"
6741
+ (click)="open.emit()"
6742
+ >
6743
+ <span class="praxis-ai-assistant-dock__orb" aria-hidden="true">
6744
+ <mat-icon>{{ resolvedIcon() }}</mat-icon>
6745
+ </span>
6746
+ <span class="praxis-ai-assistant-dock__copy">
6747
+ <strong>{{ title }}</strong>
6748
+ <span>{{ summary }}</span>
6749
+ </span>
6750
+ <span class="praxis-ai-assistant-dock__badge">{{ resolvedBadge() }}</span>
6751
+ </button>
6752
+ </section>
6753
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:var(--praxis-ai-assistant-dock-position, absolute);z-index:var(--praxis-ai-assistant-dock-z-index, 135);right:var(--praxis-ai-assistant-dock-right, 16px);bottom:var(--praxis-ai-assistant-dock-bottom, 88px);width:min(var(--praxis-ai-assistant-dock-width, 560px),calc(100% - 32px));pointer-events:auto}.praxis-ai-assistant-dock__main{appearance:none;width:100%;min-height:76px;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:12px;padding:12px 14px;border:1px solid rgba(56,189,248,.34);border-radius:18px;background:radial-gradient(circle at 8% 18%,rgba(14,165,233,.24),transparent 34%),linear-gradient(135deg,#0f172af0,#1e293beb 48%,#082f49e6);color:#e0f2fe;box-shadow:0 22px 58px #02061757,inset 0 1px #ffffff1f;cursor:pointer;text-align:left}.praxis-ai-assistant-dock__main:hover,.praxis-ai-assistant-dock__main:focus-visible{border-color:#7dd3fc9e;box-shadow:0 26px 72px #0206176b,0 0 0 3px #0ea5e92e;outline:none}.praxis-ai-assistant-dock__orb{width:46px;height:46px;display:inline-grid;place-items:center;border-radius:16px;background:linear-gradient(135deg,#38bdf8,#22c55e);color:#082f49;box-shadow:0 12px 28px #22c55e3d}.praxis-ai-assistant-dock--working .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#38bdf8,#a78bfa)}.praxis-ai-assistant-dock--review .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#facc15,#38bdf8)}.praxis-ai-assistant-dock--governed .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#22c55e,#14b8a6)}.praxis-ai-assistant-dock--error .praxis-ai-assistant-dock__orb{background:linear-gradient(135deg,#f97316,#ef4444);color:#fff7ed}.praxis-ai-assistant-dock__copy{min-width:0;display:grid;gap:3px}.praxis-ai-assistant-dock__copy strong{color:#f8fafc;font-size:14px;line-height:1.2}.praxis-ai-assistant-dock__copy span{color:#bae6fd;font-size:12px;line-height:1.35;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-ai-assistant-dock__badge{max-width:132px;padding:5px 9px;border:1px solid rgba(186,230,253,.24);border-radius:999px;background:#0f172a7a;color:#f0f9ff;font-size:11px;font-weight:800;letter-spacing:.02em;overflow:hidden;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap}@media(max-width:720px){:host{right:var(--praxis-ai-assistant-dock-mobile-right, 12px);bottom:var(--praxis-ai-assistant-dock-mobile-bottom, 84px);width:calc(100% - 24px)}.praxis-ai-assistant-dock__main{grid-template-columns:auto minmax(0,1fr)}.praxis-ai-assistant-dock__badge{grid-column:2;justify-self:start}}\n"] }]
6754
+ }], propDecorators: { title: [{
6755
+ type: Input
6756
+ }], summary: [{
6757
+ type: Input
6758
+ }], badge: [{
6759
+ type: Input
6760
+ }], icon: [{
6761
+ type: Input
6762
+ }], state: [{
6763
+ type: Input
6764
+ }], tone: [{
6765
+ type: Input
6766
+ }], ariaLabel: [{
6767
+ type: Input
6768
+ }], openAriaLabel: [{
6769
+ type: Input
6770
+ }], openTooltip: [{
6771
+ type: Input
6772
+ }], testId: [{
6773
+ type: Input
6774
+ }], openTestId: [{
6775
+ type: Input
6776
+ }], open: [{
6777
+ type: Output
6778
+ }] } });
6779
+
6780
+ class PraxisAiAssistantSessionHostComponent {
6781
+ registry = inject(PraxisAssistantSessionRegistryService);
6782
+ testId = 'praxis-ai-assistant-session-host';
6783
+ dockTestIdPrefix = 'praxis-ai-assistant-session';
6784
+ ariaLabel = 'Active Praxis assistant sessions';
6785
+ openAriaLabel = 'Open assistant session';
6786
+ openTooltip = 'Open assistant session';
6787
+ ownerType = null;
6788
+ ownerId = null;
6789
+ visibility = 'minimized';
6790
+ sessionOpen = new EventEmitter();
6791
+ visibleSessions() {
6792
+ return this.registry.sessions().filter((session) => {
6793
+ if (this.visibility !== 'all' && session.visibility !== this.visibility)
6794
+ return false;
6795
+ if (this.ownerType && session.ownerType !== this.ownerType)
6796
+ return false;
6797
+ if (this.ownerId && session.ownerId !== this.ownerId)
6798
+ return false;
6799
+ return true;
6800
+ });
6801
+ }
6802
+ openSession(sessionId) {
6803
+ const session = this.registry.openSession(sessionId);
6804
+ if (session) {
6805
+ this.sessionOpen.emit(session);
6806
+ }
6807
+ }
6808
+ sessionAriaLabel(session) {
6809
+ const summary = session.summary ? `: ${session.summary}` : '';
6810
+ return `${session.title}${summary}`;
6811
+ }
6812
+ dockTestId(session, first) {
6813
+ return first ? `${this.dockTestIdPrefix}-dock` : `${this.dockTestIdPrefix}-dock-${this.safeId(session.id)}`;
6814
+ }
6815
+ dockOpenTestId(session, first) {
6816
+ return first ? `${this.dockTestIdPrefix}-dock-open` : `${this.dockTestIdPrefix}-dock-open-${this.safeId(session.id)}`;
6817
+ }
6818
+ trackSession(_index, session) {
6819
+ return session.id;
6820
+ }
6821
+ safeId(value) {
6822
+ return value.replace(/[^a-zA-Z0-9_-]+/g, '-');
6823
+ }
6824
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantSessionHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6825
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisAiAssistantSessionHostComponent, isStandalone: true, selector: "praxis-ai-assistant-session-host", inputs: { testId: "testId", dockTestIdPrefix: "dockTestIdPrefix", ariaLabel: "ariaLabel", openAriaLabel: "openAriaLabel", openTooltip: "openTooltip", ownerType: "ownerType", ownerId: "ownerId", visibility: "visibility" }, outputs: { sessionOpen: "sessionOpen" }, ngImport: i0, template: `
6826
+ <section
6827
+ *ngIf="visibleSessions().length"
6828
+ class="praxis-ai-assistant-session-host"
6829
+ [attr.data-testid]="testId"
6830
+ [attr.aria-label]="ariaLabel"
6831
+ >
6832
+ <praxis-ai-assistant-dock
6833
+ *ngFor="let session of visibleSessions(); let first = first; trackBy: trackSession"
6834
+ [title]="session.title"
6835
+ [summary]="session.summary"
6836
+ [badge]="session.badge"
6837
+ [icon]="session.icon"
6838
+ [state]="session.state"
6839
+ [ariaLabel]="sessionAriaLabel(session)"
6840
+ [openAriaLabel]="openAriaLabel"
6841
+ [openTooltip]="openTooltip"
6842
+ [testId]="dockTestId(session, first)"
6843
+ [openTestId]="dockOpenTestId(session, first)"
6844
+ (open)="openSession(session.id)"
6845
+ />
6846
+ </section>
6847
+ `, isInline: true, styles: [":host{display:block;position:absolute;z-index:var(--praxis-ai-assistant-session-host-z-index, 136);right:var(--praxis-ai-assistant-session-host-right, 16px);bottom:var(--praxis-ai-assistant-session-host-bottom, 88px);width:min(var(--praxis-ai-assistant-session-host-width, 560px),calc(100% - 32px));pointer-events:none}.praxis-ai-assistant-session-host{display:grid;gap:10px;pointer-events:auto}praxis-ai-assistant-dock{--praxis-ai-assistant-dock-position: static;--praxis-ai-assistant-dock-width: 100%}@media(max-width:720px){:host{right:var(--praxis-ai-assistant-session-host-mobile-right, 12px);bottom:var(--praxis-ai-assistant-session-host-mobile-bottom, 84px);width:calc(100% - 24px)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: PraxisAiAssistantDockComponent, selector: "praxis-ai-assistant-dock", inputs: ["title", "summary", "badge", "icon", "state", "tone", "ariaLabel", "openAriaLabel", "openTooltip", "testId", "openTestId"], outputs: ["open"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6848
+ }
6849
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisAiAssistantSessionHostComponent, decorators: [{
6850
+ type: Component,
6851
+ args: [{ selector: 'praxis-ai-assistant-session-host', standalone: true, imports: [CommonModule, PraxisAiAssistantDockComponent], template: `
6852
+ <section
6853
+ *ngIf="visibleSessions().length"
6854
+ class="praxis-ai-assistant-session-host"
6855
+ [attr.data-testid]="testId"
6856
+ [attr.aria-label]="ariaLabel"
6857
+ >
6858
+ <praxis-ai-assistant-dock
6859
+ *ngFor="let session of visibleSessions(); let first = first; trackBy: trackSession"
6860
+ [title]="session.title"
6861
+ [summary]="session.summary"
6862
+ [badge]="session.badge"
6863
+ [icon]="session.icon"
6864
+ [state]="session.state"
6865
+ [ariaLabel]="sessionAriaLabel(session)"
6866
+ [openAriaLabel]="openAriaLabel"
6867
+ [openTooltip]="openTooltip"
6868
+ [testId]="dockTestId(session, first)"
6869
+ [openTestId]="dockOpenTestId(session, first)"
6870
+ (open)="openSession(session.id)"
6871
+ />
6872
+ </section>
6873
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:absolute;z-index:var(--praxis-ai-assistant-session-host-z-index, 136);right:var(--praxis-ai-assistant-session-host-right, 16px);bottom:var(--praxis-ai-assistant-session-host-bottom, 88px);width:min(var(--praxis-ai-assistant-session-host-width, 560px),calc(100% - 32px));pointer-events:none}.praxis-ai-assistant-session-host{display:grid;gap:10px;pointer-events:auto}praxis-ai-assistant-dock{--praxis-ai-assistant-dock-position: static;--praxis-ai-assistant-dock-width: 100%}@media(max-width:720px){:host{right:var(--praxis-ai-assistant-session-host-mobile-right, 12px);bottom:var(--praxis-ai-assistant-session-host-mobile-bottom, 84px);width:calc(100% - 24px)}}\n"] }]
6874
+ }], propDecorators: { testId: [{
6875
+ type: Input
6876
+ }], dockTestIdPrefix: [{
6877
+ type: Input
6878
+ }], ariaLabel: [{
6879
+ type: Input
6880
+ }], openAriaLabel: [{
6881
+ type: Input
6882
+ }], openTooltip: [{
6883
+ type: Input
6884
+ }], ownerType: [{
6885
+ type: Input
6886
+ }], ownerId: [{
6887
+ type: Input
6888
+ }], visibility: [{
6889
+ type: Input
6890
+ }], sessionOpen: [{
6891
+ type: Output
6892
+ }] } });
6893
+
6371
6894
  class StreamingFeedbackComponent {
6372
6895
  title = 'Processando...';
6373
6896
  displayText = '';
@@ -7165,4 +7688,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
7165
7688
  * Generated bundle index. Do not edit.
7166
7689
  */
7167
7690
 
7168
- export { AI_BACKEND_CONFIG_STORE, AI_BACKEND_STORAGE_OPTIONS, AI_CONTRACT_SCHEMA_HASH, AI_CONTRACT_VERSION, AI_INTENT_CONTRACT_SCHEMA_HASH, AI_INTENT_CONTRACT_VERSION, AI_STREAM_EVENT_SCHEMA_VERSION, AI_STREAM_EVENT_TYPES, AiBackendApiService, AiPatchStreamConnectionError, AiResponseValidatorService, AiRuleWizardDialogComponent, BaseAiAdapter, PraxisAi, PraxisAiAssistantComponent, PraxisAiAssistantShellComponent, PraxisAiService, PraxisAssistantTurnController, PraxisAssistantTurnOrchestratorService, SchemaMinifierService };
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 };