@lobehub/chat 0.43.0 → 0.44.1

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 (76) hide show
  1. package/.github/workflows/release.yml +1 -1
  2. package/CHANGELOG.md +66 -0
  3. package/package.json +5 -2
  4. package/src/const/meta.ts +3 -0
  5. package/src/const/session.ts +1 -0
  6. package/src/helpers/export.ts +15 -9
  7. package/src/layout/index.tsx +5 -5
  8. package/src/locales/default/common.ts +8 -1
  9. package/src/pages/chat/SessionList/Inbox.tsx +25 -0
  10. package/src/pages/chat/SessionList/List/Item/Actions.tsx +112 -0
  11. package/src/pages/chat/SessionList/List/Item/index.tsx +115 -0
  12. package/src/pages/chat/SessionList/List/{SkeletonItem.tsx → SkeletonList.tsx} +2 -2
  13. package/src/pages/chat/SessionList/List/index.tsx +7 -7
  14. package/src/pages/chat/SessionList/index.tsx +13 -1
  15. package/src/pages/chat/[id]/index.page.tsx +7 -9
  16. package/src/pages/chat/[id]/layout.tsx +7 -23
  17. package/src/pages/chat/[id]/setting/index.page.tsx +3 -3
  18. package/src/pages/chat/features/Conversation/ChatList/SkeletonList.tsx +43 -0
  19. package/src/pages/chat/{[id] → features}/Conversation/ChatList/index.tsx +35 -5
  20. package/src/pages/chat/{[id] → features}/Conversation/Input/index.tsx +1 -1
  21. package/src/pages/chat/{[id] → features}/Header/index.tsx +32 -34
  22. package/src/pages/chat/features/Sidebar/Inner.tsx +42 -0
  23. package/src/pages/chat/{[id]/Sidebar/Inner.tsx → features/Sidebar/SystemRole.tsx} +7 -37
  24. package/src/pages/chat/{[id] → features}/Sidebar/index.tsx +8 -4
  25. package/src/pages/chat/index.page.tsx +39 -1
  26. package/src/pages/chat/layout.tsx +27 -0
  27. package/src/pages/welcome/Footer/index.tsx +1 -0
  28. package/src/services/chatModel.ts +1 -1
  29. package/src/store/global/hooks/useOnFinishHydrationGlobal.ts +18 -0
  30. package/src/store/global/hooks/useSwitchSideBarOnInit.ts +4 -8
  31. package/src/store/session/hooks/index.ts +2 -0
  32. package/src/store/session/hooks/useOnFinishHydrationSession.ts +18 -0
  33. package/src/store/session/hooks/useSessionHydrated.ts +1 -1
  34. package/src/store/session/index.ts +1 -3
  35. package/src/store/session/initialState.ts +6 -3
  36. package/src/store/session/selectors.ts +3 -3
  37. package/src/store/session/slices/agentConfig/action.ts +2 -1
  38. package/src/store/session/slices/agentConfig/selectors.ts +11 -11
  39. package/src/store/session/slices/chat/actions/message.ts +13 -20
  40. package/src/store/session/slices/chat/actions/topic.ts +3 -1
  41. package/src/store/session/slices/chat/selectors/chat.ts +1 -1
  42. package/src/store/session/slices/chat/selectors/token.ts +1 -1
  43. package/src/store/session/slices/chat/selectors/topic.ts +4 -2
  44. package/src/store/session/slices/session/action.ts +24 -16
  45. package/src/store/session/slices/session/initialState.ts +18 -4
  46. package/src/store/session/slices/session/selectors/export.ts +13 -8
  47. package/src/store/session/slices/session/selectors/list.test.ts +303 -0
  48. package/src/store/session/slices/session/selectors/list.ts +8 -3
  49. package/src/store/session/store.ts +5 -5
  50. package/src/types/exportConfig.ts +93 -31
  51. package/src/utils/config.ts +9 -0
  52. package/src/pages/chat/SessionList/List/SessionItem.tsx +0 -178
  53. package/src/pages/chat/SessionList/List/style.ts +0 -36
  54. package/src/store/session/slices/chat/index.ts +0 -3
  55. package/src/store/session/slices/session/index.ts +0 -3
  56. package/src/pages/chat/{[id] → features}/Conversation/ChatList/Error/ApiKeyForm.tsx +0 -0
  57. package/src/pages/chat/{[id] → features}/Conversation/ChatList/Error/ErrorActionContainer.tsx +0 -0
  58. package/src/pages/chat/{[id] → features}/Conversation/ChatList/Error/InvalidAccess.tsx +1 -1
  59. /package/src/pages/chat/{[id] → features}/Conversation/ChatList/Error/OpenAiBizError.tsx +0 -0
  60. /package/src/pages/chat/{[id] → features}/Conversation/ChatList/Error/style.tsx +0 -0
  61. /package/src/pages/chat/{[id] → features}/Conversation/ChatList/MessageExtra.tsx +0 -0
  62. /package/src/pages/chat/{[id] → features}/Conversation/ChatList/OTPInput.tsx +0 -0
  63. /package/src/pages/chat/{[id] → features}/Conversation/ChatList/Plugins/FunctionCall.tsx +0 -0
  64. /package/src/pages/chat/{[id] → features}/Conversation/ChatList/Plugins/PluginMessage.tsx +0 -0
  65. /package/src/pages/chat/{[id] → features}/Conversation/ChatList/Plugins/PluginResultRender.tsx +0 -0
  66. /package/src/pages/chat/{[id] → features}/Conversation/ChatList/Plugins/style.ts +0 -0
  67. /package/src/pages/chat/{[id] → features}/Conversation/Input/Action.tsx +0 -0
  68. /package/src/pages/chat/{[id] → features}/Conversation/Input/ActionRight.tsx +0 -0
  69. /package/src/pages/chat/{[id] → features}/Conversation/Input/Token.tsx +0 -0
  70. /package/src/pages/chat/{[id] → features}/Conversation/index.tsx +0 -0
  71. /package/src/pages/chat/{[id] → features}/Header/PluginTag.tsx +0 -0
  72. /package/src/pages/chat/{[id] → features}/Sidebar/Header.tsx +0 -0
  73. /package/src/pages/chat/{[id] → features}/Sidebar/Topic/DefaultContent.tsx +0 -0
  74. /package/src/pages/chat/{[id] → features}/Sidebar/Topic/TopicContent.tsx +0 -0
  75. /package/src/pages/chat/{[id] → features}/Sidebar/Topic/TopicItem.tsx +0 -0
  76. /package/src/pages/chat/{[id] → features}/Sidebar/Topic/index.tsx +0 -0
@@ -7,7 +7,7 @@ import { SessionStore } from '@/store/session';
7
7
  import { LanguageModel } from '@/types/llm';
8
8
  import { MetaData } from '@/types/meta';
9
9
 
10
- import { sessionSelectors } from '../session';
10
+ import { sessionSelectors } from '../session/selectors';
11
11
  import { initialLobeAgentConfig } from './initialState';
12
12
 
13
13
  const currentAgentMeta = (s: SessionStore): MetaData => {
@@ -25,8 +25,17 @@ const currentAutocomplete = (s: SessionStore): Autocomplete => ({
25
25
 
26
26
  const currentAgentTitle = (s: SessionStore) => currentAgentMeta(s)?.title || t('defaultSession');
27
27
 
28
+ const currentAgentConfig = (s: SessionStore) => {
29
+ const session = sessionSelectors.currentSession(s);
30
+ return merge({}, initialLobeAgentConfig, session?.config);
31
+ };
32
+
33
+ const currentAgentSystemRole = (s: SessionStore) => {
34
+ return currentAgentConfig(s).systemRole;
35
+ };
36
+
28
37
  const currentAgentDescription = (s: SessionStore) =>
29
- currentAgentMeta(s)?.description || t('noDescription');
38
+ currentAgentMeta(s)?.description || currentAgentSystemRole(s) || t('noDescription');
30
39
 
31
40
  const currentAgentBackgroundColor = (s: SessionStore) => {
32
41
  const session = sessionSelectors.currentSession(s);
@@ -41,15 +50,6 @@ const currentAgentAvatar = (s: SessionStore) => {
41
50
  return session.meta.avatar || DEFAULT_AVATAR;
42
51
  };
43
52
 
44
- const currentAgentConfig = (s: SessionStore) => {
45
- const session = sessionSelectors.currentSession(s);
46
- return merge({}, initialLobeAgentConfig, session?.config);
47
- };
48
-
49
- const currentAgentSystemRole = (s: SessionStore) => {
50
- return currentAgentConfig(s).systemRole;
51
- };
52
-
53
53
  const currentAgentModel = (s: SessionStore): LanguageModel => {
54
54
  const config = currentAgentConfig(s);
55
55
 
@@ -4,15 +4,18 @@ import { StateCreator } from 'zustand/vanilla';
4
4
  import { LOADING_FLAT } from '@/const/message';
5
5
  import { fetchChatModel } from '@/services/chatModel';
6
6
  import { fetchPlugin } from '@/services/plugin';
7
- import { SessionStore, agentSelectors, chatSelectors, sessionSelectors } from '@/store/session';
8
- import { getSlicedMessagesWithConfig } from '@/store/session/slices/chat/utils';
7
+ import { SessionStore } from '@/store/session';
9
8
  import { ChatMessage, OpenAIFunctionCall } from '@/types/chatMessage';
10
9
  import { fetchSSE } from '@/utils/fetch';
11
10
  import { isFunctionMessage } from '@/utils/message';
12
11
  import { setNamespace } from '@/utils/storeDebug';
13
12
  import { nanoid } from '@/utils/uuid';
14
13
 
14
+ import { agentSelectors } from '../../agentConfig/selectors';
15
+ import { sessionSelectors } from '../../session/selectors';
15
16
  import { MessageDispatch, messagesReducer } from '../reducers/message';
17
+ import { chatSelectors } from '../selectors';
18
+ import { getSlicedMessagesWithConfig } from '../utils';
16
19
 
17
20
  const t = setNamespace('chat/message');
18
21
 
@@ -24,11 +27,6 @@ export interface ChatMessageAction {
24
27
  * 清除消息
25
28
  */
26
29
  clearMessage: () => void;
27
- /**
28
- * 创建或发送消息
29
- * @param text - 消息文本
30
- */
31
- createOrSendMsg: (text: string) => Promise<void>;
32
30
  /**
33
31
  * 删除消息
34
32
  * @param id - 消息 ID
@@ -86,19 +84,6 @@ export const chatMessage: StateCreator<
86
84
  }
87
85
  },
88
86
 
89
- createOrSendMsg: async (message) => {
90
- if (!message) return;
91
-
92
- const { sendMessage, createSession } = get();
93
- const session = sessionSelectors.currentSession(get());
94
-
95
- if (!session) {
96
- await createSession();
97
- }
98
-
99
- sendMessage(message);
100
- },
101
-
102
87
  deleteMessage: (id) => {
103
88
  get().dispatchMessage({ id, type: 'deleteMessage' });
104
89
  },
@@ -325,4 +310,12 @@ export const chatMessage: StateCreator<
325
310
 
326
311
  await realFetchAIResponse(chats, message.id);
327
312
  },
313
+
314
+ // genShareUrl: () => {
315
+ // const session = sessionSelectors.currentSession(get());
316
+ // if (!session) return '';
317
+ //
318
+ // const agent = session.config;
319
+ // return genShareMessagesUrl(session.chats, agent.systemRole);
320
+ // },
328
321
  });
@@ -2,12 +2,14 @@ import { StateCreator } from 'zustand/vanilla';
2
2
 
3
3
  import { LOADING_FLAT } from '@/const/message';
4
4
  import { promptSummaryTitle } from '@/prompts/chat';
5
- import { SessionStore, chatSelectors, sessionSelectors, topicSelectors } from '@/store/session';
5
+ import { SessionStore } from '@/store/session';
6
6
  import { fetchPresetTaskResult } from '@/utils/fetch';
7
7
  import { setNamespace } from '@/utils/storeDebug';
8
8
  import { nanoid } from '@/utils/uuid';
9
9
 
10
+ import { sessionSelectors } from '../../session/selectors';
10
11
  import { ChatTopicDispatch, topicReducer } from '../reducers/topic';
12
+ import { chatSelectors, topicSelectors } from '../selectors';
11
13
 
12
14
  const t = setNamespace('chat/topic');
13
15
  export interface ChatTopicAction {
@@ -4,7 +4,7 @@ import { ChatMessage } from '@/types/chatMessage';
4
4
 
5
5
  import type { SessionStore } from '../../../store';
6
6
  import { agentSelectors } from '../../agentConfig';
7
- import { sessionSelectors } from '../../session';
7
+ import { sessionSelectors } from '../../session/selectors';
8
8
  import { getSlicedMessagesWithConfig } from '../utils';
9
9
  import { organizeChats } from './utils';
10
10
 
@@ -1,6 +1,6 @@
1
1
  import { encode } from 'gpt-tokenizer';
2
2
 
3
- import { agentSelectors } from '@/store/session';
3
+ import { agentSelectors } from '@/store/session/slices/agentConfig';
4
4
 
5
5
  import type { SessionStore } from '../../../store';
6
6
  import { currentChatsWithHistoryConfig } from './chat';
@@ -1,8 +1,10 @@
1
1
  // 展示在聊天框中的消息
2
- import { SessionStore, sessionSelectors } from '@/store/session';
3
- import { organizeChats } from '@/store/session/slices/chat/selectors/utils';
4
2
  import { ChatTopic } from '@/types/topic';
5
3
 
4
+ import { SessionStore } from '../../../store';
5
+ import { sessionSelectors } from '../../session/selectors';
6
+ import { organizeChats } from './utils';
7
+
6
8
  export const currentTopics = (s: SessionStore): ChatTopic[] => {
7
9
  const session = sessionSelectors.currentSession(s);
8
10
  if (!session) return [];
@@ -3,19 +3,21 @@ import { merge } from 'lodash-es';
3
3
  import Router from 'next/router';
4
4
  import { StateCreator } from 'zustand/vanilla';
5
5
 
6
+ import { INBOX_SESSION_ID } from '@/const/session';
6
7
  import { useGlobalStore } from '@/store/global';
7
- import { SessionStore, initLobeSession } from '@/store/session';
8
+ import { SessionStore } from '@/store/session';
8
9
  import { LobeAgentSession, LobeSessions } from '@/types/session';
9
10
  import { setNamespace } from '@/utils/storeDebug';
10
11
  import { uuid } from '@/utils/uuid';
11
12
 
13
+ import { initLobeSession } from './initialState';
12
14
  import { SessionDispatch, sessionsReducer } from './reducers/session';
13
15
 
14
16
  const t = setNamespace('session');
17
+
15
18
  export interface SessionAction {
16
19
  activeSession: (sessionId: string) => void;
17
20
  clearSessions: () => void;
18
-
19
21
  /**
20
22
  * @title 添加会话
21
23
  * @param session - 会话信息
@@ -27,7 +29,7 @@ export interface SessionAction {
27
29
  * @param payload - 聊天记录
28
30
  */
29
31
  dispatchSession: (payload: SessionDispatch) => void;
30
-
32
+ importInbox: (inbox: LobeAgentSession) => void;
31
33
  /**
32
34
  * 导入会话
33
35
  * @param sessions
@@ -39,6 +41,7 @@ export interface SessionAction {
39
41
  * @param sessionId
40
42
  */
41
43
  pinSession: (sessionId: string, pinned?: boolean) => void;
44
+
42
45
  /**
43
46
  * 生成压缩后的消息
44
47
  * @returns 压缩后的消息
@@ -95,13 +98,24 @@ export const createSessionSlice: StateCreator<
95
98
 
96
99
  dispatchSession: (payload) => {
97
100
  const { type, ...res } = payload;
98
- set(
99
- { sessions: sessionsReducer(get().sessions, payload) },
100
- false,
101
- t(`dispatchChat/${type}`, res),
102
- );
103
- },
104
101
 
102
+ // 如果是 inbox 类型的 session
103
+ if ('id' in res && res.id === INBOX_SESSION_ID) {
104
+ const nextInbox = sessionsReducer({ inbox: get().inbox }, payload) as {
105
+ inbox: LobeAgentSession;
106
+ };
107
+ set({ inbox: nextInbox.inbox }, false, t(`dispatchInbox/${type}`, res));
108
+ } else {
109
+ // 常规类型的session
110
+ set(
111
+ { sessions: sessionsReducer(get().sessions, payload) },
112
+ false,
113
+ t(`dispatchSessions/${type}`, res),
114
+ );
115
+ }
116
+ },
117
+ // TODO:暂时先不实现导入 inbox 的功能
118
+ importInbox: () => {},
105
119
  importSessions: (importSessions) => {
106
120
  const { sessions } = get();
107
121
  set(
@@ -119,13 +133,7 @@ export const createSessionSlice: StateCreator<
119
133
  t('importSessions', importSessions),
120
134
  );
121
135
  },
122
- // genShareUrl: () => {
123
- // const session = sessionSelectors.currentSession(get());
124
- // if (!session) return '';
125
- //
126
- // const agent = session.config;
127
- // return genShareMessagesUrl(session.chats, agent.systemRole);
128
- // },
136
+
129
137
  pinSession: (sessionId, pinned) => {
130
138
  const nextValue = typeof pinned === 'boolean' ? pinned : !get().sessions[sessionId].pinned;
131
139
 
@@ -1,7 +1,9 @@
1
- import { DEFAULT_AGENT_META } from '@/const/meta';
2
- import { LobeAgentSession, LobeSessionType } from '@/types/session';
1
+ import { merge } from 'lodash-es';
3
2
 
4
- import { initialLobeAgentConfig } from '../agentConfig';
3
+ import { DEFAULT_AGENT_META, DEFAULT_INBOX_AVATAR } from '@/const/meta';
4
+ import { LobeAgentConfig, LobeAgentSession, LobeSessionType } from '@/types/session';
5
+
6
+ import { initialLobeAgentConfig } from '../agentConfig/initialState';
5
7
 
6
8
  export interface SessionState {
7
9
  /**
@@ -10,6 +12,8 @@ export interface SessionState {
10
12
  * @default null
11
13
  */
12
14
  activeId: string | null;
15
+ // 默认会话
16
+ inbox: LobeAgentSession;
13
17
  searchKeywords: string;
14
18
  sessions: Record<string, LobeAgentSession>;
15
19
  }
@@ -24,9 +28,19 @@ export const initLobeSession: LobeAgentSession = {
24
28
  updateAt: Date.now(),
25
29
  };
26
30
 
31
+ export const initInbox = merge({}, initLobeSession, {
32
+ config: {
33
+ systemRole: '你是一名 AI 助理',
34
+ } as LobeAgentConfig,
35
+ id: 'inbox',
36
+ meta: {
37
+ avatar: DEFAULT_INBOX_AVATAR,
38
+ },
39
+ } as Partial<LobeAgentSession>);
40
+
27
41
  export const initialSessionState: SessionState = {
28
42
  activeId: null,
29
-
43
+ inbox: initInbox,
30
44
  searchKeywords: '',
31
45
  sessions: {},
32
46
  };
@@ -1,20 +1,25 @@
1
1
  import { transform } from 'lodash-es';
2
2
 
3
3
  import { SessionStore } from '@/store/session';
4
+ import { ConfigStateAgents, ConfigStateSessions } from '@/types/exportConfig';
4
5
  import { LobeAgentSession, LobeSessions } from '@/types/session';
5
6
 
6
7
  import { getSessionById } from './list';
7
8
 
8
- export const exportSessions = (s: SessionStore) => s.sessions;
9
+ export const exportSessions = (s: SessionStore): ConfigStateSessions => ({
10
+ inbox: s.inbox,
11
+ sessions: s.sessions,
12
+ });
9
13
 
10
- // 排除 chats
11
- export const exportAgents = (s: SessionStore) => {
12
- return transform(s.sessions, (result: LobeSessions, value, key) => {
13
- // 移除 chats 和 topics
14
- result[key] = { ...value, chats: {}, topics: {} } as LobeAgentSession;
15
- });
14
+ export const exportAgents = (s: SessionStore): ConfigStateAgents => {
15
+ return {
16
+ sessions: transform(s.sessions, (result: LobeSessions, value, key) => {
17
+ // 移除 chats 和 topics
18
+ result[key] = { ...value, chats: {}, topics: {} } as LobeAgentSession;
19
+ }),
20
+ };
16
21
  };
17
- // 排除 chats
22
+
18
23
  export const getExportAgent =
19
24
  (id: string) =>
20
25
  (s: SessionStore): LobeAgentSession => {
@@ -0,0 +1,303 @@
1
+ import type { SessionStore } from '@/store/session';
2
+ import { LanguageModel } from '@/types/llm';
3
+ import { LobeAgentSession, LobeSessionType } from '@/types/session';
4
+
5
+ import { initLobeSession } from '../initialState';
6
+ import {
7
+ currentSession,
8
+ currentSessionSafe,
9
+ getSessionById,
10
+ getSessionMetaById,
11
+ hasSessionList,
12
+ sessionList,
13
+ } from './list';
14
+
15
+ describe('currentSession', () => {
16
+ const s = {
17
+ activeId: '1',
18
+ sessions: {
19
+ '1': {
20
+ id: '1',
21
+ config: {
22
+ model: LanguageModel.GPT3_5,
23
+ params: {},
24
+ systemRole: 'system-role',
25
+ },
26
+ type: LobeSessionType.Agent,
27
+ } as LobeAgentSession,
28
+ '2': {
29
+ id: '2',
30
+ config: {
31
+ model: LanguageModel.GPT3_5,
32
+ params: {},
33
+ systemRole: 'system-role',
34
+ },
35
+ type: LobeSessionType.Agent,
36
+ } as LobeAgentSession,
37
+ },
38
+ inbox: {
39
+ id: 'inbox',
40
+ config: {
41
+ model: LanguageModel.GPT3_5,
42
+ params: {},
43
+ systemRole: 'system-role',
44
+ },
45
+ type: LobeSessionType.Agent,
46
+ } as LobeAgentSession,
47
+ } as unknown as SessionStore;
48
+
49
+ it('should return undefined when s.activeId is not defined', () => {
50
+ expect(currentSession({ sessions: {} } as any)).toBeUndefined();
51
+ });
52
+
53
+ it('should return s.inbox when s.activeId is equal to INBOX_SESSION_ID', () => {
54
+ expect(currentSession({ ...s, activeId: 'inbox' })).toEqual(s.inbox);
55
+ });
56
+
57
+ it('should return s.sessions[s.activeId] when s.activeId is not equal to INBOX_SESSION_ID', () => {
58
+ expect(currentSession(s)).toEqual(s.sessions['1']);
59
+ });
60
+ });
61
+
62
+ describe('currentSessionSafe', () => {
63
+ const s = {
64
+ activeId: '1',
65
+ sessions: {
66
+ '1': {
67
+ id: '1',
68
+ config: {
69
+ model: LanguageModel.GPT3_5,
70
+ params: {},
71
+ systemRole: 'system-role',
72
+ },
73
+ type: LobeSessionType.Agent,
74
+ } as LobeAgentSession,
75
+ '2': {
76
+ id: '2',
77
+ config: {
78
+ model: LanguageModel.GPT3_5,
79
+ params: {},
80
+ systemRole: 'system-role',
81
+ },
82
+ type: LobeSessionType.Agent,
83
+ } as LobeAgentSession,
84
+ },
85
+ inbox: {
86
+ id: 'inbox',
87
+ config: {
88
+ model: LanguageModel.GPT3_5,
89
+ params: {},
90
+ systemRole: 'system-role',
91
+ },
92
+ type: LobeSessionType.Agent,
93
+ } as LobeAgentSession,
94
+ } as unknown as SessionStore;
95
+
96
+ it('should return initLobeSession when currentSession(s) returns undefined', () => {
97
+ expect(currentSessionSafe({ sessions: {} } as any)).toEqual(initLobeSession);
98
+ });
99
+
100
+ it('should return the result of currentSession(s) when it returns a non-undefined value', () => {
101
+ expect(currentSessionSafe(s)).toEqual(s.sessions['1']);
102
+ });
103
+ });
104
+
105
+ describe('sessionList', () => {
106
+ const s = {
107
+ sessions: {
108
+ '1': {
109
+ pinned: true,
110
+ updateAt: 100,
111
+ },
112
+ '2': {
113
+ pinned: false,
114
+ updateAt: 200,
115
+ },
116
+ '3': {
117
+ pinned: true,
118
+ updateAt: 300,
119
+ },
120
+ },
121
+ } as unknown as SessionStore;
122
+
123
+ it('should return an array of sessions sorted by pinned and updateAt fields', () => {
124
+ expect(sessionList(s)).toEqual([s.sessions['3'], s.sessions['1'], s.sessions['2']]);
125
+ });
126
+
127
+ it('should return an empty array when s.sessions is an empty object', () => {
128
+ const result = sessionList({
129
+ sessions: {},
130
+ } as any);
131
+ expect(result).toEqual([]);
132
+ });
133
+
134
+ it('should return a sorted session list when s.sessions is not empty object', () => {
135
+ const session1 = {
136
+ id: '1',
137
+ pinned: true,
138
+ updateAt: 1635705600000,
139
+ meta: {},
140
+ };
141
+ const session2 = {
142
+ id: '2',
143
+ pinned: false,
144
+ updateAt: 1635705600000,
145
+ meta: {},
146
+ };
147
+ const session3 = {
148
+ id: '3',
149
+ pinned: true,
150
+ updateAt: 1635705700000,
151
+ meta: {},
152
+ };
153
+ const s = {
154
+ sessions: {
155
+ '1': session1,
156
+ '2': session2,
157
+ '3': session3,
158
+ },
159
+ } as unknown as SessionStore;
160
+ const result = sessionList(s);
161
+ expect(result).toEqual([session3, session1, session2]);
162
+ });
163
+
164
+ it('should return a sorted session list when s.searchKeywords is not an empty string', () => {
165
+ const session1 = {
166
+ id: '1',
167
+ pinned: true,
168
+ meta: { title: 'yword' },
169
+
170
+ updateAt: 1635705600000,
171
+ };
172
+ const session2 = {
173
+ id: '2',
174
+ pinned: false,
175
+ meta: { title: 'keyword' },
176
+ updateAt: 1635705600000,
177
+ };
178
+ const session3 = {
179
+ id: '3',
180
+ pinned: true,
181
+ meta: { title: 'kyword' },
182
+ updateAt: 1635705800000,
183
+ };
184
+ const s = {
185
+ sessions: {
186
+ '1': session1,
187
+ '2': session2,
188
+ '3': session3,
189
+ },
190
+ searchKeywords: 'k',
191
+ } as unknown as SessionStore;
192
+
193
+ const result = sessionList(s);
194
+ expect(result).toEqual([session3, session2]);
195
+ });
196
+
197
+ it('should return an empty array when s.sessions is an empty object and s.searchKeywords is not an empty string', () => {
198
+ const result = sessionList({
199
+ sessions: {},
200
+ searchKeywords: 'keyword',
201
+ } as unknown as SessionStore);
202
+ expect(result).toEqual([]);
203
+ });
204
+ });
205
+
206
+ describe('hasSessionList', () => {
207
+ it('should return true when sessionList(s) returns a non-empty array', () => {
208
+ const s = {
209
+ sessions: {
210
+ '1': {},
211
+ },
212
+ } as unknown as SessionStore;
213
+ expect(hasSessionList(s)).toBe(true);
214
+ });
215
+
216
+ it('should return false when sessionList(s) returns an empty array', () => {
217
+ const s = {
218
+ sessions: {},
219
+ } as unknown as SessionStore;
220
+ expect(hasSessionList(s)).toBe(false);
221
+ });
222
+ });
223
+
224
+ describe('getSessionById', () => {
225
+ const s = {
226
+ activeId: '1',
227
+ sessions: {
228
+ '1': {
229
+ id: '1',
230
+ config: {
231
+ model: LanguageModel.GPT3_5,
232
+ params: {},
233
+ systemRole: 'system-role',
234
+ },
235
+ type: LobeSessionType.Agent,
236
+ } as LobeAgentSession,
237
+ '2': {
238
+ id: '2',
239
+ config: {
240
+ model: LanguageModel.GPT3_5,
241
+ params: {},
242
+ systemRole: 'system-role',
243
+ },
244
+ type: LobeSessionType.Agent,
245
+ } as LobeAgentSession,
246
+ },
247
+ inbox: {
248
+ id: 'inbox',
249
+ config: {
250
+ model: LanguageModel.GPT3_5,
251
+ params: {},
252
+ systemRole: 'system-role',
253
+ },
254
+ type: LobeSessionType.Agent,
255
+ } as LobeAgentSession,
256
+ } as unknown as SessionStore;
257
+
258
+ it('should return s.inbox when id is equal to INBOX_SESSION_ID', () => {
259
+ expect(getSessionById('inbox')(s)).toEqual(s.inbox);
260
+ });
261
+
262
+ it('should return the session with the specified id when id is not equal to INBOX_SESSION_ID', () => {
263
+ expect(getSessionById('1')(s)).toEqual(s.sessions['1']);
264
+ });
265
+
266
+ it('should return initLobeSession when the session with the specified id does not exist', () => {
267
+ expect(getSessionById('3')(s)).toEqual(initLobeSession);
268
+ });
269
+ });
270
+
271
+ describe('getSessionMetaById', () => {
272
+ const s: SessionStore = {
273
+ sessions: {
274
+ '1': {
275
+ meta: {
276
+ title: 'Session 1',
277
+ },
278
+ },
279
+ '2': {
280
+ meta: {
281
+ title: 'Session 2',
282
+ },
283
+ },
284
+ },
285
+ inbox: {
286
+ meta: {
287
+ title: 'Inbox',
288
+ },
289
+ },
290
+ } as unknown as SessionStore;
291
+
292
+ it('should return the meta data of the session with the specified id', () => {
293
+ expect(getSessionMetaById('1')(s)).toEqual({ title: 'Session 1' });
294
+ });
295
+
296
+ it('should return the meta data of the inbox session when id is equal to INBOX_SESSION_ID', () => {
297
+ expect(getSessionMetaById('inbox')(s)).toEqual({ title: 'Inbox' });
298
+ });
299
+
300
+ it('should return an empty object when the session with the specified id does not exist', () => {
301
+ expect(getSessionMetaById('3')(s)).toEqual({});
302
+ });
303
+ });
@@ -1,13 +1,16 @@
1
- import { SessionStore } from '@/store/session';
1
+ import { INBOX_SESSION_ID } from '@/const/session';
2
2
  import { MetaData } from '@/types/meta';
3
3
  import { LobeAgentSession } from '@/types/session';
4
4
  import { filterWithKeywords } from '@/utils/filter';
5
5
 
6
+ import { SessionStore } from '../../../store';
6
7
  import { initLobeSession } from '../initialState';
7
8
 
8
9
  export const currentSession = (s: SessionStore): LobeAgentSession | undefined => {
9
10
  if (!s.activeId) return;
10
11
 
12
+ if (s.activeId === INBOX_SESSION_ID) return s.inbox;
13
+
11
14
  return s.sessions[s.activeId];
12
15
  };
13
16
 
@@ -17,7 +20,7 @@ export const currentSessionSafe = (s: SessionStore): LobeAgentSession => {
17
20
 
18
21
  export const sessionList = (s: SessionStore) => {
19
22
  const filterChats = filterWithKeywords(s.sessions, s.searchKeywords, (item) => [
20
- Object.values(item.chats)
23
+ Object.values(item.chats || {})
21
24
  .map((c) => c.content)
22
25
  .join(''),
23
26
  ]);
@@ -44,6 +47,8 @@ export const hasSessionList = (s: SessionStore) => {
44
47
  export const getSessionById =
45
48
  (id: string) =>
46
49
  (s: SessionStore): LobeAgentSession => {
50
+ if (id === INBOX_SESSION_ID) return s.inbox;
51
+
47
52
  const session = s.sessions[id];
48
53
 
49
54
  if (!session) return initLobeSession;
@@ -53,7 +58,7 @@ export const getSessionById =
53
58
  export const getSessionMetaById =
54
59
  (id: string) =>
55
60
  (s: SessionStore): MetaData => {
56
- const session = s.sessions[id];
61
+ const session = getSessionById(id)(s);
57
62
 
58
63
  if (!session) return {};
59
64
  return session.meta;