@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.
- package/.github/workflows/claude-translator.yml +1 -0
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/chat.json +3 -0
- package/locales/bg-BG/chat.json +3 -0
- package/locales/de-DE/chat.json +3 -0
- package/locales/en-US/chat.json +3 -0
- package/locales/es-ES/chat.json +3 -0
- package/locales/fa-IR/chat.json +3 -0
- package/locales/fr-FR/chat.json +3 -0
- package/locales/it-IT/chat.json +3 -0
- package/locales/ja-JP/chat.json +3 -0
- package/locales/ko-KR/chat.json +3 -0
- package/locales/nl-NL/chat.json +3 -0
- package/locales/pl-PL/chat.json +3 -0
- package/locales/pt-BR/chat.json +3 -0
- package/locales/ru-RU/chat.json +3 -0
- package/locales/tr-TR/chat.json +3 -0
- package/locales/vi-VN/chat.json +3 -0
- package/locales/zh-CN/chat.json +3 -0
- package/locales/zh-TW/chat.json +3 -0
- package/package.json +7 -6
- package/packages/conversation-flow/src/__tests__/fixtures/index.ts +4 -8
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/{assistant-with-tools.json → assistantGroup/assistant-with-tools.json} +2 -1
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistantGroup/index.ts +8 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/index.ts +2 -4
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/{assistant-with-tools.json → assistantGroup/assistant-with-tools.json} +8 -8
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistantGroup/index.ts +8 -0
- package/packages/conversation-flow/src/__tests__/parse.test.ts +6 -6
- package/packages/conversation-flow/src/parse.ts +45 -1
- package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +64 -0
- package/packages/database/package.json +2 -2
- package/packages/obervability-otel/package.json +1 -1
- package/packages/types/src/message/common/metadata.ts +8 -1
- package/packages/types/src/message/ui/chat.ts +1 -0
- package/src/app/(backend)/market/agent/[[...segments]]/route.ts +1 -1
- package/src/app/(backend)/market/oidc/[[...segments]]/route.ts +1 -1
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/ChatItem/index.tsx +1 -0
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatMinimap/index.tsx +21 -28
- package/src/app/market-auth-callback/layout.tsx +27 -3
- package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +2 -2
- package/src/features/ChatItem/style.ts +4 -0
- package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +18 -4
- package/src/features/Conversation/Messages/Assistant/CollapsedMessage.tsx +37 -0
- package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +16 -9
- package/src/features/Conversation/Messages/Assistant/index.tsx +329 -230
- package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +31 -9
- package/src/features/Conversation/Messages/Group/CollapsedMessage.tsx +37 -0
- package/src/features/Conversation/Messages/Group/{GroupChildren.tsx → Group.tsx} +18 -4
- package/src/features/Conversation/Messages/Group/GroupItem.tsx +3 -5
- package/src/features/Conversation/Messages/Group/index.tsx +84 -19
- package/src/features/Conversation/Messages/User/Actions/ActionsBar.tsx +3 -3
- package/src/features/Conversation/Messages/index.tsx +24 -8
- package/src/features/Conversation/components/VirtualizedList/VirtuosoContext.ts +13 -13
- package/src/features/Conversation/components/VirtualizedList/index.tsx +92 -58
- package/src/features/Conversation/components/WideScreenContainer/index.tsx +10 -6
- package/src/features/Conversation/hooks/useChatListActionsBar.tsx +14 -0
- package/src/features/Conversation/hooks/useDoubleClickEdit.ts +3 -3
- package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +1 -1
- package/src/libs/mcp/__tests__/index.test.ts +6 -6
- package/src/locales/default/chat.ts +3 -0
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +9 -1
- package/src/store/chat/slices/message/actions/publicApi.ts +17 -0
- package/src/store/chat/slices/message/selectors/displayMessage.ts +1 -1
- package/src/store/chat/slices/message/selectors/messageState.ts +7 -0
- package/src/store/chat/slices/translate/action.test.ts +26 -32
- package/src/store/chat/slices/translate/action.ts +3 -3
- /package/packages/conversation-flow/src/__tests__/fixtures/inputs/{complex-scenario.json → assistantGroup/tools-with-branches.json} +0 -0
- /package/packages/conversation-flow/src/__tests__/fixtures/outputs/{complex-scenario.json → assistantGroup/tools-with-branches.json} +0 -0
- /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 {
|
|
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 {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
<
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
91
|
+
upsertVirtuaVisibleItem(index, {
|
|
90
92
|
bottom,
|
|
91
93
|
ratio: entry.intersectionRatio,
|
|
92
94
|
top,
|
|
93
95
|
});
|
|
94
96
|
} else {
|
|
95
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
2
|
+
import { VListHandle } from 'virtua';
|
|
3
3
|
|
|
4
|
-
export const
|
|
4
|
+
export const VirtuaContext = createContext<RefObject<VListHandle | null> | null>(null);
|
|
5
5
|
|
|
6
|
-
type
|
|
6
|
+
type VirtuaRef = RefObject<VListHandle | null> | null;
|
|
7
7
|
|
|
8
|
-
let
|
|
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
|
|
49
|
-
|
|
48
|
+
export const setVirtuaGlobalRef = (ref: VirtuaRef) => {
|
|
49
|
+
currentVirtuaRef = ref;
|
|
50
50
|
refListeners.forEach((listener) => listener());
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
export const
|
|
53
|
+
export const getVirtuaGlobalRef = () => currentVirtuaRef;
|
|
54
54
|
|
|
55
|
-
export const
|
|
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
|
|
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
|
|
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
|
|
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
|
|
84
|
+
export const getVirtuaActiveIndex = () => currentActiveIndex;
|
|
85
85
|
|
|
86
|
-
export const
|
|
86
|
+
export const subscribeVirtuaActiveIndex = (listener: () => void) => {
|
|
87
87
|
activeIndexListeners.add(listener);
|
|
88
88
|
|
|
89
89
|
return () => {
|