@lobehub/lobehub 2.1.2 → 2.1.4

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 (174) hide show
  1. package/.env.example +4 -6
  2. package/.env.example.development +0 -3
  3. package/.github/workflows/release-desktop-stable.yml +1 -1
  4. package/CHANGELOG.md +59 -0
  5. package/Dockerfile +6 -4
  6. package/README.md +2 -3
  7. package/README.zh-CN.md +2 -3
  8. package/changelog/v2.json +14 -0
  9. package/docker-compose/deploy/.env.example +3 -1
  10. package/docker-compose/deploy/.env.zh-CN.example +4 -1
  11. package/docker-compose/local/.env.example +0 -1
  12. package/docker-compose/local/.env.zh-CN.example +0 -1
  13. package/docker-compose/local/grafana/.env.example +0 -1
  14. package/docker-compose/local/grafana/.env.zh-CN.example +0 -1
  15. package/docker-compose/local/logto/docker-compose.yml +0 -1
  16. package/docker-compose/local/zitadel/.env.example +1 -2
  17. package/docker-compose/local/zitadel/.env.zh-CN.example +1 -2
  18. package/docker-compose/production/grafana/.env.example +0 -1
  19. package/docker-compose/production/grafana/.env.zh-CN.example +0 -1
  20. package/docker-compose/production/logto/.env.example +0 -2
  21. package/docker-compose/production/logto/.env.zh-CN.example +0 -2
  22. package/docker-compose/production/zitadel/.env.example +0 -2
  23. package/docker-compose/production/zitadel/.env.zh-CN.example +0 -2
  24. package/docker-compose/setup.sh +16 -2
  25. package/docs/development/basic/folder-structure.mdx +23 -14
  26. package/docs/development/basic/folder-structure.zh-CN.mdx +23 -14
  27. package/docs/development/basic/work-with-server-side-database.mdx +0 -1
  28. package/docs/development/basic/work-with-server-side-database.zh-CN.mdx +0 -1
  29. package/docs/development/start.mdx +19 -12
  30. package/docs/development/start.zh-CN.mdx +19 -12
  31. package/docs/self-hosting/advanced/s3/cloudflare-r2.mdx +0 -5
  32. package/docs/self-hosting/advanced/s3/cloudflare-r2.zh-CN.mdx +0 -5
  33. package/docs/self-hosting/advanced/s3/rustfs.mdx +0 -2
  34. package/docs/self-hosting/advanced/s3/rustfs.zh-CN.mdx +0 -2
  35. package/docs/self-hosting/advanced/s3/tencent-cloud.mdx +0 -1
  36. package/docs/self-hosting/advanced/s3/tencent-cloud.zh-CN.mdx +0 -2
  37. package/docs/self-hosting/advanced/s3.mdx +0 -9
  38. package/docs/self-hosting/advanced/s3.zh-CN.mdx +0 -8
  39. package/docs/self-hosting/auth/providers/password.mdx +112 -0
  40. package/docs/self-hosting/auth/providers/password.zh-CN.mdx +103 -0
  41. package/docs/self-hosting/auth.mdx +12 -0
  42. package/docs/self-hosting/auth.zh-CN.mdx +12 -0
  43. package/docs/self-hosting/environment-variables/auth.mdx +7 -0
  44. package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +7 -0
  45. package/docs/self-hosting/environment-variables/basic.mdx +0 -7
  46. package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +0 -7
  47. package/docs/self-hosting/environment-variables/s3.mdx +0 -7
  48. package/docs/self-hosting/environment-variables/s3.zh-CN.mdx +0 -7
  49. package/docs/self-hosting/examples/azure-openai.mdx +0 -1
  50. package/docs/self-hosting/examples/azure-openai.zh-CN.mdx +0 -1
  51. package/docs/self-hosting/platform/docker-compose.mdx +0 -1
  52. package/docs/self-hosting/platform/docker-compose.zh-CN.mdx +0 -1
  53. package/docs/self-hosting/platform/docker.mdx +5 -3
  54. package/docs/self-hosting/platform/docker.zh-CN.mdx +5 -4
  55. package/docs/self-hosting/platform/dokploy.mdx +0 -2
  56. package/docs/self-hosting/platform/dokploy.zh-CN.mdx +0 -2
  57. package/docs/self-hosting/platform/vercel.mdx +0 -7
  58. package/docs/self-hosting/platform/vercel.zh-CN.mdx +0 -7
  59. package/e2e/src/steps/home/sidebarAgent.steps.ts +56 -24
  60. package/locales/ar/authError.json +1 -0
  61. package/locales/ar/models.json +25 -22
  62. package/locales/ar/providers.json +0 -1
  63. package/locales/ar/setting.json +16 -0
  64. package/locales/bg-BG/authError.json +1 -0
  65. package/locales/bg-BG/models.json +18 -21
  66. package/locales/bg-BG/providers.json +0 -1
  67. package/locales/bg-BG/setting.json +16 -0
  68. package/locales/de-DE/authError.json +1 -0
  69. package/locales/de-DE/models.json +20 -20
  70. package/locales/de-DE/providers.json +0 -1
  71. package/locales/de-DE/setting.json +16 -0
  72. package/locales/en-US/auth.json +1 -0
  73. package/locales/en-US/models.json +22 -22
  74. package/locales/en-US/providers.json +0 -1
  75. package/locales/es-ES/authError.json +1 -0
  76. package/locales/es-ES/models.json +84 -20
  77. package/locales/es-ES/providers.json +0 -1
  78. package/locales/es-ES/setting.json +16 -0
  79. package/locales/fa-IR/authError.json +1 -0
  80. package/locales/fa-IR/models.json +43 -20
  81. package/locales/fa-IR/providers.json +0 -1
  82. package/locales/fa-IR/setting.json +16 -0
  83. package/locales/fr-FR/authError.json +1 -0
  84. package/locales/fr-FR/models.json +19 -21
  85. package/locales/fr-FR/providers.json +0 -1
  86. package/locales/fr-FR/setting.json +16 -0
  87. package/locales/it-IT/authError.json +1 -0
  88. package/locales/it-IT/models.json +17 -19
  89. package/locales/it-IT/providers.json +0 -1
  90. package/locales/it-IT/setting.json +16 -0
  91. package/locales/ja-JP/authError.json +1 -0
  92. package/locales/ja-JP/models.json +43 -22
  93. package/locales/ja-JP/providers.json +0 -1
  94. package/locales/ja-JP/setting.json +16 -0
  95. package/locales/ko-KR/authError.json +1 -0
  96. package/locales/ko-KR/models.json +41 -20
  97. package/locales/ko-KR/providers.json +0 -1
  98. package/locales/ko-KR/setting.json +16 -0
  99. package/locales/nl-NL/authError.json +1 -0
  100. package/locales/nl-NL/models.json +48 -20
  101. package/locales/nl-NL/providers.json +0 -1
  102. package/locales/nl-NL/setting.json +16 -0
  103. package/locales/pl-PL/authError.json +1 -0
  104. package/locales/pl-PL/models.json +19 -22
  105. package/locales/pl-PL/providers.json +0 -1
  106. package/locales/pl-PL/setting.json +16 -0
  107. package/locales/pt-BR/authError.json +1 -0
  108. package/locales/pt-BR/models.json +21 -21
  109. package/locales/pt-BR/providers.json +0 -1
  110. package/locales/pt-BR/setting.json +16 -0
  111. package/locales/ru-RU/authError.json +1 -0
  112. package/locales/ru-RU/models.json +23 -20
  113. package/locales/ru-RU/providers.json +0 -1
  114. package/locales/ru-RU/setting.json +16 -0
  115. package/locales/tr-TR/authError.json +1 -0
  116. package/locales/tr-TR/models.json +37 -20
  117. package/locales/tr-TR/providers.json +0 -1
  118. package/locales/tr-TR/setting.json +16 -0
  119. package/locales/vi-VN/authError.json +1 -0
  120. package/locales/vi-VN/models.json +15 -19
  121. package/locales/vi-VN/providers.json +0 -1
  122. package/locales/vi-VN/setting.json +16 -0
  123. package/locales/zh-CN/auth.json +1 -0
  124. package/locales/zh-CN/models.json +20 -20
  125. package/locales/zh-CN/providers.json +0 -1
  126. package/locales/zh-TW/authError.json +1 -0
  127. package/locales/zh-TW/models.json +20 -20
  128. package/locales/zh-TW/providers.json +0 -1
  129. package/locales/zh-TW/setting.json +16 -0
  130. package/netlify.toml +0 -1
  131. package/package.json +1 -1
  132. package/packages/model-bank/src/aiModels/google.ts +0 -19
  133. package/packages/model-bank/src/aiModels/moonshot.ts +56 -5
  134. package/packages/model-bank/src/aiModels/ollamacloud.ts +14 -0
  135. package/packages/model-bank/src/aiModels/openrouter.ts +0 -14
  136. package/packages/model-bank/src/aiModels/qwen.ts +105 -4
  137. package/packages/model-bank/src/aiModels/siliconcloud.ts +39 -0
  138. package/packages/model-bank/src/aiModels/wenxin.ts +0 -99
  139. package/packages/model-runtime/src/core/contextBuilders/openai.test.ts +24 -0
  140. package/packages/model-runtime/src/core/contextBuilders/openai.ts +22 -5
  141. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +10 -3
  142. package/packages/model-runtime/src/core/streams/google/google-ai.test.ts +54 -13
  143. package/packages/model-runtime/src/core/streams/google/index.ts +1 -4
  144. package/packages/model-runtime/src/providers/moonshot/index.ts +24 -2
  145. package/packages/model-runtime/src/providers/qwen/index.ts +16 -15
  146. package/packages/types/src/serverConfig.ts +1 -0
  147. package/src/app/[variants]/(auth)/signin/SignInEmailStep.tsx +56 -49
  148. package/src/app/[variants]/(auth)/signin/page.tsx +2 -0
  149. package/src/app/[variants]/(auth)/signin/useSignIn.ts +2 -0
  150. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +7 -0
  151. package/src/envs/app.ts +0 -2
  152. package/src/envs/auth.ts +3 -0
  153. package/src/libs/better-auth/define-config.ts +1 -1
  154. package/src/libs/next/proxy/define-config.ts +0 -1
  155. package/src/locales/default/auth.ts +2 -0
  156. package/src/server/globalConfig/index.ts +1 -0
  157. package/src/server/routers/lambda/__tests__/integration/aiAgent/execAgent.integration.test.ts +3 -2
  158. package/src/store/chat/slices/topic/action.ts +1 -1
  159. package/src/store/electron/actions/settings.ts +7 -7
  160. package/src/store/electron/actions/sync.ts +11 -11
  161. package/src/store/global/actions/general.ts +12 -12
  162. package/src/store/global/initialState.ts +11 -11
  163. package/src/store/global/selectors/clientDB.ts +1 -1
  164. package/src/store/global/selectors/systemStatus.ts +1 -1
  165. package/src/store/image/slices/generationConfig/action.ts +12 -12
  166. package/src/store/image/utils/size.ts +11 -11
  167. package/src/store/library/slices/ragEval/actions/dataset.ts +1 -1
  168. package/src/store/serverConfig/selectors.ts +1 -0
  169. package/src/store/session/slices/session/initialState.ts +6 -6
  170. package/src/store/session/slices/session/reducers.ts +1 -1
  171. package/src/store/session/slices/sessionGroup/initialState.ts +2 -2
  172. package/src/store/tool/slices/customPlugin/action.ts +2 -2
  173. package/src/store/tool/slices/oldStore/action.ts +5 -5
  174. package/src/store/userMemory/slices/preference/action.ts +6 -6
@@ -1,3 +1,6 @@
1
+ import { redirect } from 'next/navigation';
2
+
3
+ import { authEnv } from '@/envs/auth';
1
4
  import { metadataModule } from '@/server/metadata';
2
5
  import { translation } from '@/server/translation';
3
6
  import { type DynamicLayoutProps } from '@/types/next';
@@ -17,6 +20,10 @@ export const generateMetadata = async (props: DynamicLayoutProps) => {
17
20
  };
18
21
 
19
22
  const Page = () => {
23
+ if (authEnv.AUTH_DISABLE_EMAIL_PASSWORD) {
24
+ redirect('/signin');
25
+ }
26
+
20
27
  return <BetterAuthSignUpForm />;
21
28
  };
22
29
 
package/src/envs/app.ts CHANGED
@@ -52,7 +52,6 @@ export const getAppConfig = () => {
52
52
  INTERNAL_APP_URL: z.string().optional(),
53
53
  VERCEL_EDGE_CONFIG: z.string().optional(),
54
54
  MIDDLEWARE_REWRITE_THROUGH_LOCAL: z.boolean().optional(),
55
- ENABLE_AUTH_PROTECTION: z.boolean().optional(),
56
55
 
57
56
  CDN_USE_GLOBAL: z.boolean().optional(),
58
57
  CUSTOM_FONT_FAMILY: z.string().optional(),
@@ -107,7 +106,6 @@ export const getAppConfig = () => {
107
106
  APP_URL,
108
107
  INTERNAL_APP_URL,
109
108
  MIDDLEWARE_REWRITE_THROUGH_LOCAL: process.env.MIDDLEWARE_REWRITE_THROUGH_LOCAL === '1',
110
- ENABLE_AUTH_PROTECTION: process.env.ENABLE_AUTH_PROTECTION === '1',
111
109
 
112
110
  CUSTOM_FONT_FAMILY: process.env.CUSTOM_FONT_FAMILY,
113
111
  CUSTOM_FONT_URL: process.env.CUSTOM_FONT_URL,
package/src/envs/auth.ts CHANGED
@@ -13,6 +13,7 @@ declare global {
13
13
  AUTH_SSO_PROVIDERS?: string;
14
14
  AUTH_TRUSTED_ORIGINS?: string;
15
15
  AUTH_ALLOWED_EMAILS?: string;
16
+ AUTH_DISABLE_EMAIL_PASSWORD?: string;
16
17
 
17
18
  // ===== Auth Provider Credentials ===== //
18
19
  AUTH_GOOGLE_ID?: string;
@@ -112,6 +113,7 @@ export const getAuthConfig = () => {
112
113
  AUTH_EMAIL_VERIFICATION: z.boolean().optional().default(false),
113
114
  AUTH_ENABLE_MAGIC_LINK: z.boolean().optional().default(false),
114
115
  AUTH_ALLOWED_EMAILS: z.string().optional(),
116
+ AUTH_DISABLE_EMAIL_PASSWORD: z.boolean().optional().default(false),
115
117
 
116
118
  AUTH_GOOGLE_ID: z.string().optional(),
117
119
  AUTH_GOOGLE_SECRET: z.string().optional(),
@@ -199,6 +201,7 @@ export const getAuthConfig = () => {
199
201
  AUTH_SSO_PROVIDERS: process.env.AUTH_SSO_PROVIDERS,
200
202
  AUTH_TRUSTED_ORIGINS: process.env.AUTH_TRUSTED_ORIGINS,
201
203
  AUTH_ALLOWED_EMAILS: process.env.AUTH_ALLOWED_EMAILS,
204
+ AUTH_DISABLE_EMAIL_PASSWORD: process.env.AUTH_DISABLE_EMAIL_PASSWORD === '1',
202
205
 
203
206
  // Cognito provider specific env vars
204
207
  AUTH_COGNITO_DOMAIN: process.env.AUTH_COGNITO_DOMAIN,
@@ -107,7 +107,7 @@ export function defineConfig(customOptions: CustomBetterAuthOptions) {
107
107
 
108
108
  emailAndPassword: {
109
109
  autoSignIn: true,
110
- enabled: true,
110
+ enabled: !authEnv.AUTH_DISABLE_EMAIL_PASSWORD,
111
111
  maxPasswordLength: 64,
112
112
  minPasswordLength: 8,
113
113
  requireEmailVerification: authEnv.AUTH_EMAIL_VERIFICATION,
@@ -228,7 +228,6 @@ export function defineConfig() {
228
228
  };
229
229
 
230
230
  logDefault('Middleware configuration: %O', {
231
- enableAuthProtection: appEnv.ENABLE_AUTH_PROTECTION,
232
231
  enableOIDC: authEnv.ENABLE_OIDC,
233
232
  });
234
233
 
@@ -102,6 +102,8 @@ export default {
102
102
  'betterAuth.signin.socialError': 'Social sign in failed, please try again',
103
103
  'betterAuth.signin.socialOnlyHint':
104
104
  'This email was registered via a third-party social account. Sign in with that provider, or',
105
+ 'betterAuth.signin.ssoOnlyNoProviders':
106
+ 'Email registration is disabled and no SSO providers are configured. Please contact your administrator.',
105
107
  'betterAuth.signin.submit': 'Sign In',
106
108
  'betterAuth.signup.confirmPasswordPlaceholder': 'Confirm your password',
107
109
  'betterAuth.signup.emailPlaceholder': 'Enter your email address',
@@ -74,6 +74,7 @@ export const getServerGlobalConfig = async () => {
74
74
  defaultAgent: {
75
75
  config: parseAgentConfig(DEFAULT_AGENT_CONFIG),
76
76
  },
77
+ disableEmailPassword: authEnv.AUTH_DISABLE_EMAIL_PASSWORD,
77
78
  enableBusinessFeatures: ENABLE_BUSINESS_FEATURES,
78
79
  enableEmailVerification: authEnv.AUTH_EMAIL_VERIFICATION,
79
80
  enableKlavis: !!klavisEnv.KLAVIS_API_KEY,
@@ -232,14 +232,15 @@ describe('execAgent', () => {
232
232
  })
233
233
  .returning();
234
234
 
235
- const [thread] = await serverDB
235
+ const threadResult = (await serverDB
236
236
  .insert(threads)
237
237
  .values({
238
238
  topicId: topic.id,
239
239
  type: 'standalone',
240
240
  userId,
241
241
  })
242
- .returning();
242
+ .returning()) as { id: string }[];
243
+ const thread = threadResult[0];
243
244
 
244
245
  const caller = aiAgentRouter.createCaller(createTestContext());
245
246
 
@@ -616,7 +616,7 @@ export const chatTopic: StateCreator<
616
616
  await topicService.batchRemoveTopics(topics.map((t) => t.id));
617
617
  await refreshTopic();
618
618
 
619
- // 切换到默认 topic
619
+ // Switch to default topic
620
620
  switchTopic(null);
621
621
  },
622
622
 
@@ -9,7 +9,7 @@ import { desktopSettingsService } from '@/services/electron/settings';
9
9
  import type { ElectronStore } from '../store';
10
10
 
11
11
  /**
12
- * 设置操作
12
+ * Settings actions
13
13
  */
14
14
  export interface ElectronSettingsAction {
15
15
  refreshDesktopHotkeys: () => Promise<void>;
@@ -39,29 +39,29 @@ export const settingsSlice: StateCreator<
39
39
 
40
40
  setProxySettings: async (values) => {
41
41
  try {
42
- // 更新设置
42
+ // Update settings
43
43
  await desktopSettingsService.setSettings(values);
44
44
 
45
- // 刷新状态
45
+ // Refresh state
46
46
  await get().refreshProxySettings();
47
47
  } catch (error) {
48
- console.error('代理设置更新失败:', error);
48
+ console.error('Proxy settings update failed:', error);
49
49
  }
50
50
  },
51
51
 
52
52
  updateDesktopHotkey: async (id, accelerator) => {
53
53
  try {
54
- // 更新热键配置
54
+ // Update hotkey configuration
55
55
  const result = await desktopSettingsService.updateDesktopHotkey(id, accelerator);
56
56
 
57
- // 如果更新成功,刷新状态
57
+ // If update successful, refresh state
58
58
  if (result.success) {
59
59
  await get().refreshDesktopHotkeys();
60
60
  }
61
61
 
62
62
  return result;
63
63
  } catch (error) {
64
- console.error('桌面热键更新失败:', error);
64
+ console.error('Desktop hotkey update failed:', error);
65
65
  return {
66
66
  errorType: 'UNKNOWN' as const,
67
67
  success: false,
@@ -10,7 +10,7 @@ import { initialState } from '../initialState';
10
10
  import type { ElectronStore } from '../store';
11
11
 
12
12
  /**
13
- * 设置操作
13
+ * Remote server actions
14
14
  */
15
15
  export interface ElectronRemoteServerAction {
16
16
  clearRemoteServerSyncError: () => void;
@@ -39,28 +39,28 @@ export const remoteSyncSlice: StateCreator<
39
39
  set({ isConnectingServer: true });
40
40
  get().clearRemoteServerSyncError();
41
41
  try {
42
- // 获取当前配置
42
+ // Get current configuration
43
43
  const config = await remoteServerService.getRemoteServerConfig();
44
44
 
45
- // 如果已经激活,需要先清除
45
+ // If already active, need to clear first
46
46
  if (!isEqual(config, values)) {
47
47
  await remoteServerService.setRemoteServerConfig({ ...values, active: false });
48
48
  }
49
49
 
50
- // 请求授权
50
+ // Request authorization
51
51
  const result = await remoteServerService.requestAuthorization(values);
52
52
 
53
53
  if (!result.success) {
54
- console.error('请求授权失败:', result.error);
54
+ console.error('Authorization request failed:', result.error);
55
55
 
56
56
  set({
57
57
  remoteServerSyncError: { message: result.error, type: 'AUTH_ERROR' },
58
58
  });
59
59
  }
60
- // 刷新状态
60
+ // Refresh state
61
61
  await get().refreshServerConfig();
62
62
  } catch (error) {
63
- console.error('远程服务器配置出错:', error);
63
+ console.error('Remote server configuration error:', error);
64
64
  set({
65
65
  remoteServerSyncError: { message: (error as Error).message, type: 'CONFIG_ERROR' },
66
66
  });
@@ -74,12 +74,12 @@ export const remoteSyncSlice: StateCreator<
74
74
  get().clearRemoteServerSyncError();
75
75
  try {
76
76
  await remoteServerService.setRemoteServerConfig({ active: false, storageMode: 'cloud' });
77
- // 更新表单URL为空
77
+ // Update form URL to empty
78
78
  set({ dataSyncConfig: initialState.dataSyncConfig });
79
- // 刷新状态
79
+ // Refresh state
80
80
  await get().refreshServerConfig();
81
81
  } catch (error) {
82
- console.error('断开连接失败:', error);
82
+ console.error('Disconnect failed:', error);
83
83
  set({
84
84
  remoteServerSyncError: { message: (error as Error).message, type: 'DISCONNECT_ERROR' },
85
85
  });
@@ -110,7 +110,7 @@ export const remoteSyncSlice: StateCreator<
110
110
  try {
111
111
  return await remoteServerService.getRemoteServerConfig();
112
112
  } catch (error) {
113
- console.error('获取远程服务器配置失败:', error);
113
+ console.error('Failed to get remote server configuration:', error);
114
114
  throw error;
115
115
  }
116
116
  },
@@ -188,18 +188,18 @@ export const generalActionSlice: StateCreator<
188
188
  if (!clientVersion || !serverVersion) return;
189
189
 
190
190
  const DIFF_THRESHOLD = 5;
191
- // 版本差异计算规则
192
- // ┌─────────────────┬────────┬─────────┐
193
- // │ 客户端服务端 差异值 结果
194
- // ├─────────────────┼────────┼─────────┤
195
- // │ 1.0.5 → 1.0.0 │ 5 │ ⚠️ 过旧
196
- // ├─────────────────┼────────┼─────────┤
197
- // │ 1.1.0 → 1.0.5 │ 5 │ ⚠️ 过旧
198
- // ├─────────────────┼────────┼─────────┤
199
- // │ 2.0.0 → 1.9.9 │ 91 │ ⚠️ 过旧
200
- // ├─────────────────┼────────┼─────────┤
201
- // │ 1.0.4 → 1.0.0 │ 4 │ ✅ 正常
202
- // └─────────────────┴────────┴─────────┘
191
+ // Version difference calculation rules
192
+ // ┌─────────────────┬────────┬───────────┐
193
+ // │ ClientServer Diff Result
194
+ // ├─────────────────┼────────┼───────────┤
195
+ // │ 1.0.5 → 1.0.0 │ 5 │ ⚠️ Too old
196
+ // ├─────────────────┼────────┼───────────┤
197
+ // │ 1.1.0 → 1.0.5 │ 5 │ ⚠️ Too old
198
+ // ├─────────────────┼────────┼───────────┤
199
+ // │ 2.0.0 → 1.9.9 │ 91 │ ⚠️ Too old
200
+ // ├─────────────────┼────────┼───────────┤
201
+ // │ 1.0.4 → 1.0.0 │ 4 │ ✅ Normal
202
+ // └─────────────────┴────────┴───────────┘
203
203
  const versionDiff =
204
204
  (clientVersion.major - serverVersion.major) * 100 +
205
205
  (clientVersion.minor - serverVersion.minor) * 10 +
@@ -105,18 +105,18 @@ export interface SystemStatus {
105
105
  imagePanelWidth: number;
106
106
  imageTopicPanelWidth?: number;
107
107
  /**
108
- * 应用初始化时不启用 PGLite,只有当用户手动开启时才启用
108
+ * Do not enable PGLite on app initialization, only enable when user manually turns it on
109
109
  */
110
110
  isEnablePglite?: boolean;
111
111
  isShowCredit?: boolean;
112
112
  knowledgeBaseModalViewMode?: 'list' | 'masonry';
113
113
  language?: LocaleMode;
114
114
  /**
115
- * 记住用户最后选择的图像生成模型
115
+ * Remember user's last selected image generation model
116
116
  */
117
117
  lastSelectedImageModel?: string;
118
118
  /**
119
- * 记住用户最后选择的图像生成提供商
119
+ * Remember user's last selected image generation provider
120
120
  */
121
121
  lastSelectedImageProvider?: string;
122
122
  latestChangelogId?: string;
@@ -124,11 +124,11 @@ export interface SystemStatus {
124
124
  mobileShowPortal?: boolean;
125
125
  mobileShowTopic?: boolean;
126
126
  /**
127
- * ModelSwitchPanel 的分组模式
127
+ * ModelSwitchPanel grouping mode
128
128
  */
129
129
  modelSwitchPanelGroupMode?: 'byModel' | 'byProvider';
130
130
  /**
131
- * ModelSwitchPanel 的宽度
131
+ * ModelSwitchPanel width
132
132
  */
133
133
  modelSwitchPanelWidth?: number;
134
134
  noWideScreen?: boolean;
@@ -157,7 +157,7 @@ export interface SystemStatus {
157
157
  showSystemRole?: boolean;
158
158
  systemRoleExpandedMap: Record<string, boolean>;
159
159
  /**
160
- * 是否使用短格式显示 token
160
+ * Whether to display tokens in short format
161
161
  */
162
162
  tokenDisplayFormatShort?: boolean;
163
163
  /**
@@ -177,21 +177,21 @@ export interface GlobalState {
177
177
 
178
178
  initClientDBProcess?: { costTime?: number; phase: 'wasm' | 'dependencies'; progress: number };
179
179
  /**
180
- * 客户端数据库初始化状态
181
- * 启动时为 Idle,完成为 Ready,报错为 Error
180
+ * Client database initialization state
181
+ * Idle on startup, Ready when complete, Error on failure
182
182
  */
183
183
  initClientDBStage: DatabaseLoadingState;
184
184
  isMobile?: boolean;
185
185
  /**
186
- * 服务端版本过旧,不支持 /api/version 接口
187
- * 需要提示用户更新服务端
186
+ * Server version is too old, does not support /api/version endpoint
187
+ * Need to prompt user to update server
188
188
  */
189
189
  isServerVersionOutdated?: boolean;
190
190
  isStatusInit?: boolean;
191
191
  latestVersion?: string;
192
192
  navigate?: NavigateFunction;
193
193
  /**
194
- * 服务端版本号,用于检测客户端与服务端版本是否一致
194
+ * Server version number, used to detect client-server version consistency
195
195
  */
196
196
  serverVersion?: string;
197
197
  sidebarKey: SidebarTabKey;
@@ -24,7 +24,7 @@ const displayMigrationStatus = (s: GlobalState) => {
24
24
  status: !!recordInTable ? 'success' : 'error',
25
25
  };
26
26
  })
27
- // 时间倒序
27
+ // Sort by time in descending order
28
28
  .sort((a, b) => b.folderMillis - a.folderMillis)
29
29
  );
30
30
  };
@@ -51,7 +51,7 @@ const getAgentSystemRoleExpanded =
51
51
  (agentId: string) =>
52
52
  (s: GlobalState): boolean => {
53
53
  const map = s.status.systemRoleExpandedMap || {};
54
- return map[agentId] === true; // 角色设定默认为折叠状态
54
+ return map[agentId] === true; // System role is collapsed by default
55
55
  };
56
56
 
57
57
  const disabledModelProvidersSortType = (s: GlobalState) =>
@@ -40,7 +40,7 @@ export interface GenerationConfigAction {
40
40
  toggleAspectRatioLock(): void;
41
41
  setAspectRatio(aspectRatio: string): void;
42
42
 
43
- // 初始化相关方法
43
+ // Initialization related methods
44
44
  _initializeDefaultImageConfig(): void;
45
45
  initializeImageConfig(
46
46
  isLogin?: boolean,
@@ -185,12 +185,12 @@ export const createGenerationConfigSlice: StateCreator<
185
185
  } = state;
186
186
  const newLockState = !isAspectRatioLocked;
187
187
 
188
- // 如果是从解锁变为锁定,且有活动的宽高比,则立即调整尺寸
188
+ // If transitioning from unlocked to locked and there's an active aspect ratio, adjust dimensions immediately
189
189
  if (newLockState && activeAspectRatio && parameters && parametersSchema) {
190
190
  const currentWidth = parameters.width;
191
191
  const currentHeight = parameters.height;
192
192
 
193
- // 只有当widthheight都存在时才进行调整
193
+ // Only adjust when both width and height exist
194
194
  if (
195
195
  typeof currentWidth === 'number' &&
196
196
  typeof currentHeight === 'number' &&
@@ -200,9 +200,9 @@ export const createGenerationConfigSlice: StateCreator<
200
200
  const targetRatio = parseRatio(activeAspectRatio);
201
201
  const currentRatio = currentWidth / currentHeight;
202
202
 
203
- // 如果当前比例与目标比例不匹配,则需要调整
203
+ // If current ratio doesn't match target ratio, adjustment is needed
204
204
  if (Math.abs(currentRatio - targetRatio) > 0.01) {
205
- // 允许小误差
205
+ // Allow small margin of error
206
206
  const widthSchema = parametersSchema.width;
207
207
  const heightSchema = parametersSchema.height;
208
208
 
@@ -214,19 +214,19 @@ export const createGenerationConfigSlice: StateCreator<
214
214
  typeof heightSchema.max === 'number' &&
215
215
  typeof heightSchema.min === 'number'
216
216
  ) {
217
- // 优先保持宽度,调整高度
217
+ // Prioritize keeping width, adjust height
218
218
  let newWidth = currentWidth;
219
219
  let newHeight = Math.round(currentWidth / targetRatio);
220
220
 
221
- // 如果计算出的高度超出范围,则改为保持高度,调整宽度
221
+ // If calculated height is out of range, switch to keeping height and adjust width
222
222
  if (newHeight > heightSchema.max || newHeight < heightSchema.min) {
223
223
  newHeight = currentHeight;
224
224
  newWidth = Math.round(currentHeight * targetRatio);
225
225
 
226
- // 确保宽度也在范围内
226
+ // Ensure width is also within range
227
227
  newWidth = Math.max(Math.min(newWidth, widthSchema.max), widthSchema.min);
228
228
  } else {
229
- // 确保高度在范围内
229
+ // Ensure height is within range
230
230
  newHeight = Math.max(Math.min(newHeight, heightSchema.max), heightSchema.min);
231
231
  }
232
232
 
@@ -253,7 +253,7 @@ export const createGenerationConfigSlice: StateCreator<
253
253
  const defaultValues = extractDefaultValues(parametersSchema);
254
254
  const newParams = { ...parameters };
255
255
 
256
- // 如果模型支持 width/height,则计算新尺寸
256
+ // If model supports width/height, calculate new dimensions
257
257
  if (
258
258
  parametersSchema?.width &&
259
259
  parametersSchema?.height &&
@@ -266,7 +266,7 @@ export const createGenerationConfigSlice: StateCreator<
266
266
  newParams.height = height;
267
267
  }
268
268
 
269
- // 如果模型本身支持 aspectRatio,则更新它
269
+ // If model itself supports aspectRatio, update it
270
270
  if (parametersSchema?.aspectRatio) {
271
271
  newParams.aspectRatio = aspectRatio;
272
272
  }
@@ -297,7 +297,7 @@ export const createGenerationConfigSlice: StateCreator<
297
297
  `setModelAndProviderOnSelect/${model}/${provider}`,
298
298
  );
299
299
 
300
- // 仅在登录用户下记忆上次选择,保持与恢复策略一致
300
+ // Only remember last selection for logged-in users, consistent with recovery strategy
301
301
  const isLogin = authSelectors.isLogin(useUserStore.getState());
302
302
  if (isLogin) {
303
303
  useGlobalStore.getState().updateSystemStatus({
@@ -1,7 +1,7 @@
1
1
  /**
2
- * 解析比例字符串,例如 "16:9" -> 1.777
3
- * @param ratio - 格式为 "width:height" 的比例字符串
4
- * @returns 比例数值,出现错误时返回 1:1 比例
2
+ * Parse ratio string, e.g., "16:9" -> 1.777
3
+ * @param ratio - Ratio string in "width:height" format
4
+ * @returns Ratio value, returns 1:1 ratio on error
5
5
  */
6
6
  export function parseRatio(ratio: string): number {
7
7
  if (!ratio || typeof ratio !== 'string') return 1;
@@ -21,14 +21,14 @@ export function parseRatio(ratio: string): number {
21
21
  }
22
22
 
23
23
  /**
24
- * 根据目标比例和默认尺寸,计算最合适的尺寸
25
- * @param ratio - 目标宽高比例
26
- * @param defaultWidth - 默认宽度
27
- * @param defaultHeight - 默认高度
28
- * @returns 计算出的尺寸对象
24
+ * Calculate the most appropriate size based on target ratio and default dimensions
25
+ * @param ratio - Target aspect ratio
26
+ * @param defaultWidth - Default width
27
+ * @param defaultHeight - Default height
28
+ * @returns Calculated size object
29
29
  */
30
30
  export function adaptSizeToRatio(ratio: number, defaultWidth: number, defaultHeight: number) {
31
- // 验证输入参数
31
+ // Validate input parameters
32
32
  if (!Number.isFinite(ratio) || ratio <= 0) {
33
33
  throw new Error('Invalid ratio: must be a positive finite number');
34
34
  }
@@ -42,10 +42,10 @@ export function adaptSizeToRatio(ratio: number, defaultWidth: number, defaultHei
42
42
  const currentRatio = defaultWidth / defaultHeight;
43
43
 
44
44
  if (ratio > currentRatio) {
45
- // 目标比例更宽,保持宽度,调整高度
45
+ // Target ratio is wider, keep width and adjust height
46
46
  return { width: defaultWidth, height: Math.round(defaultWidth / ratio) };
47
47
  } else {
48
- // 目标比例更高,保持高度,调整宽度
48
+ // Target ratio is taller, keep height and adjust width
49
49
  return { width: Math.round(defaultHeight * ratio), height: defaultHeight };
50
50
  }
51
51
  }
@@ -42,7 +42,7 @@ export const createRagEvalDatasetSlice: StateCreator<
42
42
  const fileType = file.name.split('.').pop();
43
43
 
44
44
  if (fileType === 'jsonl') {
45
- // jsonl 文件 需要拆分成单个条,然后逐一校验格式
45
+ // jsonl file needs to be split into individual entries, then validated one by one
46
46
  const jsonl = await file.text();
47
47
  const { default: JSONL } = await import('jsonl-parse-stringify');
48
48
 
@@ -3,6 +3,7 @@ import { type ServerConfigStore } from './store';
3
3
  export const featureFlagsSelectors = (s: ServerConfigStore) => s.featureFlags;
4
4
 
5
5
  export const serverConfigSelectors = {
6
+ disableEmailPassword: (s: ServerConfigStore) => s.serverConfig.disableEmailPassword || false,
6
7
  enableBusinessFeatures: (s: ServerConfigStore) => s.serverConfig.enableBusinessFeatures || false,
7
8
  enableEmailVerification: (s: ServerConfigStore) =>
8
9
  s.serverConfig.enableEmailVerification || false,
@@ -3,8 +3,8 @@ import { type LobeSessions } from '@/types/session';
3
3
  export interface SessionState {
4
4
  activeAgentId?: string;
5
5
  /**
6
- * @title 当前活动的会话
7
- * @description 当前正在编辑或查看的会话
6
+ * @title Current active session
7
+ * @description The session currently being edited or viewed
8
8
  */
9
9
  activeId: string;
10
10
  /**
@@ -22,14 +22,14 @@ export interface SessionState {
22
22
  pinnedSessions: LobeSessions;
23
23
  searchKeywords: string;
24
24
  /**
25
- * @title 正在重命名的会话 ID
26
- * @description 用于控制会话重命名弹窗的显示状态
25
+ * @title Session ID being renamed
26
+ * @description Used to control the display state of the session rename modal
27
27
  */
28
28
  sessionRenamingId: string | null;
29
29
  sessionSearchKeywords?: string;
30
30
  /**
31
- * @title 正在更新的会话 ID
32
- * @description 用于显示会话更新时的加载状态
31
+ * @title Session ID being updated
32
+ * @description Used to display loading state when session is being updated
33
33
  */
34
34
  sessionUpdatingId: string | null;
35
35
  /**
@@ -27,7 +27,7 @@ export const sessionsReducer = (state: LobeSessions, payload: SessionDispatch):
27
27
  const { session } = payload;
28
28
  if (!session) return;
29
29
 
30
- // TODO: 后续将 Date 类型做个迁移,就可以移除这里的 ignore
30
+ // TODO: Migrate Date type in the future to remove this ignore
31
31
  // @ts-ignore
32
32
  draft.unshift({ ...session, createdAt: new Date(), updatedAt: new Date() });
33
33
  });
@@ -4,8 +4,8 @@ export interface SessionGroupState {
4
4
  customSessionGroups: CustomSessionGroup[];
5
5
  sessionGroupRenamingId: string | null;
6
6
  /**
7
- * @title 正在更新的分组 ID
8
- * @description 用于显示分组更新时的加载状态
7
+ * @title Group ID being updated
8
+ * @description Used to display loading state when group is being updated
9
9
  */
10
10
  sessionGroupUpdatingId: string | null;
11
11
  sessionGroups: LobeSessionGroups;
@@ -93,10 +93,10 @@ export const createCustomPluginSlice: StateCreator<
93
93
 
94
94
  updateCustomPlugin: async (id, value) => {
95
95
  const { reinstallCustomPlugin } = get();
96
- // 1. 更新 list 项信息
96
+ // 1. Update list item information
97
97
  await pluginService.updatePlugin(id, value);
98
98
 
99
- // 2. 重新安装插件
99
+ // 2. Reinstall plugin
100
100
  await reinstallCustomPlugin(id);
101
101
  },
102
102
  updateNewCustomPlugin: (newCustomPlugin) => {
@@ -148,7 +148,7 @@ export const createPluginStoreSlice: StateCreator<
148
148
  loadMorePlugins: () => {
149
149
  const { oldPluginItems, pluginTotalCount, currentPluginPage } = get();
150
150
 
151
- // 检查是否还有更多数据可以加载
151
+ // Check if there is more data to load
152
152
  if (oldPluginItems.length < (pluginTotalCount || 0)) {
153
153
  set(
154
154
  produce((draft: PluginStoreState) => {
@@ -234,19 +234,19 @@ export const createPluginStoreSlice: StateCreator<
234
234
  produce((draft: PluginStoreState) => {
235
235
  draft.pluginSearchLoading = false;
236
236
 
237
- // 设置基础信息
237
+ // Set basic information
238
238
  if (!draft.isPluginListInit) {
239
239
  draft.activePluginIdentifier = data.items?.[0]?.identifier;
240
240
  draft.isPluginListInit = true;
241
241
  draft.pluginTotalCount = data.totalCount;
242
242
  }
243
243
 
244
- // 累积数据逻辑
244
+ // Accumulate data logic
245
245
  if (params.page === 1) {
246
- // 第一页,直接设置
246
+ // First page, set directly
247
247
  draft.oldPluginItems = uniqBy(data.items, 'identifier');
248
248
  } else {
249
- // 后续页面,累积数据
249
+ // Subsequent pages, accumulate data
250
250
  draft.oldPluginItems = uniqBy(
251
251
  [...draft.oldPluginItems, ...data.items],
252
252
  'identifier',