@gravity-ui/aikit 1.0.0 → 1.0.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.
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect } from 'react';
3
2
  import { useScrollPreservation, useSmartScroll } from '../../../hooks';
4
3
  import { isAssistantMessage, isUserMessage, resolveMessageActions, } from '../../../utils';
5
4
  import { block } from '../../../utils/cn';
@@ -13,12 +12,12 @@ const b = block('message-list');
13
12
  export function MessageList({ messages, messageRendererRegistry, transformOptions, showActionsOnHover, showTimestamp, showAvatar, userActions, assistantActions, loaderStatuses = ['submitted'], className, qa, status, errorMessage, onRetry, hasPreviousMessages = false, onLoadPreviousMessages, }) {
14
13
  const isStreaming = status === 'streaming';
15
14
  const isSubmitted = status === 'submitted';
16
- const messagesCount = messages.length;
17
15
  const showLoader = status && loaderStatuses.includes(status);
18
- const { containerRef, endRef, scrollToBottom } = useSmartScroll(isStreaming, messagesCount);
19
- useEffect(() => {
20
- scrollToBottom();
21
- }, []);
16
+ const { containerRef, endRef } = useSmartScroll({
17
+ isStreaming: isStreaming || isSubmitted,
18
+ messagesCount: messages.length,
19
+ status,
20
+ });
22
21
  // Preserve scroll position when older messages are loaded
23
22
  useScrollPreservation(containerRef, messages.length);
24
23
  const renderMessage = (message, index) => {
@@ -1,7 +1,12 @@
1
1
  import { type RefObject } from 'react';
2
+ import type { ChatStatus } from '../types';
2
3
  export interface UseSmartScrollReturn<T extends HTMLElement> {
3
4
  containerRef: RefObject<T>;
4
5
  endRef: RefObject<T>;
5
- scrollToBottom: () => void;
6
+ scrollToBottom: (behavior?: ScrollBehavior) => void;
6
7
  }
7
- export declare function useSmartScroll<T extends HTMLElement>(isStreaming?: boolean, messagesCount?: number): UseSmartScrollReturn<T>;
8
+ export declare function useSmartScroll<T extends HTMLElement>({ isStreaming, messagesCount, status, }: {
9
+ isStreaming?: boolean;
10
+ messagesCount: number;
11
+ status?: ChatStatus;
12
+ }): UseSmartScrollReturn<T>;
@@ -1,26 +1,21 @@
1
- import { useCallback, useEffect, useRef, useState } from 'react';
1
+ import { useCallback, useEffect, useRef } from 'react';
2
2
  const SCROLL_THRESHOLD = 10;
3
- export function useSmartScroll(isStreaming = false, messagesCount = 0) {
3
+ export function useSmartScroll({ isStreaming = false, messagesCount, status, }) {
4
4
  const containerRef = useRef(null);
5
5
  const endRef = useRef(null);
6
- const [userScrolledUp, setUserScrolledUp] = useState(false);
7
- const prevMessagesCount = useRef(messagesCount);
8
- const checkIfUserScrolledUp = useCallback(() => {
9
- const container = containerRef.current;
10
- if (!container)
11
- return false;
12
- const { scrollTop, scrollHeight, clientHeight } = container;
13
- const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
14
- return distanceFromBottom > SCROLL_THRESHOLD;
15
- }, []);
6
+ const userScrolledUpRef = useRef(false);
16
7
  const scrollToBottom = useCallback((behavior = 'instant') => {
17
- if (!userScrolledUp) {
8
+ if (!userScrolledUpRef.current) {
18
9
  const end = endRef.current;
19
10
  if (end) {
20
11
  end.scrollIntoView({ behavior, block: 'end' });
21
12
  }
22
13
  }
23
- }, [userScrolledUp]);
14
+ }, []);
15
+ // Initial scroll to bottom
16
+ useEffect(() => {
17
+ scrollToBottom();
18
+ }, []);
24
19
  // Handle user scroll events
25
20
  useEffect(() => {
26
21
  const container = containerRef.current;
@@ -28,14 +23,19 @@ export function useSmartScroll(isStreaming = false, messagesCount = 0) {
28
23
  return undefined;
29
24
  }
30
25
  const handleScroll = () => {
31
- const scrolledUp = checkIfUserScrolledUp();
32
- setUserScrolledUp(scrolledUp);
26
+ if (!container) {
27
+ return;
28
+ }
29
+ const { scrollTop, scrollHeight, clientHeight } = container;
30
+ const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
31
+ const scrolledUp = distanceFromBottom > SCROLL_THRESHOLD;
32
+ userScrolledUpRef.current = scrolledUp;
33
33
  };
34
34
  container.addEventListener('scroll', handleScroll, { passive: true });
35
35
  return () => {
36
36
  container.removeEventListener('scroll', handleScroll);
37
37
  };
38
- }, [checkIfUserScrolledUp]);
38
+ }, []);
39
39
  // Handle DOM mutations during streaming
40
40
  useEffect(() => {
41
41
  const container = containerRef.current;
@@ -54,14 +54,16 @@ export function useSmartScroll(isStreaming = false, messagesCount = 0) {
54
54
  return () => {
55
55
  observer.disconnect();
56
56
  };
57
- }, [isStreaming, scrollToBottom]);
58
- // Handle new messages
57
+ }, [isStreaming]);
58
+ // Handle status changes
59
+ useEffect(() => {
60
+ scrollToBottom('smooth');
61
+ }, [status]);
59
62
  useEffect(() => {
60
- if (messagesCount > prevMessagesCount.current) {
63
+ if (messagesCount) {
61
64
  scrollToBottom('smooth');
62
65
  }
63
- prevMessagesCount.current = messagesCount;
64
- }, [messagesCount, scrollToBottom]);
66
+ }, [messagesCount]);
65
67
  return {
66
68
  containerRef,
67
69
  endRef,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/aikit",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Gravity UI base kit for building ai assistant chats",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
@@ -118,8 +118,8 @@
118
118
  "typescript": "^5.4.2"
119
119
  },
120
120
  "peerDependencies": {
121
- "react": "^18.2.0",
122
- "react-dom": "^18.2.0",
121
+ "react": "^18.0.0 || ^19.0.0",
122
+ "react-dom": "^18.0.0 || ^19.0.0",
123
123
  "@diplodoc/transform": "^4.63.3",
124
124
  "@gravity-ui/i18n": "^1.8.0",
125
125
  "@gravity-ui/icons": "^2.16.0",