@memberjunction/ng-conversations 2.106.0 → 2.108.0

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 (112) hide show
  1. package/dist/lib/components/collection/artifact-collection-picker-modal.component.d.ts +67 -0
  2. package/dist/lib/components/collection/artifact-collection-picker-modal.component.d.ts.map +1 -0
  3. package/dist/lib/components/collection/artifact-collection-picker-modal.component.js +725 -0
  4. package/dist/lib/components/collection/artifact-collection-picker-modal.component.js.map +1 -0
  5. package/dist/lib/components/collection/artifact-create-modal.component.d.ts +39 -0
  6. package/dist/lib/components/collection/artifact-create-modal.component.d.ts.map +1 -0
  7. package/dist/lib/components/collection/artifact-create-modal.component.js +351 -0
  8. package/dist/lib/components/collection/artifact-create-modal.component.js.map +1 -0
  9. package/dist/lib/components/collection/collection-form-modal.component.d.ts +3 -1
  10. package/dist/lib/components/collection/collection-form-modal.component.d.ts.map +1 -1
  11. package/dist/lib/components/collection/collection-form-modal.component.js +60 -10
  12. package/dist/lib/components/collection/collection-form-modal.component.js.map +1 -1
  13. package/dist/lib/components/collection/collection-share-modal.component.d.ts +43 -0
  14. package/dist/lib/components/collection/collection-share-modal.component.d.ts.map +1 -0
  15. package/dist/lib/components/collection/collection-share-modal.component.js +728 -0
  16. package/dist/lib/components/collection/collection-share-modal.component.js.map +1 -0
  17. package/dist/lib/components/collection/collection-tree.component.d.ts +8 -1
  18. package/dist/lib/components/collection/collection-tree.component.d.ts.map +1 -1
  19. package/dist/lib/components/collection/collection-tree.component.js +217 -115
  20. package/dist/lib/components/collection/collection-tree.component.js.map +1 -1
  21. package/dist/lib/components/collection/collection-view.component.d.ts +2 -1
  22. package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
  23. package/dist/lib/components/collection/collection-view.component.js +52 -34
  24. package/dist/lib/components/collection/collection-view.component.js.map +1 -1
  25. package/dist/lib/components/collection/collections-full-view.component.d.ts +45 -9
  26. package/dist/lib/components/collection/collections-full-view.component.d.ts.map +1 -1
  27. package/dist/lib/components/collection/collections-full-view.component.js +586 -220
  28. package/dist/lib/components/collection/collections-full-view.component.js.map +1 -1
  29. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +53 -20
  30. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  31. package/dist/lib/components/conversation/conversation-chat-area.component.js +365 -250
  32. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  33. package/dist/lib/components/message/message-input.component.d.ts +71 -1
  34. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  35. package/dist/lib/components/message/message-input.component.js +303 -104
  36. package/dist/lib/components/message/message-input.component.js.map +1 -1
  37. package/dist/lib/components/message/message-item.component.d.ts +39 -5
  38. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  39. package/dist/lib/components/message/message-item.component.js +259 -137
  40. package/dist/lib/components/message/message-item.component.js.map +1 -1
  41. package/dist/lib/components/message/message-list.component.d.ts +8 -6
  42. package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
  43. package/dist/lib/components/message/message-list.component.js +52 -9
  44. package/dist/lib/components/message/message-list.component.js.map +1 -1
  45. package/dist/lib/components/message/suggested-responses.component.d.ts +55 -0
  46. package/dist/lib/components/message/suggested-responses.component.d.ts.map +1 -0
  47. package/dist/lib/components/message/suggested-responses.component.js +207 -0
  48. package/dist/lib/components/message/suggested-responses.component.js.map +1 -0
  49. package/dist/lib/components/search/search-panel.component.d.ts.map +1 -1
  50. package/dist/lib/components/search/search-panel.component.js +245 -113
  51. package/dist/lib/components/search/search-panel.component.js.map +1 -1
  52. package/dist/lib/components/shared/user-picker.component.d.ts +29 -0
  53. package/dist/lib/components/shared/user-picker.component.d.ts.map +1 -0
  54. package/dist/lib/components/shared/user-picker.component.js +229 -0
  55. package/dist/lib/components/shared/user-picker.component.js.map +1 -0
  56. package/dist/lib/components/tasks/tasks-dropdown.component.d.ts +7 -1
  57. package/dist/lib/components/tasks/tasks-dropdown.component.d.ts.map +1 -1
  58. package/dist/lib/components/tasks/tasks-dropdown.component.js +36 -6
  59. package/dist/lib/components/tasks/tasks-dropdown.component.js.map +1 -1
  60. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +19 -2
  61. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  62. package/dist/lib/components/workspace/conversation-workspace.component.js +167 -58
  63. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  64. package/dist/lib/conversations.module.d.ts +52 -47
  65. package/dist/lib/conversations.module.d.ts.map +1 -1
  66. package/dist/lib/conversations.module.js +27 -4
  67. package/dist/lib/conversations.module.js.map +1 -1
  68. package/dist/lib/models/conversation-complete-query.model.d.ts +75 -0
  69. package/dist/lib/models/conversation-complete-query.model.d.ts.map +1 -0
  70. package/dist/lib/models/conversation-complete-query.model.js +19 -0
  71. package/dist/lib/models/conversation-complete-query.model.js.map +1 -0
  72. package/dist/lib/models/conversation-state.model.d.ts +27 -0
  73. package/dist/lib/models/conversation-state.model.d.ts.map +1 -1
  74. package/dist/lib/models/lazy-artifact-info.d.ts +68 -0
  75. package/dist/lib/models/lazy-artifact-info.d.ts.map +1 -0
  76. package/dist/lib/models/lazy-artifact-info.js +150 -0
  77. package/dist/lib/models/lazy-artifact-info.js.map +1 -0
  78. package/dist/lib/services/agent-state.service.d.ts.map +1 -1
  79. package/dist/lib/services/agent-state.service.js +5 -0
  80. package/dist/lib/services/agent-state.service.js.map +1 -1
  81. package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
  82. package/dist/lib/services/artifact-state.service.js +14 -9
  83. package/dist/lib/services/artifact-state.service.js.map +1 -1
  84. package/dist/lib/services/collection-permission.service.d.ts +96 -0
  85. package/dist/lib/services/collection-permission.service.d.ts.map +1 -0
  86. package/dist/lib/services/collection-permission.service.js +303 -0
  87. package/dist/lib/services/collection-permission.service.js.map +1 -0
  88. package/dist/lib/services/collection-state.service.d.ts +34 -0
  89. package/dist/lib/services/collection-state.service.d.ts.map +1 -0
  90. package/dist/lib/services/collection-state.service.js +50 -0
  91. package/dist/lib/services/collection-state.service.js.map +1 -0
  92. package/dist/lib/services/conversation-agent.service.d.ts +20 -4
  93. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  94. package/dist/lib/services/conversation-agent.service.js +179 -17
  95. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  96. package/dist/lib/services/data-cache.service.d.ts.map +1 -1
  97. package/dist/lib/services/data-cache.service.js +5 -0
  98. package/dist/lib/services/data-cache.service.js.map +1 -1
  99. package/dist/lib/services/mention-autocomplete.service.js +1 -1
  100. package/dist/lib/services/mention-autocomplete.service.js.map +1 -1
  101. package/dist/lib/services/mention-parser.service.d.ts.map +1 -1
  102. package/dist/lib/services/mention-parser.service.js +0 -5
  103. package/dist/lib/services/mention-parser.service.js.map +1 -1
  104. package/dist/lib/services/search.service.d.ts +26 -3
  105. package/dist/lib/services/search.service.d.ts.map +1 -1
  106. package/dist/lib/services/search.service.js +172 -12
  107. package/dist/lib/services/search.service.js.map +1 -1
  108. package/dist/public-api.d.ts +4 -0
  109. package/dist/public-api.d.ts.map +1 -1
  110. package/dist/public-api.js +4 -0
  111. package/dist/public-api.js.map +1 -1
  112. package/package.json +12 -12
@@ -19,8 +19,12 @@ function MessageInputComponent_div_6_Template(rf, ctx) { if (rf & 1) {
19
19
  i0.ɵɵelementStart(0, "div", 10);
20
20
  i0.ɵɵelement(1, "i", 11);
21
21
  i0.ɵɵelementStart(2, "span");
22
- i0.ɵɵtext(3, "AI is responding...");
22
+ i0.ɵɵtext(3);
23
23
  i0.ɵɵelementEnd()();
24
+ } if (rf & 2) {
25
+ const ctx_r1 = i0.ɵɵnextContext();
26
+ i0.ɵɵadvance(3);
27
+ i0.ɵɵtextInterpolate(ctx_r1.processingMessage);
24
28
  } }
25
29
  export class MessageInputComponent {
26
30
  dialogService;
@@ -48,6 +52,7 @@ export class MessageInputComponent {
48
52
  messageText = '';
49
53
  isSending = false;
50
54
  isProcessing = false; // True when waiting for agent/naming response
55
+ processingMessage = 'AI is responding...'; // Message shown during processing
51
56
  converationManagerAgent = null;
52
57
  // Mention autocomplete state
53
58
  showMentionDropdown = false;
@@ -60,6 +65,8 @@ export class MessageInputComponent {
60
65
  pushStatusSubscription;
61
66
  // Track active task execution message IDs for real-time updates
62
67
  activeTaskExecutionMessageIds = new Set();
68
+ // Track completion timestamps to prevent race conditions with late progress updates
69
+ completionTimestamps = new Map();
63
70
  constructor(dialogService, toastService, agentService, conversationState, dataCache, activeTasks, mentionAutocomplete, mentionParser) {
64
71
  this.dialogService = dialogService;
65
72
  this.toastService = toastService;
@@ -297,95 +304,210 @@ export class MessageInputComponent {
297
304
  if (!this.canSend)
298
305
  return;
299
306
  this.isSending = true;
307
+ try {
308
+ const messageDetail = await this.createMessageDetail();
309
+ const saved = await messageDetail.Save();
310
+ if (saved) {
311
+ await this.handleSuccessfulSend(messageDetail);
312
+ }
313
+ else {
314
+ this.handleSendFailure(messageDetail);
315
+ }
316
+ }
317
+ catch (error) {
318
+ this.handleSendError(error);
319
+ }
320
+ finally {
321
+ this.isSending = false;
322
+ }
323
+ }
324
+ /**
325
+ * Send a message with custom text WITHOUT modifying the visible messageText input
326
+ * Used for suggested responses - sends message silently without affecting user's current input
327
+ */
328
+ async sendMessageWithText(text) {
329
+ if (!text || !text.trim()) {
330
+ return;
331
+ }
332
+ if (this.isSending) {
333
+ return;
334
+ }
335
+ this.isSending = true;
300
336
  try {
301
337
  const detail = await this.dataCache.createConversationDetail(this.currentUser);
302
338
  detail.ConversationID = this.conversationId;
303
- detail.Message = this.messageText.trim();
339
+ detail.Message = text.trim();
304
340
  detail.Role = 'User';
305
- // Parse mentions from message (not stored, used for routing only)
306
- const mentionResult = this.mentionParser.parseMentions(detail.Message, this.mentionAutocomplete.getAvailableAgents(), this.mentionAutocomplete.getAvailableUsers());
307
- console.log('[MentionInput] Parsing message for routing:', detail.Message);
308
- console.log('[MentionInput] Found mentions:', mentionResult);
309
- console.log('[MentionInput] Agent mention:', mentionResult.agentMention);
310
- // Set ParentID if this is a thread reply
311
341
  if (this.parentMessageId) {
312
342
  detail.ParentID = this.parentMessageId;
313
343
  }
314
344
  const saved = await detail.Save();
315
345
  if (saved) {
316
346
  this.messageSent.emit(detail);
317
- this.messageText = '';
318
- // Check if this is the first message in the conversation
347
+ const mentionResult = this.parseMentionsFromMessage(detail.Message);
319
348
  const isFirstMessage = this.conversationHistory.length === 0;
320
- // Determine routing: @mention > last agent context > Sage
321
- if (mentionResult.agentMention) {
322
- // Direct @mention - skip Sage, invoke agent directly
323
- console.log('🎯 Direct @mention detected, bypassing Sage');
324
- if (isFirstMessage) {
325
- Promise.all([
326
- this.invokeAgentDirectly(detail, mentionResult.agentMention, this.conversationId),
327
- this.nameConversation(detail.Message)
328
- ]);
329
- }
330
- else {
331
- this.invokeAgentDirectly(detail, mentionResult.agentMention, this.conversationId);
332
- }
333
- }
334
- else {
335
- // Check if user is replying to an agent (implicit continuation)
336
- const lastAIMessage = this.conversationHistory
337
- .slice()
338
- .reverse()
339
- .find(msg => msg.Role === 'AI' &&
340
- msg.AgentID &&
341
- msg.AgentID !== this.converationManagerAgent?.ID);
342
- if (lastAIMessage && lastAIMessage.AgentID) {
343
- // Continue with same agent - skip Sage
344
- console.log('🔄 Implicit continuation detected, continuing with last agent');
345
- if (isFirstMessage) {
346
- Promise.all([
347
- this.continueWithAgent(detail, lastAIMessage.AgentID, this.conversationId),
348
- this.nameConversation(detail.Message)
349
- ]);
350
- }
351
- else {
352
- this.continueWithAgent(detail, lastAIMessage.AgentID, this.conversationId);
353
- }
354
- }
355
- else {
356
- // No context - use Sage
357
- console.log('🤖 No agent context, using Sage');
358
- if (isFirstMessage) {
359
- Promise.all([
360
- this.processMessageThroughAgent(detail, mentionResult),
361
- this.nameConversation(detail.Message)
362
- ]);
363
- }
364
- else {
365
- this.processMessageThroughAgent(detail, mentionResult);
366
- }
367
- }
368
- }
369
- // Focus back on textarea
370
- setTimeout(() => {
371
- if (this.messageTextarea && this.messageTextarea.nativeElement) {
372
- this.messageTextarea.nativeElement.focus();
373
- }
374
- }, 100);
349
+ await this.routeMessage(detail, mentionResult, isFirstMessage);
375
350
  }
376
351
  else {
377
- console.error('Failed to send message:', detail.LatestResult?.Message);
378
- this.toastService.error('Failed to send message. Please try again.');
352
+ this.handleSendFailure(detail);
379
353
  }
380
354
  }
381
355
  catch (error) {
382
- console.error('Error sending message:', error);
383
- this.toastService.error('Error sending message. Please try again.');
356
+ this.handleSendError(error);
384
357
  }
385
358
  finally {
386
359
  this.isSending = false;
387
360
  }
388
361
  }
362
+ /**
363
+ * Creates and configures a new conversation detail message
364
+ */
365
+ async createMessageDetail() {
366
+ const detail = await this.dataCache.createConversationDetail(this.currentUser);
367
+ detail.ConversationID = this.conversationId;
368
+ detail.Message = this.messageText.trim();
369
+ detail.Role = 'User';
370
+ if (this.parentMessageId) {
371
+ detail.ParentID = this.parentMessageId;
372
+ }
373
+ return detail;
374
+ }
375
+ /**
376
+ * Handles successful message send - routes to appropriate agent
377
+ */
378
+ async handleSuccessfulSend(messageDetail) {
379
+ this.messageSent.emit(messageDetail);
380
+ this.messageText = '';
381
+ const mentionResult = this.parseMentionsFromMessage(messageDetail.Message);
382
+ const isFirstMessage = this.conversationHistory.length === 0;
383
+ await this.routeMessage(messageDetail, mentionResult, isFirstMessage);
384
+ this.refocusTextarea();
385
+ }
386
+ /**
387
+ * Parses mentions from the message for routing decisions
388
+ */
389
+ parseMentionsFromMessage(message) {
390
+ const mentionResult = this.mentionParser.parseMentions(message, this.mentionAutocomplete.getAvailableAgents(), this.mentionAutocomplete.getAvailableUsers());
391
+ return mentionResult;
392
+ }
393
+ /**
394
+ * Routes the message to the appropriate agent or Sage based on context
395
+ * Priority: @mention > intent check > Sage
396
+ */
397
+ async routeMessage(messageDetail, mentionResult, isFirstMessage) {
398
+ // Priority 1: Direct @mention
399
+ if (mentionResult.agentMention) {
400
+ await this.handleDirectMention(messageDetail, mentionResult.agentMention, isFirstMessage);
401
+ return;
402
+ }
403
+ // Priority 2: Check for previous agent with intent check
404
+ const lastAgentId = this.findLastNonSageAgentId();
405
+ if (lastAgentId) {
406
+ await this.handleAgentContinuity(messageDetail, lastAgentId, mentionResult, isFirstMessage);
407
+ return;
408
+ }
409
+ // Priority 3: No context - use Sage
410
+ await this.handleNoAgentContext(messageDetail, mentionResult, isFirstMessage);
411
+ }
412
+ /**
413
+ * Handles routing when user directly mentions an agent with @
414
+ */
415
+ async handleDirectMention(messageDetail, agentMention, isFirstMessage) {
416
+ console.log('🎯 Direct @mention detected, bypassing Sage');
417
+ await this.executeRouteWithNaming(() => this.invokeAgentDirectly(messageDetail, agentMention, this.conversationId), messageDetail.Message, isFirstMessage);
418
+ }
419
+ /**
420
+ * Handles routing when there's a previous agent - checks intent first
421
+ */
422
+ async handleAgentContinuity(messageDetail, lastAgentId, mentionResult, isFirstMessage) {
423
+ console.log('🔍 Previous agent found, checking continuity intent...');
424
+ const intent = await this.checkContinuityIntent(lastAgentId, messageDetail.Message);
425
+ if (intent === 'YES') {
426
+ console.log('✅ Intent check: YES - continuing with previous agent');
427
+ await this.executeRouteWithNaming(() => this.continueWithAgent(messageDetail, lastAgentId, this.conversationId), messageDetail.Message, isFirstMessage);
428
+ }
429
+ else {
430
+ console.log(`🤖 Intent check: ${intent} - routing through Sage for evaluation`);
431
+ await this.executeRouteWithNaming(() => this.processMessageThroughAgent(messageDetail, mentionResult), messageDetail.Message, isFirstMessage);
432
+ }
433
+ }
434
+ /**
435
+ * Handles routing when there's no previous agent context
436
+ */
437
+ async handleNoAgentContext(messageDetail, mentionResult, isFirstMessage) {
438
+ console.log('🤖 No agent context, using Sage');
439
+ await this.executeRouteWithNaming(() => this.processMessageThroughAgent(messageDetail, mentionResult), messageDetail.Message, isFirstMessage);
440
+ }
441
+ /**
442
+ * Finds the last agent ID that isn't Sage
443
+ */
444
+ findLastNonSageAgentId() {
445
+ const lastAIMessage = this.conversationHistory
446
+ .slice()
447
+ .reverse()
448
+ .find(msg => msg.Role === 'AI' &&
449
+ msg.AgentID &&
450
+ msg.AgentID !== this.converationManagerAgent?.ID);
451
+ return lastAIMessage?.AgentID || null;
452
+ }
453
+ /**
454
+ * Checks if message should continue with the previous agent
455
+ * Shows UI indicator during check
456
+ */
457
+ async checkContinuityIntent(agentId, message) {
458
+ this.processingMessage = 'Analyzing intent...';
459
+ this.isProcessing = true;
460
+ try {
461
+ const intent = await this.agentService.checkAgentContinuityIntent(agentId, message, this.conversationHistory);
462
+ return intent;
463
+ }
464
+ catch (error) {
465
+ console.error('❌ Intent check failed, defaulting to UNSURE:', error);
466
+ return 'UNSURE';
467
+ }
468
+ finally {
469
+ this.processingMessage = 'AI is responding...';
470
+ this.isProcessing = false;
471
+ }
472
+ }
473
+ /**
474
+ * Executes a routing function, optionally with conversation naming for first message
475
+ */
476
+ async executeRouteWithNaming(routeFunction, userMessage, isFirstMessage) {
477
+ if (isFirstMessage) {
478
+ await Promise.all([
479
+ routeFunction(),
480
+ this.nameConversation(userMessage)
481
+ ]);
482
+ }
483
+ else {
484
+ await routeFunction();
485
+ }
486
+ }
487
+ /**
488
+ * Returns focus to the message textarea
489
+ */
490
+ refocusTextarea() {
491
+ setTimeout(() => {
492
+ if (this.messageTextarea?.nativeElement) {
493
+ this.messageTextarea.nativeElement.focus();
494
+ }
495
+ }, 100);
496
+ }
497
+ /**
498
+ * Handles message send failure
499
+ */
500
+ handleSendFailure(messageDetail) {
501
+ console.error('Failed to send message:', messageDetail.LatestResult?.Message);
502
+ this.toastService.error('Failed to send message. Please try again.');
503
+ }
504
+ /**
505
+ * Handles message send error
506
+ */
507
+ handleSendError(error) {
508
+ console.error('Error sending message:', error);
509
+ this.toastService.error('Error sending message. Please try again.');
510
+ }
389
511
  /**
390
512
  * Safe save for ConversationDetail - prevents overwrites of completed/errored messages
391
513
  * Use this ONLY in progress update paths to prevent race conditions
@@ -408,9 +530,23 @@ export class MessageInputComponent {
408
530
  * IMPORTANT: Filters by agentRunId to prevent cross-contamination when multiple agents run in parallel
409
531
  */
410
532
  createProgressCallback(conversationDetail, agentName) {
533
+ // Use closure to capture the agent run ID from the first progress message
534
+ // This allows us to filter out progress messages from other concurrent agents
535
+ let capturedAgentRunId = null;
411
536
  return async (progress) => {
412
537
  // Extract agentRunId from progress metadata
413
538
  const progressAgentRunId = progress.metadata?.agentRunId;
539
+ // Capture the agent run ID from the first progress message
540
+ if (!capturedAgentRunId && progressAgentRunId) {
541
+ capturedAgentRunId = progressAgentRunId;
542
+ console.log(`[${agentName}] 📌 Captured agent run ID: ${capturedAgentRunId} for conversation detail: ${conversationDetail.ID}`);
543
+ }
544
+ // Filter out progress messages from other concurrent agents
545
+ // This prevents cross-contamination when multiple agents run in parallel
546
+ if (capturedAgentRunId && progressAgentRunId && progressAgentRunId !== capturedAgentRunId) {
547
+ console.log(`[${agentName}] 🚫 Ignoring progress from different agent run (expected: ${capturedAgentRunId}, got: ${progressAgentRunId})`);
548
+ return;
549
+ }
414
550
  // Format progress message with visual indicator
415
551
  const progressText = progress.message;
416
552
  // Update the active task with progress details (if it exists)
@@ -419,10 +555,16 @@ export class MessageInputComponent {
419
555
  try {
420
556
  if (conversationDetail) {
421
557
  console.log(`[${agentName}] Got conversation detail from cache - Status: ${conversationDetail.Status}, ID: ${conversationDetail.ID}`);
422
- // Skip progress updates if message is already complete
423
- // Since we're using the cached instance, this check sees the ACTUAL current state
424
- if (conversationDetail.Status === 'Complete') {
425
- console.log(`[${agentName}] ⛔ Skipping progress update - message already complete`);
558
+ // Check 1: Skip if message is already complete or errored
559
+ if (conversationDetail.Status === 'Complete' || conversationDetail.Status === 'Error') {
560
+ console.log(`[${agentName}] ⛔ Skipping progress update - message status is ${conversationDetail.Status}`);
561
+ return;
562
+ }
563
+ // Check 2: Skip if message was marked as completed (prevents race condition)
564
+ // Once a message is marked complete, we reject ALL further progress updates
565
+ const completionTime = this.completionTimestamps.get(conversationDetail.ID);
566
+ if (completionTime) {
567
+ console.log(`[${agentName}] ⛔ Skipping progress update - message was marked complete at ${completionTime}`);
426
568
  return;
427
569
  }
428
570
  // Emit agentRunId if we have it (for parent to track)
@@ -492,7 +634,8 @@ export class MessageInputComponent {
492
634
  taskId = null;
493
635
  }
494
636
  if (!result || !result.success) {
495
- // Evaluation failed
637
+ // Evaluation failed - mark as complete to stop progress updates
638
+ this.markMessageComplete(conversationManagerMessage);
496
639
  conversationManagerMessage.Status = 'Error';
497
640
  conversationManagerMessage.Message = `❌ Evaluation failed`;
498
641
  conversationManagerMessage.Error = result?.agentRun?.ErrorMessage || 'Agent evaluation failed';
@@ -502,6 +645,8 @@ export class MessageInputComponent {
502
645
  await userMessage.Save();
503
646
  this.messageSent.emit(userMessage);
504
647
  console.warn('⚠️ Sage failed:', result?.agentRun?.ErrorMessage);
648
+ // Clean up completion timestamp
649
+ this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
505
650
  return;
506
651
  }
507
652
  console.log('🤖 Sage Response:', {
@@ -509,7 +654,8 @@ export class MessageInputComponent {
509
654
  hasPayload: !!result.payload,
510
655
  hasMessage: !!result.agentRun.Message,
511
656
  payloadKeys: result.payload ? Object.keys(result.payload) : [],
512
- payload: result.payload // Full payload for debugging
657
+ payload: result.payload, // Full payload for debugging,
658
+ suggestedResponses: result.suggestedResponses
513
659
  });
514
660
  // Stage 2: Check for task graph (multi-step orchestration)
515
661
  if (result.payload?.taskGraph) {
@@ -531,11 +677,11 @@ export class MessageInputComponent {
531
677
  }
532
678
  // Stage 4: Direct chat response from Sage
533
679
  else if (result.agentRun.FinalStep === 'Chat' && result.agentRun.Message) {
680
+ // Mark message as completing BEFORE setting final content (prevents race condition)
681
+ this.markMessageComplete(conversationManagerMessage);
534
682
  // Normal chat response
535
- conversationManagerMessage.Message = result.agentRun.Message;
536
- conversationManagerMessage.Status = 'Complete';
537
- await conversationManagerMessage.Save();
538
- this.messageSent.emit(conversationManagerMessage);
683
+ // use update helper to ensure that if there is a race condition with more streaming updates we don't allow that to override this final message
684
+ await this.updateConversationDetail(conversationManagerMessage, result.agentRun.Message, 'Complete', result.suggestedResponses);
539
685
  // Handle artifacts if any (but NOT task graphs - those are intermediate work products)
540
686
  if (result.payload && Object.keys(result.payload).length > 0) {
541
687
  await this.createArtifactFromPayload(result.payload, conversationManagerMessage, result.agentRun.AgentID);
@@ -549,26 +695,35 @@ export class MessageInputComponent {
549
695
  if (taskId) {
550
696
  this.activeTasks.remove(taskId);
551
697
  }
698
+ // Clean up completion timestamp after delay
699
+ this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
552
700
  }
553
701
  // Stage 5: Silent observation - but check for message content first
554
702
  else {
555
703
  // Check if there's a message to display even without payload/taskGraph
556
704
  if (result.agentRun.Message) {
557
705
  console.log('💬 Sage provided a message without payload');
558
- conversationManagerMessage.Message = result.agentRun.Message;
559
- conversationManagerMessage.Status = 'Complete';
706
+ // Mark message as completing BEFORE setting final content
707
+ this.markMessageComplete(conversationManagerMessage);
560
708
  conversationManagerMessage.HiddenToUser = false;
561
- await conversationManagerMessage.Save();
709
+ // use update helper to ensure that if there is a race condition with more streaming updates we don't allow that to override this final message
710
+ await this.updateConversationDetail(conversationManagerMessage, result.agentRun.Message, 'Complete', result.suggestedResponses);
562
711
  this.messageSent.emit(conversationManagerMessage);
712
+ // Clean up completion timestamp after delay
713
+ this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
563
714
  }
564
715
  else {
565
716
  console.log('🔇 Sage chose to observe silently');
717
+ // Mark message as completing
718
+ this.markMessageComplete(conversationManagerMessage);
566
719
  // Hide the Sage message
567
720
  conversationManagerMessage.HiddenToUser = true;
568
- conversationManagerMessage.Status = 'Complete';
569
- await conversationManagerMessage.Save();
721
+ // use update helper to ensure that if there is a race condition with more streaming updates we don't allow that to override this final message
722
+ await this.updateConversationDetail(conversationManagerMessage, conversationManagerMessage.Message, 'Complete');
570
723
  this.messageSent.emit(conversationManagerMessage);
571
724
  await this.handleSilentObservation(userMessage, this.conversationId);
725
+ // Clean up completion timestamp after delay
726
+ this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
572
727
  }
573
728
  // Remove CM from active tasks
574
729
  if (taskId) {
@@ -580,11 +735,15 @@ export class MessageInputComponent {
580
735
  console.error('❌ Error processing message through agents:', error);
581
736
  // Update conversationManagerMessage status to Error
582
737
  if (conversationManagerMessage && conversationManagerMessage.ID) {
738
+ // Mark as complete to stop progress updates
739
+ this.markMessageComplete(conversationManagerMessage);
583
740
  conversationManagerMessage.Status = 'Error';
584
741
  conversationManagerMessage.Message = `❌ Error: ${String(error)}`;
585
742
  conversationManagerMessage.Error = String(error);
586
743
  await conversationManagerMessage.Save();
587
744
  this.messageSent.emit(conversationManagerMessage);
745
+ // Clean up completion timestamp
746
+ this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
588
747
  }
589
748
  // Mark user message as complete
590
749
  userMessage.Status = 'Complete';
@@ -685,27 +844,20 @@ export class MessageInputComponent {
685
844
  };
686
845
  const result = await GraphQLDataProvider.Instance.ExecuteGQL(mutation, variables);
687
846
  console.log('📊 ExecuteTaskGraph result:', {
688
- hasData: !!result?.data,
689
- hasErrors: !!result?.errors,
690
- data: result?.data,
691
- errors: result?.errors
847
+ hasExecuteTaskGraph: !!result?.ExecuteTaskGraph,
848
+ success: result?.ExecuteTaskGraph?.success,
849
+ resultsCount: result?.ExecuteTaskGraph?.results?.length,
850
+ result: result
692
851
  });
693
852
  // Step 4: Update task execution message with results
694
- // Check for GraphQL errors first
695
- if (result?.errors && result.errors.length > 0) {
696
- const errorMsg = result.errors.map((e) => e.message).join(', ');
697
- console.error('❌ GraphQL errors:', result.errors);
698
- taskExecutionMessage.Message = `❌ **${workflowName}** failed: ${errorMsg}`;
699
- taskExecutionMessage.Status = 'Error';
700
- taskExecutionMessage.Error = errorMsg;
701
- }
702
- else if (result?.data?.ExecuteTaskGraph?.success) {
853
+ // ExecuteGQL returns data directly (not wrapped in {data, errors})
854
+ if (result?.ExecuteTaskGraph?.success) {
703
855
  console.log('✅ Task graph execution completed successfully');
704
856
  taskExecutionMessage.Message = `✅ **${workflowName}** completed successfully`;
705
857
  taskExecutionMessage.Status = 'Complete';
706
858
  }
707
859
  else {
708
- const errorMsg = result?.data?.ExecuteTaskGraph?.errorMessage || 'Unknown error';
860
+ const errorMsg = result?.ExecuteTaskGraph?.errorMessage || 'Unknown error';
709
861
  console.error('❌ Task graph execution failed:', errorMsg);
710
862
  taskExecutionMessage.Message = `❌ **${workflowName}** failed: ${errorMsg}`;
711
863
  taskExecutionMessage.Status = 'Error';
@@ -713,6 +865,16 @@ export class MessageInputComponent {
713
865
  }
714
866
  await taskExecutionMessage.Save();
715
867
  this.messageSent.emit(taskExecutionMessage);
868
+ // Trigger artifact reload for this message
869
+ // Artifacts were created on server during task execution and linked to this message
870
+ // This event triggers the parent component to reload artifacts from the database
871
+ this.artifactCreated.emit({
872
+ artifactId: '', // Placeholder - reload will fetch actual artifacts from DB
873
+ versionId: '',
874
+ versionNumber: 1,
875
+ conversationDetailId: taskExecutionMessage.ID,
876
+ name: ''
877
+ });
716
878
  // Unregister from real-time updates (task complete)
717
879
  this.activeTaskExecutionMessageIds.delete(taskExecutionMessage.ID);
718
880
  // Mark user message as complete
@@ -727,6 +889,14 @@ export class MessageInputComponent {
727
889
  taskExecutionMessage.Error = String(error);
728
890
  await taskExecutionMessage.Save();
729
891
  this.messageSent.emit(taskExecutionMessage);
892
+ // Trigger artifact reload even on error - partial artifacts may have been created
893
+ this.artifactCreated.emit({
894
+ artifactId: '',
895
+ versionId: '',
896
+ versionNumber: 1,
897
+ conversationDetailId: taskExecutionMessage.ID,
898
+ name: ''
899
+ });
730
900
  // Unregister from real-time updates (task failed)
731
901
  this.activeTaskExecutionMessageIds.delete(taskExecutionMessage.ID);
732
902
  userMessage.Status = 'Complete';
@@ -734,15 +904,22 @@ export class MessageInputComponent {
734
904
  this.messageSent.emit(userMessage);
735
905
  }
736
906
  }
737
- async updateConversationDetail(convoDetail, message, status) {
907
+ async updateConversationDetail(convoDetail, message, status, suggestedResponses) {
738
908
  if (convoDetail.Status === 'Complete' || convoDetail.Status === 'Error') {
739
909
  return; // Do not update completed or errored messages
740
910
  }
911
+ // Mark as completing BEFORE updating if status is Complete or Error
912
+ if (status === 'Complete' || status === 'Error') {
913
+ this.markMessageComplete(convoDetail);
914
+ }
741
915
  const maxAttempts = 2;
742
916
  let attempts = 0, done = false;
743
917
  while (attempts < maxAttempts && !done) {
744
918
  convoDetail.Message = message;
745
919
  convoDetail.Status = status;
920
+ if (suggestedResponses !== undefined) {
921
+ convoDetail.SuggestedResponses = JSON.stringify(suggestedResponses);
922
+ }
746
923
  await convoDetail.Save();
747
924
  if (convoDetail.Message === message && convoDetail.Status === status) {
748
925
  done = true;
@@ -753,6 +930,10 @@ export class MessageInputComponent {
753
930
  }
754
931
  attempts++;
755
932
  }
933
+ // Clean up completion timestamp after delay
934
+ if (status === 'Complete' || status === 'Error') {
935
+ this.cleanupCompletionTimestamp(convoDetail.ID);
936
+ }
756
937
  }
757
938
  /**
758
939
  * Handle single task execution from task graph using direct agent execution
@@ -1445,13 +1626,30 @@ export class MessageInputComponent {
1445
1626
  // Don't show error to user - naming failures should be silent
1446
1627
  }
1447
1628
  }
1629
+ /**
1630
+ * Marks a conversation detail as complete and records timestamp to prevent race conditions
1631
+ */
1632
+ markMessageComplete(conversationDetail) {
1633
+ const now = Date.now();
1634
+ this.completionTimestamps.set(conversationDetail.ID, now);
1635
+ console.log(`🏁 Marked message ${conversationDetail.ID} as complete at ${now}`);
1636
+ }
1637
+ /**
1638
+ * Cleans up completion timestamps for completed messages (prevents memory leak)
1639
+ */
1640
+ cleanupCompletionTimestamp(conversationDetailId) {
1641
+ // Keep timestamp for a short period to catch any late progress updates
1642
+ setTimeout(() => {
1643
+ this.completionTimestamps.delete(conversationDetailId);
1644
+ }, 5000); // 5 seconds should be more than enough
1645
+ }
1448
1646
  static ɵfac = function MessageInputComponent_Factory(t) { return new (t || MessageInputComponent)(i0.ɵɵdirectiveInject(i1.DialogService), i0.ɵɵdirectiveInject(i2.ToastService), i0.ɵɵdirectiveInject(i3.ConversationAgentService), i0.ɵɵdirectiveInject(i4.ConversationStateService), i0.ɵɵdirectiveInject(i5.DataCacheService), i0.ɵɵdirectiveInject(i6.ActiveTasksService), i0.ɵɵdirectiveInject(i7.MentionAutocompleteService), i0.ɵɵdirectiveInject(i8.MentionParserService)); };
1449
1647
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MessageInputComponent, selectors: [["mj-message-input"]], viewQuery: function MessageInputComponent_Query(rf, ctx) { if (rf & 1) {
1450
1648
  i0.ɵɵviewQuery(_c0, 5);
1451
1649
  } if (rf & 2) {
1452
1650
  let _t;
1453
1651
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.messageTextarea = _t.first);
1454
- } }, inputs: { conversationId: "conversationId", currentUser: "currentUser", disabled: "disabled", placeholder: "placeholder", parentMessageId: "parentMessageId", conversationHistory: "conversationHistory" }, outputs: { messageSent: "messageSent", agentResponse: "agentResponse", agentRunDetected: "agentRunDetected", artifactCreated: "artifactCreated", conversationRenamed: "conversationRenamed" }, decls: 11, vars: 11, consts: [["messageTextarea", ""], [1, "message-input-container"], ["rows", "3", 1, "message-input", 3, "ngModelChange", "keydown", "input", "ngModel", "placeholder", "disabled"], [3, "suggestionSelected", "closed", "suggestions", "position", "visible", "showAbove"], [1, "input-actions"], ["class", "processing-indicator", 4, "ngIf"], ["title", "Attach file (coming soon)", 1, "btn-attach", 3, "disabled"], [1, "fas", "fa-paperclip"], [1, "btn-send", 3, "click", "disabled", "title"], [1, "fas", "fa-paper-plane"], [1, "processing-indicator"], [1, "fas", "fa-circle-notch", "fa-spin"]], template: function MessageInputComponent_Template(rf, ctx) { if (rf & 1) {
1652
+ } }, inputs: { conversationId: "conversationId", currentUser: "currentUser", disabled: "disabled", placeholder: "placeholder", parentMessageId: "parentMessageId", conversationHistory: "conversationHistory" }, outputs: { messageSent: "messageSent", agentResponse: "agentResponse", agentRunDetected: "agentRunDetected", artifactCreated: "artifactCreated", conversationRenamed: "conversationRenamed" }, decls: 11, vars: 13, consts: [["messageTextarea", ""], [1, "message-input-container"], ["rows", "3", 1, "message-input", 3, "ngModelChange", "keydown", "input", "ngModel", "placeholder", "disabled"], [3, "suggestionSelected", "closed", "suggestions", "position", "visible", "showAbove"], [1, "input-actions"], ["class", "processing-indicator", 4, "ngIf"], ["title", "Attach file (coming soon)", 1, "btn-attach", 3, "disabled"], [1, "fas", "fa-paperclip"], [1, "btn-send", 3, "click", "disabled", "title"], [1, "fas", "fa-paper-plane"], [1, "processing-indicator"], [1, "fas", "fa-circle-notch", "fa-spin"]], template: function MessageInputComponent_Template(rf, ctx) { if (rf & 1) {
1455
1653
  const _r1 = i0.ɵɵgetCurrentView();
1456
1654
  i0.ɵɵelementStart(0, "div", 1)(1, "textarea", 2, 0);
1457
1655
  i0.ɵɵtwoWayListener("ngModelChange", function MessageInputComponent_Template_textarea_ngModelChange_1_listener($event) { i0.ɵɵrestoreView(_r1); i0.ɵɵtwoWayBindingSet(ctx.messageText, $event) || (ctx.messageText = $event); return i0.ɵɵresetView($event); });
@@ -1462,7 +1660,7 @@ export class MessageInputComponent {
1462
1660
  i0.ɵɵlistener("suggestionSelected", function MessageInputComponent_Template_mj_mention_dropdown_suggestionSelected_4_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onMentionSelected($event)); })("closed", function MessageInputComponent_Template_mj_mention_dropdown_closed_4_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.closeMentionDropdown()); });
1463
1661
  i0.ɵɵelementEnd();
1464
1662
  i0.ɵɵelementStart(5, "div", 4);
1465
- i0.ɵɵtemplate(6, MessageInputComponent_div_6_Template, 4, 0, "div", 5);
1663
+ i0.ɵɵtemplate(6, MessageInputComponent_div_6_Template, 4, 1, "div", 5);
1466
1664
  i0.ɵɵelementStart(7, "button", 6);
1467
1665
  i0.ɵɵelement(8, "i", 7);
1468
1666
  i0.ɵɵelementEnd();
@@ -1472,8 +1670,9 @@ export class MessageInputComponent {
1472
1670
  i0.ɵɵelementEnd()()();
1473
1671
  } if (rf & 2) {
1474
1672
  i0.ɵɵadvance();
1673
+ i0.ɵɵclassProp("intent-checking", ctx.isProcessing);
1475
1674
  i0.ɵɵtwoWayProperty("ngModel", ctx.messageText);
1476
- i0.ɵɵproperty("placeholder", ctx.placeholder)("disabled", ctx.disabled || ctx.isSending);
1675
+ i0.ɵɵproperty("placeholder", ctx.placeholder)("disabled", ctx.disabled || ctx.isProcessing);
1477
1676
  i0.ɵɵadvance(3);
1478
1677
  i0.ɵɵproperty("suggestions", ctx.mentionSuggestions)("position", ctx.mentionDropdownPosition)("visible", ctx.showMentionDropdown)("showAbove", ctx.mentionDropdownShowAbove);
1479
1678
  i0.ɵɵadvance(2);
@@ -1482,11 +1681,11 @@ export class MessageInputComponent {
1482
1681
  i0.ɵɵproperty("disabled", ctx.disabled);
1483
1682
  i0.ɵɵadvance(2);
1484
1683
  i0.ɵɵproperty("disabled", !ctx.canSend)("title", ctx.isSending ? "Sending..." : "Send message");
1485
- } }, dependencies: [i9.NgIf, i10.DefaultValueAccessor, i10.NgControlStatus, i10.NgModel, i11.MentionDropdownComponent], styles: [".message-input-container[_ngcontent-%COMP%] {\n position: relative;\n padding: 16px 24px;\n border-top: 1px solid #D9D9D9;\n background: #FFF;\n}\n\n.message-input-wrapper[_ngcontent-%COMP%] {\n border: 2px solid #D9D9D9;\n border-radius: 8px;\n padding: 12px;\n transition: border-color 0.2s, box-shadow 0.2s;\n background: #FFF;\n}\n\n.message-input-wrapper[_ngcontent-%COMP%]:focus-within {\n border-color: #0076B6;\n box-shadow: 0 0 0 3px rgba(0, 118, 182, 0.1);\n}\n\n.message-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 0;\n border: none;\n resize: none;\n font-family: inherit;\n font-size: 14px;\n min-height: 40px;\n max-height: 200px;\n line-height: 1.5;\n}\n\n.message-input[_ngcontent-%COMP%]:focus {\n outline: none;\n}\n\n.message-input[_ngcontent-%COMP%]:disabled {\n background: #F4F4F4;\n cursor: not-allowed;\n}\n.input-actions[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-top: 12px;\n}\n.btn-attach[_ngcontent-%COMP%] {\n padding: 8px 16px;\n background: transparent;\n border: 1px solid #D9D9D9;\n border-radius: 6px;\n cursor: pointer;\n color: #333;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.btn-attach[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #F4F4F4;\n border-color: #AAA;\n}\n.btn-attach[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.btn-send[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n background: #3B82F6;\n color: white;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s;\n flex-shrink: 0;\n}\n.btn-send[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #2563EB;\n}\n.btn-send[_ngcontent-%COMP%]:disabled {\n background: #D9D9D9;\n color: #AAA;\n cursor: not-allowed;\n}\n.btn-send[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n}\n.processing-indicator[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #6B7280;\n margin-right: auto;\n}\n.processing-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #0076B6;\n}"] });
1684
+ } }, dependencies: [i9.NgIf, i10.DefaultValueAccessor, i10.NgControlStatus, i10.NgModel, i11.MentionDropdownComponent], styles: [".message-input-container[_ngcontent-%COMP%] {\n position: relative;\n padding: 16px 24px;\n border-top: 1px solid #D9D9D9;\n background: #FFF;\n}\n\n.message-input-wrapper[_ngcontent-%COMP%] {\n border: 2px solid #D9D9D9;\n border-radius: 8px;\n padding: 12px;\n transition: border-color 0.2s, box-shadow 0.2s;\n background: #FFF;\n}\n\n.message-input-wrapper[_ngcontent-%COMP%]:focus-within {\n border-color: #0076B6;\n box-shadow: 0 0 0 3px rgba(0, 118, 182, 0.1);\n}\n\n.message-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 0;\n border: none;\n resize: none;\n font-family: inherit;\n font-size: 14px;\n min-height: 40px;\n max-height: 200px;\n line-height: 1.5;\n}\n\n.message-input[_ngcontent-%COMP%]:focus {\n outline: none;\n}\n\n.message-input[_ngcontent-%COMP%]:disabled {\n background: transparent;\n cursor: wait;\n}\n\n//[_ngcontent-%COMP%] Subtle[_ngcontent-%COMP%] visual[_ngcontent-%COMP%] feedback[_ngcontent-%COMP%] when[_ngcontent-%COMP%] checking[_ngcontent-%COMP%] intent[_ngcontent-%COMP%] (no[_ngcontent-%COMP%] ugly[_ngcontent-%COMP%] gray[_ngcontent-%COMP%] background)\n.message-input.intent-checking[_ngcontent-%COMP%] {\n opacity: 0.6;\n transition: opacity 0.2s;\n}\n.input-actions[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-top: 12px;\n}\n.btn-attach[_ngcontent-%COMP%] {\n padding: 8px 16px;\n background: transparent;\n border: 1px solid #D9D9D9;\n border-radius: 6px;\n cursor: pointer;\n color: #333;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.btn-attach[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #F4F4F4;\n border-color: #AAA;\n}\n.btn-attach[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.btn-send[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n background: #3B82F6;\n color: white;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s;\n flex-shrink: 0;\n}\n.btn-send[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #2563EB;\n}\n.btn-send[_ngcontent-%COMP%]:disabled {\n background: #D9D9D9;\n color: #AAA;\n cursor: not-allowed;\n}\n.btn-send[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n}\n.processing-indicator[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #6B7280;\n margin-right: auto;\n}\n.processing-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #0076B6;\n}"] });
1486
1685
  }
1487
1686
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MessageInputComponent, [{
1488
1687
  type: Component,
1489
- args: [{ selector: 'mj-message-input', template: "<div class=\"message-input-container\">\n <textarea\n #messageTextarea\n class=\"message-input\"\n [(ngModel)]=\"messageText\"\n [placeholder]=\"placeholder\"\n [disabled]=\"disabled || isSending\"\n (keydown)=\"onKeyDown($event)\"\n (input)=\"onInput($event)\"\n rows=\"3\">\n </textarea>\n\n <!-- Mention Autocomplete Dropdown -->\n <mj-mention-dropdown\n [suggestions]=\"mentionSuggestions\"\n [position]=\"mentionDropdownPosition\"\n [visible]=\"showMentionDropdown\"\n [showAbove]=\"mentionDropdownShowAbove\"\n (suggestionSelected)=\"onMentionSelected($event)\"\n (closed)=\"closeMentionDropdown()\">\n </mj-mention-dropdown>\n\n <div class=\"input-actions\">\n <div class=\"processing-indicator\" *ngIf=\"isProcessing\">\n <i class=\"fas fa-circle-notch fa-spin\"></i>\n <span>AI is responding...</span>\n </div>\n <button\n class=\"btn-attach\"\n [disabled]=\"disabled\"\n title=\"Attach file (coming soon)\">\n <i class=\"fas fa-paperclip\"></i>\n </button>\n <button\n class=\"btn-send\"\n [disabled]=\"!canSend\"\n (click)=\"onSend()\"\n [title]=\"isSending ? 'Sending...' : 'Send message'\">\n <i class=\"fas fa-paper-plane\"></i>\n </button>\n </div>\n</div>", styles: [".message-input-container {\n position: relative;\n padding: 16px 24px;\n border-top: 1px solid #D9D9D9;\n background: #FFF;\n}\n\n.message-input-wrapper {\n border: 2px solid #D9D9D9;\n border-radius: 8px;\n padding: 12px;\n transition: border-color 0.2s, box-shadow 0.2s;\n background: #FFF;\n}\n\n.message-input-wrapper:focus-within {\n border-color: #0076B6;\n box-shadow: 0 0 0 3px rgba(0, 118, 182, 0.1);\n}\n\n.message-input {\n width: 100%;\n padding: 0;\n border: none;\n resize: none;\n font-family: inherit;\n font-size: 14px;\n min-height: 40px;\n max-height: 200px;\n line-height: 1.5;\n}\n\n.message-input:focus {\n outline: none;\n}\n\n.message-input:disabled {\n background: #F4F4F4;\n cursor: not-allowed;\n}\n.input-actions {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-top: 12px;\n}\n.btn-attach {\n padding: 8px 16px;\n background: transparent;\n border: 1px solid #D9D9D9;\n border-radius: 6px;\n cursor: pointer;\n color: #333;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.btn-attach:hover:not(:disabled) {\n background: #F4F4F4;\n border-color: #AAA;\n}\n.btn-attach:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.btn-send {\n width: 40px;\n height: 40px;\n background: #3B82F6;\n color: white;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s;\n flex-shrink: 0;\n}\n.btn-send:hover:not(:disabled) {\n background: #2563EB;\n}\n.btn-send:disabled {\n background: #D9D9D9;\n color: #AAA;\n cursor: not-allowed;\n}\n.btn-send i {\n font-size: 16px;\n}\n.processing-indicator {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #6B7280;\n margin-right: auto;\n}\n.processing-indicator i {\n color: #0076B6;\n}"] }]
1688
+ args: [{ selector: 'mj-message-input', template: "<div class=\"message-input-container\">\n <textarea\n #messageTextarea\n class=\"message-input\"\n [class.intent-checking]=\"isProcessing\"\n [(ngModel)]=\"messageText\"\n [placeholder]=\"placeholder\"\n [disabled]=\"disabled || isProcessing\"\n (keydown)=\"onKeyDown($event)\"\n (input)=\"onInput($event)\"\n rows=\"3\">\n </textarea>\n\n <!-- Mention Autocomplete Dropdown -->\n <mj-mention-dropdown\n [suggestions]=\"mentionSuggestions\"\n [position]=\"mentionDropdownPosition\"\n [visible]=\"showMentionDropdown\"\n [showAbove]=\"mentionDropdownShowAbove\"\n (suggestionSelected)=\"onMentionSelected($event)\"\n (closed)=\"closeMentionDropdown()\">\n </mj-mention-dropdown>\n\n <div class=\"input-actions\">\n <div class=\"processing-indicator\" *ngIf=\"isProcessing\">\n <i class=\"fas fa-circle-notch fa-spin\"></i>\n <span>{{ processingMessage }}</span>\n </div>\n <button\n class=\"btn-attach\"\n [disabled]=\"disabled\"\n title=\"Attach file (coming soon)\">\n <i class=\"fas fa-paperclip\"></i>\n </button>\n <button\n class=\"btn-send\"\n [disabled]=\"!canSend\"\n (click)=\"onSend()\"\n [title]=\"isSending ? 'Sending...' : 'Send message'\">\n <i class=\"fas fa-paper-plane\"></i>\n </button>\n </div>\n</div>", styles: [".message-input-container {\n position: relative;\n padding: 16px 24px;\n border-top: 1px solid #D9D9D9;\n background: #FFF;\n}\n\n.message-input-wrapper {\n border: 2px solid #D9D9D9;\n border-radius: 8px;\n padding: 12px;\n transition: border-color 0.2s, box-shadow 0.2s;\n background: #FFF;\n}\n\n.message-input-wrapper:focus-within {\n border-color: #0076B6;\n box-shadow: 0 0 0 3px rgba(0, 118, 182, 0.1);\n}\n\n.message-input {\n width: 100%;\n padding: 0;\n border: none;\n resize: none;\n font-family: inherit;\n font-size: 14px;\n min-height: 40px;\n max-height: 200px;\n line-height: 1.5;\n}\n\n.message-input:focus {\n outline: none;\n}\n\n.message-input:disabled {\n background: transparent;\n cursor: wait;\n}\n\n// Subtle visual feedback when checking intent (no ugly gray background)\n.message-input.intent-checking {\n opacity: 0.6;\n transition: opacity 0.2s;\n}\n.input-actions {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-top: 12px;\n}\n.btn-attach {\n padding: 8px 16px;\n background: transparent;\n border: 1px solid #D9D9D9;\n border-radius: 6px;\n cursor: pointer;\n color: #333;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.btn-attach:hover:not(:disabled) {\n background: #F4F4F4;\n border-color: #AAA;\n}\n.btn-attach:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.btn-send {\n width: 40px;\n height: 40px;\n background: #3B82F6;\n color: white;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s;\n flex-shrink: 0;\n}\n.btn-send:hover:not(:disabled) {\n background: #2563EB;\n}\n.btn-send:disabled {\n background: #D9D9D9;\n color: #AAA;\n cursor: not-allowed;\n}\n.btn-send i {\n font-size: 16px;\n}\n.processing-indicator {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #6B7280;\n margin-right: auto;\n}\n.processing-indicator i {\n color: #0076B6;\n}"] }]
1490
1689
  }], () => [{ type: i1.DialogService }, { type: i2.ToastService }, { type: i3.ConversationAgentService }, { type: i4.ConversationStateService }, { type: i5.DataCacheService }, { type: i6.ActiveTasksService }, { type: i7.MentionAutocompleteService }, { type: i8.MentionParserService }], { conversationId: [{
1491
1690
  type: Input
1492
1691
  }], currentUser: [{