@lobehub/lobehub 2.0.0-next.16 → 2.0.0-next.18

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 (45) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +2 -45
  3. package/README.zh-CN.md +2 -45
  4. package/changelog/v1.json +18 -0
  5. package/e2e/src/features/discover/smoke.feature +34 -1
  6. package/e2e/src/steps/discover/smoke.steps.ts +116 -4
  7. package/locales/ar/oauth.json +1 -0
  8. package/locales/bg-BG/oauth.json +1 -0
  9. package/locales/de-DE/oauth.json +1 -0
  10. package/locales/en-US/oauth.json +1 -0
  11. package/locales/es-ES/oauth.json +1 -0
  12. package/locales/fa-IR/oauth.json +1 -0
  13. package/locales/fr-FR/oauth.json +1 -0
  14. package/locales/it-IT/oauth.json +1 -0
  15. package/locales/ja-JP/oauth.json +1 -0
  16. package/locales/ko-KR/oauth.json +1 -0
  17. package/locales/nl-NL/oauth.json +1 -0
  18. package/locales/pl-PL/oauth.json +1 -0
  19. package/locales/pt-BR/oauth.json +1 -0
  20. package/locales/ru-RU/oauth.json +1 -0
  21. package/locales/tr-TR/oauth.json +1 -0
  22. package/locales/vi-VN/oauth.json +1 -0
  23. package/locales/zh-CN/oauth.json +1 -0
  24. package/locales/zh-TW/oauth.json +1 -0
  25. package/package.json +1 -1
  26. package/packages/model-runtime/src/utils/googleErrorParser.test.ts +125 -0
  27. package/packages/model-runtime/src/utils/googleErrorParser.ts +103 -77
  28. package/packages/types/src/message/ui/params.ts +98 -4
  29. package/packages/types/src/user/index.ts +13 -1
  30. package/packages/types/src/user/settings/index.ts +22 -0
  31. package/src/app/[variants]/(main)/discover/(list)/features/Pagination.tsx +1 -0
  32. package/src/app/[variants]/(main)/discover/(list)/features/SortButton/index.tsx +1 -1
  33. package/src/app/[variants]/(main)/discover/(list)/mcp/features/List/Item.tsx +1 -0
  34. package/src/app/[variants]/(main)/discover/(list)/model/features/List/Item.tsx +1 -0
  35. package/src/app/[variants]/(main)/discover/(list)/provider/features/List/Item.tsx +1 -0
  36. package/src/app/[variants]/(main)/discover/components/CategoryMenu.tsx +9 -1
  37. package/src/app/[variants]/oauth/consent/[uid]/Consent/BuiltinConsent.tsx +57 -0
  38. package/src/app/[variants]/oauth/consent/[uid]/{Consent.tsx → Consent/index.tsx} +9 -1
  39. package/src/app/[variants]/oauth/consent/[uid]/Login.tsx +9 -1
  40. package/src/locales/default/oauth.ts +1 -0
  41. package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +0 -41
  42. package/src/server/routers/lambda/message.ts +4 -11
  43. package/src/server/routers/lambda/user.ts +24 -25
  44. package/src/services/message/server.ts +0 -4
  45. package/src/services/message/type.ts +0 -2
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+
3
+ import { Icon } from '@lobehub/ui';
4
+ import { Card, Result } from 'antd';
5
+ import { useTheme } from 'antd-style';
6
+ import { LoaderCircle } from 'lucide-react';
7
+ import { memo, useEffect, useRef } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { Center } from 'react-layout-kit';
10
+
11
+ interface BuiltinConsentProps {
12
+ uid: string;
13
+ }
14
+
15
+ const BuiltinConsent = memo<BuiltinConsentProps>(({ uid }) => {
16
+ const { t } = useTranslation('oauth');
17
+ const formRef = useRef<HTMLFormElement>(null);
18
+
19
+ useEffect(() => {
20
+ // Auto-submit on mount
21
+ formRef.current?.submit();
22
+ }, []);
23
+
24
+ const theme = useTheme();
25
+ return (
26
+ <>
27
+ <Center height="100vh">
28
+ <Card
29
+ style={{
30
+ alignItems: 'center',
31
+ display: 'flex',
32
+ justifyContent: 'center',
33
+ minHeight: 280,
34
+ minWidth: 500,
35
+ width: '100%',
36
+ }}
37
+ >
38
+ <Result
39
+ icon={<Icon icon={LoaderCircle} spin style={{ color: theme.colorText }} />}
40
+ status="success"
41
+ style={{ padding: 0 }}
42
+ subTitle={t('consent.redirecting')}
43
+ title=""
44
+ />
45
+ </Card>
46
+ </Center>
47
+ <form action="/oidc/consent" method="post" ref={formRef} style={{ display: 'none' }}>
48
+ <input name="uid" type="hidden" value={uid} />
49
+ <input name="consent" type="hidden" value="accept" />
50
+ </form>
51
+ </>
52
+ );
53
+ });
54
+
55
+ BuiltinConsent.displayName = 'BuiltinConsent';
56
+
57
+ export default BuiltinConsent;
@@ -7,7 +7,8 @@ import { memo, useState } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Center, Flexbox } from 'react-layout-kit';
9
9
 
10
- import OAuthApplicationLogo from './components/OAuthApplicationLogo';
10
+ import OAuthApplicationLogo from '../components/OAuthApplicationLogo';
11
+ import BuiltinConsent from './BuiltinConsent';
11
12
 
12
13
  interface ClientProps {
13
14
  clientId: string;
@@ -117,6 +118,8 @@ function getScopeDescription(scope: string, t: any): string {
117
118
  return t(`consent.scope.${scope.replace(':', '-')}`, scope);
118
119
  }
119
120
 
121
+ const BUILTIN_CLIENTS = new Set(['lobehub-desktop', 'lobehub-mobile', 'lobehub-market']);
122
+
120
123
  const ConsentClient = memo<ClientProps>(({ uid, clientId, scopes, clientMetadata }) => {
121
124
  const { styles } = useStyles();
122
125
  const { t } = useTranslation('oauth');
@@ -124,6 +127,11 @@ const ConsentClient = memo<ClientProps>(({ uid, clientId, scopes, clientMetadata
124
127
  const [isLoading, setIsLoading] = useState(false);
125
128
 
126
129
  const clientDisplayName = clientMetadata?.clientName || clientId;
130
+
131
+ if (BUILTIN_CLIENTS.has(clientId)) {
132
+ return <BuiltinConsent uid={clientId} />;
133
+ }
134
+
127
135
  return (
128
136
  <Center className={styles.container} gap={16}>
129
137
  <Flexbox gap={40}>
@@ -59,6 +59,8 @@ const LoginConfirmClient = memo<LoginConfirmProps>(({ uid, clientMetadata }) =>
59
59
  const avatar = useUserStore(userProfileSelectors.userAvatar);
60
60
  const nickName = useUserStore(userProfileSelectors.nickName);
61
61
 
62
+ const [isLoading, setIsLoading] = React.useState(false);
63
+
62
64
  const titleText = t('login.title', { clientName: clientDisplayName });
63
65
  const descriptionText = t('login.description', { clientName: clientDisplayName });
64
66
  const buttonText = t('login.button'); // Or "Continue"
@@ -99,7 +101,12 @@ const LoginConfirmClient = memo<LoginConfirmProps>(({ uid, clientMetadata }) =>
99
101
 
100
102
  <Flexbox gap={16}>
101
103
  {/* Form points to the endpoint handling login confirmation */}
102
- <form action="/oidc/consent" method="post" style={{ width: '100%' }}>
104
+ <form
105
+ action="/oidc/consent"
106
+ method="post"
107
+ onSubmit={() => setIsLoading(true)}
108
+ style={{ width: '100%' }}
109
+ >
103
110
  {/* Adjust action URL */}
104
111
  <input name="uid" type="hidden" value={uid} />
105
112
  <input name="choice" type="hidden" value={'accept'} />
@@ -108,6 +115,7 @@ const LoginConfirmClient = memo<LoginConfirmProps>(({ uid, clientMetadata }) =>
108
115
  className={styles.authButton}
109
116
  disabled={!isUserStateInit}
110
117
  htmlType="submit"
118
+ loading={isLoading}
111
119
  name="consent"
112
120
  size="large"
113
121
  type="primary"
@@ -19,6 +19,7 @@ const oauth = {
19
19
  },
20
20
  permissionsTitle: '请求以下权限:',
21
21
  redirectUri: '授权成功后将重定向到',
22
+ redirecting: '授权成功,跳转中...',
22
23
  scope: {
23
24
  'email': '访问您的电子邮件地址',
24
25
  'offline_access': '允许客户端访问您的数据',
@@ -318,47 +318,6 @@ describe('Message Router Integration Tests', () => {
318
318
  });
319
319
  });
320
320
 
321
- describe('batchCreateMessages', () => {
322
- it('should create multiple messages in batch', async () => {
323
- const caller = messageRouter.createCaller(createTestContext(userId));
324
-
325
- const messagesToCreate = [
326
- {
327
- content: 'Batch message 1',
328
- role: 'user' as const,
329
- sessionId: testSessionId,
330
- },
331
- {
332
- content: 'Batch message 2',
333
- role: 'assistant' as const,
334
- sessionId: testSessionId,
335
- },
336
- {
337
- content: 'Batch message 3',
338
- role: 'user' as const,
339
- sessionId: testSessionId,
340
- topicId: testTopicId,
341
- },
342
- ];
343
-
344
- const result = await caller.batchCreateMessages(messagesToCreate);
345
-
346
- expect(result.success).toBe(true);
347
- // Note: rowCount might be undefined in PGlite, so we skip this check
348
- // expect(result.added).toBe(3);
349
-
350
- // 验证数据库中的消息
351
- const dbMessages = await serverDB
352
- .select()
353
- .from(messages)
354
- .where(eq(messages.sessionId, testSessionId));
355
-
356
- expect(dbMessages.length).toBeGreaterThanOrEqual(3);
357
- const topicMessage = dbMessages.find((m) => m.content === 'Batch message 3');
358
- expect(topicMessage?.topicId).toBe(testTopicId);
359
- });
360
- });
361
-
362
321
  describe('removeMessages', () => {
363
322
  it('should remove multiple messages', async () => {
364
323
  const caller = messageRouter.createCaller(createTestContext(userId));
@@ -1,5 +1,6 @@
1
1
  import {
2
- BatchTaskResult,
2
+ CreateMessageParamsSchema,
3
+ CreateNewMessageParamsSchema,
3
4
  UIChatMessage,
4
5
  UpdateMessageParamsSchema,
5
6
  UpdateMessageRAGParamsSchema,
@@ -27,14 +28,6 @@ const messageProcedure = authedProcedure.use(serverDatabase).use(async (opts) =>
27
28
  });
28
29
 
29
30
  export const messageRouter = router({
30
- batchCreateMessages: messageProcedure
31
- .input(z.array(z.any()))
32
- .mutation(async ({ input, ctx }): Promise<BatchTaskResult> => {
33
- const data = await ctx.messageModel.batchCreate(input);
34
-
35
- return { added: data.rowCount as number, ids: [], skips: [], success: true };
36
- }),
37
-
38
31
  count: messageProcedure
39
32
  .input(
40
33
  z
@@ -64,7 +57,7 @@ export const messageRouter = router({
64
57
  }),
65
58
 
66
59
  createMessage: messageProcedure
67
- .input(z.object({}).passthrough().partial())
60
+ .input(CreateMessageParamsSchema)
68
61
  .mutation(async ({ input, ctx }) => {
69
62
  const data = await ctx.messageModel.create(input as any);
70
63
 
@@ -72,7 +65,7 @@ export const messageRouter = router({
72
65
  }),
73
66
 
74
67
  createNewMessage: messageProcedure
75
- .input(z.object({}).passthrough().partial())
68
+ .input(CreateNewMessageParamsSchema)
76
69
  .mutation(async ({ input, ctx }) => {
77
70
  return ctx.messageModel.createNewMessage(input as any, {
78
71
  postProcessUrl: (path) => ctx.fileService.getFullFileUrl(path),
@@ -1,9 +1,17 @@
1
1
  import { UserJSON } from '@clerk/backend';
2
+ import { enableClerk, isDesktop } from '@lobechat/const';
3
+ import {
4
+ NextAuthAccountSchame,
5
+ UserGuideSchema,
6
+ UserInitializationState,
7
+ UserPreference,
8
+ UserPreferenceSchema,
9
+ UserSettings,
10
+ UserSettingsSchema,
11
+ } from '@lobechat/types';
2
12
  import { v4 as uuidv4 } from 'uuid';
3
13
  import { z } from 'zod';
4
14
 
5
- import { enableClerk } from '@/const/auth';
6
- import { isDesktop } from '@/const/version';
7
15
  import { MessageModel } from '@/database/models/message';
8
16
  import { SessionModel } from '@/database/models/session';
9
17
  import { UserModel, UserNotFoundError } from '@/database/models/user';
@@ -16,13 +24,6 @@ import { S3 } from '@/server/modules/S3';
16
24
  import { FileService } from '@/server/services/file';
17
25
  import { NextAuthUserService } from '@/server/services/nextAuthUser';
18
26
  import { UserService } from '@/server/services/user';
19
- import {
20
- NextAuthAccountSchame,
21
- UserGuideSchema,
22
- UserInitializationState,
23
- UserPreference,
24
- } from '@/types/user';
25
- import { UserSettings } from '@/types/user/settings';
26
27
 
27
28
  const userProcedure = authedProcedure.use(serverDatabase).use(async ({ ctx, next }) => {
28
29
  return next({
@@ -199,30 +200,28 @@ export const userRouter = router({
199
200
  return ctx.userModel.updateGuide(input);
200
201
  }),
201
202
 
202
- updatePreference: userProcedure.input(z.any()).mutation(async ({ ctx, input }) => {
203
+ updatePreference: userProcedure.input(UserPreferenceSchema).mutation(async ({ ctx, input }) => {
203
204
  return ctx.userModel.updatePreference(input);
204
205
  }),
205
206
 
206
- updateSettings: userProcedure
207
- .input(z.object({}).passthrough())
208
- .mutation(async ({ ctx, input }) => {
209
- const { keyVaults, ...res } = input as Partial<UserSettings>;
207
+ updateSettings: userProcedure.input(UserSettingsSchema).mutation(async ({ ctx, input }) => {
208
+ const { keyVaults, ...res } = input as Partial<UserSettings>;
210
209
 
211
- // Encrypt keyVaults
212
- let encryptedKeyVaults: string | null = null;
210
+ // Encrypt keyVaults
211
+ let encryptedKeyVaults: string | null = null;
213
212
 
214
- if (keyVaults) {
215
- // TODO: better to add a validation
216
- const data = JSON.stringify(keyVaults);
217
- const gateKeeper = await KeyVaultsGateKeeper.initWithEnvKey();
213
+ if (keyVaults) {
214
+ // TODO: better to add a validation
215
+ const data = JSON.stringify(keyVaults);
216
+ const gateKeeper = await KeyVaultsGateKeeper.initWithEnvKey();
218
217
 
219
- encryptedKeyVaults = await gateKeeper.encrypt(data);
220
- }
218
+ encryptedKeyVaults = await gateKeeper.encrypt(data);
219
+ }
221
220
 
222
- const nextValue = { ...res, keyVaults: encryptedKeyVaults };
221
+ const nextValue = { ...res, keyVaults: encryptedKeyVaults };
223
222
 
224
- return ctx.userModel.updateSetting(nextValue);
225
- }),
223
+ return ctx.userModel.updateSetting(nextValue);
224
+ }),
226
225
  });
227
226
 
228
227
  export type UserRouter = typeof userRouter;
@@ -21,10 +21,6 @@ export class ServerService implements IMessageService {
21
21
  });
22
22
  };
23
23
 
24
- batchCreateMessages: IMessageService['batchCreateMessages'] = async (messages) => {
25
- return lambdaClient.message.batchCreateMessages.mutate(messages);
26
- };
27
-
28
24
  getMessages: IMessageService['getMessages'] = async (sessionId, topicId, groupId) => {
29
25
  const data = await lambdaClient.message.getMessages.query({
30
26
  groupId,
@@ -5,7 +5,6 @@ import {
5
5
  ChatTranslate,
6
6
  CreateMessageParams,
7
7
  CreateMessageResult,
8
- DBMessageItem,
9
8
  ModelRankItem,
10
9
  UIChatMessage,
11
10
  UpdateMessageParams,
@@ -19,7 +18,6 @@ import type { HeatmapsProps } from '@lobehub/charts';
19
18
  export interface IMessageService {
20
19
  createMessage(data: CreateMessageParams): Promise<string>;
21
20
  createNewMessage(data: CreateMessageParams): Promise<CreateMessageResult>;
22
- batchCreateMessages(messages: DBMessageItem[]): Promise<any>;
23
21
 
24
22
  getMessages(sessionId: string, topicId?: string, groupId?: string): Promise<UIChatMessage[]>;
25
23
  getGroupMessages(groupId: string, topicId?: string): Promise<UIChatMessage[]>;