@lobehub/chat 0.148.8 → 0.148.10

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 (41) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/package.json +2 -2
  3. package/src/app/chat/features/SessionListContent/CollapseGroup/Actions.tsx +11 -5
  4. package/src/app/chat/features/SessionListContent/DefaultMode.tsx +6 -4
  5. package/src/app/chat/features/SessionListContent/List/AddButton.tsx +4 -9
  6. package/src/app/chat/features/SessionListContent/Modals/ConfigGroupModal/GroupItem.tsx +21 -16
  7. package/src/app/chat/features/SessionListContent/Modals/ConfigGroupModal/index.tsx +8 -2
  8. package/src/app/chat/features/SessionListContent/Modals/CreateGroupModal.tsx +9 -1
  9. package/src/app/chat/features/SessionListContent/Modals/RenameGroupModal.tsx +12 -3
  10. package/src/app/home/Redirect.tsx +2 -2
  11. package/src/app/welcome/(desktop)/features/Footer.tsx +1 -1
  12. package/src/app/welcome/features/Banner/index.tsx +5 -9
  13. package/src/config/modelProviders/ollama.ts +38 -38
  14. package/src/database/client/models/__tests__/session.test.ts +2 -2
  15. package/src/database/client/models/session.ts +4 -14
  16. package/src/libs/agent-runtime/zeroone/index.test.ts +16 -16
  17. package/src/locales/default/chat.ts +6 -2
  18. package/src/locales/default/welcome.ts +1 -0
  19. package/src/migrations/FromV3ToV4/fixtures/ollama-output-v4.json +0 -1
  20. package/src/services/config.ts +5 -21
  21. package/src/services/message/client.ts +10 -0
  22. package/src/services/message/index.ts +1 -13
  23. package/src/services/message/type.ts +3 -0
  24. package/src/services/session/client.ts +3 -0
  25. package/src/services/session/type.ts +8 -2
  26. package/src/services/topic/client.ts +1 -1
  27. package/src/services/topic/type.ts +3 -2
  28. package/src/store/global/slices/settings/actions/llm.test.ts +0 -1
  29. package/src/store/global/slices/settings/selectors/modelProvider.test.ts +1 -1
  30. package/src/store/session/slices/session/action.ts +74 -76
  31. package/src/store/session/slices/session/initialState.ts +8 -1
  32. package/src/store/session/slices/session/reducers.test.ts +79 -0
  33. package/src/store/session/slices/session/reducers.ts +61 -0
  34. package/src/store/session/slices/sessionGroup/action.test.ts +9 -0
  35. package/src/store/session/slices/sessionGroup/action.ts +25 -6
  36. package/src/store/session/slices/sessionGroup/reducer.test.ts +86 -0
  37. package/src/store/session/slices/sessionGroup/reducer.ts +56 -0
  38. package/src/store/session/slices/sessionGroup/selectors.ts +1 -5
  39. package/src/types/service.ts +7 -0
  40. package/src/types/session.ts +5 -7
  41. package/src/utils/uuid.ts +4 -4
@@ -50,7 +50,7 @@ describe('LobeZeroOneAI', () => {
50
50
  // Act
51
51
  const result = await instance.chat({
52
52
  messages: [{ content: 'Hello', role: 'user' }],
53
- model: 'mistralai/mistral-7b-instruct:free',
53
+ model: 'yi-34b-chat-0205',
54
54
  temperature: 0,
55
55
  });
56
56
 
@@ -69,7 +69,7 @@ describe('LobeZeroOneAI', () => {
69
69
  const result = await instance.chat({
70
70
  max_tokens: 1024,
71
71
  messages: [{ content: 'Hello', role: 'user' }],
72
- model: 'mistralai/mistral-7b-instruct:free',
72
+ model: 'yi-34b-chat-0205',
73
73
  temperature: 0.7,
74
74
  top_p: 1,
75
75
  });
@@ -79,7 +79,7 @@ describe('LobeZeroOneAI', () => {
79
79
  {
80
80
  max_tokens: 1024,
81
81
  messages: [{ content: 'Hello', role: 'user' }],
82
- model: 'mistralai/mistral-7b-instruct:free',
82
+ model: 'yi-34b-chat-0205',
83
83
  temperature: 0.7,
84
84
  top_p: 1,
85
85
  },
@@ -89,7 +89,7 @@ describe('LobeZeroOneAI', () => {
89
89
  });
90
90
 
91
91
  describe('Error', () => {
92
- it('should return OpenRouterBizError with an openai error response when OpenAI.APIError is thrown', async () => {
92
+ it('should return ZeroOneBizError with an openai error response when OpenAI.APIError is thrown', async () => {
93
93
  // Arrange
94
94
  const apiError = new OpenAI.APIError(
95
95
  400,
@@ -109,7 +109,7 @@ describe('LobeZeroOneAI', () => {
109
109
  try {
110
110
  await instance.chat({
111
111
  messages: [{ content: 'Hello', role: 'user' }],
112
- model: 'mistralai/mistral-7b-instruct:free',
112
+ model: 'yi-34b-chat-0205',
113
113
  temperature: 0,
114
114
  });
115
115
  } catch (e) {
@@ -125,7 +125,7 @@ describe('LobeZeroOneAI', () => {
125
125
  }
126
126
  });
127
127
 
128
- it('should throw AgentRuntimeError with InvalidOpenRouterAPIKey if no apiKey is provided', async () => {
128
+ it('should throw AgentRuntimeError with InvalidZeroOneAPIKey if no apiKey is provided', async () => {
129
129
  try {
130
130
  new LobeZeroOneAI({});
131
131
  } catch (e) {
@@ -133,7 +133,7 @@ describe('LobeZeroOneAI', () => {
133
133
  }
134
134
  });
135
135
 
136
- it('should return OpenRouterBizError with the cause when OpenAI.APIError is thrown with cause', async () => {
136
+ it('should return ZeroOneBizError with the cause when OpenAI.APIError is thrown with cause', async () => {
137
137
  // Arrange
138
138
  const errorInfo = {
139
139
  stack: 'abc',
@@ -149,7 +149,7 @@ describe('LobeZeroOneAI', () => {
149
149
  try {
150
150
  await instance.chat({
151
151
  messages: [{ content: 'Hello', role: 'user' }],
152
- model: 'mistralai/mistral-7b-instruct:free',
152
+ model: 'yi-34b-chat-0205',
153
153
  temperature: 0,
154
154
  });
155
155
  } catch (e) {
@@ -165,7 +165,7 @@ describe('LobeZeroOneAI', () => {
165
165
  }
166
166
  });
167
167
 
168
- it('should return OpenRouterBizError with an cause response with desensitize Url', async () => {
168
+ it('should return ZeroOneBizError with an cause response with desensitize Url', async () => {
169
169
  // Arrange
170
170
  const errorInfo = {
171
171
  stack: 'abc',
@@ -185,7 +185,7 @@ describe('LobeZeroOneAI', () => {
185
185
  try {
186
186
  await instance.chat({
187
187
  messages: [{ content: 'Hello', role: 'user' }],
188
- model: 'mistralai/mistral-7b-instruct:free',
188
+ model: 'yi-34b-chat-0205',
189
189
  temperature: 0,
190
190
  });
191
191
  } catch (e) {
@@ -201,7 +201,7 @@ describe('LobeZeroOneAI', () => {
201
201
  }
202
202
  });
203
203
 
204
- it('should throw an InvalidOpenRouterAPIKey error type on 401 status code', async () => {
204
+ it('should throw an InvalidZeroOneAPIKey error type on 401 status code', async () => {
205
205
  // Mock the API call to simulate a 401 error
206
206
  const error = new Error('Unauthorized') as any;
207
207
  error.status = 401;
@@ -210,7 +210,7 @@ describe('LobeZeroOneAI', () => {
210
210
  try {
211
211
  await instance.chat({
212
212
  messages: [{ content: 'Hello', role: 'user' }],
213
- model: 'mistralai/mistral-7b-instruct:free',
213
+ model: 'yi-34b-chat-0205',
214
214
  temperature: 0,
215
215
  });
216
216
  } catch (e) {
@@ -234,7 +234,7 @@ describe('LobeZeroOneAI', () => {
234
234
  try {
235
235
  await instance.chat({
236
236
  messages: [{ content: 'Hello', role: 'user' }],
237
- model: 'mistralai/mistral-7b-instruct:free',
237
+ model: 'yi-34b-chat-0205',
238
238
  temperature: 0,
239
239
  });
240
240
  } catch (e) {
@@ -265,7 +265,7 @@ describe('LobeZeroOneAI', () => {
265
265
  id: 'chatcmpl-8xDx5AETP8mESQN7UB30GxTN2H1SO',
266
266
  object: 'chat.completion.chunk',
267
267
  created: 1709125675,
268
- model: 'mistralai/mistral-7b-instruct:free',
268
+ model: 'yi-34b-chat-0205',
269
269
  system_fingerprint: 'fp_86156a94a0',
270
270
  choices: [
271
271
  { index: 0, delta: { content: 'hello' }, logprobs: null, finish_reason: null },
@@ -287,7 +287,7 @@ describe('LobeZeroOneAI', () => {
287
287
  const result = await instance.chat(
288
288
  {
289
289
  messages: [{ content: 'Hello', role: 'user' }],
290
- model: 'mistralai/mistral-7b-instruct:free',
290
+ model: 'yi-34b-chat-0205',
291
291
  temperature: 0,
292
292
  },
293
293
  { callback: mockCallback, headers: mockHeaders },
@@ -335,7 +335,7 @@ describe('LobeZeroOneAI', () => {
335
335
  // 假设的测试函数调用,你可能需要根据实际情况调整
336
336
  await instance.chat({
337
337
  messages: [{ content: 'Hello', role: 'user' }],
338
- model: 'mistralai/mistral-7b-instruct:free',
338
+ model: 'yi-34b-chat-0205',
339
339
  temperature: 0,
340
340
  });
341
341
 
@@ -50,13 +50,17 @@ export default {
50
50
  sessionGroup: {
51
51
  config: '分组管理',
52
52
  confirmRemoveGroupAlert: '即将删除该分组,删除后该分组的助手将移动到默认列表,请确认你的操作',
53
+ createAgentSuccess: '助手创建成功',
53
54
  createGroup: '添加新分组',
54
- createSuccess: '创建成功',
55
+ createSuccess: '分组创建成功',
56
+ creatingAgent: '助手创建中...',
55
57
  inputPlaceholder: '请输入分组名称...',
56
58
  moveGroup: '移动到分组',
57
59
  newGroup: '新分组',
58
60
  rename: '重命名分组',
59
61
  renameSuccess: '重命名成功',
62
+ sortSuccess: '重新排序成功',
63
+ sorting: '分组排序更新中...',
60
64
  tooLong: '分组名称长度需在 1-20 之内',
61
65
  },
62
66
  shareModal: {
@@ -126,6 +130,6 @@ export default {
126
130
  dragDesc: '拖拽文件到这里,支持上传多个图片。按住 Shift 直接发送图片',
127
131
  dragFileDesc: '拖拽图片和文件到这里,支持上传多个图片和文件。按住 Shift 直接发送图片或文件',
128
132
  dragFileTitle: '上传文件',
129
- dragTitle: '上传图片'
133
+ dragTitle: '上传图片',
130
134
  },
131
135
  };
@@ -1,6 +1,7 @@
1
1
  export default {
2
2
  button: {
3
3
  import: '导入配置',
4
+ market: '逛逛市场',
4
5
  start: '立即开始',
5
6
  },
6
7
  header: '欢迎使用',
@@ -47,7 +47,6 @@
47
47
  {
48
48
  "displayName": "LLaVA 7B",
49
49
  "enabled": true,
50
- "functionCall": false,
51
50
  "id": "llava",
52
51
  "tokens": 4000,
53
52
  "vision": true
@@ -65,31 +65,15 @@ class ConfigService {
65
65
 
66
66
  case 'all': {
67
67
  await this.importSettings(config.state.settings);
68
-
69
- const sessionGroups = await this.importSessionGroups(config.state.sessionGroups);
70
-
71
- const [sessions, messages, topics] = await Promise.all([
72
- this.importSessions(config.state.sessions),
73
- this.importMessages(config.state.messages),
74
- this.importTopics(config.state.topics),
75
- ]);
76
-
77
- return {
78
- messages: this.mapImportResult(messages),
79
- sessionGroups: this.mapImportResult(sessionGroups),
80
- sessions: this.mapImportResult(sessions),
81
- topics: this.mapImportResult(topics),
82
- };
83
68
  }
69
+ // all and sessions have the same data process, so we can fall through
84
70
 
71
+ // eslint-disable-next-line no-fallthrough
85
72
  case 'sessions': {
86
73
  const sessionGroups = await this.importSessionGroups(config.state.sessionGroups);
87
-
88
- const [sessions, messages, topics] = await Promise.all([
89
- this.importSessions(config.state.sessions),
90
- this.importMessages(config.state.messages),
91
- this.importTopics(config.state.topics),
92
- ]);
74
+ const sessions = await this.importSessions(config.state.sessions);
75
+ const topics = await this.importTopics(config.state.topics);
76
+ const messages = await this.importMessages(config.state.messages);
93
77
 
94
78
  return {
95
79
  messages: this.mapImportResult(messages),
@@ -62,4 +62,14 @@ export class ClientService implements IMessageService {
62
62
  async removeAllMessages() {
63
63
  return MessageModel.clearTable();
64
64
  }
65
+
66
+ async hasMessages() {
67
+ const number = await this.countMessages();
68
+ return number > 0;
69
+ }
70
+
71
+ async messageCountToCheckTrace() {
72
+ const number = await this.countMessages();
73
+ return number >= 4;
74
+ }
65
75
  }
@@ -9,16 +9,4 @@ import { ClientService } from './client';
9
9
 
10
10
  export type { CreateMessageParams } from './type';
11
11
 
12
- class MessageService extends ClientService {
13
- async hasMessages() {
14
- const number = await this.countMessages();
15
- return number > 0;
16
- }
17
-
18
- async messageCountToCheckTrace() {
19
- const number = await this.countMessages();
20
- return number >= 4;
21
- }
22
- }
23
-
24
- export const messageService = new MessageService();
12
+ export const messageService = new ClientService();
@@ -30,4 +30,7 @@ export interface IMessageService {
30
30
  removeMessage(id: string): Promise<any>;
31
31
  removeMessages(assistantId: string, topicId?: string): Promise<any>;
32
32
  removeAllMessages(): Promise<any>;
33
+
34
+ hasMessages(): Promise<boolean>;
35
+ messageCountToCheckTrace(): Promise<boolean>;
33
36
  }
@@ -64,6 +64,9 @@ export class ClientService implements ISessionService {
64
64
  async countSessions() {
65
65
  return SessionModel.count();
66
66
  }
67
+ async hasSessions() {
68
+ return (await this.countSessions()) === 0;
69
+ }
67
70
 
68
71
  async searchSessions(keyword: string) {
69
72
  return SessionModel.queryByKeyword(keyword);
@@ -2,11 +2,13 @@
2
2
  import { DeepPartial } from 'utility-types';
3
3
 
4
4
  import { LobeAgentConfig } from '@/types/agent';
5
+ import { BatchTaskResult } from '@/types/service';
5
6
  import {
6
7
  ChatSessionList,
7
8
  LobeAgentSession,
8
9
  LobeSessionType,
9
10
  LobeSessions,
11
+ SessionGroupId,
10
12
  SessionGroupItem,
11
13
  SessionGroups,
12
14
  } from '@/types/session';
@@ -19,9 +21,13 @@ export interface ISessionService {
19
21
  getGroupedSessions(): Promise<ChatSessionList>;
20
22
  getSessionsByType(type: 'agent' | 'group' | 'all'): Promise<LobeSessions>;
21
23
  countSessions(): Promise<number>;
24
+ hasSessions(): Promise<boolean>;
22
25
  searchSessions(keyword: string): Promise<LobeSessions>;
23
26
 
24
- updateSession(id: string, data: Partial<Pick<LobeAgentSession, 'group' | 'meta'>>): Promise<any>;
27
+ updateSession(
28
+ id: string,
29
+ data: Partial<{ group?: SessionGroupId; pinned?: boolean }>,
30
+ ): Promise<any>;
25
31
  updateSessionConfig(id: string, config: DeepPartial<LobeAgentConfig>): Promise<any>;
26
32
 
27
33
  removeSession(id: string): Promise<any>;
@@ -32,7 +38,7 @@ export interface ISessionService {
32
38
  // ************************************** //
33
39
 
34
40
  createSessionGroup(name: string, sort?: number): Promise<string>;
35
- batchCreateSessionGroups(groups: SessionGroups): Promise<any>;
41
+ batchCreateSessionGroups(groups: SessionGroups): Promise<BatchTaskResult>;
36
42
 
37
43
  getSessionGroups(): Promise<SessionGroupItem[]>;
38
44
 
@@ -5,7 +5,7 @@ import { CreateTopicParams, ITopicService, QueryTopicParams } from './type';
5
5
 
6
6
  export class ClientService implements ITopicService {
7
7
  async createTopic(params: CreateTopicParams): Promise<string> {
8
- const item = await TopicModel.create(params);
8
+ const item = await TopicModel.create(params as any);
9
9
 
10
10
  if (!item) {
11
11
  throw new Error('topic create Error');
@@ -1,10 +1,11 @@
1
1
  /* eslint-disable typescript-sort-keys/interface */
2
+ import { BatchTaskResult } from '@/types/service';
2
3
  import { ChatTopic } from '@/types/topic';
3
4
 
4
5
  export interface CreateTopicParams {
5
6
  favorite?: boolean;
6
7
  messages?: string[];
7
- sessionId: string;
8
+ sessionId?: string | null;
8
9
  title: string;
9
10
  }
10
11
 
@@ -16,7 +17,7 @@ export interface QueryTopicParams {
16
17
 
17
18
  export interface ITopicService {
18
19
  createTopic(params: CreateTopicParams): Promise<string>;
19
- batchCreateTopics(importTopics: ChatTopic[]): Promise<any>;
20
+ batchCreateTopics(importTopics: ChatTopic[]): Promise<BatchTaskResult>;
20
21
  cloneTopic(id: string, newTitle?: string): Promise<string>;
21
22
 
22
23
  getTopics(params: QueryTopicParams): Promise<ChatTopic[]>;
@@ -110,7 +110,6 @@ describe('LLMSettingsSliceAction', () => {
110
110
  // Assert that setModelProviderConfig was not called
111
111
  expect(ollamaList?.chatModels.find((c) => c.id === 'llava')).toEqual({
112
112
  displayName: 'LLaVA 7B',
113
- functionCall: false,
114
113
  enabled: true,
115
114
  id: 'llava',
116
115
  tokens: 4000,
@@ -85,7 +85,7 @@ describe('modelProviderSelectors', () => {
85
85
  });
86
86
 
87
87
  describe('modelEnabledFiles', () => {
88
- it.skip('should return false if the model does not have file ability', () => {
88
+ it('should return false if the model does not have file ability', () => {
89
89
  const enabledFiles = modelProviderSelectors.isModelEnabledFiles('gpt-4-vision-preview')(
90
90
  useGlobalStore.getState(),
91
91
  );
@@ -1,5 +1,4 @@
1
1
  import { t } from 'i18next';
2
- import { produce } from 'immer';
3
2
  import useSWR, { SWRResponse, mutate } from 'swr';
4
3
  import { DeepPartial } from 'utility-types';
5
4
  import { StateCreator } from 'zustand/vanilla';
@@ -11,12 +10,20 @@ import { sessionService } from '@/services/session';
11
10
  import { useGlobalStore } from '@/store/global';
12
11
  import { settingsSelectors } from '@/store/global/selectors';
13
12
  import { SessionStore } from '@/store/session';
14
- import { ChatSessionList, LobeAgentSession, LobeSessionType, LobeSessions } from '@/types/session';
13
+ import {
14
+ ChatSessionList,
15
+ LobeAgentSession,
16
+ LobeSessionGroups,
17
+ LobeSessionType,
18
+ LobeSessions,
19
+ SessionGroupId,
20
+ } from '@/types/session';
15
21
  import { merge } from '@/utils/merge';
16
22
  import { setNamespace } from '@/utils/storeDebug';
17
23
 
18
24
  import { agentSelectors } from '../agent/selectors';
19
25
  import { initLobeSession } from './initialState';
26
+ import { SessionDispatch, sessionsReducer } from './reducers';
20
27
  import { sessionSelectors } from './selectors';
21
28
 
22
29
  const n = setNamespace('session');
@@ -24,6 +31,7 @@ const n = setNamespace('session');
24
31
  const FETCH_SESSIONS_KEY = 'fetchSessions';
25
32
  const SEARCH_SESSIONS_KEY = 'searchSessions';
26
33
 
34
+ /* eslint-disable typescript-sort-keys/interface */
27
35
  export interface SessionAction {
28
36
  /**
29
37
  * active the session
@@ -44,6 +52,8 @@ export interface SessionAction {
44
52
  isSwitchSession?: boolean,
45
53
  ) => Promise<string>;
46
54
  duplicateSession: (id: string) => Promise<void>;
55
+ updateSessionGroupId: (sessionId: string, groupId: string) => Promise<void>;
56
+
47
57
  /**
48
58
  * Pins or unpins a session.
49
59
  */
@@ -52,17 +62,26 @@ export interface SessionAction {
52
62
  * re-fetch the data
53
63
  */
54
64
  refreshSessions: (params?: SWRRefreshParams<ChatSessionList>) => Promise<void>;
55
-
56
65
  /**
57
66
  * remove session
58
67
  * @param id - sessionId
59
68
  */
60
- removeSession: (id: string) => void;
61
- /**
62
- * A custom hook that uses SWR to fetch sessions data.
63
- */
69
+ removeSession: (id: string) => Promise<void>;
70
+
64
71
  useFetchSessions: () => SWRResponse<ChatSessionList>;
65
72
  useSearchSessions: (keyword?: string) => SWRResponse<any>;
73
+
74
+ internal_dispatchSessions: (payload: SessionDispatch) => void;
75
+ internal_updateSession: (
76
+ id: string,
77
+ data: Partial<{ group?: SessionGroupId; meta?: any; pinned?: boolean }>,
78
+ ) => Promise<void>;
79
+ internal_processSessions: (
80
+ sessions: LobeSessions,
81
+ customGroups: LobeSessionGroups,
82
+ actions?: string,
83
+ ) => void;
84
+ /* eslint-enable */
66
85
  }
67
86
 
68
87
  export const createSessionSlice: StateCreator<
@@ -101,7 +120,6 @@ export const createSessionSlice: StateCreator<
101
120
 
102
121
  return id;
103
122
  },
104
-
105
123
  duplicateSession: async (id) => {
106
124
  const { activeSession, refreshSessions } = get();
107
125
  const session = sessionSelectors.getSessionById(id)(get());
@@ -135,63 +153,12 @@ export const createSessionSlice: StateCreator<
135
153
  activeSession(newId);
136
154
  },
137
155
 
138
- pinSession: async (sessionId, pinned) => {
139
- await get().refreshSessions({
140
- action: async () => {
141
- await sessionService.updateSession(sessionId, { pinned });
142
- },
143
- // 乐观更新
144
- optimisticData: produce((draft) => {
145
- if (!draft) return;
146
-
147
- const session = draft.all.find((i) => i.id === sessionId);
148
- if (!session) return;
149
-
150
- session.pinned = pinned;
151
-
152
- if (pinned) {
153
- draft.pinned.unshift(session);
154
-
155
- if (session.group === 'default') {
156
- const index = draft.default.findIndex((i) => i.id === sessionId);
157
- draft.default.splice(index, 1);
158
- } else {
159
- const customGroup = draft.customGroup.find((group) => group.id === session.group);
160
-
161
- if (customGroup) {
162
- const index = customGroup.children.findIndex((i) => i.id === sessionId);
163
- customGroup.children.splice(index, 1);
164
- }
165
- }
166
- } else {
167
- const index = draft.pinned.findIndex((i) => i.id === sessionId);
168
- if (index !== -1) {
169
- draft.pinned.splice(index, 1);
170
- }
171
-
172
- if (session.group === 'default') {
173
- draft.default.push(session);
174
- } else {
175
- const customGroup = draft.customGroup.find((group) => group.id === session.group);
176
- if (customGroup) {
177
- customGroup.children.push(session);
178
- }
179
- }
180
- }
181
- }),
182
- });
156
+ pinSession: async (id, pinned) => {
157
+ await get().internal_updateSession(id, { pinned });
183
158
  },
184
159
 
185
- refreshSessions: async (params) => {
186
- if (params) {
187
- // @ts-ignore
188
- await mutate(FETCH_SESSIONS_KEY, params.action, {
189
- optimisticData: params.optimisticData,
190
- // we won't need to make the action's data go into cache ,or the display will be
191
- // old -> optimistic -> undefined -> new
192
- populateCache: false,
193
- });
194
- } else await mutate(FETCH_SESSIONS_KEY);
160
+ refreshSessions: async () => {
161
+ await mutate(FETCH_SESSIONS_KEY);
195
162
  },
196
163
 
197
164
  removeSession: async (sessionId) => {
@@ -204,8 +171,10 @@ export const createSessionSlice: StateCreator<
204
171
  }
205
172
  },
206
173
 
207
- // TODO: 这里的逻辑需要优化,后续不应该是直接请求一个大的 sessions 数据
208
- // 最好拆成一个 all 请求,然后在前端完成 groupBy 的分组逻辑
174
+ updateSessionGroupId: async (sessionId, group) => {
175
+ await get().internal_updateSession(sessionId, { group });
176
+ },
177
+
209
178
  useFetchSessions: () =>
210
179
  useClientDataSWR<ChatSessionList>(FETCH_SESSIONS_KEY, sessionService.getGroupedSessions, {
211
180
  onSuccess: (data) => {
@@ -217,20 +186,14 @@ export const createSessionSlice: StateCreator<
217
186
  // TODO:后续的根本解法应该是解除 inbox 和 session 的数据耦合
218
187
  // 避免互相依赖的情况出现
219
188
 
220
- set(
221
- {
222
- customSessionGroups: data.customGroup,
223
- defaultSessions: data.default,
224
- isSessionsFirstFetchFinished: true,
225
- pinnedSessions: data.pinned,
226
- sessions: data.all,
227
- },
228
- false,
229
- n('useFetchSessions/onSuccess', data),
189
+ get().internal_processSessions(
190
+ data.sessions,
191
+ data.sessionGroups,
192
+ n('useFetchSessions/updateData') as any,
230
193
  );
194
+ set({ isSessionsFirstFetchFinished: true }, false, n('useFetchSessions/onSuccess', data));
231
195
  },
232
196
  }),
233
-
234
197
  useSearchSessions: (keyword) =>
235
198
  useSWR<LobeSessions>(
236
199
  [SEARCH_SESSIONS_KEY, keyword],
@@ -241,4 +204,39 @@ export const createSessionSlice: StateCreator<
241
204
  },
242
205
  { revalidateOnFocus: false, revalidateOnMount: false },
243
206
  ),
207
+
208
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
209
+ internal_dispatchSessions: (payload) => {
210
+ const nextSessions = sessionsReducer(get().sessions, payload);
211
+ get().internal_processSessions(nextSessions, get().sessionGroups);
212
+ },
213
+ internal_updateSession: async (id, data) => {
214
+ get().internal_dispatchSessions({ type: 'updateSession', id, value: data });
215
+
216
+ await sessionService.updateSession(id, data);
217
+ await get().refreshSessions();
218
+ },
219
+ internal_processSessions: (sessions, sessionGroups) => {
220
+ const customGroups = sessionGroups.map((item) => ({
221
+ ...item,
222
+ children: sessions.filter((i) => i.group === item.id && !i.pinned),
223
+ }));
224
+
225
+ const defaultGroup = sessions.filter(
226
+ (item) => (!item.group || item.group === 'default') && !item.pinned,
227
+ );
228
+ const pinnedGroup = sessions.filter((item) => item.pinned);
229
+
230
+ set(
231
+ {
232
+ customSessionGroups: customGroups,
233
+ defaultSessions: defaultGroup,
234
+ pinnedSessions: pinnedGroup,
235
+ sessionGroups,
236
+ sessions,
237
+ },
238
+ false,
239
+ n('processSessions'),
240
+ );
241
+ },
244
242
  });
@@ -1,6 +1,11 @@
1
1
  import { DEFAULT_AGENT_META } from '@/const/meta';
2
2
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
3
- import { CustomSessionGroup, LobeAgentSession, LobeSessionType } from '@/types/session';
3
+ import {
4
+ CustomSessionGroup,
5
+ LobeAgentSession,
6
+ LobeSessionGroups,
7
+ LobeSessionType,
8
+ } from '@/types/session';
4
9
 
5
10
  export const initLobeSession: LobeAgentSession = {
6
11
  config: DEFAULT_AGENT_CONFIG,
@@ -24,6 +29,7 @@ export interface SessionState {
24
29
  isSessionsFirstFetchFinished: boolean;
25
30
  pinnedSessions: LobeAgentSession[];
26
31
  searchKeywords: string;
32
+ sessionGroups: LobeSessionGroups;
27
33
  sessionSearchKeywords?: string;
28
34
  /**
29
35
  * it means defaultSessions
@@ -40,5 +46,6 @@ export const initialSessionState: SessionState = {
40
46
  isSessionsFirstFetchFinished: false,
41
47
  pinnedSessions: [],
42
48
  searchKeywords: '',
49
+ sessionGroups: [],
43
50
  sessions: [],
44
51
  };