@lobehub/chat 1.92.0 → 1.92.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 +53 -0
- package/changelog/v1.json +18 -0
- package/package.json +1 -1
- package/src/components/ModelSelect/index.tsx +6 -3
- package/src/config/aiModels/google.ts +25 -0
- package/src/config/aiModels/hunyuan.ts +44 -0
- package/src/config/aiModels/novita.ts +39 -3
- package/src/config/aiModels/openrouter.ts +0 -1
- package/src/config/aiModels/qwen.ts +48 -6
- package/src/config/aiModels/siliconcloud.ts +0 -106
- package/src/database/migrations/0024_add_rbac_tables.sql +49 -0
- package/src/database/migrations/meta/0024_snapshot.json +6192 -0
- package/src/database/migrations/meta/_journal.json +7 -0
- package/src/database/schemas/index.ts +1 -0
- package/src/database/schemas/rbac.ts +82 -0
- package/src/libs/model-runtime/azureOpenai/index.ts +47 -23
- package/src/store/user/slices/auth/selectors.test.ts +76 -0
- package/src/store/user/slices/auth/selectors.ts +2 -0
- package/src/utils/client/parserPlaceholder.test.ts +66 -77
- package/src/utils/client/parserPlaceholder.ts +3 -1
@@ -168,6 +168,13 @@
|
|
168
168
|
"when": 1748925630721,
|
169
169
|
"tag": "0023_remove_param_and_doubao",
|
170
170
|
"breakpoints": true
|
171
|
+
},
|
172
|
+
{
|
173
|
+
"idx": 24,
|
174
|
+
"version": "7",
|
175
|
+
"when": 1749301573666,
|
176
|
+
"tag": "0024_add_rbac_tables",
|
177
|
+
"breakpoints": true
|
171
178
|
}
|
172
179
|
],
|
173
180
|
"version": "6"
|
@@ -0,0 +1,82 @@
|
|
1
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
2
|
+
import { boolean, index, integer, pgTable, primaryKey, text, timestamp } from 'drizzle-orm/pg-core';
|
3
|
+
|
4
|
+
import { timestamps } from './_helpers';
|
5
|
+
import { users } from './user';
|
6
|
+
|
7
|
+
// Roles table
|
8
|
+
export const roles = pgTable('rbac_roles', {
|
9
|
+
id: integer('id').primaryKey().generatedByDefaultAsIdentity(),
|
10
|
+
name: text('name').notNull().unique(), // Role name, e.g.: admin, user, guest
|
11
|
+
displayName: text('display_name').notNull(), // Display name
|
12
|
+
description: text('description'), // Role description
|
13
|
+
isSystem: boolean('is_system').default(false).notNull(), // Whether it's a system role
|
14
|
+
isActive: boolean('is_active').default(true).notNull(), // Whether it's active
|
15
|
+
|
16
|
+
...timestamps,
|
17
|
+
});
|
18
|
+
|
19
|
+
export type NewRole = typeof roles.$inferInsert;
|
20
|
+
export type RoleItem = typeof roles.$inferSelect;
|
21
|
+
|
22
|
+
// Permissions table
|
23
|
+
export const permissions = pgTable('rbac_permissions', {
|
24
|
+
id: integer('id').primaryKey().generatedByDefaultAsIdentity(),
|
25
|
+
code: text('code').notNull().unique(), // Permission code, e.g.: chat:create, file:upload
|
26
|
+
name: text('name').notNull(), // Permission name
|
27
|
+
description: text('description'), // Permission description
|
28
|
+
category: text('category').notNull(), // Category it belongs to, e.g.: message, knowledge_base, agent
|
29
|
+
isActive: boolean('is_active').default(true).notNull(), // Whether it's active
|
30
|
+
|
31
|
+
...timestamps,
|
32
|
+
});
|
33
|
+
|
34
|
+
export type NewPermission = typeof permissions.$inferInsert;
|
35
|
+
export type PermissionItem = typeof permissions.$inferSelect;
|
36
|
+
|
37
|
+
// Role-permission association table
|
38
|
+
export const rolePermissions = pgTable(
|
39
|
+
'rbac_role_permissions',
|
40
|
+
{
|
41
|
+
roleId: integer('role_id')
|
42
|
+
.references(() => roles.id, { onDelete: 'cascade' })
|
43
|
+
.notNull(),
|
44
|
+
permissionId: integer('permission_id')
|
45
|
+
.references(() => permissions.id, { onDelete: 'cascade' })
|
46
|
+
.notNull(),
|
47
|
+
|
48
|
+
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
49
|
+
},
|
50
|
+
(self) => [
|
51
|
+
primaryKey({ columns: [self.roleId, self.permissionId] }),
|
52
|
+
index('rbac_role_permissions_role_id_idx').on(self.roleId),
|
53
|
+
index('rbac_role_permissions_permission_id_idx').on(self.permissionId),
|
54
|
+
],
|
55
|
+
);
|
56
|
+
|
57
|
+
export type NewRolePermission = typeof rolePermissions.$inferInsert;
|
58
|
+
export type RolePermissionItem = typeof rolePermissions.$inferSelect;
|
59
|
+
|
60
|
+
// User-role association table
|
61
|
+
export const userRoles = pgTable(
|
62
|
+
'rbac_user_roles',
|
63
|
+
{
|
64
|
+
userId: text('user_id')
|
65
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
66
|
+
.notNull(),
|
67
|
+
roleId: integer('role_id')
|
68
|
+
.references(() => roles.id, { onDelete: 'cascade' })
|
69
|
+
.notNull(),
|
70
|
+
|
71
|
+
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
72
|
+
expiresAt: timestamp('expires_at', { withTimezone: true }), // Support for temporary roles
|
73
|
+
},
|
74
|
+
(self) => [
|
75
|
+
primaryKey({ columns: [self.userId, self.roleId] }),
|
76
|
+
index('rbac_user_roles_user_id_idx').on(self.userId),
|
77
|
+
index('rbac_user_roles_role_id_idx').on(self.roleId),
|
78
|
+
],
|
79
|
+
);
|
80
|
+
|
81
|
+
export type NewUserRole = typeof userRoles.$inferInsert;
|
82
|
+
export type UserRoleItem = typeof userRoles.$inferSelect;
|
@@ -5,7 +5,14 @@ import { systemToUserModels } from '@/const/models';
|
|
5
5
|
|
6
6
|
import { LobeRuntimeAI } from '../BaseAI';
|
7
7
|
import { AgentRuntimeErrorType } from '../error';
|
8
|
-
import {
|
8
|
+
import {
|
9
|
+
ChatMethodOptions,
|
10
|
+
ChatStreamPayload,
|
11
|
+
Embeddings,
|
12
|
+
EmbeddingsOptions,
|
13
|
+
EmbeddingsPayload,
|
14
|
+
ModelProvider,
|
15
|
+
} from '../types';
|
9
16
|
import { AgentRuntimeError } from '../utils/createError';
|
10
17
|
import { debugStream } from '../utils/debugStream';
|
11
18
|
import { transformResponseToStream } from '../utils/openaiCompatibleFactory';
|
@@ -75,33 +82,50 @@ export class LobeAzureOpenAI implements LobeRuntimeAI {
|
|
75
82
|
});
|
76
83
|
}
|
77
84
|
} catch (e) {
|
78
|
-
|
85
|
+
return this.handleError(e, model);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
async embeddings(payload: EmbeddingsPayload, options?: EmbeddingsOptions): Promise<Embeddings[]> {
|
90
|
+
try {
|
91
|
+
const res = await this.client.embeddings.create(
|
92
|
+
{ ...payload, encoding_format: 'float', user: options?.user },
|
93
|
+
{ headers: options?.headers, signal: options?.signal },
|
94
|
+
);
|
95
|
+
|
96
|
+
return res.data.map((item) => item.embedding);
|
97
|
+
} catch (error) {
|
98
|
+
return this.handleError(error, payload.model);
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
protected handleError(e: any, model?: string): never {
|
103
|
+
let error = e as { [key: string]: any; code: string; message: string };
|
79
104
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
}
|
105
|
+
if (error.code) {
|
106
|
+
switch (error.code) {
|
107
|
+
case 'DeploymentNotFound': {
|
108
|
+
error = { ...error, deployId: model };
|
85
109
|
}
|
86
|
-
} else {
|
87
|
-
error = {
|
88
|
-
cause: error.cause,
|
89
|
-
message: error.message,
|
90
|
-
name: error.name,
|
91
|
-
} as any;
|
92
110
|
}
|
111
|
+
} else {
|
112
|
+
error = {
|
113
|
+
cause: error.cause,
|
114
|
+
message: error.message,
|
115
|
+
name: error.name,
|
116
|
+
} as any;
|
117
|
+
}
|
93
118
|
|
94
|
-
|
95
|
-
|
96
|
-
|
119
|
+
const errorType = error.code
|
120
|
+
? AgentRuntimeErrorType.ProviderBizError
|
121
|
+
: AgentRuntimeErrorType.AgentRuntimeError;
|
97
122
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
}
|
123
|
+
throw AgentRuntimeError.chat({
|
124
|
+
endpoint: this.maskSensitiveUrl(this.baseURL),
|
125
|
+
error,
|
126
|
+
errorType,
|
127
|
+
provider: ModelProvider.Azure,
|
128
|
+
});
|
105
129
|
}
|
106
130
|
|
107
131
|
// Convert object keys to camel case, copy from `@azure/openai` in `node_modules/@azure/openai/dist/index.cjs`
|
@@ -33,6 +33,82 @@ afterEach(() => {
|
|
33
33
|
});
|
34
34
|
|
35
35
|
describe('userProfileSelectors', () => {
|
36
|
+
describe('displayUserName', () => {
|
37
|
+
it('should return default username when auth is disabled and not desktop', () => {
|
38
|
+
enableAuth = false;
|
39
|
+
isDesktop = false;
|
40
|
+
|
41
|
+
const store: UserStore = {
|
42
|
+
isSignedIn: false,
|
43
|
+
user: null,
|
44
|
+
enableAuth: () => false,
|
45
|
+
} as unknown as UserStore;
|
46
|
+
|
47
|
+
expect(userProfileSelectors.displayUserName(store)).toBe('LobeChat');
|
48
|
+
});
|
49
|
+
|
50
|
+
it('should return user username when auth is disabled and is desktop', () => {
|
51
|
+
enableAuth = false;
|
52
|
+
isDesktop = true;
|
53
|
+
|
54
|
+
const store: UserStore = {
|
55
|
+
isSignedIn: false,
|
56
|
+
user: { username: 'johndoe' },
|
57
|
+
enableAuth: () => false,
|
58
|
+
} as unknown as UserStore;
|
59
|
+
|
60
|
+
expect(userProfileSelectors.displayUserName(store)).toBe('johndoe');
|
61
|
+
});
|
62
|
+
|
63
|
+
it('should return user username when signed in', () => {
|
64
|
+
const store: UserStore = {
|
65
|
+
isSignedIn: true,
|
66
|
+
user: { username: 'johndoe' },
|
67
|
+
enableAuth: () => true,
|
68
|
+
} as UserStore;
|
69
|
+
|
70
|
+
expect(userProfileSelectors.displayUserName(store)).toBe('johndoe');
|
71
|
+
});
|
72
|
+
|
73
|
+
it('should return email when signed in but username is not existed in UserStore', () => {
|
74
|
+
const store: UserStore = {
|
75
|
+
isSignedIn: true,
|
76
|
+
user: { email: 'demo@lobehub.com' },
|
77
|
+
enableAuth: () => true,
|
78
|
+
} as UserStore;
|
79
|
+
|
80
|
+
expect(userProfileSelectors.displayUserName(store)).toBe('demo@lobehub.com');
|
81
|
+
});
|
82
|
+
|
83
|
+
it('should return "anonymous" when not signed in', () => {
|
84
|
+
const store: UserStore = {
|
85
|
+
enableAuth: () => true,
|
86
|
+
isSignedIn: false,
|
87
|
+
user: null,
|
88
|
+
} as unknown as UserStore;
|
89
|
+
|
90
|
+
expect(userProfileSelectors.displayUserName(store)).toBe('anonymous');
|
91
|
+
});
|
92
|
+
});
|
93
|
+
|
94
|
+
describe('email', () => {
|
95
|
+
it('should return user email if exist', () => {
|
96
|
+
const store: UserStore = {
|
97
|
+
user: { email: 'demo@lobehub.com' },
|
98
|
+
} as UserStore;
|
99
|
+
|
100
|
+
expect(userProfileSelectors.email(store)).toBe('demo@lobehub.com');
|
101
|
+
});
|
102
|
+
|
103
|
+
it('should return empty string if not exist', () => {
|
104
|
+
const store: UserStore = {
|
105
|
+
user: { email: undefined },
|
106
|
+
} as UserStore;
|
107
|
+
|
108
|
+
expect(userProfileSelectors.email(store)).toBe('');
|
109
|
+
});
|
110
|
+
});
|
111
|
+
|
36
112
|
describe('fullName', () => {
|
37
113
|
it('should return user fullName if exist', () => {
|
38
114
|
const store: UserStore = {
|
@@ -36,6 +36,8 @@ const username = (s: UserStore) => {
|
|
36
36
|
};
|
37
37
|
|
38
38
|
export const userProfileSelectors = {
|
39
|
+
displayUserName: (s: UserStore): string => username(s) || s.user?.email || '',
|
40
|
+
email: (s: UserStore): string => s.user?.email || '',
|
39
41
|
fullName: (s: UserStore): string => s.user?.fullName || '',
|
40
42
|
nickName,
|
41
43
|
userAvatar: (s: UserStore): string => s.user?.avatar || '',
|
@@ -1,35 +1,36 @@
|
|
1
|
-
import {
|
2
|
-
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
2
|
+
|
3
|
+
import { VARIABLE_GENERATORS, parsePlaceholderVariablesMessages } from './parserPlaceholder';
|
3
4
|
|
4
5
|
// Mock dependencies
|
5
6
|
vi.mock('@/utils/uuid', () => ({
|
6
|
-
uuid: () => 'mocked-uuid-12345'
|
7
|
+
uuid: () => 'mocked-uuid-12345',
|
7
8
|
}));
|
8
9
|
|
9
10
|
vi.mock('@/store/user', () => ({
|
10
11
|
useUserStore: {
|
11
|
-
getState: () => ({})
|
12
|
-
}
|
12
|
+
getState: () => ({}),
|
13
|
+
},
|
13
14
|
}));
|
14
15
|
|
15
16
|
vi.mock('@/store/user/selectors', () => ({
|
16
17
|
userProfileSelectors: {
|
18
|
+
displayUserName: () => 'testuser',
|
17
19
|
nickName: () => 'Test User',
|
18
|
-
|
19
|
-
|
20
|
-
}
|
20
|
+
fullName: () => 'Test Full Name',
|
21
|
+
},
|
21
22
|
}));
|
22
23
|
|
23
24
|
vi.mock('@/store/agent/store', () => ({
|
24
|
-
getAgentStoreState: () => ({})
|
25
|
+
getAgentStoreState: () => ({}),
|
25
26
|
}));
|
26
27
|
|
27
28
|
vi.mock('@/store/agent/selectors', () => ({
|
28
29
|
agentChatConfigSelectors: {
|
29
30
|
currentChatConfig: () => ({
|
30
|
-
inputTemplate: 'Hello {{username}}!'
|
31
|
-
})
|
32
|
-
}
|
31
|
+
inputTemplate: 'Hello {{username}}!',
|
32
|
+
}),
|
33
|
+
},
|
33
34
|
}));
|
34
35
|
|
35
36
|
describe('parsePlaceholderVariablesMessages', () => {
|
@@ -52,8 +53,8 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
52
53
|
const messages = [
|
53
54
|
{
|
54
55
|
id: '1',
|
55
|
-
content: 'Hello {{username}}, today is {{date}}'
|
56
|
-
}
|
56
|
+
content: 'Hello {{username}}, today is {{date}}',
|
57
|
+
},
|
57
58
|
];
|
58
59
|
|
59
60
|
const result = parsePlaceholderVariablesMessages(messages);
|
@@ -66,8 +67,8 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
66
67
|
const messages = [
|
67
68
|
{
|
68
69
|
id: '1',
|
69
|
-
content: 'Time: {{time}}, Date: {{date}}, User: {{nickname}}'
|
70
|
-
}
|
70
|
+
content: 'Time: {{time}}, Date: {{date}}, User: {{nickname}}',
|
71
|
+
},
|
71
72
|
];
|
72
73
|
|
73
74
|
const result = parsePlaceholderVariablesMessages(messages);
|
@@ -81,8 +82,8 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
81
82
|
{
|
82
83
|
id: '1',
|
83
84
|
role: 'user',
|
84
|
-
content: 'Hello {{username}}'
|
85
|
-
}
|
85
|
+
content: 'Hello {{username}}',
|
86
|
+
},
|
86
87
|
];
|
87
88
|
|
88
89
|
const result = parsePlaceholderVariablesMessages(messages);
|
@@ -90,7 +91,7 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
90
91
|
expect(result[0]).toEqual({
|
91
92
|
id: '1',
|
92
93
|
role: 'user',
|
93
|
-
content: 'Hello testuser'
|
94
|
+
content: 'Hello testuser',
|
94
95
|
});
|
95
96
|
});
|
96
97
|
});
|
@@ -103,14 +104,14 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
103
104
|
content: [
|
104
105
|
{
|
105
106
|
type: 'text',
|
106
|
-
text: 'Hello {{username}}'
|
107
|
+
text: 'Hello {{username}}',
|
107
108
|
},
|
108
109
|
{
|
109
110
|
type: 'image_url',
|
110
|
-
image_url: 'image.jpg'
|
111
|
-
}
|
112
|
-
]
|
113
|
-
}
|
111
|
+
image_url: 'image.jpg',
|
112
|
+
},
|
113
|
+
],
|
114
|
+
},
|
114
115
|
];
|
115
116
|
|
116
117
|
const result = parsePlaceholderVariablesMessages(messages);
|
@@ -118,7 +119,7 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
118
119
|
expect(result[0].content[0].text).toBe('Hello testuser');
|
119
120
|
expect(result[0].content[1]).toEqual({
|
120
121
|
type: 'image_url',
|
121
|
-
image_url: 'image.jpg'
|
122
|
+
image_url: 'image.jpg',
|
122
123
|
});
|
123
124
|
});
|
124
125
|
|
@@ -129,18 +130,18 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
129
130
|
content: [
|
130
131
|
{
|
131
132
|
type: 'text',
|
132
|
-
text: 'Date: {{date}}'
|
133
|
+
text: 'Date: {{date}}',
|
133
134
|
},
|
134
135
|
{
|
135
136
|
type: 'text',
|
136
|
-
text: 'Time: {{time}}'
|
137
|
+
text: 'Time: {{time}}',
|
137
138
|
},
|
138
139
|
{
|
139
140
|
type: 'image_url',
|
140
|
-
image_url: 'test.jpg'
|
141
|
-
}
|
142
|
-
]
|
143
|
-
}
|
141
|
+
image_url: 'test.jpg',
|
142
|
+
},
|
143
|
+
],
|
144
|
+
},
|
144
145
|
];
|
145
146
|
|
146
147
|
const result = parsePlaceholderVariablesMessages(messages);
|
@@ -149,7 +150,7 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
149
150
|
expect(result[0].content[1].text).toContain(new Date().toLocaleTimeString());
|
150
151
|
expect(result[0].content[2]).toEqual({
|
151
152
|
type: 'image_url',
|
152
|
-
image_url: 'test.jpg'
|
153
|
+
image_url: 'test.jpg',
|
153
154
|
});
|
154
155
|
});
|
155
156
|
|
@@ -164,10 +165,10 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
164
165
|
},
|
165
166
|
{
|
166
167
|
type: 'image_url',
|
167
|
-
name: 'image2.jpg'
|
168
|
-
}
|
169
|
-
]
|
170
|
-
}
|
168
|
+
name: 'image2.jpg',
|
169
|
+
},
|
170
|
+
],
|
171
|
+
},
|
171
172
|
];
|
172
173
|
|
173
174
|
const result = parsePlaceholderVariablesMessages(messages);
|
@@ -175,12 +176,12 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
175
176
|
expect(result[0].content).toEqual([
|
176
177
|
{
|
177
178
|
type: 'image_url',
|
178
|
-
image_url: 'image.jpg'
|
179
|
+
image_url: 'image.jpg',
|
179
180
|
},
|
180
181
|
{
|
181
182
|
type: 'image_url',
|
182
|
-
name: 'image2.jpg'
|
183
|
-
}
|
183
|
+
name: 'image2.jpg',
|
184
|
+
},
|
184
185
|
]);
|
185
186
|
});
|
186
187
|
});
|
@@ -192,25 +193,19 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
192
193
|
});
|
193
194
|
|
194
195
|
it('should handle messages without content', () => {
|
195
|
-
const messages = [
|
196
|
-
{ id: '1' },
|
197
|
-
{ id: '2', content: null },
|
198
|
-
{ id: '3', content: undefined }
|
199
|
-
];
|
196
|
+
const messages = [{ id: '1' }, { id: '2', content: null }, { id: '3', content: undefined }];
|
200
197
|
|
201
198
|
const result = parsePlaceholderVariablesMessages(messages);
|
202
199
|
|
203
200
|
expect(result).toEqual([
|
204
201
|
{ id: '1' },
|
205
202
|
{ id: '2', content: null },
|
206
|
-
{ id: '3', content: undefined }
|
203
|
+
{ id: '3', content: undefined },
|
207
204
|
]);
|
208
205
|
});
|
209
206
|
|
210
207
|
it('should handle empty string content', () => {
|
211
|
-
const messages = [
|
212
|
-
{ id: '1', content: '' }
|
213
|
-
];
|
208
|
+
const messages = [{ id: '1', content: '' }];
|
214
209
|
|
215
210
|
const result = parsePlaceholderVariablesMessages(messages);
|
216
211
|
|
@@ -220,13 +215,13 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
220
215
|
it('should handle content without variables', () => {
|
221
216
|
const messages = [
|
222
217
|
{ id: '1', content: 'Hello world!' },
|
223
|
-
{
|
224
|
-
id: '2',
|
218
|
+
{
|
219
|
+
id: '2',
|
225
220
|
content: [
|
226
221
|
{ type: 'text', text: 'No variables here' },
|
227
|
-
{ type: 'image_url', image_url: 'test.jpg' }
|
228
|
-
]
|
229
|
-
}
|
222
|
+
{ type: 'image_url', image_url: 'test.jpg' },
|
223
|
+
],
|
224
|
+
},
|
230
225
|
];
|
231
226
|
|
232
227
|
const result = parsePlaceholderVariablesMessages(messages);
|
@@ -236,9 +231,7 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
236
231
|
});
|
237
232
|
|
238
233
|
it('should handle unknown variable types', () => {
|
239
|
-
const messages = [
|
240
|
-
{ id: '1', content: 'Hello {{unknown_variable}}!' }
|
241
|
-
];
|
234
|
+
const messages = [{ id: '1', content: 'Hello {{unknown_variable}}!' }];
|
242
235
|
|
243
236
|
const result = parsePlaceholderVariablesMessages(messages);
|
244
237
|
|
@@ -247,9 +240,7 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
247
240
|
});
|
248
241
|
|
249
242
|
it('should handle nested variables (input_template)', () => {
|
250
|
-
const messages = [
|
251
|
-
{ id: '1', content: 'Template: {{input_template}}' }
|
252
|
-
];
|
243
|
+
const messages = [{ id: '1', content: 'Template: {{input_template}}' }];
|
253
244
|
|
254
245
|
const result = parsePlaceholderVariablesMessages(messages);
|
255
246
|
|
@@ -261,10 +252,10 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
261
252
|
describe('specific variable types', () => {
|
262
253
|
it('should handle time variables', () => {
|
263
254
|
const messages = [
|
264
|
-
{
|
265
|
-
id: '1',
|
266
|
-
content: 'Year: {{year}}, Month: {{month}}, Day: {{day}}'
|
267
|
-
}
|
255
|
+
{
|
256
|
+
id: '1',
|
257
|
+
content: 'Year: {{year}}, Month: {{month}}, Day: {{day}}',
|
258
|
+
},
|
268
259
|
];
|
269
260
|
|
270
261
|
const result = parsePlaceholderVariablesMessages(messages);
|
@@ -276,10 +267,10 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
276
267
|
|
277
268
|
it('should handle random variables', () => {
|
278
269
|
const messages = [
|
279
|
-
{
|
280
|
-
id: '1',
|
281
|
-
content: 'Random: {{random}}, Bool: {{random_bool}}, UUID: {{uuid}}'
|
282
|
-
}
|
270
|
+
{
|
271
|
+
id: '1',
|
272
|
+
content: 'Random: {{random}}, Bool: {{random_bool}}, UUID: {{uuid}}',
|
273
|
+
},
|
283
274
|
];
|
284
275
|
|
285
276
|
const result = parsePlaceholderVariablesMessages(messages);
|
@@ -291,10 +282,10 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
291
282
|
|
292
283
|
it('should handle user variables', () => {
|
293
284
|
const messages = [
|
294
|
-
{
|
295
|
-
id: '1',
|
296
|
-
content: 'User: {{username}}, Nickname: {{nickname}}'
|
297
|
-
}
|
285
|
+
{
|
286
|
+
id: '1',
|
287
|
+
content: 'User: {{username}}, Nickname: {{nickname}}',
|
288
|
+
},
|
298
289
|
];
|
299
290
|
|
300
291
|
const result = parsePlaceholderVariablesMessages(messages);
|
@@ -307,13 +298,11 @@ describe('parsePlaceholderVariablesMessages', () => {
|
|
307
298
|
it('should process multiple messages correctly', () => {
|
308
299
|
const messages = [
|
309
300
|
{ id: '1', content: 'Hello {{username}}' },
|
310
|
-
{
|
311
|
-
id: '2',
|
312
|
-
content: [
|
313
|
-
{ type: 'text', text: 'Today is {{date}}' }
|
314
|
-
]
|
301
|
+
{
|
302
|
+
id: '2',
|
303
|
+
content: [{ type: 'text', text: 'Today is {{date}}' }],
|
315
304
|
},
|
316
|
-
{ id: '3', content: 'Time: {{time}}' }
|
305
|
+
{ id: '3', content: 'Time: {{time}}' },
|
317
306
|
];
|
318
307
|
|
319
308
|
const result = parsePlaceholderVariablesMessages(messages);
|
@@ -53,12 +53,14 @@ export const VARIABLE_GENERATORS = {
|
|
53
53
|
*
|
54
54
|
* | Value | Example |
|
55
55
|
* |-------|---------|
|
56
|
+
* | `{{email}}` | demo@lobehub.com |
|
56
57
|
* | `{{nickname}}` | 社区版用户 |
|
57
58
|
* | `{{username}}` | LobeChat |
|
58
59
|
*
|
59
60
|
*/
|
61
|
+
email: () => userProfileSelectors.email(useUserStore.getState()) ?? '',
|
60
62
|
nickname: () => userProfileSelectors.nickName(useUserStore.getState()) ?? '',
|
61
|
-
username: () => userProfileSelectors.
|
63
|
+
username: () => userProfileSelectors.displayUserName(useUserStore.getState()) ?? userProfileSelectors.fullName(useUserStore.getState()) ?? '',
|
62
64
|
|
63
65
|
/**
|
64
66
|
* 随机值类模板变量
|