@lobehub/chat 0.18.2 → 0.20.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 (31) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/package.json +1 -1
  3. package/src/locales/default/common.ts +4 -2
  4. package/src/pages/chat/[id]/Config/SideBar.tsx +10 -6
  5. package/src/pages/chat/[id]/Config/Topic.tsx +27 -0
  6. package/src/pages/chat/[id]/Config/TopicItem.tsx +80 -0
  7. package/src/pages/chat/[id]/Conversation/Input/index.tsx +7 -4
  8. package/src/pages/chat/[id]/Header.tsx +14 -10
  9. package/src/pages/chat/layout.tsx +10 -4
  10. package/src/prompts/chat.ts +19 -0
  11. package/src/store/session/selectors.ts +1 -1
  12. package/src/store/session/slices/agentConfig/selectors.ts +4 -0
  13. package/src/store/session/slices/chat/actions/index.ts +21 -0
  14. package/src/store/session/slices/chat/{action.ts → actions/message.ts} +34 -32
  15. package/src/store/session/slices/chat/actions/topic.ts +98 -0
  16. package/src/store/session/slices/chat/index.ts +1 -1
  17. package/src/store/session/slices/chat/initialState.ts +2 -5
  18. package/src/store/session/slices/chat/{messageReducer.test.ts → reducers/message.test.ts} +93 -1
  19. package/src/store/session/slices/chat/{messageReducer.ts → reducers/message.ts} +16 -1
  20. package/src/store/session/slices/chat/reducers/topic.test.ts +179 -0
  21. package/src/store/session/slices/chat/reducers/topic.ts +59 -0
  22. package/src/store/session/slices/chat/selectors/chat.ts +8 -4
  23. package/src/store/session/slices/chat/selectors/index.ts +5 -0
  24. package/src/store/session/slices/chat/selectors/topic.ts +16 -0
  25. package/src/store/session/slices/chat/selectors/utils.test.ts +82 -2
  26. package/src/store/session/slices/chat/selectors/utils.ts +8 -2
  27. package/src/store/session/slices/session/reducers/session.ts +25 -1
  28. package/src/types/chatMessage.ts +4 -2
  29. package/src/types/session.ts +2 -1
  30. package/src/types/topic.ts +8 -0
  31. package/src/pages/chat/[id]/Config/ConfigCell.tsx +0 -68
@@ -1,12 +1,9 @@
1
1
  export interface ChatState {
2
+ activeTopicId?: string;
2
3
  chatLoading: boolean;
3
- editingMessageId?: string;
4
+ topicLoadingId?: string;
4
5
  }
5
6
 
6
7
  export const initialChatState: ChatState = {
7
8
  chatLoading: false,
8
-
9
- // activeId: null,
10
- // searchKeywords: '',
11
- //
12
9
  };
@@ -1,6 +1,6 @@
1
1
  import { ChatMessageMap } from '@/types/chatMessage';
2
2
 
3
- import { MessageDispatch, messagesReducer } from './messageReducer';
3
+ import { MessageDispatch, messagesReducer } from './message';
4
4
 
5
5
  describe('messagesReducer', () => {
6
6
  let initialState: ChatMessageMap;
@@ -116,6 +116,30 @@ describe('messagesReducer', () => {
116
116
  quotaId: 'message2',
117
117
  });
118
118
  });
119
+
120
+ it('should use the provided parentId and quotaId when adding a new message', () => {
121
+ const payload: MessageDispatch = {
122
+ type: 'addMessage',
123
+ message: 'New Message',
124
+ id: 'message3',
125
+ role: 'user',
126
+ parentId: 'message1',
127
+ quotaId: 'message2',
128
+ };
129
+
130
+ const newState = messagesReducer(initialState, payload);
131
+
132
+ expect(newState.message3).toEqual({
133
+ id: 'message3',
134
+ content: 'New Message',
135
+ meta: {},
136
+ createAt: expect.any(Number),
137
+ updateAt: expect.any(Number),
138
+ role: 'user',
139
+ parentId: 'message1',
140
+ quotaId: 'message2',
141
+ });
142
+ });
119
143
  });
120
144
 
121
145
  describe('deleteMessage', () => {
@@ -142,6 +166,17 @@ describe('messagesReducer', () => {
142
166
 
143
167
  expect(newState).toEqual(initialState);
144
168
  });
169
+
170
+ it('should not modify the state if the specified message does not exist', () => {
171
+ const payload: MessageDispatch = {
172
+ type: 'deleteMessage',
173
+ id: 'nonexistentMessage',
174
+ };
175
+
176
+ const newState = messagesReducer(initialState, payload);
177
+
178
+ expect(newState).toEqual(initialState);
179
+ });
145
180
  });
146
181
 
147
182
  describe('updateMessage', () => {
@@ -171,6 +206,19 @@ describe('messagesReducer', () => {
171
206
 
172
207
  expect(newState).toEqual(initialState);
173
208
  });
209
+
210
+ it('should not modify the state if the specified message does not exist', () => {
211
+ const payload: MessageDispatch = {
212
+ type: 'updateMessage',
213
+ id: 'nonexistentMessage',
214
+ key: 'content',
215
+ value: 'Updated Message',
216
+ };
217
+
218
+ const newState = messagesReducer(initialState, payload);
219
+
220
+ expect(newState).toEqual(initialState);
221
+ });
174
222
  });
175
223
 
176
224
  describe('updateMessageExtra', () => {
@@ -200,6 +248,19 @@ describe('messagesReducer', () => {
200
248
 
201
249
  expect(newState).toEqual(initialState);
202
250
  });
251
+
252
+ it('should not modify the state if the specified message does not exist', () => {
253
+ const payload: MessageDispatch = {
254
+ type: 'updateMessageExtra',
255
+ id: 'nonexistentMessage',
256
+ key: 'translate',
257
+ value: { target: 'en', to: 'zh' },
258
+ };
259
+
260
+ const newState = messagesReducer(initialState, payload);
261
+
262
+ expect(newState).toEqual(initialState);
263
+ });
203
264
  });
204
265
 
205
266
  describe('resetMessages', () => {
@@ -212,6 +273,37 @@ describe('messagesReducer', () => {
212
273
 
213
274
  expect(newState).toEqual({});
214
275
  });
276
+
277
+ it('should delete messages with the specified topicId', () => {
278
+ const initialState = {
279
+ message1: {
280
+ id: 'message1',
281
+ content: 'Hello World',
282
+ createAt: 1629264000000,
283
+ updateAt: 1629264000000,
284
+ role: 'user',
285
+ topicId: 'topic1',
286
+ },
287
+ message2: {
288
+ id: 'message2',
289
+ content: 'How are you?',
290
+ createAt: 1629264000000,
291
+ updateAt: 1629264000000,
292
+ role: 'system',
293
+ },
294
+ } as unknown as ChatMessageMap;
295
+
296
+ const payload: MessageDispatch = {
297
+ type: 'resetMessages',
298
+ topicId: 'topic1',
299
+ };
300
+
301
+ const newState = messagesReducer(initialState, payload);
302
+
303
+ expect(Object.keys(newState)).toHaveLength(1);
304
+ expect(newState).not.toHaveProperty('message1');
305
+ expect(newState).toHaveProperty('message2');
306
+ });
215
307
  });
216
308
 
217
309
  describe('unimplemented type', () => {
@@ -21,6 +21,7 @@ interface DeleteMessage {
21
21
  }
22
22
 
23
23
  interface ResetMessages {
24
+ topicId?: string;
24
25
  type: 'resetMessages';
25
26
  }
26
27
 
@@ -101,7 +102,21 @@ export const messagesReducer = (
101
102
  }
102
103
 
103
104
  case 'resetMessages': {
104
- return {};
105
+ return produce(state, (draftState) => {
106
+ const { topicId } = payload;
107
+
108
+ const messages = Object.values(draftState).filter((message) => {
109
+ // 如果没有 topicId,说明是清空默认对话里的消息
110
+ if (!topicId) return !message.topicId;
111
+
112
+ return message.topicId === topicId;
113
+ });
114
+
115
+ // 删除上述找到的消息
116
+ for (const message of messages) {
117
+ delete draftState[message.id];
118
+ }
119
+ });
105
120
  }
106
121
 
107
122
  default: {
@@ -0,0 +1,179 @@
1
+ import { produce } from 'immer';
2
+
3
+ import { ChatTopic, ChatTopicMap } from '@/types/topic';
4
+
5
+ import { ChatTopicDispatch, topicReducer } from './topic';
6
+
7
+ describe('topicReducer', () => {
8
+ let state: ChatTopicMap;
9
+
10
+ beforeEach(() => {
11
+ state = {};
12
+ });
13
+
14
+ describe('addChatTopic', () => {
15
+ it('should add a new ChatTopic object to state', () => {
16
+ const payload: ChatTopicDispatch = {
17
+ type: 'addChatTopic',
18
+ topic: {
19
+ id: '1',
20
+ title: 'Test Topic',
21
+ createAt: Date.now(),
22
+ updateAt: Date.now(),
23
+ },
24
+ };
25
+
26
+ const newState = topicReducer(state, payload);
27
+
28
+ expect(newState).toMatchObject({
29
+ '1': payload.topic,
30
+ });
31
+ });
32
+
33
+ it('should add a ChatTopic object with correct id', () => {
34
+ const payload: ChatTopicDispatch = {
35
+ type: 'addChatTopic',
36
+ topic: {
37
+ id: '1',
38
+ title: 'Test Topic',
39
+ createAt: Date.now(),
40
+ updateAt: Date.now(),
41
+ },
42
+ };
43
+
44
+ const newState = topicReducer(state, payload);
45
+
46
+ expect(newState['1']).toBeDefined();
47
+ });
48
+
49
+ it('should add a ChatTopic object with correct properties', () => {
50
+ const payload: ChatTopicDispatch = {
51
+ type: 'addChatTopic',
52
+ topic: {
53
+ id: '1',
54
+ title: 'Test Topic',
55
+ createAt: Date.now(),
56
+ updateAt: Date.now(),
57
+ },
58
+ };
59
+
60
+ const newState = topicReducer(state, payload);
61
+
62
+ expect(newState['1']).toMatchObject(payload.topic);
63
+ });
64
+ });
65
+
66
+ describe('updateChatTopic', () => {
67
+ it('should update the ChatTopic object in state', () => {
68
+ const topic: ChatTopic = {
69
+ id: '1',
70
+ title: 'Test Topic',
71
+ createAt: Date.now(),
72
+ updateAt: Date.now(),
73
+ };
74
+
75
+ state['1'] = topic;
76
+
77
+ const payload: ChatTopicDispatch = {
78
+ type: 'updateChatTopic',
79
+ id: '1',
80
+ key: 'title',
81
+ value: 'Updated Topic',
82
+ };
83
+
84
+ const newState = topicReducer(state, payload);
85
+
86
+ expect(newState['1'].title).toBe('Updated Topic');
87
+ });
88
+
89
+ it('should update the ChatTopic object with correct properties', () => {
90
+ const topic: ChatTopic = {
91
+ id: '1',
92
+ title: 'Test Topic',
93
+ createAt: Date.now() - 1,
94
+ updateAt: Date.now() - 1, // 设定比当前时间前面一点
95
+ };
96
+
97
+ state['1'] = topic;
98
+
99
+ const payload: ChatTopicDispatch = {
100
+ type: 'updateChatTopic',
101
+ id: '1',
102
+ key: 'title',
103
+ value: 'Updated Topic',
104
+ };
105
+
106
+ const newState = topicReducer(state, payload);
107
+
108
+ expect(newState['1'].updateAt).toBeGreaterThan(topic.updateAt);
109
+ });
110
+ });
111
+
112
+ describe('deleteChatTopic', () => {
113
+ it('should delete the specified ChatTopic object from state', () => {
114
+ const topic: ChatTopic = {
115
+ id: '1',
116
+ title: 'Test Topic',
117
+ createAt: Date.now(),
118
+ updateAt: Date.now(),
119
+ };
120
+
121
+ state['1'] = topic;
122
+
123
+ const payload: ChatTopicDispatch = {
124
+ type: 'deleteChatTopic',
125
+ id: '1',
126
+ };
127
+
128
+ const newState = topicReducer(state, payload);
129
+
130
+ expect(newState['1']).toBeUndefined();
131
+ });
132
+ });
133
+
134
+ describe('default', () => {
135
+ it('should return the original state object', () => {
136
+ const payload = {
137
+ type: 'unknown',
138
+ } as unknown as ChatTopicDispatch;
139
+
140
+ const newState = topicReducer(state, payload);
141
+
142
+ expect(newState).toBe(state);
143
+ });
144
+ });
145
+
146
+ describe('produce', () => {
147
+ it('should generate immutable state object', () => {
148
+ const payload: ChatTopicDispatch = {
149
+ type: 'addChatTopic',
150
+ topic: {
151
+ id: '1',
152
+ title: 'Test Topic',
153
+ createAt: Date.now(),
154
+ updateAt: Date.now(),
155
+ },
156
+ };
157
+
158
+ const newState = topicReducer(state, payload);
159
+
160
+ expect(newState).not.toBe(state);
161
+ });
162
+
163
+ it('should not modify the original state object', () => {
164
+ const payload: ChatTopicDispatch = {
165
+ type: 'addChatTopic',
166
+ topic: {
167
+ id: '1',
168
+ title: 'Test Topic',
169
+ createAt: Date.now(),
170
+ updateAt: Date.now(),
171
+ },
172
+ };
173
+
174
+ const newState = topicReducer(state, payload);
175
+
176
+ expect(state).toMatchObject({});
177
+ });
178
+ });
179
+ });
@@ -0,0 +1,59 @@
1
+ import { produce } from 'immer';
2
+
3
+ import { ChatTopic, ChatTopicMap } from '@/types/topic';
4
+
5
+ interface AddChatTopicAction {
6
+ topic: ChatTopic;
7
+ type: 'addChatTopic';
8
+ }
9
+
10
+ interface UpdateChatTopicAction {
11
+ id: string;
12
+ key: keyof ChatTopic;
13
+ type: 'updateChatTopic';
14
+ value: any;
15
+ }
16
+
17
+ interface DeleteChatTopicAction {
18
+ id: string;
19
+ type: 'deleteChatTopic';
20
+ }
21
+
22
+ export type ChatTopicDispatch = AddChatTopicAction | UpdateChatTopicAction | DeleteChatTopicAction;
23
+
24
+ export const topicReducer = (state: ChatTopicMap, payload: ChatTopicDispatch): ChatTopicMap => {
25
+ switch (payload.type) {
26
+ case 'addChatTopic': {
27
+ return produce(state, (draftState) => {
28
+ draftState[payload.topic.id] = payload.topic;
29
+ });
30
+ }
31
+
32
+ case 'updateChatTopic': {
33
+ return produce(state, (draftState) => {
34
+ const { key, value, id } = payload;
35
+
36
+ if (!draftState[id]) return;
37
+
38
+ const topic = draftState[id];
39
+
40
+ if (value !== undefined) {
41
+ // @ts-ignore
42
+ topic[key] = value;
43
+
44
+ topic.updateAt = Date.now();
45
+ }
46
+ });
47
+ }
48
+
49
+ case 'deleteChatTopic': {
50
+ return produce(state, (draftState) => {
51
+ delete draftState[payload.id];
52
+ });
53
+ }
54
+
55
+ default: {
56
+ return state;
57
+ }
58
+ }
59
+ };
@@ -12,10 +12,14 @@ export const currentChats = (s: SessionStore): ChatMessage[] => {
12
12
  const session = sessionSelectors.currentSession(s);
13
13
  if (!session) return [];
14
14
 
15
- return organizeChats(session, {
16
- assistant: agentSelectors.currentAgentAvatar(s),
17
- user: useSettings.getState().settings.avatar || DEFAULT_USER_AVATAR,
18
- });
15
+ return organizeChats(
16
+ session,
17
+ {
18
+ assistant: agentSelectors.currentAgentAvatar(s),
19
+ user: useSettings.getState().settings.avatar || DEFAULT_USER_AVATAR,
20
+ },
21
+ s.activeTopicId,
22
+ );
19
23
  };
20
24
 
21
25
  export const systemRoleSel = (s: SessionStore): string => {
@@ -1,5 +1,6 @@
1
1
  import { currentChats } from './chat';
2
2
  import { chatsTokenCount, systemRoleTokenCount, totalTokenCount } from './token';
3
+ import { currentTopics } from './topic';
3
4
 
4
5
  export const chatSelectors = {
5
6
  chatsTokenCount,
@@ -7,3 +8,7 @@ export const chatSelectors = {
7
8
  systemRoleTokenCount,
8
9
  totalTokenCount,
9
10
  };
11
+
12
+ export const topicSelectors = {
13
+ currentTopics,
14
+ };
@@ -0,0 +1,16 @@
1
+ // 展示在聊天框中的消息
2
+ import { SessionStore, sessionSelectors } from '@/store/session';
3
+ import { ChatTopic } from '@/types/topic';
4
+
5
+ export const currentTopics = (s: SessionStore): ChatTopic[] => {
6
+ const session = sessionSelectors.currentSession(s);
7
+ if (!session) return [];
8
+
9
+ const topics = Object.values(session.topics || {});
10
+
11
+ // 按时间倒序
12
+ const favTopics = topics.filter((t) => t.favorite).sort((a, b) => b.updateAt - a.updateAt);
13
+ const defaultTopics = topics.filter((t) => !t.favorite).sort((a, b) => b.updateAt - a.updateAt);
14
+
15
+ return [...favTopics, ...defaultTopics];
16
+ };
@@ -1,5 +1,6 @@
1
- import { beforeEach } from 'vitest';
1
+ import { beforeEach, describe } from 'vitest';
2
2
 
3
+ import { ChatMessage } from '@/types/chatMessage';
3
4
  import { LobeAgentSession } from '@/types/session';
4
5
 
5
6
  import { organizeChats } from './utils';
@@ -67,7 +68,7 @@ describe('organizeChats', () => {
67
68
  });
68
69
 
69
70
  it('should filter out archived messages', () => {
70
- session.chats['2'].archive = true;
71
+ session.chats['2'].topicId = '123';
71
72
  const result = organizeChats(session, { assistant: '', user: '' });
72
73
  expect(result.length).toBe(2);
73
74
  expect(result[0].id).toBe('1');
@@ -261,4 +262,83 @@ describe('organizeChats', () => {
261
262
  },
262
263
  ]);
263
264
  });
265
+
266
+ const avatar = {
267
+ assistant: 'assistant-avatar',
268
+ user: 'user-avatar',
269
+ };
270
+
271
+ it('should organize chats in chronological order when topicId is not provided', () => {
272
+ const result = organizeChats(session, avatar);
273
+
274
+ expect(result.length).toBe(3);
275
+ expect(result[0].id).toBe('1');
276
+ expect(result[1].id).toBe('2');
277
+ expect(result[2].id).toBe('3');
278
+ });
279
+
280
+ it('should only return chats with specified topicId when topicId is provided', () => {
281
+ const result = organizeChats(session, avatar, 'topic-id');
282
+
283
+ expect(result.length).toBe(0);
284
+ });
285
+
286
+ describe('user Meta', () => {
287
+ it('should return correct meta for user role', () => {
288
+ const result = organizeChats(session, avatar);
289
+ const meta = result[1].meta;
290
+
291
+ expect(meta.avatar).toBe(avatar.user);
292
+ });
293
+
294
+ it('should return correct meta for assistant role', () => {
295
+ const result = organizeChats(session, avatar);
296
+ const meta = result[0].meta;
297
+
298
+ expect(meta.avatar).toBe(avatar.assistant);
299
+ expect(meta.title).toBeUndefined();
300
+ });
301
+
302
+ describe('should return correct meta for function role', () => {
303
+ it('找不到插件', () => {
304
+ const message = {
305
+ id: '4',
306
+ createAt: 1927785600004,
307
+ updateAt: 1927785600004,
308
+ role: 'function',
309
+ function_call: {
310
+ name: 'plugin-name',
311
+ },
312
+ } as ChatMessage;
313
+
314
+ session.chats[message.id] = message;
315
+
316
+ const result = organizeChats(session, avatar);
317
+ const meta = result[3].meta;
318
+
319
+ expect(meta.avatar).toBe('🧩');
320
+ expect(meta.title).toBe('plugin-unknown');
321
+ });
322
+
323
+ it('找到的插件', () => {
324
+ const message = {
325
+ id: '4',
326
+ createAt: 1927785600004,
327
+ updateAt: 1927785600004,
328
+ role: 'function',
329
+ function_call: {
330
+ name: 'realtimeWeather',
331
+ },
332
+ } as ChatMessage;
333
+
334
+ session.chats[message.id] = message;
335
+
336
+ const result = organizeChats(session, avatar);
337
+ const meta = result[3].meta;
338
+
339
+ expect(meta.avatar).toBe('☂️');
340
+ expect(meta.title).toBe('realtimeWeather');
341
+ });
342
+ });
343
+ });
264
344
  });
@@ -5,6 +5,7 @@ import { LobeAgentSession } from '@/types/session';
5
5
  export const organizeChats = (
6
6
  session: LobeAgentSession,
7
7
  avatar: { assistant: string; user: string },
8
+ topicId?: string,
8
9
  ) => {
9
10
  const getMeta = (message: ChatMessage) => {
10
11
  switch (message.role) {
@@ -38,8 +39,13 @@ export const organizeChats = (
38
39
  const basic = Object.values<ChatMessage>(session.chats)
39
40
  // 首先按照时间顺序排序,越早的在越前面
40
41
  .sort((pre, next) => pre.createAt - next.createAt)
41
- // 过滤掉已归档的消息,归档消息不应该出现在聊天框中
42
- .filter((m) => !m.archive)
42
+ .filter((m) => {
43
+ // 过滤掉包含 topicId 的消息,有主题的消息不应该出现在聊天框中
44
+ if (!topicId) return !m.topicId;
45
+
46
+ // 或者当话题 id 一致时,再展示话题
47
+ return m.topicId === topicId;
48
+ })
43
49
  // 映射头像关系
44
50
  .map((m) => {
45
51
  return {
@@ -3,6 +3,7 @@ import { produce } from 'immer';
3
3
  import { ChatMessageMap } from '@/types/chatMessage';
4
4
  import { MetaData } from '@/types/meta';
5
5
  import { LobeAgentConfig, LobeAgentSession, LobeSessions } from '@/types/session';
6
+ import { ChatTopicMap } from '@/types/topic';
6
7
 
7
8
  /**
8
9
  * @title 添加会话
@@ -37,6 +38,19 @@ interface UpdateSessionChat {
37
38
  type: 'updateSessionChat';
38
39
  }
39
40
 
41
+ /**
42
+ * @title 更新会话聊天上下文
43
+ */
44
+ interface UpdateSessionTopic {
45
+ /**
46
+ * 会话 ID
47
+ */
48
+ id: string;
49
+ topics: ChatTopicMap;
50
+
51
+ type: 'updateSessionTopic';
52
+ }
53
+
40
54
  interface UpdateSessionMeta {
41
55
  id: string;
42
56
  key: keyof MetaData;
@@ -55,7 +69,8 @@ export type SessionDispatch =
55
69
  | UpdateSessionChat
56
70
  | RemoveSession
57
71
  | UpdateSessionMeta
58
- | UpdateSessionAgentConfig;
72
+ | UpdateSessionAgentConfig
73
+ | UpdateSessionTopic;
59
74
 
60
75
  export const sessionsReducer = (state: LobeSessions, payload: SessionDispatch): LobeSessions => {
61
76
  switch (payload.type) {
@@ -96,6 +111,15 @@ export const sessionsReducer = (state: LobeSessions, payload: SessionDispatch):
96
111
  });
97
112
  }
98
113
 
114
+ case 'updateSessionTopic': {
115
+ return produce(state, (draft) => {
116
+ const chat = draft[payload.id];
117
+ if (!chat) return;
118
+
119
+ chat.topics = payload.topics;
120
+ });
121
+ }
122
+
99
123
  case 'updateSessionConfig': {
100
124
  return produce(state, (draft) => {
101
125
  const { id, config } = payload;
@@ -14,8 +14,6 @@ export interface ChatMessageError {
14
14
  }
15
15
 
16
16
  export interface ChatMessage extends BaseDataModel {
17
- archive?: boolean;
18
-
19
17
  /**
20
18
  * @title 内容
21
19
  * @description 消息内容
@@ -43,6 +41,10 @@ export interface ChatMessage extends BaseDataModel {
43
41
  * @description 消息发送者的角色
44
42
  */
45
43
  role: LLMRoleType;
44
+ /**
45
+ * 保存到主题的消息
46
+ */
47
+ topicId?: string;
46
48
  }
47
49
 
48
50
  export type ChatMessageMap = Record<string, ChatMessage>;
@@ -1,6 +1,7 @@
1
1
  import { ChatMessageMap } from './chatMessage';
2
2
  import { LLMExample, LLMParams, LanguageModel } from './llm';
3
3
  import { BaseDataModel } from './meta';
4
+ import { ChatTopicMap } from './topic';
4
5
 
5
6
  export enum LobeSessionType {
6
7
  /**
@@ -18,7 +19,7 @@ interface LobeSessionBase extends BaseDataModel {
18
19
  * 聊天记录
19
20
  */
20
21
  chats: ChatMessageMap;
21
-
22
+ topics?: ChatTopicMap;
22
23
  /**
23
24
  * 每个会话的类别
24
25
  */
@@ -0,0 +1,8 @@
1
+ import { BaseDataModel } from '@/types/meta';
2
+
3
+ export interface ChatTopic extends Omit<BaseDataModel, 'meta'> {
4
+ favorite?: boolean;
5
+ title: string;
6
+ }
7
+
8
+ export type ChatTopicMap = Record<string, ChatTopic>;