@lobehub/chat 1.56.1 → 1.56.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.56.2](https://github.com/lobehub/lobe-chat/compare/v1.56.1...v1.56.2)
6
+
7
+ <sup>Released on **2025-02-16**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix inbox agent can not save config.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix inbox agent can not save config, closes [#6186](https://github.com/lobehub/lobe-chat/issues/6186) ([588cba7](https://github.com/lobehub/lobe-chat/commit/588cba7))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 1.56.1](https://github.com/lobehub/lobe-chat/compare/v1.56.0...v1.56.1)
6
31
 
7
32
  <sup>Released on **2025-02-16**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix inbox agent can not save config."
6
+ ]
7
+ },
8
+ "date": "2025-02-16",
9
+ "version": "1.56.2"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "fixes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.56.1",
3
+ "version": "1.56.2",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -628,7 +628,7 @@ describe('SessionModel', () => {
628
628
 
629
629
  describe('createInbox', () => {
630
630
  it('should create inbox session if not exists', async () => {
631
- const inbox = await sessionModel.createInbox();
631
+ const inbox = await sessionModel.createInbox({});
632
632
 
633
633
  expect(inbox).toBeDefined();
634
634
  expect(inbox?.slug).toBe('inbox');
@@ -641,10 +641,10 @@ describe('SessionModel', () => {
641
641
 
642
642
  it('should not create duplicate inbox session', async () => {
643
643
  // Create first inbox
644
- await sessionModel.createInbox();
644
+ await sessionModel.createInbox({});
645
645
 
646
646
  // Try to create another inbox
647
- const duplicateInbox = await sessionModel.createInbox();
647
+ const duplicateInbox = await sessionModel.createInbox({});
648
648
 
649
649
  // Should return undefined as inbox already exists
650
650
  expect(duplicateInbox).toBeUndefined();
@@ -1,7 +1,7 @@
1
1
  import { Column, count, sql } from 'drizzle-orm';
2
2
  import { and, asc, desc, eq, gt, inArray, isNull, like, not, or } from 'drizzle-orm/expressions';
3
+ import { DeepPartial } from 'utility-types';
3
4
 
4
- import { appEnv } from '@/config/app';
5
5
  import { DEFAULT_INBOX_AVATAR } from '@/const/meta';
6
6
  import { INBOX_SESSION_ID } from '@/const/session';
7
7
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
@@ -13,7 +13,7 @@ import {
13
13
  genWhere,
14
14
  } from '@/database/utils/genWhere';
15
15
  import { idGenerator } from '@/database/utils/idGenerator';
16
- import { parseAgentConfig } from '@/server/globalConfig/parseDefaultAgent';
16
+ import { LobeAgentConfig } from '@/types/agent';
17
17
  import { ChatSessionList, LobeAgentSession, SessionRankItem } from '@/types/session';
18
18
  import { merge } from '@/utils/merge';
19
19
 
@@ -226,16 +226,15 @@ export class SessionModel {
226
226
  });
227
227
  };
228
228
 
229
- createInbox = async () => {
229
+ createInbox = async (defaultAgentConfig: DeepPartial<LobeAgentConfig>) => {
230
230
  const item = await this.db.query.sessions.findFirst({
231
231
  where: and(eq(sessions.userId, this.userId), eq(sessions.slug, INBOX_SESSION_ID)),
232
232
  });
233
- if (item) return;
234
233
 
235
- const serverAgentConfig = parseAgentConfig(appEnv.DEFAULT_AGENT_CONFIG) || {};
234
+ if (item) return;
236
235
 
237
236
  return await this.create({
238
- config: merge(DEFAULT_AGENT_CONFIG, serverAgentConfig),
237
+ config: merge(DEFAULT_AGENT_CONFIG, defaultAgentConfig),
239
238
  slug: INBOX_SESSION_ID,
240
239
  type: 'agent',
241
240
  });
@@ -10,7 +10,6 @@ import { merge } from '@/utils/merge';
10
10
  import { today } from '@/utils/time';
11
11
 
12
12
  import { NewUser, UserItem, UserSettingsItem, userSettings, users } from '../../schemas';
13
- import { SessionModel } from './session';
14
13
 
15
14
  type DecryptUserKeyVaults = (
16
15
  encryptKeyVaultsStr: string | null,
@@ -160,10 +159,7 @@ export class UserModel {
160
159
  .values({ ...params })
161
160
  .returning();
162
161
 
163
- // Create an inbox session for the user
164
- const model = new SessionModel(db, user.id);
165
-
166
- await model.createInbox();
162
+ return user;
167
163
  };
168
164
 
169
165
  static deleteUser = async (db: LobeChatDatabase, id: string) => {
@@ -1,29 +1,27 @@
1
- import { Typography } from 'antd';
2
- import { createStyles } from 'antd-style';
3
1
  import dayjs from 'dayjs';
4
2
  import { get, isDate } from 'lodash-es';
5
3
  import React, { useMemo } from 'react';
6
4
 
7
- import TooltipContent from './TooltipContent';
5
+ // import TooltipContent from './TooltipContent';
8
6
 
9
- const { Text } = Typography;
7
+ // const { Text } = Typography;
10
8
 
11
- const useStyles = createStyles(({ token, css }) => ({
12
- cell: css`
13
- font-family: ${token.fontFamilyCode};
14
- font-size: ${token.fontSizeSM}px;
15
- `,
16
- tooltip: css`
17
- border: 1px solid ${token.colorBorder};
18
-
19
- font-family: ${token.fontFamilyCode};
20
- font-size: ${token.fontSizeSM}px;
21
- color: ${token.colorText} !important;
22
- word-break: break-all;
23
-
24
- background: ${token.colorBgElevated} !important;
25
- `,
26
- }));
9
+ // const useStyles = createStyles(({ token, css }) => ({
10
+ // cell: css`
11
+ // font-family: ${token.fontFamilyCode};
12
+ // font-size: ${token.fontSizeSM}px;
13
+ // `,
14
+ // tooltip: css`
15
+ // border: 1px solid ${token.colorBorder};
16
+ //
17
+ // font-family: ${token.fontFamilyCode};
18
+ // font-size: ${token.fontSizeSM}px;
19
+ // color: ${token.colorText} !important;
20
+ // word-break: break-all;
21
+ //
22
+ // background: ${token.colorBgElevated} !important;
23
+ // `,
24
+ // }));
27
25
 
28
26
  interface TableCellProps {
29
27
  column: string;
@@ -32,7 +30,7 @@ interface TableCellProps {
32
30
  }
33
31
 
34
32
  const TableCell = ({ dataItem, column, rowIndex }: TableCellProps) => {
35
- const { styles } = useStyles();
33
+ // const { styles } = useStyles();
36
34
  const data = get(dataItem, column);
37
35
  const content = useMemo(() => {
38
36
  if (isDate(data)) return dayjs(data).format('YYYY-MM-DD HH:mm:ss');
@@ -54,18 +52,21 @@ const TableCell = ({ dataItem, column, rowIndex }: TableCellProps) => {
54
52
 
55
53
  return (
56
54
  <td key={column} onDoubleClick={() => console.log('Edit cell:', rowIndex, column)}>
57
- <Text
58
- className={styles.cell}
59
- ellipsis={{
60
- tooltip: {
61
- arrow: false,
62
- classNames: { body: styles.tooltip },
63
- title: <TooltipContent>{content}</TooltipContent>,
64
- },
65
- }}
66
- >
67
- {content}
68
- </Text>
55
+ {content}
56
+
57
+ {/* 不能使用 antd 的 Text, 会有大量的重渲染导致滚动极其卡顿 */}
58
+ {/*<Text*/}
59
+ {/* className={styles.cell}*/}
60
+ {/* ellipsis={{*/}
61
+ {/* tooltip: {*/}
62
+ {/* arrow: false,*/}
63
+ {/* classNames: { body: styles.tooltip },*/}
64
+ {/* title: <TooltipContent>{content}</TooltipContent>,*/}
65
+ {/* },*/}
66
+ {/* }}*/}
67
+ {/*>*/}
68
+ {/* {content}*/}
69
+ {/*</Text>*/}
69
70
  </td>
70
71
  );
71
72
  };
@@ -10,6 +10,7 @@ import { Adapter, AdapterAccount } from 'next-auth/adapters';
10
10
 
11
11
  import * as schema from '@/database/schemas';
12
12
  import { UserModel } from '@/database/server/models/user';
13
+ import { AgentService } from '@/server/services/agent';
13
14
  import { merge } from '@/utils/merge';
14
15
 
15
16
  import {
@@ -65,6 +66,7 @@ export function LobeNextAuthDbAdapter(serverDB: NeonDatabase<typeof schema>): Ad
65
66
  const adapterUser = mapLobeUserToAdapterUser(existingUser);
66
67
  return adapterUser;
67
68
  }
69
+
68
70
  // create a new user if it does not exist
69
71
  await UserModel.createUser(
70
72
  serverDB,
@@ -77,6 +79,11 @@ export function LobeNextAuthDbAdapter(serverDB: NeonDatabase<typeof schema>): Ad
77
79
  name,
78
80
  }),
79
81
  );
82
+
83
+ // 3. Create an inbox session for the user
84
+ const agentService = new AgentService(serverDB, id);
85
+ await agentService.createInbox();
86
+
80
87
  return { ...user, id: providerAccountId ?? id };
81
88
  },
82
89
  async createVerificationToken(data): Promise<VerificationToken | null | undefined> {
@@ -10,6 +10,7 @@ import { SessionModel } from '@/database/server/models/session';
10
10
  import { UserModel } from '@/database/server/models/user';
11
11
  import { pino } from '@/libs/logger';
12
12
  import { authedProcedure, router } from '@/libs/trpc';
13
+ import { AgentService } from '@/server/services/agent';
13
14
  import { KnowledgeItem, KnowledgeType } from '@/types/knowledgeBase';
14
15
 
15
16
  const agentProcedure = authedProcedure.use(async (opts) => {
@@ -18,6 +19,7 @@ const agentProcedure = authedProcedure.use(async (opts) => {
18
19
  return opts.next({
19
20
  ctx: {
20
21
  agentModel: new AgentModel(serverDB, ctx.userId),
22
+ agentService: new AgentService(serverDB, ctx.userId),
21
23
  fileModel: new FileModel(serverDB, ctx.userId),
22
24
  knowledgeBaseModel: new KnowledgeBaseModel(serverDB, ctx.userId),
23
25
  sessionModel: new SessionModel(serverDB, ctx.userId),
@@ -91,7 +93,7 @@ export const agentRouter = router({
91
93
  const user = await UserModel.findById(serverDB, ctx.userId);
92
94
  if (!user) return DEFAULT_AGENT_CONFIG;
93
95
 
94
- const res = await ctx.sessionModel.createInbox();
96
+ const res = await ctx.agentService.createInbox();
95
97
  pino.info('create inbox session', res);
96
98
  }
97
99
  }
@@ -0,0 +1,65 @@
1
+ // @vitest-environment node
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { SessionModel } from '@/database/server/models/session';
5
+ import { parseAgentConfig } from '@/server/globalConfig/parseDefaultAgent';
6
+
7
+ import { AgentService } from './index';
8
+
9
+ vi.mock('@/config/app', () => ({
10
+ appEnv: {
11
+ DEFAULT_AGENT_CONFIG: 'model=gpt-4;temperature=0.7',
12
+ },
13
+ }));
14
+
15
+ vi.mock('@/server/globalConfig/parseDefaultAgent', () => ({
16
+ parseAgentConfig: vi.fn(),
17
+ }));
18
+
19
+ vi.mock('@/database/server/models/session', () => ({
20
+ SessionModel: vi.fn(),
21
+ }));
22
+
23
+ describe('AgentService', () => {
24
+ let service: AgentService;
25
+ const mockDb = {} as any;
26
+ const mockUserId = 'test-user-id';
27
+
28
+ beforeEach(() => {
29
+ vi.clearAllMocks();
30
+ service = new AgentService(mockDb, mockUserId);
31
+ });
32
+
33
+ describe('createInbox', () => {
34
+ it('should create inbox with default agent config', async () => {
35
+ const mockConfig = { model: 'gpt-4', temperature: 0.7 };
36
+ const mockSessionModel = {
37
+ createInbox: vi.fn(),
38
+ };
39
+
40
+ (SessionModel as any).mockImplementation(() => mockSessionModel);
41
+ (parseAgentConfig as any).mockReturnValue(mockConfig);
42
+
43
+ await service.createInbox();
44
+
45
+ expect(SessionModel).toHaveBeenCalledWith(mockDb, mockUserId);
46
+ expect(parseAgentConfig).toHaveBeenCalledWith('model=gpt-4;temperature=0.7');
47
+ expect(mockSessionModel.createInbox).toHaveBeenCalledWith(mockConfig);
48
+ });
49
+
50
+ it('should create inbox with empty config if parseAgentConfig returns undefined', async () => {
51
+ const mockSessionModel = {
52
+ createInbox: vi.fn(),
53
+ };
54
+
55
+ (SessionModel as any).mockImplementation(() => mockSessionModel);
56
+ (parseAgentConfig as any).mockReturnValue(undefined);
57
+
58
+ await service.createInbox();
59
+
60
+ expect(SessionModel).toHaveBeenCalledWith(mockDb, mockUserId);
61
+ expect(parseAgentConfig).toHaveBeenCalledWith('model=gpt-4;temperature=0.7');
62
+ expect(mockSessionModel.createInbox).toHaveBeenCalledWith({});
63
+ });
64
+ });
65
+ });
@@ -0,0 +1,22 @@
1
+ import { appEnv } from '@/config/app';
2
+ import { SessionModel } from '@/database/server/models/session';
3
+ import { LobeChatDatabase } from '@/database/type';
4
+ import { parseAgentConfig } from '@/server/globalConfig/parseDefaultAgent';
5
+
6
+ export class AgentService {
7
+ private readonly userId: string;
8
+ private readonly db: LobeChatDatabase;
9
+
10
+ constructor(db: LobeChatDatabase, userId: string) {
11
+ this.userId = userId;
12
+ this.db = db;
13
+ }
14
+
15
+ async createInbox() {
16
+ const sessionModel = new SessionModel(this.db, this.userId);
17
+
18
+ const defaultAgentConfig = parseAgentConfig(appEnv.DEFAULT_AGENT_CONFIG) || {};
19
+
20
+ await sessionModel.createInbox(defaultAgentConfig);
21
+ }
22
+ }
@@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
4
4
  import { UserItem } from '@/database/schemas';
5
5
  import { UserModel } from '@/database/server/models/user';
6
6
  import { pino } from '@/libs/logger';
7
+ import { AgentService } from '@/server/services/agent';
7
8
 
8
9
  import { UserService } from './index';
9
10
 
@@ -29,6 +30,12 @@ vi.mock('@/libs/logger', () => ({
29
30
  },
30
31
  }));
31
32
 
33
+ vi.mock('@/server/services/agent', () => ({
34
+ AgentService: vi.fn().mockImplementation(() => ({
35
+ createInbox: vi.fn().mockResolvedValue(undefined),
36
+ })),
37
+ }));
38
+
32
39
  let service: UserService;
33
40
  const mockUserId = 'test-user-id';
34
41
 
@@ -57,7 +64,7 @@ describe('UserService', () => {
57
64
  // Mock user not found
58
65
  vi.mocked(UserModel.findById).mockResolvedValue(null as any);
59
66
 
60
- await service.createUser(mockUserId, mockUserJSON);
67
+ const result = await service.createUser(mockUserId, mockUserJSON);
61
68
 
62
69
  expect(UserModel.findById).toHaveBeenCalledWith(expect.anything(), mockUserId);
63
70
  expect(UserModel.createUser).toHaveBeenCalledWith(
@@ -73,6 +80,12 @@ describe('UserService', () => {
73
80
  clerkCreatedAt: new Date('2023-01-01T00:00:00Z'),
74
81
  }),
75
82
  );
83
+ expect(AgentService).toHaveBeenCalledWith(expect.anything(), mockUserId);
84
+ expect(vi.mocked(AgentService).mock.results[0].value.createInbox).toHaveBeenCalled();
85
+ expect(result).toEqual({
86
+ message: 'user created',
87
+ success: true,
88
+ });
76
89
  });
77
90
 
78
91
  it('should not create user if already exists', async () => {
@@ -83,6 +96,7 @@ describe('UserService', () => {
83
96
 
84
97
  expect(UserModel.findById).toHaveBeenCalledWith(expect.anything(), mockUserId);
85
98
  expect(UserModel.createUser).not.toHaveBeenCalled();
99
+ expect(AgentService).not.toHaveBeenCalled();
86
100
  expect(result).toEqual({
87
101
  message: 'user not created due to user already existing in the database',
88
102
  success: false,
@@ -106,6 +120,8 @@ describe('UserService', () => {
106
120
  phone: '+1234567890', // Should use first phone number
107
121
  }),
108
122
  );
123
+ expect(AgentService).toHaveBeenCalledWith(expect.anything(), mockUserId);
124
+ expect(vi.mocked(AgentService).mock.results[0].value.createInbox).toHaveBeenCalled();
109
125
  });
110
126
  });
111
127
 
@@ -4,6 +4,7 @@ import { serverDB } from '@/database/server';
4
4
  import { UserModel } from '@/database/server/models/user';
5
5
  import { pino } from '@/libs/logger';
6
6
  import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
7
+ import { AgentService } from '@/server/services/agent';
7
8
 
8
9
  export class UserService {
9
10
  createUser = async (id: string, params: UserJSON) => {
@@ -41,6 +42,10 @@ export class UserService {
41
42
  username: params.username,
42
43
  });
43
44
 
45
+ // 3. Create an inbox session for the user
46
+ const agentService = new AgentService(serverDB, id);
47
+ await agentService.createInbox();
48
+
44
49
  /* ↓ cloud slot ↓ */
45
50
 
46
51
  /* ↑ cloud slot ↑ */
@@ -52,6 +52,18 @@ export class ClientService extends BaseClientService implements ISessionService
52
52
  };
53
53
 
54
54
  getSessionConfig: ISessionService['getSessionConfig'] = async (id) => {
55
+ if (id === INBOX_SESSION_ID) {
56
+ const item = await this.sessionModel.findByIdOrSlug(INBOX_SESSION_ID);
57
+
58
+ // if there is no session for user, create one
59
+ if (!item) {
60
+ const defaultAgentConfig =
61
+ window.global_serverConfigStore.getState().serverConfig.defaultAgent?.config || {};
62
+
63
+ await this.sessionModel.createInbox(defaultAgentConfig);
64
+ }
65
+ }
66
+
55
67
  const res = await this.sessionModel.findByIdOrSlug(id);
56
68
 
57
69
  if (!res) throw new Error('Session not found');