@hef2024/llmasaservice-ui 0.22.0 → 0.22.2

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.
package/dist/index.js CHANGED
@@ -3894,6 +3894,7 @@ var AIChatPanel = ({
3894
3894
  const lastScrollTopRef = (0, import_react12.useRef)(0);
3895
3895
  const scrollRAFRef = (0, import_react12.useRef)(null);
3896
3896
  const lastScrollTimeRef = (0, import_react12.useRef)(0);
3897
+ const prevResponseLengthRef = (0, import_react12.useRef)(0);
3897
3898
  const prevIdleRef = (0, import_react12.useRef)(true);
3898
3899
  const hasNotifiedCompletionRef = (0, import_react12.useRef)(true);
3899
3900
  const latestHistoryRef = (0, import_react12.useRef)(initialHistory);
@@ -4334,11 +4335,22 @@ var AIChatPanel = ({
4334
4335
  thumbsDownClick(callId);
4335
4336
  }
4336
4337
  }), [thumbsDownClick, interactionClicked]);
4337
- const scrollToBottom = (0, import_react12.useCallback)(() => {
4338
+ const scrollToBottom = (0, import_react12.useCallback)((force = false) => {
4338
4339
  var _a2;
4339
4340
  if (scrollRAFRef.current) {
4340
4341
  cancelAnimationFrame(scrollRAFRef.current);
4341
4342
  }
4343
+ if (!force && responseAreaRef.current) {
4344
+ const scrollViewport = responseAreaRef.current.querySelector("[data-radix-scroll-area-viewport]");
4345
+ const scrollElement = scrollViewport || responseAreaRef.current;
4346
+ const scrollTop = scrollElement.scrollTop;
4347
+ const scrollHeight = scrollElement.scrollHeight;
4348
+ const clientHeight = scrollElement.clientHeight;
4349
+ const isNearBottom = scrollHeight - scrollTop - clientHeight < 100;
4350
+ if (!isNearBottom) {
4351
+ return;
4352
+ }
4353
+ }
4342
4354
  const now = Date.now();
4343
4355
  if (now - lastScrollTimeRef.current < 100) {
4344
4356
  scrollRAFRef.current = requestAnimationFrame(() => {
@@ -4357,6 +4369,7 @@ var AIChatPanel = ({
4357
4369
  setCurrentThinkingIndex(0);
4358
4370
  setError(null);
4359
4371
  setUserHasScrolled(false);
4372
+ prevResponseLengthRef.current = 0;
4360
4373
  setResponse("");
4361
4374
  if (!idle) {
4362
4375
  stop(lastController);
@@ -4382,7 +4395,7 @@ var AIChatPanel = ({
4382
4395
  setLastPrompt(promptToSend.trim());
4383
4396
  setLastKey(promptKey);
4384
4397
  setTimeout(() => {
4385
- scrollToBottom();
4398
+ scrollToBottom(true);
4386
4399
  }, 0);
4387
4400
  console.log("AIChatPanel.continueChat - about to call ensureConversation");
4388
4401
  ensureConversation().then((convId) => {
@@ -4557,12 +4570,16 @@ var AIChatPanel = ({
4557
4570
  }
4558
4571
  if (!isNowIdle && hasNotifiedCompletionRef.current) {
4559
4572
  hasNotifiedCompletionRef.current = false;
4573
+ prevResponseLengthRef.current = 0;
4560
4574
  }
4561
4575
  }, [idle]);
4562
4576
  (0, import_react12.useEffect)(() => {
4577
+ const currentResponseLength = response.length;
4578
+ const responseGotLonger = currentResponseLength > prevResponseLengthRef.current;
4579
+ prevResponseLengthRef.current = currentResponseLength;
4563
4580
  const shouldAutoScroll = scrollToEnd || !userHasScrolled;
4564
- if (!idle && shouldAutoScroll && response) {
4565
- scrollToBottom();
4581
+ if (!idle && shouldAutoScroll && response && responseGotLonger) {
4582
+ scrollToBottom(false);
4566
4583
  }
4567
4584
  }, [response, scrollToBottom, idle, userHasScrolled, scrollToEnd]);
4568
4585
  const idleRef = (0, import_react12.useRef)(idle);
@@ -4722,7 +4739,7 @@ var AIChatPanel = ({
4722
4739
  const AgentSuggestionCard = import_react12.default.memo(({ agentId, agentName, reason }) => {
4723
4740
  (0, import_react12.useEffect)(() => {
4724
4741
  const timer = setTimeout(() => {
4725
- scrollToBottom();
4742
+ scrollToBottom(true);
4726
4743
  }, 100);
4727
4744
  return () => clearTimeout(timer);
4728
4745
  }, []);
@@ -4769,7 +4786,7 @@ var AIChatPanel = ({
4769
4786
  onClick: () => {
4770
4787
  onAgentChange(agentId);
4771
4788
  setTimeout(() => {
4772
- scrollToBottom();
4789
+ scrollToBottom(true);
4773
4790
  }, 100);
4774
4791
  }
4775
4792
  },
package/dist/index.mjs CHANGED
@@ -3861,6 +3861,7 @@ var AIChatPanel = ({
3861
3861
  const lastScrollTopRef = useRef5(0);
3862
3862
  const scrollRAFRef = useRef5(null);
3863
3863
  const lastScrollTimeRef = useRef5(0);
3864
+ const prevResponseLengthRef = useRef5(0);
3864
3865
  const prevIdleRef = useRef5(true);
3865
3866
  const hasNotifiedCompletionRef = useRef5(true);
3866
3867
  const latestHistoryRef = useRef5(initialHistory);
@@ -4301,11 +4302,22 @@ var AIChatPanel = ({
4301
4302
  thumbsDownClick(callId);
4302
4303
  }
4303
4304
  }), [thumbsDownClick, interactionClicked]);
4304
- const scrollToBottom = useCallback2(() => {
4305
+ const scrollToBottom = useCallback2((force = false) => {
4305
4306
  var _a2;
4306
4307
  if (scrollRAFRef.current) {
4307
4308
  cancelAnimationFrame(scrollRAFRef.current);
4308
4309
  }
4310
+ if (!force && responseAreaRef.current) {
4311
+ const scrollViewport = responseAreaRef.current.querySelector("[data-radix-scroll-area-viewport]");
4312
+ const scrollElement = scrollViewport || responseAreaRef.current;
4313
+ const scrollTop = scrollElement.scrollTop;
4314
+ const scrollHeight = scrollElement.scrollHeight;
4315
+ const clientHeight = scrollElement.clientHeight;
4316
+ const isNearBottom = scrollHeight - scrollTop - clientHeight < 100;
4317
+ if (!isNearBottom) {
4318
+ return;
4319
+ }
4320
+ }
4309
4321
  const now = Date.now();
4310
4322
  if (now - lastScrollTimeRef.current < 100) {
4311
4323
  scrollRAFRef.current = requestAnimationFrame(() => {
@@ -4324,6 +4336,7 @@ var AIChatPanel = ({
4324
4336
  setCurrentThinkingIndex(0);
4325
4337
  setError(null);
4326
4338
  setUserHasScrolled(false);
4339
+ prevResponseLengthRef.current = 0;
4327
4340
  setResponse("");
4328
4341
  if (!idle) {
4329
4342
  stop(lastController);
@@ -4349,7 +4362,7 @@ var AIChatPanel = ({
4349
4362
  setLastPrompt(promptToSend.trim());
4350
4363
  setLastKey(promptKey);
4351
4364
  setTimeout(() => {
4352
- scrollToBottom();
4365
+ scrollToBottom(true);
4353
4366
  }, 0);
4354
4367
  console.log("AIChatPanel.continueChat - about to call ensureConversation");
4355
4368
  ensureConversation().then((convId) => {
@@ -4524,12 +4537,16 @@ var AIChatPanel = ({
4524
4537
  }
4525
4538
  if (!isNowIdle && hasNotifiedCompletionRef.current) {
4526
4539
  hasNotifiedCompletionRef.current = false;
4540
+ prevResponseLengthRef.current = 0;
4527
4541
  }
4528
4542
  }, [idle]);
4529
4543
  useEffect7(() => {
4544
+ const currentResponseLength = response.length;
4545
+ const responseGotLonger = currentResponseLength > prevResponseLengthRef.current;
4546
+ prevResponseLengthRef.current = currentResponseLength;
4530
4547
  const shouldAutoScroll = scrollToEnd || !userHasScrolled;
4531
- if (!idle && shouldAutoScroll && response) {
4532
- scrollToBottom();
4548
+ if (!idle && shouldAutoScroll && response && responseGotLonger) {
4549
+ scrollToBottom(false);
4533
4550
  }
4534
4551
  }, [response, scrollToBottom, idle, userHasScrolled, scrollToEnd]);
4535
4552
  const idleRef = useRef5(idle);
@@ -4689,7 +4706,7 @@ var AIChatPanel = ({
4689
4706
  const AgentSuggestionCard = React12.memo(({ agentId, agentName, reason }) => {
4690
4707
  useEffect7(() => {
4691
4708
  const timer = setTimeout(() => {
4692
- scrollToBottom();
4709
+ scrollToBottom(true);
4693
4710
  }, 100);
4694
4711
  return () => clearTimeout(timer);
4695
4712
  }, []);
@@ -4736,7 +4753,7 @@ var AIChatPanel = ({
4736
4753
  onClick: () => {
4737
4754
  onAgentChange(agentId);
4738
4755
  setTimeout(() => {
4739
- scrollToBottom();
4756
+ scrollToBottom(true);
4740
4757
  }, 100);
4741
4758
  }
4742
4759
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hef2024/llmasaservice-ui",
3
- "version": "0.22.0",
3
+ "version": "0.22.2",
4
4
  "description": "Prebuilt UI components for LLMAsAService.io",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -846,6 +846,7 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
846
846
  const lastScrollTopRef = useRef<number>(0);
847
847
  const scrollRAFRef = useRef<number | null>(null);
848
848
  const lastScrollTimeRef = useRef<number>(0);
849
+ const prevResponseLengthRef = useRef<number>(0);
849
850
 
850
851
  // === NEW: Clean history management refs ===
851
852
  // Track previous idle state to detect transitions (false→true = completion)
@@ -1470,12 +1471,28 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1470
1471
  }, [thumbsDownClick, interactionClicked]);
1471
1472
 
1472
1473
  // Scroll to bottom - throttled using RAF to prevent layout thrashing
1473
- const scrollToBottom = useCallback(() => {
1474
+ const scrollToBottom = useCallback((force: boolean = false) => {
1474
1475
  // Cancel any pending scroll
1475
1476
  if (scrollRAFRef.current) {
1476
1477
  cancelAnimationFrame(scrollRAFRef.current);
1477
1478
  }
1478
1479
 
1480
+ // Check if we should scroll - only if user is near bottom or force is true
1481
+ if (!force && responseAreaRef.current) {
1482
+ const scrollViewport = responseAreaRef.current.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement;
1483
+ const scrollElement = scrollViewport || responseAreaRef.current;
1484
+
1485
+ const scrollTop = scrollElement.scrollTop;
1486
+ const scrollHeight = scrollElement.scrollHeight;
1487
+ const clientHeight = scrollElement.clientHeight;
1488
+ const isNearBottom = scrollHeight - scrollTop - clientHeight < 100;
1489
+
1490
+ // If user is not near bottom, don't scroll (prevents scroll on layout changes like toast)
1491
+ if (!isNearBottom) {
1492
+ return;
1493
+ }
1494
+ }
1495
+
1479
1496
  // Throttle to max once per 100ms to prevent performance issues during streaming
1480
1497
  const now = Date.now();
1481
1498
  if (now - lastScrollTimeRef.current < 100) {
@@ -1505,6 +1522,7 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1505
1522
 
1506
1523
  // Reset scroll tracking for new message - enable auto-scroll
1507
1524
  setUserHasScrolled(false);
1525
+ prevResponseLengthRef.current = 0;
1508
1526
 
1509
1527
  // IMPORTANT: Clear the response BEFORE setting new lastKey
1510
1528
  // This prevents the old response from being written to the new history entry
@@ -1551,8 +1569,9 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1551
1569
 
1552
1570
  // Scroll to bottom immediately to show the new prompt
1553
1571
  // Use setTimeout to ensure the DOM has updated
1572
+ // Force scroll since this is a user-initiated action
1554
1573
  setTimeout(() => {
1555
- scrollToBottom();
1574
+ scrollToBottom(true);
1556
1575
  }, 0);
1557
1576
 
1558
1577
  // Now proceed with API calls in the background (conversation creation + LLM call)
@@ -1797,18 +1816,26 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1797
1816
  // Reset notification flag when starting a new stream
1798
1817
  if (!isNowIdle && hasNotifiedCompletionRef.current) {
1799
1818
  hasNotifiedCompletionRef.current = false;
1819
+ // Reset response length tracking for new stream
1820
+ prevResponseLengthRef.current = 0;
1800
1821
  }
1801
1822
  }, [idle]); // ONLY depends on idle - no history, no callbacks in deps
1802
-
1823
+
1803
1824
  // Auto-scroll to bottom - only while streaming and user hasn't manually scrolled
1804
1825
  useEffect(() => {
1805
1826
  // Only auto-scroll if:
1806
1827
  // 1. We're actively streaming (!idle)
1807
1828
  // 2. User hasn't manually scrolled up during this response (or scrollToEnd prop is true)
1808
1829
  // 3. We have content to show (response exists)
1830
+ // 4. The response actually got longer (not just a re-render from layout change)
1831
+ const currentResponseLength = response.length;
1832
+ const responseGotLonger = currentResponseLength > prevResponseLengthRef.current;
1833
+ prevResponseLengthRef.current = currentResponseLength;
1834
+
1809
1835
  const shouldAutoScroll = scrollToEnd || !userHasScrolled;
1810
- if (!idle && shouldAutoScroll && response) {
1811
- scrollToBottom();
1836
+ if (!idle && shouldAutoScroll && response && responseGotLonger) {
1837
+ // Use non-forced scroll - will only scroll if near bottom
1838
+ scrollToBottom(false);
1812
1839
  }
1813
1840
  }, [response, scrollToBottom, idle, userHasScrolled, scrollToEnd]); // Removed history dependency
1814
1841
 
@@ -2037,8 +2064,9 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
2037
2064
  // Auto-scroll when the agent suggestion card appears
2038
2065
  useEffect(() => {
2039
2066
  // Small delay to ensure the card is fully rendered in the DOM
2067
+ // Force scroll since this is new content being added
2040
2068
  const timer = setTimeout(() => {
2041
- scrollToBottom();
2069
+ scrollToBottom(true);
2042
2070
  }, 100);
2043
2071
  return () => clearTimeout(timer);
2044
2072
  }, []); // Empty deps - only run on mount
@@ -2158,8 +2186,9 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
2158
2186
  onClick={() => {
2159
2187
  onAgentChange(agentId);
2160
2188
  // Scroll to bottom after a brief delay to let React re-render
2189
+ // Force scroll since this is a user-initiated action
2161
2190
  setTimeout(() => {
2162
- scrollToBottom();
2191
+ scrollToBottom(true);
2163
2192
  }, 100);
2164
2193
  }}
2165
2194
  >