@lobehub/chat 0.19.0 → 0.21.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/package.json +1 -1
  3. package/src/{features → components}/SliderWithInput/index.tsx +1 -0
  4. package/src/locales/default/common.ts +2 -2
  5. package/src/locales/default/setting.ts +1 -0
  6. package/src/pages/chat/[id]/Config/SideBar.tsx +9 -5
  7. package/src/pages/chat/[id]/Config/Topic.tsx +27 -0
  8. package/src/pages/chat/[id]/Config/TopicItem.tsx +80 -0
  9. package/src/pages/chat/[id]/Conversation/Input/Action.tsx +66 -7
  10. package/src/pages/chat/[id]/Conversation/Input/index.tsx +5 -2
  11. package/src/pages/chat/[id]/Header.tsx +14 -10
  12. package/src/pages/chat/[id]/edit/AgentConfig/index.tsx +1 -1
  13. package/src/pages/chat/layout.tsx +10 -4
  14. package/src/pages/setting/SettingForm.tsx +1 -1
  15. package/src/prompts/chat.ts +19 -0
  16. package/src/store/session/selectors.ts +1 -1
  17. package/src/store/session/slices/agentConfig/selectors.ts +4 -0
  18. package/src/store/session/slices/chat/actions/index.ts +21 -0
  19. package/src/store/session/slices/chat/{action.ts → actions/message.ts} +33 -23
  20. package/src/store/session/slices/chat/actions/topic.ts +98 -0
  21. package/src/store/session/slices/chat/index.ts +1 -1
  22. package/src/store/session/slices/chat/initialState.ts +2 -5
  23. package/src/store/session/slices/chat/reducers/message.test.ts +92 -0
  24. package/src/store/session/slices/chat/reducers/message.ts +16 -1
  25. package/src/store/session/slices/chat/reducers/topic.test.ts +179 -0
  26. package/src/store/session/slices/chat/reducers/topic.ts +59 -0
  27. package/src/store/session/slices/chat/selectors/chat.ts +8 -4
  28. package/src/store/session/slices/chat/selectors/index.ts +5 -0
  29. package/src/store/session/slices/chat/selectors/topic.ts +16 -0
  30. package/src/store/session/slices/chat/selectors/utils.test.ts +81 -1
  31. package/src/store/session/slices/chat/selectors/utils.ts +8 -2
  32. package/src/store/session/slices/session/reducers/session.ts +25 -1
  33. package/src/types/topic.ts +0 -1
  34. package/src/pages/chat/[id]/Config/ConfigCell.tsx +0 -68
package/CHANGELOG.md CHANGED
@@ -2,6 +2,64 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.21.0](https://github.com/lobehub/lobe-chat/compare/v0.20.0...v0.21.0)
6
+
7
+ <sup>Released on **2023-07-25**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Move component folder.
12
+
13
+ #### ✨ Features
14
+
15
+ - **misc**: 支持快捷配置模型、温度.
16
+
17
+ <br/>
18
+
19
+ <details>
20
+ <summary><kbd>Improvements and Fixes</kbd></summary>
21
+
22
+ #### Code refactoring
23
+
24
+ - **misc**: Move component folder ([fb85d16](https://github.com/lobehub/lobe-chat/commit/fb85d16))
25
+
26
+ #### What's improved
27
+
28
+ - **misc**: 支持快捷配置模型、温度, closes [#19](https://github.com/lobehub/lobe-chat/issues/19) ([31daee1](https://github.com/lobehub/lobe-chat/commit/31daee1))
29
+
30
+ </details>
31
+
32
+ <div align="right">
33
+
34
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
35
+
36
+ </div>
37
+
38
+ ## [Version 0.20.0](https://github.com/lobehub/lobe-chat/compare/v0.19.0...v0.20.0)
39
+
40
+ <sup>Released on **2023-07-25**</sup>
41
+
42
+ #### ✨ Features
43
+
44
+ - **misc**: 实现话题模块.
45
+
46
+ <br/>
47
+
48
+ <details>
49
+ <summary><kbd>Improvements and Fixes</kbd></summary>
50
+
51
+ #### What's improved
52
+
53
+ - **misc**: 实现话题模块, closes [#16](https://github.com/lobehub/lobe-chat/issues/16) ([64fd6ee](https://github.com/lobehub/lobe-chat/commit/64fd6ee))
54
+
55
+ </details>
56
+
57
+ <div align="right">
58
+
59
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
60
+
61
+ </div>
62
+
5
63
  ## [Version 0.19.0](https://github.com/lobehub/lobe-chat/compare/v0.18.2...v0.19.0)
6
64
 
7
65
  <sup>Released on **2023-07-24**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -20,6 +20,7 @@ const SliderWithInput = memo<SliderSingleProps>(
20
20
  onChange={handleOnchange}
21
21
  step={step}
22
22
  style={{ flex: 1 }}
23
+ tooltip={{ open: false }}
23
24
  value={typeof value === 'number' ? value : 0}
24
25
  {...props}
25
26
  />
@@ -27,8 +27,8 @@ export default {
27
27
  share: '分享',
28
28
  tokenDetail: '系统设定: {{systemRoleToken}} 历史消息: {{chatsToken}}',
29
29
  topic: {
30
- saveCurrentMessages: '保存当前对话为话题',
31
- searchPlaceholder: '搜索归档对话...',
30
+ saveCurrentMessages: '将当前会话保存为话题',
31
+ searchPlaceholder: '搜索话题...',
32
32
  },
33
33
  updateAgent: '更新助理信息',
34
34
  updatePrompt: '更新提示词',
@@ -102,6 +102,7 @@ export default {
102
102
  temperature: {
103
103
  desc: '值越大,回复越随机',
104
104
  title: '随机性',
105
+ titleWithValue: '随机性 {{value}}',
105
106
  },
106
107
  title: '模型设置',
107
108
  topP: {
@@ -1,11 +1,12 @@
1
1
  import { ActionIcon, DraggablePanelBody, EditableMessage, SearchBar } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
- import { ChevronRight } from 'lucide-react';
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((s) => [s.updateAgentConfig], shallow);
46
- const systemRole = useSessionStore(agentSelectors.currentAgentSystemRole, shallow);
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={ChevronRight}
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 }}>
82
+ <Flexbox gap={12} style={{ padding: 16 }}>
80
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,21 +1,80 @@
1
1
  import { ActionIcon } from '@lobehub/ui';
2
- import { Popconfirm } from 'antd';
3
- import { BrainCog, Eraser, Thermometer, Timer } from 'lucide-react';
2
+ import { Dropdown, Popconfirm, Popover } from 'antd';
3
+ import { BrainCog, Eraser, Thermometer } from 'lucide-react';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { shallow } from 'zustand/shallow';
7
7
 
8
- import { useSessionStore } from '@/store/session';
8
+ import SliderWithInput from '@/components/SliderWithInput';
9
+ import { agentSelectors, useSessionStore } from '@/store/session';
10
+ import { LanguageModel } from '@/types/llm';
9
11
 
10
12
  const InputActions = memo(() => {
11
13
  const { t } = useTranslation('setting');
12
- const [clearMessage] = useSessionStore((s) => [s.clearMessage], shallow);
14
+ const [clearMessage, updateAgentConfig] = useSessionStore(
15
+ (s) => [s.clearMessage, s.updateAgentConfig],
16
+ shallow,
17
+ );
18
+ const [model, temperature] = useSessionStore((s) => {
19
+ const config = agentSelectors.currentAgentConfigSafe(s);
20
+ return [
21
+ config.model,
22
+ config.params.temperature,
23
+ // config.historyCount
24
+ ];
25
+ }, shallow);
13
26
 
14
27
  return (
15
28
  <>
16
- <ActionIcon icon={BrainCog} title={t('settingModel.model.title')} />
17
- <ActionIcon icon={Thermometer} title={t('settingModel.temperature.title')} />
18
- <ActionIcon icon={Timer} title={t('settingChat.historyCount.title')} />
29
+ <Dropdown
30
+ menu={{
31
+ activeKey: model,
32
+ items: Object.values(LanguageModel).map((i) => ({ key: i, label: i })),
33
+ onClick: (e) => {
34
+ updateAgentConfig({ model: e.key as LanguageModel });
35
+ },
36
+ }}
37
+ trigger={['click']}
38
+ >
39
+ <ActionIcon icon={BrainCog} title={t('settingModel.model.title')} />
40
+ </Dropdown>
41
+ <Popover
42
+ content={
43
+ <SliderWithInput
44
+ max={1}
45
+ min={0}
46
+ onChange={(v) => {
47
+ updateAgentConfig({ params: { temperature: v } });
48
+ }}
49
+ step={0.1}
50
+ style={{ width: 120 }}
51
+ value={temperature}
52
+ />
53
+ }
54
+ trigger={'click'}
55
+ >
56
+ <ActionIcon
57
+ icon={Thermometer}
58
+ title={t('settingModel.temperature.titleWithValue', { value: temperature })}
59
+ />
60
+ </Popover>
61
+ {/*TODO 历史记录功能实现 */}
62
+ {/*<Popover*/}
63
+ {/* content={*/}
64
+ {/* <SliderWithInput*/}
65
+ {/* min={0}*/}
66
+ {/* onChange={(v) => {*/}
67
+ {/* updateAgentConfig({ historyCount: v });*/}
68
+ {/* }}*/}
69
+ {/* step={1}*/}
70
+ {/* style={{ width: 120 }}*/}
71
+ {/* value={historyCount}*/}
72
+ {/* />*/}
73
+ {/* }*/}
74
+ {/* trigger={'click'}*/}
75
+ {/*>*/}
76
+ {/* <ActionIcon icon={Timer} title={t('settingChat.historyCount.title')} />*/}
77
+ {/*</Popover>*/}
19
78
  <Popconfirm
20
79
  cancelText={t('cancel', { ns: 'common' })}
21
80
  okButtonProps={{ danger: true }}
@@ -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((s) => [s.createOrSendMsg], shallow);
21
+ const [sendMessage, saveToTopic] = useSessionStore(
22
+ (s) => [s.createOrSendMsg, s.saveToTopic],
23
+ shallow,
24
+ );
22
25
 
23
26
  const footer = useMemo(
24
27
  () => (
25
28
  <Tooltip title={t('topic.saveCurrentMessages')}>
26
- <Button icon={<Icon icon={LucideGalleryVerticalEnd} />} />
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 [meta, id, modle] = useSessionStore(
16
- (s) => [agentSelectors.currentAgentMeta(s), s.activeId, agentSelectors.currentAgentModel(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={meta?.avatar}
30
- background={meta?.backgroundColor}
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={meta?.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={
@@ -6,10 +6,10 @@ import { debounce } from 'lodash-es';
6
6
  import { BrainCog, MessagesSquare } from 'lucide-react';
7
7
  import { useMemo } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
+ import SliderWithInput from 'src/components/SliderWithInput';
9
10
  import { shallow } from 'zustand/shallow';
10
11
 
11
12
  import { FORM_STYLE } from '@/const/layoutTokens';
12
- import SliderWithInput from '@/features/SliderWithInput';
13
13
  import { agentSelectors, useSessionStore } from '@/store/session';
14
14
  import { LanguageModel } from '@/types/llm';
15
15
  import type { LobeAgentConfig } from '@/types/session';
@@ -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 (typeof id === 'string' && hasRehydrated) {
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(() => {
@@ -5,11 +5,11 @@ import { debounce } from 'lodash-es';
5
5
  import { AppWindow, BrainCog, MessagesSquare, Palette, Webhook } from 'lucide-react';
6
6
  import { memo, useCallback, useMemo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
+ import SliderWithInput from 'src/components/SliderWithInput';
8
9
  import { shallow } from 'zustand/shallow';
9
10
 
10
11
  import { FORM_STYLE } from '@/const/layoutTokens';
11
12
  import AvatarWithUpload from '@/features/AvatarWithUpload';
12
- import SliderWithInput from '@/features/SliderWithInput';
13
13
  import { options } from '@/locales/options';
14
14
  import { settingsSelectors, useSettings } from '@/store/settings';
15
15
  import { DEFAULT_SETTINGS } from '@/store/settings/initialState';
@@ -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
+ });
@@ -1,3 +1,3 @@
1
1
  export { agentSelectors } from './slices/agentConfig';
2
- export { chatSelectors } from './slices/chat';
2
+ export { chatSelectors, topicSelectors } from './slices/chat';
3
3
  export { sessionSelectors } from './slices/session';
@@ -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 './reducers/message';
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 ChatAction {
17
+ export interface ChatMessageAction {
18
18
  /**
19
19
  * 清除消息
20
20
  */
@@ -57,6 +57,7 @@ export interface ChatAction {
57
57
  * @param id - 消息 ID
58
58
  */
59
59
  resendMessage: (id: string) => Promise<void>;
60
+
60
61
  /**
61
62
  * 发送消息
62
63
  * @param text - 消息文本
@@ -64,14 +65,20 @@ export interface ChatAction {
64
65
  sendMessage: (text: string) => Promise<void>;
65
66
  }
66
67
 
67
- export const createChatSlice: StateCreator<
68
+ export const chatMessage: StateCreator<
68
69
  SessionStore,
69
70
  [['zustand/devtools', never]],
70
71
  [],
71
- ChatAction
72
+ ChatMessageAction
72
73
  > = (set, get) => ({
73
74
  clearMessage: () => {
74
- get().dispatchMessage({ type: 'resetMessages' });
75
+ const { dispatchMessage, activeTopicId, dispatchTopic } = get();
76
+
77
+ dispatchMessage({ topicId: activeTopicId, type: 'resetMessages' });
78
+
79
+ if (activeTopicId) {
80
+ dispatchTopic({ id: activeTopicId, type: 'deleteChatTopic' });
81
+ }
75
82
  },
76
83
 
77
84
  createOrSendMsg: async (message) => {
@@ -155,7 +162,7 @@ export const createChatSlice: StateCreator<
155
162
  },
156
163
 
157
164
  realFetchAIResponse: async (messages, userMessageId) => {
158
- const { dispatchMessage, generateMessage } = get();
165
+ const { dispatchMessage, generateMessage, activeTopicId } = get();
159
166
 
160
167
  // 添加 systemRole
161
168
  const { systemRole, model } = agentSelectors.currentAgentConfigSafe(get());
@@ -165,49 +172,46 @@ export const createChatSlice: StateCreator<
165
172
 
166
173
  // 再添加一个空的信息用于放置 ai 响应,注意顺序不能反
167
174
  // 因为如果顺序反了,messages 中将包含新增的 ai message
168
- const assistantId = nanoid();
175
+ const mid = nanoid();
169
176
 
170
177
  dispatchMessage({
171
- id: assistantId,
178
+ id: mid,
172
179
  message: LOADING_FLAT,
173
180
  parentId: userMessageId,
174
181
  role: 'assistant',
175
182
  type: 'addMessage',
176
183
  });
177
184
 
185
+ // 如果有 activeTopicId,则添加 topicId
186
+ if (activeTopicId) {
187
+ dispatchMessage({ id: mid, key: 'topicId', type: 'updateMessage', value: activeTopicId });
188
+ }
189
+
178
190
  // 为模型添加 fromModel 的额外信息
179
- dispatchMessage({
180
- id: assistantId,
181
- key: 'fromModel',
182
- type: 'updateMessageExtra',
183
- value: model,
184
- });
191
+ dispatchMessage({ id: mid, key: 'fromModel', type: 'updateMessageExtra', value: model });
185
192
 
186
193
  // 生成 ai message
187
- const { output, isFunctionCall } = await generateMessage(messages, assistantId);
194
+ const { output, isFunctionCall } = await generateMessage(messages, mid);
188
195
 
189
196
  // 如果是 function,则发送函数调用方法
190
197
  if (isFunctionCall) {
191
198
  const { function_call } = JSON.parse(output);
192
199
 
193
200
  dispatchMessage({
194
- id: assistantId,
201
+ id: mid,
195
202
  key: 'function_call',
196
203
  type: 'updateMessage',
197
204
  value: function_call,
198
205
  });
199
206
 
200
207
  await generateMessage(
201
- [
202
- ...messages,
203
- { content: '', function_call, id: assistantId, role: 'assistant' } as ChatMessage,
204
- ],
205
- assistantId,
208
+ [...messages, { content: '', function_call, id: mid, role: 'assistant' } as ChatMessage],
209
+ mid,
206
210
  true,
207
211
  );
208
212
 
209
213
  dispatchMessage({
210
- id: assistantId,
214
+ id: mid,
211
215
  key: 'role',
212
216
  type: 'updateMessage',
213
217
  value: 'assistant',
@@ -243,13 +247,19 @@ export const createChatSlice: StateCreator<
243
247
  },
244
248
 
245
249
  sendMessage: async (message) => {
246
- const { dispatchMessage, realFetchAIResponse, autocompleteSessionAgentMeta } = get();
250
+ const { dispatchMessage, realFetchAIResponse, autocompleteSessionAgentMeta, activeTopicId } =
251
+ get();
247
252
  const session = sessionSelectors.currentSession(get());
248
253
  if (!session || !message) return;
249
254
 
250
255
  const userId = nanoid();
251
256
  dispatchMessage({ id: userId, message, role: 'user', type: 'addMessage' });
252
257
 
258
+ // 如果有 activeTopicId,则添加 topicId
259
+ if (activeTopicId) {
260
+ dispatchMessage({ id: userId, key: 'topicId', type: 'updateMessage', value: activeTopicId });
261
+ }
262
+
253
263
  // 先拿到当前的 messages
254
264
  const messages = chatSelectors.currentChats(get());
255
265