@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 +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/src/database/server/models/__tests__/session.test.ts +3 -3
- package/src/database/server/models/session.ts +5 -6
- package/src/database/server/models/user.ts +1 -5
- package/src/features/DevPanel/features/Table/TableCell.tsx +34 -33
- package/src/libs/next-auth/adapter/index.ts +7 -0
- package/src/server/routers/lambda/agent.ts +3 -1
- package/src/server/services/agent/index.test.ts +65 -0
- package/src/server/services/agent/index.ts +22 -0
- package/src/server/services/user/index.test.ts +17 -1
- package/src/server/services/user/index.ts +5 -0
- package/src/services/session/client.ts +12 -0
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
|
+
[](#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
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.56.
|
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 {
|
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
|
-
|
234
|
+
if (item) return;
|
236
235
|
|
237
236
|
return await this.create({
|
238
|
-
config: merge(DEFAULT_AGENT_CONFIG,
|
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
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
>
|
67
|
-
|
68
|
-
|
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.
|
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');
|