@lobehub/chat 0.18.2 → 0.20.0
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 +59 -0
- package/package.json +1 -1
- package/src/locales/default/common.ts +4 -2
- package/src/pages/chat/[id]/Config/SideBar.tsx +10 -6
- package/src/pages/chat/[id]/Config/Topic.tsx +27 -0
- package/src/pages/chat/[id]/Config/TopicItem.tsx +80 -0
- package/src/pages/chat/[id]/Conversation/Input/index.tsx +7 -4
- package/src/pages/chat/[id]/Header.tsx +14 -10
- package/src/pages/chat/layout.tsx +10 -4
- package/src/prompts/chat.ts +19 -0
- package/src/store/session/selectors.ts +1 -1
- package/src/store/session/slices/agentConfig/selectors.ts +4 -0
- package/src/store/session/slices/chat/actions/index.ts +21 -0
- package/src/store/session/slices/chat/{action.ts → actions/message.ts} +34 -32
- package/src/store/session/slices/chat/actions/topic.ts +98 -0
- package/src/store/session/slices/chat/index.ts +1 -1
- package/src/store/session/slices/chat/initialState.ts +2 -5
- package/src/store/session/slices/chat/{messageReducer.test.ts → reducers/message.test.ts} +93 -1
- package/src/store/session/slices/chat/{messageReducer.ts → reducers/message.ts} +16 -1
- package/src/store/session/slices/chat/reducers/topic.test.ts +179 -0
- package/src/store/session/slices/chat/reducers/topic.ts +59 -0
- package/src/store/session/slices/chat/selectors/chat.ts +8 -4
- package/src/store/session/slices/chat/selectors/index.ts +5 -0
- package/src/store/session/slices/chat/selectors/topic.ts +16 -0
- package/src/store/session/slices/chat/selectors/utils.test.ts +82 -2
- package/src/store/session/slices/chat/selectors/utils.ts +8 -2
- package/src/store/session/slices/session/reducers/session.ts +25 -1
- package/src/types/chatMessage.ts +4 -2
- package/src/types/session.ts +2 -1
- package/src/types/topic.ts +8 -0
- package/src/pages/chat/[id]/Config/ConfigCell.tsx +0 -68
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 0.20.0](https://github.com/lobehub/lobe-chat/compare/v0.19.0...v0.20.0)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2023-07-25**</sup>
|
|
8
|
+
|
|
9
|
+
#### ✨ Features
|
|
10
|
+
|
|
11
|
+
- **misc**: 实现话题模块.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's improved
|
|
19
|
+
|
|
20
|
+
- **misc**: 实现话题模块, closes [#16](https://github.com/lobehub/lobe-chat/issues/16) ([64fd6ee](https://github.com/lobehub/lobe-chat/commit/64fd6ee))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## [Version 0.19.0](https://github.com/lobehub/lobe-chat/compare/v0.18.2...v0.19.0)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2023-07-24**</sup>
|
|
33
|
+
|
|
34
|
+
#### ♻ Code Refactoring
|
|
35
|
+
|
|
36
|
+
- **misc**: 将 message reducer 提取到独立文件夹中, 清理无用代码实现.
|
|
37
|
+
|
|
38
|
+
#### ✨ Features
|
|
39
|
+
|
|
40
|
+
- **misc**: 数据结构层完成 topic 模型改造.
|
|
41
|
+
|
|
42
|
+
<br/>
|
|
43
|
+
|
|
44
|
+
<details>
|
|
45
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
46
|
+
|
|
47
|
+
#### Code refactoring
|
|
48
|
+
|
|
49
|
+
- **misc**: 将 message reducer 提取到独立文件夹中 ([64f40ca](https://github.com/lobehub/lobe-chat/commit/64f40ca))
|
|
50
|
+
- **misc**: 清理无用代码实现 ([3655b60](https://github.com/lobehub/lobe-chat/commit/3655b60))
|
|
51
|
+
|
|
52
|
+
#### What's improved
|
|
53
|
+
|
|
54
|
+
- **misc**: 数据结构层完成 topic 模型改造 ([99fa2a6](https://github.com/lobehub/lobe-chat/commit/99fa2a6))
|
|
55
|
+
|
|
56
|
+
</details>
|
|
57
|
+
|
|
58
|
+
<div align="right">
|
|
59
|
+
|
|
60
|
+
[](#readme-top)
|
|
61
|
+
|
|
62
|
+
</div>
|
|
63
|
+
|
|
5
64
|
### [Version 0.18.2](https://github.com/lobehub/lobe-chat/compare/v0.18.1...v0.18.2)
|
|
6
65
|
|
|
7
66
|
<sup>Released on **2023-07-24**</sup>
|
package/package.json
CHANGED
|
@@ -4,8 +4,6 @@ export default {
|
|
|
4
4
|
agentModel: '模型',
|
|
5
5
|
agentProfile: '助手信息',
|
|
6
6
|
archive: '归档',
|
|
7
|
-
archiveCurrentMessages: '归档当前会话',
|
|
8
|
-
archiveSearchPlaceholder: '搜索归档对话...',
|
|
9
7
|
autoGenerate: '自动补全',
|
|
10
8
|
autoGenerateTooltip: '基于提示词自动补全助手描述',
|
|
11
9
|
cancel: '取消',
|
|
@@ -28,6 +26,10 @@ export default {
|
|
|
28
26
|
setting: '设置',
|
|
29
27
|
share: '分享',
|
|
30
28
|
tokenDetail: '系统设定: {{systemRoleToken}} 历史消息: {{chatsToken}}',
|
|
29
|
+
topic: {
|
|
30
|
+
saveCurrentMessages: '将当前会话保存为话题',
|
|
31
|
+
searchPlaceholder: '搜索话题...',
|
|
32
|
+
},
|
|
31
33
|
updateAgent: '更新助理信息',
|
|
32
34
|
updatePrompt: '更新提示词',
|
|
33
35
|
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ActionIcon, DraggablePanelBody, EditableMessage, SearchBar } from '@lobehub/ui';
|
|
2
2
|
import { createStyles } from 'antd-style';
|
|
3
|
-
import {
|
|
3
|
+
import { Maximize2Icon } 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';
|
|
7
7
|
import { shallow } from 'zustand/shallow';
|
|
8
8
|
|
|
9
|
+
import { Topic } from '@/pages/chat/[id]/Config/Topic';
|
|
9
10
|
import { agentSelectors, useSessionStore } from '@/store/session';
|
|
10
11
|
|
|
11
12
|
import Header from './Header';
|
|
@@ -42,8 +43,10 @@ const useStyles = createStyles(({ css, token }) => ({
|
|
|
42
43
|
const SideBar = memo(() => {
|
|
43
44
|
const [openModal, setOpenModal] = useState(false);
|
|
44
45
|
const { styles } = useStyles();
|
|
45
|
-
const [updateAgentConfig] = useSessionStore(
|
|
46
|
-
|
|
46
|
+
const [systemRole, updateAgentConfig] = useSessionStore(
|
|
47
|
+
(s) => [agentSelectors.currentAgentSystemRole(s), s.updateAgentConfig],
|
|
48
|
+
shallow,
|
|
49
|
+
);
|
|
47
50
|
|
|
48
51
|
const { t } = useTranslation('common');
|
|
49
52
|
return (
|
|
@@ -51,7 +54,7 @@ const SideBar = memo(() => {
|
|
|
51
54
|
<Header
|
|
52
55
|
actions={
|
|
53
56
|
<ActionIcon
|
|
54
|
-
icon={
|
|
57
|
+
icon={Maximize2Icon}
|
|
55
58
|
onClick={() => setOpenModal(true)}
|
|
56
59
|
size="small"
|
|
57
60
|
title={t('edit')}
|
|
@@ -76,8 +79,9 @@ const SideBar = memo(() => {
|
|
|
76
79
|
}}
|
|
77
80
|
value={systemRole}
|
|
78
81
|
/>
|
|
79
|
-
<Flexbox style={{ padding: 16 }}>
|
|
80
|
-
<SearchBar placeholder={t('
|
|
82
|
+
<Flexbox gap={12} style={{ padding: 16 }}>
|
|
83
|
+
<SearchBar placeholder={t('topic.searchPlaceholder')} type={'block'} />
|
|
84
|
+
<Topic />
|
|
81
85
|
</Flexbox>
|
|
82
86
|
</DraggablePanelBody>
|
|
83
87
|
);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Flexbox } from 'react-layout-kit';
|
|
2
|
+
import { shallow } from 'zustand/shallow';
|
|
3
|
+
|
|
4
|
+
import { topicSelectors, useSessionStore } from '@/store/session';
|
|
5
|
+
|
|
6
|
+
import TopicItem from './TopicItem';
|
|
7
|
+
|
|
8
|
+
export const Topic = () => {
|
|
9
|
+
const topics = useSessionStore(topicSelectors.currentTopics);
|
|
10
|
+
const [activeTopicId] = useSessionStore((s) => [s.activeTopicId], shallow);
|
|
11
|
+
return (
|
|
12
|
+
<Flexbox gap={8}>
|
|
13
|
+
<TopicItem active={!activeTopicId} fav={false} title={'默认话题'} />
|
|
14
|
+
|
|
15
|
+
{topics.map(({ id, favorite, title }) => (
|
|
16
|
+
<TopicItem
|
|
17
|
+
active={activeTopicId === id}
|
|
18
|
+
fav={favorite}
|
|
19
|
+
id={id}
|
|
20
|
+
key={id}
|
|
21
|
+
showFav
|
|
22
|
+
title={title}
|
|
23
|
+
/>
|
|
24
|
+
))}
|
|
25
|
+
</Flexbox>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { StarFilled, StarOutlined } from '@ant-design/icons';
|
|
2
|
+
import { ActionIcon } from '@lobehub/ui';
|
|
3
|
+
import { createStyles } from 'antd-style';
|
|
4
|
+
import { LucideIcon } from 'lucide-react';
|
|
5
|
+
import { memo } from 'react';
|
|
6
|
+
import { Flexbox } from 'react-layout-kit';
|
|
7
|
+
import { shallow } from 'zustand/shallow';
|
|
8
|
+
|
|
9
|
+
import { useSessionStore } from '@/store/session';
|
|
10
|
+
|
|
11
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
12
|
+
active: css`
|
|
13
|
+
background: ${token.colorFill};
|
|
14
|
+
|
|
15
|
+
&:hover {
|
|
16
|
+
background: ${token.colorFill};
|
|
17
|
+
}
|
|
18
|
+
`,
|
|
19
|
+
container: css`
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
background: ${token.colorFillTertiary};
|
|
22
|
+
border-radius: 6px;
|
|
23
|
+
|
|
24
|
+
&:hover {
|
|
25
|
+
background: ${token.colorFillSecondary};
|
|
26
|
+
}
|
|
27
|
+
`,
|
|
28
|
+
split: css`
|
|
29
|
+
border-bottom: 1px solid ${token.colorSplit};
|
|
30
|
+
`,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
export interface ConfigCellProps {
|
|
34
|
+
active?: boolean;
|
|
35
|
+
fav?: boolean;
|
|
36
|
+
id?: string;
|
|
37
|
+
showFav?: boolean;
|
|
38
|
+
title: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const TopicItem = memo<ConfigCellProps>(({ title, active, id, showFav, fav }) => {
|
|
42
|
+
const { styles, theme, cx } = useStyles();
|
|
43
|
+
|
|
44
|
+
const [dispatchTopic, toggleTopic] = useSessionStore(
|
|
45
|
+
(s) => [s.dispatchTopic, s.toggleTopic],
|
|
46
|
+
shallow,
|
|
47
|
+
);
|
|
48
|
+
const starIcon = (fav ? StarFilled : StarOutlined) as LucideIcon;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Flexbox
|
|
52
|
+
align={'center'}
|
|
53
|
+
className={cx(styles.container, active && styles.active)}
|
|
54
|
+
distribution={'space-between'}
|
|
55
|
+
horizontal
|
|
56
|
+
onClick={() => {
|
|
57
|
+
toggleTopic(id);
|
|
58
|
+
}}
|
|
59
|
+
padding={'10px 12px'}
|
|
60
|
+
>
|
|
61
|
+
{title}
|
|
62
|
+
{!showFav ? undefined : (
|
|
63
|
+
<ActionIcon
|
|
64
|
+
icon={starIcon}
|
|
65
|
+
onClick={() => {
|
|
66
|
+
if (!id) return;
|
|
67
|
+
|
|
68
|
+
dispatchTopic({ id, key: 'favorite', type: 'updateChatTopic', value: !fav });
|
|
69
|
+
}}
|
|
70
|
+
size={'small'}
|
|
71
|
+
style={{
|
|
72
|
+
color: fav ? theme.yellow : undefined,
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
</Flexbox>
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export default TopicItem;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChatInputArea, DraggablePanel, Icon, Tooltip } from '@lobehub/ui';
|
|
2
2
|
import { Button } from 'antd';
|
|
3
|
-
import {
|
|
3
|
+
import { LucideGalleryVerticalEnd } from 'lucide-react';
|
|
4
4
|
import { memo, useMemo, useState } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { shallow } from 'zustand/shallow';
|
|
@@ -18,12 +18,15 @@ const ChatInput = () => {
|
|
|
18
18
|
const [text, setText] = useState('');
|
|
19
19
|
|
|
20
20
|
const [inputHeight] = useSettings((s) => [s.inputHeight], shallow);
|
|
21
|
-
const [sendMessage] = useSessionStore(
|
|
21
|
+
const [sendMessage, saveToTopic] = useSessionStore(
|
|
22
|
+
(s) => [s.createOrSendMsg, s.saveToTopic],
|
|
23
|
+
shallow,
|
|
24
|
+
);
|
|
22
25
|
|
|
23
26
|
const footer = useMemo(
|
|
24
27
|
() => (
|
|
25
|
-
<Tooltip title={t('
|
|
26
|
-
<Button icon={<Icon icon={
|
|
28
|
+
<Tooltip title={t('topic.saveCurrentMessages')}>
|
|
29
|
+
<Button icon={<Icon icon={LucideGalleryVerticalEnd} />} onClick={saveToTopic} />
|
|
27
30
|
</Tooltip>
|
|
28
31
|
),
|
|
29
32
|
[],
|
|
@@ -12,10 +12,18 @@ import { agentSelectors, useSessionStore } from '@/store/session';
|
|
|
12
12
|
const Header = memo(() => {
|
|
13
13
|
const { t } = useTranslation('common');
|
|
14
14
|
|
|
15
|
-
const [
|
|
16
|
-
(s) => [
|
|
15
|
+
const [title, description, avatar, backgroundColor, id, model] = useSessionStore(
|
|
16
|
+
(s) => [
|
|
17
|
+
agentSelectors.currentAgentTitle(s),
|
|
18
|
+
agentSelectors.currentAgentDescription(s),
|
|
19
|
+
agentSelectors.currentAgentAvatar(s),
|
|
20
|
+
agentSelectors.currentAgentBackgroundColor(s),
|
|
21
|
+
s.activeId,
|
|
22
|
+
agentSelectors.currentAgentModel(s),
|
|
23
|
+
],
|
|
17
24
|
shallow,
|
|
18
25
|
);
|
|
26
|
+
|
|
19
27
|
const [showAgentSettings, toggleConfig] = useSessionStore(
|
|
20
28
|
(s) => [s.showAgentSettings, s.toggleConfig],
|
|
21
29
|
shallow,
|
|
@@ -26,20 +34,16 @@ const Header = memo(() => {
|
|
|
26
34
|
left={
|
|
27
35
|
<>
|
|
28
36
|
<Avatar
|
|
29
|
-
avatar={
|
|
30
|
-
background={
|
|
37
|
+
avatar={avatar}
|
|
38
|
+
background={backgroundColor}
|
|
31
39
|
onClick={() => {
|
|
32
40
|
Router.push(`/chat/${id}/edit`);
|
|
33
41
|
}}
|
|
34
42
|
size={40}
|
|
35
43
|
style={{ cursor: 'pointer' }}
|
|
36
|
-
title={
|
|
37
|
-
/>
|
|
38
|
-
<HeaderTitle
|
|
39
|
-
desc={meta?.description || t('noDescription')}
|
|
40
|
-
tag={<Tag>{modle}</Tag>}
|
|
41
|
-
title={meta?.title || t('defaultAgent')}
|
|
44
|
+
title={title}
|
|
42
45
|
/>
|
|
46
|
+
<HeaderTitle desc={description} tag={<Tag>{model}</Tag>} title={title} />
|
|
43
47
|
</>
|
|
44
48
|
}
|
|
45
49
|
right={
|
|
@@ -17,8 +17,8 @@ const ChatLayout = memo<PropsWithChildren>(({ children }) => {
|
|
|
17
17
|
initI18n.finally();
|
|
18
18
|
}, []);
|
|
19
19
|
|
|
20
|
-
const [activeSession] = useSessionStore((s) => {
|
|
21
|
-
return [s.activeSession];
|
|
20
|
+
const [activeSession, toggleTopic] = useSessionStore((s) => {
|
|
21
|
+
return [s.activeSession, s.toggleTopic];
|
|
22
22
|
}, shallow);
|
|
23
23
|
|
|
24
24
|
const router = useRouter();
|
|
@@ -26,10 +26,16 @@ const ChatLayout = memo<PropsWithChildren>(({ children }) => {
|
|
|
26
26
|
|
|
27
27
|
useEffect(() => {
|
|
28
28
|
const hasRehydrated = useSessionStore.persist.hasHydrated();
|
|
29
|
-
//
|
|
30
|
-
if (
|
|
29
|
+
// 只有当水合完毕后再开始做操作
|
|
30
|
+
if (!hasRehydrated) return;
|
|
31
|
+
|
|
32
|
+
// 1. 正常激活会话
|
|
33
|
+
if (typeof id === 'string') {
|
|
31
34
|
activeSession(id);
|
|
32
35
|
}
|
|
36
|
+
|
|
37
|
+
// 将话题重置为默认值
|
|
38
|
+
toggleTopic();
|
|
33
39
|
}, [id]);
|
|
34
40
|
|
|
35
41
|
useEffect(() => {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { OpenAIChatMessage, OpenAIStreamPayload } from '@/types/openai';
|
|
2
|
+
|
|
3
|
+
export const promptSummaryTitle = (
|
|
4
|
+
messages: OpenAIChatMessage[],
|
|
5
|
+
): Partial<OpenAIStreamPayload> => ({
|
|
6
|
+
messages: [
|
|
7
|
+
{
|
|
8
|
+
content:
|
|
9
|
+
'你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不需要包含标点符号',
|
|
10
|
+
role: 'system',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
content: `${messages.map((message) => `${message.role}: ${message.content}`).join('\n')}
|
|
14
|
+
|
|
15
|
+
请总结上述对话为10个字以内的标题,不需要包含标点符号`,
|
|
16
|
+
role: 'user',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
});
|
|
@@ -17,6 +17,9 @@ const currentAgentMeta = (s: SessionStore): MetaData => {
|
|
|
17
17
|
|
|
18
18
|
const currentAgentTitle = (s: SessionStore) => currentAgentMeta(s)?.title || t('defaultSession');
|
|
19
19
|
|
|
20
|
+
const currentAgentDescription = (s: SessionStore) =>
|
|
21
|
+
currentAgentMeta(s)?.description || t('noDescription');
|
|
22
|
+
|
|
20
23
|
const currentAgentBackgroundColor = (s: SessionStore) => {
|
|
21
24
|
const session = sessionSelectors.currentSession(s);
|
|
22
25
|
if (!session) return DEFAULT_BACKGROUND_COLOR;
|
|
@@ -62,6 +65,7 @@ export const agentSelectors = {
|
|
|
62
65
|
currentAgentBackgroundColor,
|
|
63
66
|
currentAgentConfig,
|
|
64
67
|
currentAgentConfigSafe,
|
|
68
|
+
currentAgentDescription,
|
|
65
69
|
currentAgentMeta,
|
|
66
70
|
currentAgentModel,
|
|
67
71
|
currentAgentSystemRole,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
2
|
+
|
|
3
|
+
import { SessionStore } from '@/store/session';
|
|
4
|
+
|
|
5
|
+
import { ChatMessageAction, chatMessage } from './message';
|
|
6
|
+
import { ChatTopicAction, chatTopic } from './topic';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 聊天操作
|
|
10
|
+
*/
|
|
11
|
+
export interface ChatAction extends ChatTopicAction, ChatMessageAction {}
|
|
12
|
+
|
|
13
|
+
export const createChatSlice: StateCreator<
|
|
14
|
+
SessionStore,
|
|
15
|
+
[['zustand/devtools', never]],
|
|
16
|
+
[],
|
|
17
|
+
ChatAction
|
|
18
|
+
> = (...params) => ({
|
|
19
|
+
...chatTopic(...params),
|
|
20
|
+
...chatMessage(...params),
|
|
21
|
+
});
|
|
@@ -7,14 +7,14 @@ import { fetchSSE } from '@/utils/fetch';
|
|
|
7
7
|
import { isFunctionMessage } from '@/utils/message';
|
|
8
8
|
import { nanoid } from '@/utils/uuid';
|
|
9
9
|
|
|
10
|
-
import { MessageDispatch, messagesReducer } from '
|
|
10
|
+
import { MessageDispatch, messagesReducer } from '../reducers/message';
|
|
11
11
|
|
|
12
12
|
const LOADING_FLAT = '...';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* 聊天操作
|
|
16
16
|
*/
|
|
17
|
-
export interface
|
|
17
|
+
export interface ChatMessageAction {
|
|
18
18
|
/**
|
|
19
19
|
* 清除消息
|
|
20
20
|
*/
|
|
@@ -44,11 +44,7 @@ export interface ChatAction {
|
|
|
44
44
|
assistantMessageId: string,
|
|
45
45
|
withPlugin?: boolean,
|
|
46
46
|
) => Promise<{ isFunctionCall: boolean; output: string }>;
|
|
47
|
-
|
|
48
|
-
* 处理消息编辑
|
|
49
|
-
* @param messageId - 消息 ID,可选
|
|
50
|
-
*/
|
|
51
|
-
handleMessageEditing: (messageId: string | undefined) => void;
|
|
47
|
+
|
|
52
48
|
/**
|
|
53
49
|
* 实际获取 AI 响应
|
|
54
50
|
*
|
|
@@ -61,6 +57,7 @@ export interface ChatAction {
|
|
|
61
57
|
* @param id - 消息 ID
|
|
62
58
|
*/
|
|
63
59
|
resendMessage: (id: string) => Promise<void>;
|
|
60
|
+
|
|
64
61
|
/**
|
|
65
62
|
* 发送消息
|
|
66
63
|
* @param text - 消息文本
|
|
@@ -68,14 +65,20 @@ export interface ChatAction {
|
|
|
68
65
|
sendMessage: (text: string) => Promise<void>;
|
|
69
66
|
}
|
|
70
67
|
|
|
71
|
-
export const
|
|
68
|
+
export const chatMessage: StateCreator<
|
|
72
69
|
SessionStore,
|
|
73
70
|
[['zustand/devtools', never]],
|
|
74
71
|
[],
|
|
75
|
-
|
|
72
|
+
ChatMessageAction
|
|
76
73
|
> = (set, get) => ({
|
|
77
74
|
clearMessage: () => {
|
|
78
|
-
|
|
75
|
+
const { dispatchMessage, activeTopicId, dispatchTopic } = get();
|
|
76
|
+
|
|
77
|
+
dispatchMessage({ topicId: activeTopicId, type: 'resetMessages' });
|
|
78
|
+
|
|
79
|
+
if (activeTopicId) {
|
|
80
|
+
dispatchTopic({ id: activeTopicId, type: 'deleteChatTopic' });
|
|
81
|
+
}
|
|
79
82
|
},
|
|
80
83
|
|
|
81
84
|
createOrSendMsg: async (message) => {
|
|
@@ -158,12 +161,8 @@ export const createChatSlice: StateCreator<
|
|
|
158
161
|
return { isFunctionCall, output };
|
|
159
162
|
},
|
|
160
163
|
|
|
161
|
-
handleMessageEditing: (messageId) => {
|
|
162
|
-
set({ editingMessageId: messageId });
|
|
163
|
-
},
|
|
164
|
-
|
|
165
164
|
realFetchAIResponse: async (messages, userMessageId) => {
|
|
166
|
-
const { dispatchMessage, generateMessage } = get();
|
|
165
|
+
const { dispatchMessage, generateMessage, activeTopicId } = get();
|
|
167
166
|
|
|
168
167
|
// 添加 systemRole
|
|
169
168
|
const { systemRole, model } = agentSelectors.currentAgentConfigSafe(get());
|
|
@@ -173,49 +172,46 @@ export const createChatSlice: StateCreator<
|
|
|
173
172
|
|
|
174
173
|
// 再添加一个空的信息用于放置 ai 响应,注意顺序不能反
|
|
175
174
|
// 因为如果顺序反了,messages 中将包含新增的 ai message
|
|
176
|
-
const
|
|
175
|
+
const mid = nanoid();
|
|
177
176
|
|
|
178
177
|
dispatchMessage({
|
|
179
|
-
id:
|
|
178
|
+
id: mid,
|
|
180
179
|
message: LOADING_FLAT,
|
|
181
180
|
parentId: userMessageId,
|
|
182
181
|
role: 'assistant',
|
|
183
182
|
type: 'addMessage',
|
|
184
183
|
});
|
|
185
184
|
|
|
185
|
+
// 如果有 activeTopicId,则添加 topicId
|
|
186
|
+
if (activeTopicId) {
|
|
187
|
+
dispatchMessage({ id: mid, key: 'topicId', type: 'updateMessage', value: activeTopicId });
|
|
188
|
+
}
|
|
189
|
+
|
|
186
190
|
// 为模型添加 fromModel 的额外信息
|
|
187
|
-
dispatchMessage({
|
|
188
|
-
id: assistantId,
|
|
189
|
-
key: 'fromModel',
|
|
190
|
-
type: 'updateMessageExtra',
|
|
191
|
-
value: model,
|
|
192
|
-
});
|
|
191
|
+
dispatchMessage({ id: mid, key: 'fromModel', type: 'updateMessageExtra', value: model });
|
|
193
192
|
|
|
194
193
|
// 生成 ai message
|
|
195
|
-
const { output, isFunctionCall } = await generateMessage(messages,
|
|
194
|
+
const { output, isFunctionCall } = await generateMessage(messages, mid);
|
|
196
195
|
|
|
197
196
|
// 如果是 function,则发送函数调用方法
|
|
198
197
|
if (isFunctionCall) {
|
|
199
198
|
const { function_call } = JSON.parse(output);
|
|
200
199
|
|
|
201
200
|
dispatchMessage({
|
|
202
|
-
id:
|
|
201
|
+
id: mid,
|
|
203
202
|
key: 'function_call',
|
|
204
203
|
type: 'updateMessage',
|
|
205
204
|
value: function_call,
|
|
206
205
|
});
|
|
207
206
|
|
|
208
207
|
await generateMessage(
|
|
209
|
-
[
|
|
210
|
-
|
|
211
|
-
{ content: '', function_call, id: assistantId, role: 'assistant' } as ChatMessage,
|
|
212
|
-
],
|
|
213
|
-
assistantId,
|
|
208
|
+
[...messages, { content: '', function_call, id: mid, role: 'assistant' } as ChatMessage],
|
|
209
|
+
mid,
|
|
214
210
|
true,
|
|
215
211
|
);
|
|
216
212
|
|
|
217
213
|
dispatchMessage({
|
|
218
|
-
id:
|
|
214
|
+
id: mid,
|
|
219
215
|
key: 'role',
|
|
220
216
|
type: 'updateMessage',
|
|
221
217
|
value: 'assistant',
|
|
@@ -251,13 +247,19 @@ export const createChatSlice: StateCreator<
|
|
|
251
247
|
},
|
|
252
248
|
|
|
253
249
|
sendMessage: async (message) => {
|
|
254
|
-
const { dispatchMessage, realFetchAIResponse, autocompleteSessionAgentMeta } =
|
|
250
|
+
const { dispatchMessage, realFetchAIResponse, autocompleteSessionAgentMeta, activeTopicId } =
|
|
251
|
+
get();
|
|
255
252
|
const session = sessionSelectors.currentSession(get());
|
|
256
253
|
if (!session || !message) return;
|
|
257
254
|
|
|
258
255
|
const userId = nanoid();
|
|
259
256
|
dispatchMessage({ id: userId, message, role: 'user', type: 'addMessage' });
|
|
260
257
|
|
|
258
|
+
// 如果有 activeTopicId,则添加 topicId
|
|
259
|
+
if (activeTopicId) {
|
|
260
|
+
dispatchMessage({ id: userId, key: 'topicId', type: 'updateMessage', value: activeTopicId });
|
|
261
|
+
}
|
|
262
|
+
|
|
261
263
|
// 先拿到当前的 messages
|
|
262
264
|
const messages = chatSelectors.currentChats(get());
|
|
263
265
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
2
|
+
|
|
3
|
+
import { promptSummaryTitle } from '@/prompts/chat';
|
|
4
|
+
import { SessionStore, chatSelectors, sessionSelectors } from '@/store/session';
|
|
5
|
+
import { fetchPresetTaskResult } from '@/utils/fetch';
|
|
6
|
+
import { nanoid } from '@/utils/uuid';
|
|
7
|
+
|
|
8
|
+
import { ChatTopicDispatch, topicReducer } from '../reducers/topic';
|
|
9
|
+
|
|
10
|
+
export interface ChatTopicAction {
|
|
11
|
+
/**
|
|
12
|
+
* 分发主题
|
|
13
|
+
* @param payload - 要分发的主题
|
|
14
|
+
*/
|
|
15
|
+
dispatchTopic: (payload: ChatTopicDispatch) => void;
|
|
16
|
+
/**
|
|
17
|
+
* 将当前消息保存为主题
|
|
18
|
+
*/
|
|
19
|
+
saveToTopic: () => void;
|
|
20
|
+
/**
|
|
21
|
+
* 切换主题
|
|
22
|
+
* @param id - 要切换的主题的 ID
|
|
23
|
+
*/
|
|
24
|
+
toggleTopic: (id?: string) => void;
|
|
25
|
+
/**
|
|
26
|
+
* 更新主题加载状态
|
|
27
|
+
* @param id - 要更新的主题的 ID
|
|
28
|
+
*/
|
|
29
|
+
updateTopicLoading: (id?: string) => void;
|
|
30
|
+
}
|
|
31
|
+
export const chatTopic: StateCreator<
|
|
32
|
+
SessionStore,
|
|
33
|
+
[['zustand/devtools', never]],
|
|
34
|
+
[],
|
|
35
|
+
ChatTopicAction
|
|
36
|
+
> = (set, get) => ({
|
|
37
|
+
dispatchTopic: (payload) => {
|
|
38
|
+
const { activeId } = get();
|
|
39
|
+
const session = sessionSelectors.currentSession(get());
|
|
40
|
+
if (!activeId || !session) return;
|
|
41
|
+
|
|
42
|
+
const topics = topicReducer(session.topics || {}, payload);
|
|
43
|
+
|
|
44
|
+
get().dispatchSession({ id: activeId, topics, type: 'updateSessionTopic' });
|
|
45
|
+
},
|
|
46
|
+
saveToTopic: () => {
|
|
47
|
+
const session = sessionSelectors.currentSession(get());
|
|
48
|
+
if (!session) return;
|
|
49
|
+
|
|
50
|
+
const { dispatchTopic, dispatchMessage, updateTopicLoading } = get();
|
|
51
|
+
// 获取当前的 messages
|
|
52
|
+
const messages = chatSelectors.currentChats(get());
|
|
53
|
+
|
|
54
|
+
const topicId = nanoid();
|
|
55
|
+
|
|
56
|
+
const defaultTitle = '默认话题';
|
|
57
|
+
const newTopic = {
|
|
58
|
+
createAt: Date.now(),
|
|
59
|
+
id: topicId,
|
|
60
|
+
title: defaultTitle,
|
|
61
|
+
updateAt: Date.now(),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
dispatchTopic({
|
|
65
|
+
topic: newTopic,
|
|
66
|
+
type: 'addChatTopic',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// 为所有 message 添加 topicId
|
|
70
|
+
for (const m of messages) {
|
|
71
|
+
dispatchMessage({ id: m.id, key: 'topicId', type: 'updateMessage', value: topicId });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let output = '';
|
|
75
|
+
|
|
76
|
+
// 自动总结话题标题
|
|
77
|
+
fetchPresetTaskResult({
|
|
78
|
+
onError: () => {
|
|
79
|
+
dispatchTopic({ id: topicId, key: 'title', type: 'updateChatTopic', value: defaultTitle });
|
|
80
|
+
},
|
|
81
|
+
onLoadingChange: (loading) => {
|
|
82
|
+
updateTopicLoading(loading ? topicId : undefined);
|
|
83
|
+
},
|
|
84
|
+
onMessageHandle: (x) => {
|
|
85
|
+
output += x;
|
|
86
|
+
dispatchTopic({ id: topicId, key: 'title', type: 'updateChatTopic', value: output });
|
|
87
|
+
},
|
|
88
|
+
params: promptSummaryTitle(messages),
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
toggleTopic: (id) => {
|
|
92
|
+
set({ activeTopicId: id });
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
updateTopicLoading: (id) => {
|
|
96
|
+
set({ topicLoadingId: id });
|
|
97
|
+
},
|
|
98
|
+
});
|