@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.
- package/.changelogrc.js +1 -0
- package/.commitlintrc.js +1 -0
- package/.editorconfig +16 -0
- package/.eslintignore +32 -0
- package/.eslintrc.js +6 -0
- package/.github/ISSUE_TEMPLATE/1_bug_report.yml +45 -0
- package/.github/ISSUE_TEMPLATE/2_feature_request.yml +21 -0
- package/.github/ISSUE_TEMPLATE/3_question.yml +15 -0
- package/.github/ISSUE_TEMPLATE/4_other.md +7 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +17 -0
- package/.github/dependabot.yml +17 -0
- package/.github/workflows/auto-merge.yml +32 -0
- package/.github/workflows/contributor-help.yml +29 -0
- package/.github/workflows/issue-check-inactive.yml +22 -0
- package/.github/workflows/issue-close-require.yml +46 -0
- package/.github/workflows/issue-remove-inactive.yml +25 -0
- package/.github/workflows/release.yml +34 -0
- package/.github/workflows/test.yml +30 -0
- package/.gitpod.yml +3 -0
- package/.husky/commit-msg +4 -0
- package/.husky/pre-commit +5 -0
- package/.i18nrc.js +13 -0
- package/.prettierignore +63 -0
- package/.prettierrc.js +1 -0
- package/.releaserc.js +1 -0
- package/.remarkrc.js +1 -0
- package/.stylelintrc.js +8 -0
- package/CHANGELOG.md +80 -0
- package/README.md +147 -0
- package/locales/en_US/common.json +40 -0
- package/locales/en_US/setting.json +97 -0
- package/locales/zh_CN/common.json +40 -0
- package/locales/zh_CN/setting.json +98 -0
- package/next.config.mjs +32 -0
- package/package.json +138 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/scripts/genDefaultLocale.mjs +12 -0
- package/scripts/toc.mjs +40 -0
- package/src/const/fetch.ts +1 -0
- package/src/const/modelTokens.ts +8 -0
- package/src/features/FolderPanel/index.tsx +55 -0
- package/src/helpers/prompt.test.ts +36 -0
- package/src/helpers/prompt.ts +36 -0
- package/src/helpers/url.ts +17 -0
- package/src/layout/index.tsx +42 -0
- package/src/layout/style.ts +18 -0
- package/src/locales/create.ts +48 -0
- package/src/locales/default/common.ts +41 -0
- package/src/locales/default/setting.ts +97 -0
- package/src/locales/index.ts +5 -0
- package/src/locales/resources/en_US.ts +9 -0
- package/src/locales/resources/index.ts +7 -0
- package/src/locales/resources/zh_CN.ts +9 -0
- package/src/migrations/FromV0ToV1.ts +12 -0
- package/src/migrations/index.ts +13 -0
- package/src/pages/Sidebar.tsx +36 -0
- package/src/pages/_app.page.tsx +13 -0
- package/src/pages/_document.page.tsx +70 -0
- package/src/pages/api/LangChainStream.ts +95 -0
- package/src/pages/api/chain.api.ts +17 -0
- package/src/pages/api/openai.api.ts +31 -0
- package/src/pages/chat/SessionList/Header.tsx +56 -0
- package/src/pages/chat/SessionList/List/SessionItem.tsx +90 -0
- package/src/pages/chat/SessionList/List/index.tsx +31 -0
- package/src/pages/chat/SessionList/List/style.ts +77 -0
- package/src/pages/chat/SessionList/index.tsx +18 -0
- package/src/pages/chat/[id]/Config/ConfigCell.tsx +68 -0
- package/src/pages/chat/[id]/Config/ReadMode.tsx +63 -0
- package/src/pages/chat/[id]/Config/index.tsx +79 -0
- package/src/pages/chat/[id]/Conversation/ChatList.tsx +36 -0
- package/src/pages/chat/[id]/Conversation/Input.tsx +61 -0
- package/src/pages/chat/[id]/Conversation/index.tsx +32 -0
- package/src/pages/chat/[id]/Header.tsx +86 -0
- package/src/pages/chat/[id]/edit/AgentConfig.tsx +95 -0
- package/src/pages/chat/[id]/edit/AgentMeta.tsx +117 -0
- package/src/pages/chat/[id]/edit/FormItem.tsx +26 -0
- package/src/pages/chat/[id]/edit/Prompt.tsx +68 -0
- package/src/pages/chat/[id]/edit/index.page.tsx +62 -0
- package/src/pages/chat/[id]/edit/style.ts +42 -0
- package/src/pages/chat/[id]/index.page.tsx +40 -0
- package/src/pages/chat/index.page.tsx +1 -0
- package/src/pages/chat/layout.tsx +51 -0
- package/src/pages/index.page.tsx +1 -0
- package/src/pages/setting/Header.tsx +27 -0
- package/src/pages/setting/SettingForm.tsx +42 -0
- package/src/pages/setting/index.page.tsx +41 -0
- package/src/prompts/agent.ts +65 -0
- package/src/services/chatModel.ts +34 -0
- package/src/services/langChain.ts +18 -0
- package/src/services/url.ts +8 -0
- package/src/store/middleware/createHashStorage.ts +49 -0
- package/src/store/session/index.ts +33 -0
- package/src/store/session/initialState.ts +11 -0
- package/src/store/session/selectors.ts +3 -0
- package/src/store/session/slices/agentConfig/action.ts +226 -0
- package/src/store/session/slices/agentConfig/index.ts +3 -0
- package/src/store/session/slices/agentConfig/initialState.ts +34 -0
- package/src/store/session/slices/agentConfig/selectors.ts +54 -0
- package/src/store/session/slices/chat/action.ts +210 -0
- package/src/store/session/slices/chat/index.ts +3 -0
- package/src/store/session/slices/chat/initialState.ts +12 -0
- package/src/store/session/slices/chat/messageReducer.test.ts +70 -0
- package/src/store/session/slices/chat/messageReducer.ts +84 -0
- package/src/store/session/slices/chat/selectors.ts +83 -0
- package/src/store/session/slices/session/action.ts +118 -0
- package/src/store/session/slices/session/index.ts +3 -0
- package/src/store/session/slices/session/initialState.ts +31 -0
- package/src/store/session/slices/session/reducers/session.test.ts +456 -0
- package/src/store/session/slices/session/reducers/session.ts +113 -0
- package/src/store/session/slices/session/selectors/chat.ts +4 -0
- package/src/store/session/slices/session/selectors/index.ts +20 -0
- package/src/store/session/slices/session/selectors/list.ts +65 -0
- package/src/store/session/store.ts +17 -0
- package/src/store/settings/action.ts +31 -0
- package/src/store/settings/index.ts +23 -0
- package/src/store/settings/initialState.ts +25 -0
- package/src/store/settings/selectors.ts +9 -0
- package/src/store/settings/store.ts +13 -0
- package/src/styles/antdOverride.ts +29 -0
- package/src/styles/global.ts +23 -0
- package/src/styles/index.ts +6 -0
- package/src/types/chatMessage.ts +46 -0
- package/src/types/exportConfig.ts +23 -0
- package/src/types/global.d.ts +14 -0
- package/src/types/i18next.d.ts +8 -0
- package/src/types/langchain.ts +34 -0
- package/src/types/llm.ts +49 -0
- package/src/types/locale.ts +7 -0
- package/src/types/meta.ts +26 -0
- package/src/types/openai.ts +62 -0
- package/src/types/session.ts +59 -0
- package/src/utils/VersionController.test.ts +90 -0
- package/src/utils/VersionController.ts +64 -0
- package/src/utils/compass.ts +94 -0
- package/src/utils/fetch.ts +132 -0
- package/src/utils/filter.test.ts +120 -0
- package/src/utils/filter.ts +29 -0
- package/src/utils/uploadFIle.ts +8 -0
- package/src/utils/uuid.ts +9 -0
- package/tsconfig.json +26 -0
- 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,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,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
|
+
};
|