@lobehub/chat 1.0.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 (142) hide show
  1. package/.changelogrc.js +1 -0
  2. package/.commitlintrc.js +1 -0
  3. package/.editorconfig +16 -0
  4. package/.eslintignore +32 -0
  5. package/.eslintrc.js +6 -0
  6. package/.github/ISSUE_TEMPLATE/1_bug_report.yml +45 -0
  7. package/.github/ISSUE_TEMPLATE/2_feature_request.yml +21 -0
  8. package/.github/ISSUE_TEMPLATE/3_question.yml +15 -0
  9. package/.github/ISSUE_TEMPLATE/4_other.md +7 -0
  10. package/.github/PULL_REQUEST_TEMPLATE.md +17 -0
  11. package/.github/dependabot.yml +17 -0
  12. package/.github/workflows/auto-merge.yml +32 -0
  13. package/.github/workflows/contributor-help.yml +29 -0
  14. package/.github/workflows/issue-check-inactive.yml +22 -0
  15. package/.github/workflows/issue-close-require.yml +46 -0
  16. package/.github/workflows/issue-remove-inactive.yml +25 -0
  17. package/.github/workflows/release.yml +34 -0
  18. package/.github/workflows/test.yml +30 -0
  19. package/.gitpod.yml +3 -0
  20. package/.husky/commit-msg +4 -0
  21. package/.husky/pre-commit +5 -0
  22. package/.i18nrc.js +13 -0
  23. package/.prettierignore +63 -0
  24. package/.prettierrc.js +1 -0
  25. package/.releaserc.js +1 -0
  26. package/.remarkrc.js +1 -0
  27. package/.stylelintrc.js +8 -0
  28. package/CHANGELOG.md +80 -0
  29. package/README.md +147 -0
  30. package/locales/en_US/common.json +40 -0
  31. package/locales/en_US/setting.json +97 -0
  32. package/locales/zh_CN/common.json +40 -0
  33. package/locales/zh_CN/setting.json +98 -0
  34. package/next.config.mjs +32 -0
  35. package/package.json +138 -0
  36. package/public/next.svg +1 -0
  37. package/public/vercel.svg +1 -0
  38. package/scripts/genDefaultLocale.mjs +12 -0
  39. package/scripts/toc.mjs +40 -0
  40. package/src/const/fetch.ts +1 -0
  41. package/src/const/modelTokens.ts +8 -0
  42. package/src/features/FolderPanel/index.tsx +55 -0
  43. package/src/helpers/prompt.test.ts +36 -0
  44. package/src/helpers/prompt.ts +36 -0
  45. package/src/helpers/url.ts +17 -0
  46. package/src/layout/index.tsx +42 -0
  47. package/src/layout/style.ts +18 -0
  48. package/src/locales/create.ts +48 -0
  49. package/src/locales/default/common.ts +41 -0
  50. package/src/locales/default/setting.ts +97 -0
  51. package/src/locales/index.ts +5 -0
  52. package/src/locales/resources/en_US.ts +9 -0
  53. package/src/locales/resources/index.ts +7 -0
  54. package/src/locales/resources/zh_CN.ts +9 -0
  55. package/src/migrations/FromV0ToV1.ts +12 -0
  56. package/src/migrations/index.ts +13 -0
  57. package/src/pages/Sidebar.tsx +36 -0
  58. package/src/pages/_app.page.tsx +13 -0
  59. package/src/pages/_document.page.tsx +70 -0
  60. package/src/pages/api/LangChainStream.ts +95 -0
  61. package/src/pages/api/chain.api.ts +17 -0
  62. package/src/pages/api/openai.api.ts +31 -0
  63. package/src/pages/chat/SessionList/Header.tsx +56 -0
  64. package/src/pages/chat/SessionList/List/SessionItem.tsx +90 -0
  65. package/src/pages/chat/SessionList/List/index.tsx +31 -0
  66. package/src/pages/chat/SessionList/List/style.ts +77 -0
  67. package/src/pages/chat/SessionList/index.tsx +18 -0
  68. package/src/pages/chat/[id]/Config/ConfigCell.tsx +68 -0
  69. package/src/pages/chat/[id]/Config/ReadMode.tsx +63 -0
  70. package/src/pages/chat/[id]/Config/index.tsx +79 -0
  71. package/src/pages/chat/[id]/Conversation/ChatList.tsx +36 -0
  72. package/src/pages/chat/[id]/Conversation/Input.tsx +61 -0
  73. package/src/pages/chat/[id]/Conversation/index.tsx +32 -0
  74. package/src/pages/chat/[id]/Header.tsx +86 -0
  75. package/src/pages/chat/[id]/edit/AgentConfig.tsx +95 -0
  76. package/src/pages/chat/[id]/edit/AgentMeta.tsx +117 -0
  77. package/src/pages/chat/[id]/edit/FormItem.tsx +26 -0
  78. package/src/pages/chat/[id]/edit/Prompt.tsx +68 -0
  79. package/src/pages/chat/[id]/edit/index.page.tsx +62 -0
  80. package/src/pages/chat/[id]/edit/style.ts +42 -0
  81. package/src/pages/chat/[id]/index.page.tsx +40 -0
  82. package/src/pages/chat/index.page.tsx +1 -0
  83. package/src/pages/chat/layout.tsx +51 -0
  84. package/src/pages/index.page.tsx +1 -0
  85. package/src/pages/setting/Header.tsx +27 -0
  86. package/src/pages/setting/SettingForm.tsx +42 -0
  87. package/src/pages/setting/index.page.tsx +41 -0
  88. package/src/prompts/agent.ts +65 -0
  89. package/src/services/chatModel.ts +34 -0
  90. package/src/services/langChain.ts +18 -0
  91. package/src/services/url.ts +8 -0
  92. package/src/store/middleware/createHashStorage.ts +49 -0
  93. package/src/store/session/index.ts +33 -0
  94. package/src/store/session/initialState.ts +11 -0
  95. package/src/store/session/selectors.ts +3 -0
  96. package/src/store/session/slices/agentConfig/action.ts +226 -0
  97. package/src/store/session/slices/agentConfig/index.ts +3 -0
  98. package/src/store/session/slices/agentConfig/initialState.ts +34 -0
  99. package/src/store/session/slices/agentConfig/selectors.ts +54 -0
  100. package/src/store/session/slices/chat/action.ts +210 -0
  101. package/src/store/session/slices/chat/index.ts +3 -0
  102. package/src/store/session/slices/chat/initialState.ts +12 -0
  103. package/src/store/session/slices/chat/messageReducer.test.ts +70 -0
  104. package/src/store/session/slices/chat/messageReducer.ts +84 -0
  105. package/src/store/session/slices/chat/selectors.ts +83 -0
  106. package/src/store/session/slices/session/action.ts +118 -0
  107. package/src/store/session/slices/session/index.ts +3 -0
  108. package/src/store/session/slices/session/initialState.ts +31 -0
  109. package/src/store/session/slices/session/reducers/session.test.ts +456 -0
  110. package/src/store/session/slices/session/reducers/session.ts +113 -0
  111. package/src/store/session/slices/session/selectors/chat.ts +4 -0
  112. package/src/store/session/slices/session/selectors/index.ts +20 -0
  113. package/src/store/session/slices/session/selectors/list.ts +65 -0
  114. package/src/store/session/store.ts +17 -0
  115. package/src/store/settings/action.ts +31 -0
  116. package/src/store/settings/index.ts +23 -0
  117. package/src/store/settings/initialState.ts +25 -0
  118. package/src/store/settings/selectors.ts +9 -0
  119. package/src/store/settings/store.ts +13 -0
  120. package/src/styles/antdOverride.ts +29 -0
  121. package/src/styles/global.ts +23 -0
  122. package/src/styles/index.ts +6 -0
  123. package/src/types/chatMessage.ts +46 -0
  124. package/src/types/exportConfig.ts +23 -0
  125. package/src/types/global.d.ts +14 -0
  126. package/src/types/i18next.d.ts +8 -0
  127. package/src/types/langchain.ts +34 -0
  128. package/src/types/llm.ts +49 -0
  129. package/src/types/locale.ts +7 -0
  130. package/src/types/meta.ts +26 -0
  131. package/src/types/openai.ts +62 -0
  132. package/src/types/session.ts +59 -0
  133. package/src/utils/VersionController.test.ts +90 -0
  134. package/src/utils/VersionController.ts +64 -0
  135. package/src/utils/compass.ts +94 -0
  136. package/src/utils/fetch.ts +132 -0
  137. package/src/utils/filter.test.ts +120 -0
  138. package/src/utils/filter.ts +29 -0
  139. package/src/utils/uploadFIle.ts +8 -0
  140. package/src/utils/uuid.ts +9 -0
  141. package/tsconfig.json +26 -0
  142. package/vitest.config.ts +11 -0
@@ -0,0 +1,210 @@
1
+ import { StateCreator } from 'zustand/vanilla';
2
+
3
+ import { fetchChatModel } from '@/services/chatModel';
4
+ import { SessionStore, agentSelectors, chatSelectors, sessionSelectors } from '@/store/session';
5
+ import { ChatMessage } from '@/types/chatMessage';
6
+ import { FetchSSEOptions, fetchSSE } from '@/utils/fetch';
7
+ import { nanoid } from '@/utils/uuid';
8
+
9
+ import { MessageDispatch, messagesReducer } from './messageReducer';
10
+
11
+ const LOADING_FLAT = '...';
12
+
13
+ /**
14
+ * 聊天操作
15
+ */
16
+ export interface ChatAction {
17
+ /**
18
+ * 清除消息
19
+ */
20
+ clearMessage: () => void;
21
+ /**
22
+ * 创建或发送消息
23
+ * @param text - 消息文本
24
+ */
25
+ createOrSendMsg: (text: string) => Promise<void>;
26
+ /**
27
+ * 删除消息
28
+ * @param id - 消息 ID
29
+ */
30
+ deleteMessage: (id: string) => void;
31
+ /**
32
+ * 分发消息
33
+ * @param payload - 消息分发参数
34
+ */
35
+ dispatchMessage: (payload: MessageDispatch) => void;
36
+ /**
37
+ * 生成消息
38
+ * @param messages - 聊天消息数组
39
+ * @param options - 获取 SSE 选项
40
+ */
41
+ generateMessage: (messages: ChatMessage[], options: FetchSSEOptions) => Promise<void>;
42
+ /**
43
+ * 处理消息编辑
44
+ * @param messageId - 消息 ID,可选
45
+ */
46
+ handleMessageEditing: (messageId: string | undefined) => void;
47
+ /**
48
+ * 实际获取 AI 响应
49
+ *
50
+ * @param messages - 聊天消息数组
51
+ * @param parentId - 父消息 ID,可选
52
+ */
53
+ realFetchAIResponse: (messages: ChatMessage[], parentId?: string) => Promise<void>;
54
+ /**
55
+ * 重新发送消息
56
+ * @param id - 消息 ID
57
+ */
58
+ resendMessage: (id: string) => Promise<void>;
59
+ /**
60
+ * 发送消息
61
+ * @param text - 消息文本
62
+ */
63
+ sendMessage: (text: string) => Promise<void>;
64
+ }
65
+
66
+ export const createChatSlice: StateCreator<
67
+ SessionStore,
68
+ [['zustand/devtools', never]],
69
+ [],
70
+ ChatAction
71
+ > = (set, get) => ({
72
+ clearMessage: () => {
73
+ get().dispatchMessage({ type: 'resetMessages' });
74
+ },
75
+
76
+ createOrSendMsg: async (message) => {
77
+ if (!message) return;
78
+
79
+ const { sendMessage, createSession } = get();
80
+ const session = sessionSelectors.currentSession(get());
81
+
82
+ if (!session) {
83
+ await createSession();
84
+ }
85
+
86
+ sendMessage(message);
87
+ },
88
+
89
+ deleteMessage: (id) => {
90
+ get().dispatchMessage({ id, type: 'deleteMessage' });
91
+ },
92
+
93
+ dispatchMessage: (payload) => {
94
+ const { activeId } = get();
95
+ const session = sessionSelectors.currentSession(get());
96
+ if (!activeId || !session) return;
97
+
98
+ const chats = messagesReducer(session.chats, payload);
99
+
100
+ get().dispatchSession({ chats, id: activeId, type: 'updateSessionChat' });
101
+ },
102
+
103
+ generateMessage: async (messages, options) => {
104
+ set({ chatLoading: true });
105
+ const config = agentSelectors.currentAgentConfigSafe(get());
106
+
107
+ const fetcher = () => fetchChatModel({ messages, model: config.model, ...config.params });
108
+
109
+ await fetchSSE(fetcher, options);
110
+
111
+ set({ chatLoading: false });
112
+ },
113
+
114
+ handleMessageEditing: (messageId) => {
115
+ set({ editingMessageId: messageId });
116
+ },
117
+
118
+ realFetchAIResponse: async (messages: ChatMessage[], parentId?: string) => {
119
+ const { dispatchMessage, generateMessage } = get();
120
+
121
+ // 添加 systemRole
122
+ const { systemRole } = agentSelectors.currentAgentConfigSafe(get());
123
+ if (systemRole) {
124
+ messages.unshift({ content: systemRole, role: 'system' } as ChatMessage);
125
+ }
126
+
127
+ // 再添加一个空的信息用于放置 ai 响应,注意顺序不能反
128
+ // 因为如果顺序反了,messages 中将包含新增的 ai message
129
+ const assistantId = nanoid();
130
+ const userId = parentId ?? nanoid();
131
+
132
+ dispatchMessage({
133
+ id: assistantId,
134
+ message: LOADING_FLAT,
135
+ parentId: userId,
136
+ role: 'assistant',
137
+ type: 'addMessage',
138
+ });
139
+
140
+ let output = '';
141
+ // 生成 ai message
142
+ await generateMessage(messages, {
143
+ onErrorHandle: (error) => {
144
+ dispatchMessage({ id: assistantId, key: 'error', type: 'updateMessage', value: error });
145
+ },
146
+ onMessageHandle: (text) => {
147
+ output += text;
148
+
149
+ dispatchMessage({
150
+ id: assistantId,
151
+ key: 'content',
152
+ type: 'updateMessage',
153
+ value: output,
154
+ });
155
+
156
+ // 滚动到最后一条消息
157
+ const item = document.querySelector('#for-loading');
158
+ if (!item) return;
159
+
160
+ item.scrollIntoView({ behavior: 'smooth' });
161
+ },
162
+ });
163
+ },
164
+
165
+ resendMessage: async (messageId) => {
166
+ const session = sessionSelectors.currentSession(get());
167
+
168
+ if (!session) return;
169
+
170
+ // 1. 构造所有相关的历史记录
171
+ const chats = chatSelectors.currentChats(get());
172
+
173
+ const currentIndex = chats.findIndex((c) => c.id === messageId);
174
+
175
+ const histories = chats
176
+ .slice(0, currentIndex + 1)
177
+ // 如果点击重新发送的 message 其 role 是 assistant,那么需要移除
178
+ // 如果点击重新发送的 message 其 role 是 user,则不需要移除
179
+ .filter((c) => !(c.role === 'assistant' && c.id === messageId));
180
+
181
+ if (histories.length <= 0) return;
182
+
183
+ const { realFetchAIResponse } = get();
184
+
185
+ const latestMsg = histories.filter((s) => s.role === 'user').at(-1);
186
+
187
+ if (!latestMsg) return;
188
+
189
+ await realFetchAIResponse(histories, latestMsg.id);
190
+ },
191
+
192
+ sendMessage: async (message) => {
193
+ const { dispatchMessage, realFetchAIResponse, autocompleteSessionAgentMeta } = get();
194
+ const session = sessionSelectors.currentSession(get());
195
+ if (!session || !message) return;
196
+
197
+ const userId = nanoid();
198
+ dispatchMessage({ id: userId, message, role: 'user', type: 'addMessage' });
199
+
200
+ // 先拿到当前的 messages
201
+ const messages = chatSelectors.currentChats(get());
202
+
203
+ await realFetchAIResponse(messages);
204
+
205
+ const chats = chatSelectors.currentChats(get());
206
+ if (chats.length >= 4) {
207
+ autocompleteSessionAgentMeta(session.id);
208
+ }
209
+ },
210
+ });
@@ -0,0 +1,3 @@
1
+ export * from './action';
2
+ export * from './initialState';
3
+ export * from './selectors';
@@ -0,0 +1,12 @@
1
+ export interface ChatState {
2
+ chatLoading: boolean;
3
+ editingMessageId?: string;
4
+ }
5
+
6
+ export const initialChatState: ChatState = {
7
+ chatLoading: false,
8
+
9
+ // activeId: null,
10
+ // searchKeywords: '',
11
+ //
12
+ };
@@ -0,0 +1,70 @@
1
+ test('placeholder', () => {});
2
+ // describe('messagesReducer', () => {
3
+ // let initialState: ChatMessage[];
4
+ //
5
+ // beforeEach(() => {
6
+ // initialState = [
7
+ // { role: 'user', content: 'Hello!' },
8
+ // { role: 'assistant', content: 'Hi there!' },
9
+ // ];
10
+ // });
11
+ //
12
+ // it('should add a message', () => {
13
+ // const newMessage: ChatMessage = { role: 'user', content: 'How are you?' };
14
+ // const action: MessageDispatch = { type: 'addMessage', message: newMessage };
15
+ // const newState = messagesReducer(initialState, action);
16
+ // expect(newState).toEqual([...initialState, newMessage]);
17
+ // });
18
+ //
19
+ // it('should delete a message', () => {
20
+ // const action: MessageDispatch = { type: 'deleteMessage', index: 1 };
21
+ // const newState = messagesReducer(initialState, action);
22
+ // expect(newState).toEqual([{ role: 'user', content: 'Hello!' }]);
23
+ // });
24
+ //
25
+ // it('should update a message', () => {
26
+ // const action: MessageDispatch = { type: 'updateMessage', index: 1, message: 'I am fine!' };
27
+ // const newState = messagesReducer(initialState, action);
28
+ // expect(newState).toEqual([
29
+ // { role: 'user', content: 'Hello!' },
30
+ // { role: 'assistant', content: 'I am fine!' },
31
+ // ]);
32
+ // });
33
+ //
34
+ // it('should add a user message', () => {
35
+ // const action: MessageDispatch = { type: 'addUserMessage', message: 'Goodbye!' };
36
+ // const newState = messagesReducer(initialState, action);
37
+ // expect(newState).toEqual([
38
+ // { role: 'user', content: 'Hello!' },
39
+ // { role: 'assistant', content: 'Hi there!' },
40
+ // { role: 'user', content: 'Goodbye!' },
41
+ // ]);
42
+ // });
43
+ //
44
+ // it('should set error message correctly', () => {
45
+ // const action: MessageDispatch = {
46
+ // type: 'setErrorMessage',
47
+ // error: { message: 'Not Found', status: 404, type: 'chatbot' },
48
+ // index: 0,
49
+ // };
50
+ // const newState = messagesReducer(initialState, action);
51
+ // expect(newState).toEqual([
52
+ // {
53
+ // role: 'user',
54
+ // content: 'Hello!',
55
+ // error: { message: 'Not Found', status: 404, type: 'chatbot' },
56
+ // },
57
+ // { role: 'assistant', content: 'Hi there!' },
58
+ // ]);
59
+ // });
60
+ //
61
+ // it('should update the latest bot message', () => {
62
+ // const responseStream = ['I', ' am', ' a', ' bot.'];
63
+ // const action: MessageDispatch = { type: 'updateLatestBotMessage', responseStream };
64
+ // const newState = messagesReducer(initialState, action);
65
+ // expect(newState).toEqual([
66
+ // { role: 'user', content: 'Hello!' },
67
+ // { role: 'assistant', content: 'I am a bot.' },
68
+ // ]);
69
+ // });
70
+ // });
@@ -0,0 +1,84 @@
1
+ import { produce } from 'immer';
2
+
3
+ import { ChatMessage, ChatMessageMap } from '@/types/chatMessage';
4
+ import { LLMRoleType } from '@/types/llm';
5
+ import { MetaData } from '@/types/meta';
6
+ import { nanoid } from '@/utils/uuid';
7
+
8
+ interface AddMessage {
9
+ id?: string;
10
+ message: string;
11
+ meta?: MetaData;
12
+ parentId?: string;
13
+ quotaId?: string;
14
+ role: LLMRoleType;
15
+ type: 'addMessage';
16
+ }
17
+
18
+ interface DeleteMessage {
19
+ id: string;
20
+ type: 'deleteMessage';
21
+ }
22
+
23
+ interface ResetMessages {
24
+ type: 'resetMessages';
25
+ }
26
+
27
+ interface UpdateMessage {
28
+ id: string;
29
+ key: keyof ChatMessage;
30
+ type: 'updateMessage';
31
+ value: ChatMessage[keyof ChatMessage];
32
+ }
33
+
34
+ export type MessageDispatch = AddMessage | DeleteMessage | ResetMessages | UpdateMessage;
35
+
36
+ export const messagesReducer = (
37
+ state: ChatMessageMap,
38
+ payload: MessageDispatch,
39
+ ): ChatMessageMap => {
40
+ switch (payload.type) {
41
+ case 'addMessage': {
42
+ return produce(state, (draftState) => {
43
+ const mid = payload.id || nanoid();
44
+
45
+ draftState[mid] = {
46
+ content: payload.message,
47
+ createAt: Date.now(),
48
+ id: mid,
49
+ meta: payload.meta || {},
50
+ parentId: payload.parentId,
51
+ quotaId: payload.quotaId,
52
+ role: payload.role,
53
+ updateAt: Date.now(),
54
+ };
55
+ });
56
+ }
57
+
58
+ case 'deleteMessage': {
59
+ return produce(state, (draftState) => {
60
+ delete draftState[payload.id];
61
+ });
62
+ }
63
+
64
+ case 'updateMessage': {
65
+ return produce(state, (draftState) => {
66
+ const { id, key, value } = payload;
67
+ const message = draftState[id];
68
+ if (!message) return;
69
+
70
+ // @ts-ignore
71
+ message[key] = value;
72
+ message.updateAt = Date.now();
73
+ });
74
+ }
75
+
76
+ case 'resetMessages': {
77
+ return {};
78
+ }
79
+
80
+ default: {
81
+ throw new Error('暂未实现的 type,请检查 reducer');
82
+ }
83
+ }
84
+ };
@@ -0,0 +1,83 @@
1
+ import { encode } from 'gpt-tokenizer';
2
+
3
+ import { agentSelectors } from '@/store/session';
4
+ import { ChatMessage } from '@/types/chatMessage';
5
+
6
+ import type { SessionStore } from '../../store';
7
+ import { sessionSelectors } from '../session';
8
+
9
+ // 展示在聊天框中的消息
10
+ const currentChats = (s: SessionStore): ChatMessage[] => {
11
+ const session = sessionSelectors.currentSession(s);
12
+ if (!session) return [];
13
+
14
+ const basic = Object.values<ChatMessage>(session.chats)
15
+ // 首先按照时间顺序排序,越早的在越前面
16
+ .sort((pre, next) => pre.createAt - next.createAt)
17
+ // 过滤掉已归档的消息,归档消息不应该出现在聊天框中
18
+ .filter((m) => !m.archive)
19
+ // 映射头像关系
20
+ .map((m) => {
21
+ return {
22
+ ...m,
23
+ meta:
24
+ m.role === 'assistant'
25
+ ? {
26
+ avatar: agentSelectors.currentAgentAvatar(s),
27
+ title: session.meta.title,
28
+ }
29
+ : m.meta,
30
+ };
31
+ });
32
+
33
+ const finalList: ChatMessage[] = [];
34
+
35
+ const addItem = (item: ChatMessage) => {
36
+ const isExist = finalList.findIndex((i) => item.id === i.id) > -1;
37
+ if (!isExist) {
38
+ finalList.push(item);
39
+ }
40
+ };
41
+
42
+ for (const item of basic) {
43
+ // 先判存在与否,不存在就加入
44
+ addItem(item);
45
+
46
+ for (const another of basic) {
47
+ if (another.parentId === item.id) {
48
+ addItem(another);
49
+ }
50
+ }
51
+ }
52
+
53
+ return finalList;
54
+ };
55
+
56
+ const systemRoleSel = (s: SessionStore): string => {
57
+ const config = agentSelectors.currentAgentConfigSafe(s);
58
+
59
+ return config.systemRole;
60
+ };
61
+
62
+ const totalTokens = (s: SessionStore): number[] => {
63
+ const chats = currentChats(s);
64
+ return encode(chats.map((m) => m.content).join(''));
65
+ };
66
+
67
+ const systemRoleTokens = (s: SessionStore): number[] => {
68
+ const systemRole = systemRoleSel(s);
69
+
70
+ return encode(systemRole || '');
71
+ };
72
+
73
+ const totalTokenCount = (s: SessionStore) => totalTokens(s).length;
74
+
75
+ const systemRoleTokenCount = (s: SessionStore) => systemRoleTokens(s).length;
76
+
77
+ export const chatSelectors = {
78
+ currentChats,
79
+ systemRoleTokenCount,
80
+ systemRoleTokens,
81
+ totalTokenCount,
82
+ totalTokens,
83
+ };
@@ -0,0 +1,118 @@
1
+ import Router from 'next/router';
2
+ import { StateCreator } from 'zustand/vanilla';
3
+
4
+ import { SessionStore } from '@/store/session';
5
+ import { LanguageModel } from '@/types/llm';
6
+ import { LobeAgentSession, LobeSessionType } from '@/types/session';
7
+ import { uuid } from '@/utils/uuid';
8
+
9
+ import { SessionDispatch, sessionsReducer } from './reducers/session';
10
+
11
+ export interface SessionAction {
12
+ activeSession: (sessionId: string) => void;
13
+ /**
14
+ * @title 添加会话
15
+ * @param session - 会话信息
16
+ * @returns void
17
+ */
18
+ createSession: () => Promise<string>;
19
+
20
+ /**
21
+ * 分发聊天记录
22
+ * @param payload - 聊天记录
23
+ */
24
+ dispatchSession: (payload: SessionDispatch) => void;
25
+ /**
26
+ * @title 删除会话
27
+ * @param index - 会话索引
28
+ * @returns void
29
+ */
30
+ removeSession: (sessionId: string) => void;
31
+
32
+ /**
33
+ * @title 切换会话
34
+ * @param sessionId - 会话索引
35
+ * @returns void
36
+ */
37
+ switchSession: (sessionId?: string | 'new') => Promise<void>;
38
+
39
+ /**
40
+ * 生成压缩后的消息
41
+ * @returns 压缩后的消息
42
+ */
43
+ // genShareUrl: () => string;
44
+ }
45
+
46
+ export const createSessionSlice: StateCreator<
47
+ SessionStore,
48
+ [['zustand/devtools', never]],
49
+ [],
50
+ SessionAction
51
+ > = (set, get) => ({
52
+ activeSession: (sessionId) => {
53
+ set({ activeId: sessionId });
54
+ },
55
+
56
+ createSession: async () => {
57
+ const { dispatchSession, switchSession } = get();
58
+
59
+ const timestamp = Date.now();
60
+
61
+ const newSession: LobeAgentSession = {
62
+ chats: {},
63
+ config: {
64
+ model: LanguageModel.GPT3_5,
65
+ params: {
66
+ temperature: 0.6,
67
+ },
68
+ systemRole: '',
69
+ },
70
+ createAt: timestamp,
71
+ id: uuid(),
72
+ meta: {},
73
+ type: LobeSessionType.Agent,
74
+ updateAt: timestamp,
75
+ };
76
+
77
+ dispatchSession({ session: newSession, type: 'addSession' });
78
+
79
+ await switchSession(newSession.id);
80
+
81
+ return newSession.id;
82
+ },
83
+
84
+ dispatchSession: (payload) => {
85
+ const { type, ...res } = payload;
86
+ set({ sessions: sessionsReducer(get().sessions, payload) }, false, {
87
+ payload: res,
88
+ type: `dispatchChat/${type}`,
89
+ });
90
+ },
91
+
92
+ removeSession: (sessionId) => {
93
+ get().dispatchSession({ id: sessionId, type: 'removeSession' });
94
+
95
+ if (sessionId === get().activeId) {
96
+ Router.push('/');
97
+ }
98
+ },
99
+
100
+ switchSession: async (sessionId) => {
101
+ if (get().activeId === sessionId) return;
102
+
103
+ if (sessionId) {
104
+ get().activeSession(sessionId);
105
+ }
106
+
107
+ // 新会话
108
+ await Router.push(`/chat/${sessionId}`);
109
+ },
110
+
111
+ // genShareUrl: () => {
112
+ // const session = sessionSelectors.currentSession(get());
113
+ // if (!session) return '';
114
+ //
115
+ // const agent = session.config;
116
+ // return genShareMessagesUrl(session.chats, agent.systemRole);
117
+ // },
118
+ });
@@ -0,0 +1,3 @@
1
+ export * from './action';
2
+ export * from './initialState';
3
+ export * from './selectors';
@@ -0,0 +1,31 @@
1
+ import { LobeAgentSession, LobeSessionType } from '@/types/session';
2
+
3
+ import { initialLobeAgentConfig } from '../agentConfig';
4
+
5
+ export interface SessionState {
6
+ /**
7
+ * @title 当前活动的会话
8
+ * @description 当前正在编辑或查看的会话
9
+ * @default null
10
+ */
11
+ activeId: string | null;
12
+ searchKeywords: string;
13
+ sessions: Record<string, LobeAgentSession>;
14
+ }
15
+
16
+ export const initLobeSession: LobeAgentSession = {
17
+ chats: {},
18
+ config: initialLobeAgentConfig,
19
+ createAt: Date.now(),
20
+ id: '',
21
+ meta: {},
22
+ type: LobeSessionType.Agent,
23
+ updateAt: Date.now(),
24
+ };
25
+
26
+ export const initialSessionState: SessionState = {
27
+ activeId: null,
28
+
29
+ searchKeywords: '',
30
+ sessions: {},
31
+ };