@hef2024/llmasaservice-ui 0.21.0 → 0.22.1

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.
@@ -96,6 +96,8 @@ export interface AIAgentPanelProps {
96
96
  theme?: 'light' | 'dark';
97
97
  collapsible?: boolean;
98
98
  defaultCollapsed?: boolean;
99
+ isCollapsed?: boolean;
100
+ onCollapsedChange?: (isCollapsed: boolean) => void;
99
101
  defaultWidth?: number;
100
102
  minWidth?: number;
101
103
  maxWidth?: number;
@@ -618,6 +620,8 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
618
620
  theme = 'light',
619
621
  collapsible = true,
620
622
  defaultCollapsed = false,
623
+ isCollapsed: controlledIsCollapsed,
624
+ onCollapsedChange,
621
625
  defaultWidth = 720,
622
626
  minWidth = 520,
623
627
  maxWidth = 1200,
@@ -660,8 +664,36 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
660
664
  customerEmailCaptureMode,
661
665
  customerEmailCapturePlaceholder,
662
666
  }, ref) => {
667
+ // Dev mode warnings for prop conflicts
668
+ useEffect(() => {
669
+ if (process.env.NODE_ENV === 'development') {
670
+ // Warn if isCollapsed is provided without onCollapsedChange (user can't interact)
671
+ if (controlledIsCollapsed !== undefined && !onCollapsedChange) {
672
+ console.warn(
673
+ 'AIAgentPanel: You provided `isCollapsed` prop without `onCollapsedChange`. ' +
674
+ 'This will render a read-only collapsed state. To allow user interaction, provide both props.'
675
+ );
676
+ }
677
+
678
+ // Warn if both isCollapsed and defaultCollapsed are provided (conflicting props)
679
+ if (controlledIsCollapsed !== undefined && defaultCollapsed !== false) {
680
+ console.warn(
681
+ 'AIAgentPanel: You provided both `isCollapsed` and `defaultCollapsed` props. ' +
682
+ 'When using controlled mode (isCollapsed), the defaultCollapsed prop is ignored. ' +
683
+ 'Remove defaultCollapsed to avoid confusion.'
684
+ );
685
+ }
686
+ }
687
+ }, [controlledIsCollapsed, onCollapsedChange, defaultCollapsed]);
688
+
663
689
  // Panel state - only start collapsed if collapsible is true
664
- const [isCollapsed, setIsCollapsed] = useState(collapsible && defaultCollapsed);
690
+ const [uncontrolledIsCollapsed, setUncontrolledIsCollapsed] = useState(collapsible && defaultCollapsed);
691
+
692
+ // Determine if controlled
693
+ const isControlled = controlledIsCollapsed !== undefined;
694
+
695
+ // Use controlled value if provided, otherwise use internal state
696
+ const isCollapsed = isControlled ? controlledIsCollapsed : uncontrolledIsCollapsed;
665
697
  const [isHistoryCollapsed, setIsHistoryCollapsed] = useState(() => {
666
698
  if (typeof window !== 'undefined') {
667
699
  const saved = localStorage.getItem('ai-agent-panel-history-collapsed');
@@ -1876,8 +1908,17 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
1876
1908
  // Toggle collapse - only if collapsible is enabled
1877
1909
  const toggleCollapse = useCallback(() => {
1878
1910
  if (!collapsible) return;
1879
- setIsCollapsed((prev) => !prev);
1880
- }, [collapsible]);
1911
+
1912
+ const newValue = !isCollapsed;
1913
+
1914
+ // Update internal state if uncontrolled
1915
+ if (!isControlled) {
1916
+ setUncontrolledIsCollapsed(newValue);
1917
+ }
1918
+
1919
+ // Call callback if provided
1920
+ onCollapsedChange?.(newValue);
1921
+ }, [collapsible, isCollapsed, isControlled, onCollapsedChange]);
1881
1922
 
1882
1923
  // Toggle history collapse
1883
1924
  const toggleHistoryCollapse = useCallback(() => {
@@ -1927,7 +1968,11 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
1927
1968
  variant="ghost"
1928
1969
  size="icon"
1929
1970
  onClick={() => {
1930
- setIsCollapsed(false);
1971
+ // Expand panel in controlled or uncontrolled mode
1972
+ if (!isControlled) {
1973
+ setUncontrolledIsCollapsed(false);
1974
+ }
1975
+ onCollapsedChange?.(false);
1931
1976
  setShowSearch(true);
1932
1977
  }}
1933
1978
  >
@@ -1941,7 +1986,11 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
1941
1986
  variant="ghost"
1942
1987
  size="icon"
1943
1988
  onClick={() => {
1944
- setIsCollapsed(false);
1989
+ // Expand panel in controlled or uncontrolled mode
1990
+ if (!isControlled) {
1991
+ setUncontrolledIsCollapsed(false);
1992
+ }
1993
+ onCollapsedChange?.(false);
1945
1994
  handleNewConversation();
1946
1995
  }}
1947
1996
  >
@@ -1964,7 +2013,11 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
1964
2013
  variant={agent.id === currentAgentId ? 'secondary' : 'ghost'}
1965
2014
  size="icon"
1966
2015
  onClick={() => {
1967
- setIsCollapsed(false);
2016
+ // Expand panel in controlled or uncontrolled mode
2017
+ if (!isControlled) {
2018
+ setUncontrolledIsCollapsed(false);
2019
+ }
2020
+ onCollapsedChange?.(false);
1968
2021
  if (hasActiveConversation && activeConvForAgent) {
1969
2022
  // Switch to the existing conversation
1970
2023
  setCurrentConversationId(activeConvForAgent.conversationId);
@@ -1470,12 +1470,28 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1470
1470
  }, [thumbsDownClick, interactionClicked]);
1471
1471
 
1472
1472
  // Scroll to bottom - throttled using RAF to prevent layout thrashing
1473
- const scrollToBottom = useCallback(() => {
1473
+ const scrollToBottom = useCallback((force: boolean = false) => {
1474
1474
  // Cancel any pending scroll
1475
1475
  if (scrollRAFRef.current) {
1476
1476
  cancelAnimationFrame(scrollRAFRef.current);
1477
1477
  }
1478
1478
 
1479
+ // Check if we should scroll - only if user is near bottom or force is true
1480
+ if (!force && responseAreaRef.current) {
1481
+ const scrollViewport = responseAreaRef.current.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement;
1482
+ const scrollElement = scrollViewport || responseAreaRef.current;
1483
+
1484
+ const scrollTop = scrollElement.scrollTop;
1485
+ const scrollHeight = scrollElement.scrollHeight;
1486
+ const clientHeight = scrollElement.clientHeight;
1487
+ const isNearBottom = scrollHeight - scrollTop - clientHeight < 100;
1488
+
1489
+ // If user is not near bottom, don't scroll (prevents scroll on layout changes like toast)
1490
+ if (!isNearBottom) {
1491
+ return;
1492
+ }
1493
+ }
1494
+
1479
1495
  // Throttle to max once per 100ms to prevent performance issues during streaming
1480
1496
  const now = Date.now();
1481
1497
  if (now - lastScrollTimeRef.current < 100) {
@@ -1551,8 +1567,9 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1551
1567
 
1552
1568
  // Scroll to bottom immediately to show the new prompt
1553
1569
  // Use setTimeout to ensure the DOM has updated
1570
+ // Force scroll since this is a user-initiated action
1554
1571
  setTimeout(() => {
1555
- scrollToBottom();
1572
+ scrollToBottom(true);
1556
1573
  }, 0);
1557
1574
 
1558
1575
  // Now proceed with API calls in the background (conversation creation + LLM call)
@@ -1808,7 +1825,8 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
1808
1825
  // 3. We have content to show (response exists)
1809
1826
  const shouldAutoScroll = scrollToEnd || !userHasScrolled;
1810
1827
  if (!idle && shouldAutoScroll && response) {
1811
- scrollToBottom();
1828
+ // Use non-forced scroll - will only scroll if near bottom
1829
+ scrollToBottom(false);
1812
1830
  }
1813
1831
  }, [response, scrollToBottom, idle, userHasScrolled, scrollToEnd]); // Removed history dependency
1814
1832
 
@@ -2037,8 +2055,9 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
2037
2055
  // Auto-scroll when the agent suggestion card appears
2038
2056
  useEffect(() => {
2039
2057
  // Small delay to ensure the card is fully rendered in the DOM
2058
+ // Force scroll since this is new content being added
2040
2059
  const timer = setTimeout(() => {
2041
- scrollToBottom();
2060
+ scrollToBottom(true);
2042
2061
  }, 100);
2043
2062
  return () => clearTimeout(timer);
2044
2063
  }, []); // Empty deps - only run on mount
@@ -2158,8 +2177,9 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
2158
2177
  onClick={() => {
2159
2178
  onAgentChange(agentId);
2160
2179
  // Scroll to bottom after a brief delay to let React re-render
2180
+ // Force scroll since this is a user-initiated action
2161
2181
  setTimeout(() => {
2162
- scrollToBottom();
2182
+ scrollToBottom(true);
2163
2183
  }, 100);
2164
2184
  }}
2165
2185
  >
@@ -60,3 +60,5 @@ export default Button;
60
60
 
61
61
 
62
62
 
63
+
64
+
@@ -156,3 +156,5 @@ export default Dialog;
156
156
 
157
157
 
158
158
 
159
+
160
+
@@ -36,3 +36,5 @@ export default Input;
36
36
 
37
37
 
38
38
 
39
+
40
+
@@ -159,3 +159,5 @@ export default Select;
159
159
 
160
160
 
161
161
 
162
+
163
+
@@ -67,3 +67,5 @@ export default ToolInfoModal;
67
67
 
68
68
 
69
69
 
70
+
71
+
@@ -76,3 +76,5 @@ export default Tooltip;
76
76
 
77
77
 
78
78
 
79
+
80
+
@@ -23,3 +23,5 @@ export type { DialogProps, DialogFooterProps } from './Dialog';
23
23
 
24
24
 
25
25
 
26
+
27
+