@lobehub/chat 0.119.3 → 0.119.5
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/CHANGELOG.md +70 -0
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/locales/de-DE/chat.json +4 -0
- package/locales/en-US/chat.json +4 -0
- package/locales/es-ES/chat.json +4 -0
- package/locales/fr-FR/chat.json +4 -0
- package/locales/it-IT/chat.json +4 -0
- package/locales/ja-JP/chat.json +4 -0
- package/locales/ko-KR/chat.json +4 -0
- package/locales/nl-NL/chat.json +4 -0
- package/locales/pl-PL/chat.json +4 -0
- package/locales/pt-BR/chat.json +4 -0
- package/locales/ru-RU/chat.json +4 -0
- package/locales/tr-TR/chat.json +4 -0
- package/locales/vi-VN/chat.json +4 -0
- package/locales/zh-CN/chat.json +5 -1
- package/locales/zh-TW/chat.json +4 -0
- package/package.json +2 -1
- package/src/app/chat/features/TopicListContent/Topic/SkeletonList.tsx +46 -12
- package/src/app/chat/features/TopicListContent/Topic/index.tsx +67 -33
- package/src/app/chat/features/TopicListContent/index.tsx +2 -1
- package/src/app/settings/llm/LLM/index.tsx +7 -1
- package/src/features/Conversation/components/AutoScroll.tsx +25 -0
- package/src/features/Conversation/components/BackBottom/index.tsx +31 -0
- package/src/features/Conversation/components/BackBottom/style.ts +36 -0
- package/src/features/Conversation/components/ChatItem/index.tsx +63 -42
- package/src/features/Conversation/components/ChatList/index.tsx +6 -29
- package/src/features/Conversation/components/SkeletonList.tsx +5 -2
- package/src/features/Conversation/components/VirtualizedList/index.tsx +78 -0
- package/src/features/Conversation/index.tsx +8 -18
- package/src/locales/create.ts +1 -1
- package/src/locales/default/chat.ts +5 -1
- package/src/locales/default/index.ts +0 -2
- package/src/store/chat/slices/message/selectors.ts +6 -0
- package/vercel.json +1 -1
- package/locales/de-DE/empty.json +0 -6
- package/locales/en-US/empty.json +0 -6
- package/locales/es-ES/empty.json +0 -6
- package/locales/fr-FR/empty.json +0 -6
- package/locales/it-IT/empty.json +0 -6
- package/locales/ja-JP/empty.json +0 -6
- package/locales/ko-KR/empty.json +0 -6
- package/locales/nl-NL/empty.json +0 -6
- package/locales/pl-PL/empty.json +0 -6
- package/locales/pt-BR/empty.json +0 -6
- package/locales/ru-RU/empty.json +0 -6
- package/locales/tr-TR/empty.json +0 -6
- package/locales/vi-VN/empty.json +0 -6
- package/locales/zh-CN/empty.json +0 -6
- package/locales/zh-TW/empty.json +0 -6
- package/src/features/Conversation/components/ChatList/style.ts +0 -10
- package/src/features/Conversation/components/ScrollAnchor/index.tsx +0 -29
- package/src/features/Conversation/components/ScrollAnchor/useAtBottom.ts +0 -20
- package/src/locales/default/empty.ts +0 -6
- /package/src/features/Conversation/components/{ChatList → ChatItem}/HistoryDivider.tsx +0 -0
|
@@ -13,12 +13,14 @@ import { renderErrorMessages } from '../../Error';
|
|
|
13
13
|
import { renderMessagesExtra } from '../../Extras';
|
|
14
14
|
import { renderMessages, useAvatarsClick } from '../../Messages';
|
|
15
15
|
import ActionsBar from './ActionsBar';
|
|
16
|
+
import HistoryDivider from './HistoryDivider';
|
|
16
17
|
|
|
17
18
|
export interface ChatListItemProps {
|
|
19
|
+
id: string;
|
|
18
20
|
index: number;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
const Item = memo<ChatListItemProps>(({ index }) => {
|
|
23
|
+
const Item = memo<ChatListItemProps>(({ index, id }) => {
|
|
22
24
|
const { t } = useTranslation('common');
|
|
23
25
|
|
|
24
26
|
const [editing, setEditing] = useState(false);
|
|
@@ -28,13 +30,18 @@ const Item = memo<ChatListItemProps>(({ index }) => {
|
|
|
28
30
|
});
|
|
29
31
|
|
|
30
32
|
const meta = useSessionStore(agentSelectors.currentAgentMeta, isEqual);
|
|
31
|
-
const item = useChatStore(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const item = useChatStore((s) => {
|
|
34
|
+
const chats = chatSelectors.currentChatsWithGuideMessage(meta)(s);
|
|
35
|
+
|
|
36
|
+
if (index >= chats.length) return;
|
|
37
|
+
|
|
38
|
+
return chatSelectors.currentChatsWithGuideMessage(meta)(s)[index];
|
|
39
|
+
}, isEqual);
|
|
40
|
+
|
|
41
|
+
const historyLength = useChatStore((s) => chatSelectors.currentChats(s).length);
|
|
35
42
|
|
|
36
43
|
const [loading, onMessageChange] = useChatStore((s) => [
|
|
37
|
-
s.chatLoadingId ===
|
|
44
|
+
s.chatLoadingId === id,
|
|
38
45
|
s.updateMessageContent,
|
|
39
46
|
]);
|
|
40
47
|
|
|
@@ -49,7 +56,7 @@ const Item = memo<ChatListItemProps>(({ index }) => {
|
|
|
49
56
|
|
|
50
57
|
return <RenderFunction {...data} editableContent={editableContent} />;
|
|
51
58
|
},
|
|
52
|
-
[item
|
|
59
|
+
[item?.role],
|
|
53
60
|
);
|
|
54
61
|
|
|
55
62
|
const MessageExtra = useCallback(
|
|
@@ -61,7 +68,7 @@ const Item = memo<ChatListItemProps>(({ index }) => {
|
|
|
61
68
|
if (!RenderFunction) return;
|
|
62
69
|
return <RenderFunction {...data} />;
|
|
63
70
|
},
|
|
64
|
-
[item
|
|
71
|
+
[item?.role],
|
|
65
72
|
);
|
|
66
73
|
|
|
67
74
|
const ErrorMessage = useCallback(
|
|
@@ -75,51 +82,65 @@ const Item = memo<ChatListItemProps>(({ index }) => {
|
|
|
75
82
|
if (!RenderFunction) return;
|
|
76
83
|
return <RenderFunction {...data} />;
|
|
77
84
|
},
|
|
78
|
-
[item
|
|
85
|
+
[item?.error],
|
|
79
86
|
);
|
|
80
87
|
|
|
81
88
|
const error = useMemo(() => {
|
|
82
|
-
if (!item
|
|
89
|
+
if (!item?.error) return;
|
|
83
90
|
const message = item.error?.message;
|
|
84
91
|
let alertConfig = {};
|
|
85
92
|
if (item.error.type && renderErrorMessages?.[item.error.type]) {
|
|
86
93
|
alertConfig = renderErrorMessages[item.error.type]?.config as AlertProps;
|
|
87
94
|
}
|
|
88
95
|
return { message, ...alertConfig };
|
|
89
|
-
}, [item
|
|
96
|
+
}, [item?.error]);
|
|
97
|
+
|
|
98
|
+
const enableHistoryDivider = useSessionStore((s) => {
|
|
99
|
+
const config = agentSelectors.currentAgentConfig(s);
|
|
100
|
+
return (
|
|
101
|
+
config.enableHistoryCount &&
|
|
102
|
+
historyLength > (config.historyCount ?? 0) &&
|
|
103
|
+
config.historyCount === historyLength - index + 1
|
|
104
|
+
);
|
|
105
|
+
});
|
|
90
106
|
|
|
91
107
|
return (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
108
|
+
item && (
|
|
109
|
+
<>
|
|
110
|
+
<HistoryDivider enable={enableHistoryDivider} />
|
|
111
|
+
<ChatItem
|
|
112
|
+
actions={<ActionsBar index={index} setEditing={setEditing} />}
|
|
113
|
+
avatar={item.meta}
|
|
114
|
+
editing={editing}
|
|
115
|
+
error={error}
|
|
116
|
+
errorMessage={<ErrorMessage data={item} />}
|
|
117
|
+
loading={loading}
|
|
118
|
+
message={item.content}
|
|
119
|
+
messageExtra={<MessageExtra data={item} />}
|
|
120
|
+
onAvatarClick={onAvatarsClick?.(item.role)}
|
|
121
|
+
onChange={(value) => onMessageChange(item.id, value)}
|
|
122
|
+
onDoubleClick={(e) => {
|
|
123
|
+
if (item.id === 'default' || item.error) return;
|
|
124
|
+
if (item.role && ['assistant', 'user'].includes(item.role) && e.altKey) {
|
|
125
|
+
setEditing(true);
|
|
126
|
+
}
|
|
127
|
+
}}
|
|
128
|
+
onEditingChange={setEditing}
|
|
129
|
+
placement={type === 'chat' ? (item.role === 'user' ? 'right' : 'left') : 'left'}
|
|
130
|
+
primary={item.role === 'user'}
|
|
131
|
+
renderMessage={(editableContent) => (
|
|
132
|
+
<RenderMessage data={item} editableContent={editableContent} />
|
|
133
|
+
)}
|
|
134
|
+
text={{
|
|
135
|
+
cancel: t('cancel'),
|
|
136
|
+
confirm: t('ok'),
|
|
137
|
+
edit: t('edit'),
|
|
138
|
+
}}
|
|
139
|
+
time={item.updatedAt || item.createdAt}
|
|
140
|
+
type={type === 'chat' ? 'block' : 'pure'}
|
|
141
|
+
/>
|
|
142
|
+
</>
|
|
143
|
+
)
|
|
123
144
|
);
|
|
124
145
|
});
|
|
125
146
|
|
|
@@ -1,42 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Fragment, memo } from 'react';
|
|
1
|
+
import { memo } from 'react';
|
|
3
2
|
import { Flexbox } from 'react-layout-kit';
|
|
4
3
|
|
|
5
4
|
import { useChatStore } from '@/store/chat';
|
|
6
5
|
import { chatSelectors } from '@/store/chat/selectors';
|
|
7
|
-
import { useSessionStore } from '@/store/session';
|
|
8
|
-
import { agentSelectors } from '@/store/session/selectors';
|
|
9
6
|
|
|
10
7
|
import Item from '../ChatItem';
|
|
11
|
-
import HistoryDivider from './HistoryDivider';
|
|
12
|
-
import { useStyles } from './style';
|
|
13
8
|
|
|
14
9
|
const ChatList = memo(() => {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const data = useChatStore(chatSelectors.currentChatIDsWithGuideMessage, isEqual);
|
|
18
|
-
|
|
19
|
-
const [enableHistoryCount, historyCount = 0] = useSessionStore((s) => {
|
|
20
|
-
const config = agentSelectors.currentAgentConfig(s);
|
|
21
|
-
return [config.enableHistoryCount, config.historyCount];
|
|
22
|
-
});
|
|
10
|
+
const ids = useChatStore(chatSelectors.currentChatIDsWithGuideMessage);
|
|
23
11
|
|
|
24
12
|
return (
|
|
25
|
-
<Flexbox
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
enableHistoryCount &&
|
|
30
|
-
historyLength > historyCount &&
|
|
31
|
-
historyCount === historyLength - index + 1;
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<Fragment key={item}>
|
|
35
|
-
<HistoryDivider enable={enableHistoryDivider} />
|
|
36
|
-
<Item index={index} />
|
|
37
|
-
</Fragment>
|
|
38
|
-
);
|
|
39
|
-
})}
|
|
13
|
+
<Flexbox height={'100%'} style={{ position: 'relative' }}>
|
|
14
|
+
{ids.map((id, index) => (
|
|
15
|
+
<Item id={id} index={index} key={id} />
|
|
16
|
+
))}
|
|
40
17
|
</Flexbox>
|
|
41
18
|
);
|
|
42
19
|
});
|
|
@@ -16,11 +16,14 @@ const useStyles = createStyles(({ css, prefixCls }) => ({
|
|
|
16
16
|
}
|
|
17
17
|
`,
|
|
18
18
|
}));
|
|
19
|
-
|
|
19
|
+
interface SkeletonListProps {
|
|
20
|
+
mobile?: boolean;
|
|
21
|
+
}
|
|
22
|
+
const SkeletonList = memo<SkeletonListProps>(({ mobile }) => {
|
|
20
23
|
const { styles } = useStyles();
|
|
21
24
|
|
|
22
25
|
return (
|
|
23
|
-
<Flexbox gap={24} padding={12} style={{ marginTop: 24 }}>
|
|
26
|
+
<Flexbox gap={24} padding={12} style={{ marginTop: 24 + (mobile ? 0 : 64) }}>
|
|
24
27
|
<Skeleton
|
|
25
28
|
active
|
|
26
29
|
avatar={{ size: 40 }}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import isEqual from 'fast-deep-equal';
|
|
2
|
+
import React, { memo, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { Flexbox } from 'react-layout-kit';
|
|
4
|
+
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
|
|
5
|
+
|
|
6
|
+
import { useChatStore } from '@/store/chat';
|
|
7
|
+
import { chatSelectors } from '@/store/chat/selectors';
|
|
8
|
+
import { isMobileScreen } from '@/utils/screen';
|
|
9
|
+
|
|
10
|
+
import AutoScroll from '../AutoScroll';
|
|
11
|
+
import Item from '../ChatItem';
|
|
12
|
+
|
|
13
|
+
const itemContent = (index: number, id: string) => {
|
|
14
|
+
const isMobile = isMobileScreen();
|
|
15
|
+
|
|
16
|
+
return index === 0 ? (
|
|
17
|
+
<div style={{ height: isMobile ? 24 : 64 }} />
|
|
18
|
+
) : (
|
|
19
|
+
<Item id={id} index={index - 1} />
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
interface VirtualizedListProps {
|
|
24
|
+
mobile?: boolean;
|
|
25
|
+
}
|
|
26
|
+
const VirtualizedList = memo<VirtualizedListProps>(({ mobile }) => {
|
|
27
|
+
const virtuosoRef = useRef<VirtuosoHandle>(null);
|
|
28
|
+
const [atBottom, setAtBottom] = useState(true);
|
|
29
|
+
|
|
30
|
+
const data = useChatStore(
|
|
31
|
+
(s) => ['empty', ...chatSelectors.currentChatIDsWithGuideMessage(s)],
|
|
32
|
+
isEqual,
|
|
33
|
+
);
|
|
34
|
+
const [id, chatLoading] = useChatStore((s) => [
|
|
35
|
+
chatSelectors.currentChatKey(s),
|
|
36
|
+
chatSelectors.currentChatLoadingState(s),
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (virtuosoRef.current) {
|
|
41
|
+
virtuosoRef.current.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
|
|
42
|
+
}
|
|
43
|
+
}, [id]);
|
|
44
|
+
|
|
45
|
+
return chatLoading && data.length === 2 ? null : (
|
|
46
|
+
<Flexbox height={'100%'}>
|
|
47
|
+
<Virtuoso
|
|
48
|
+
atBottomStateChange={setAtBottom}
|
|
49
|
+
atBottomThreshold={60 * (mobile ? 2 : 1)}
|
|
50
|
+
computeItemKey={(_, item) => item}
|
|
51
|
+
data={data}
|
|
52
|
+
followOutput={'auto'}
|
|
53
|
+
initialTopMostItemIndex={data?.length - 1}
|
|
54
|
+
itemContent={itemContent}
|
|
55
|
+
overscan={70 * 10}
|
|
56
|
+
ref={virtuosoRef}
|
|
57
|
+
/>
|
|
58
|
+
<AutoScroll
|
|
59
|
+
atBottom={atBottom}
|
|
60
|
+
onScrollToBottom={(type) => {
|
|
61
|
+
const virtuoso = virtuosoRef.current;
|
|
62
|
+
switch (type) {
|
|
63
|
+
case 'auto': {
|
|
64
|
+
virtuoso?.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case 'click': {
|
|
68
|
+
virtuoso?.scrollToIndex({ align: 'end', behavior: 'smooth', index: 'LAST' });
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
</Flexbox>
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export default VirtualizedList;
|
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
import { BackBottom } from '@lobehub/ui';
|
|
2
1
|
import { createStyles } from 'antd-style';
|
|
3
|
-
import { ReactNode, memo
|
|
4
|
-
import { useTranslation } from 'react-i18next';
|
|
2
|
+
import { ReactNode, memo } from 'react';
|
|
5
3
|
import { Flexbox } from 'react-layout-kit';
|
|
6
4
|
|
|
7
|
-
import SafeSpacing from '@/components/SafeSpacing';
|
|
8
5
|
import { useGlobalStore } from '@/store/global';
|
|
9
6
|
import { settingsSelectors } from '@/store/global/selectors';
|
|
10
7
|
|
|
11
|
-
import ChatList from './components/ChatList';
|
|
12
|
-
import ChatScrollAnchor from './components/ScrollAnchor';
|
|
13
8
|
import SkeletonList from './components/SkeletonList';
|
|
9
|
+
import ChatList from './components/VirtualizedList';
|
|
14
10
|
import { useInitConversation } from './hooks/useInitConversation';
|
|
15
11
|
|
|
16
12
|
const useStyles = createStyles(({ css, responsive, stylish, cx }, fontSize: number = 14) =>
|
|
17
13
|
cx(
|
|
18
14
|
css`
|
|
19
|
-
|
|
15
|
+
position: relative;
|
|
16
|
+
overflow-y: auto;
|
|
20
17
|
height: 100%;
|
|
21
18
|
|
|
22
19
|
${responsive.mobile} {
|
|
@@ -46,9 +43,7 @@ interface ConversationProps {
|
|
|
46
43
|
mobile?: boolean;
|
|
47
44
|
}
|
|
48
45
|
|
|
49
|
-
const Conversation = memo<ConversationProps>(({
|
|
50
|
-
const ref = useRef(null);
|
|
51
|
-
const { t } = useTranslation('chat');
|
|
46
|
+
const Conversation = memo<ConversationProps>(({ chatInput, mobile }) => {
|
|
52
47
|
const fontSize = useGlobalStore((s) => settingsSelectors.currentSettings(s).fontSize);
|
|
53
48
|
const { styles } = useStyles(fontSize);
|
|
54
49
|
|
|
@@ -56,14 +51,9 @@ const Conversation = memo<ConversationProps>(({ mobile, chatInput }) => {
|
|
|
56
51
|
const init = useInitConversation();
|
|
57
52
|
|
|
58
53
|
return (
|
|
59
|
-
<Flexbox
|
|
60
|
-
<div
|
|
61
|
-
<
|
|
62
|
-
{!mobile && <SafeSpacing />}
|
|
63
|
-
{init ? <ChatList /> : <SkeletonList />}
|
|
64
|
-
<ChatScrollAnchor />
|
|
65
|
-
</div>
|
|
66
|
-
<BackBottom target={ref} text={t('backToBottom')} />
|
|
54
|
+
<Flexbox data-id={'conversation'} flex={1}>
|
|
55
|
+
<div className={styles}>
|
|
56
|
+
{init ? <ChatList mobile={mobile} /> : <SkeletonList mobile={mobile} />}
|
|
67
57
|
</div>
|
|
68
58
|
{chatInput}
|
|
69
59
|
</Flexbox>
|
package/src/locales/create.ts
CHANGED
|
@@ -28,7 +28,7 @@ export const createI18nNext = (lang?: string) => {
|
|
|
28
28
|
init: () =>
|
|
29
29
|
instance.init({
|
|
30
30
|
debug: debugMode,
|
|
31
|
-
defaultNS: ['error', 'common', '
|
|
31
|
+
defaultNS: ['error', 'common', 'chat'],
|
|
32
32
|
detection: {
|
|
33
33
|
caches: ['cookie'],
|
|
34
34
|
cookieMinutes: 60 * 24 * COOKIE_CACHE_DAYS,
|
|
@@ -65,12 +65,16 @@ export default {
|
|
|
65
65
|
confirmRemoveTopic: '即将删除该话题,删除后将不可恢复,请谨慎操作。',
|
|
66
66
|
confirmRemoveUnstarred: '即将删除未收藏话题,删除后将不可恢复,请谨慎操作。',
|
|
67
67
|
defaultTitle: '默认话题',
|
|
68
|
+
guide: {
|
|
69
|
+
desc: '点击发送左侧按钮可将当前会话保存为历史话题,并开启新一轮会话',
|
|
70
|
+
title: '话题列表',
|
|
71
|
+
},
|
|
68
72
|
openNewTopic: '开启新话题',
|
|
69
73
|
removeAll: '删除全部话题',
|
|
70
74
|
removeUnstarred: '删除未收藏话题',
|
|
71
75
|
saveCurrentMessages: '将当前会话保存为话题',
|
|
72
76
|
searchPlaceholder: '搜索话题...',
|
|
73
|
-
title: '
|
|
77
|
+
title: '话题',
|
|
74
78
|
},
|
|
75
79
|
translate: {
|
|
76
80
|
action: '翻译',
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import tool from '../default/tool';
|
|
2
2
|
import chat from './chat';
|
|
3
3
|
import common from './common';
|
|
4
|
-
import empty from './empty';
|
|
5
4
|
import error from './error';
|
|
6
5
|
import market from './market';
|
|
7
6
|
import migration from './migration';
|
|
@@ -12,7 +11,6 @@ import welcome from './welcome';
|
|
|
12
11
|
const resources = {
|
|
13
12
|
chat,
|
|
14
13
|
common,
|
|
15
|
-
empty,
|
|
16
14
|
error,
|
|
17
15
|
market,
|
|
18
16
|
migration,
|
|
@@ -39,6 +39,8 @@ const getMeta = (message: ChatMessage) => {
|
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
const currentChatKey = (s: ChatStore) => `${s.activeId}_${s.activeTopicId}`;
|
|
43
|
+
|
|
42
44
|
// 当前激活的消息列表
|
|
43
45
|
const currentChats = (s: ChatStore): ChatMessage[] => {
|
|
44
46
|
if (!s.activeId) return [];
|
|
@@ -117,9 +119,13 @@ const getMessageById = (id: string) => (s: ChatStore) => chatHelpers.getMessageB
|
|
|
117
119
|
|
|
118
120
|
const latestMessage = (s: ChatStore) => currentChats(s).at(-1);
|
|
119
121
|
|
|
122
|
+
const currentChatLoadingState = (s: ChatStore) => !s.messagesInit;
|
|
123
|
+
|
|
120
124
|
export const chatSelectors = {
|
|
121
125
|
chatsMessageString,
|
|
122
126
|
currentChatIDsWithGuideMessage,
|
|
127
|
+
currentChatKey,
|
|
128
|
+
currentChatLoadingState,
|
|
123
129
|
currentChats,
|
|
124
130
|
currentChatsWithGuideMessage,
|
|
125
131
|
currentChatsWithHistoryConfig,
|
package/vercel.json
CHANGED
package/locales/de-DE/empty.json
DELETED
package/locales/en-US/empty.json
DELETED
package/locales/es-ES/empty.json
DELETED
package/locales/fr-FR/empty.json
DELETED
package/locales/it-IT/empty.json
DELETED
package/locales/ja-JP/empty.json
DELETED
package/locales/ko-KR/empty.json
DELETED
package/locales/nl-NL/empty.json
DELETED
package/locales/pl-PL/empty.json
DELETED
package/locales/pt-BR/empty.json
DELETED
package/locales/ru-RU/empty.json
DELETED
package/locales/tr-TR/empty.json
DELETED
package/locales/vi-VN/empty.json
DELETED
package/locales/zh-CN/empty.json
DELETED
package/locales/zh-TW/empty.json
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { memo, useEffect } from 'react';
|
|
2
|
-
import { useInView } from 'react-intersection-observer';
|
|
3
|
-
|
|
4
|
-
import { useChatStore } from '@/store/chat';
|
|
5
|
-
import { chatSelectors } from '@/store/chat/selectors';
|
|
6
|
-
|
|
7
|
-
import { useAtBottom } from './useAtBottom';
|
|
8
|
-
|
|
9
|
-
const ChatScrollAnchor = memo(() => {
|
|
10
|
-
const trackVisibility = useChatStore((s) => !!s.chatLoadingId);
|
|
11
|
-
const str = useChatStore(chatSelectors.chatsMessageString);
|
|
12
|
-
|
|
13
|
-
const isAtBottom = useAtBottom();
|
|
14
|
-
const { ref, entry, inView } = useInView({
|
|
15
|
-
delay: 100,
|
|
16
|
-
rootMargin: '0px 0px -150px 0px',
|
|
17
|
-
trackVisibility,
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
if (isAtBottom && trackVisibility && !inView) {
|
|
22
|
-
entry?.target.scrollIntoView({ block: 'start' });
|
|
23
|
-
}
|
|
24
|
-
}, [inView, entry, isAtBottom, trackVisibility, str]);
|
|
25
|
-
|
|
26
|
-
return <div ref={ref} style={{ height: 1, width: '100%' }} />;
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
export default ChatScrollAnchor;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
export const useAtBottom = (offset = 0) => {
|
|
4
|
-
const [isAtBottom, setIsAtBottom] = useState(false);
|
|
5
|
-
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
const handleScroll = () => {
|
|
8
|
-
setIsAtBottom(window.innerHeight + window.scrollY >= document.body.offsetHeight - offset);
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
12
|
-
handleScroll();
|
|
13
|
-
|
|
14
|
-
return () => {
|
|
15
|
-
window.removeEventListener('scroll', handleScroll);
|
|
16
|
-
};
|
|
17
|
-
}, [offset]);
|
|
18
|
-
|
|
19
|
-
return isAtBottom;
|
|
20
|
-
};
|
|
File without changes
|