@lobehub/chat 1.36.30 → 1.36.32

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 (71) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/docs/self-hosting/environment-variables/model-provider.mdx +7 -0
  4. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +7 -0
  5. package/docs/self-hosting/server-database/dokploy.zh-CN.mdx +12 -12
  6. package/package.json +1 -1
  7. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/Content.tsx +3 -9
  8. package/src/app/(main)/chat/(workspace)/@conversation/features/ThreadHydration.tsx +2 -4
  9. package/src/app/(main)/chat/@session/features/SessionListContent/DefaultMode.tsx +2 -5
  10. package/src/app/(main)/discover/(detail)/plugin/[slug]/features/InstallPlugin.tsx +10 -15
  11. package/src/database/repositories/dataImporter/__tests__/index.test.ts +11 -18
  12. package/src/database/repositories/dataImporter/index.ts +31 -46
  13. package/src/database/server/models/__tests__/_test_template.ts +1 -1
  14. package/src/database/server/models/__tests__/agent.test.ts +1 -1
  15. package/src/database/server/models/__tests__/asyncTask.test.ts +1 -1
  16. package/src/database/server/models/__tests__/chunk.test.ts +1 -1
  17. package/src/database/server/models/__tests__/file.test.ts +1 -1
  18. package/src/database/server/models/__tests__/knowledgeBase.test.ts +1 -2
  19. package/src/database/server/models/__tests__/message.test.ts +35 -72
  20. package/src/database/server/models/__tests__/nextauth.test.ts +1 -1
  21. package/src/database/server/models/__tests__/session.test.ts +1 -1
  22. package/src/database/server/models/__tests__/sessionGroup.test.ts +1 -2
  23. package/src/database/server/models/__tests__/topic.test.ts +1 -1
  24. package/src/database/server/models/__tests__/user.test.ts +1 -1
  25. package/src/database/server/models/_template.ts +2 -2
  26. package/src/database/server/models/agent.ts +17 -25
  27. package/src/database/server/models/asyncTask.ts +2 -2
  28. package/src/database/server/models/chunk.ts +14 -14
  29. package/src/database/server/models/embedding.ts +1 -1
  30. package/src/database/server/models/file.ts +8 -10
  31. package/src/database/server/models/knowledgeBase.ts +4 -6
  32. package/src/database/server/models/message.ts +54 -65
  33. package/src/database/server/models/plugin.ts +6 -2
  34. package/src/database/server/models/ragEval/dataset.ts +2 -2
  35. package/src/database/server/models/ragEval/datasetRecord.ts +3 -8
  36. package/src/database/server/models/ragEval/evaluation.ts +3 -2
  37. package/src/database/server/models/ragEval/evaluationRecord.ts +2 -2
  38. package/src/database/server/models/session.ts +40 -35
  39. package/src/database/server/models/sessionGroup.ts +4 -4
  40. package/src/database/server/models/thread.ts +2 -2
  41. package/src/database/server/models/topic.ts +48 -53
  42. package/src/database/server/models/user.ts +12 -12
  43. package/src/features/AgentSetting/AgentPlugin/index.tsx +1 -1
  44. package/src/features/ChatInput/ActionBar/Tools/Dropdown.tsx +4 -4
  45. package/src/features/Portal/Thread/Chat/ChatList.tsx +1 -2
  46. package/src/hooks/useCheckPluginsIsInstalled.ts +10 -0
  47. package/src/hooks/useFetchInstalledPlugins.ts +10 -0
  48. package/src/hooks/useFetchMessages.ts +15 -0
  49. package/src/hooks/useFetchSessions.ts +13 -0
  50. package/src/hooks/useFetchThreads.ts +11 -0
  51. package/src/hooks/useFetchTopics.ts +6 -6
  52. package/src/layout/GlobalProvider/StoreInitialization.tsx +3 -1
  53. package/src/libs/agent-runtime/utils/streams/azureOpenai.test.ts +0 -1
  54. package/src/libs/next-auth/adapter/index.ts +1 -1
  55. package/src/server/routers/lambda/chunk.ts +2 -2
  56. package/src/services/user/client.ts +2 -2
  57. package/src/store/agent/slices/chat/action.test.ts +21 -10
  58. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +10 -0
  59. package/src/store/chat/slices/builtinTool/action.ts +0 -1
  60. package/src/store/chat/slices/message/action.test.ts +3 -1
  61. package/src/store/chat/slices/message/action.ts +7 -3
  62. package/src/store/chat/slices/thread/action.ts +3 -3
  63. package/src/store/chat/slices/topic/action.test.ts +1 -1
  64. package/src/store/chat/slices/topic/action.ts +3 -3
  65. package/src/store/global/selectors.ts +6 -0
  66. package/src/store/session/slices/session/action.ts +6 -3
  67. package/src/store/session/slices/sessionGroup/action.test.ts +8 -6
  68. package/src/store/tool/slices/plugin/action.ts +5 -3
  69. package/src/store/tool/slices/store/action.ts +4 -3
  70. package/src/store/user/slices/common/action.test.ts +3 -1
  71. package/vercel.json +1 -1
@@ -29,46 +29,42 @@ export class TopicModel {
29
29
  }
30
30
  // **************** Query *************** //
31
31
 
32
- async query({ current = 0, pageSize = 9999, sessionId }: QueryTopicParams = {}) {
32
+ query = async ({ current = 0, pageSize = 9999, sessionId }: QueryTopicParams = {}) => {
33
33
  const offset = current * pageSize;
34
-
35
- return (
36
- this.db
37
- .select({
38
- createdAt: topics.createdAt,
39
- favorite: topics.favorite,
40
- historySummary: topics.historySummary,
41
- id: topics.id,
42
- metadata: topics.metadata,
43
- title: topics.title,
44
- updatedAt: topics.updatedAt,
45
- })
46
- .from(topics)
47
- .where(and(eq(topics.userId, this.userId), this.matchSession(sessionId)))
48
- // In boolean sorting, false is considered "smaller" than true.
49
- // So here we use desc to ensure that topics with favorite as true are in front.
50
- .orderBy(desc(topics.favorite), desc(topics.updatedAt))
51
- .limit(pageSize)
52
- .offset(offset)
53
- );
54
- }
55
-
56
- async findById(id: string) {
34
+ return this.db
35
+ .select({
36
+ createdAt: topics.createdAt,
37
+ favorite: topics.favorite,
38
+ historySummary: topics.historySummary,
39
+ id: topics.id,
40
+ metadata: topics.metadata,
41
+ title: topics.title,
42
+ updatedAt: topics.updatedAt,
43
+ })
44
+ .from(topics)
45
+ .where(and(eq(topics.userId, this.userId), this.matchSession(sessionId)))
46
+ // In boolean sorting, false is considered "smaller" than true.
47
+ // So here we use desc to ensure that topics with favorite as true are in front.
48
+ .orderBy(desc(topics.favorite), desc(topics.updatedAt))
49
+ .limit(pageSize)
50
+ .offset(offset);
51
+ };
52
+
53
+ findById = async (id: string) => {
57
54
  return this.db.query.topics.findFirst({
58
55
  where: and(eq(topics.id, id), eq(topics.userId, this.userId)),
59
56
  });
60
- }
57
+ };
61
58
 
62
- async queryAll(): Promise<TopicItem[]> {
59
+ queryAll = async (): Promise<TopicItem[]> => {
63
60
  return this.db
64
61
  .select()
65
62
  .from(topics)
66
63
  .orderBy(topics.updatedAt)
67
- .where(eq(topics.userId, this.userId))
68
- .execute();
69
- }
64
+ .where(eq(topics.userId, this.userId));
65
+ };
70
66
 
71
- async queryByKeyword(keyword: string, sessionId?: string | null): Promise<TopicItem[]> {
67
+ queryByKeyword = async (keyword: string, sessionId?: string | null): Promise<TopicItem[]> => {
72
68
  if (!keyword) return [];
73
69
 
74
70
  const keywordLowerCase = keyword.toLowerCase();
@@ -92,26 +88,25 @@ export class TopicModel {
92
88
  ),
93
89
  ),
94
90
  });
95
- }
91
+ };
96
92
 
97
- async count() {
93
+ count = async (): Promise<number> => {
98
94
  const result = await this.db
99
95
  .select({
100
- count: count(),
96
+ count: count(topics.id),
101
97
  })
102
98
  .from(topics)
103
- .where(eq(topics.userId, this.userId))
104
- .execute();
99
+ .where(eq(topics.userId, this.userId));
105
100
 
106
101
  return result[0].count;
107
- }
102
+ };
108
103
 
109
104
  // **************** Create *************** //
110
105
 
111
- async create(
106
+ create = async (
112
107
  { messages: messageIds, ...params }: CreateTopicParams,
113
108
  id: string = this.genId(),
114
- ): Promise<TopicItem> {
109
+ ): Promise<TopicItem> => {
115
110
  return this.db.transaction(async (tx) => {
116
111
  // 在 topics 表中插入新的 topic
117
112
  const [topic] = await tx
@@ -133,9 +128,9 @@ export class TopicModel {
133
128
 
134
129
  return topic;
135
130
  });
136
- }
131
+ };
137
132
 
138
- async batchCreate(topicParams: (CreateTopicParams & { id?: string })[]) {
133
+ batchCreate = async (topicParams: (CreateTopicParams & { id?: string })[]) => {
139
134
  // 开始一个事务
140
135
  return this.db.transaction(async (tx) => {
141
136
  // 在 topics 表中批量插入新的 topics
@@ -167,9 +162,9 @@ export class TopicModel {
167
162
 
168
163
  return createdTopics;
169
164
  });
170
- }
165
+ };
171
166
 
172
- async duplicate(topicId: string, newTitle?: string) {
167
+ duplicate = async (topicId: string, newTitle?: string) => {
173
168
  return this.db.transaction(async (tx) => {
174
169
  // find original topic
175
170
  const originalTopic = await tx.query.topics.findFirst({
@@ -217,48 +212,48 @@ export class TopicModel {
217
212
  topic: duplicatedTopic,
218
213
  };
219
214
  });
220
- }
215
+ };
221
216
 
222
217
  // **************** Delete *************** //
223
218
 
224
219
  /**
225
220
  * Delete a session, also delete all messages and topics associated with it.
226
221
  */
227
- async delete(id: string) {
222
+ delete = async (id: string) => {
228
223
  return this.db.delete(topics).where(and(eq(topics.id, id), eq(topics.userId, this.userId)));
229
- }
224
+ };
230
225
 
231
226
  /**
232
227
  * Deletes multiple topics based on the sessionId.
233
228
  */
234
- async batchDeleteBySessionId(sessionId?: string | null) {
229
+ batchDeleteBySessionId = async (sessionId?: string | null) => {
235
230
  return this.db
236
231
  .delete(topics)
237
232
  .where(and(this.matchSession(sessionId), eq(topics.userId, this.userId)));
238
- }
233
+ };
239
234
 
240
235
  /**
241
236
  * Deletes multiple topics and all messages associated with them in a transaction.
242
237
  */
243
- async batchDelete(ids: string[]) {
238
+ batchDelete = async (ids: string[]) => {
244
239
  return this.db
245
240
  .delete(topics)
246
241
  .where(and(inArray(topics.id, ids), eq(topics.userId, this.userId)));
247
- }
242
+ };
248
243
 
249
- async deleteAll() {
244
+ deleteAll = async () => {
250
245
  return this.db.delete(topics).where(eq(topics.userId, this.userId));
251
- }
246
+ };
252
247
 
253
248
  // **************** Update *************** //
254
249
 
255
- async update(id: string, data: Partial<TopicItem>) {
250
+ update = async (id: string, data: Partial<TopicItem>) => {
256
251
  return this.db
257
252
  .update(topics)
258
253
  .set({ ...data, updatedAt: new Date() })
259
254
  .where(and(eq(topics.id, id), eq(topics.userId, this.userId)))
260
255
  .returning();
261
- }
256
+ };
262
257
 
263
258
  // **************** Helper *************** //
264
259
 
@@ -26,7 +26,7 @@ export class UserModel {
26
26
  this.db = db;
27
27
  }
28
28
 
29
- async getUserState() {
29
+ getUserState = async () => {
30
30
  const result = await this.db
31
31
  .select({
32
32
  isOnboarded: users.isOnboarded,
@@ -81,20 +81,20 @@ export class UserModel {
81
81
  settings,
82
82
  userId: this.userId,
83
83
  };
84
- }
84
+ };
85
85
 
86
- async updateUser(value: Partial<UserItem>) {
86
+ updateUser = async (value: Partial<UserItem>) => {
87
87
  return this.db
88
88
  .update(users)
89
89
  .set({ ...value, updatedAt: new Date() })
90
90
  .where(eq(users.id, this.userId));
91
- }
91
+ };
92
92
 
93
- async deleteSetting() {
93
+ deleteSetting = async () => {
94
94
  return this.db.delete(userSettings).where(eq(userSettings.id, this.userId));
95
- }
95
+ };
96
96
 
97
- async updateSetting(value: Partial<UserSettings>) {
97
+ updateSetting = async (value: Partial<UserSettings>) => {
98
98
  const { keyVaults, ...res } = value;
99
99
 
100
100
  // Encrypt keyVaults
@@ -120,9 +120,9 @@ export class UserModel {
120
120
  }
121
121
 
122
122
  return this.db.update(userSettings).set(newValue).where(eq(userSettings.id, this.userId));
123
- }
123
+ };
124
124
 
125
- async updatePreference(value: Partial<UserPreference>) {
125
+ updatePreference = async (value: Partial<UserPreference>) => {
126
126
  const user = await this.db.query.users.findFirst({ where: eq(users.id, this.userId) });
127
127
  if (!user) return;
128
128
 
@@ -130,9 +130,9 @@ export class UserModel {
130
130
  .update(users)
131
131
  .set({ preference: merge(user.preference, value) })
132
132
  .where(eq(users.id, this.userId));
133
- }
133
+ };
134
134
 
135
- async updateGuide(value: Partial<UserGuide>) {
135
+ updateGuide = async (value: Partial<UserGuide>) => {
136
136
  const user = await this.db.query.users.findFirst({ where: eq(users.id, this.userId) });
137
137
  if (!user) return;
138
138
 
@@ -141,7 +141,7 @@ export class UserModel {
141
141
  .update(users)
142
142
  .set({ preference: { ...prevPreference, guide: merge(prevPreference.guide || {}, value) } })
143
143
  .where(eq(users.id, this.userId));
144
- }
144
+ };
145
145
 
146
146
  // Static method
147
147
 
@@ -11,6 +11,7 @@ import { Center, Flexbox } from 'react-layout-kit';
11
11
  import { FORM_STYLE } from '@/const/layoutTokens';
12
12
  import PluginStore from '@/features/PluginStore';
13
13
  import PluginTag from '@/features/PluginStore/PluginItem/PluginTag';
14
+ import { useFetchInstalledPlugins } from '@/hooks/useFetchInstalledPlugins';
14
15
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
15
16
  import { pluginHelpers, useToolStore } from '@/store/tool';
16
17
  import { toolSelectors } from '@/store/tool/selectors';
@@ -33,7 +34,6 @@ const AgentPlugin = memo(() => {
33
34
 
34
35
  const { showDalle } = useServerConfigStore(featureFlagsSelectors);
35
36
  const installedPlugins = useToolStore(toolSelectors.metaList(showDalle), isEqual);
36
- const useFetchInstalledPlugins = useToolStore((s) => s.useFetchInstalledPlugins);
37
37
 
38
38
  const { isLoading } = useFetchInstalledPlugins();
39
39
 
@@ -10,6 +10,8 @@ import { Flexbox } from 'react-layout-kit';
10
10
 
11
11
  import { useWorkspaceModal } from '@/app/(main)/chat/(workspace)/features/useWorkspaceModal';
12
12
  import PluginStore from '@/features/PluginStore';
13
+ import { useCheckPluginsIsInstalled } from '@/hooks/useCheckPluginsIsInstalled';
14
+ import { useFetchInstalledPlugins } from '@/hooks/useFetchInstalledPlugins';
13
15
  import { useAgentStore } from '@/store/agent';
14
16
  import { agentSelectors } from '@/store/agent/selectors';
15
17
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
@@ -109,13 +111,11 @@ const DropdownMenu = memo<PropsWithChildren>(({ children }) => {
109
111
 
110
112
  const plugins = useAgentStore((s) => agentSelectors.currentAgentPlugins(s));
111
113
 
112
- const [useFetchPluginStore, useFetchInstalledPlugins, checkPluginsIsInstalled] = useToolStore(
113
- (s) => [s.useFetchPluginStore, s.useFetchInstalledPlugins, s.useCheckPluginsIsInstalled],
114
- );
114
+ const [useFetchPluginStore] = useToolStore((s) => [s.useFetchPluginStore]);
115
115
 
116
116
  useFetchPluginStore();
117
117
  useFetchInstalledPlugins();
118
- checkPluginsIsInstalled(plugins);
118
+ useCheckPluginsIsInstalled(plugins);
119
119
 
120
120
  return (
121
121
  <>
@@ -2,6 +2,7 @@ import React, { memo, useCallback } from 'react';
2
2
  import { Flexbox } from 'react-layout-kit';
3
3
 
4
4
  import { SkeletonList, VirtualizedList } from '@/features/Conversation';
5
+ import { useFetchThreads } from '@/hooks/useFetchThreads';
5
6
  import { useChatStore } from '@/store/chat';
6
7
  import { threadSelectors } from '@/store/chat/selectors';
7
8
 
@@ -15,8 +16,6 @@ const ChatList = memo(({ mobile }: ChatListProps) => {
15
16
  const data = useChatStore(threadSelectors.portalDisplayChatIDs);
16
17
  const isInit = useChatStore((s) => s.threadsInit);
17
18
 
18
- const useFetchThreads = useChatStore((s) => s.useFetchThreads);
19
-
20
19
  useFetchThreads();
21
20
 
22
21
  const itemContent = useCallback(
@@ -0,0 +1,10 @@
1
+ import { useGlobalStore } from '@/store/global';
2
+ import { systemStatusSelectors } from '@/store/global/selectors';
3
+ import { useToolStore } from '@/store/tool';
4
+
5
+ export const useCheckPluginsIsInstalled = (plugins: string[]) => {
6
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
7
+ const checkPluginsIsInstalled = useToolStore((s) => s.useCheckPluginsIsInstalled);
8
+
9
+ checkPluginsIsInstalled(isPgliteInited, plugins);
10
+ };
@@ -0,0 +1,10 @@
1
+ import { useGlobalStore } from '@/store/global';
2
+ import { systemStatusSelectors } from '@/store/global/selectors';
3
+ import { useToolStore } from '@/store/tool';
4
+
5
+ export const useFetchInstalledPlugins = () => {
6
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
7
+ const [useFetchInstalledPlugins] = useToolStore((s) => [s.useFetchInstalledPlugins]);
8
+
9
+ return useFetchInstalledPlugins(isPgliteInited);
10
+ };
@@ -0,0 +1,15 @@
1
+ import { useChatStore } from '@/store/chat';
2
+ import { useGlobalStore } from '@/store/global';
3
+ import { systemStatusSelectors } from '@/store/global/selectors';
4
+ import { useSessionStore } from '@/store/session';
5
+
6
+ export const useFetchMessages = () => {
7
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
8
+ const [sessionId] = useSessionStore((s) => [s.activeId]);
9
+ const [activeTopicId, useFetchMessages] = useChatStore((s) => [
10
+ s.activeTopicId,
11
+ s.useFetchMessages,
12
+ ]);
13
+
14
+ useFetchMessages(isPgliteInited, sessionId, activeTopicId);
15
+ };
@@ -0,0 +1,13 @@
1
+ import { useGlobalStore } from '@/store/global';
2
+ import { systemStatusSelectors } from '@/store/global/selectors';
3
+ import { useSessionStore } from '@/store/session';
4
+ import { useUserStore } from '@/store/user';
5
+ import { authSelectors } from '@/store/user/slices/auth/selectors';
6
+
7
+ export const useFetchSessions = () => {
8
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
9
+ const isLogin = useUserStore(authSelectors.isLogin);
10
+ const useFetchSessions = useSessionStore((s) => s.useFetchSessions);
11
+
12
+ useFetchSessions(isPgliteInited, isLogin);
13
+ };
@@ -0,0 +1,11 @@
1
+ import { useChatStore } from '@/store/chat';
2
+ import { useGlobalStore } from '@/store/global';
3
+ import { systemStatusSelectors } from '@/store/global/selectors';
4
+
5
+ export const useFetchThreads = (activeTopicId?: string) => {
6
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
7
+
8
+ const [useFetchThreads] = useChatStore((s) => [s.useFetchThreads]);
9
+
10
+ useFetchThreads(isPgliteInited, activeTopicId);
11
+ };
@@ -1,4 +1,7 @@
1
+ import { useFetchThreads } from '@/hooks/useFetchThreads';
1
2
  import { useChatStore } from '@/store/chat';
3
+ import { useGlobalStore } from '@/store/global';
4
+ import { systemStatusSelectors } from '@/store/global/selectors';
2
5
  import { useSessionStore } from '@/store/session';
3
6
 
4
7
  /**
@@ -6,12 +9,9 @@ import { useSessionStore } from '@/store/session';
6
9
  */
7
10
  export const useFetchTopics = () => {
8
11
  const [sessionId] = useSessionStore((s) => [s.activeId]);
9
- const [activeTopicId, useFetchTopics, useFetchThreads] = useChatStore((s) => [
10
- s.activeTopicId,
11
- s.useFetchTopics,
12
- s.useFetchThreads,
13
- ]);
14
- useFetchTopics(sessionId);
12
+ const [activeTopicId, useFetchTopics] = useChatStore((s) => [s.activeTopicId, s.useFetchTopics]);
13
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
15
14
 
15
+ useFetchTopics(isPgliteInited, sessionId);
16
16
  useFetchThreads(activeTopicId);
17
17
  };
@@ -10,6 +10,7 @@ import { useIsMobile } from '@/hooks/useIsMobile';
10
10
  import { useEnabledDataSync } from '@/hooks/useSyncData';
11
11
  import { useAgentStore } from '@/store/agent';
12
12
  import { useGlobalStore } from '@/store/global';
13
+ import { systemStatusSelectors } from '@/store/global/selectors';
13
14
  import { useServerConfigStore } from '@/store/serverConfig';
14
15
  import { serverConfigSelectors } from '@/store/serverConfig/selectors';
15
16
  import { useUserStore } from '@/store/user';
@@ -50,7 +51,8 @@ const StoreInitialization = memo(() => {
50
51
  * But during initialization, the value of `enableAuth` might be incorrect cause of the async fetch.
51
52
  * So we need to use `isSignedIn` only to determine whether request for the default agent config and user state.
52
53
  */
53
- const isLoginOnInit = enableNextAuth ? isSignedIn : isLogin;
54
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
55
+ const isLoginOnInit = isPgliteInited && (enableNextAuth ? isSignedIn : isLogin);
54
56
 
55
57
  // init inbox agent and default agent config
56
58
  useInitAgentStore(isLoginOnInit, serverConfig.defaultAgent?.config);
@@ -1,4 +1,3 @@
1
- import { desc } from 'drizzle-orm/expressions';
2
1
  import { describe, expect, it, vi } from 'vitest';
3
2
 
4
3
  import { AzureOpenAIStream } from './azureOpenai';
@@ -4,7 +4,7 @@ import type {
4
4
  AdapterUser,
5
5
  VerificationToken,
6
6
  } from '@auth/core/adapters';
7
- import { and, eq } from 'drizzle-orm';
7
+ import { and, eq } from 'drizzle-orm/expressions';
8
8
  import type { NeonDatabase } from 'drizzle-orm/neon-serverless';
9
9
  import { Adapter, AdapterAccount } from 'next-auth/adapters';
10
10
 
@@ -1,14 +1,14 @@
1
- import { inArray } from 'drizzle-orm';
1
+ import { inArray } from 'drizzle-orm/expressions';
2
2
  import { z } from 'zod';
3
3
 
4
4
  import { DEFAULT_EMBEDDING_MODEL } from '@/const/settings';
5
+ import { knowledgeBaseFiles } from '@/database/schemas';
5
6
  import { serverDB } from '@/database/server';
6
7
  import { AsyncTaskModel } from '@/database/server/models/asyncTask';
7
8
  import { ChunkModel } from '@/database/server/models/chunk';
8
9
  import { EmbeddingModel } from '@/database/server/models/embedding';
9
10
  import { FileModel } from '@/database/server/models/file';
10
11
  import { MessageModel } from '@/database/server/models/message';
11
- import { knowledgeBaseFiles } from '@/database/schemas';
12
12
  import { ModelProvider } from '@/libs/agent-runtime';
13
13
  import { authedProcedure, router } from '@/libs/trpc';
14
14
  import { keyVaults } from '@/libs/trpc/middleware/keyVaults';
@@ -42,8 +42,8 @@ export class ClientService implements IUserService {
42
42
  return UserModel.resetSettings();
43
43
  };
44
44
 
45
- updateAvatar(avatar: string) {
46
- return UserModel.updateAvatar(avatar);
45
+ async updateAvatar(avatar: string) {
46
+ await UserModel.updateAvatar(avatar);
47
47
  }
48
48
 
49
49
  async updatePreference(preference: Partial<UserPreference>) {
@@ -3,7 +3,6 @@ import { mutate } from 'swr';
3
3
  import { describe, expect, it, vi } from 'vitest';
4
4
 
5
5
  import { INBOX_SESSION_ID } from '@/const/session';
6
- import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
7
6
  import { globalService } from '@/services/global';
8
7
  import { sessionService } from '@/services/session';
9
8
  import { useAgentStore } from '@/store/agent';
@@ -24,7 +23,9 @@ describe('AgentSlice', () => {
24
23
  it('should call togglePlugin with the provided id and false', async () => {
25
24
  const { result } = renderHook(() => useAgentStore());
26
25
  const pluginId = 'plugin-id';
27
- const togglePluginMock = vi.spyOn(result.current, 'togglePlugin');
26
+ const togglePluginMock = vi
27
+ .spyOn(result.current, 'togglePlugin')
28
+ .mockResolvedValue(undefined);
28
29
 
29
30
  await act(async () => {
30
31
  await result.current.removePlugin(pluginId);
@@ -39,8 +40,9 @@ describe('AgentSlice', () => {
39
40
  it('should add plugin id to plugins array if not present and open is true or undefined', async () => {
40
41
  const { result } = renderHook(() => useAgentStore());
41
42
  const pluginId = 'plugin-id';
42
- const updateAgentConfigMock = vi.spyOn(result.current, 'updateAgentConfig');
43
-
43
+ const updateAgentConfigMock = vi
44
+ .spyOn(result.current, 'updateAgentConfig')
45
+ .mockResolvedValue(undefined);
44
46
  // 模拟当前配置不包含插件 ID
45
47
  vi.spyOn(agentSelectors, 'currentAgentConfig').mockReturnValue({ plugins: [] } as any);
46
48
 
@@ -57,8 +59,9 @@ describe('AgentSlice', () => {
57
59
  it('should remove plugin id from plugins array if present and open is false', async () => {
58
60
  const { result } = renderHook(() => useAgentStore());
59
61
  const pluginId = 'plugin-id';
60
- const updateAgentConfigMock = vi.spyOn(result.current, 'updateAgentConfig');
61
-
62
+ const updateAgentConfigMock = vi
63
+ .spyOn(result.current, 'updateAgentConfig')
64
+ .mockResolvedValue(undefined);
62
65
  // 模拟当前配置包含插件 ID
63
66
  vi.spyOn(agentSelectors, 'currentAgentConfig').mockReturnValue({
64
67
  plugins: [pluginId],
@@ -75,7 +78,9 @@ describe('AgentSlice', () => {
75
78
  it('should not modify plugins array if plugin id is not present and open is false', async () => {
76
79
  const { result } = renderHook(() => useAgentStore());
77
80
  const pluginId = 'plugin-id';
78
- const updateAgentConfigMock = vi.spyOn(result.current, 'updateAgentConfig');
81
+ const updateAgentConfigMock = vi
82
+ .spyOn(result.current, 'updateAgentConfig')
83
+ .mockResolvedValue(undefined);
79
84
 
80
85
  // 模拟当前配置不包含插件 ID
81
86
  vi.spyOn(agentSelectors, 'currentAgentConfig').mockReturnValue({ plugins: [] } as any);
@@ -93,7 +98,9 @@ describe('AgentSlice', () => {
93
98
  it('should update global config if current session is inbox session', async () => {
94
99
  const { result } = renderHook(() => useAgentStore());
95
100
  const config = { model: 'gpt-3.5-turbo' };
96
- const updateSessionConfigMock = vi.spyOn(sessionService, 'updateSessionConfig');
101
+ const updateSessionConfigMock = vi
102
+ .spyOn(sessionService, 'updateSessionConfig')
103
+ .mockResolvedValue(undefined);
97
104
  const refreshMock = vi.spyOn(result.current, 'internal_refreshAgentConfig');
98
105
 
99
106
  await act(async () => {
@@ -113,7 +120,9 @@ describe('AgentSlice', () => {
113
120
  it('should update session config if current session is not inbox session', async () => {
114
121
  const { result } = renderHook(() => useAgentStore());
115
122
  const config = { model: 'gpt-3.5-turbo' };
116
- const updateSessionConfigMock = vi.spyOn(sessionService, 'updateSessionConfig');
123
+ const updateSessionConfigMock = vi
124
+ .spyOn(sessionService, 'updateSessionConfig')
125
+ .mockResolvedValue(undefined);
117
126
  const refreshMock = vi.spyOn(result.current, 'internal_refreshAgentConfig');
118
127
 
119
128
  // 模拟当前会话不是收件箱会话
@@ -247,7 +256,9 @@ describe('AgentSlice', () => {
247
256
  it('should call sessionService.updateSessionConfig', async () => {
248
257
  const { result } = renderHook(() => useAgentStore());
249
258
 
250
- const updateSessionConfigMock = vi.spyOn(sessionService, 'updateSessionConfig');
259
+ const updateSessionConfigMock = vi
260
+ .spyOn(sessionService, 'updateSessionConfig')
261
+ .mockResolvedValue(undefined);
251
262
 
252
263
  await act(async () => {
253
264
  await result.current.internal_updateAgentConfig('test-session-id', { foo: 'bar' } as any);
@@ -5,6 +5,7 @@ import { LOADING_FLAT } from '@/const/message';
5
5
  import { DEFAULT_AGENT_CHAT_CONFIG, DEFAULT_AGENT_CONFIG } from '@/const/settings';
6
6
  import { chatService } from '@/services/chat';
7
7
  import { messageService } from '@/services/message';
8
+ import { sessionService } from '@/services/session';
8
9
  import { topicService } from '@/services/topic';
9
10
  import { useAgentStore } from '@/store/agent';
10
11
  import { agentSelectors } from '@/store/agent/selectors';
@@ -51,6 +52,15 @@ vi.mock('@/services/chat', async (importOriginal) => {
51
52
  },
52
53
  };
53
54
  });
55
+ vi.mock('@/services/session', async (importOriginal) => {
56
+ const module = await importOriginal();
57
+
58
+ return {
59
+ sessionService: {
60
+ updateSession: vi.fn(),
61
+ },
62
+ };
63
+ });
54
64
 
55
65
  const realCoreProcessMessage = useChatStore.getState().internal_coreProcessMessage;
56
66
 
@@ -72,7 +72,6 @@ export const chatToolSlice: StateCreator<
72
72
 
73
73
  const data = await useFileStore.getState().uploadWithProgress({
74
74
  file: imageFile,
75
- onStatusUpdate: () => {},
76
75
  });
77
76
 
78
77
  if (!data) return;
@@ -466,7 +466,9 @@ describe('chatMessage actions', () => {
466
466
  // 设置模拟返回值
467
467
  (messageService.getMessages as Mock).mockResolvedValue(messages);
468
468
 
469
- const { result } = renderHook(() => useChatStore().useFetchMessages(sessionId, topicId));
469
+ const { result } = renderHook(() =>
470
+ useChatStore().useFetchMessages(true, sessionId, topicId),
471
+ );
470
472
 
471
473
  // 等待异步操作完成
472
474
  await waitFor(() => {
@@ -47,7 +47,11 @@ export interface ChatMessageAction {
47
47
  modifyMessageContent: (id: string, content: string) => Promise<void>;
48
48
  toggleMessageEditing: (id: string, editing: boolean) => void;
49
49
  // query
50
- useFetchMessages: (sessionId: string, topicId?: string) => SWRResponse<ChatMessage[]>;
50
+ useFetchMessages: (
51
+ enable: boolean,
52
+ sessionId: string,
53
+ topicId?: string,
54
+ ) => SWRResponse<ChatMessage[]>;
51
55
  copyMessage: (id: string, content: string) => Promise<void>;
52
56
  refreshMessages: () => Promise<void>;
53
57
 
@@ -220,9 +224,9 @@ export const chatMessage: StateCreator<
220
224
 
221
225
  await get().internal_updateMessageContent(id, content);
222
226
  },
223
- useFetchMessages: (sessionId, activeTopicId) =>
227
+ useFetchMessages: (enable, sessionId, activeTopicId) =>
224
228
  useClientDataSWR<ChatMessage[]>(
225
- [SWR_USE_FETCH_MESSAGES, sessionId, activeTopicId],
229
+ enable ? [SWR_USE_FETCH_MESSAGES, sessionId, activeTopicId] : null,
226
230
  async ([, sessionId, topicId]: [string, string, string | undefined]) =>
227
231
  messageService.getMessages(sessionId, topicId),
228
232
  {
@@ -44,7 +44,7 @@ export interface ChatThreadAction {
44
44
  openThreadCreator: (messageId: string) => void;
45
45
  openThreadInPortal: (threadId: string, sourceMessageId: string) => void;
46
46
  closeThreadPortal: () => void;
47
- useFetchThreads: (topicId?: string) => SWRResponse<ThreadItem[]>;
47
+ useFetchThreads: (enable: boolean, topicId?: string) => SWRResponse<ThreadItem[]>;
48
48
  summaryThreadTitle: (threadId: string, messages: ChatMessage[]) => Promise<void>;
49
49
  updateThreadTitle: (id: string, title: string) => Promise<void>;
50
50
  removeThread: (id: string) => Promise<void>;
@@ -209,9 +209,9 @@ export const chatThreadMessage: StateCreator<
209
209
  return data;
210
210
  },
211
211
 
212
- useFetchThreads: (topicId) =>
212
+ useFetchThreads: (enable, topicId) =>
213
213
  useClientDataSWR<ThreadItem[]>(
214
- !!topicId && isServerMode ? [SWR_USE_FETCH_THREADS, topicId] : null,
214
+ enable && !!topicId && isServerMode ? [SWR_USE_FETCH_THREADS, topicId] : null,
215
215
  async ([, topicId]: [string, string]) => threadService.getThreads(topicId),
216
216
  {
217
217
  suspense: true,