@acorex-platform/extension-ai-conversation-client 0.0.38 → 0.0.48

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.
Files changed (12) hide show
  1. package/fesm2022/{acorex-platform-extension-ai-conversation-client-acorex-platform-extension-ai-conversation-client-CRjLZIU5.mjs → acorex-platform-extension-ai-conversation-client-acorex-platform-extension-ai-conversation-client-DfdGeEG4.mjs} +1002 -271
  2. package/fesm2022/acorex-platform-extension-ai-conversation-client-acorex-platform-extension-ai-conversation-client-DfdGeEG4.mjs.map +1 -0
  3. package/fesm2022/{acorex-platform-extension-ai-conversation-client-send-assist-chat-message.command-B3rJWdsz.mjs → acorex-platform-extension-ai-conversation-client-send-assist-chat-message.command-B6QodNVg.mjs} +7 -5
  4. package/fesm2022/acorex-platform-extension-ai-conversation-client-send-assist-chat-message.command-B6QodNVg.mjs.map +1 -0
  5. package/fesm2022/{acorex-platform-extension-ai-conversation-client-start-assist-chat.command-qYtaBoTP.mjs → acorex-platform-extension-ai-conversation-client-start-assist-chat.command-zQPeOCRi.mjs} +3 -2
  6. package/fesm2022/{acorex-platform-extension-ai-conversation-client-start-assist-chat.command-qYtaBoTP.mjs.map → acorex-platform-extension-ai-conversation-client-start-assist-chat.command-zQPeOCRi.mjs.map} +1 -1
  7. package/fesm2022/acorex-platform-extension-ai-conversation-client.mjs +2 -1
  8. package/fesm2022/acorex-platform-extension-ai-conversation-client.mjs.map +1 -1
  9. package/package.json +3 -1
  10. package/types/acorex-platform-extension-ai-conversation-client.d.ts +50 -4
  11. package/fesm2022/acorex-platform-extension-ai-conversation-client-acorex-platform-extension-ai-conversation-client-CRjLZIU5.mjs.map +0 -1
  12. package/fesm2022/acorex-platform-extension-ai-conversation-client-send-assist-chat-message.command-B3rJWdsz.mjs.map +0 -1
@@ -1,38 +1,41 @@
1
1
  import { AXP_SEARCH_PROVIDER, AXP_SEARCH_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/common';
2
2
  import * as i1$1 from '@acorex-platform/framework-client/core';
3
3
  import { AXPComponentSlotModule, provideAppStartUpTask, AXP_APP_STARTUP_PRIORITY } from '@acorex-platform/framework-client/core';
4
- import { AXPRuntimeTransportService, AXPCommandService, provideCommandSetups } from '@acorex-platform/framework-client/runtime';
5
- import { AXM_AI_MANAGEMENT_MODULE_NAME, AXM_AI_MANAGEMENT_ASSIST_ENTITY_NAME, AXMConversationEntityStoreService, axmChatAiTargetDisplayName, axmChatAiTargetDisplayDescription, axmChatAiTargetAvatarUrl, axmFilterChatAiTargets, axmChatAiPeerParticipantId, axmBuildAiChatConversationMetadata, CONVERSATION_START_ASSIST_CHAT_COMMAND, axmChatAiTargetShortcutDescriptionKey, AXM_ASSIST_DEFAULT_RESOLVER_PORT, axmAssistStarterPromptChipItems, AXMAssistSuggestionChipsComponent, AXM_CHAT_EMPTY_STATE_COMPONENT, AXM_MINI_CHAT_INBOX_CONVERSATION_FILTER, axmConversationHasAiChat, AXMConversationFocusService, axmIsAssistRetryableStatus, axmReadAssistResponseStatus, axmReadReplyToUserMessageId, axmMessageEntityToMessage, axmAssistPersistedMessageLooksComplete, axmShouldPreferPersistedAssistOverLiveCache, axmRoomWithAssistPeerDisplayName, axmSyncAssistPeerDisplayName, axmRoomEntityToConversation, axmIsAssistTerminalStatus, CONVERSATION_SYNC_READ_STATE_COMMAND, axmAssistHasFinalizeMetadata, AXM_CHAT_SIDEBAR_ACTION_PROVIDER, AXMMiniChatHeaderSlotComponent, AXM_CONVERSATION_ASSIST_RESPONSE_PORT, AXM_CHAT_ASSIST_CATALOG_PORT, AXM_CONVERSATION_ASSIST_BACKGROUND_OPERATION_HANDLERS, CONVERSATION_SEND_ASSIST_CHAT_MESSAGE_COMMAND } from '@acorex-platform/module-conversation-client';
4
+ import { AXPCommandService, AXPRuntimeTransportService, provideCommandSetups } from '@acorex-platform/framework-client/runtime';
5
+ import { AXM_AI_MANAGEMENT_MODULE_NAME, AXM_AI_MANAGEMENT_ASSIST_ENTITY_NAME, AXMEntityChatService, AXMConversationEntityStoreService, axmChatAiTargetDisplayName, axmChatAiTargetDisplayDescription, axmChatAiTargetAvatarUrl, axmFilterChatAiTargets, axmChatAiPeerParticipantId, axmBuildAiChatConversationMetadata, axmEntityRefFromLaunchContext, axmEntityRefForMiniChatPageContext, CONVERSATION_START_ASSIST_CHAT_COMMAND, axmChatAiTargetShortcutDescriptionKey, AXMChatEmptyAssistSessionService, AXM_ASSIST_DEFAULT_RESOLVER_PORT, AXMMiniChatFileDropService, axmAssistStarterPromptChipItems, AXMAssistSuggestionChipsComponent, AXMChatComposerAssistPickerComponent, AXM_CHAT_EMPTY_STATE_COMPONENT, AXM_MINI_CHAT_INBOX_CONVERSATION_FILTER, axmConversationHasAiChat, AXMConversationFocusService, axmIsAssistRetryableStatus, axmReadAssistResponseStatus, axmReadReplyToUserMessageId, axmMessageEntityToMessage, axmAssistPersistedMessageLooksComplete, axmShouldPreferPersistedAssistOverLiveCache, axmRoomWithAssistPeerDisplayName, axmSyncAssistPeerDisplayName, axmRoomEntityToConversation, axmIsAssistTerminalStatus, CONVERSATION_SYNC_READ_STATE_COMMAND, axmAssistHasFinalizeMetadata, AXM_CHAT_SIDEBAR_ACTION_PROVIDER, AXMMiniChatPanelService, AXMMiniChatFooterDockComponent, AXMMiniChatHeaderSlotComponent, AXM_CONVERSATION_ASSIST_RESPONSE_PORT, AXM_CHAT_ASSIST_CATALOG_PORT, AXM_CONVERSATION_ASSIST_BACKGROUND_OPERATION_HANDLERS, CONVERSATION_SEND_ASSIST_CHAT_MESSAGE_COMMAND } from '@acorex-platform/module-conversation-client';
6
6
  import * as i0 from '@angular/core';
7
- import { inject, Injectable, computed, output, input, signal, viewChild, effect, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
7
+ import { inject, Injectable, DestroyRef, viewChild, output, input, computed, signal, effect, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
8
8
  import { AXPSystemActionType, runtimeCommandPayloadData } from '@acorex-platform/framework-shared/core';
9
9
  import { sortBy } from 'lodash-es';
10
- import { AXConversationService } from '@acorex/components/conversation';
10
+ import { AXConversationService, AXConversationComposerGenerationStateService } from '@acorex/components/conversation';
11
11
  import { AXToastService } from '@acorex/components/toast';
12
- import * as i6 from '@acorex/core/translation';
12
+ import * as i7 from '@acorex/core/translation';
13
13
  import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
14
14
  import { AXPSessionService } from '@acorex-platform/framework-client/auth';
15
+ import { AXM_ASSIST_LAUNCH_CONTEXT_METADATA_KEY, axmSanitizeContextualAssistContext, AXM_CONTEXTUAL_ASSIST_STARTER_SYSTEM_PROMPT, axmBuildContextualAssistStarterUserPrompt, axmParseContextualAssistStarterItems } from '@acorex-platform/module-conversation-client/contracts';
15
16
  import { AXPEntityService } from '@acorex-platform/framework-client/layout/entity';
16
17
  import { Router } from '@angular/router';
17
- import { AXPLayoutThemeService } from '@acorex-platform/framework-client/themes/shared';
18
+ import { AIMANAGEMENT_STRUCTURED_TEXT_COMPLETION_COMMAND_KEY, axpAiChatTextMessage } from '@acorex-platform/module-ai-management-client/contracts';
18
19
  import * as i2 from '@acorex/components/button';
19
20
  import { AXButtonModule, AXButtonComponent } from '@acorex/components/button';
20
- import * as i4 from '@acorex/components/decorators';
21
+ import * as i3 from '@acorex/components/decorators';
21
22
  import { AXDecoratorModule, AXDecoratorGenericComponent } from '@acorex/components/decorators';
22
- import * as i3 from '@acorex/components/dropdown';
23
+ import * as i4 from '@acorex/components/dropdown';
23
24
  import { AXDropdownModule } from '@acorex/components/dropdown';
24
- import { AXTextBoxModule } from '@acorex/components/text-box';
25
+ import * as i5 from '@acorex/components/loading';
26
+ import { AXLoadingModule } from '@acorex/components/loading';
25
27
  import { AXLocaleService } from '@acorex/core/locale';
26
- import * as i5 from '@angular/common';
28
+ import * as i6 from '@angular/common';
27
29
  import { CommonModule } from '@angular/common';
28
- import { toSignal } from '@angular/core/rxjs-interop';
30
+ import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
29
31
  import * as i1 from '@angular/forms';
30
32
  import { FormsModule } from '@angular/forms';
31
33
  import { AXPAiManagerService } from '@acorex-platform/module-ai-management-client';
32
- import { AXMAiManagementTransportKeys, AIMANAGEMENT_STRUCTURED_TEXT_COMPLETION_COMMAND_KEY, axpAiChatTextMessage } from '@acorex-platform/module-ai-management-client/contracts';
34
+ import { AXMAiManagementTransportKeys, AIMANAGEMENT_STRUCTURED_TEXT_COMPLETION_COMMAND_KEY as AIMANAGEMENT_STRUCTURED_TEXT_COMPLETION_COMMAND_KEY$1, axpAiChatTextMessage as axpAiChatTextMessage$1 } from '@acorex-platform/module-ai-management-client/contracts';
33
35
  import { axmCompareMessagesChronologically, axmExtractAssistIdFromMetadata, AXM_ASSIST_CONVERSATION_TITLE_SYSTEM_PROMPT, axmSanitizeAiGeneratedConversationTitle, extractMessageTextFromPayload, axmEnsureAssistMessageSortsAfterReplyTarget } from '@acorex-platform/module-conversation-client/contracts';
34
36
  import { AXPopupService } from '@acorex/components/popup';
35
37
  import { AXPAvatarComponent } from '@acorex-platform/framework-client/layout/components';
38
+ import { AXPLayoutThemeService } from '@acorex-platform/framework-client/themes/shared';
36
39
  import { AXDataSource, AXComponentCloseEvent, AXClosableComponent } from '@acorex/cdk/common';
37
40
  import { AXListComponent } from '@acorex/components/list';
38
41
  import { AXBasePageComponent } from '@acorex/components/page';
@@ -169,6 +172,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
169
172
  class AXMChatAssistLauncherService {
170
173
  constructor() {
171
174
  this.conversationService = inject(AXConversationService);
175
+ this.entityChatService = inject(AXMEntityChatService);
172
176
  this.assistCatalog = inject(AXMChatAssistCatalogService);
173
177
  this.entityStore = inject(AXMConversationEntityStoreService);
174
178
  this.translationService = inject(AXTranslationService);
@@ -202,11 +206,11 @@ class AXMChatAssistLauncherService {
202
206
  this.toastService.danger(message);
203
207
  return null;
204
208
  }
205
- return this.startAssistChatFromRow(row, options.navigate !== false);
209
+ return this.startAssistChatFromRow(row, options.navigate !== false, options.launchContext);
206
210
  }
207
- async startAssistChatFromRow(row, navigate = true) {
211
+ async startAssistChatFromRow(row, navigate = true, launchContext) {
208
212
  try {
209
- const conversation = await this.#createTargetConversation(row);
213
+ const conversation = await this.#createTargetConversation(row, launchContext);
210
214
  await this.conversationService.selectConversation(conversation.id);
211
215
  if (navigate) {
212
216
  await this.navigateToConversation(conversation.id);
@@ -228,18 +232,26 @@ class AXMChatAssistLauncherService {
228
232
  }
229
233
  await this.router.navigate([this.#applicationRouteSegment(), 'chat', scoped]);
230
234
  }
231
- async #createTargetConversation(row) {
235
+ async #createTargetConversation(row, launchContext) {
232
236
  await this.assistCatalog.loadCatalogTargets();
233
237
  const participantId = axmChatAiPeerParticipantId(row.id);
234
238
  const title = this.displayName(row);
235
239
  const avatar = this.targetAvatarUrl(row);
236
240
  const assistRow = this.assistCatalog.getCachedAssist(row.id);
237
241
  const assistSnapshot = assistRow ? this.assistCatalog.toSnapshot(assistRow) : undefined;
242
+ const metadata = axmBuildAiChatConversationMetadata(row, assistSnapshot);
243
+ if (launchContext) {
244
+ metadata[AXM_ASSIST_LAUNCH_CONTEXT_METADATA_KEY] = axmSanitizeContextualAssistContext(launchContext);
245
+ }
246
+ const entityRef = axmEntityRefFromLaunchContext(launchContext) ?? axmEntityRefForMiniChatPageContext(launchContext);
247
+ if (entityRef) {
248
+ return this.entityChatService.createEntityChat(entityRef, row.id, metadata);
249
+ }
238
250
  const conversation = await this.conversationService.createConversation([participantId], 'bot', {
239
251
  title,
240
252
  ...(avatar ? { avatar } : {}),
241
253
  icon: 'fa-solid fa-robot',
242
- metadata: axmBuildAiChatConversationMetadata(row, assistSnapshot),
254
+ metadata,
243
255
  });
244
256
  this.entityStore.cacheConversation(conversation);
245
257
  this.entityStore.cacheEmptyMessageList(conversation.id);
@@ -320,126 +332,309 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
320
332
  type: Injectable
321
333
  }] });
322
334
 
335
+ const FALLBACK_CHIP_KEYS = ['@conversation:chat.contextual-assist.fallback.summarize', '@conversation:chat.contextual-assist.fallback.what-can-i-change', '@conversation:chat.contextual-assist.fallback.explain-record'];
336
+ const DRAFT_KIND_ICONS = {
337
+ image: 'fa-light fa-image',
338
+ video: 'fa-light fa-video',
339
+ audio: 'fa-light fa-music',
340
+ file: 'fa-light fa-file',
341
+ };
342
+ const MAX_EMPTY_DRAFT_FILES = 3;
323
343
  class AXMChatEmptyAssistComposerComponent {
344
+ //#endregion
345
+ //#region ----- Lifecycle -----
324
346
  constructor() {
325
- this.conversationService = inject(AXConversationService);
347
+ //#region ----- Services & Dependencies -----
348
+ this.commandService = inject(AXPCommandService);
326
349
  this.toastService = inject(AXToastService);
327
350
  this.translationService = inject(AXTranslationService);
328
351
  this.localeService = inject(AXLocaleService);
329
352
  this.assistCatalog = inject(AXMChatAssistCatalogService);
330
- this.layoutTheme = inject(AXPLayoutThemeService);
353
+ this.emptyAssistSession = inject(AXMChatEmptyAssistSessionService);
354
+ this.conversationService = inject(AXConversationService);
331
355
  this.defaultAssistResolver = inject(AXM_ASSIST_DEFAULT_RESOLVER_PORT, { optional: true });
332
- this.localeProfile = toSignal(this.localeService.profileChanged$, {
333
- initialValue: this.localeService.activeProfile(),
334
- });
335
- this.textDirection = computed(() => this.localeProfile()?.i18nMeta?.rtl === true ? 'rtl' : 'ltr', /* @ts-ignore */
336
- ...(ngDevMode ? [{ debugName: "textDirection" }] : /* istanbul ignore next */ []));
356
+ this.destroyRef = inject(DestroyRef);
357
+ this.fileDrop = inject(AXMMiniChatFileDropService);
358
+ this.unregisterEmptyDraft = null;
359
+ this.draftInput = viewChild('draftInput', /* @ts-ignore */
360
+ ...(ngDevMode ? [{ debugName: "draftInput" }] : /* istanbul ignore next */ []));
361
+ this.fileInput = viewChild('fileInput', /* @ts-ignore */
362
+ ...(ngDevMode ? [{ debugName: "fileInput" }] : /* istanbul ignore next */ []));
363
+ //#endregion
364
+ //#region ----- Inputs / Outputs -----
337
365
  this.conversationCreated = output();
338
366
  this.initialAssistId = input(undefined, /* @ts-ignore */
339
367
  ...(ngDevMode ? [{ debugName: "initialAssistId" }] : /* istanbul ignore next */ []));
368
+ this.assistSelection = input('optional', /* @ts-ignore */
369
+ ...(ngDevMode ? [{ debugName: "assistSelection" }] : /* istanbul ignore next */ []));
370
+ this.acceptExternalFileDrop = input(false, /* @ts-ignore */
371
+ ...(ngDevMode ? [{ debugName: "acceptExternalFileDrop" }] : /* istanbul ignore next */ []));
340
372
  this.entityId = input(undefined, /* @ts-ignore */
341
373
  ...(ngDevMode ? [{ debugName: "entityId" }] : /* istanbul ignore next */ []));
342
374
  this.entityType = input(undefined, /* @ts-ignore */
343
375
  ...(ngDevMode ? [{ debugName: "entityType" }] : /* istanbul ignore next */ []));
344
376
  this.sendStarterPromptsInPlace = input(false, /* @ts-ignore */
345
377
  ...(ngDevMode ? [{ debugName: "sendStarterPromptsInPlace" }] : /* istanbul ignore next */ []));
378
+ this.launchContext = input(undefined, /* @ts-ignore */
379
+ ...(ngDevMode ? [{ debugName: "launchContext" }] : /* istanbul ignore next */ []));
346
380
  this.compact = input(false, /* @ts-ignore */
347
381
  ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
382
+ this.assistConversationCreated = input(undefined, /* @ts-ignore */
383
+ ...(ngDevMode ? [{ debugName: "assistConversationCreated" }] : /* istanbul ignore next */ []));
384
+ //#endregion
385
+ //#region ----- State Signals -----
386
+ this.localeProfile = toSignal(this.localeService.profileChanged$, {
387
+ initialValue: this.localeService.activeProfile(),
388
+ });
389
+ this.textDirection = computed(() => (this.localeProfile()?.i18nMeta?.rtl === true ? 'rtl' : 'ltr'), /* @ts-ignore */
390
+ ...(ngDevMode ? [{ debugName: "textDirection" }] : /* istanbul ignore next */ []));
391
+ this.hasActiveConversation = computed(() => Boolean(this.conversationService.activeConversationId()?.trim()), /* @ts-ignore */
392
+ ...(ngDevMode ? [{ debugName: "hasActiveConversation" }] : /* istanbul ignore next */ []));
348
393
  this.rows = signal([], /* @ts-ignore */
349
394
  ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
350
395
  this.loading = signal(true, /* @ts-ignore */
351
396
  ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
397
+ this.sending = signal(false, /* @ts-ignore */
398
+ ...(ngDevMode ? [{ debugName: "sending" }] : /* istanbul ignore next */ []));
399
+ this.chipsLoading = signal(false, /* @ts-ignore */
400
+ ...(ngDevMode ? [{ debugName: "chipsLoading" }] : /* istanbul ignore next */ []));
352
401
  this.selectedAssistId = signal(undefined, /* @ts-ignore */
353
402
  ...(ngDevMode ? [{ debugName: "selectedAssistId" }] : /* istanbul ignore next */ []));
354
- this.starterPromptItems = signal([], /* @ts-ignore */
355
- ...(ngDevMode ? [{ debugName: "starterPromptItems" }] : /* istanbul ignore next */ []));
356
403
  this.draftText = signal('', /* @ts-ignore */
357
404
  ...(ngDevMode ? [{ debugName: "draftText" }] : /* istanbul ignore next */ []));
358
- this.sending = signal(false, /* @ts-ignore */
359
- ...(ngDevMode ? [{ debugName: "sending" }] : /* istanbul ignore next */ []));
405
+ this.draftFiles = signal([], /* @ts-ignore */
406
+ ...(ngDevMode ? [{ debugName: "draftFiles" }] : /* istanbul ignore next */ []));
407
+ this.starterPromptItems = signal([], /* @ts-ignore */
408
+ ...(ngDevMode ? [{ debugName: "starterPromptItems" }] : /* istanbul ignore next */ []));
409
+ this.maxDraftFiles = MAX_EMPTY_DRAFT_FILES;
360
410
  this.isBusy = computed(() => this.sending() || this.loading(), /* @ts-ignore */
361
411
  ...(ngDevMode ? [{ debugName: "isBusy" }] : /* istanbul ignore next */ []));
362
- this.draftInput = viewChild('draftInput', /* @ts-ignore */
363
- ...(ngDevMode ? [{ debugName: "draftInput" }] : /* istanbul ignore next */ []));
412
+ this.asksForAssist = computed(() => this.assistSelection() === 'ask' && !this.selectedAssistId(), /* @ts-ignore */
413
+ ...(ngDevMode ? [{ debugName: "asksForAssist" }] : /* istanbul ignore next */ []));
414
+ this.canSend = computed(() => {
415
+ if (this.isBusy() || !this.hasDraftContent()) {
416
+ return false;
417
+ }
418
+ if (this.selectedAssistId()) {
419
+ return true;
420
+ }
421
+ return this.canResolveAssist();
422
+ }, /* @ts-ignore */
423
+ ...(ngDevMode ? [{ debugName: "canSend" }] : /* istanbul ignore next */ []));
364
424
  this.selectedAssistLabel = computed(() => {
365
425
  const selected = this.rows().find((row) => row.id === this.selectedAssistId());
366
- return selected?.title;
426
+ return this.translationService.resolve(selected?.title) ?? selected?.name ?? '';
367
427
  }, /* @ts-ignore */
368
428
  ...(ngDevMode ? [{ debugName: "selectedAssistLabel" }] : /* istanbul ignore next */ []));
369
- this.maxDraftHeightPx = computed(() => (this.compact() ? 128 : 224), /* @ts-ignore */
370
- ...(ngDevMode ? [{ debugName: "maxDraftHeightPx" }] : /* istanbul ignore next */ []));
429
+ this.draftPlaceholder = computed(() => {
430
+ if (this.asksForAssist()) {
431
+ return this.translationService.translateSync('@conversation:mini-chat.choose-assist.placeholder');
432
+ }
433
+ if (this.compact()) {
434
+ return this.translationService.translateSync('@conversation:mini-chat.composer.placeholder');
435
+ }
436
+ const assist = this.selectedAssistLabel();
437
+ return this.translationService.translateSync('@conversation:chat.empty-assist.placeholder', {
438
+ params: { assist },
439
+ });
440
+ }, /* @ts-ignore */
441
+ ...(ngDevMode ? [{ debugName: "draftPlaceholder" }] : /* istanbul ignore next */ []));
371
442
  this.assistsLoadPromise = null;
372
443
  this.starterPromptsLoadGeneration = 0;
444
+ this.starterAbort = null;
445
+ this.askAssistCleared = false;
446
+ //#endregion
447
+ //#region ----- Event Handlers -----
448
+ this.onAssistConversationCreated = (conversationId) => {
449
+ const handler = this.assistConversationCreated();
450
+ if (handler) {
451
+ return handler(conversationId);
452
+ }
453
+ this.conversationCreated.emit(conversationId);
454
+ };
373
455
  void this.ensureAssistsLoaded();
456
+ this.destroyRef.onDestroy(() => {
457
+ this.unregisterEmptyDraft?.();
458
+ this.starterAbort?.abort();
459
+ this.clearDraftFiles();
460
+ this.emptyAssistSession.closeAssistPicker();
461
+ });
374
462
  effect(() => {
375
- void this.loadStarterPromptsForAssist(this.selectedAssistId());
463
+ if (!this.acceptExternalFileDrop()) {
464
+ this.unregisterEmptyDraft?.();
465
+ this.unregisterEmptyDraft = null;
466
+ return;
467
+ }
468
+ if (this.unregisterEmptyDraft) {
469
+ return;
470
+ }
471
+ this.unregisterEmptyDraft = this.fileDrop.registerEmptyDraftAdder((files) => this.addDraftFiles(files));
472
+ });
473
+ // Header inbox (`ask`) must not inherit a leftover assist from another surface.
474
+ // Drop that leftover once. A Global AI Settings default, or a later pick, must stick.
475
+ effect(() => {
476
+ const mode = this.assistSelection();
477
+ if (mode !== 'ask') {
478
+ this.askAssistCleared = false;
479
+ return;
480
+ }
481
+ if (this.askAssistCleared) {
482
+ return;
483
+ }
484
+ this.askAssistCleared = true;
485
+ this.emptyAssistSession.setSelectedAssistId(this.selectedAssistId()?.trim());
486
+ });
487
+ effect(() => {
488
+ if (this.assistSelection() !== 'locked' || this.loading()) {
489
+ return;
490
+ }
491
+ const requestedId = this.initialAssistId()?.trim();
492
+ if (!requestedId || !this.rows().some((row) => row.id === requestedId)) {
493
+ return;
494
+ }
495
+ if (this.selectedAssistId() !== requestedId) {
496
+ this.selectedAssistId.set(requestedId);
497
+ }
498
+ });
499
+ effect(() => {
500
+ const sessionAssistId = this.emptyAssistSession.selectedAssistId();
501
+ if (this.assistSelection() === 'ask' && !sessionAssistId) {
502
+ return;
503
+ }
504
+ if (sessionAssistId && sessionAssistId !== this.selectedAssistId()) {
505
+ this.selectedAssistId.set(sessionAssistId);
506
+ }
507
+ });
508
+ effect(() => {
509
+ this.emptyAssistSession.bind({
510
+ selectedAssistId: this.selectedAssistId(),
511
+ rows: this.rows(),
512
+ entityId: this.entityId(),
513
+ entityType: this.entityType(),
514
+ launchContext: this.launchContext(),
515
+ assistSelection: this.assistSelection(),
516
+ });
376
517
  });
377
518
  effect(() => {
378
519
  this.draftText();
379
520
  queueMicrotask(() => this.resizeDraftInput());
380
521
  });
522
+ effect(() => {
523
+ const assistId = this.selectedAssistId();
524
+ const launchContext = this.launchContext();
525
+ if (launchContext) {
526
+ void this.loadContextualChips(assistId, launchContext);
527
+ return;
528
+ }
529
+ void this.loadStarterPromptsForAssist(assistId);
530
+ });
381
531
  }
382
- onAssistIdChange(value) {
383
- this.selectedAssistId.set(value?.trim() || undefined);
532
+ draftFileIcon(kind) {
533
+ return DRAFT_KIND_ICONS[kind];
384
534
  }
385
- canSend() {
386
- return Boolean(this.selectedAssistId() && this.draftText().trim().length > 0 && !this.sending() && !this.loading());
535
+ browseDraftFiles() {
536
+ this.fileInput()?.nativeElement.click();
537
+ }
538
+ onDraftFilesSelected(event) {
539
+ const input = event.target;
540
+ if (!(input instanceof HTMLInputElement)) {
541
+ return;
542
+ }
543
+ const selected = Array.from(input.files ?? []);
544
+ input.value = '';
545
+ this.addDraftFiles(selected);
546
+ }
547
+ addDraftFiles(files) {
548
+ if (files.length === 0 || this.isBusy()) {
549
+ return;
550
+ }
551
+ const remaining = Math.max(0, MAX_EMPTY_DRAFT_FILES - this.draftFiles().length);
552
+ const accepted = files.slice(0, remaining);
553
+ if (accepted.length === 0) {
554
+ return;
555
+ }
556
+ this.draftFiles.update((current) => [...current, ...accepted.map((file) => this.toDraftFile(file))]);
557
+ }
558
+ removeDraftFile(id) {
559
+ const current = this.draftFiles();
560
+ const next = current.filter((item) => item.id !== id);
561
+ current.filter((item) => item.id === id).forEach((item) => this.revokePreview(item.preview));
562
+ this.draftFiles.set(next);
387
563
  }
388
564
  resizeDraftInput() {
389
565
  const textarea = this.draftInput()?.nativeElement;
390
- if (!textarea)
566
+ if (!textarea) {
391
567
  return;
568
+ }
392
569
  textarea.style.height = '0px';
393
- textarea.style.height = `${Math.min(textarea.scrollHeight, this.maxDraftHeightPx())}px`;
570
+ textarea.style.height = `${Math.min(textarea.scrollHeight, this.compact() ? 128 : 224)}px`;
394
571
  }
395
572
  onDraftKeydown(event) {
396
- if (event.key !== 'Enter' || event.shiftKey || event.isComposing)
573
+ if (event.key !== 'Enter' || event.shiftKey || event.isComposing) {
397
574
  return;
575
+ }
398
576
  event.preventDefault();
399
- if (this.canSend())
577
+ if (this.canSend()) {
400
578
  void this.send();
579
+ }
401
580
  }
402
581
  async send() {
403
- const assistId = this.selectedAssistId();
404
- const text = this.draftText().trim();
405
- if (!assistId || !text || this.sending())
582
+ if (!this.canSend()) {
406
583
  return;
407
- const row = this.rows().find((r) => r.id === assistId);
408
- if (!row)
584
+ }
585
+ const assistId = await this.ensureSelectedAssist();
586
+ if (!assistId) {
409
587
  return;
588
+ }
589
+ const text = this.draftText();
590
+ const files = this.draftFiles().map((item) => item.file);
410
591
  this.sending.set(true);
411
- this.layoutTheme.setOverlayLoading(true);
412
592
  try {
413
- const participantId = axmChatAiPeerParticipantId(row.id);
414
- const avatar = row.professionImageUrl?.trim() || undefined;
415
- const entityId = this.entityId()?.trim();
416
- const entityType = this.entityType()?.trim();
417
- const assistRow = this.assistCatalog.getCachedAssist(row.id) ?? (await this.assistCatalog.getAssist(row.id));
418
- const assistSnapshot = assistRow ? this.assistCatalog.toSnapshot(assistRow) : undefined;
419
- const conversation = await this.conversationService.createConversation([participantId], 'bot', {
420
- title: this.translationService.resolve(row.title) ?? row.name,
421
- ...(avatar ? { avatar } : {}),
422
- icon: 'fa-solid fa-robot',
423
- metadata: {
424
- ...axmBuildAiChatConversationMetadata(row, assistSnapshot),
425
- ...(entityId && entityType ? { entityId, entityType } : {}),
426
- },
427
- });
428
- await this.conversationService.selectConversation(conversation.id);
429
- const payload = { type: 'text', text };
430
- await this.conversationService.sendMessage({ conversationId: conversation.id, type: 'text', payload });
431
- this.draftText.set('');
432
- this.conversationCreated.emit(conversation.id);
433
- }
434
- catch {
435
- const message = await this.translationService.translateAsync('@conversation:chat.empty-assist.errors.send');
436
- this.toastService.danger(message);
593
+ const conversationId = await this.emptyAssistSession.sendWelcome(text, files, this.onAssistConversationCreated);
594
+ if (conversationId) {
595
+ this.draftText.set('');
596
+ this.clearDraftFiles();
597
+ }
437
598
  }
438
599
  finally {
439
600
  this.sending.set(false);
440
- this.layoutTheme.setOverlayLoading(false);
441
601
  }
442
602
  }
603
+ //#endregion
604
+ //#region ----- Draft files -----
605
+ toDraftFile(file) {
606
+ const kind = this.classifyDraftFile(file);
607
+ return {
608
+ id: globalThis.crypto?.randomUUID?.() ?? `${file.name}-${file.size}-${Date.now()}`,
609
+ file,
610
+ kind,
611
+ preview: kind === 'image' || kind === 'video' ? URL.createObjectURL(file) : undefined,
612
+ };
613
+ }
614
+ classifyDraftFile(file) {
615
+ const type = file.type.toLowerCase();
616
+ if (type.startsWith('image/')) {
617
+ return 'image';
618
+ }
619
+ if (type.startsWith('video/')) {
620
+ return 'video';
621
+ }
622
+ if (type.startsWith('audio/')) {
623
+ return 'audio';
624
+ }
625
+ return 'file';
626
+ }
627
+ clearDraftFiles() {
628
+ this.draftFiles().forEach((item) => this.revokePreview(item.preview));
629
+ this.draftFiles.set([]);
630
+ }
631
+ revokePreview(preview) {
632
+ if (preview) {
633
+ URL.revokeObjectURL(preview);
634
+ }
635
+ }
636
+ //#endregion
637
+ //#region ----- Data Loading -----
443
638
  async ensureAssistsLoaded() {
444
639
  if (!this.assistsLoadPromise)
445
640
  this.assistsLoadPromise = this.loadAssistsFromApi();
@@ -453,7 +648,7 @@ class AXMChatEmptyAssistComposerComponent {
453
648
  rows = await this.assistCatalog.loadCatalogTargets(true);
454
649
  }
455
650
  this.rows.set(rows);
456
- this.selectedAssistId.set(await this.resolveInitialAssistId(rows));
651
+ this.selectedAssistId.set(await this.resolveAssistIdForMode(rows));
457
652
  }
458
653
  catch (error) {
459
654
  console.error('Failed to load AI chat targets (empty composer):', error);
@@ -466,17 +661,99 @@ class AXMChatEmptyAssistComposerComponent {
466
661
  this.loading.set(false);
467
662
  }
468
663
  }
469
- async resolveInitialAssistId(rows) {
470
- if (!rows.length)
664
+ hasDraftContent() {
665
+ return this.draftText().trim().length > 0 || this.draftFiles().length > 0;
666
+ }
667
+ canResolveAssist() {
668
+ if (this.assistSelection() === 'ask') {
669
+ return false;
670
+ }
671
+ const rows = this.rows();
672
+ if (this.assistSelection() === 'locked') {
673
+ const requestedId = this.initialAssistId()?.trim();
674
+ return !!requestedId && rows.some((row) => row.id === requestedId);
675
+ }
676
+ return rows.length > 0;
677
+ }
678
+ async ensureSelectedAssist() {
679
+ const current = this.selectedAssistId()?.trim();
680
+ if (current) {
681
+ this.emptyAssistSession.setSelectedAssistId(current);
682
+ return current;
683
+ }
684
+ const assistId = await this.resolveAssistIdForMode(this.rows());
685
+ if (!assistId) {
471
686
  return undefined;
687
+ }
688
+ this.selectedAssistId.set(assistId);
689
+ this.emptyAssistSession.setSelectedAssistId(assistId);
690
+ return assistId;
691
+ }
692
+ /**
693
+ * Header (`ask`) uses Global AI Settings → Default Assist.
694
+ * Form designer (`locked`) uses the assist id passed in (Form Designer → Default Assist).
695
+ */
696
+ async resolveAssistIdForMode(rows) {
472
697
  const requestedId = this.initialAssistId()?.trim();
473
- if (requestedId && rows.some((row) => row.id === requestedId))
474
- return requestedId;
698
+ const mode = this.assistSelection();
699
+ if (mode === 'locked') {
700
+ return requestedId ? this.includeAssistRow(rows, requestedId) : undefined;
701
+ }
702
+ if (requestedId) {
703
+ const matched = await this.includeAssistRow(rows, requestedId);
704
+ if (matched) {
705
+ return matched;
706
+ }
707
+ }
708
+ if (mode === 'ask') {
709
+ const defaultAssistId = await this.resolveDefaultAssistId();
710
+ return defaultAssistId ? this.includeAssistRow(rows, defaultAssistId) : undefined;
711
+ }
712
+ if (!rows.length) {
713
+ return undefined;
714
+ }
475
715
  const defaultAssistId = await this.resolveDefaultAssistId();
476
- if (defaultAssistId && rows.some((row) => row.id === defaultAssistId))
477
- return defaultAssistId;
716
+ if (defaultAssistId) {
717
+ const matched = await this.includeAssistRow(rows, defaultAssistId);
718
+ if (matched) {
719
+ return matched;
720
+ }
721
+ }
478
722
  return rows[0]?.id;
479
723
  }
724
+ /** Keeps a settings assist selectable even when it is outside the first catalog page. */
725
+ async includeAssistRow(rows, assistId) {
726
+ const id = assistId.trim();
727
+ if (!id) {
728
+ return undefined;
729
+ }
730
+ if (rows.some((row) => row.id === id) || this.rows().some((row) => row.id === id)) {
731
+ return id;
732
+ }
733
+ try {
734
+ const assist = await this.assistCatalog.getAssist(id);
735
+ const resolvedId = assist?.id?.trim();
736
+ if (!assist || !resolvedId) {
737
+ return undefined;
738
+ }
739
+ if (!this.rows().some((row) => row.id === resolvedId)) {
740
+ this.rows.update((current) => [
741
+ ...current,
742
+ {
743
+ id: resolvedId,
744
+ name: String(assist.name ?? ''),
745
+ title: assist.title ?? assist.name ?? resolvedId,
746
+ description: assist.description,
747
+ professionImageUrl: assist.professionImageUrl,
748
+ },
749
+ ]);
750
+ }
751
+ return resolvedId;
752
+ }
753
+ catch {
754
+ return undefined;
755
+ }
756
+ }
480
757
  async resolveDefaultAssistId() {
481
758
  if (!this.defaultAssistResolver)
482
759
  return undefined;
@@ -488,6 +765,8 @@ class AXMChatEmptyAssistComposerComponent {
488
765
  }
489
766
  }
490
767
  async loadStarterPromptsForAssist(assistId) {
768
+ this.starterAbort?.abort();
769
+ this.chipsLoading.set(false);
491
770
  const generation = ++this.starterPromptsLoadGeneration;
492
771
  const id = assistId?.trim();
493
772
  if (!id) {
@@ -502,14 +781,15 @@ class AXMChatEmptyAssistComposerComponent {
502
781
  }
503
782
  return;
504
783
  }
505
- const entityId = this.entityId()?.trim();
506
- const entityType = this.entityType()?.trim();
507
- const isEntityChat = Boolean(entityId && entityType);
784
+ const entityRef = this.resolveEntityChatRef();
785
+ const isEntityChat = Boolean(entityRef);
508
786
  const sendInPlace = this.sendStarterPromptsInPlace();
787
+ const launchContext = this.launchContext();
509
788
  this.starterPromptItems.set(axmAssistStarterPromptChipItems(assist, {
510
789
  assistId: id,
511
- ...(isEntityChat ? { entityId, entityType } : {}),
790
+ ...(entityRef ? { entityId: entityRef.entityId, entityType: entityRef.entityType } : {}),
512
791
  ...(sendInPlace || isEntityChat ? { navigate: false } : {}),
792
+ ...(launchContext ? { launchContext } : {}),
513
793
  }));
514
794
  }
515
795
  catch {
@@ -517,8 +797,76 @@ class AXMChatEmptyAssistComposerComponent {
517
797
  this.starterPromptItems.set([]);
518
798
  }
519
799
  }
800
+ async loadContextualChips(assistId, launchContext) {
801
+ const id = assistId?.trim();
802
+ if (!id) {
803
+ this.starterPromptItems.set([]);
804
+ this.chipsLoading.set(false);
805
+ return;
806
+ }
807
+ this.starterAbort?.abort();
808
+ const abort = new AbortController();
809
+ this.starterAbort = abort;
810
+ const generation = ++this.starterPromptsLoadGeneration;
811
+ this.chipsLoading.set(true);
812
+ try {
813
+ const locale = this.localeService.activeProfile()?.localeInfo?.code ?? 'en-US';
814
+ const context = axmSanitizeContextualAssistContext(launchContext);
815
+ const result = await this.commandService.execute(AIMANAGEMENT_STRUCTURED_TEXT_COMPLETION_COMMAND_KEY, {
816
+ assistId: id,
817
+ messages: [axpAiChatTextMessage('system', AXM_CONTEXTUAL_ASSIST_STARTER_SYSTEM_PROMPT), axpAiChatTextMessage('user', axmBuildContextualAssistStarterUserPrompt(context, locale))],
818
+ maxTokens: 512,
819
+ temperature: 0.3,
820
+ });
821
+ if (abort.signal.aborted || generation !== this.starterPromptsLoadGeneration) {
822
+ return;
823
+ }
824
+ const data = runtimeCommandPayloadData(result);
825
+ const items = axmParseContextualAssistStarterItems(data?.text ?? '');
826
+ const labels = items.length > 0 ? items : await this.fallbackChipLabels();
827
+ this.starterPromptItems.set(this.toInPlaceChipItems(labels, id, context));
828
+ }
829
+ catch {
830
+ if (!abort.signal.aborted && generation === this.starterPromptsLoadGeneration) {
831
+ const labels = await this.fallbackChipLabels();
832
+ this.starterPromptItems.set(this.toInPlaceChipItems(labels, id, axmSanitizeContextualAssistContext(launchContext)));
833
+ }
834
+ }
835
+ finally {
836
+ if (!abort.signal.aborted && generation === this.starterPromptsLoadGeneration) {
837
+ this.chipsLoading.set(false);
838
+ }
839
+ }
840
+ }
841
+ toInPlaceChipItems(labels, assistId, launchContext) {
842
+ const entityRef = this.resolveEntityChatRef();
843
+ return axmAssistStarterPromptChipItems({ starterPrompts: labels.map((text) => ({ text })) }, {
844
+ assistId,
845
+ navigate: false,
846
+ launchContext,
847
+ ...(entityRef ? { entityId: entityRef.entityId, entityType: entityRef.entityType } : {}),
848
+ });
849
+ }
850
+ resolveEntityChatRef() {
851
+ const entityId = this.entityId()?.trim();
852
+ const entityType = this.entityType()?.trim();
853
+ if (entityId && entityType) {
854
+ return { entityId, entityType };
855
+ }
856
+ return axmEntityRefFromLaunchContext(this.launchContext());
857
+ }
858
+ async fallbackChipLabels() {
859
+ const labels = [];
860
+ for (const key of FALLBACK_CHIP_KEYS) {
861
+ const text = (await this.translationService.translateAsync(key))?.trim();
862
+ if (text) {
863
+ labels.push(text);
864
+ }
865
+ }
866
+ return labels;
867
+ }
520
868
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMChatEmptyAssistComposerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
521
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMChatEmptyAssistComposerComponent, isStandalone: true, selector: "axm-chat-empty-assist-composer", inputs: { initialAssistId: { classPropertyName: "initialAssistId", publicName: "initialAssistId", isSignal: true, isRequired: false, transformFunction: null }, entityId: { classPropertyName: "entityId", publicName: "entityId", isSignal: true, isRequired: false, transformFunction: null }, entityType: { classPropertyName: "entityType", publicName: "entityType", isSignal: true, isRequired: false, transformFunction: null }, sendStarterPromptsInPlace: { classPropertyName: "sendStarterPromptsInPlace", publicName: "sendStarterPromptsInPlace", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { conversationCreated: "conversationCreated" }, host: { properties: { "class.axm-chat-empty-assist-composer-host--compact": "compact()", "attr.dir": "textDirection()", "style.direction": "textDirection()" }, classAttribute: "axm-chat-empty-assist-composer-host" }, viewQueries: [{ propertyName: "draftInput", first: true, predicate: ["draftInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
869
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMChatEmptyAssistComposerComponent, isStandalone: true, selector: "axm-chat-empty-assist-composer", inputs: { initialAssistId: { classPropertyName: "initialAssistId", publicName: "initialAssistId", isSignal: true, isRequired: false, transformFunction: null }, assistSelection: { classPropertyName: "assistSelection", publicName: "assistSelection", isSignal: true, isRequired: false, transformFunction: null }, acceptExternalFileDrop: { classPropertyName: "acceptExternalFileDrop", publicName: "acceptExternalFileDrop", isSignal: true, isRequired: false, transformFunction: null }, entityId: { classPropertyName: "entityId", publicName: "entityId", isSignal: true, isRequired: false, transformFunction: null }, entityType: { classPropertyName: "entityType", publicName: "entityType", isSignal: true, isRequired: false, transformFunction: null }, sendStarterPromptsInPlace: { classPropertyName: "sendStarterPromptsInPlace", publicName: "sendStarterPromptsInPlace", isSignal: true, isRequired: false, transformFunction: null }, launchContext: { classPropertyName: "launchContext", publicName: "launchContext", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, assistConversationCreated: { classPropertyName: "assistConversationCreated", publicName: "assistConversationCreated", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { conversationCreated: "conversationCreated" }, host: { properties: { "class.axm-chat-empty-assist-composer-host--compact": "compact()", "attr.dir": "textDirection()", "style.direction": "textDirection()" }, classAttribute: "axm-chat-empty-assist-composer-host" }, viewQueries: [{ propertyName: "draftInput", first: true, predicate: ["draftInput"], descendants: true, isSignal: true }, { propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
522
870
  <div class="axm-chat-empty-assist-composer">
523
871
  <div class="axm-chat-empty-assist-composer__hero">
524
872
  @if (compact()) {
@@ -527,92 +875,173 @@ class AXMChatEmptyAssistComposerComponent {
527
875
  </span>
528
876
  }
529
877
  <div class="axm-chat-empty-assist-composer__caption">
530
- {{ '@conversation:chat.empty-assist.caption' | translate | async }}
878
+ @if (asksForAssist()) {
879
+ {{ '@conversation:mini-chat.choose-assist.title' | translate | async }}
880
+ }
881
+ <!-- ----- Default welcome when an assist is already chosen or selection is optional ----- -->
882
+ @else {
883
+ {{ (compact() ? '@conversation:mini-chat.empty.title' : '@conversation:chat.empty-assist.caption') | translate | async }}
884
+ }
531
885
  </div>
532
- </div>
533
-
534
- <div class="axm-chat-empty-assist-composer__composer-row">
535
- @if (!compact()) {
536
- <ax-button color="default" look="blank" class="rounded-full" [disabled]="isBusy()">
537
- <ax-icon icon="fa-regular fa-sparkles"></ax-icon>
538
- <span class="axm-chat-empty-assist-composer__assist-text">
539
- @if (selectedAssistLabel(); as assistLabel) {
540
- {{ assistLabel | translate | async }}
541
- } @else {
542
- {{ '@conversation:chat.empty-assist.assist-default' | translate | async }}
543
- }
544
- </span>
545
- @if (!isBusy()) {
546
- <ax-dropdown-panel>
547
- <ax-button-item-list>
548
- @for (row of rows(); track row.id) {
549
- <ax-button-item [text]="(row.title | translate | async)!" [selected]="row.id === selectedAssistId()" (onClick)="onAssistIdChange(row.id)"></ax-button-item>
550
- }
551
- </ax-button-item-list>
552
- </ax-dropdown-panel>
553
- }
554
- </ax-button>
555
- } @else {
556
- <ax-button
557
- color="default"
558
- look="blank"
559
- class="axm-chat-empty-assist-composer__assist-trigger rounded-full"
560
- [iconOnly]="true"
561
- [disabled]="isBusy()"
562
- [attr.aria-label]="('@conversation:chat.empty-assist.assist-default' | translate | async) ?? ''"
563
- >
564
- <ax-icon icon="fa-regular fa-sparkles"></ax-icon>
565
- @if (!isBusy()) {
566
- <ax-dropdown-panel>
567
- <ax-button-item-list>
568
- @for (row of rows(); track row.id) {
569
- <ax-button-item [text]="(row.title | translate | async)!" [selected]="row.id === selectedAssistId()" (onClick)="onAssistIdChange(row.id)"></ax-button-item>
570
- }
571
- </ax-button-item-list>
572
- </ax-dropdown-panel>
573
- }
574
- </ax-button>
886
+ @if (asksForAssist()) {
887
+ <p class="axm-chat-empty-assist-composer__subtitle">
888
+ {{ '@conversation:mini-chat.choose-assist.subtitle' | translate | async }}
889
+ </p>
575
890
  }
891
+ <!-- ----- Compact welcome subtitle when the header is not waiting for an assist ----- -->
892
+ @else if (compact()) {
893
+ <p class="axm-chat-empty-assist-composer__subtitle">
894
+ {{ '@conversation:mini-chat.empty.subtitle' | translate | async }}
895
+ </p>
896
+ }
897
+ </div>
576
898
 
577
- @let assistTitle = (selectedAssistLabel() | translate | async) ?? '';
578
-
579
- <textarea
580
- #draftInput
581
- rows="1"
582
- class="axm-chat-empty-assist-composer__textarea border-none outline-none w-full flex-1"
583
- [ngModel]="draftText()"
584
- (ngModelChange)="draftText.set($event ?? '')"
585
- (input)="resizeDraftInput()"
586
- (keydown)="onDraftKeydown($event)"
587
- [disabled]="isBusy()"
588
- [placeholder]="('@conversation:chat.empty-assist.placeholder' | translate: { params: { assist: assistTitle } } | async) ?? ''"
589
- ></textarea>
590
- <ax-button
591
- color="primary"
592
- class="axm-chat-empty-assist-composer__send rounded-full"
593
- [disabled]="!canSend()"
594
- [text]="sending() ? '…' : ''"
595
- [attr.aria-label]="('@conversation:comments.actions.send.title' | translate | async) ?? ''"
596
- (onClick)="send()"
597
- >
598
- @if (!sending()) {
599
- <ax-icon icon="fa-light fa-arrow-up"></ax-icon>
899
+ <!-- ----- Lookalike composer: attach and type stay local until send ----- -->
900
+ @if (!hasActiveConversation()) {
901
+ <div class="axm-chat-empty-assist-composer__composer">
902
+ <!---->
903
+ <!-- ----- Local attachment chips before the first message is sent ----- -->
904
+ @if (draftFiles().length > 0) {
905
+ <div class="axm-chat-empty-assist-composer__attachments">
906
+ <div class="axm-chat-empty-assist-composer__attachments-scroll" role="list">
907
+ @for (attachment of draftFiles(); track attachment.id) {
908
+ <div
909
+ class="axm-chat-empty-assist-composer__attachment"
910
+ role="listitem"
911
+ [class.axm-chat-empty-assist-composer__attachment--media]="attachment.kind === 'image' || attachment.kind === 'video'"
912
+ [class.axm-chat-empty-assist-composer__attachment--file]="attachment.kind === 'audio' || attachment.kind === 'file'"
913
+ [title]="attachment.file.name"
914
+ >
915
+ <!---->
916
+ <!-- ----- Image thumbnail ----- -->
917
+ @if (attachment.kind === 'image' && attachment.preview) {
918
+ <span class="axm-chat-empty-assist-composer__attachment-thumb" aria-hidden="true">
919
+ <img [src]="attachment.preview" alt="" />
920
+ </span>
921
+ }
922
+ <!-- ----- Video thumbnail ----- -->
923
+ @else if (attachment.kind === 'video' && attachment.preview) {
924
+ <span class="axm-chat-empty-assist-composer__attachment-thumb" aria-hidden="true">
925
+ <video [src]="attachment.preview" muted playsinline preload="metadata"></video>
926
+ </span>
927
+ }
928
+ <!-- ----- File name fallback ----- -->
929
+ @else {
930
+ <span class="axm-chat-empty-assist-composer__attachment-icon" aria-hidden="true">
931
+ <i [class]="draftFileIcon(attachment.kind)"></i>
932
+ </span>
933
+ <span class="axm-chat-empty-assist-composer__attachment-name">{{ attachment.file.name }}</span>
934
+ }
935
+ <button
936
+ type="button"
937
+ class="axm-chat-empty-assist-composer__attachment-remove"
938
+ [attr.aria-label]="('@acorex:chat.actions.remove-attachment' | translate | async) ?? ''"
939
+ (click)="removeDraftFile(attachment.id); $event.stopPropagation()"
940
+ >
941
+ <i class="fa-solid fa-xmark"></i>
942
+ </button>
943
+ </div>
944
+ }
945
+ <ax-button
946
+ class="axm-chat-empty-assist-composer__attachment-add ax-sm rounded-full"
947
+ look="blank"
948
+ [iconOnly]="true"
949
+ [disabled]="isBusy() || draftFiles().length >= maxDraftFiles"
950
+ [text]="('@acorex:chat.pickers.common.add-more' | translate | async) ?? ''"
951
+ (onClick)="browseDraftFiles()"
952
+ >
953
+ <ax-prefix>
954
+ <i class="fa-light fa-plus"></i>
955
+ </ax-prefix>
956
+ </ax-button>
957
+ </div>
958
+ </div>
600
959
  }
601
- </ax-button>
602
- </div>
960
+ <div class="axm-chat-empty-assist-composer__input-row">
961
+ <div class="axm-chat-empty-assist-composer__actions-start">
962
+ <ax-button
963
+ class="rounded-full text-xl"
964
+ look="blank"
965
+ [iconOnly]="true"
966
+ [disabled]="isBusy() || draftFiles().length >= maxDraftFiles"
967
+ [text]="('@acorex:chat.tooltips.attach-file' | translate | async) ?? ''"
968
+ (onClick)="browseDraftFiles()"
969
+ >
970
+ <ax-prefix>
971
+ <i class="fa-light fa-paperclip"></i>
972
+ </ax-prefix>
973
+ </ax-button>
974
+ @if (assistSelection() !== 'locked') {
975
+ <ax-button class="rounded-full axm-chat-composer-assist-picker" look="blank" [iconOnly]="true" [disabled]="isBusy() || rows().length === 0" [text]="('@conversation:chat.empty-assist.assist-default' | translate | async) ?? ''">
976
+ <ax-prefix>
977
+ <i class="fa-light fa-sparkles"></i>
978
+ </ax-prefix>
979
+ <!---->
980
+ <!-- ----- Assist list as a dropdown, not a popup ----- -->
981
+ @if (rows().length > 0) {
982
+ <ax-dropdown-panel>
983
+ <axm-chat-composer-assist-picker />
984
+ </ax-dropdown-panel>
985
+ }
986
+ </ax-button>
987
+ }
988
+ </div>
989
+ <textarea
990
+ #draftInput
991
+ rows="1"
992
+ class="axm-chat-empty-assist-composer__textarea"
993
+ [ngModel]="draftText()"
994
+ (ngModelChange)="draftText.set($event ?? '')"
995
+ (input)="resizeDraftInput()"
996
+ (keydown)="onDraftKeydown($event)"
997
+ [disabled]="isBusy()"
998
+ [placeholder]="draftPlaceholder()"
999
+ ></textarea>
1000
+ <div class="axm-chat-empty-assist-composer__primary">
1001
+ <ax-button
1002
+ class="rounded-full"
1003
+ [class.composer-primary-button--send]="canSend()"
1004
+ look="blank"
1005
+ color="primary"
1006
+ [iconOnly]="true"
1007
+ [disabled]="!canSend()"
1008
+ [text]="('@acorex:chat.actions.send' | translate | async) ?? ''"
1009
+ (onClick)="send()"
1010
+ >
1011
+ <ax-prefix>
1012
+ <i class="fa-solid fa-paper-plane-top"></i>
1013
+ </ax-prefix>
1014
+ </ax-button>
1015
+ </div>
1016
+ </div>
1017
+ <input #fileInput class="axm-chat-empty-assist-composer__file-input" type="file" multiple (change)="onDraftFilesSelected($event)" />
1018
+ </div>
1019
+ }
603
1020
 
604
- @if (starterPromptItems().length > 0) {
1021
+ <!-- ----- Starter chips loading indicator ----- -->
1022
+ @if (chipsLoading()) {
1023
+ <div class="axm-chat-empty-assist-composer__chips-loading">
1024
+ <ax-loading></ax-loading>
1025
+ <p class="axm-chat-empty-assist-composer__suggestions-label">
1026
+ {{ '@conversation:chat.contextual-assist.chips-loading' | translate | async }}
1027
+ </p>
1028
+ </div>
1029
+ }
1030
+ <!-- ----- Starter suggestion chips ----- -->
1031
+ @else if (starterPromptItems().length > 0) {
605
1032
  <div class="axm-chat-empty-assist-composer__suggestions">
1033
+ <!---->
1034
+ <!-- ----- Compact starter section label ----- -->
606
1035
  @if (compact()) {
607
1036
  <p class="axm-chat-empty-assist-composer__suggestions-label">
608
- {{ '@conversation:chat.starter-prompts.section-label' | translate | async }}
1037
+ {{ '@conversation:mini-chat.try-asking' | translate | async }}
609
1038
  </p>
610
1039
  }
611
- <axm-assist-suggestion-chips [items]="starterPromptItems()" [compact]="compact()" [sendInPlace]="sendStarterPromptsInPlace()" [disabled]="isBusy()" (conversationCreated)="conversationCreated.emit($event)" />
1040
+ <axm-assist-suggestion-chips [items]="starterPromptItems()" [compact]="compact()" [sendInPlace]="sendStarterPromptsInPlace()" [disabled]="loading()" (conversationCreated)="conversationCreated.emit($event)" />
612
1041
  </div>
613
1042
  }
614
1043
  </div>
615
- `, isInline: true, styles: [":host{display:block;width:100%}.axm-chat-empty-assist-composer{display:flex;flex-direction:column;gap:1rem;width:min(100%,64rem);margin-inline:auto}.axm-chat-empty-assist-composer__hero{display:flex;flex-direction:column;align-items:center;gap:.625rem}.axm-chat-empty-assist-composer__hero-icon{display:grid;place-items:center;width:2.5rem;height:2.5rem;border-radius:.875rem;color:rgb(var(--ax-sys-color-primary-600));background:linear-gradient(145deg,rgba(var(--ax-sys-color-primary-500),.14),rgba(var(--ax-sys-color-primary-500),.04));border:1px solid rgba(var(--ax-sys-color-primary-500),.18)}.axm-chat-empty-assist-composer__caption{margin-bottom:3rem;text-align:center;font-size:clamp(1.9rem,3.4vw,2.7rem);line-height:1.15;font-weight:700;letter-spacing:-.02em;color:rgb(var(--ax-sys-color-on-surface))}.axm-chat-empty-assist-composer__composer-row{display:flex;gap:.375rem;align-items:flex-end;padding:.5rem;border-radius:2em;border:1px solid rgb(var(--ax-sys-color-border-light-surface));background:rgb(var(--ax-sys-color-lightest-surface))}.axm-chat-empty-assist-composer__assist-text{display:inline-block;max-width:12rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.axm-chat-empty-assist-composer__send{flex-shrink:0}.axm-chat-empty-assist-composer__suggestions{display:flex;flex-direction:column;gap:.5rem}.axm-chat-empty-assist-composer__suggestions-label{margin:0;padding-inline:.125rem;font-size:.6875rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:rgb(var(--ax-sys-color-on-surface));opacity:.52}.axm-chat-empty-assist-composer__textarea{resize:none;overflow:hidden;background:transparent;min-height:2.25rem;max-height:14rem;line-height:1.5;padding-block:.25rem;font-size:inherit;text-align:start;color:rgb(var(--ax-sys-color-on-surface))}.axm-chat-empty-assist-composer__textarea::placeholder{color:rgb(var(--ax-sys-color-on-surface));opacity:.45}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer{width:100%;max-width:none;gap:.875rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__hero{gap:.5rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__caption{margin-bottom:0;font-size:1.125rem;line-height:1.35;font-weight:600;letter-spacing:-.015em}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__composer-row{gap:.25rem;align-items:center;padding-block:.375rem;padding-inline:.375rem .4375rem;border-radius:1.5rem;border-color:rgba(var(--ax-sys-color-border-light-surface),.95);background:rgba(var(--ax-sys-color-surface),.35)}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__assist-trigger{color:rgb(var(--ax-sys-color-primary-600));opacity:.9}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__assist-text{max-width:5.5rem;font-size:.8125rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__textarea{min-height:1.625rem;max-height:8rem;line-height:1.4;font-size:.875rem;padding-block:.125rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__suggestions{gap:.375rem;margin-top:.125rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i2.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "component", type: i2.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i2.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i3.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXTextBoxModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i4.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXMAssistSuggestionChipsComponent, selector: "axm-assist-suggestion-chips", inputs: ["items", "compact", "sendInPlace", "disabled"], outputs: ["conversationCreated"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1044
+ `, isInline: true, styles: [":host{display:block;width:100%}:host(.axm-chat-empty-assist-composer-host--compact){display:flex;height:100%;min-height:0}.axm-chat-empty-assist-composer{display:flex;flex-direction:column;gap:1rem;width:min(100%,64rem);margin-inline:auto}.axm-chat-empty-assist-composer__hero{display:flex;flex-direction:column;align-items:center;gap:.625rem}.axm-chat-empty-assist-composer__hero-icon{display:grid;place-items:center;width:2.5rem;height:2.5rem;border-radius:.875rem;color:rgb(var(--ax-sys-color-primary-600));background:linear-gradient(145deg,rgba(var(--ax-sys-color-primary-500),.14),rgba(var(--ax-sys-color-primary-500),.04));border:1px solid rgba(var(--ax-sys-color-primary-500),.18)}.axm-chat-empty-assist-composer__composer{position:relative;display:flex;flex-direction:column;width:100%;overflow:hidden;border-radius:2em;border:1px solid rgb(var(--ax-sys-color-border-light-surface));background:rgb(var(--ax-sys-color-lightest-surface))}.axm-chat-empty-assist-composer__attachments{display:flex;align-items:center;width:100%;min-width:0;padding:.125rem .5rem 0;border-bottom:1px solid rgba(var(--ax-sys-color-border-light-surface),.65)}.axm-chat-empty-assist-composer__attachments-scroll{display:flex;flex:1 1 auto;flex-wrap:nowrap;align-items:center;gap:.375rem;min-width:0;width:0;overflow-x:auto;overflow-y:hidden;padding-block:.375rem;scrollbar-width:thin}.axm-chat-empty-assist-composer__attachment{position:relative;display:flex;flex-shrink:0;align-items:center}.axm-chat-empty-assist-composer__attachment--file{width:9.5rem;max-width:9.5rem;height:2rem;gap:.375rem;padding-inline:.375rem .5rem;border-radius:.5rem;border:1px solid rgba(var(--ax-sys-color-border-light-surface),.85);background:rgb(var(--ax-sys-color-surface))}.axm-chat-empty-assist-composer__attachment-thumb{display:flex;width:2rem;height:2rem;align-items:center;justify-content:center;overflow:hidden;border-radius:.5rem;border:1px solid rgba(var(--ax-sys-color-border-light-surface),.85);background:rgb(var(--ax-sys-color-surface))}.axm-chat-empty-assist-composer__attachment-thumb img,.axm-chat-empty-assist-composer__attachment-thumb video{width:100%;height:100%;object-fit:cover}.axm-chat-empty-assist-composer__attachment-icon{display:flex;width:1.25rem;height:1.25rem;flex-shrink:0;align-items:center;justify-content:center;font-size:.75rem;color:rgb(var(--ax-sys-color-on-surface));opacity:.72}.axm-chat-empty-assist-composer__attachment-name{min-width:0;flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.75rem;color:rgb(var(--ax-sys-color-on-surface))}.axm-chat-empty-assist-composer__attachment-remove{position:absolute;inset-block-start:-.25rem;inset-inline-end:-.25rem;z-index:1;display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;padding:0;border:none;border-radius:9999px;background:rgb(var(--ax-sys-color-danger-500));color:rgb(var(--ax-sys-color-lightest-surface));font-size:.5rem;cursor:pointer;opacity:0;pointer-events:none;transition:opacity .15s ease}.axm-chat-empty-assist-composer__attachment:hover .axm-chat-empty-assist-composer__attachment-remove,.axm-chat-empty-assist-composer__attachment:focus-within .axm-chat-empty-assist-composer__attachment-remove,.axm-chat-empty-assist-composer__attachment-remove:focus-visible{opacity:1;pointer-events:auto}@media(hover:none){.axm-chat-empty-assist-composer__attachment-remove{opacity:1;pointer-events:auto}}.axm-chat-empty-assist-composer__attachment-add{flex-shrink:0;align-self:center}.axm-chat-empty-assist-composer__attachment-add ax-prefix i{font-size:.875rem;line-height:1}.axm-chat-empty-assist-composer__input-row{display:flex;width:100%;min-width:0;align-items:center;gap:.5rem;padding:.25rem .5rem}.axm-chat-empty-assist-composer__actions-start,.axm-chat-empty-assist-composer__primary{display:flex;flex-shrink:0;align-items:center}.axm-chat-empty-assist-composer__actions-start ax-icon i,.axm-chat-empty-assist-composer__actions-start ax-prefix i,.axm-chat-empty-assist-composer__primary ax-prefix i{font-size:1.375rem;line-height:1}.axm-chat-empty-assist-composer__textarea{flex:1 1 auto;min-width:0;min-height:2.25rem;max-height:14rem;margin:0;padding-block:.25rem;border:none;outline:none;resize:none;overflow:hidden;background:transparent;font-size:inherit;line-height:1.5;color:rgb(var(--ax-sys-color-on-surface))}.axm-chat-empty-assist-composer__textarea::placeholder{color:rgb(var(--ax-sys-color-on-surface));opacity:.45}.axm-chat-empty-assist-composer__file-input{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)}.axm-chat-empty-assist-composer__caption{margin-bottom:1.25rem;text-align:center;font-size:clamp(1.9rem,3.4vw,2.7rem);line-height:1.15;font-weight:700;letter-spacing:-.02em;color:rgb(var(--ax-sys-color-on-surface))}.axm-chat-empty-assist-composer__suggestions{display:flex;flex-direction:column;gap:.5rem}.axm-chat-empty-assist-composer__suggestions-label{margin:0;padding-inline:.125rem;font-size:.6875rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:rgb(var(--ax-sys-color-on-surface));opacity:.52}.axm-chat-empty-assist-composer__chips-loading{display:flex;flex-direction:column;align-items:center;gap:.5rem;padding-block:.5rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer{width:100%;max-width:none;height:100%;min-height:0;gap:.875rem;justify-content:center;overflow:hidden;padding-block:.5rem .125rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__hero{flex-shrink:0;gap:.5rem;padding-block-start:.5rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__hero-icon{width:2.5rem;height:2.5rem;border-radius:.875rem;color:rgb(var(--ax-sys-color-primary-600))}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__caption{margin-bottom:0;font-size:1.125rem;line-height:1.3;font-weight:700;letter-spacing:-.03em}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__composer{flex-shrink:0;width:100%;border-radius:1.5rem;border-color:rgba(var(--ax-sys-color-border-light-surface),.95)}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__textarea{min-height:1.625rem;max-height:8rem;font-size:.875rem;line-height:1.4}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__subtitle{margin:0;max-width:20rem;text-align:center;font-size:.8125rem;line-height:1.5;font-weight:400;color:rgb(var(--ax-sys-color-on-surface));opacity:.58}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__suggestions,:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__chips-loading{display:flex;flex-direction:column;flex:0 1 auto;min-height:0;overflow:auto;justify-content:flex-start}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__suggestions{gap:.5rem;margin-top:.125rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__suggestions-label{flex-shrink:0;margin-block-end:.125rem;letter-spacing:.02em;text-transform:none;font-size:.75rem;font-weight:500;opacity:.55}:host(.axm-chat-empty-assist-composer-host--compact) axm-assist-suggestion-chips{flex:0 1 auto;min-height:0;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i2.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i4.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i5.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXMAssistSuggestionChipsComponent, selector: "axm-assist-suggestion-chips", inputs: ["items", "compact", "sendInPlace", "disabled"], outputs: ["conversationCreated"] }, { kind: "component", type: AXMChatComposerAssistPickerComponent, selector: "axm-chat-composer-assist-picker", inputs: ["__popup__"] }, { kind: "pipe", type: i6.AsyncPipe, name: "async" }, { kind: "pipe", type: i7.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
616
1045
  }
617
1046
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMChatEmptyAssistComposerComponent, decorators: [{
618
1047
  type: Component,
@@ -621,7 +1050,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
621
1050
  '[class.axm-chat-empty-assist-composer-host--compact]': 'compact()',
622
1051
  '[attr.dir]': 'textDirection()',
623
1052
  '[style.direction]': 'textDirection()',
624
- }, imports: [CommonModule, FormsModule, AXButtonModule, AXDropdownModule, AXTextBoxModule, AXDecoratorModule, AXTranslationModule, AXMAssistSuggestionChipsComponent], template: `
1053
+ }, imports: [CommonModule, FormsModule, AXButtonModule, AXDecoratorModule, AXDropdownModule, AXLoadingModule, AXTranslationModule, AXMAssistSuggestionChipsComponent, AXMChatComposerAssistPickerComponent], template: `
625
1054
  <div class="axm-chat-empty-assist-composer">
626
1055
  <div class="axm-chat-empty-assist-composer__hero">
627
1056
  @if (compact()) {
@@ -630,93 +1059,174 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
630
1059
  </span>
631
1060
  }
632
1061
  <div class="axm-chat-empty-assist-composer__caption">
633
- {{ '@conversation:chat.empty-assist.caption' | translate | async }}
1062
+ @if (asksForAssist()) {
1063
+ {{ '@conversation:mini-chat.choose-assist.title' | translate | async }}
1064
+ }
1065
+ <!-- ----- Default welcome when an assist is already chosen or selection is optional ----- -->
1066
+ @else {
1067
+ {{ (compact() ? '@conversation:mini-chat.empty.title' : '@conversation:chat.empty-assist.caption') | translate | async }}
1068
+ }
634
1069
  </div>
635
- </div>
636
-
637
- <div class="axm-chat-empty-assist-composer__composer-row">
638
- @if (!compact()) {
639
- <ax-button color="default" look="blank" class="rounded-full" [disabled]="isBusy()">
640
- <ax-icon icon="fa-regular fa-sparkles"></ax-icon>
641
- <span class="axm-chat-empty-assist-composer__assist-text">
642
- @if (selectedAssistLabel(); as assistLabel) {
643
- {{ assistLabel | translate | async }}
644
- } @else {
645
- {{ '@conversation:chat.empty-assist.assist-default' | translate | async }}
646
- }
647
- </span>
648
- @if (!isBusy()) {
649
- <ax-dropdown-panel>
650
- <ax-button-item-list>
651
- @for (row of rows(); track row.id) {
652
- <ax-button-item [text]="(row.title | translate | async)!" [selected]="row.id === selectedAssistId()" (onClick)="onAssistIdChange(row.id)"></ax-button-item>
653
- }
654
- </ax-button-item-list>
655
- </ax-dropdown-panel>
656
- }
657
- </ax-button>
658
- } @else {
659
- <ax-button
660
- color="default"
661
- look="blank"
662
- class="axm-chat-empty-assist-composer__assist-trigger rounded-full"
663
- [iconOnly]="true"
664
- [disabled]="isBusy()"
665
- [attr.aria-label]="('@conversation:chat.empty-assist.assist-default' | translate | async) ?? ''"
666
- >
667
- <ax-icon icon="fa-regular fa-sparkles"></ax-icon>
668
- @if (!isBusy()) {
669
- <ax-dropdown-panel>
670
- <ax-button-item-list>
671
- @for (row of rows(); track row.id) {
672
- <ax-button-item [text]="(row.title | translate | async)!" [selected]="row.id === selectedAssistId()" (onClick)="onAssistIdChange(row.id)"></ax-button-item>
673
- }
674
- </ax-button-item-list>
675
- </ax-dropdown-panel>
676
- }
677
- </ax-button>
1070
+ @if (asksForAssist()) {
1071
+ <p class="axm-chat-empty-assist-composer__subtitle">
1072
+ {{ '@conversation:mini-chat.choose-assist.subtitle' | translate | async }}
1073
+ </p>
678
1074
  }
1075
+ <!-- ----- Compact welcome subtitle when the header is not waiting for an assist ----- -->
1076
+ @else if (compact()) {
1077
+ <p class="axm-chat-empty-assist-composer__subtitle">
1078
+ {{ '@conversation:mini-chat.empty.subtitle' | translate | async }}
1079
+ </p>
1080
+ }
1081
+ </div>
679
1082
 
680
- @let assistTitle = (selectedAssistLabel() | translate | async) ?? '';
681
-
682
- <textarea
683
- #draftInput
684
- rows="1"
685
- class="axm-chat-empty-assist-composer__textarea border-none outline-none w-full flex-1"
686
- [ngModel]="draftText()"
687
- (ngModelChange)="draftText.set($event ?? '')"
688
- (input)="resizeDraftInput()"
689
- (keydown)="onDraftKeydown($event)"
690
- [disabled]="isBusy()"
691
- [placeholder]="('@conversation:chat.empty-assist.placeholder' | translate: { params: { assist: assistTitle } } | async) ?? ''"
692
- ></textarea>
693
- <ax-button
694
- color="primary"
695
- class="axm-chat-empty-assist-composer__send rounded-full"
696
- [disabled]="!canSend()"
697
- [text]="sending() ? '…' : ''"
698
- [attr.aria-label]="('@conversation:comments.actions.send.title' | translate | async) ?? ''"
699
- (onClick)="send()"
700
- >
701
- @if (!sending()) {
702
- <ax-icon icon="fa-light fa-arrow-up"></ax-icon>
1083
+ <!-- ----- Lookalike composer: attach and type stay local until send ----- -->
1084
+ @if (!hasActiveConversation()) {
1085
+ <div class="axm-chat-empty-assist-composer__composer">
1086
+ <!---->
1087
+ <!-- ----- Local attachment chips before the first message is sent ----- -->
1088
+ @if (draftFiles().length > 0) {
1089
+ <div class="axm-chat-empty-assist-composer__attachments">
1090
+ <div class="axm-chat-empty-assist-composer__attachments-scroll" role="list">
1091
+ @for (attachment of draftFiles(); track attachment.id) {
1092
+ <div
1093
+ class="axm-chat-empty-assist-composer__attachment"
1094
+ role="listitem"
1095
+ [class.axm-chat-empty-assist-composer__attachment--media]="attachment.kind === 'image' || attachment.kind === 'video'"
1096
+ [class.axm-chat-empty-assist-composer__attachment--file]="attachment.kind === 'audio' || attachment.kind === 'file'"
1097
+ [title]="attachment.file.name"
1098
+ >
1099
+ <!---->
1100
+ <!-- ----- Image thumbnail ----- -->
1101
+ @if (attachment.kind === 'image' && attachment.preview) {
1102
+ <span class="axm-chat-empty-assist-composer__attachment-thumb" aria-hidden="true">
1103
+ <img [src]="attachment.preview" alt="" />
1104
+ </span>
1105
+ }
1106
+ <!-- ----- Video thumbnail ----- -->
1107
+ @else if (attachment.kind === 'video' && attachment.preview) {
1108
+ <span class="axm-chat-empty-assist-composer__attachment-thumb" aria-hidden="true">
1109
+ <video [src]="attachment.preview" muted playsinline preload="metadata"></video>
1110
+ </span>
1111
+ }
1112
+ <!-- ----- File name fallback ----- -->
1113
+ @else {
1114
+ <span class="axm-chat-empty-assist-composer__attachment-icon" aria-hidden="true">
1115
+ <i [class]="draftFileIcon(attachment.kind)"></i>
1116
+ </span>
1117
+ <span class="axm-chat-empty-assist-composer__attachment-name">{{ attachment.file.name }}</span>
1118
+ }
1119
+ <button
1120
+ type="button"
1121
+ class="axm-chat-empty-assist-composer__attachment-remove"
1122
+ [attr.aria-label]="('@acorex:chat.actions.remove-attachment' | translate | async) ?? ''"
1123
+ (click)="removeDraftFile(attachment.id); $event.stopPropagation()"
1124
+ >
1125
+ <i class="fa-solid fa-xmark"></i>
1126
+ </button>
1127
+ </div>
1128
+ }
1129
+ <ax-button
1130
+ class="axm-chat-empty-assist-composer__attachment-add ax-sm rounded-full"
1131
+ look="blank"
1132
+ [iconOnly]="true"
1133
+ [disabled]="isBusy() || draftFiles().length >= maxDraftFiles"
1134
+ [text]="('@acorex:chat.pickers.common.add-more' | translate | async) ?? ''"
1135
+ (onClick)="browseDraftFiles()"
1136
+ >
1137
+ <ax-prefix>
1138
+ <i class="fa-light fa-plus"></i>
1139
+ </ax-prefix>
1140
+ </ax-button>
1141
+ </div>
1142
+ </div>
703
1143
  }
704
- </ax-button>
705
- </div>
1144
+ <div class="axm-chat-empty-assist-composer__input-row">
1145
+ <div class="axm-chat-empty-assist-composer__actions-start">
1146
+ <ax-button
1147
+ class="rounded-full text-xl"
1148
+ look="blank"
1149
+ [iconOnly]="true"
1150
+ [disabled]="isBusy() || draftFiles().length >= maxDraftFiles"
1151
+ [text]="('@acorex:chat.tooltips.attach-file' | translate | async) ?? ''"
1152
+ (onClick)="browseDraftFiles()"
1153
+ >
1154
+ <ax-prefix>
1155
+ <i class="fa-light fa-paperclip"></i>
1156
+ </ax-prefix>
1157
+ </ax-button>
1158
+ @if (assistSelection() !== 'locked') {
1159
+ <ax-button class="rounded-full axm-chat-composer-assist-picker" look="blank" [iconOnly]="true" [disabled]="isBusy() || rows().length === 0" [text]="('@conversation:chat.empty-assist.assist-default' | translate | async) ?? ''">
1160
+ <ax-prefix>
1161
+ <i class="fa-light fa-sparkles"></i>
1162
+ </ax-prefix>
1163
+ <!---->
1164
+ <!-- ----- Assist list as a dropdown, not a popup ----- -->
1165
+ @if (rows().length > 0) {
1166
+ <ax-dropdown-panel>
1167
+ <axm-chat-composer-assist-picker />
1168
+ </ax-dropdown-panel>
1169
+ }
1170
+ </ax-button>
1171
+ }
1172
+ </div>
1173
+ <textarea
1174
+ #draftInput
1175
+ rows="1"
1176
+ class="axm-chat-empty-assist-composer__textarea"
1177
+ [ngModel]="draftText()"
1178
+ (ngModelChange)="draftText.set($event ?? '')"
1179
+ (input)="resizeDraftInput()"
1180
+ (keydown)="onDraftKeydown($event)"
1181
+ [disabled]="isBusy()"
1182
+ [placeholder]="draftPlaceholder()"
1183
+ ></textarea>
1184
+ <div class="axm-chat-empty-assist-composer__primary">
1185
+ <ax-button
1186
+ class="rounded-full"
1187
+ [class.composer-primary-button--send]="canSend()"
1188
+ look="blank"
1189
+ color="primary"
1190
+ [iconOnly]="true"
1191
+ [disabled]="!canSend()"
1192
+ [text]="('@acorex:chat.actions.send' | translate | async) ?? ''"
1193
+ (onClick)="send()"
1194
+ >
1195
+ <ax-prefix>
1196
+ <i class="fa-solid fa-paper-plane-top"></i>
1197
+ </ax-prefix>
1198
+ </ax-button>
1199
+ </div>
1200
+ </div>
1201
+ <input #fileInput class="axm-chat-empty-assist-composer__file-input" type="file" multiple (change)="onDraftFilesSelected($event)" />
1202
+ </div>
1203
+ }
706
1204
 
707
- @if (starterPromptItems().length > 0) {
1205
+ <!-- ----- Starter chips loading indicator ----- -->
1206
+ @if (chipsLoading()) {
1207
+ <div class="axm-chat-empty-assist-composer__chips-loading">
1208
+ <ax-loading></ax-loading>
1209
+ <p class="axm-chat-empty-assist-composer__suggestions-label">
1210
+ {{ '@conversation:chat.contextual-assist.chips-loading' | translate | async }}
1211
+ </p>
1212
+ </div>
1213
+ }
1214
+ <!-- ----- Starter suggestion chips ----- -->
1215
+ @else if (starterPromptItems().length > 0) {
708
1216
  <div class="axm-chat-empty-assist-composer__suggestions">
1217
+ <!---->
1218
+ <!-- ----- Compact starter section label ----- -->
709
1219
  @if (compact()) {
710
1220
  <p class="axm-chat-empty-assist-composer__suggestions-label">
711
- {{ '@conversation:chat.starter-prompts.section-label' | translate | async }}
1221
+ {{ '@conversation:mini-chat.try-asking' | translate | async }}
712
1222
  </p>
713
1223
  }
714
- <axm-assist-suggestion-chips [items]="starterPromptItems()" [compact]="compact()" [sendInPlace]="sendStarterPromptsInPlace()" [disabled]="isBusy()" (conversationCreated)="conversationCreated.emit($event)" />
1224
+ <axm-assist-suggestion-chips [items]="starterPromptItems()" [compact]="compact()" [sendInPlace]="sendStarterPromptsInPlace()" [disabled]="loading()" (conversationCreated)="conversationCreated.emit($event)" />
715
1225
  </div>
716
1226
  }
717
1227
  </div>
718
- `, styles: [":host{display:block;width:100%}.axm-chat-empty-assist-composer{display:flex;flex-direction:column;gap:1rem;width:min(100%,64rem);margin-inline:auto}.axm-chat-empty-assist-composer__hero{display:flex;flex-direction:column;align-items:center;gap:.625rem}.axm-chat-empty-assist-composer__hero-icon{display:grid;place-items:center;width:2.5rem;height:2.5rem;border-radius:.875rem;color:rgb(var(--ax-sys-color-primary-600));background:linear-gradient(145deg,rgba(var(--ax-sys-color-primary-500),.14),rgba(var(--ax-sys-color-primary-500),.04));border:1px solid rgba(var(--ax-sys-color-primary-500),.18)}.axm-chat-empty-assist-composer__caption{margin-bottom:3rem;text-align:center;font-size:clamp(1.9rem,3.4vw,2.7rem);line-height:1.15;font-weight:700;letter-spacing:-.02em;color:rgb(var(--ax-sys-color-on-surface))}.axm-chat-empty-assist-composer__composer-row{display:flex;gap:.375rem;align-items:flex-end;padding:.5rem;border-radius:2em;border:1px solid rgb(var(--ax-sys-color-border-light-surface));background:rgb(var(--ax-sys-color-lightest-surface))}.axm-chat-empty-assist-composer__assist-text{display:inline-block;max-width:12rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.axm-chat-empty-assist-composer__send{flex-shrink:0}.axm-chat-empty-assist-composer__suggestions{display:flex;flex-direction:column;gap:.5rem}.axm-chat-empty-assist-composer__suggestions-label{margin:0;padding-inline:.125rem;font-size:.6875rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:rgb(var(--ax-sys-color-on-surface));opacity:.52}.axm-chat-empty-assist-composer__textarea{resize:none;overflow:hidden;background:transparent;min-height:2.25rem;max-height:14rem;line-height:1.5;padding-block:.25rem;font-size:inherit;text-align:start;color:rgb(var(--ax-sys-color-on-surface))}.axm-chat-empty-assist-composer__textarea::placeholder{color:rgb(var(--ax-sys-color-on-surface));opacity:.45}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer{width:100%;max-width:none;gap:.875rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__hero{gap:.5rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__caption{margin-bottom:0;font-size:1.125rem;line-height:1.35;font-weight:600;letter-spacing:-.015em}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__composer-row{gap:.25rem;align-items:center;padding-block:.375rem;padding-inline:.375rem .4375rem;border-radius:1.5rem;border-color:rgba(var(--ax-sys-color-border-light-surface),.95);background:rgba(var(--ax-sys-color-surface),.35)}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__assist-trigger{color:rgb(var(--ax-sys-color-primary-600));opacity:.9}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__assist-text{max-width:5.5rem;font-size:.8125rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__textarea{min-height:1.625rem;max-height:8rem;line-height:1.4;font-size:.875rem;padding-block:.125rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__suggestions{gap:.375rem;margin-top:.125rem}\n"] }]
719
- }], ctorParameters: () => [], propDecorators: { conversationCreated: [{ type: i0.Output, args: ["conversationCreated"] }], initialAssistId: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialAssistId", required: false }] }], entityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityId", required: false }] }], entityType: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityType", required: false }] }], sendStarterPromptsInPlace: [{ type: i0.Input, args: [{ isSignal: true, alias: "sendStarterPromptsInPlace", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], draftInput: [{ type: i0.ViewChild, args: ['draftInput', { isSignal: true }] }] } });
1228
+ `, styles: [":host{display:block;width:100%}:host(.axm-chat-empty-assist-composer-host--compact){display:flex;height:100%;min-height:0}.axm-chat-empty-assist-composer{display:flex;flex-direction:column;gap:1rem;width:min(100%,64rem);margin-inline:auto}.axm-chat-empty-assist-composer__hero{display:flex;flex-direction:column;align-items:center;gap:.625rem}.axm-chat-empty-assist-composer__hero-icon{display:grid;place-items:center;width:2.5rem;height:2.5rem;border-radius:.875rem;color:rgb(var(--ax-sys-color-primary-600));background:linear-gradient(145deg,rgba(var(--ax-sys-color-primary-500),.14),rgba(var(--ax-sys-color-primary-500),.04));border:1px solid rgba(var(--ax-sys-color-primary-500),.18)}.axm-chat-empty-assist-composer__composer{position:relative;display:flex;flex-direction:column;width:100%;overflow:hidden;border-radius:2em;border:1px solid rgb(var(--ax-sys-color-border-light-surface));background:rgb(var(--ax-sys-color-lightest-surface))}.axm-chat-empty-assist-composer__attachments{display:flex;align-items:center;width:100%;min-width:0;padding:.125rem .5rem 0;border-bottom:1px solid rgba(var(--ax-sys-color-border-light-surface),.65)}.axm-chat-empty-assist-composer__attachments-scroll{display:flex;flex:1 1 auto;flex-wrap:nowrap;align-items:center;gap:.375rem;min-width:0;width:0;overflow-x:auto;overflow-y:hidden;padding-block:.375rem;scrollbar-width:thin}.axm-chat-empty-assist-composer__attachment{position:relative;display:flex;flex-shrink:0;align-items:center}.axm-chat-empty-assist-composer__attachment--file{width:9.5rem;max-width:9.5rem;height:2rem;gap:.375rem;padding-inline:.375rem .5rem;border-radius:.5rem;border:1px solid rgba(var(--ax-sys-color-border-light-surface),.85);background:rgb(var(--ax-sys-color-surface))}.axm-chat-empty-assist-composer__attachment-thumb{display:flex;width:2rem;height:2rem;align-items:center;justify-content:center;overflow:hidden;border-radius:.5rem;border:1px solid rgba(var(--ax-sys-color-border-light-surface),.85);background:rgb(var(--ax-sys-color-surface))}.axm-chat-empty-assist-composer__attachment-thumb img,.axm-chat-empty-assist-composer__attachment-thumb video{width:100%;height:100%;object-fit:cover}.axm-chat-empty-assist-composer__attachment-icon{display:flex;width:1.25rem;height:1.25rem;flex-shrink:0;align-items:center;justify-content:center;font-size:.75rem;color:rgb(var(--ax-sys-color-on-surface));opacity:.72}.axm-chat-empty-assist-composer__attachment-name{min-width:0;flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.75rem;color:rgb(var(--ax-sys-color-on-surface))}.axm-chat-empty-assist-composer__attachment-remove{position:absolute;inset-block-start:-.25rem;inset-inline-end:-.25rem;z-index:1;display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;padding:0;border:none;border-radius:9999px;background:rgb(var(--ax-sys-color-danger-500));color:rgb(var(--ax-sys-color-lightest-surface));font-size:.5rem;cursor:pointer;opacity:0;pointer-events:none;transition:opacity .15s ease}.axm-chat-empty-assist-composer__attachment:hover .axm-chat-empty-assist-composer__attachment-remove,.axm-chat-empty-assist-composer__attachment:focus-within .axm-chat-empty-assist-composer__attachment-remove,.axm-chat-empty-assist-composer__attachment-remove:focus-visible{opacity:1;pointer-events:auto}@media(hover:none){.axm-chat-empty-assist-composer__attachment-remove{opacity:1;pointer-events:auto}}.axm-chat-empty-assist-composer__attachment-add{flex-shrink:0;align-self:center}.axm-chat-empty-assist-composer__attachment-add ax-prefix i{font-size:.875rem;line-height:1}.axm-chat-empty-assist-composer__input-row{display:flex;width:100%;min-width:0;align-items:center;gap:.5rem;padding:.25rem .5rem}.axm-chat-empty-assist-composer__actions-start,.axm-chat-empty-assist-composer__primary{display:flex;flex-shrink:0;align-items:center}.axm-chat-empty-assist-composer__actions-start ax-icon i,.axm-chat-empty-assist-composer__actions-start ax-prefix i,.axm-chat-empty-assist-composer__primary ax-prefix i{font-size:1.375rem;line-height:1}.axm-chat-empty-assist-composer__textarea{flex:1 1 auto;min-width:0;min-height:2.25rem;max-height:14rem;margin:0;padding-block:.25rem;border:none;outline:none;resize:none;overflow:hidden;background:transparent;font-size:inherit;line-height:1.5;color:rgb(var(--ax-sys-color-on-surface))}.axm-chat-empty-assist-composer__textarea::placeholder{color:rgb(var(--ax-sys-color-on-surface));opacity:.45}.axm-chat-empty-assist-composer__file-input{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)}.axm-chat-empty-assist-composer__caption{margin-bottom:1.25rem;text-align:center;font-size:clamp(1.9rem,3.4vw,2.7rem);line-height:1.15;font-weight:700;letter-spacing:-.02em;color:rgb(var(--ax-sys-color-on-surface))}.axm-chat-empty-assist-composer__suggestions{display:flex;flex-direction:column;gap:.5rem}.axm-chat-empty-assist-composer__suggestions-label{margin:0;padding-inline:.125rem;font-size:.6875rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:rgb(var(--ax-sys-color-on-surface));opacity:.52}.axm-chat-empty-assist-composer__chips-loading{display:flex;flex-direction:column;align-items:center;gap:.5rem;padding-block:.5rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer{width:100%;max-width:none;height:100%;min-height:0;gap:.875rem;justify-content:center;overflow:hidden;padding-block:.5rem .125rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__hero{flex-shrink:0;gap:.5rem;padding-block-start:.5rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__hero-icon{width:2.5rem;height:2.5rem;border-radius:.875rem;color:rgb(var(--ax-sys-color-primary-600))}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__caption{margin-bottom:0;font-size:1.125rem;line-height:1.3;font-weight:700;letter-spacing:-.03em}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__composer{flex-shrink:0;width:100%;border-radius:1.5rem;border-color:rgba(var(--ax-sys-color-border-light-surface),.95)}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__textarea{min-height:1.625rem;max-height:8rem;font-size:.875rem;line-height:1.4}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__subtitle{margin:0;max-width:20rem;text-align:center;font-size:.8125rem;line-height:1.5;font-weight:400;color:rgb(var(--ax-sys-color-on-surface));opacity:.58}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__suggestions,:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__chips-loading{display:flex;flex-direction:column;flex:0 1 auto;min-height:0;overflow:auto;justify-content:flex-start}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__suggestions{gap:.5rem;margin-top:.125rem}:host(.axm-chat-empty-assist-composer-host--compact) .axm-chat-empty-assist-composer__suggestions-label{flex-shrink:0;margin-block-end:.125rem;letter-spacing:.02em;text-transform:none;font-size:.75rem;font-weight:500;opacity:.55}:host(.axm-chat-empty-assist-composer-host--compact) axm-assist-suggestion-chips{flex:0 1 auto;min-height:0;overflow:hidden}\n"] }]
1229
+ }], ctorParameters: () => [], propDecorators: { draftInput: [{ type: i0.ViewChild, args: ['draftInput', { isSignal: true }] }], fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }], conversationCreated: [{ type: i0.Output, args: ["conversationCreated"] }], initialAssistId: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialAssistId", required: false }] }], assistSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "assistSelection", required: false }] }], acceptExternalFileDrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceptExternalFileDrop", required: false }] }], entityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityId", required: false }] }], entityType: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityType", required: false }] }], sendStarterPromptsInPlace: [{ type: i0.Input, args: [{ isSignal: true, alias: "sendStarterPromptsInPlace", required: false }] }], launchContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "launchContext", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], assistConversationCreated: [{ type: i0.Input, args: [{ isSignal: true, alias: "assistConversationCreated", required: false }] }] } });
720
1230
 
721
1231
  const AXE_CHAT_EMPTY_STATE_PROVIDERS = [
722
1232
  {
@@ -772,6 +1282,7 @@ function coerceTimestampMs(value) {
772
1282
  * AI generation and entity persistence run on the server ({@link AiConversationAssistResponseService}).
773
1283
  */
774
1284
  class AXEConversationAssistResponseService {
1285
+ static { this.ASSIST_SYNC_MIN_INTERVAL_MS = 2000; }
775
1286
  constructor() {
776
1287
  this.transportService = inject(AXPRuntimeTransportService);
777
1288
  this.toastService = inject(AXToastService);
@@ -779,30 +1290,41 @@ class AXEConversationAssistResponseService {
779
1290
  this.store = inject(AXMConversationEntityStoreService);
780
1291
  this.focus = inject(AXMConversationFocusService);
781
1292
  this.commandService = inject(AXPCommandService);
1293
+ this.generationState = inject(AXConversationComposerGenerationStateService);
1294
+ this.destroyRef = inject(DestroyRef);
782
1295
  this.messageCache = new Map();
1296
+ this.forgottenMessageIds = new Set();
783
1297
  this.conversationCache = new Map();
784
1298
  this.streamTasks = new Map();
785
1299
  this.streamReconnectTimers = new Map();
786
1300
  this.lastAssistSyncAt = new Map();
787
1301
  this.currentUserId = '';
788
1302
  this.composerGenerationConversationId = null;
789
- this.cancelAssistRequest = null;
1303
+ this.cancelAssistByConversation = new Map();
1304
+ this.userCancelledConversationIds = new Set();
1305
+ this.generationState.onCancelled.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
1306
+ const conversationId = this.composerGenerationConversationId?.trim()
1307
+ || this.focus.getFocusedConversationId()?.trim()
1308
+ || '';
1309
+ void this.cancelAssistGeneration(conversationId);
1310
+ });
790
1311
  }
791
- static { this.ASSIST_SYNC_MIN_INTERVAL_MS = 2000; }
792
- async handleAfterUserMessage(conversationId, _userMessageId, _command, currentUserId) {
1312
+ async handleAfterUserMessage(conversationId, _userMessageId, _command, currentUserId, options) {
793
1313
  this.currentUserId = currentUserId;
794
1314
  this.ensureAssistStreamSubscription(conversationId);
795
1315
  const scoped = conversationId.trim();
1316
+ this.userCancelledConversationIds.delete(scoped);
796
1317
  if (!this.conversationCache.has(scoped)) {
797
1318
  const storeCached = this.store.getCachedConversation(scoped);
798
1319
  if (storeCached) {
799
1320
  this.conversationCache.set(scoped, storeCached);
800
1321
  }
801
1322
  }
802
- if (this.focus.isFocused(scoped)) {
1323
+ const focusedId = this.focus.getFocusedConversationId();
1324
+ if (focusedId === scoped || !focusedId) {
803
1325
  this.markComposerGenerating(scoped, true);
804
1326
  }
805
- if (!this.streamTasks.has(scoped)) {
1327
+ if (!options?.skipAssistSync && !this.streamTasks.has(scoped)) {
806
1328
  void this.requestAssistSync(conversationId, { force: true });
807
1329
  }
808
1330
  }
@@ -827,6 +1349,7 @@ class AXEConversationAssistResponseService {
827
1349
  this.messageCache.delete(messageId);
828
1350
  }
829
1351
  this.ensureAssistStreamSubscription(conversationId);
1352
+ this.userCancelledConversationIds.delete(conversationId.trim());
830
1353
  if (this.focus.isFocused(conversationId)) {
831
1354
  this.markComposerGenerating(conversationId, true);
832
1355
  }
@@ -919,18 +1442,35 @@ class AXEConversationAssistResponseService {
919
1442
  return streaming.length > 0;
920
1443
  }
921
1444
  mergeLiveConversationMessages(conversationId, persisted) {
922
- const byId = new Map(persisted.map((message) => [message.id, message]));
1445
+ const byId = new Map();
1446
+ for (const message of persisted) {
1447
+ if (message.deleted || this.forgottenMessageIds.has(message.id)) {
1448
+ continue;
1449
+ }
1450
+ byId.set(message.id, message);
1451
+ }
923
1452
  for (const message of this.messageCache.values()) {
924
- if (message.conversationId === conversationId && !message.deleted) {
925
- const existing = byId.get(message.id);
926
- if (existing && axmShouldPreferPersistedAssistOverLiveCache(existing, message)) {
927
- continue;
928
- }
929
- byId.set(message.id, this.withStreamDraftForDisplay(message));
1453
+ if (message.conversationId !== conversationId || message.deleted || this.forgottenMessageIds.has(message.id)) {
1454
+ continue;
930
1455
  }
1456
+ const existing = byId.get(message.id);
1457
+ if (existing && axmShouldPreferPersistedAssistOverLiveCache(existing, message)) {
1458
+ continue;
1459
+ }
1460
+ byId.set(message.id, this.withStreamDraftForDisplay(message));
931
1461
  }
932
1462
  return [...byId.values()].sort((a, b) => axmCompareMessagesChronologically(a, b));
933
1463
  }
1464
+ forgetLiveMessages(messageIds) {
1465
+ for (const rawId of messageIds) {
1466
+ const messageId = rawId.trim();
1467
+ if (!messageId) {
1468
+ continue;
1469
+ }
1470
+ this.forgottenMessageIds.add(messageId);
1471
+ this.messageCache.delete(messageId);
1472
+ }
1473
+ }
934
1474
  ensureAssistStreamSubscription(conversationId) {
935
1475
  const key = conversationId.trim();
936
1476
  if (!key || this.streamTasks.has(key)) {
@@ -994,6 +1534,9 @@ class AXEConversationAssistResponseService {
994
1534
  async handleAssistWireEvent(event) {
995
1535
  switch (event.type) {
996
1536
  case 'assist_started': {
1537
+ if (this.shouldIgnoreStreamingAfterUserCancel(event.conversationId)) {
1538
+ break;
1539
+ }
997
1540
  void this.markUserMessageSeenByAssist(event.conversationId, event.userMessageId);
998
1541
  const mapped = axmMessageEntityToMessage(event.message);
999
1542
  this.applyLiveMessage(mapped, { isNew: !this.messageCache.has(mapped.id) });
@@ -1002,12 +1545,16 @@ class AXEConversationAssistResponseService {
1002
1545
  }
1003
1546
  case 'message_patch': {
1004
1547
  const mapped = axmMessageEntityToMessage(event.message);
1548
+ const status = axmReadAssistResponseStatus(mapped.metadata);
1549
+ if (status === 'streaming' && this.shouldIgnoreStreamingAfterUserCancel(mapped.conversationId)) {
1550
+ break;
1551
+ }
1005
1552
  const isNew = !this.messageCache.has(mapped.id);
1006
1553
  this.applyLiveMessage(mapped, { isNew });
1007
- if (axmReadAssistResponseStatus(mapped.metadata) === 'streaming') {
1554
+ if (status === 'streaming') {
1008
1555
  this.syncComposerGeneratingForConversation(mapped.conversationId, true);
1009
1556
  }
1010
- else if (axmIsAssistTerminalStatus(axmReadAssistResponseStatus(mapped.metadata))) {
1557
+ else if (axmIsAssistTerminalStatus(status)) {
1011
1558
  this.syncComposerGeneratingForConversation(mapped.conversationId, false);
1012
1559
  }
1013
1560
  break;
@@ -1021,6 +1568,10 @@ class AXEConversationAssistResponseService {
1021
1568
  }
1022
1569
  case 'assist_completed': {
1023
1570
  const mapped = axmMessageEntityToMessage(event.message);
1571
+ if (this.shouldIgnoreStreamingAfterUserCancel(mapped.conversationId)) {
1572
+ this.syncComposerGeneratingForConversation(mapped.conversationId, false);
1573
+ break;
1574
+ }
1024
1575
  this.applyLiveMessage(mapped, { isNew: false });
1025
1576
  void this.maybeMarkFocusedIncomingRead(mapped);
1026
1577
  if (event.room) {
@@ -1033,13 +1584,17 @@ class AXEConversationAssistResponseService {
1033
1584
  case 'assist_failed': {
1034
1585
  const mapped = axmMessageEntityToMessage(event.message);
1035
1586
  this.applyLiveMessage(mapped, { isNew: false });
1036
- if (this.focus.isFocused(mapped.conversationId)) {
1587
+ if (this.focus.isFocused(mapped.conversationId) && axmReadAssistResponseStatus(mapped.metadata) === 'failed') {
1037
1588
  void this.showAssistToast('@conversation:chat.assist.errors.title', event.error);
1038
1589
  }
1039
1590
  this.syncComposerGeneratingForConversation(mapped.conversationId, false);
1040
1591
  break;
1041
1592
  }
1042
1593
  case 'frontend_tool_call': {
1594
+ const cancelId = this.composerGenerationConversationId;
1595
+ if (cancelId && this.shouldIgnoreStreamingAfterUserCancel(cancelId)) {
1596
+ break;
1597
+ }
1043
1598
  await this.submitFrontendToolResult(event.runId, event.call);
1044
1599
  break;
1045
1600
  }
@@ -1049,7 +1604,13 @@ class AXEConversationAssistResponseService {
1049
1604
  }
1050
1605
  async submitFrontendToolResult(runId, call) {
1051
1606
  try {
1052
- await this.transportService.execute(AXMAiManagementTransportKeys.Engine.SubmitToolResult, { runId, call });
1607
+ const result = await this.transportService.execute(AXMAiManagementTransportKeys.Engine.SubmitToolResult, {
1608
+ runId,
1609
+ call,
1610
+ });
1611
+ if (result && typeof result === 'object' && 'ok' in result && result.ok === false) {
1612
+ return;
1613
+ }
1053
1614
  }
1054
1615
  catch (error) {
1055
1616
  const text = error instanceof Error ? error.message : String(error);
@@ -1081,10 +1642,10 @@ class AXEConversationAssistResponseService {
1081
1642
  }
1082
1643
  const assistId = axmExtractAssistIdFromMetadata(metadata);
1083
1644
  const modelEntityId = typeof metadata['modelEntityId'] === 'string' && metadata['modelEntityId'].trim() ? metadata['modelEntityId'].trim() : undefined;
1084
- const result = await this.commandService.execute(AIMANAGEMENT_STRUCTURED_TEXT_COMPLETION_COMMAND_KEY, {
1645
+ const result = await this.commandService.execute(AIMANAGEMENT_STRUCTURED_TEXT_COMPLETION_COMMAND_KEY$1, {
1085
1646
  assistId,
1086
1647
  modelEntityId,
1087
- messages: [axpAiChatTextMessage('system', AXM_ASSIST_CONVERSATION_TITLE_SYSTEM_PROMPT), axpAiChatTextMessage('user', scopedText)],
1648
+ messages: [axpAiChatTextMessage$1('system', AXM_ASSIST_CONVERSATION_TITLE_SYSTEM_PROMPT), axpAiChatTextMessage$1('user', scopedText)],
1088
1649
  maxTokens: 48,
1089
1650
  temperature: 0.3,
1090
1651
  });
@@ -1160,14 +1721,14 @@ class AXEConversationAssistResponseService {
1160
1721
  if (!scopedId) {
1161
1722
  return;
1162
1723
  }
1163
- const message = this.messageCache.get(scopedId) ?? (await this.store.getMessage(scopedId));
1724
+ const message = this.store.getCachedMessage(scopedId) ?? this.messageCache.get(scopedId);
1164
1725
  if (!message || message.conversationId !== conversationId) {
1165
1726
  return;
1166
1727
  }
1167
1728
  if (message.senderId !== this.currentUserId || message.status === 'read') {
1168
1729
  return;
1169
1730
  }
1170
- await this.store.updateMessage({ ...message, status: 'read' });
1731
+ this.store.publishMessageUpdate({ ...message, status: 'read' });
1171
1732
  }
1172
1733
  async maybeMarkFocusedIncomingRead(message) {
1173
1734
  const conversationId = message.conversationId?.trim();
@@ -1184,6 +1745,20 @@ class AXEConversationAssistResponseService {
1184
1745
  });
1185
1746
  }
1186
1747
  applyLiveMessage(message, options) {
1748
+ if (message.deleted || this.forgottenMessageIds.has(message.id)) {
1749
+ this.forgottenMessageIds.add(message.id);
1750
+ this.messageCache.delete(message.id);
1751
+ this.store.publishMessageDeletion(message.id);
1752
+ return;
1753
+ }
1754
+ this.forgottenMessageIds.delete(message.id);
1755
+ if (this.shouldIgnoreStaleUserTextPatch(message)) {
1756
+ return;
1757
+ }
1758
+ if (axmReadAssistResponseStatus(message.metadata) === 'streaming'
1759
+ && this.shouldIgnoreStreamingAfterUserCancel(message.conversationId)) {
1760
+ return;
1761
+ }
1187
1762
  const normalized = this.coerceLaggyStreamingTerminal(message);
1188
1763
  if (this.shouldIgnoreStaleStreamingDowngrade(normalized)) {
1189
1764
  return;
@@ -1240,6 +1815,30 @@ class AXEConversationAssistResponseService {
1240
1815
  delete nextMeta['streamDraftThink'];
1241
1816
  return { ...message, metadata: nextMeta };
1242
1817
  }
1818
+ /**
1819
+ * After a user-text edit, entity cache / status-seen persist can emit the pre-edit payload.
1820
+ * Keep the newer local bubble (and never put the old text into the live cache).
1821
+ */
1822
+ shouldIgnoreStaleUserTextPatch(incoming) {
1823
+ if (incoming.metadata?.['isAssistResponse'] === true || incoming.type === 'assist-response') {
1824
+ return false;
1825
+ }
1826
+ if (incoming.type !== 'text') {
1827
+ return false;
1828
+ }
1829
+ const local = this.store.getCachedMessage(incoming.id) ?? this.messageCache.get(incoming.id);
1830
+ if (!local) {
1831
+ return false;
1832
+ }
1833
+ const localText = extractMessageTextFromPayload(local.payload);
1834
+ const incomingText = extractMessageTextFromPayload(incoming.payload);
1835
+ if (localText === incomingText) {
1836
+ return false;
1837
+ }
1838
+ const localEdited = coerceTimestampMs(local.editedAt);
1839
+ const incomingEdited = coerceTimestampMs(incoming.editedAt);
1840
+ return localEdited > 0 && incomingEdited < localEdited + 5000;
1841
+ }
1243
1842
  /** Ignore out-of-order streaming patches after a terminal assist state was applied. */
1244
1843
  shouldIgnoreStaleStreamingDowngrade(incoming) {
1245
1844
  if (axmReadAssistResponseStatus(incoming.metadata) !== 'streaming') {
@@ -1462,21 +2061,40 @@ class AXEConversationAssistResponseService {
1462
2061
  return;
1463
2062
  }
1464
2063
  const hasStreaming = await this.hasStreamingAssistPlaceholders(scoped);
2064
+ if (this.userCancelledConversationIds.has(scoped)) {
2065
+ this.markComposerGenerating(scoped, false);
2066
+ return;
2067
+ }
2068
+ if (hasStreaming && this.shouldDeferGeneratingTrue(scoped)) {
2069
+ return;
2070
+ }
1465
2071
  this.markComposerGenerating(scoped, hasStreaming);
1466
2072
  }
1467
2073
  async cancelAssistGeneration(conversationId) {
1468
- if (this.cancelAssistRequest) {
1469
- return this.cancelAssistRequest;
2074
+ const scoped = conversationId.trim()
2075
+ || this.composerGenerationConversationId?.trim()
2076
+ || this.focus.getFocusedConversationId()?.trim()
2077
+ || this.resolveActiveStreamingConversationId();
2078
+ if (!scoped) {
2079
+ this.markComposerGenerating(null, false);
2080
+ return;
2081
+ }
2082
+ this.userCancelledConversationIds.add(scoped);
2083
+ this.freezeLiveStreamingAsUserCancelled(scoped);
2084
+ const existing = this.cancelAssistByConversation.get(scoped);
2085
+ if (existing) {
2086
+ return existing;
1470
2087
  }
1471
- this.cancelAssistRequest = this.runCancelAssistGeneration(conversationId).finally(() => {
1472
- this.cancelAssistRequest = null;
2088
+ const pending = this.runCancelAssistGeneration(scoped).finally(() => {
2089
+ if (this.cancelAssistByConversation.get(scoped) === pending) {
2090
+ this.cancelAssistByConversation.delete(scoped);
2091
+ }
1473
2092
  });
1474
- return this.cancelAssistRequest;
2093
+ this.cancelAssistByConversation.set(scoped, pending);
2094
+ return pending;
1475
2095
  }
1476
2096
  async runCancelAssistGeneration(conversationId) {
1477
- const scoped = conversationId.trim()
1478
- || this.composerGenerationConversationId?.trim()
1479
- || this.resolveActiveStreamingConversationId();
2097
+ const scoped = conversationId.trim();
1480
2098
  if (!scoped) {
1481
2099
  this.markComposerGenerating(null, false);
1482
2100
  return;
@@ -1487,32 +2105,68 @@ class AXEConversationAssistResponseService {
1487
2105
  });
1488
2106
  await this.requestAssistSync(scoped, { force: true });
1489
2107
  await this.syncAssistMessagesFromStore(scoped);
2108
+ this.markComposerGenerating(scoped, false);
1490
2109
  }
1491
2110
  catch (error) {
1492
2111
  const text = error instanceof Error ? error.message : String(error);
1493
2112
  if (this.focus.isFocused(scoped)) {
1494
2113
  void this.showAssistToast('@conversation:chat.assist.errors.stream-title', text);
1495
2114
  }
2115
+ this.userCancelledConversationIds.delete(scoped);
2116
+ const hasStreaming = await this.hasStreamingAssistPlaceholders(scoped);
2117
+ this.markComposerGenerating(scoped, hasStreaming);
1496
2118
  }
1497
- finally {
1498
- this.markComposerGenerating(scoped, false);
2119
+ }
2120
+ freezeLiveStreamingAsUserCancelled(conversationId) {
2121
+ for (const message of [...this.messageCache.values()]) {
2122
+ if (message.conversationId !== conversationId) {
2123
+ continue;
2124
+ }
2125
+ if (axmReadAssistResponseStatus(message.metadata) !== 'streaming') {
2126
+ continue;
2127
+ }
2128
+ const meta = { ...(message.metadata ?? {}) };
2129
+ meta['isStreaming'] = false;
2130
+ meta['assistResponseStatus'] = 'incomplete';
2131
+ meta['assistResponseError'] = 'Cancelled by user.';
2132
+ this.applyLiveMessage({ ...message, metadata: meta }, { isNew: false });
1499
2133
  }
1500
2134
  }
2135
+ shouldIgnoreStreamingAfterUserCancel(conversationId) {
2136
+ const scoped = conversationId.trim();
2137
+ return this.userCancelledConversationIds.has(scoped) || this.cancelAssistByConversation.has(scoped);
2138
+ }
1501
2139
  markComposerGenerating(conversationId, generating) {
1502
2140
  if (generating && conversationId) {
1503
2141
  this.composerGenerationConversationId = conversationId;
2142
+ this.generationState.setGenerating(true);
1504
2143
  return;
1505
2144
  }
1506
2145
  if (!generating) {
2146
+ if (conversationId &&
2147
+ this.composerGenerationConversationId &&
2148
+ this.composerGenerationConversationId !== conversationId) {
2149
+ return;
2150
+ }
1507
2151
  this.composerGenerationConversationId = null;
2152
+ this.generationState.setGenerating(false);
1508
2153
  }
1509
2154
  }
1510
2155
  syncComposerGeneratingForConversation(conversationId, generating) {
1511
2156
  if (!this.focus.isFocused(conversationId)) {
1512
2157
  return;
1513
2158
  }
2159
+ if (generating && this.shouldDeferGeneratingTrue(conversationId)) {
2160
+ return;
2161
+ }
1514
2162
  this.markComposerGenerating(conversationId, generating);
1515
2163
  }
2164
+ /** Stream patches must not reset `stopping` back to `generating` while cancel is in flight. */
2165
+ shouldDeferGeneratingTrue(conversationId) {
2166
+ return (this.cancelAssistByConversation.has(conversationId)
2167
+ || this.userCancelledConversationIds.has(conversationId)
2168
+ || this.generationState.isStopping());
2169
+ }
1516
2170
  resolveActiveStreamingConversationId() {
1517
2171
  for (const message of this.messageCache.values()) {
1518
2172
  if (axmReadAssistResponseStatus(message.metadata) === 'streaming') {
@@ -1526,7 +2180,7 @@ class AXEConversationAssistResponseService {
1526
2180
  }
1527
2181
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEConversationAssistResponseService, decorators: [{
1528
2182
  type: Injectable
1529
- }] });
2183
+ }], ctorParameters: () => [] });
1530
2184
  /** @deprecated Use {@link AXEConversationAssistResponseService}. */
1531
2185
  const AXCConversationAssistResponseService = AXEConversationAssistResponseService;
1532
2186
 
@@ -1764,7 +2418,7 @@ class AXMChatWithAssistDialogComponent extends AXBasePageComponent {
1764
2418
  </div>
1765
2419
  }
1766
2420
  </ng-template>
1767
- `, isInline: true, styles: [":host{display:block}.new-conversation-panel{display:flex;flex-direction:column;width:100%;overflow:hidden}.new-conversation-dialog-content{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;gap:.75rem}.form-field--grow{flex:1;min-height:0;display:flex;flex-direction:column}.form-field{display:flex;flex-direction:column;gap:.35rem}.users-list-wrap{display:flex;flex-direction:column;height:min(50vh,36rem);overflow:hidden}.users-list-search{flex:0 0 auto;padding:.5rem .75rem;border-bottom:1px solid rgb(var(--ax-sys-color-border-light-surface));background:rgb(var(--ax-sys-color-lighter-surface))}.users-list{display:block;flex:1 1 auto;min-height:0}.user-item-content{display:flex;align-items:center;gap:.75rem;height:100%;padding:.5rem .75rem;min-width:0}.user-item-text{display:flex;flex-direction:column;justify-content:center;min-width:0;flex:1;gap:.125rem}.user-name-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem;line-height:1.25rem;font-weight:500;color:rgb(var(--ax-sys-color-on-surface))}.user-description-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem;line-height:1.25rem;font-weight:400;opacity:.72;color:rgb(var(--ax-sys-color-on-surface))}.users-list-empty{padding:1rem;text-align:center;font-size:.85rem;opacity:.7}.user-item-skeleton{display:flex;align-items:center;gap:.75rem;height:60px;padding:.5rem 1.25rem;min-width:0}.skeleton-avatar{flex:0 0 auto;width:3rem;height:3rem;border-radius:9999px}.skeleton-text{display:flex;flex-direction:column;min-width:0;flex:1;gap:.5rem}.skeleton-name{height:1rem;width:60%;border-radius:.25rem}.skeleton-description{height:.75rem;width:40%;border-radius:.25rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: AXButtonComponent, selector: "ax-button", inputs: ["disabled", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "component", type: AXListComponent, selector: "ax-list", inputs: ["id", "name", "disabled", "readonly", "valueField", "textField", "textTemplate", "disabledField", "multiple", "selectionMode", "isItemTruncated", "showItemTooltip", "dataSource", "itemHeight", "itemTemplate", "emptyTemplate", "loadingTemplate", "checkbox"], outputs: ["onValueChanged", "disabledChange", "readonlyChange", "onBlur", "onFocus", "onItemClick", "onItemSelected", "onScrolledIndexChanged"] }, { kind: "component", type: AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "component", type: AXPAvatarComponent, selector: "axp-avatar", inputs: ["size", "src", "fullName", "showTitle", "fallbackText", "placeholder"] }, { kind: "component", type: AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "component", type: AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2421
+ `, isInline: true, styles: [":host{display:block}.new-conversation-panel{display:flex;flex-direction:column;width:100%;overflow:hidden}.new-conversation-dialog-content{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;gap:.75rem}.form-field--grow{flex:1;min-height:0;display:flex;flex-direction:column}.form-field{display:flex;flex-direction:column;gap:.35rem}.users-list-wrap{display:flex;flex-direction:column;height:min(50vh,36rem);overflow:hidden}.users-list-search{flex:0 0 auto;padding:.5rem .75rem;border-bottom:1px solid rgb(var(--ax-sys-color-border-light-surface));background:rgb(var(--ax-sys-color-lighter-surface))}.users-list{display:block;flex:1 1 auto;min-height:0}.user-item-content{display:flex;align-items:center;gap:.75rem;height:100%;padding:.5rem .75rem;min-width:0}.user-item-text{display:flex;flex-direction:column;justify-content:center;min-width:0;flex:1;gap:.125rem}.user-name-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem;line-height:1.25rem;font-weight:500;color:rgb(var(--ax-sys-color-on-surface))}.user-description-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem;line-height:1.25rem;font-weight:400;opacity:.72;color:rgb(var(--ax-sys-color-on-surface))}.users-list-empty{padding:1rem;text-align:center;font-size:.85rem;opacity:.7}.user-item-skeleton{display:flex;align-items:center;gap:.75rem;height:60px;padding:.5rem 1.25rem;min-width:0}.skeleton-avatar{flex:0 0 auto;width:3rem;height:3rem;border-radius:9999px}.skeleton-text{display:flex;flex-direction:column;min-width:0;flex:1;gap:.5rem}.skeleton-name{height:1rem;width:60%;border-radius:.25rem}.skeleton-description{height:.75rem;width:40%;border-radius:.25rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: AXButtonComponent, selector: "ax-button", inputs: ["disabled", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "component", type: AXListComponent, selector: "ax-list", inputs: ["id", "name", "disabled", "readonly", "valueField", "textField", "textTemplate", "disabledField", "multiple", "selectionMode", "isItemTruncated", "showItemTooltip", "dataSource", "itemHeight", "itemTemplate", "emptyTemplate", "loadingTemplate", "checkbox"], outputs: ["onValueChanged", "disabledChange", "readonlyChange", "onBlur", "onFocus", "onItemClick", "onItemSelected", "onScrolledIndexChanged"] }, { kind: "component", type: AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "component", type: AXPAvatarComponent, selector: "axp-avatar", inputs: ["size", "src", "fullName", "showTitle", "fallbackText", "placeholder", "loading"] }, { kind: "component", type: AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "component", type: AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i6.AsyncPipe, name: "async" }, { kind: "pipe", type: i7.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1768
2422
  }
1769
2423
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMChatWithAssistDialogComponent, decorators: [{
1770
2424
  type: Component,
@@ -1877,6 +2531,67 @@ const AXM_CHAT_SIDEBAR_ASSIST_ACTION_PROVIDERS = [
1877
2531
  },
1878
2532
  ];
1879
2533
 
2534
+ //#region ----- Imports -----
2535
+ //#endregion
2536
+ //#region ----- Service -----
2537
+ /**
2538
+ * Opens the shared mini-chat overlay with caller-supplied assist context.
2539
+ * Same panel as the header inbox so streaming and route persistence stay aligned.
2540
+ */
2541
+ class AXMContextualAssistLauncherService {
2542
+ constructor() {
2543
+ //#region ----- Services & Dependencies -----
2544
+ this.panelService = inject(AXMMiniChatPanelService);
2545
+ }
2546
+ //#endregion
2547
+ //#region ----- Public API -----
2548
+ isOpen() {
2549
+ return this.panelService.isOpen();
2550
+ }
2551
+ /** True while the window is on screen (a retained hidden window is not visible). */
2552
+ isVisible() {
2553
+ return this.panelService.isVisible();
2554
+ }
2555
+ async open(context, options) {
2556
+ const launchContext = axmSanitizeContextualAssistContext(context);
2557
+ const assistId = options?.assistId?.trim();
2558
+ const entityRef = axmEntityRefFromLaunchContext(launchContext);
2559
+ await this.panelService.open({
2560
+ ...(entityRef
2561
+ ? { scope: 'entity', entityId: entityRef.entityId, entityType: entityRef.entityType }
2562
+ : { scope: 'inbox' }),
2563
+ forceNewChat: options?.forceNewChat ?? true,
2564
+ showInfoBar: false,
2565
+ showComposer: true,
2566
+ launchContext,
2567
+ ...(assistId ? { assistId } : {}),
2568
+ ...(options?.assistSelection ? { assistSelection: options.assistSelection } : {}),
2569
+ ...(options?.minimizable != null ? { minimizable: options.minimizable } : {}),
2570
+ ...(options?.retainOnClose != null ? { retainOnClose: options.retainOnClose } : {}),
2571
+ });
2572
+ }
2573
+ /** Hides a retained window, or destroys the window when retention is off. */
2574
+ dismiss() {
2575
+ this.panelService.dismiss();
2576
+ }
2577
+ /** Closes the panel when it is open; otherwise opens it (restores the last thread). */
2578
+ async toggle(context, options) {
2579
+ if (this.isOpen()) {
2580
+ this.close();
2581
+ return;
2582
+ }
2583
+ await this.open(context, { ...options, forceNewChat: false });
2584
+ }
2585
+ close() {
2586
+ this.panelService.close();
2587
+ }
2588
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMContextualAssistLauncherService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2589
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMContextualAssistLauncherService }); }
2590
+ }
2591
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMContextualAssistLauncherService, decorators: [{
2592
+ type: Injectable
2593
+ }] });
2594
+
1880
2595
  class AXEAIConversationExtensionModule {
1881
2596
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAIConversationExtensionModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
1882
2597
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXEAIConversationExtensionModule, imports: [i1$1.AXPComponentSlotModule] }); }
@@ -1904,6 +2619,7 @@ class AXEAIConversationExtensionModule {
1904
2619
  ...AXE_MINI_CHAT_INBOX_FILTER_PROVIDERS,
1905
2620
  ...AXE_ASSIST_DEFAULT_RESOLVER_PROVIDERS,
1906
2621
  ...AXM_CHAT_SIDEBAR_ASSIST_ACTION_PROVIDERS,
2622
+ AXMContextualAssistLauncherService,
1907
2623
  provideAppStartUpTask(() => {
1908
2624
  const launcher = inject(AXMChatAssistLauncherService);
1909
2625
  return {
@@ -1917,11 +2633,11 @@ class AXEAIConversationExtensionModule {
1917
2633
  provideCommandSetups([
1918
2634
  {
1919
2635
  key: CONVERSATION_START_ASSIST_CHAT_COMMAND,
1920
- command: () => import('./acorex-platform-extension-ai-conversation-client-start-assist-chat.command-qYtaBoTP.mjs').then((m) => m.AXMConversationStartAssistChatCommand),
2636
+ command: () => import('./acorex-platform-extension-ai-conversation-client-start-assist-chat.command-zQPeOCRi.mjs').then((m) => m.AXMConversationStartAssistChatCommand),
1921
2637
  },
1922
2638
  {
1923
2639
  key: CONVERSATION_SEND_ASSIST_CHAT_MESSAGE_COMMAND,
1924
- command: () => import('./acorex-platform-extension-ai-conversation-client-send-assist-chat-message.command-B3rJWdsz.mjs').then((m) => m.AXMConversationSendAssistChatMessageCommand),
2640
+ command: () => import('./acorex-platform-extension-ai-conversation-client-send-assist-chat-message.command-B6QodNVg.mjs').then((m) => m.AXMConversationSendAssistChatMessageCommand),
1925
2641
  },
1926
2642
  ]),
1927
2643
  ], imports: [AXPComponentSlotModule.forChild({
@@ -1932,6 +2648,13 @@ class AXEAIConversationExtensionModule {
1932
2648
  component: AXMMiniChatHeaderSlotComponent,
1933
2649
  },
1934
2650
  ],
2651
+ 'root-footer-end': [
2652
+ {
2653
+ priority: 5,
2654
+ name: 'conversation-mini-chat-dock',
2655
+ component: AXMMiniChatFooterDockComponent,
2656
+ },
2657
+ ],
1935
2658
  })] }); }
1936
2659
  }
1937
2660
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAIConversationExtensionModule, decorators: [{
@@ -1946,6 +2669,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1946
2669
  component: AXMMiniChatHeaderSlotComponent,
1947
2670
  },
1948
2671
  ],
2672
+ 'root-footer-end': [
2673
+ {
2674
+ priority: 5,
2675
+ name: 'conversation-mini-chat-dock',
2676
+ component: AXMMiniChatFooterDockComponent,
2677
+ },
2678
+ ],
1949
2679
  }),
1950
2680
  ],
1951
2681
  providers: [
@@ -1972,6 +2702,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1972
2702
  ...AXE_MINI_CHAT_INBOX_FILTER_PROVIDERS,
1973
2703
  ...AXE_ASSIST_DEFAULT_RESOLVER_PROVIDERS,
1974
2704
  ...AXM_CHAT_SIDEBAR_ASSIST_ACTION_PROVIDERS,
2705
+ AXMContextualAssistLauncherService,
1975
2706
  provideAppStartUpTask(() => {
1976
2707
  const launcher = inject(AXMChatAssistLauncherService);
1977
2708
  return {
@@ -1985,11 +2716,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1985
2716
  provideCommandSetups([
1986
2717
  {
1987
2718
  key: CONVERSATION_START_ASSIST_CHAT_COMMAND,
1988
- command: () => import('./acorex-platform-extension-ai-conversation-client-start-assist-chat.command-qYtaBoTP.mjs').then((m) => m.AXMConversationStartAssistChatCommand),
2719
+ command: () => import('./acorex-platform-extension-ai-conversation-client-start-assist-chat.command-zQPeOCRi.mjs').then((m) => m.AXMConversationStartAssistChatCommand),
1989
2720
  },
1990
2721
  {
1991
2722
  key: CONVERSATION_SEND_ASSIST_CHAT_MESSAGE_COMMAND,
1992
- command: () => import('./acorex-platform-extension-ai-conversation-client-send-assist-chat-message.command-B3rJWdsz.mjs').then((m) => m.AXMConversationSendAssistChatMessageCommand),
2723
+ command: () => import('./acorex-platform-extension-ai-conversation-client-send-assist-chat-message.command-B6QodNVg.mjs').then((m) => m.AXMConversationSendAssistChatMessageCommand),
1993
2724
  },
1994
2725
  ]),
1995
2726
  ],
@@ -2000,5 +2731,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2000
2731
  * Generated bundle index. Do not edit.
2001
2732
  */
2002
2733
 
2003
- export { AXMChatAssistLauncherService as A, AXCConversationAssistResponseService as a, AXEAIConversationExtensionModule as b, AXEConversationAssistResponseService as c };
2004
- //# sourceMappingURL=acorex-platform-extension-ai-conversation-client-acorex-platform-extension-ai-conversation-client-CRjLZIU5.mjs.map
2734
+ export { AXMChatAssistLauncherService as A, AXCConversationAssistResponseService as a, AXEAIConversationExtensionModule as b, AXEConversationAssistResponseService as c, AXMContextualAssistLauncherService as d };
2735
+ //# sourceMappingURL=acorex-platform-extension-ai-conversation-client-acorex-platform-extension-ai-conversation-client-DfdGeEG4.mjs.map