@hivegpt/hiveai-angular 0.0.611 → 0.0.613

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.
@@ -1614,6 +1614,8 @@ class ChatDrawerComponent {
1614
1614
  /** Guards against our own programmatic scrolls triggering the userHasScrolled detection. */
1615
1615
  this.isProgrammaticScroll = false;
1616
1616
  this.scrollListenerTimer = null;
1617
+ /** Throttle timer for streaming chunk scroll — prevents firing on every chunk. */
1618
+ this.chunkScrollRAF = null;
1617
1619
  /** Connections list from host (e.g. from store selectConnectionsList). Each item has userId. When set, used for Connect/Request Sent/Disconnect button state. */
1618
1620
  this.connectionsList = [];
1619
1621
  /** Pending sent request user IDs from host (e.g. from store selectPendingConnectionsSentList). When set, used for Request Sent state. */
@@ -3441,7 +3443,8 @@ class ChatDrawerComponent {
3441
3443
  reader.read().then(({ done, value }) => {
3442
3444
  const lastItem = this.chatLog[this.chatLog.length - 1];
3443
3445
  if (done) {
3444
- lastItem.message = this.processMessageForDisplay(lastItem.message);
3446
+ // Final pass with the complete raw text (aiResponse) for a clean render
3447
+ lastItem.message = this.processMessageForDisplay(this.aiResponse);
3445
3448
  this.chatLog.pop();
3446
3449
  this.chatLog.push(lastItem);
3447
3450
  if (allSuggestions?.length) {
@@ -3470,7 +3473,7 @@ class ChatDrawerComponent {
3470
3473
  allSuggestions.push(match?.replace(/<\/?sug>/g, ''));
3471
3474
  });
3472
3475
  }
3473
- lastItem.message = this.aiResponse;
3476
+ lastItem.message = this.processMessageForDisplay(this.aiResponse);
3474
3477
  this.cdr.markForCheck();
3475
3478
  }
3476
3479
  else {
@@ -3757,7 +3760,7 @@ class ChatDrawerComponent {
3757
3760
  }
3758
3761
  this.cdr.detectChanges();
3759
3762
  }
3760
- scrollToBottom(force = false, smooth = true) {
3763
+ scrollToBottom(force = false) {
3761
3764
  if (!force && !this.autoScrollOnNewMessage) {
3762
3765
  return;
3763
3766
  }
@@ -3770,10 +3773,36 @@ class ChatDrawerComponent {
3770
3773
  this.isProgrammaticScroll = true;
3771
3774
  el.scrollTo({
3772
3775
  top: el.scrollHeight,
3773
- behavior: smooth ? 'smooth' : 'auto',
3776
+ behavior: 'smooth',
3777
+ });
3778
+ setTimeout(() => { this.isProgrammaticScroll = false; }, 400);
3779
+ }
3780
+ /**
3781
+ * Lightweight scroll-to-bottom used during streaming chunks.
3782
+ * Coalesces via requestAnimationFrame so we get at most one smooth
3783
+ * nudge per paint frame, no matter how fast chunks arrive.
3784
+ */
3785
+ scrollToBottomStreaming() {
3786
+ if (this.userHasScrolled)
3787
+ return;
3788
+ if (this.chunkScrollRAF !== null)
3789
+ return; // already queued
3790
+ this.chunkScrollRAF = requestAnimationFrame(() => {
3791
+ this.chunkScrollRAF = null;
3792
+ if (this.userHasScrolled)
3793
+ return;
3794
+ const el = this.chatMain?.nativeElement;
3795
+ if (!el)
3796
+ return;
3797
+ // Small gap check: only scroll if we're within a reasonable distance from the bottom
3798
+ const gap = el.scrollHeight - el.scrollTop - el.clientHeight;
3799
+ if (gap <= 0)
3800
+ return; // already at bottom
3801
+ this.isProgrammaticScroll = true;
3802
+ // Use a quick smooth nudge rather than instant jump
3803
+ el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' });
3804
+ setTimeout(() => { this.isProgrammaticScroll = false; }, 300);
3774
3805
  });
3775
- // Release the guard after the smooth scroll settles
3776
- setTimeout(() => { this.isProgrammaticScroll = false; }, smooth ? 400 : 50);
3777
3806
  }
3778
3807
  /** Scrolls the chat container so the top of the AI message with the given id is visible. */
3779
3808
  scrollToAiMessage(messageId) {
@@ -4675,7 +4704,9 @@ class ChatDrawerComponent {
4675
4704
  return;
4676
4705
  const isNewAiMessage = !this.chatLog.find((p) => p._id == messageId);
4677
4706
  const currentChatMessage = this.upsertAiChatMessage(messageId);
4678
- currentChatMessage.message = `${currentChatMessage.message || ''}${payload?.text || ''}`;
4707
+ // Accumulate raw text and render markdown in real-time
4708
+ currentChatMessage.rawMessage = `${currentChatMessage.rawMessage || ''}${payload?.text || ''}`;
4709
+ currentChatMessage.message = this.processMessageForDisplay(currentChatMessage.rawMessage);
4679
4710
  if (isNewAiMessage) {
4680
4711
  // First chunk: hide Thinking indicator and scroll to the message start
4681
4712
  this.isChatingWithAi = false;
@@ -4684,7 +4715,7 @@ class ChatDrawerComponent {
4684
4715
  }
4685
4716
  else {
4686
4717
  this.cdr.markForCheck();
4687
- setTimeout(() => this.scrollToBottom(true, false), 0);
4718
+ this.scrollToBottomStreaming();
4688
4719
  }
4689
4720
  break;
4690
4721
  }
@@ -4695,12 +4726,14 @@ class ChatDrawerComponent {
4695
4726
  break;
4696
4727
  }
4697
4728
  const currentChatMessage = this.upsertAiChatMessage(messageId);
4698
- const finalAnswer = payload?.text ?? currentChatMessage.message ?? '';
4729
+ const finalAnswer = payload?.text ?? currentChatMessage.rawMessage ?? currentChatMessage.message ?? '';
4699
4730
  const hasCardResponse = this.applyToolResultCardMessage(currentChatMessage, messageId, finalAnswer, payload?.tool_results);
4700
4731
  if (!hasCardResponse) {
4701
4732
  currentChatMessage.type = 'ai';
4702
4733
  currentChatMessage.message = this.processMessageForDisplay(finalAnswer);
4703
4734
  }
4735
+ // Clean up raw accumulator
4736
+ delete currentChatMessage.rawMessage;
4704
4737
  if (Array.isArray(payload?.suggestions)) {
4705
4738
  currentChatMessage.relatedListItems = payload.suggestions;
4706
4739
  }
@@ -4715,13 +4748,10 @@ class ChatDrawerComponent {
4715
4748
  this.showFeedBackIconsIndex = this.chatLog.length - 1;
4716
4749
  this.activeAskMessageId = '';
4717
4750
  this.isChatingWithAi = false;
4718
- // For card responses (tool results), scroll to the message text bubble
4719
- // instead of the very bottom avoids cards pushing the view too far down
4720
- if (hasCardResponse && messageId) {
4721
- this.cdr.markForCheck();
4722
- setTimeout(() => this.scrollToAiMessage(messageId), 30);
4723
- }
4724
- else {
4751
+ // For card responses (tool results), don't scroll at all
4752
+ // user is already viewing the AI text from the streaming phase,
4753
+ // cards render below and the user can scroll down to see them.
4754
+ if (!hasCardResponse) {
4725
4755
  this.scrollToBottom();
4726
4756
  }
4727
4757
  this.focusOnTextarea();