@lobehub/lobehub 2.0.0-next.65 → 2.0.0-next.67

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 (70) hide show
  1. package/.github/workflows/claude-translator.yml +1 -0
  2. package/CHANGELOG.md +50 -0
  3. package/changelog/v1.json +18 -0
  4. package/locales/ar/chat.json +3 -0
  5. package/locales/bg-BG/chat.json +3 -0
  6. package/locales/de-DE/chat.json +3 -0
  7. package/locales/en-US/chat.json +3 -0
  8. package/locales/es-ES/chat.json +3 -0
  9. package/locales/fa-IR/chat.json +3 -0
  10. package/locales/fr-FR/chat.json +3 -0
  11. package/locales/it-IT/chat.json +3 -0
  12. package/locales/ja-JP/chat.json +3 -0
  13. package/locales/ko-KR/chat.json +3 -0
  14. package/locales/nl-NL/chat.json +3 -0
  15. package/locales/pl-PL/chat.json +3 -0
  16. package/locales/pt-BR/chat.json +3 -0
  17. package/locales/ru-RU/chat.json +3 -0
  18. package/locales/tr-TR/chat.json +3 -0
  19. package/locales/vi-VN/chat.json +3 -0
  20. package/locales/zh-CN/chat.json +3 -0
  21. package/locales/zh-TW/chat.json +3 -0
  22. package/package.json +7 -6
  23. package/packages/conversation-flow/src/__tests__/fixtures/index.ts +4 -8
  24. package/packages/conversation-flow/src/__tests__/fixtures/inputs/{assistant-with-tools.json → assistantGroup/assistant-with-tools.json} +2 -1
  25. package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistantGroup/index.ts +8 -0
  26. package/packages/conversation-flow/src/__tests__/fixtures/inputs/index.ts +2 -4
  27. package/packages/conversation-flow/src/__tests__/fixtures/outputs/{assistant-with-tools.json → assistantGroup/assistant-with-tools.json} +8 -8
  28. package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistantGroup/index.ts +8 -0
  29. package/packages/conversation-flow/src/__tests__/parse.test.ts +6 -6
  30. package/packages/conversation-flow/src/parse.ts +45 -1
  31. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +64 -0
  32. package/packages/database/package.json +2 -2
  33. package/packages/obervability-otel/package.json +1 -1
  34. package/packages/types/src/message/common/metadata.ts +8 -1
  35. package/packages/types/src/message/ui/chat.ts +1 -0
  36. package/src/app/(backend)/market/agent/[[...segments]]/route.ts +1 -1
  37. package/src/app/(backend)/market/oidc/[[...segments]]/route.ts +1 -1
  38. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/ChatItem/index.tsx +1 -0
  39. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatMinimap/index.tsx +21 -28
  40. package/src/app/market-auth-callback/layout.tsx +27 -3
  41. package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +2 -2
  42. package/src/features/ChatItem/style.ts +4 -0
  43. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +18 -4
  44. package/src/features/Conversation/Messages/Assistant/CollapsedMessage.tsx +37 -0
  45. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +16 -9
  46. package/src/features/Conversation/Messages/Assistant/index.tsx +329 -230
  47. package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +31 -9
  48. package/src/features/Conversation/Messages/Group/CollapsedMessage.tsx +37 -0
  49. package/src/features/Conversation/Messages/Group/{GroupChildren.tsx → Group.tsx} +18 -4
  50. package/src/features/Conversation/Messages/Group/GroupItem.tsx +3 -5
  51. package/src/features/Conversation/Messages/Group/index.tsx +84 -19
  52. package/src/features/Conversation/Messages/User/Actions/ActionsBar.tsx +3 -3
  53. package/src/features/Conversation/Messages/index.tsx +24 -8
  54. package/src/features/Conversation/components/VirtualizedList/VirtuosoContext.ts +13 -13
  55. package/src/features/Conversation/components/VirtualizedList/index.tsx +92 -58
  56. package/src/features/Conversation/components/WideScreenContainer/index.tsx +10 -6
  57. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +14 -0
  58. package/src/features/Conversation/hooks/useDoubleClickEdit.ts +3 -3
  59. package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +1 -1
  60. package/src/libs/mcp/__tests__/index.test.ts +6 -6
  61. package/src/locales/default/chat.ts +3 -0
  62. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +9 -1
  63. package/src/store/chat/slices/message/actions/publicApi.ts +17 -0
  64. package/src/store/chat/slices/message/selectors/displayMessage.ts +1 -1
  65. package/src/store/chat/slices/message/selectors/messageState.ts +7 -0
  66. package/src/store/chat/slices/translate/action.test.ts +26 -32
  67. package/src/store/chat/slices/translate/action.ts +3 -3
  68. /package/packages/conversation-flow/src/__tests__/fixtures/inputs/{complex-scenario.json → assistantGroup/tools-with-branches.json} +0 -0
  69. /package/packages/conversation-flow/src/__tests__/fixtures/outputs/{complex-scenario.json → assistantGroup/tools-with-branches.json} +0 -0
  70. /package/src/features/Conversation/Messages/Group/{GroupContext.tsx → GroupContext.ts} +0 -0
@@ -6,7 +6,7 @@ import { memo, use, useCallback, useContext, useMemo, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import ShareMessageModal from '@/features/Conversation/components/ShareMessageModal';
9
- import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
9
+ import { VirtuaContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
10
10
  import { useChatStore } from '@/store/chat';
11
11
  import { messageStateSelectors, threadSelectors } from '@/store/chat/selectors';
12
12
  import { useSessionStore } from '@/store/session';
@@ -24,19 +24,30 @@ interface GroupActionsProps {
24
24
 
25
25
  const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }) => {
26
26
  const { tools } = data;
27
- const [isThreadMode, hasThread, isRegenerating] = useChatStore((s) => [
27
+ const [isThreadMode, hasThread, isRegenerating, isCollapsed] = useChatStore((s) => [
28
28
  !!s.activeThreadId,
29
29
  threadSelectors.hasThreadBySourceMsgId(id)(s),
30
30
  messageStateSelectors.isMessageRegenerating(id)(s),
31
+ messageStateSelectors.isMessageCollapsed(id)(s),
31
32
  ]);
32
33
  const isGroupSession = useSessionStore(sessionSelectors.isCurrentSessionGroupSession);
33
34
  const [showShareModal, setShareModal] = useState(false);
34
35
 
35
- const { edit, delAndRegenerate, regenerate, copy, divider, del, branching, share } =
36
- useChatListActionsBar({
37
- hasThread,
38
- isRegenerating,
39
- });
36
+ const {
37
+ edit,
38
+ delAndRegenerate,
39
+ regenerate,
40
+ copy,
41
+ divider,
42
+ del,
43
+ branching,
44
+ share,
45
+ expand,
46
+ collapse,
47
+ } = useChatListActionsBar({
48
+ hasThread,
49
+ isRegenerating,
50
+ });
40
51
 
41
52
  const hasTools = !!tools;
42
53
 
@@ -64,6 +75,7 @@ const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }
64
75
  resendThreadMessage,
65
76
  delAndResendThreadMessage,
66
77
  toggleMessageEditing,
78
+ toggleMessageCollapsed,
67
79
  ] = useChatStore((s) => [
68
80
  s.deleteMessage,
69
81
  s.regenerateAssistantMessage,
@@ -74,9 +86,10 @@ const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }
74
86
  s.resendThreadMessage,
75
87
  s.delAndResendThreadMessage,
76
88
  s.toggleMessageEditing,
89
+ s.toggleMessageCollapsed,
77
90
  ]);
78
91
  const { message } = App.useApp();
79
- const virtuosoRef = use(VirtuosoContext);
92
+ const virtuaRef = use(VirtuaContext);
80
93
 
81
94
  const onActionClick = useCallback(
82
95
  async (action: ActionIconGroupEvent) => {
@@ -84,7 +97,7 @@ const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }
84
97
  case 'edit': {
85
98
  toggleMessageEditing(id, true);
86
99
 
87
- virtuosoRef?.current?.scrollIntoView({ align: 'start', behavior: 'auto', index });
100
+ virtuaRef?.current?.scrollToIndex(index, { align: 'start' });
88
101
  }
89
102
  }
90
103
  if (!data) return;
@@ -133,6 +146,12 @@ const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }
133
146
  setShareModal(true);
134
147
  break;
135
148
  }
149
+
150
+ case 'collapse':
151
+ case 'expand': {
152
+ toggleMessageCollapsed(id);
153
+ break;
154
+ }
136
155
  }
137
156
 
138
157
  if (action.keyPath.at(-1) === 'translate') {
@@ -146,6 +165,8 @@ const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }
146
165
  [data, topic],
147
166
  );
148
167
 
168
+ const collapseAction = isCollapsed ? expand : collapse;
169
+
149
170
  return (
150
171
  <>
151
172
  <ActionIconGroup
@@ -154,6 +175,7 @@ const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }
154
175
  items: [
155
176
  edit,
156
177
  copy,
178
+ collapseAction,
157
179
  divider,
158
180
  share,
159
181
  divider,
@@ -0,0 +1,37 @@
1
+ import { Button, Markdown, MaskShadow } from '@lobehub/ui';
2
+ import { memo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { useChatStore } from '@/store/chat';
7
+
8
+ interface CollapsedMessageProps {
9
+ content: string;
10
+ id: string;
11
+ }
12
+
13
+ export const CollapsedMessage = memo<CollapsedMessageProps>(({ id, content }) => {
14
+ const { t } = useTranslation('chat');
15
+ const toggleMessageCollapsed = useChatStore((s) => s.toggleMessageCollapsed);
16
+
17
+ return (
18
+ <Flexbox>
19
+ <MaskShadow>
20
+ <Markdown variant={'chat'}>{content?.slice(0, 300)}</Markdown>
21
+ </MaskShadow>
22
+ <Flexbox padding={4}>
23
+ <Button
24
+ block
25
+ color={'default'}
26
+ onClick={() => {
27
+ toggleMessageCollapsed(id, false);
28
+ }}
29
+ size={'small'}
30
+ variant={'filled'}
31
+ >
32
+ {t('chatList.expandMessage')}
33
+ </Button>
34
+ </Flexbox>
35
+ </Flexbox>
36
+ );
37
+ });
@@ -4,6 +4,10 @@ import isEqual from 'fast-deep-equal';
4
4
  import { memo, useMemo } from 'react';
5
5
  import { Flexbox } from 'react-layout-kit';
6
6
 
7
+ import { CollapsedMessage } from '@/features/Conversation/Messages/Group/CollapsedMessage';
8
+ import { useChatStore } from '@/store/chat';
9
+ import { messageStateSelectors } from '@/store/chat/slices/message/selectors';
10
+
7
11
  import { GroupMessageContext } from './GroupContext';
8
12
  import GroupItem from './GroupItem';
9
13
 
@@ -19,18 +23,28 @@ const useStyles = createStyles(({ css }) => {
19
23
 
20
24
  interface GroupChildrenProps {
21
25
  blocks: AssistantContentBlock[];
26
+ content?: string;
22
27
  contentId?: string;
23
28
  disableEditing?: boolean;
24
29
  id: string;
25
30
  messageIndex: number;
26
31
  }
27
32
 
28
- const GroupChildren = memo<GroupChildrenProps>(
29
- ({ blocks, contentId, disableEditing, messageIndex, id }) => {
33
+ const Group = memo<GroupChildrenProps>(
34
+ ({ blocks, contentId, disableEditing, messageIndex, id, content }) => {
30
35
  const { styles } = useStyles();
31
-
36
+ const [isCollapsed] = useChatStore((s) => [messageStateSelectors.isMessageCollapsed(id)(s)]);
32
37
  const contextValue = useMemo(() => ({ assistantGroupId: id }), [id]);
33
38
 
39
+ if (isCollapsed) {
40
+ return (
41
+ content && (
42
+ <Flexbox>
43
+ <CollapsedMessage content={content} id={id} />
44
+ </Flexbox>
45
+ )
46
+ );
47
+ }
34
48
  return (
35
49
  <GroupMessageContext value={contextValue}>
36
50
  <Flexbox className={styles.container} gap={8}>
@@ -53,4 +67,4 @@ const GroupChildren = memo<GroupChildrenProps>(
53
67
  isEqual,
54
68
  );
55
69
 
56
- export default GroupChildren;
70
+ export default Group;
@@ -4,7 +4,7 @@ import { memo, use } from 'react';
4
4
  import { Flexbox } from 'react-layout-kit';
5
5
 
6
6
  import { ContentBlock } from '@/features/Conversation/Messages/Group/ContentBlock';
7
- import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
7
+ import { VirtuaContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
8
8
  import { useChatStore } from '@/store/chat';
9
9
 
10
10
  interface GroupItemProps extends AssistantContentBlock {
@@ -17,7 +17,7 @@ interface GroupItemProps extends AssistantContentBlock {
17
17
  const GroupItem = memo<GroupItemProps>(
18
18
  ({ contentId, messageIndex, index, disableEditing, error, ...item }) => {
19
19
  const [toggleMessageEditing] = useChatStore((s) => [s.toggleMessageEditing]);
20
- const virtuosoRef = use(VirtuosoContext);
20
+ const virtuaRef = use(VirtuaContext);
21
21
 
22
22
  return item.id === contentId ? (
23
23
  <Flexbox
@@ -25,10 +25,8 @@ const GroupItem = memo<GroupItemProps>(
25
25
  if (disableEditing || error || !e.altKey) return;
26
26
 
27
27
  toggleMessageEditing(item.id, true);
28
- virtuosoRef?.current?.scrollIntoView({
28
+ virtuaRef?.current?.scrollToIndex(messageIndex, {
29
29
  align: 'start',
30
- behavior: 'auto',
31
- index: messageIndex,
32
30
  });
33
31
  }}
34
32
  >
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { UIChatMessage } from '@lobechat/types';
4
- import { useResponsive } from 'antd-style';
4
+ import { createStyles, useResponsive } from 'antd-style';
5
5
  import isEqual from 'fast-deep-equal';
6
6
  import { memo, useCallback } from 'react';
7
7
  import { Flexbox } from 'react-layout-kit';
@@ -9,33 +9,101 @@ import { Flexbox } from 'react-layout-kit';
9
9
  import Avatar from '@/features/ChatItem/components/Avatar';
10
10
  import BorderSpacing from '@/features/ChatItem/components/BorderSpacing';
11
11
  import Title from '@/features/ChatItem/components/Title';
12
- import { useStyles } from '@/features/ChatItem/style';
13
- import GroupChildren from '@/features/Conversation/Messages/Group/GroupChildren';
14
12
  import Usage from '@/features/Conversation/components/Extras/Usage';
15
13
  import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
16
14
  import { useAgentStore } from '@/store/agent';
17
15
  import { agentChatConfigSelectors } from '@/store/agent/selectors';
18
16
  import { useChatStore } from '@/store/chat';
19
- import {
20
- displayMessageSelectors,
21
- messageStateSelectors,
22
- } from '@/store/chat/slices/message/selectors';
17
+ import { displayMessageSelectors, messageStateSelectors } from '@/store/chat/selectors';
23
18
  import { useGlobalStore } from '@/store/global';
24
19
  import { useSessionStore } from '@/store/session';
25
20
  import { sessionSelectors } from '@/store/session/selectors';
26
21
 
27
22
  import { GroupActionsBar } from './Actions';
28
23
  import EditState from './EditState';
24
+ import Group from './Group';
29
25
 
30
26
  const MOBILE_AVATAR_SIZE = 32;
31
27
 
28
+ const useStyles = createStyles(
29
+ ({ cx, css, token, responsive }, { variant }: { variant?: 'bubble' | 'docs' }) => {
30
+ const rawContainerStylish = css`
31
+ margin-block-end: -16px;
32
+ transition: background-color 100ms ${token.motionEaseOut};
33
+ `;
34
+
35
+ return {
36
+ actions: css`
37
+ flex: none;
38
+ align-self: flex-end;
39
+ justify-content: flex-end;
40
+ `,
41
+ container: cx(
42
+ variant === 'docs' && rawContainerStylish,
43
+ css`
44
+ position: relative;
45
+
46
+ width: 100%;
47
+ max-width: 100vw;
48
+ padding-block: 24px 12px;
49
+ padding-inline: 12px;
50
+
51
+ @supports (content-visibility: auto) {
52
+ contain-intrinsic-size: auto 100lvh;
53
+ }
54
+
55
+ time {
56
+ display: inline-block;
57
+ white-space: nowrap;
58
+ }
59
+
60
+ div[role='menubar'] {
61
+ display: flex;
62
+ }
63
+
64
+ time,
65
+ div[role='menubar'] {
66
+ pointer-events: none;
67
+ opacity: 0;
68
+ transition: opacity 200ms ${token.motionEaseOut};
69
+ }
70
+
71
+ &:hover {
72
+ time,
73
+ div[role='menubar'] {
74
+ pointer-events: unset;
75
+ opacity: 1;
76
+ }
77
+ }
78
+
79
+ ${responsive.mobile} {
80
+ padding-block-start: ${variant === 'docs' ? '16px' : '12px'};
81
+ padding-inline: 8px;
82
+ }
83
+ `,
84
+ ),
85
+ messageContent: css`
86
+ position: relative;
87
+ overflow: hidden;
88
+ width: 100%;
89
+ max-width: 100%;
90
+
91
+ ${responsive.mobile} {
92
+ flex-direction: column !important;
93
+ }
94
+ `,
95
+ };
96
+ },
97
+ );
98
+
32
99
  interface GroupMessageProps {
33
100
  disableEditing?: boolean;
34
101
  id: string;
35
102
  index: number;
103
+ isLatestItem?: boolean;
36
104
  }
37
105
 
38
- const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing }) => {
106
+ const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing, isLatestItem }) => {
39
107
  const item = useChatStore(
40
108
  displayMessageSelectors.getDisplayMessageById(id),
41
109
  isEqual,
@@ -48,15 +116,7 @@ const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing }) =>
48
116
  const type = useAgentStore(agentChatConfigSelectors.displayMode);
49
117
  const variant = type === 'chat' ? 'bubble' : 'docs';
50
118
 
51
- const { styles } = useStyles({
52
- editing: false,
53
- placement,
54
- primary: false,
55
- showTitle: true,
56
- time: createdAt,
57
- title: avatar.title,
58
- variant,
59
- });
119
+ const { styles } = useStyles({ variant });
60
120
 
61
121
  const [isInbox] = useSessionStore((s) => [sessionSelectors.isInboxSession(s)]);
62
122
  const [toggleSystemRole] = useGlobalStore((s) => [s.toggleSystemRole]);
@@ -82,7 +142,11 @@ const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing }) =>
82
142
  }, [isInbox]);
83
143
 
84
144
  return (
85
- <Flexbox className={styles.container} gap={mobile ? 6 : 12}>
145
+ <Flexbox
146
+ className={styles.container}
147
+ gap={mobile ? 6 : 12}
148
+ style={isLatestItem ? { minHeight: 'calc(-300px + 100dvh)' } : undefined}
149
+ >
86
150
  <Flexbox gap={4} horizontal>
87
151
  <Avatar
88
152
  alt={avatar.title || 'avatar'}
@@ -111,8 +175,9 @@ const GroupMessage = memo<GroupMessageProps>(({ id, index, disableEditing }) =>
111
175
  width={'100%'}
112
176
  >
113
177
  {children && children.length > 0 && (
114
- <GroupChildren
178
+ <Group
115
179
  blocks={children}
180
+ content={lastAssistantMsg?.content}
116
181
  contentId={contentId}
117
182
  disableEditing={disableEditing}
118
183
  id={id}
@@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
10
10
  import { useChatStore } from '@/store/chat';
11
11
  import { messageStateSelectors, threadSelectors } from '@/store/chat/selectors';
12
12
 
13
- import { VirtuosoContext } from '../../../components/VirtualizedList/VirtuosoContext';
13
+ import { VirtuaContext } from '../../../components/VirtualizedList/VirtuosoContext';
14
14
  import { InPortalThreadContext } from '../../../context/InPortalThreadContext';
15
15
  import { useChatListActionsBar } from '../../../hooks/useChatListActionsBar';
16
16
 
@@ -76,7 +76,7 @@ export const UserActionsBar = memo<UserActionsProps>(({ id, data, index }) => {
76
76
 
77
77
  // remove line breaks in artifact tag to make the ast transform easier
78
78
 
79
- const virtuosoRef = use(VirtuosoContext);
79
+ const virtuaRef = use(VirtuaContext);
80
80
 
81
81
  const onActionClick = useCallback(
82
82
  async (action: ActionIconGroupEvent) => {
@@ -84,7 +84,7 @@ export const UserActionsBar = memo<UserActionsProps>(({ id, data, index }) => {
84
84
  case 'edit': {
85
85
  toggleMessageEditing(id, true);
86
86
 
87
- virtuosoRef?.current?.scrollIntoView({ align: 'start', behavior: 'auto', index });
87
+ virtuaRef?.current?.scrollToIndex(index, { align: 'start' });
88
88
  }
89
89
  }
90
90
 
@@ -7,8 +7,8 @@ import { ReactNode, memo, useCallback, useEffect, useMemo, useRef } from 'react'
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
9
  import {
10
- removeVirtuosoVisibleItem,
11
- upsertVirtuosoVisibleItem,
10
+ removeVirtuaVisibleItem,
11
+ upsertVirtuaVisibleItem,
12
12
  } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
13
13
  import { getChatStoreState, useChatStore } from '@/store/chat';
14
14
  import { displayMessageSelectors, messageStateSelectors } from '@/store/chat/selectors';
@@ -42,6 +42,7 @@ export interface ChatListItemProps {
42
42
  id: string;
43
43
  inPortalThread?: boolean;
44
44
  index: number;
45
+ isLatestItem?: boolean;
45
46
  }
46
47
 
47
48
  const Item = memo<ChatListItemProps>(
@@ -53,6 +54,7 @@ const Item = memo<ChatListItemProps>(
53
54
  disableEditing,
54
55
  inPortalThread = false,
55
56
  index,
57
+ isLatestItem,
56
58
  }) => {
57
59
  const { styles, cx } = useStyles();
58
60
  const containerRef = useRef<HTMLDivElement | null>(null);
@@ -86,13 +88,13 @@ const Item = memo<ChatListItemProps>(
86
88
  if (entry.isIntersecting) {
87
89
  const { bottom, top } = entry.intersectionRect;
88
90
 
89
- upsertVirtuosoVisibleItem(index, {
91
+ upsertVirtuaVisibleItem(index, {
90
92
  bottom,
91
93
  ratio: entry.intersectionRatio,
92
94
  top,
93
95
  });
94
96
  } else {
95
- removeVirtuosoVisibleItem(index);
97
+ removeVirtuaVisibleItem(index);
96
98
  }
97
99
  });
98
100
  }, options);
@@ -101,7 +103,7 @@ const Item = memo<ChatListItemProps>(
101
103
 
102
104
  return () => {
103
105
  observer.disconnect();
104
- removeVirtuosoVisibleItem(index);
106
+ removeVirtuaVisibleItem(index);
105
107
  };
106
108
  }, [index]);
107
109
 
@@ -127,11 +129,25 @@ const Item = memo<ChatListItemProps>(
127
129
  }
128
130
 
129
131
  case 'assistant': {
130
- return <AssistantMessage disableEditing={disableEditing} id={id} index={index} />;
132
+ return (
133
+ <AssistantMessage
134
+ disableEditing={disableEditing}
135
+ id={id}
136
+ index={index}
137
+ isLatestItem={isLatestItem}
138
+ />
139
+ );
131
140
  }
132
141
 
133
142
  case 'assistantGroup': {
134
- return <GroupMessage disableEditing={disableEditing} id={id} index={index} />;
143
+ return (
144
+ <GroupMessage
145
+ disableEditing={disableEditing}
146
+ id={id}
147
+ index={index}
148
+ isLatestItem={isLatestItem}
149
+ />
150
+ );
135
151
  }
136
152
 
137
153
  case 'tool': {
@@ -144,7 +160,7 @@ const Item = memo<ChatListItemProps>(
144
160
  }
145
161
 
146
162
  return null;
147
- }, [role, disableEditing, id, index]);
163
+ }, [role, disableEditing, id, index, isLatestItem]);
148
164
 
149
165
  if (!role) return;
150
166
 
@@ -1,11 +1,11 @@
1
1
  import { RefObject, createContext } from 'react';
2
- import { VirtuosoHandle } from 'react-virtuoso';
2
+ import { VListHandle } from 'virtua';
3
3
 
4
- export const VirtuosoContext = createContext<RefObject<VirtuosoHandle | null> | null>(null);
4
+ export const VirtuaContext = createContext<RefObject<VListHandle | null> | null>(null);
5
5
 
6
- type VirtuosoRef = RefObject<VirtuosoHandle | null> | null;
6
+ type VirtuaRef = RefObject<VListHandle | null> | null;
7
7
 
8
- let currentVirtuosoRef: VirtuosoRef = null;
8
+ let currentVirtuaRef: VirtuaRef = null;
9
9
  const refListeners = new Set<() => void>();
10
10
 
11
11
  const visibleItems = new Map<number, { bottom: number; ratio: number; top: number }>();
@@ -45,14 +45,14 @@ const recalculateActiveIndex = () => {
45
45
  notifyActiveIndex(candidate ?? null);
46
46
  };
47
47
 
48
- export const setVirtuosoGlobalRef = (ref: VirtuosoRef) => {
49
- currentVirtuosoRef = ref;
48
+ export const setVirtuaGlobalRef = (ref: VirtuaRef) => {
49
+ currentVirtuaRef = ref;
50
50
  refListeners.forEach((listener) => listener());
51
51
  };
52
52
 
53
- export const getVirtuosoGlobalRef = () => currentVirtuosoRef;
53
+ export const getVirtuaGlobalRef = () => currentVirtuaRef;
54
54
 
55
- export const subscribeVirtuosoGlobalRef = (listener: () => void) => {
55
+ export const subscribeVirtuaGlobalRef = (listener: () => void) => {
56
56
  refListeners.add(listener);
57
57
 
58
58
  return () => {
@@ -60,7 +60,7 @@ export const subscribeVirtuosoGlobalRef = (listener: () => void) => {
60
60
  };
61
61
  };
62
62
 
63
- export const upsertVirtuosoVisibleItem = (
63
+ export const upsertVirtuaVisibleItem = (
64
64
  index: number,
65
65
  metrics: { bottom: number; ratio: number; top: number },
66
66
  ) => {
@@ -68,22 +68,22 @@ export const upsertVirtuosoVisibleItem = (
68
68
  recalculateActiveIndex();
69
69
  };
70
70
 
71
- export const removeVirtuosoVisibleItem = (index: number) => {
71
+ export const removeVirtuaVisibleItem = (index: number) => {
72
72
  if (!visibleItems.delete(index)) return;
73
73
 
74
74
  recalculateActiveIndex();
75
75
  };
76
76
 
77
- export const resetVirtuosoVisibleItems = () => {
77
+ export const resetVirtuaVisibleItems = () => {
78
78
  if (visibleItems.size === 0 && currentActiveIndex === null) return;
79
79
 
80
80
  visibleItems.clear();
81
81
  notifyActiveIndex(null);
82
82
  };
83
83
 
84
- export const getVirtuosoActiveIndex = () => currentActiveIndex;
84
+ export const getVirtuaActiveIndex = () => currentActiveIndex;
85
85
 
86
- export const subscribeVirtuosoActiveIndex = (listener: () => void) => {
86
+ export const subscribeVirtuaActiveIndex = (listener: () => void) => {
87
87
  activeIndexListeners.add(listener);
88
88
 
89
89
  return () => {