@lobehub/chat 0.119.2 → 0.119.4
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/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/Error/InvalidAccess.tsx +2 -2
- 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/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
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,76 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 0.119.4](https://github.com/lobehub/lobe-chat/compare/v0.119.3...v0.119.4)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2024-01-06**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: Refactor to virtual list with react-virtuoso.
|
|
12
|
+
|
|
13
|
+
#### 🐛 Bug Fixes
|
|
14
|
+
|
|
15
|
+
- **misc**: Fix auto scroll error and BackBottom error.
|
|
16
|
+
|
|
17
|
+
#### 💄 Styles
|
|
18
|
+
|
|
19
|
+
- **misc**: Fix BackBottom zIndex, improve chat list on mobile, improve chat list scrolling to bottom at initial render, improve custom model input, improve topic scroll.
|
|
20
|
+
|
|
21
|
+
<br/>
|
|
22
|
+
|
|
23
|
+
<details>
|
|
24
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
25
|
+
|
|
26
|
+
#### Code refactoring
|
|
27
|
+
|
|
28
|
+
- **misc**: Refactor to virtual list with react-virtuoso ([d6d63b2](https://github.com/lobehub/lobe-chat/commit/d6d63b2))
|
|
29
|
+
|
|
30
|
+
#### What's fixed
|
|
31
|
+
|
|
32
|
+
- **misc**: Fix auto scroll error and BackBottom error ([6100970](https://github.com/lobehub/lobe-chat/commit/6100970))
|
|
33
|
+
|
|
34
|
+
#### Styles
|
|
35
|
+
|
|
36
|
+
- **misc**: Fix BackBottom zIndex ([254cc54](https://github.com/lobehub/lobe-chat/commit/254cc54))
|
|
37
|
+
- **misc**: Improve chat list on mobile ([a894fc5](https://github.com/lobehub/lobe-chat/commit/a894fc5))
|
|
38
|
+
- **misc**: Improve chat list scrolling to bottom at initial render ([476304b](https://github.com/lobehub/lobe-chat/commit/476304b))
|
|
39
|
+
- **misc**: Improve custom model input ([1c4722f](https://github.com/lobehub/lobe-chat/commit/1c4722f))
|
|
40
|
+
- **misc**: Improve topic scroll ([8daf3ac](https://github.com/lobehub/lobe-chat/commit/8daf3ac))
|
|
41
|
+
|
|
42
|
+
</details>
|
|
43
|
+
|
|
44
|
+
<div align="right">
|
|
45
|
+
|
|
46
|
+
[](#readme-top)
|
|
47
|
+
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
### [Version 0.119.3](https://github.com/lobehub/lobe-chat/compare/v0.119.2...v0.119.3)
|
|
51
|
+
|
|
52
|
+
<sup>Released on **2024-01-06**</sup>
|
|
53
|
+
|
|
54
|
+
#### 🐛 Bug Fixes
|
|
55
|
+
|
|
56
|
+
- **misc**: Fix deploy error. Changed SquareAsterisk to AsteriskSquare.
|
|
57
|
+
|
|
58
|
+
<br/>
|
|
59
|
+
|
|
60
|
+
<details>
|
|
61
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
62
|
+
|
|
63
|
+
#### What's fixed
|
|
64
|
+
|
|
65
|
+
- **misc**: Fix deploy error. Changed SquareAsterisk to AsteriskSquare, closes [#952](https://github.com/lobehub/lobe-chat/issues/952) ([61cbcf1](https://github.com/lobehub/lobe-chat/commit/61cbcf1))
|
|
66
|
+
|
|
67
|
+
</details>
|
|
68
|
+
|
|
69
|
+
<div align="right">
|
|
70
|
+
|
|
71
|
+
[](#readme-top)
|
|
72
|
+
|
|
73
|
+
</div>
|
|
74
|
+
|
|
5
75
|
### [Version 0.119.2](https://github.com/lobehub/lobe-chat/compare/v0.119.1...v0.119.2)
|
|
6
76
|
|
|
7
77
|
<sup>Released on **2024-01-05**</sup>
|
package/README.md
CHANGED
|
@@ -210,12 +210,12 @@ Our marketplace is not just a showcase platform but also a collaborative space.
|
|
|
210
210
|
|
|
211
211
|
| Recent Submits | Description |
|
|
212
212
|
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
213
|
+
| [Performance Evaluation Superhero](https://chat-preview.lobehub.com/market?agent=kpi-hero)<br/><sup>By **[canisminor1990](https://github.com/canisminor1990)** on **2024-01-05**</sup> | Specializes in writing performance evaluation reports and year-end summaries<br/>`performance-evaluation` `report-writing` `data-analysis` `professional-insights` `okr` `kpi` |
|
|
213
214
|
| [SVG Flowchart Explanation Assistant](https://chat-preview.lobehub.com/market?agent=svg-flowchart-explanation-assistant)<br/><sup>By **[Justin3go](https://github.com/Justin3go)** on **2024-01-05**</sup> | Explain SVG flowcharts by inputting SVG source code and interpreting the flowchart.<br/>`flowchart-explanation` `technical-documentation-writing` `business-knowledge` |
|
|
215
|
+
| [Weekly Report Assistant](https://chat-preview.lobehub.com/market?agent=write-report-assistant-development)<br/><sup>By **[CaoYunzhou](https://github.com/CaoYunzhou)** on **2024-01-05**</sup> | Weekly report generation assistant<br/>`weekly-report` `daily-report` `writing` `summary` |
|
|
214
216
|
| [3D Animation Engineer](https://chat-preview.lobehub.com/market?agent=react-three-3-d-expert)<br/><sup>By **[arvinxx](https://github.com/arvinxx)** on **2024-01-03**</sup> | Proficient in React, Three.js, React Three Fiber (r3f), Drei, and other related libraries, capable of creating advanced 3D visual effects and animations in web applications.<br/>`3-d-animation` `react` `three-js` `web-design` `animation` |
|
|
215
|
-
| [Amazon Title Assistant](https://chat-preview.lobehub.com/market?agent=amazon)<br/><sup>By **[cm2457618290](https://github.com/cm2457618290)** on **2024-01-02**</sup> | Provide product keywords or product links to automatically write titles and product introductions<br/>`assistant` |
|
|
216
|
-
| [Exam Assistant](https://chat-preview.lobehub.com/market?agent=generador-examenes)<br/><sup>By **[aitorroma](https://github.com/aitorroma)** on **2024-01-02**</sup> | I am a skills summary assistant and cannot conduct interactive exams. However, I can help you summarize your skills and knowledge in a clear and concise format.<br/>`exam` `learning` `statistics` |
|
|
217
217
|
|
|
218
|
-
> 📊 Total agents: [<kbd>**
|
|
218
|
+
> 📊 Total agents: [<kbd>**91**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
|
|
219
219
|
|
|
220
220
|
<!-- AGENT LIST -->
|
|
221
221
|
|
package/README.zh-CN.md
CHANGED
|
@@ -186,12 +186,12 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
|
|
|
186
186
|
|
|
187
187
|
| 最近新增 | 助手说明 |
|
|
188
188
|
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
189
|
+
| [绩效评估超人](https://chat-preview.lobehub.com/market?agent=kpi-hero)<br/><sup>By **[canisminor1990](https://github.com/canisminor1990)** on **2024-01-05**</sup> | 擅长写绩效评估报告与年终总结<br/>`绩效评估` `报告撰写` `数据分析` `专业见解` `okr` `kpi` |
|
|
189
190
|
| [SVG 流程图解释助手](https://chat-preview.lobehub.com/market?agent=svg-flowchart-explanation-assistant)<br/><sup>By **[Justin3go](https://github.com/Justin3go)** on **2024-01-05**</sup> | SVG 流程图解释,输入 SVG 源代码,解释该流程图<br/>`流程图解释` `技术文档编写` `业务知识` |
|
|
191
|
+
| [周报助手](https://chat-preview.lobehub.com/market?agent=write-report-assistant-development)<br/><sup>By **[CaoYunzhou](https://github.com/CaoYunzhou)** on **2024-01-05**</sup> | 周报生成助手<br/>`周报` `日报` `写作` `总结` |
|
|
190
192
|
| [三维动画工程师](https://chat-preview.lobehub.com/market?agent=react-three-3-d-expert)<br/><sup>By **[arvinxx](https://github.com/arvinxx)** on **2024-01-03**</sup> | 擅长 React、Three.js、React Three Fiber(r3f)、Drei 等库,能够在网页应用中创造高级别的三维视觉效果和动画。<br/>`3-d动画` `react` `three-js` `网页设计` `动画` |
|
|
191
|
-
| [Amazon Title Assistant](https://chat-preview.lobehub.com/market?agent=amazon)<br/><sup>By **[cm2457618290](https://github.com/cm2457618290)** on **2024-01-02**</sup> | Provide product keywords or product links to automatically write titles and product introductions<br/>`assistant` |
|
|
192
|
-
| [考试助手](https://chat-preview.lobehub.com/market?agent=generador-examenes)<br/><sup>By **[aitorroma](https://github.com/aitorroma)** on **2024-01-02**</sup> | 我是一个技能总结助手,无法进行互动式考试。但我可以帮助你以清晰简洁的格式总结你的技能和知识。<br/>`考试` `学习` `统计` |
|
|
193
193
|
|
|
194
|
-
> 📊 Total agents: [<kbd>**
|
|
194
|
+
> 📊 Total agents: [<kbd>**91**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
|
|
195
195
|
|
|
196
196
|
<!-- AGENT LIST -->
|
|
197
197
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.119.
|
|
3
|
+
"version": "0.119.4",
|
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -112,6 +112,7 @@
|
|
|
112
112
|
"react-intersection-observer": "^9",
|
|
113
113
|
"react-layout-kit": "^1",
|
|
114
114
|
"react-lazy-load": "^4",
|
|
115
|
+
"react-virtuoso": "^4.6.2",
|
|
115
116
|
"react-wrap-balancer": "^1",
|
|
116
117
|
"remark": "^14",
|
|
117
118
|
"remark-gfm": "^3",
|
|
@@ -1,21 +1,55 @@
|
|
|
1
1
|
import { Skeleton } from 'antd';
|
|
2
|
+
import { createStyles } from 'antd-style';
|
|
2
3
|
import { memo } from 'react';
|
|
3
4
|
import { Flexbox } from 'react-layout-kit';
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
+
const useStyles = createStyles(({ css, prefixCls }) => ({
|
|
7
|
+
container: css`
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
|
|
12
|
+
height: 44px;
|
|
13
|
+
padding: 8px;
|
|
14
|
+
|
|
15
|
+
.${prefixCls}-skeleton-content {
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
}
|
|
19
|
+
`,
|
|
20
|
+
|
|
21
|
+
paragraph: css`
|
|
22
|
+
> li {
|
|
23
|
+
height: 24px !important;
|
|
24
|
+
}
|
|
25
|
+
`,
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
export const Placeholder = memo(() => {
|
|
29
|
+
const { styles } = useStyles();
|
|
30
|
+
|
|
6
31
|
return (
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
32
|
+
<Skeleton
|
|
33
|
+
active
|
|
34
|
+
avatar={false}
|
|
35
|
+
className={styles.container}
|
|
36
|
+
paragraph={{
|
|
37
|
+
className: styles.paragraph,
|
|
38
|
+
rows: 1,
|
|
39
|
+
style: { marginBottom: 0 },
|
|
40
|
+
width: '100%',
|
|
41
|
+
}}
|
|
42
|
+
title={false}
|
|
43
|
+
/>
|
|
18
44
|
);
|
|
19
45
|
});
|
|
20
46
|
|
|
47
|
+
export const SkeletonList = memo(() => (
|
|
48
|
+
<Flexbox>
|
|
49
|
+
{Array.from({ length: 8 }).map((_, i) => (
|
|
50
|
+
<Placeholder key={i} />
|
|
51
|
+
))}
|
|
52
|
+
</Flexbox>
|
|
53
|
+
));
|
|
54
|
+
|
|
21
55
|
export default SkeletonList;
|
|
@@ -1,58 +1,92 @@
|
|
|
1
1
|
import { EmptyCard } from '@lobehub/ui';
|
|
2
2
|
import { useThemeMode } from 'antd-style';
|
|
3
3
|
import isEqual from 'fast-deep-equal';
|
|
4
|
+
import React, { memo, useCallback, useRef } from 'react';
|
|
4
5
|
import { useTranslation } from 'react-i18next';
|
|
5
6
|
import { Flexbox } from 'react-layout-kit';
|
|
7
|
+
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
|
|
6
8
|
|
|
7
9
|
import { useChatStore } from '@/store/chat';
|
|
8
10
|
import { topicSelectors } from '@/store/chat/selectors';
|
|
9
11
|
import { useGlobalStore } from '@/store/global';
|
|
12
|
+
import { ChatTopic } from '@/types/topic';
|
|
10
13
|
|
|
11
|
-
import SkeletonList from './SkeletonList';
|
|
14
|
+
import { Placeholder, SkeletonList } from './SkeletonList';
|
|
12
15
|
import TopicItem from './TopicItem';
|
|
13
16
|
|
|
14
|
-
export const Topic = () => {
|
|
17
|
+
export const Topic = memo(() => {
|
|
18
|
+
const { t } = useTranslation('chat');
|
|
19
|
+
const virtuosoRef = useRef<VirtuosoHandle>(null);
|
|
15
20
|
const { isDarkMode } = useThemeMode();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const { t } = useTranslation('empty');
|
|
21
|
+
const [topicsInit, activeTopicId, topicLength] = useChatStore((s) => [
|
|
22
|
+
s.topicsInit,
|
|
23
|
+
s.activeTopicId,
|
|
24
|
+
topicSelectors.currentTopicLength(s),
|
|
25
|
+
]);
|
|
22
26
|
const [visible, updateGuideState] = useGlobalStore((s) => [
|
|
23
27
|
s.preference.guide?.topic,
|
|
24
28
|
s.updateGuideState,
|
|
25
29
|
]);
|
|
26
30
|
|
|
31
|
+
const topics = useChatStore(
|
|
32
|
+
(s) => [
|
|
33
|
+
{
|
|
34
|
+
favorite: false,
|
|
35
|
+
id: 'default',
|
|
36
|
+
title: t('topic.defaultTitle'),
|
|
37
|
+
} as ChatTopic,
|
|
38
|
+
...topicSelectors.displayTopics(s),
|
|
39
|
+
],
|
|
40
|
+
isEqual,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const itemContent = useCallback(
|
|
44
|
+
(index: number, { id, favorite, title }: ChatTopic) =>
|
|
45
|
+
index === 0 ? (
|
|
46
|
+
<TopicItem active={!activeTopicId} fav={favorite} title={title} />
|
|
47
|
+
) : (
|
|
48
|
+
<TopicItem active={activeTopicId === id} fav={favorite} id={id} key={id} title={title} />
|
|
49
|
+
),
|
|
50
|
+
[activeTopicId],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const activeIndex = topics.findIndex((topic) => topic.id === activeTopicId);
|
|
54
|
+
|
|
27
55
|
return !topicsInit ? (
|
|
28
56
|
<SkeletonList />
|
|
29
57
|
) : (
|
|
30
|
-
<Flexbox gap={2} style={{ marginBottom: 12 }}>
|
|
31
|
-
{
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
<Flexbox gap={2} height={'100%'} style={{ marginBottom: 12 }}>
|
|
59
|
+
{topicLength === 0 && (
|
|
60
|
+
<Flexbox flex={1}>
|
|
61
|
+
<EmptyCard
|
|
62
|
+
alt={t('topic.guide.desc')}
|
|
63
|
+
cover={`/images/empty_topic_${isDarkMode ? 'dark' : 'light'}.webp`}
|
|
64
|
+
desc={t('topic.guide.desc')}
|
|
65
|
+
height={120}
|
|
66
|
+
onVisibleChange={(visible) => {
|
|
67
|
+
updateGuideState({ topic: visible });
|
|
68
|
+
}}
|
|
69
|
+
style={{ marginBottom: 6 }}
|
|
70
|
+
title={t('topic.guide.title')}
|
|
71
|
+
visible={visible}
|
|
72
|
+
width={200}
|
|
73
|
+
/>
|
|
74
|
+
</Flexbox>
|
|
45
75
|
)}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
<Virtuoso
|
|
77
|
+
components={{ ScrollSeekPlaceholder: Placeholder }}
|
|
78
|
+
computeItemKey={(_, item) => item.id}
|
|
79
|
+
data={topics}
|
|
80
|
+
fixedItemHeight={44}
|
|
81
|
+
initialTopMostItemIndex={Math.max(activeIndex, 0)}
|
|
82
|
+
itemContent={itemContent}
|
|
83
|
+
overscan={44 * 10}
|
|
84
|
+
ref={virtuosoRef}
|
|
85
|
+
scrollSeekConfiguration={{
|
|
86
|
+
enter: (velocity) => Math.abs(velocity) > 350,
|
|
87
|
+
exit: (velocity) => Math.abs(velocity) < 10,
|
|
88
|
+
}}
|
|
51
89
|
/>
|
|
52
|
-
|
|
53
|
-
{topics.map(({ id, favorite, title }) => (
|
|
54
|
-
<TopicItem active={activeTopicId === id} fav={favorite} id={id} key={id} title={title} />
|
|
55
|
-
))}
|
|
56
90
|
</Flexbox>
|
|
57
91
|
);
|
|
58
|
-
};
|
|
92
|
+
});
|
|
@@ -11,8 +11,9 @@ const TopicListContent = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
|
11
11
|
{mobile ? <TopicSearchBar /> : <Header />}
|
|
12
12
|
<Flexbox
|
|
13
13
|
gap={16}
|
|
14
|
+
height={'100%'}
|
|
14
15
|
paddingInline={mobile ? 0 : 8}
|
|
15
|
-
style={{
|
|
16
|
+
style={{ paddingTop: 6, position: 'relative' }}
|
|
16
17
|
>
|
|
17
18
|
<Topic />
|
|
18
19
|
</Flexbox>
|
|
@@ -75,7 +75,13 @@ const LLM = memo(() => {
|
|
|
75
75
|
name: [configKey, 'openAI', 'endpoint'],
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
|
-
children:
|
|
78
|
+
children: (
|
|
79
|
+
<Input.TextArea
|
|
80
|
+
allowClear
|
|
81
|
+
placeholder={t('llm.OpenAI.customModelName.placeholder')}
|
|
82
|
+
style={{ height: 100 }}
|
|
83
|
+
/>
|
|
84
|
+
),
|
|
79
85
|
desc: t('llm.OpenAI.customModelName.desc'),
|
|
80
86
|
label: t('llm.OpenAI.customModelName.title'),
|
|
81
87
|
name: [configKey, 'openAI', 'customModelName'],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Icon } from '@lobehub/ui';
|
|
2
2
|
import { Button, Input, Segmented } from 'antd';
|
|
3
|
-
import {
|
|
3
|
+
import { AsteriskSquare, KeySquare } from 'lucide-react';
|
|
4
4
|
import { memo, useState } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { Flexbox } from 'react-layout-kit';
|
|
@@ -30,7 +30,7 @@ const InvalidAccess: RenderErrorMessage['Render'] = memo(({ id }) => {
|
|
|
30
30
|
onChange={(value) => setMode(value as Tab)}
|
|
31
31
|
options={[
|
|
32
32
|
{
|
|
33
|
-
icon: <Icon icon={
|
|
33
|
+
icon: <Icon icon={AsteriskSquare} />,
|
|
34
34
|
label: t('password', { ns: 'common' }),
|
|
35
35
|
value: Tab.Password,
|
|
36
36
|
},
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { memo, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useChatStore } from '@/store/chat';
|
|
4
|
+
import { chatSelectors } from '@/store/chat/selectors';
|
|
5
|
+
|
|
6
|
+
import BackBottom from './BackBottom';
|
|
7
|
+
|
|
8
|
+
interface AutoScrollProps {
|
|
9
|
+
atBottom: boolean;
|
|
10
|
+
onScrollToBottom: (type: 'auto' | 'click') => void;
|
|
11
|
+
}
|
|
12
|
+
const AutoScroll = memo<AutoScrollProps>(({ atBottom, onScrollToBottom }) => {
|
|
13
|
+
const trackVisibility = useChatStore((s) => !!s.chatLoadingId);
|
|
14
|
+
const str = useChatStore(chatSelectors.chatsMessageString);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (atBottom && trackVisibility) {
|
|
18
|
+
onScrollToBottom?.('auto');
|
|
19
|
+
}
|
|
20
|
+
}, [atBottom, trackVisibility, str]);
|
|
21
|
+
|
|
22
|
+
return <BackBottom onScrollToBottom={() => onScrollToBottom('click')} visible={!atBottom} />;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export default AutoScroll;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Icon } from '@lobehub/ui';
|
|
2
|
+
import { Button } from 'antd';
|
|
3
|
+
import { ListEnd } from 'lucide-react';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
|
|
7
|
+
import { useStyles } from './style';
|
|
8
|
+
|
|
9
|
+
export interface BackBottomProps {
|
|
10
|
+
onScrollToBottom: () => void;
|
|
11
|
+
visible: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const BackBottom = memo<BackBottomProps>(({ visible, onScrollToBottom }) => {
|
|
15
|
+
const { styles, cx } = useStyles();
|
|
16
|
+
|
|
17
|
+
const { t } = useTranslation('chat');
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Button
|
|
21
|
+
className={cx(styles.container, visible && styles.visible)}
|
|
22
|
+
icon={<Icon icon={ListEnd} />}
|
|
23
|
+
onClick={onScrollToBottom}
|
|
24
|
+
size={'small'}
|
|
25
|
+
>
|
|
26
|
+
{t('backToBottom', { defaultValue: 'Back to bottom' })}
|
|
27
|
+
</Button>
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export default BackBottom;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createStyles } from 'antd-style';
|
|
2
|
+
import { rgba } from 'polished';
|
|
3
|
+
|
|
4
|
+
export const useStyles = createStyles(({ token, css, stylish, cx, responsive }) => ({
|
|
5
|
+
container: cx(
|
|
6
|
+
stylish.blur,
|
|
7
|
+
css`
|
|
8
|
+
pointer-events: none;
|
|
9
|
+
|
|
10
|
+
position: absolute;
|
|
11
|
+
z-index: 1000;
|
|
12
|
+
right: 16px;
|
|
13
|
+
bottom: 16px;
|
|
14
|
+
transform: translateY(16px);
|
|
15
|
+
|
|
16
|
+
padding-inline: 12px !important;
|
|
17
|
+
|
|
18
|
+
opacity: 0;
|
|
19
|
+
background: ${rgba(token.colorBgContainer, 0.5)};
|
|
20
|
+
border-color: ${token.colorFillTertiary} !important;
|
|
21
|
+
border-radius: 16px !important;
|
|
22
|
+
|
|
23
|
+
${responsive.mobile} {
|
|
24
|
+
right: 0;
|
|
25
|
+
border-right: none;
|
|
26
|
+
border-top-right-radius: 0 !important;
|
|
27
|
+
border-bottom-right-radius: 0 !important;
|
|
28
|
+
}
|
|
29
|
+
`,
|
|
30
|
+
),
|
|
31
|
+
visible: css`
|
|
32
|
+
pointer-events: all;
|
|
33
|
+
transform: translateY(0);
|
|
34
|
+
opacity: 1;
|
|
35
|
+
`,
|
|
36
|
+
}));
|
|
@@ -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
|
@@ -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
|