@lobehub/lobehub 2.0.5 → 2.0.6

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 (29) hide show
  1. package/.eslintrc.js +1 -0
  2. package/CHANGELOG.md +25 -0
  3. package/changelog/v2.json +9 -0
  4. package/docs/self-hosting/advanced/auth/clerk-to-betterauth.mdx +2 -0
  5. package/docs/self-hosting/advanced/auth/clerk-to-betterauth.zh-CN.mdx +2 -0
  6. package/docs/self-hosting/advanced/auth/nextauth-to-betterauth.mdx +2 -0
  7. package/docs/self-hosting/advanced/auth/nextauth-to-betterauth.zh-CN.mdx +2 -0
  8. package/docs/self-hosting/platform/docker.mdx +6 -0
  9. package/docs/self-hosting/platform/docker.zh-CN.mdx +6 -0
  10. package/docs/self-hosting/platform/vercel.mdx +0 -49
  11. package/docs/self-hosting/platform/vercel.zh-CN.mdx +0 -47
  12. package/docs/self-hosting/start.mdx +0 -20
  13. package/docs/self-hosting/start.zh-CN.mdx +0 -18
  14. package/package.json +1 -1
  15. package/src/app/(backend)/api/workflows/memory-user-memory/pipelines/persona/update-writing/route.ts +19 -19
  16. package/src/app/[variants]/(main)/agent/features/Conversation/AgentWelcome/ToolAuthAlert.tsx +3 -2
  17. package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +3 -3
  18. package/src/app/[variants]/onboarding/components/KlavisServerList/hooks/useKlavisOAuth.ts +12 -5
  19. package/src/components/DebugNode.tsx +21 -0
  20. package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +3 -3
  21. package/src/features/ChatInput/ActionBar/Tools/ToolItem.tsx +16 -13
  22. package/src/features/ChatInput/ActionBar/Tools/ToolsList.tsx +51 -40
  23. package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +7 -1
  24. package/src/features/ChatInput/ActionBar/components/ActionPopover.tsx +14 -11
  25. package/src/server/routers/lambda/klavis.ts +38 -10
  26. package/src/server/services/memory/userMemory/extract.ts +1 -1
  27. package/src/store/tool/slices/klavisStore/action.ts +20 -0
  28. package/docs/self-hosting/server-database.mdx +0 -157
  29. package/docs/self-hosting/server-database.zh-CN.mdx +0 -146
package/.eslintrc.js CHANGED
@@ -20,6 +20,7 @@ config.rules['unicorn/no-array-for-each'] = 0;
20
20
  config.rules['unicorn/prefer-number-properties'] = 0;
21
21
  config.rules['unicorn/prefer-query-selector'] = 0;
22
22
  config.rules['unicorn/no-array-callback-reference'] = 0;
23
+ config.rules['@typescript-eslint/no-use-before-define'] = 0;
23
24
  // FIXME: Linting error in src/app/[variants]/(main)/chat/features/Migration/DBReader.ts, the fundamental solution should be upgrading typescript-eslint
24
25
  config.rules['@typescript-eslint/no-useless-constructor'] = 0;
25
26
  config.rules['@next/next/no-img-element'] = 0;
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 2.0.6](https://github.com/lobehub/lobe-chat/compare/v2.0.5...v2.0.6)
6
+
7
+ <sup>Released on **2026-01-27**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: The klavis in onboarding connect timeout fixed.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: The klavis in onboarding connect timeout fixed, closes [#11918](https://github.com/lobehub/lobe-chat/issues/11918) ([bc165be](https://github.com/lobehub/lobe-chat/commit/bc165be))
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 2.0.5](https://github.com/lobehub/lobe-chat/compare/v2.0.4...v2.0.5)
6
31
 
7
32
  <sup>Released on **2026-01-27**</sup>
package/changelog/v2.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "The klavis in onboarding connect timeout fixed."
6
+ ]
7
+ },
8
+ "date": "2026-01-27",
9
+ "version": "2.0.6"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "fixes": [
@@ -63,6 +63,8 @@ For small self-hosted deployments, the simplest approach is to let users reset t
63
63
 
64
64
  Remove Clerk variables and add Better Auth variables:
65
65
 
66
+ <GenerateSecret envName="AUTH_SECRET" />
67
+
66
68
  ```bash
67
69
  # Remove these
68
70
  # NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=xxx
@@ -61,6 +61,8 @@ tags:
61
61
 
62
62
  移除 Clerk 变量并添加 Better Auth 变量:
63
63
 
64
+ <GenerateSecret envName="AUTH_SECRET" />
65
+
64
66
  ```bash
65
67
  # 移除这些
66
68
  # NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=xxx
@@ -116,6 +116,8 @@ For small self-hosted deployments, the simplest approach is to let users re-logi
116
116
 
117
117
  Remove NextAuth variables and add Better Auth variables:
118
118
 
119
+ <GenerateSecret envName="AUTH_SECRET" />
120
+
119
121
  ```bash
120
122
  # Remove these
121
123
  # NEXT_AUTH_SECRET=xxx
@@ -111,6 +111,8 @@ Better Auth 支持更多功能,以下是新增的环境变量:
111
111
 
112
112
  移除 NextAuth 变量并添加 Better Auth 变量:
113
113
 
114
+ <GenerateSecret envName="AUTH_SECRET" />
115
+
114
116
  ```bash
115
117
  # 移除这些
116
118
  # NEXT_AUTH_SECRET=xxx
@@ -58,6 +58,12 @@ Here is the process for deploying the LobeHub server database version on a Linux
58
58
 
59
59
  ### Create a file named `lobe-chat.env` to store environment variables:
60
60
 
61
+ Click the buttons below to generate required secrets:
62
+
63
+ <GenerateSecret envName="KEY_VAULTS_SECRET" />
64
+
65
+ <GenerateSecret envName="AUTH_SECRET" />
66
+
61
67
  ```shell
62
68
  # Website domain
63
69
  APP_URL=https://your-prod-domain.com
@@ -54,6 +54,12 @@ tags:
54
54
 
55
55
  ### 创建名为 `lobe-chat.env` 文件用于存放环境变量:
56
56
 
57
+ 点击下方按钮生成所需密钥:
58
+
59
+ <GenerateSecret envName="KEY_VAULTS_SECRET" />
60
+
61
+ <GenerateSecret envName="AUTH_SECRET" />
62
+
57
63
  ```shell
58
64
  # 网站域名
59
65
  APP_URL=https://your-prod-domain.com
@@ -114,55 +114,6 @@ The server-side database needs to be paired with a user authentication service t
114
114
  <Callout type={'info'}>
115
115
  For advanced features like SSO providers, magic link login, and email verification, see [Authentication Service](/docs/self-hosting/advanced/auth).
116
116
  </Callout>
117
-
118
- ### Add Public and Private Key Environment Variables in Vercel
119
-
120
- In Vercel's deployment environment variables, add the `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` environment variables. You can click on "API Keys" in the menu, then copy the corresponding values and paste them into Vercel's environment variables.
121
-
122
- <Image alt={'Find the corresponding public and private key environment variables in Clerk'} src={'/blog/assets28616219/89883703-7a1a-4a11-b944-5d804544e57c.webp'} />
123
-
124
- The environment variables required for this step are as follows:
125
-
126
- ```shell
127
- NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_xxxxxxxxxxx
128
- CLERK_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxxxx
129
- ```
130
-
131
- Add the above variables to Vercel:
132
-
133
- <Image alt={'Add Clerk public and private key environment variables in Vercel'} src={'/blog/assets28616219/2bfa13df-6e20-4768-97c0-4dad06c85a2f.webp'} />
134
-
135
- ### Create and Configure Webhook in Clerk
136
-
137
- Since we let Clerk fully handle user authentication and management, we need Clerk to notify our application and store data in the database when there are changes in the user's lifecycle (create, update, delete). We achieve this requirement through the Webhook provided by Clerk.
138
-
139
- We need to add an endpoint in Clerk's Webhooks to inform Clerk to send notifications to this endpoint when a user's information changes.
140
-
141
- <Image alt={'Add Webhooks endpoint in Clerk'} src={'/blog/assets28616219/f50f47fb-5e8e-4930-bf4e-8cf6f5b8afb9.webp'} />
142
-
143
- Fill in the endpoint with the URL of your Vercel project, such as `https://your-project.vercel.app/api/webhooks/clerk`. Then, subscribe to events by checking the three user events (`user.created`, `user.deleted`, `user.updated`), and click create.
144
-
145
- <Callout type={'warning'}>
146
- The `https://` in the URL is essential to maintain the integrity of the URL.
147
- </Callout>
148
-
149
- <Image alt={'Configure URL and user events when adding Clerk Webhooks'} src={'/blog/assets28616219/0249ea56-ab17-4aa9-a56c-9ebd556c2645.webp'} />
150
-
151
- ### Add Webhook Secret to Vercel Environment Variables
152
-
153
- After creation, you can find the secret of this Webhook in the bottom right corner:
154
-
155
- <Image alt={'View Clerk Webhooks secret'} src={'/blog/assets28616219/fab4abb2-584b-49de-9340-813382951635.webp'} />
156
-
157
- The environment variable corresponding to this secret is `CLERK_WEBHOOK_SECRET`:
158
-
159
- ```shell
160
- CLERK_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxx
161
- ```
162
-
163
- Add it to Vercel's environment variables:
164
-
165
- <Image alt={'Add Clerk Webhooks secret in Vercel'} src={'/blog/assets28616219/5fdc9479-007f-46ab-9d6e-a9603e949116.webp'} />
166
117
  </Steps>
167
118
 
168
119
  By completing these steps, you have successfully configured the authentication service. Next, we will configure the S3 storage service.
@@ -114,53 +114,6 @@ tags:
114
114
  <Callout type={'info'}>
115
115
  如需 SSO 登录、魔法链接登录、邮箱验证等高级功能,请参阅 [身份验证服务](/zh/docs/self-hosting/advanced/auth)。
116
116
  </Callout>
117
-
118
- ### 在 Vercel 中添加公、私钥环境变量
119
-
120
- 在 Vercel 的部署环境变量中,添加 `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` 和 `CLERK_SECRET_KEY` 环境变量。你可以在菜单中点击「API Keys」,然后复制对应的值填入 Vercel 的环境变量中。
121
-
122
- <Image alt={'在 Clerk 中找到对应的公私钥环境变量'} src={'/blog/assets28616219/89883703-7a1a-4a11-b944-5d804544e57c.webp'} />
123
-
124
- 此步骤所需的环境变量如下:
125
-
126
- ```shell
127
- NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_xxxxxxxxxxx
128
- CLERK_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxxxx
129
- ```
130
-
131
- 添加上述变量到 Vercel 中:
132
-
133
- <Image alt={'在 Vercel 中添加 Clerk 公私钥环境变量'} src={'/blog/assets28616219/2bfa13df-6e20-4768-97c0-4dad06c85a2f.webp'} />
134
-
135
- ### 在 Clerk 中创建并配置 Webhook
136
-
137
- 由于我们让 Clerk 完全接管用户鉴权与管理,因此我们需要在 Clerk 用户生命周期变更时(创建、更新、删除)中通知我们的应用并存储落库。我们通过 Clerk 提供的 Webhook 来实现这一诉求。
138
-
139
- 我们需要在 Clerk 的 Webhooks 中添加一个端点(Endpoint),告诉 Clerk 当用户发生变更时,向这个端点发送通知。
140
-
141
- <Image alt={'Clerk 添加 Webhooks 端点'} src={'/blog/assets28616219/f50f47fb-5e8e-4930-bf4e-8cf6f5b8afb9.webp'} />
142
-
143
- 在 endpoint 中填写你的 Vercel 项目的 URL,如 `https://your-project.vercel.app/api/webhooks/clerk`。然后在订阅事件(Subscribe to events)中,勾选 user 的三个事件(`user.created` 、`user.deleted`、`user.updated`),然后点击创建。
144
-
145
- <Callout type={'warning'}>URL 的`https://`不可缺失,须保持 URL 的完整性</Callout>
146
-
147
- <Image alt={'添加 Clerk Webhooks 时,配置 URL 和用户事件'} src={'/blog/assets28616219/0249ea56-ab17-4aa9-a56c-9ebd556c2645.webp'} />
148
-
149
- ### 将 Webhook 秘钥添加到 Vercel 环境变量
150
-
151
- 创建完毕后,可以在右下角找到该 Webhook 的秘钥:
152
-
153
- <Image alt={'查看 Clerk Webhooks 秘钥'} src={'/blog/assets28616219/fab4abb2-584b-49de-9340-813382951635.webp'} />
154
-
155
- 这个秘钥所对应的环境变量名为 `CLERK_WEBHOOK_SECRET`:
156
-
157
- ```shell
158
- CLERK_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxx
159
- ```
160
-
161
- 将其添加到 Vercel 的环境变量中:
162
-
163
- <Image alt={'在 Vercel 中 添加 Clerk Webhooks 秘钥'} src={'/blog/assets28616219/5fdc9479-007f-46ab-9d6e-a9603e949116.webp'} />
164
117
  </Steps>
165
118
 
166
119
  这样,你已经成功配置了身份验证服务。接下来我们将配置 S3 存储服务。
@@ -16,24 +16,4 @@ tags:
16
16
 
17
17
  LobeHub supports various deployment platforms, including Vercel, Docker, and Docker Compose. You can choose a deployment platform that suits you to build your own Lobe Chat.
18
18
 
19
- ## Quick Deployment
20
-
21
- For users who are new to LobeHub, we recommend using the client-side database mode for quick deployment. The advantage of this mode is that deployment can be quickly completed with just one command/button, making it easy for you to quickly get started and experience LobeHub.
22
-
23
- You can follow the guide below for quick deployment of LobeHub:
24
-
25
19
  <PlatformCards urlPrefix={'platform'} />
26
-
27
- <Callout>
28
- In the client-side database mode, data is stored locally on the user's device, without
29
- cross-device synchronization, and does not support advanced features such as file uploads and
30
- knowledge base.
31
- </Callout>
32
-
33
- ## Advanced Mode: Server-Side Database
34
-
35
- For users who are already familiar with LobeHub or need cross-device synchronization, you can deploy a version with a server-side database to access a more complete and powerful LobeHub.
36
-
37
- <Cards>
38
- <Card href={'/docs/self-hosting/server-database'} title={'Server-Side Database Deployment Guide'} />
39
- </Cards>
@@ -20,22 +20,4 @@ tags:
20
20
 
21
21
  LobeHub 支持多种部署平台,包括 Vercel、Docker、 Docker Compose 、阿里云计算巢 和腾讯轻量云 等,你可以选择适合自己的部署平台进行部署,构建属于自己的 Lobe Chat。
22
22
 
23
- ## 快速部署
24
-
25
- 对于第一次了解 LobeHub 的用户,我们推荐使用客户端数据库的模式快速部署,该模式的优势是一行指令 / 一个按钮即可快捷完成部署,便于你快速上手与体验 LobeHub。
26
-
27
- 你可以通过以下指南快速部署 LobeHub:
28
-
29
23
  <PlatformCards urlPrefix={'platform'} />
30
-
31
- <Callout>
32
- 客户端数据库模式下数据均保留在用户本地,不会跨多端同步,也不支持文件上传、知识库等进阶功能。
33
- </Callout>
34
-
35
- ## 进阶模式:服务端数据库
36
-
37
- 针对已经了解 LobeHub 的用户,或需要多端同步的用户,可以自行部署带有服务端数据库的版本,进而获得更完整、功能更强大的 LobeHub。
38
-
39
- <Cards rows={1}>
40
- <Card href={'/zh/docs/self-hosting/server-database'} title={'服务端数据库部署指南'} />
41
- </Cards>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent 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",
@@ -8,39 +8,39 @@ import {
8
8
  } from '@/server/services/memory/userMemory/persona/service';
9
9
 
10
10
  const workflowPayloadSchema = z.object({
11
- userId: z.string().optional(),
12
11
  userIds: z.array(z.string()).optional(),
13
12
  });
14
13
 
15
14
  export const { POST } = serve(async (context) => {
16
- const payload = workflowPayloadSchema.parse(context.requestPayload || {});
15
+ const payload = await context.run('memory:pipelines:persona:update-writing:parse-payload', () =>
16
+ workflowPayloadSchema.parse(context.requestPayload || {}),
17
+ );
17
18
  const db = await getServerDB();
18
19
 
19
- const userIds = Array.from(
20
- new Set([...(payload.userIds || []), ...(payload.userId ? [payload.userId] : [])]),
21
- ).filter(Boolean);
22
-
20
+ const userIds = Array.from(new Set(payload.userIds || [])).filter(Boolean);
23
21
  if (userIds.length === 0) {
24
- return { message: 'userId or userIds is required', processedUsers: 0 };
22
+ throw new Error('No user IDs provided for persona update.');
25
23
  }
26
24
 
27
25
  const service = new UserPersonaService(db);
28
- const results = [];
29
26
 
30
- for (const userId of userIds) {
31
- const context = await buildUserPersonaJobInput(db, userId);
32
- const result = await service.composeWriting({ ...context, userId });
33
- results.push({
34
- diffId: result.diff?.id,
35
- documentId: result.document.id,
36
- userId,
37
- version: result.document.version,
38
- });
39
- }
27
+ await Promise.all(
28
+ userIds.map(async (userId) =>
29
+ context.run(`memory:pipelines:persona:update-writing:users:${userId}`, async () => {
30
+ const context = await buildUserPersonaJobInput(db, userId);
31
+ const result = await service.composeWriting({ ...context, userId });
32
+ return {
33
+ diffId: result.diff?.id,
34
+ documentId: result.document.id,
35
+ userId,
36
+ version: result.document.version,
37
+ };
38
+ }),
39
+ ),
40
+ );
40
41
 
41
42
  return {
42
43
  message: 'User persona processed via workflow.',
43
44
  processedUsers: userIds.length,
44
- results,
45
45
  };
46
46
  });
@@ -104,7 +104,7 @@ const KlavisToolAuthItem = memo<KlavisToolAuthItemProps>(({ tool, onAuthComplete
104
104
  try {
105
105
  await refreshKlavisServerTools(identifier);
106
106
  } catch (error) {
107
- console.error('[Klavis] Failed to check auth status:', error);
107
+ console.debug('[Klavis] Polling check (expected during auth):', error);
108
108
  }
109
109
  }, POLL_INTERVAL_MS);
110
110
 
@@ -129,7 +129,8 @@ const KlavisToolAuthItem = memo<KlavisToolAuthItemProps>(({ tool, onAuthComplete
129
129
  windowCheckIntervalRef.current = null;
130
130
  }
131
131
  oauthWindowRef.current = null;
132
- refreshKlavisServerTools(identifier);
132
+ // Start polling after window closes
133
+ startFallbackPolling(identifier);
133
134
  }
134
135
  } catch {
135
136
  if (windowCheckIntervalRef.current) {
@@ -120,7 +120,7 @@ const KlavisSkillItem = memo<KlavisSkillItemProps>(({ serverType, server }) => {
120
120
  try {
121
121
  await refreshKlavisServerTools(serverName);
122
122
  } catch (error) {
123
- console.error('[Klavis] Failed to check auth status:', error);
123
+ console.debug('[Klavis] Polling check (expected during auth):', error);
124
124
  }
125
125
  }, POLL_INTERVAL_MS);
126
126
 
@@ -145,8 +145,8 @@ const KlavisSkillItem = memo<KlavisSkillItemProps>(({ serverType, server }) => {
145
145
  windowCheckIntervalRef.current = null;
146
146
  }
147
147
  oauthWindowRef.current = null;
148
- await refreshKlavisServerTools(serverName);
149
- setIsWaitingAuth(false);
148
+ // Start polling after window closes
149
+ startFallbackPolling(serverName);
150
150
  }
151
151
  } catch {
152
152
  console.log('[Klavis] COOP blocked window.closed access, falling back to polling');
@@ -5,6 +5,7 @@ import { KlavisServerStatus } from '@/store/tool/slices/klavisStore';
5
5
 
6
6
  const POLL_INTERVAL_MS = 1000;
7
7
  const POLL_TIMEOUT_MS = 15_000;
8
+ const WINDOW_CLOSED_POLL_TIMEOUT_MS = 4000; // Shorter timeout when window is closed
8
9
 
9
10
  interface UseKlavisOAuthProps {
10
11
  serverStatus?: KlavisServerStatus;
@@ -50,14 +51,14 @@ export const useKlavisOAuth = ({ serverStatus }: UseKlavisOAuthProps) => {
50
51
  }, [serverStatus, isWaitingAuth, cleanup]);
51
52
 
52
53
  const startFallbackPolling = useCallback(
53
- (serverName: string) => {
54
+ (serverName: string, timeoutMs: number = POLL_TIMEOUT_MS) => {
54
55
  if (pollIntervalRef.current) return;
55
56
 
56
57
  pollIntervalRef.current = setInterval(async () => {
57
58
  try {
58
59
  await refreshKlavisServerTools(serverName);
59
60
  } catch (error) {
60
- console.error('[Klavis] Failed to check auth status:', error);
61
+ console.debug('[Klavis] Polling check (expected during auth):', error);
61
62
  }
62
63
  }, POLL_INTERVAL_MS);
63
64
 
@@ -67,7 +68,7 @@ export const useKlavisOAuth = ({ serverStatus }: UseKlavisOAuthProps) => {
67
68
  pollIntervalRef.current = null;
68
69
  }
69
70
  setIsWaitingAuth(false);
70
- }, POLL_TIMEOUT_MS);
71
+ }, timeoutMs);
71
72
  },
72
73
  [refreshKlavisServerTools],
73
74
  );
@@ -77,23 +78,29 @@ export const useKlavisOAuth = ({ serverStatus }: UseKlavisOAuthProps) => {
77
78
  windowCheckIntervalRef.current = setInterval(() => {
78
79
  try {
79
80
  if (oauthWindow.closed) {
81
+ // Stop monitoring window
80
82
  if (windowCheckIntervalRef.current) {
81
83
  clearInterval(windowCheckIntervalRef.current);
82
84
  windowCheckIntervalRef.current = null;
83
85
  }
84
86
  oauthWindowRef.current = null;
85
- refreshKlavisServerTools(serverName);
87
+
88
+ // Start polling to check auth status after window closes
89
+ // Use shorter timeout since user has closed the window
90
+ // Keep loading state until we confirm success or timeout
91
+ startFallbackPolling(serverName, WINDOW_CLOSED_POLL_TIMEOUT_MS);
86
92
  }
87
93
  } catch {
88
94
  if (windowCheckIntervalRef.current) {
89
95
  clearInterval(windowCheckIntervalRef.current);
90
96
  windowCheckIntervalRef.current = null;
91
97
  }
98
+ // Use default timeout for fallback polling
92
99
  startFallbackPolling(serverName);
93
100
  }
94
101
  }, 500);
95
102
  },
96
- [refreshKlavisServerTools, startFallbackPolling],
103
+ [startFallbackPolling],
97
104
  );
98
105
 
99
106
  const openOAuthWindow = useCallback(
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ import { type ReactNode, useEffect } from 'react';
4
+
5
+ interface DebugNodeProps {
6
+ children?: ReactNode;
7
+ trace: string;
8
+ }
9
+
10
+ const DebugNode = ({ children, trace }: DebugNodeProps) => {
11
+ if (process.env.NODE_ENV !== 'development') return null;
12
+ // eslint-disable-next-line react-hooks/rules-of-hooks
13
+ useEffect(() => {
14
+ // eslint-disable-next-line no-console
15
+ console.log(`[DebugNode] Suspense fallback active: ${trace}`);
16
+ }, [trace]);
17
+
18
+ return children ?? null;
19
+ };
20
+
21
+ export default DebugNode;
@@ -88,7 +88,7 @@ const KlavisServerItem = memo<KlavisServerItemProps>(
88
88
  try {
89
89
  await refreshKlavisServerTools(serverName);
90
90
  } catch (error) {
91
- console.error('[Klavis] Failed to check auth status:', error);
91
+ console.debug('[Klavis] Polling check (expected during auth):', error);
92
92
  }
93
93
  }, POLL_INTERVAL_MS);
94
94
 
@@ -121,8 +121,8 @@ const KlavisServerItem = memo<KlavisServerItemProps>(
121
121
  }
122
122
  oauthWindowRef.current = null;
123
123
 
124
- // 窗口关闭后立即检查一次认证状态
125
- refreshKlavisServerTools(serverName);
124
+ // 窗口关闭后开始轮询检查认证状态
125
+ startFallbackPolling(serverName);
126
126
  }
127
127
  } catch {
128
128
  // COOP 阻止了访问,降级到轮询方案
@@ -1,6 +1,7 @@
1
1
  import { Flexbox } from '@lobehub/ui';
2
- import { memo } from 'react';
2
+ import { Suspense, memo } from 'react';
3
3
 
4
+ import DebugNode from '@/components/DebugNode';
4
5
  import PluginTag from '@/components/Plugins/PluginTag';
5
6
  import { useToolStore } from '@/store/tool';
6
7
  import { customPluginSelectors } from '@/store/tool/selectors';
@@ -11,18 +12,20 @@ const ToolItem = memo<CheckboxItemProps>(({ id, onUpdate, label, checked }) => {
11
12
  const isCustom = useToolStore((s) => customPluginSelectors.isCustomPlugin(id)(s));
12
13
 
13
14
  return (
14
- <CheckboxItem
15
- checked={checked}
16
- hasPadding={false}
17
- id={id}
18
- label={
19
- <Flexbox align={'center'} gap={8} horizontal>
20
- {label || id}
21
- {isCustom && <PluginTag showText={false} type={'customPlugin'} />}
22
- </Flexbox>
23
- }
24
- onUpdate={onUpdate}
25
- />
15
+ <Suspense fallback={<DebugNode trace="ActionBar/Tools/ToolItem" />}>
16
+ <CheckboxItem
17
+ checked={checked}
18
+ hasPadding={false}
19
+ id={id}
20
+ label={
21
+ <Flexbox align={'center'} gap={8} horizontal>
22
+ {label || id}
23
+ {isCustom && <PluginTag showText={false} type={'customPlugin'} />}
24
+ </Flexbox>
25
+ }
26
+ onUpdate={onUpdate}
27
+ />
28
+ </Suspense>
26
29
  );
27
30
  });
28
31
 
@@ -59,51 +59,62 @@ interface ToolsListProps {
59
59
  items: ItemType[];
60
60
  }
61
61
 
62
- const ToolsList = memo<ToolsListProps>(({ items }) => {
63
- const renderItem = (item: ToolItemData, index: number) => {
64
- if (item.type === 'divider') {
65
- return <Divider key={`divider-${index}`} style={{ margin: '4px 0' }} />;
66
- }
62
+ const DividerItem = memo<{ index: number }>(({ index }) => (
63
+ <Divider key={`divider-${index}`} style={{ margin: '4px 0' }} />
64
+ ));
67
65
 
68
- if (item.type === 'group') {
69
- return (
70
- <Fragment key={item.key || `group-${index}`}>
71
- <Text className={toolsListStyles.groupLabel} fontSize={12} type="secondary">
72
- {item.label}
73
- </Text>
74
- {item.children?.map((child, childIndex) => renderItem(child, childIndex))}
75
- </Fragment>
76
- );
77
- }
66
+ const RegularItem = memo<{ index: number; item: ToolItemData }>(({ item, index }) => {
67
+ const iconNode = item.icon ? (
68
+ isValidElement(item.icon) ? (
69
+ item.icon
70
+ ) : (
71
+ <Icon icon={item.icon as any} size={20} />
72
+ )
73
+ ) : null;
74
+
75
+ return (
76
+ <div
77
+ className={toolsListStyles.item}
78
+ key={item.key || `item-${index}`}
79
+ onClick={item.onClick}
80
+ role="button"
81
+ tabIndex={0}
82
+ >
83
+ {iconNode && <div className={toolsListStyles.itemIcon}>{iconNode}</div>}
84
+ <div className={toolsListStyles.itemContent}>{item.label}</div>
85
+ {item.extra}
86
+ </div>
87
+ );
88
+ });
78
89
 
79
- // Regular item
80
- // icon can be: ReactNode (already rendered), LucideIcon/ForwardRef (needs Icon wrapper), or undefined
81
- const iconNode = item.icon ? (
82
- isValidElement(item.icon) ? (
83
- item.icon
84
- ) : (
85
- <Icon icon={item.icon as any} size={20} />
86
- )
87
- ) : null;
88
-
89
- return (
90
- <div
91
- className={toolsListStyles.item}
92
- key={item.key || `item-${index}`}
93
- onClick={item.onClick}
94
- role="button"
95
- tabIndex={0}
96
- >
97
- {iconNode && <div className={toolsListStyles.itemIcon}>{iconNode}</div>}
98
- <div className={toolsListStyles.itemContent}>{item.label}</div>
99
- {item.extra}
100
- </div>
101
- );
102
- };
90
+ const GroupItem = memo<{ index: number; item: ToolItemData }>(({ item, index }) => (
91
+ <Fragment key={item.key || `group-${index}`}>
92
+ <Text className={toolsListStyles.groupLabel} fontSize={12} type="secondary">
93
+ {item.label}
94
+ </Text>
95
+ {item.children?.map((child, childIndex) => (
96
+ <ToolListItem index={childIndex} item={child} key={child.key || `item-${childIndex}`} />
97
+ ))}
98
+ </Fragment>
99
+ ));
103
100
 
101
+ const ToolListItem = memo<{ index: number; item: ToolItemData | null }>(({ item, index }) => {
102
+ if (!item) return null;
103
+ if (item.type === 'divider') return <DividerItem index={index} />;
104
+ if (item.type === 'group') return <GroupItem index={index} item={item} />;
105
+ return <RegularItem index={index} item={item} />;
106
+ });
107
+
108
+ const ToolsList = memo<ToolsListProps>(({ items }) => {
104
109
  return (
105
110
  <Flexbox gap={0} padding={4}>
106
- {items.map((item, index) => renderItem(item as ToolItemData, index))}
111
+ {items.map((item, index) => (
112
+ <ToolListItem
113
+ index={index}
114
+ item={item as ToolItemData | null}
115
+ key={item?.key || `item-${index}`}
116
+ />
117
+ ))}
107
118
  </Flexbox>
108
119
  );
109
120
  });
@@ -17,6 +17,7 @@ import { createStaticStyles, cx } from 'antd-style';
17
17
  import {
18
18
  type CSSProperties,
19
19
  type ReactNode,
20
+ Suspense,
20
21
  isValidElement,
21
22
  memo,
22
23
  useCallback,
@@ -26,6 +27,7 @@ import {
26
27
  useState,
27
28
  } from 'react';
28
29
 
30
+ import DebugNode from '@/components/DebugNode';
29
31
  import { useIsMobile } from '@/hooks/useIsMobile';
30
32
 
31
33
  const styles = createStaticStyles(({ css }) => ({
@@ -271,7 +273,11 @@ const ActionDropdown = memo<ActionDropdownProps>(
271
273
  hoverTrigger={Boolean(resolvedTriggerProps?.openOnHover)}
272
274
  placement={isMobile ? 'top' : placement}
273
275
  >
274
- <DropdownMenuPopup {...resolvedPopupProps}>{menuContent}</DropdownMenuPopup>
276
+ <DropdownMenuPopup {...resolvedPopupProps}>
277
+ <Suspense fallback={<DebugNode trace="ActionDropdown > popup" />}>
278
+ {menuContent}
279
+ </Suspense>
280
+ </DropdownMenuPopup>
275
281
  </DropdownMenuPositioner>
276
282
  </DropdownMenuPortal>
277
283
  </DropdownMenuRoot>
@@ -2,8 +2,9 @@
2
2
 
3
3
  import { Flexbox, Popover, type PopoverProps } from '@lobehub/ui';
4
4
  import { createStaticStyles, cssVar, cx } from 'antd-style';
5
- import { type ReactNode, memo } from 'react';
5
+ import { type ReactNode, Suspense, memo } from 'react';
6
6
 
7
+ import DebugNode from '@/components/DebugNode';
7
8
  import UpdateLoading from '@/components/Loading/UpdateLoading';
8
9
  import { useIsMobile } from '@/hooks/useIsMobile';
9
10
 
@@ -65,16 +66,18 @@ const ActionPopover = memo<ActionPopoverProps>(
65
66
 
66
67
  // Compose content with optional title
67
68
  const popoverContent = (
68
- <>
69
- {title && (
70
- <Flexbox gap={8} horizontal justify={'space-between'} style={{ marginBottom: 16 }}>
71
- {title}
72
- {extra}
73
- {loading && <UpdateLoading style={{ color: cssVar.colorTextSecondary }} />}
74
- </Flexbox>
75
- )}
76
- {content}
77
- </>
69
+ <Suspense fallback={<DebugNode trace="ActionPopover > content" />}>
70
+ <>
71
+ {title && (
72
+ <Flexbox gap={8} horizontal justify={'space-between'} style={{ marginBottom: 16 }}>
73
+ {title}
74
+ {extra}
75
+ {loading && <UpdateLoading style={{ color: cssVar.colorTextSecondary }} />}
76
+ </Flexbox>
77
+ )}
78
+ {content}
79
+ </>
80
+ </Suspense>
78
81
  );
79
82
 
80
83
  return (
@@ -124,6 +124,7 @@ export const klavisRouter = router({
124
124
 
125
125
  /**
126
126
  * Get server instance status from Klavis API
127
+ * Returns error object instead of throwing on auth errors (useful for polling)
127
128
  */
128
129
  getServerInstance: klavisProcedure
129
130
  .input(
@@ -132,16 +133,43 @@ export const klavisRouter = router({
132
133
  }),
133
134
  )
134
135
  .query(async ({ input, ctx }) => {
135
- const response = await ctx.klavisClient.mcpServer.getServerInstance(input.instanceId);
136
- return {
137
- authNeeded: response.authNeeded,
138
- externalUserId: response.externalUserId,
139
- instanceId: response.instanceId,
140
- isAuthenticated: response.isAuthenticated,
141
- oauthUrl: response.oauthUrl,
142
- platform: response.platform,
143
- serverName: response.serverName,
144
- };
136
+ try {
137
+ const response = await ctx.klavisClient.mcpServer.getServerInstance(input.instanceId);
138
+ return {
139
+ authNeeded: response.authNeeded,
140
+ error: undefined,
141
+ externalUserId: response.externalUserId,
142
+ instanceId: response.instanceId,
143
+ isAuthenticated: response.isAuthenticated,
144
+ oauthUrl: response.oauthUrl,
145
+ platform: response.platform,
146
+ serverName: response.serverName,
147
+ };
148
+ } catch (error) {
149
+ // Check if this is an authentication error
150
+ const errorMessage = error instanceof Error ? error.message : String(error);
151
+ const isAuthError =
152
+ errorMessage.includes('Invalid API key or instance ID') ||
153
+ errorMessage.includes('Status code: 401');
154
+
155
+ // For auth errors, return error object instead of throwing
156
+ // This prevents 500 errors in logs during polling
157
+ if (isAuthError) {
158
+ return {
159
+ authNeeded: true,
160
+ error: 'AUTH_ERROR',
161
+ externalUserId: undefined,
162
+ instanceId: input.instanceId,
163
+ isAuthenticated: false,
164
+ oauthUrl: undefined,
165
+ platform: undefined,
166
+ serverName: undefined,
167
+ };
168
+ }
169
+
170
+ // For other errors, still throw
171
+ throw error;
172
+ }
145
173
  }),
146
174
 
147
175
  getUserIntergrations: klavisProcedure
@@ -2266,7 +2266,7 @@ export class MemoryExtractionWorkflowService {
2266
2266
 
2267
2267
  const url = getWorkflowUrl(WORKFLOW_PATHS.personaUpdate, baseUrl);
2268
2268
  return this.getClient().trigger({
2269
- body: { userId },
2269
+ body: { userIds: [userId] },
2270
2270
  flowControl: {
2271
2271
  key: `memory-user-memory.pipelines.persona.update-write.${userId}`,
2272
2272
  parallelism: 1,
@@ -210,10 +210,30 @@ export const createKlavisStoreSlice: StateCreator<
210
210
  instanceId: server.instanceId,
211
211
  });
212
212
 
213
+ // If server returned an auth error (during polling), silently return
214
+ // This happens when user is still in the process of authorizing
215
+ if (instanceStatus.error === 'AUTH_ERROR') {
216
+ set(
217
+ produce((draft: KlavisStoreState) => {
218
+ draft.loadingServerIds.delete(identifier);
219
+ }),
220
+ false,
221
+ n('refreshKlavisServerTools/pendingAuth'),
222
+ );
223
+ return;
224
+ }
225
+
213
226
  // If authentication failed, remove server and reset status
214
227
  if (!instanceStatus.isAuthenticated) {
215
228
  if (!instanceStatus.authNeeded) {
216
229
  // If no authentication needed, all is well
230
+ set(
231
+ produce((draft: KlavisStoreState) => {
232
+ draft.loadingServerIds.delete(identifier);
233
+ }),
234
+ false,
235
+ n('refreshKlavisServerTools/noAuthNeeded'),
236
+ );
217
237
  return;
218
238
  }
219
239
 
@@ -1,157 +0,0 @@
1
- ---
2
- title: Deploying Server-Side Database for LobeHub
3
- description: Learn how to deploy LobeHub's server-side database using Postgres.
4
- tags:
5
- - LobeHub
6
- - Server-Side Database
7
- - Postgres
8
- - Deployment Guide
9
- ---
10
-
11
- # Deploying Server-Side Database
12
-
13
- LobeHub defaults to using a client-side database (IndexedDB) but also supports deploying a server-side database. LobeHub uses Postgres as the backend storage database.
14
-
15
- <Callout>
16
- PostgreSQL is a powerful open-source relational database management system with high scalability
17
- and standard SQL support. It provides rich data types, concurrency control, data integrity,
18
- security, and programmability, making it suitable for complex applications and large-scale data
19
- management.
20
- </Callout>
21
-
22
- This guide will introduce the process and principles of deploying the server-side database version of LobeHub on any platform from a framework perspective, so you can understand both the what and the why, and then deploy according to your specific needs.
23
-
24
- If you are already familiar with the complete principles, you can quickly get started by checking the deployment guides for each platform:
25
-
26
- <PlatformCards urlPrefix={'platform'} />
27
-
28
- ---
29
-
30
- For the server-side database version of LobeHub, a normal deployment process typically involves configuring three modules:
31
-
32
- 1. Database configuration;
33
- 2. Authentication service configuration;
34
- 3. S3 storage service configuration.
35
-
36
- ## Configure the Database
37
-
38
- Before deployment, make sure you have a Postgres database instance ready. You can choose from the following instances:
39
-
40
- - `A.` Use Serverless Postgres instances like Vercel/Neon;
41
- - `B.` Use self-deployed Postgres instances like Docker/Railway/Zeabur, collectively referred to as Node Postgres instances;
42
-
43
- <Callout>
44
- There is a slight difference in the way they are configured in terms of environment variables.
45
- </Callout>
46
-
47
- Since we support file-based conversations/knowledge base conversations, we need to install the `pgvector` plugin for Postgres. This plugin provides vector search capabilities and is a key component for LobeHub to implement RAG.
48
-
49
- <Steps>
50
- ### `NEXT_PUBLIC_SERVICE_MODE`
51
-
52
- LobeHub supports both client-side and server-side databases, so we provide an environment variable for switching modes, which is `NEXT_PUBLIC_SERVICE_MODE`, with a default value of `client`.
53
-
54
- For server-side database deployment scenarios, you need to set `NEXT_PUBLIC_SERVICE_MODE` to `server`.
55
-
56
- <Callout type={'info'}>
57
- In the official `lobehub` Docker image, this environment variable is already set to
58
- `server` by default. Therefore, if you deploy using the Docker image, you do not need to configure
59
- this environment variable again.
60
- </Callout>
61
-
62
- <Callout type={'tip'}>
63
- Since environment variables starting with `NEXT_PUBLIC` take effect in the front-end code, they cannot be modified through container runtime injection. (Refer to the `next.js` documentation [Configuring: Environment Variables | Next.js (nextjs.org)](https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables)). This is why we chose to create a separate DB version image.
64
-
65
- If you need to modify variables with the `NEXT_PUBLIC` prefix in a Docker deployment, you must build the image yourself and inject your own `NEXT_PUBLIC` prefixed environment variables during the build.
66
- </Callout>
67
-
68
- ### `DATABASE_URL`
69
-
70
- The core of configuring the database is to add the `DATABASE_URL` environment variable and fill in the Postgres database connection URL you have prepared. The typical format of the database connection URL is `postgres://username:password@host:port/database`.
71
-
72
- <Callout type={'info'}>
73
- If you want to enable SSL when connecting to the database, please refer to the
74
- [documentation](https://stackoverflow.com/questions/14021998/using-psql-to-connect-to-postgresql-in-ssl-mode)
75
- for setup instructions.
76
- </Callout>
77
-
78
- ### `DATABASE_DRIVER`
79
-
80
- The `DATABASE_DRIVER` environment variable is used to distinguish between the two types of Postgres database instances, with values of `node` or `neon`.
81
-
82
- To streamline deployment, we have set default values based on the characteristics of different platforms:
83
-
84
- - On the Vercel platform, `DATABASE_DRIVER` defaults to `neon`;
85
- - In our provided Docker image `lobehub`, `DATABASE_DRIVER` defaults to `node`.
86
-
87
- Therefore, if you follow the standard deployment methods below, you do not need to manually configure the `DATABASE_DRIVER` environment variable:
88
-
89
- - Vercel + Serverless Postgres
90
- - Docker image + Node Postgres
91
-
92
- ### `KEY_VAULTS_SECRET`
93
-
94
- Considering that users will store sensitive information such as their API Key and baseURL in the database, we need a key to encrypt this information to prevent leakage in case of a database breach. Hence, the `KEY_VAULTS_SECRET` environment variable is used to encrypt sensitive information like user-stored apikeys.
95
-
96
- <Callout type={'info'}>
97
- You can generate a random 32-character string as the value of `KEY_VAULTS_SECRET` using `openssl
98
- rand -base64 32`.
99
- </Callout>
100
- </Steps>
101
-
102
- ## Configuring Authentication Services
103
-
104
- In the server-side database mode, we need an authentication service to distinguish the identities of different users. There are many well-developed authentication solutions in the open-source community. We have integrated two different authentication services to meet the demands of different scenarios, one is Clerk, and the other is NextAuth.
105
-
106
- ### Clerk
107
-
108
- [Clerk](https://clerk.com?utm_source=lobehub\&utm_medium=docs) is an authentication SaaS service that provides out-of-the-box authentication capabilities with high productization, low integration costs, and a great user experience. For those who offer SaaS products, Clerk is a good choice. Our official [LobeHub Cloud](https://LobeHub.com) uses Clerk as the authentication service.
109
-
110
- The integration of Clerk is relatively simple, requiring only the configuration of these environment variables:
111
-
112
- - `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY`, which can be obtained from the Clerk console
113
- - `CLERK_WEBHOOK_SECRET`, which is generated by following these instructions: [Configure Clerk Authentication Service](/docs/self-hosting/advanced/auth/clerk#create-and-configure-webhook-in-clerk).
114
-
115
- <Callout type={'tip'}>
116
- In Vercel deployment mode, we recommend using Clerk as the authentication service for a better
117
- user experience.
118
- </Callout>
119
-
120
- However, this type of authentication relies on Clerk's official service, so there may be some limitations in certain scenarios:
121
-
122
- - For example, when using Clerk in China, it may be affected by the network environment.
123
- - Clerk is not suitable for scenarios that require complete private deployment.
124
- - It relies on `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`, which may not be readily usable with public Docker images.
125
-
126
- Therefore, for the above scenarios, we also provide NextAuth as an alternative solution.
127
-
128
- ### NextAuth
129
-
130
- NextAuth is an open-source authentication library that supports multiple identity providers, including Auth0, Cognito, GitHub, Google, Facebook, Apple, Twitter, and more. NextAuth itself provides a complete authentication solution, including user registration, login, password recovery, integration with various identity providers, and more.
131
-
132
- For information on configuring NextAuth, you can refer to the [Authentication](/docs/self-hosting/advanced/authentication) documentation.
133
-
134
- <Callout type={'tip'}>
135
- In the official Docker image `lobehub`, we recommend using NextAuth as the
136
- authentication service.
137
- </Callout>
138
-
139
- ## Configuring S3 Storage Service
140
-
141
- LobeHub has supported multimodal AI conversations since [a long time ago](https://x.com/lobehub/status/1724289575672291782), involving the function of uploading images to large models. In the client-side database solution, image files are stored as binary data directly in the browser's IndexedDB database. However, this solution is not feasible in the server-side database. Storing file-like data directly in Postgres will greatly waste valuable database storage space and slow down computational performance.
142
-
143
- The best practice in this area is to use a file storage service (S3) to store image files, which is also the storage solution relied upon for subsequent file uploads/knowledge base functions.
144
-
145
- <Callout type={'info'}>
146
- In this documentation, S3 refers to a compatible S3 storage solution, which supports the Amazon S3
147
- API-compatible object storage system. Common examples include Cloudflare R2, Alibaba Cloud OSS,
148
- and self-deployable RustFS, ceph, all of which support the S3-compatible API.
149
- </Callout>
150
-
151
- For detailed configuration guidelines on S3, please refer to [S3 Object Storage](/docs/self-hosting/advanced/s3) for more information.
152
-
153
- ## Getting Started with Deployment
154
-
155
- The above is a detailed explanation of configuring LobeHub with a server-side database. You can configure it according to your actual situation and then choose a deployment platform that suits you to start deployment:
156
-
157
- <PlatformCards urlPrefix={'platform'} />
@@ -1,146 +0,0 @@
1
- ---
2
- title: 使用服务端数据库部署 - 配置数据库、身份验证服务和 S3 存储服务
3
- description: 本文将介绍服务端数据库版 LobeHub 的部署思路,解释如何配置数据库、身份验证服务和 S3 存储服务。
4
- tags:
5
- - 服务端数据库
6
- - Postgres
7
- - S3存储服务
8
- - 数据库配置
9
- - 身份验证服务
10
- - 环境变量配置
11
- ---
12
-
13
- # 使用服务端数据库部署
14
-
15
- LobeHub 默认使用客户端数据库(IndexedDB),同时也支持使用服务端数据库(下简称 DB 版)。LobeHub 采用了 Postgres 作为后端存储数据库。
16
-
17
- <Callout>
18
- PostgreSQL 是一种强大的开源关系型数据库管理系统,具备高度扩展性和标准 SQL
19
- 支持。它提供了丰富的数据类型、并发处理、数据完整性、安全性及可编程性,适用于复杂应用和大规模数据管理。
20
- </Callout>
21
-
22
- 本文将从框架角度介绍在任何一个平台中部署 DB 版 LobeHub 的流程和原理,让你知其然也知其所以然,最后可以根据自己的实际情况进行部署。
23
-
24
- 如你已经熟悉完整原理,可以查看各个平台的部署指南快速开始:
25
-
26
- <PlatformCards urlPrefix={'platform'} />
27
-
28
- ---
29
-
30
- 对于 LobeHub 的 DB 版,正常的部署流程都需要包含三个模块的配置:
31
-
32
- 1. 数据库配置;
33
- 2. 身份验证服务配置;
34
- 3. S3 存储服务配置。
35
-
36
- ## 配置数据库
37
-
38
- 在部署之前,请确保你已经准备好 Postgres 数据库实例,你可以选择以下任一实例:
39
-
40
- - `A.` 使用 Vercel / Neon 等 Serverless Postgres 实例;
41
- - `B.` 使用 Docker / Railway / Zeabur 等自部署 Postgres 实例,下统称 Node Postgres 实例;
42
-
43
- <Callout>两者的配置方式在环境变量的取值上会略有一点区别,其他方面是一样的。</Callout>
44
-
45
- 同时,由于我们支持了文件对话 / 知识库对话的能力,因此我们需要为 Postgres 安装 `pgvector` 插件,该插件提供了向量搜索的能力,是 LobeHub 实现 RAG 的重要构件之一。
46
-
47
- <Steps>
48
- ### `NEXT_PUBLIC_SERVICE_MODE`
49
-
50
- LobeHub 同时支持了客户端数据库和服务端数据库,因此我们提供了一个环境变量用于切换模式,这个变量为 `NEXT_PUBLIC_SERVICE_MODE`,该值默认为 `client`。
51
-
52
- 针对服务端数据库部署场景,你需要将 `NEXT_PUBLIC_SERVICE_MODE` 设置为 `server`。
53
-
54
- <Callout type={'info'}>
55
- 在官方的 `lobehub` Docker 镜像中,已经默认将该环境变量设为 `server`,因此如果你使用
56
- Docker 镜像部署,则无需再配置该环境变量。
57
- </Callout>
58
-
59
- <Callout type={'tip'}>
60
- 由于 `NEXT_PUBLIC` 开头的环境变量是在前端代码中生效的,而因此无法通过容器运行时注入进行修改。 (`next.js`的参考文档 [Configuring: Environment Variables | Next.js (nextjs.org)](https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables) ) 这也是为什么我们选择再打一个 DB 版镜像的原因。
61
-
62
- 如果你需要在 Docker 部署中修改 `NEXT_PUBLIC` 前缀的变量,你必须自行构建镜像,在 build 时就把自己的 `NEXT_PUBLIC` 开头的环境变量打进去。
63
- </Callout>
64
-
65
- ### `DATABASE_URL`
66
-
67
- 配置数据库,核心是添加 `DATABASE_URL` 环境变量,将你准备好的 Postgres 数据库连接 URL 填入其中。数据库连接 URL 的通常格式为 `postgres://username:password@host:port/database`。
68
-
69
- <Callout type={'info'}>
70
- 如果希望连接数据库时启用 SSL
71
- ,请自行参考[文档](https://stackoverflow.com/questions/14021998/using-psql-to-connect-to-postgresql-in-ssl-mode)进行设置
72
- </Callout>
73
-
74
- ### `DATABASE_DRIVER`
75
-
76
- `DATABASE_DRIVER` 环境变量用于区分两种 Postgres 数据库实例,`DATABASE_DRIVER` 的取值为 `node` 或 `neon`。
77
-
78
- 为提升部署便捷性,我们根据不同的平台特点设置了默认值:
79
-
80
- - 在 Vercel 平台下,`DATABASE_DRIVER` 默认为 `neon`;
81
- - 在我们提供的 Docker 镜像 `lobehub` 中,`DATABASE_DRIVER` 默认为 `node`。
82
-
83
- 因此如果你采用了以下标准的部署方式,你无需手动配置 `DATABASE_DRIVER` 环境变量:
84
-
85
- - Vercel + Serverless Postgres
86
- - Docker 镜像 + Node Postgres
87
-
88
- ### `KEY_VAULTS_SECRET`
89
-
90
- 考虑到用户会存储自己的 API Key 和 baseURL 等敏感信息到数据库中,因此我们需要一个密钥来加密这些信息,避免数据库被爆破 / 脱库时这些关键信息被泄露。 因此有了 `KEY_VAULTS_SECRET` 环境变量,用于加密用户存储的 apikey 等敏感信息。
91
-
92
- <Callout type={'info'}>
93
- 你可以使用 `openssl rand -base64 32` 生成一个随机的 32 位字符串作为 `KEY_VAULTS_SECRET` 的值。
94
- </Callout>
95
- </Steps>
96
-
97
- ## 配置身份验证服务
98
-
99
- 在服务端数据库模式下,我们要为不同用户区分身份,因此需要一个身份验证服务。开源社区中已经存在较多完善的身份验证解决方案。我们在实现过程中集成了两种不同的身份验证服务,用于满足不同场景的诉求,一种是 Clerk ,另外一种是 NextAuth。
100
-
101
- ### Clerk
102
-
103
- [Clerk](https://clerk.com?utm_source=lobehub\&utm_medium=docs) 是一个身份验证 SaaS 服务,提供了开箱即用的身份验证能力,产品化程度很高,集成成本较低,体验很好。对于提供 SaaS 化产品的诉求来说,Clerk 是一个不错的选择。我们官方提供的 [LobeHub Cloud](https://LobeHub.com),就是使用了 Clerk 作为身份验证服务。
104
-
105
- Clerk 的集成也相对简单,只需要配置 `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` 、 `CLERK_SECRET_KEY` 和 `CLERK_WEBHOOK_SECRET` 环境变量即可,这三个环境变量可以在 Clerk 控制台中获取。
106
-
107
- <Callout type={'tip'}>
108
- 在 Vercel 部署模式下,我们推荐使用 Clerk 作为身份验证服务,可以获得更好的用户体验。
109
- </Callout>
110
-
111
- 但是这种身份验证依赖了 Clerk 官方的服务,因此在一些场景下可能会有一些限制:
112
-
113
- - 比如在国内使用 Clerk 时,可能会受到网络环境的影响;
114
- - 需要完全私有化部署的场景下,Clerk 并不适用;
115
- - 必须依赖 `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`,对于公共 Docker 镜像无法开箱即用;
116
-
117
- 因此针对上述场景,我们也提供了 NextAuth 作为备选方案。
118
-
119
- ### NextAuth
120
-
121
- NextAuth 是一个开源的身份验证库,支持多种身份验证提供商,包括 Auth0、Cognito、GitHub、Google、Facebook、Apple、Twitter 等。NextAuth 本身提供了一套完整的身份验证解决方案,包括用户注册、登录、密码找回、多种身份验证提供商的集成等。
122
-
123
- 关于 NextAuth 的配置,你可以参考 [身份验证](/zh/docs/self-hosting/advanced/authentication) 的文档获取更多信息。
124
-
125
- <Callout type={'tip'}>
126
- 在官方的 Docker 镜像 `lobehub` 中,我们推荐使用 NextAuth 作为身份验证服务。
127
- </Callout>
128
-
129
- ## 配置 S3 存储服务
130
-
131
- LobeHub 在 [很早以前](https://x.com/lobehub/status/1724289575672291782) 就支持了多模态的 AI 会话,其中涉及到图片上传给大模型的功能。在客户端数据库方案中,图片文件直接以二进制数据存储在浏览器 IndexedDB 数据库,但在服务端数据库中这个方案并不可行。因为在 Postgres 中直接存储文件类二进制数据会大大浪费宝贵的数据库存储空间,并拖慢计算性能。
132
-
133
- 这块最佳实践是使用文件存储服务(S3)来存储图片文件,同时 S3 也是文件上传 / 知识库功能所依赖的大容量静态文件存储方案。
134
-
135
- <Callout type={'info'}>
136
- 在本文档库中,S3 所指代的是指兼容 S3 存储方案,即支持 Amazon S3 API 的对象存储系统,常见例如
137
- Cloudflare R2 、阿里云 OSS,可以自部署的 RustFS、ceph 等均支持 S3 兼容 API。
138
- </Callout>
139
-
140
- 关于 S3 的详细配置指南,请参阅 [S3 对象存储](/zh/docs/self-hosting/advanced/s3) 了解详情。
141
-
142
- ## 开始部署
143
-
144
- 以上就是关于服务端数据库版 LobeHub 的配置详解,你可以根据自己的实际情况进行配置,然后选择适合自己的部署平台开始部署:
145
-
146
- <PlatformCards urlPrefix={'platform'} />