@lobehub/lobehub 2.0.0-next.384 → 2.0.0-next.385

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 (35) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/docs/self-hosting/advanced/knowledge-base.mdx +1 -1
  4. package/docs/self-hosting/advanced/knowledge-base.zh-CN.mdx +1 -1
  5. package/docs/self-hosting/migration/v2/breaking-changes.mdx +8 -0
  6. package/docs/self-hosting/migration/v2/breaking-changes.zh-CN.mdx +8 -0
  7. package/docs/self-hosting/server-database/docker-compose.mdx +1 -1
  8. package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +1 -1
  9. package/docs/self-hosting/server-database/docker.mdx +2 -8
  10. package/docs/self-hosting/server-database/docker.zh-CN.mdx +2 -9
  11. package/locales/en-US/error.json +10 -3
  12. package/locales/zh-CN/error.json +10 -3
  13. package/package.json +1 -1
  14. package/packages/database/src/models/topicShare.ts +9 -1
  15. package/packages/model-bank/src/modelProviders/anthropic.ts +1 -1
  16. package/packages/model-bank/src/modelProviders/google.ts +1 -1
  17. package/packages/model-bank/src/modelProviders/hunyuan.ts +1 -1
  18. package/packages/model-bank/src/modelProviders/minimax.ts +1 -1
  19. package/packages/model-bank/src/modelProviders/openai.ts +1 -1
  20. package/packages/model-bank/src/modelProviders/siliconcloud.ts +3 -2
  21. package/packages/model-bank/src/modelProviders/vertexai.ts +1 -1
  22. package/packages/model-bank/src/modelProviders/volcengine.ts +1 -1
  23. package/packages/model-bank/src/modelProviders/xai.ts +1 -1
  24. package/packages/model-runtime/src/types/chat.ts +14 -0
  25. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ModelSelect/ImageModelItem.tsx +31 -9
  26. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ModelSelect/index.tsx +9 -2
  27. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/Checker.tsx +33 -4
  28. package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +18 -12
  29. package/src/features/Conversation/Messages/User/Extra.tsx +5 -1
  30. package/src/features/IntegrationDetailModal/IntegrationDetailContent.tsx +11 -12
  31. package/src/features/SharePopover/index.tsx +44 -5
  32. package/src/layout/GlobalProvider/useUserStateRedirect.ts +7 -1
  33. package/src/locales/default/chat.ts +1 -0
  34. package/src/locales/default/error.ts +10 -3
  35. package/src/store/global/initialState.ts +2 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.385](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.384...v2.0.0-next.385)
6
+
7
+ <sup>Released on **2026-01-26**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Share page improvements and pg17 docs update.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Share page improvements and pg17 docs update, closes [#11850](https://github.com/lobehub/lobe-chat/issues/11850) ([5b953b1](https://github.com/lobehub/lobe-chat/commit/5b953b1))
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.0-next.384](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.383...v2.0.0-next.384)
6
31
 
7
32
  <sup>Released on **2026-01-26**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "features": [
5
+ "Share page improvements and pg17 docs update."
6
+ ]
7
+ },
8
+ "date": "2026-01-26",
9
+ "version": "2.0.0-next.385"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "fixes": [
@@ -27,7 +27,7 @@ PostgreSQL is a powerful open-source relational database system, and PGVector is
27
27
  Deployment script example:
28
28
 
29
29
  ```
30
- docker run -p 5432:5432 -d --name pg -e POSTGRES_PASSWORD=mysecretpassword pgvector/pgvector:pg16
30
+ docker run -p 5432:5432 -d --name pg -e POSTGRES_PASSWORD=mysecretpassword pgvector/pgvector:pg17
31
31
  ```
32
32
 
33
33
  - **Note**: Ensure sufficient resources for vector operations
@@ -25,7 +25,7 @@ PostgreSQL 是一个强大的开源关系型数据库系统,而 PGVector 是
25
25
  示例部署脚本:
26
26
 
27
27
  ```
28
- docker run -p 5432:5432 -d --name pg -e POSTGRES_PASSWORD=mysecretpassword pgvector/pgvector:pg16
28
+ docker run -p 5432:5432 -d --name pg -e POSTGRES_PASSWORD=mysecretpassword pgvector/pgvector:pg17
29
29
  ```
30
30
 
31
31
  - **注意事项**:确保分配足够的资源以处理向量操作
@@ -71,3 +71,11 @@ See the [Clerk Migration Guide](/docs/self-hosting/advanced/auth/clerk-to-better
71
71
  LobeHub 2.0 only supports Server DB mode. Client DB (PGlite) is no longer supported. If you were using `NEXT_PUBLIC_SERVICE_MODE=client`, you need to migrate to Server DB deployment.
72
72
 
73
73
  For deployment guides, see [Server Database Deployment](/docs/self-hosting/server-database).
74
+
75
+ ## PostgreSQL Version Requirements
76
+
77
+ LobeHub 2.0 recommends using **PostgreSQL 17** or higher.
78
+
79
+ This is because LobeHub 2.0 uses the [pg\_search](https://github.com/paradedb/paradedb/tree/main/pg_search) extension for full-text search capabilities. If you use Serverless Postgres services like Neon, the pg\_search extension is only available on PostgreSQL 17.
80
+
81
+ If you self-host your database with Docker, we recommend using the `pgvector/pgvector:pg17` image.
@@ -69,3 +69,11 @@ LobeHub 2.0 仅支持 Better Auth 认证系统,不再支持 NextAuth 和 Clerk
69
69
  LobeHub 2.0 仅支持 Server DB 模式,不再支持 Client DB (PGlite)。如果您之前使用 `NEXT_PUBLIC_SERVICE_MODE=client`,需要迁移到 Server DB 部署方式。
70
70
 
71
71
  详细部署指南请参阅[服务端数据库部署](/docs/self-hosting/server-database)。
72
+
73
+ ## PostgreSQL 版本要求
74
+
75
+ LobeHub 2.0 推荐使用 **PostgreSQL 17** 及以上版本。
76
+
77
+ 这是因为 LobeHub 2.0 使用了 [pg\_search](https://github.com/paradedb/paradedb/tree/main/pg_search) 插件来提供全文搜索能力。如果您使用 Neon 等 Serverless Postgres 服务,pg\_search 插件仅在 PostgreSQL 17 上可用。
78
+
79
+ 如果您使用 Docker 自建数据库,推荐使用 `pgvector/pgvector:pg17` 镜像。
@@ -768,7 +768,7 @@ services:
768
768
  - lobe-network
769
769
 
770
770
  postgresql:
771
- image: pgvector/pgvector:pg16
771
+ image: pgvector/pgvector:pg17
772
772
  container_name: lobe-postgres
773
773
  ports:
774
774
  - '5432:5432'
@@ -743,7 +743,7 @@ services:
743
743
  - lobe-network
744
744
 
745
745
  postgresql:
746
- image: pgvector/pgvector:pg16
746
+ image: pgvector/pgvector:pg17
747
747
  container_name: lobe-postgres
748
748
  ports:
749
749
  - '5432:5432'
@@ -28,12 +28,6 @@ tags:
28
28
  Database](/docs/self-hosting/server-database) first.
29
29
  </Callout>
30
30
 
31
- <Callout type="warning">
32
- Due to the inability to expose `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` using Docker environment variables, you cannot use Clerk as an authentication service when deploying LobeHub using Docker / Docker Compose.
33
-
34
- If you do need Clerk as an authentication service, you might consider deploying using Vercel or building your own image.
35
- </Callout>
36
-
37
31
  ## Deploying on a Linux Server
38
32
 
39
33
  Here is the process for deploying the LobeHub server database version on a Linux server:
@@ -46,10 +40,10 @@ Here is the process for deploying the LobeHub server database version on a Linux
46
40
  ```sh
47
41
  docker network create pg
48
42
 
49
- docker run --name my-postgres --network pg -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 -d pgvector/pgvector:pg16
43
+ docker run --name my-postgres --network pg -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 -d pgvector/pgvector:pg17
50
44
  ```
51
45
 
52
- The above command will create a PG instance named `my-postgres` on the network `pg`, where `pgvector/pgvector:pg16` is a Postgres 16 image with the pgvector plugin installed by default.
46
+ The above command will create a PG instance named `my-postgres` on the network `pg`, where `pgvector/pgvector:pg17` is a Postgres 17 image with the pgvector plugin installed by default.
53
47
 
54
48
  <Callout type="info">
55
49
  The pgvector plugin provides vector search capabilities for Postgres, which is an important
@@ -26,13 +26,6 @@ tags:
26
26
  存储服务](/zh/docs/self-hosting/advanced/s3/tencent-cloud)。
27
27
  </Callout>
28
28
 
29
- <Callout type="warning">
30
- 由于无法使用 Docker 环境变量暴露 `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`,使用 Docker / Docker Compose
31
- 部署 LobeHub 时,你不能使用 Clerk 作为登录鉴权服务。
32
-
33
- 如果你确实需要 Clerk 作为登录鉴权服务,你可以考虑使用 Vercel 部署或者自行构建镜像。
34
- </Callout>
35
-
36
29
  ## 在 Linux 服务器上部署
37
30
 
38
31
  以下是在 Linux 服务器上部署 LobeHub DB 版的流程:
@@ -45,10 +38,10 @@ tags:
45
38
  ```sh
46
39
  docker network create pg
47
40
 
48
- docker run --name my-postgres --network pg -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 -d pgvector/pgvector:pg16
41
+ docker run --name my-postgres --network pg -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 -d pgvector/pgvector:pg17
49
42
  ```
50
43
 
51
- 上述指令会创建一个名为 `my-postgres`,并且网络为 `pg` 的 PG 实例,其中 `pgvector/pgvector:pg16` 是一个 Postgres 16 的镜像,且默认安装了 pgvector 插件。
44
+ 上述指令会创建一个名为 `my-postgres`,并且网络为 `pg` 的 PG 实例,其中 `pgvector/pgvector:pg17` 是一个 Postgres 17 的镜像,且默认安装了 pgvector 插件。
52
45
 
53
46
  <Callout type="info">
54
47
  pgvector 插件为 Postgres 提供了向量搜索的能力,是 LobeHub 实现 RAG 的重要构件之一。
@@ -9,11 +9,18 @@
9
9
  "import.importConfigFile.title": "Import Failed",
10
10
  "import.incompatible.description": "This file was exported from a higher version. Please try upgrading to the latest version and then re-importing.",
11
11
  "import.incompatible.title": "Current application does not support importing this file",
12
+ "inviteCode.continue": "Continue",
12
13
  "inviteCode.currentEmail": "Current account: {{email}}",
13
14
  "inviteCode.desc": "An invite code is required to access LobeHub. Please enter a valid invite code to continue.",
14
- "inviteCode.friends": "Friends",
15
- "inviteCode.getCodeHint": "Get an invite code from:",
16
- "inviteCode.title": "Invite Code Required",
15
+ "inviteCode.friends": "Ask a friend",
16
+ "inviteCode.joinUsOn": "Join us on",
17
+ "inviteCode.lookingForInvite": "Looking for an invite?",
18
+ "inviteCode.notYou": "Not you?",
19
+ "inviteCode.openingInStages": "is opening in stages.",
20
+ "inviteCode.placeholder": "Invite code or link",
21
+ "inviteCode.pleaseEnterCode": "Enter your invite code to continue.",
22
+ "inviteCode.switchAccount": "Switch account",
23
+ "inviteCode.title": "You're Almost In",
17
24
  "loginRequired.desc": "You will be redirected to the login page shortly",
18
25
  "loginRequired.title": "Please log in to use this feature",
19
26
  "notFound.backHome": "Back to Home",
@@ -9,11 +9,18 @@
9
9
  "import.importConfigFile.title": "导入遇到了问题",
10
10
  "import.incompatible.description": "该文件由更高版本导出。请升级到最新版后再导入",
11
11
  "import.incompatible.title": "版本不兼容",
12
+ "inviteCode.continue": "继续",
12
13
  "inviteCode.currentEmail": "当前账号:{{email}}",
13
14
  "inviteCode.desc": "需要邀请码才能访问 LobeHub。请输入有效的邀请码以继续。",
14
- "inviteCode.friends": "好友",
15
- "inviteCode.getCodeHint": "获取邀请码:",
16
- "inviteCode.title": "需要邀请码",
15
+ "inviteCode.friends": "问问好友",
16
+ "inviteCode.joinUsOn": "关注我们",
17
+ "inviteCode.lookingForInvite": "需要邀请码?",
18
+ "inviteCode.notYou": "不是你?",
19
+ "inviteCode.openingInStages": "正在分阶段开放中。",
20
+ "inviteCode.placeholder": "输入邀请码或链接",
21
+ "inviteCode.pleaseEnterCode": "请输入邀请码以继续。",
22
+ "inviteCode.switchAccount": "切换账号",
23
+ "inviteCode.title": "即将完成",
17
24
  "loginRequired.desc": "将为你跳转到登录页。登录后即可继续",
18
25
  "loginRequired.title": "需要登录后继续",
19
26
  "notFound.backHome": "返回首页",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.384",
3
+ "version": "2.0.0-next.385",
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",
@@ -19,8 +19,9 @@ export class TopicShareModel {
19
19
  }
20
20
 
21
21
  /**
22
- * Create a new share for a topic.
22
+ * Create or get existing share for a topic.
23
23
  * Each topic can only have one share record (enforced by unique constraint).
24
+ * If record already exists, returns the existing one.
24
25
  */
25
26
  create = async (topicId: string, visibility: ShareVisibility = 'private') => {
26
27
  // First verify the topic belongs to the user
@@ -39,8 +40,14 @@ export class TopicShareModel {
39
40
  userId: this.userId,
40
41
  visibility,
41
42
  })
43
+ .onConflictDoNothing({ target: topicShares.topicId })
42
44
  .returning();
43
45
 
46
+ // If conflict occurred, return existing record
47
+ if (!result) {
48
+ return this.getByTopicId(topicId);
49
+ }
50
+
44
51
  return result;
45
52
  };
46
53
 
@@ -74,6 +81,7 @@ export class TopicShareModel {
74
81
  .select({
75
82
  id: topicShares.id,
76
83
  topicId: topicShares.topicId,
84
+ userId: topicShares.userId,
77
85
  visibility: topicShares.visibility,
78
86
  })
79
87
  .from(topicShares)
@@ -2,7 +2,7 @@ import { type ModelProviderCard } from '@/types/llm';
2
2
 
3
3
  const Anthropic: ModelProviderCard = {
4
4
  chatModels: [],
5
- checkModel: 'claude-3-haiku-20240307',
5
+ checkModel: 'claude-opus-4-5-20251101',
6
6
  description:
7
7
  'Anthropic builds advanced language models like Claude 3.5 Sonnet, Claude 3 Sonnet, Claude 3 Opus, and Claude 3 Haiku, balancing intelligence, speed, and cost for workloads from enterprise to rapid-response use cases.',
8
8
  enabled: true,
@@ -3,7 +3,7 @@ import { type ModelProviderCard } from '@/types/llm';
3
3
  // ref: https://ai.google.dev/gemini-api/docs/models/gemini
4
4
  const Google: ModelProviderCard = {
5
5
  chatModels: [],
6
- checkModel: 'gemini-2.0-flash',
6
+ checkModel: 'gemini-3-flash-preview',
7
7
  description:
8
8
  "Google's Gemini family is its most advanced general-purpose AI, built by Google DeepMind for multimodal use across text, code, images, audio, and video. It scales from data centers to mobile devices with strong efficiency and reach.",
9
9
  enabled: true,
@@ -3,7 +3,7 @@ import { type ModelProviderCard } from '@/types/llm';
3
3
  // ref https://cloud.tencent.com/document/product/1729/104753
4
4
  const Hunyuan: ModelProviderCard = {
5
5
  chatModels: [],
6
- checkModel: 'hunyuan-lite',
6
+ checkModel: 'hunyuan-t1-latest',
7
7
  description:
8
8
  'A Tencent-developed LLM with strong Chinese writing, solid reasoning in complex contexts, and reliable task execution.',
9
9
  disableBrowserRequest: true,
@@ -3,7 +3,7 @@ import { type ModelProviderCard } from '@/types/llm';
3
3
  // ref: https://platform.minimaxi.com/document/Models
4
4
  const Minimax: ModelProviderCard = {
5
5
  chatModels: [],
6
- checkModel: 'MiniMax-M2',
6
+ checkModel: 'MiniMax-M2.1',
7
7
  description:
8
8
  'Founded in 2021, MiniMax builds general-purpose AI with multimodal foundation models, including trillion-parameter MoE text models, speech models, and vision models, along with apps like Hailuo AI.',
9
9
  id: 'minimax',
@@ -4,7 +4,7 @@ import { type ModelProviderCard } from '@/types/llm';
4
4
  const OpenAI: ModelProviderCard = {
5
5
  apiKeyUrl: 'https://platform.openai.com/api-keys?utm_source=lobehub',
6
6
  chatModels: [],
7
- checkModel: 'gpt-5-nano',
7
+ checkModel: 'gpt-5.2',
8
8
  description:
9
9
  'OpenAI is a leading AI research lab whose GPT models advanced natural language processing, delivering high performance and strong value across research, business, and innovation.',
10
10
  enabled: true,
@@ -3,8 +3,9 @@ import { type ModelProviderCard } from '@/types/llm';
3
3
  // ref: https://siliconflow.cn/zh-cn/pricing
4
4
  const SiliconCloud: ModelProviderCard = {
5
5
  chatModels: [],
6
- checkModel: 'Pro/Qwen/Qwen2-7B-Instruct',
7
- description: 'SiliconCloud is a cost-effective GenAI cloud service built on strong open-source base models.',
6
+ checkModel: 'Pro/zai-org/glm-4.7',
7
+ description:
8
+ 'SiliconCloud is a cost-effective GenAI cloud service built on strong open-source base models.',
8
9
  id: 'siliconcloud',
9
10
  modelList: { showModelFetcher: true },
10
11
  modelsUrl: 'https://siliconflow.cn/zh-cn/models',
@@ -3,7 +3,7 @@ import { type ModelProviderCard } from '@/types/llm';
3
3
  // ref: https://ai.google.dev/gemini-api/docs/models/gemini
4
4
  const VertexAI: ModelProviderCard = {
5
5
  chatModels: [],
6
- checkModel: 'gemini-2.0-flash',
6
+ checkModel: 'gemini-3-flash-preview',
7
7
  description:
8
8
  "Google's Gemini family is its most advanced general-purpose AI, built by Google DeepMind for multimodal use across text, code, images, audio, and video. It scales from data centers to mobile devices, improving efficiency and deployment flexibility.",
9
9
  id: 'vertexai',
@@ -3,7 +3,7 @@ import { type ModelProviderCard } from '@/types/llm';
3
3
  // ref https://www.volcengine.com/docs/82379/1330310
4
4
  const Doubao: ModelProviderCard = {
5
5
  chatModels: [],
6
- checkModel: 'doubao-seed-1-6-flash-250828',
6
+ checkModel: 'doubao-seed-1.8',
7
7
  description:
8
8
  'ByteDance’s model service platform offers secure, feature-rich, cost-competitive model access plus end-to-end tooling for data, fine-tuning, inference, and evaluation.',
9
9
  id: 'volcengine',
@@ -3,7 +3,7 @@ import { type ModelProviderCard } from '@/types/llm';
3
3
  // ref: https://x.ai/about
4
4
  const XAI: ModelProviderCard = {
5
5
  chatModels: [],
6
- checkModel: 'grok-2-1212',
6
+ checkModel: 'grok-4-1-fast-non-reasoning',
7
7
  description:
8
8
  'xAI builds AI to accelerate scientific discovery, with a mission to deepen humanity’s understanding of the universe.',
9
9
  id: 'xai',
@@ -90,6 +90,20 @@ export interface ChatStreamPayload {
90
90
  * @title List of chat messages
91
91
  */
92
92
  messages: OpenAIChatMessage[];
93
+ /**
94
+ * @title Custom text chunks for mock response
95
+ */
96
+ mockChunks?: string[];
97
+ /**
98
+ * @title Delay in milliseconds between mock chunks
99
+ * @default 50
100
+ */
101
+ mockDelayMs?: number;
102
+ /**
103
+ * @title Enable mock response for benchmark testing
104
+ * @description When true, returns a simulated SSE stream without calling real LLM API
105
+ */
106
+ mockResponse?: boolean;
93
107
  /**
94
108
  * @title Model name
95
109
  */
@@ -1,3 +1,5 @@
1
+ import { BRANDING_PROVIDER } from '@lobechat/business-const';
2
+ import { CREDITS_PER_DOLLAR } from '@lobechat/const/currency';
1
3
  import { ModelIcon } from '@lobehub/icons';
2
4
  import { Flexbox, Popover, Text } from '@lobehub/ui';
3
5
  import { createStaticStyles, cx } from 'antd-style';
@@ -7,6 +9,8 @@ import { memo, useMemo } from 'react';
7
9
 
8
10
  import NewModelBadge from '@/components/ModelSelect/NewModelBadge';
9
11
  import { useIsDark } from '@/hooks/useIsDark';
12
+ import { useServerConfigStore } from '@/store/serverConfig';
13
+ import { serverConfigSelectors } from '@/store/serverConfig/selectors';
10
14
 
11
15
  const POPOVER_MAX_WIDTH = 320;
12
16
 
@@ -38,6 +42,10 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
38
42
  }));
39
43
 
40
44
  type ImageModelItemProps = AiModelForSelect & {
45
+ /**
46
+ * Provider ID for determining price display format
47
+ */
48
+ providerId?: string;
41
49
  /**
42
50
  * Whether to show new model badge
43
51
  * @default true
@@ -55,25 +63,39 @@ const ImageModelItem = memo<ImageModelItemProps>(
55
63
  approximatePricePerImage,
56
64
  description,
57
65
  pricePerImage,
66
+ providerId,
58
67
  showPopover = true,
59
68
  showBadge = true,
60
69
  ...model
61
70
  }) => {
62
71
  const isDarkMode = useIsDark();
72
+ const enableBusinessFeatures = useServerConfigStore(
73
+ serverConfigSelectors.enableBusinessFeatures,
74
+ );
63
75
 
64
76
  const priceLabel = useMemo(() => {
65
- // Priority 1: Use exact price
66
- if (typeof pricePerImage === 'number') {
67
- return `${numeral(pricePerImage).format('$0,0.00[000]')} / image`;
68
- }
69
-
70
- // Priority 2: Use approximate price with prefix
71
- if (typeof approximatePricePerImage === 'number') {
72
- return `~ ${numeral(approximatePricePerImage).format('$0,0.00[000]')} / image`;
77
+ // Show credits only for branding provider with business features enabled
78
+ if (enableBusinessFeatures && providerId === BRANDING_PROVIDER) {
79
+ if (typeof pricePerImage === 'number') {
80
+ const credits = pricePerImage * CREDITS_PER_DOLLAR;
81
+ return `${numeral(credits).format('0,0')} credits/张`;
82
+ }
83
+ if (typeof approximatePricePerImage === 'number') {
84
+ const credits = approximatePricePerImage * CREDITS_PER_DOLLAR;
85
+ return `~ ${numeral(credits).format('0,0')} credits/张`;
86
+ }
87
+ } else {
88
+ // Show USD price for open source version or non-branding providers
89
+ if (typeof pricePerImage === 'number') {
90
+ return `${numeral(pricePerImage).format('$0,0.00[000]')} / image`;
91
+ }
92
+ if (typeof approximatePricePerImage === 'number') {
93
+ return `~ ${numeral(approximatePricePerImage).format('$0,0.00[000]')} / image`;
94
+ }
73
95
  }
74
96
 
75
97
  return undefined;
76
- }, [approximatePricePerImage, pricePerImage]);
98
+ }, [approximatePricePerImage, enableBusinessFeatures, pricePerImage, providerId]);
77
99
 
78
100
  const popoverContent = useMemo(() => {
79
101
  if (!description && !priceLabel) return null;
@@ -46,7 +46,7 @@ const ModelSelect = memo(() => {
46
46
  const options = useMemo<SelectProps['options']>(() => {
47
47
  const getImageModels = (provider: EnabledProviderWithModels) => {
48
48
  const modelOptions = provider.children.map((model) => ({
49
- label: <ImageModelItem {...model} />,
49
+ label: <ImageModelItem {...model} providerId={provider.id} />,
50
50
  provider: provider.id,
51
51
  value: `${provider.id}/${model.id}`,
52
52
  }));
@@ -130,7 +130,14 @@ const ModelSelect = memo(() => {
130
130
 
131
131
  if (!modelInfo) return props.label;
132
132
 
133
- return <ImageModelItem {...modelInfo} showBadge={false} showPopover={false} />;
133
+ return (
134
+ <ImageModelItem
135
+ {...modelInfo}
136
+ providerId={modelInfo.providerId}
137
+ showBadge={false}
138
+ showPopover={false}
139
+ />
140
+ );
134
141
  };
135
142
 
136
143
  return (
@@ -6,12 +6,12 @@ import { ModelIcon } from '@lobehub/icons';
6
6
  import { Alert, Button, Flexbox, Highlighter, Icon, LobeSelect as Select } from '@lobehub/ui';
7
7
  import { cssVar } from 'antd-style';
8
8
  import { Loader2Icon } from 'lucide-react';
9
- import { type ReactNode, memo, useEffect, useState } from 'react';
9
+ import { type ReactNode, memo, useEffect, useMemo, useState } from 'react';
10
10
  import { useTranslation } from 'react-i18next';
11
11
 
12
12
  import { useProviderName } from '@/hooks/useProviderName';
13
13
  import { chatService } from '@/services/chat';
14
- import { aiModelSelectors, aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
14
+ import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
15
15
 
16
16
  const Error = memo<{ error: ChatMessageError }>(({ error }) => {
17
17
  const { t } = useTranslation('error');
@@ -62,7 +62,36 @@ const Checker = memo<ConnectionCheckerProps>(
62
62
  aiProviderSelectors.isProviderConfigUpdating(provider)(s),
63
63
  s.updateAiProviderConfig,
64
64
  ]);
65
- const totalModels = useAiInfraStore(aiModelSelectors.aiProviderChatModelListIds);
65
+ const aiProviderModelList = useAiInfraStore((s) => s.aiProviderModelList);
66
+
67
+ // Sort models for better UX:
68
+ // 1. checkModel first (provider's recommended test model)
69
+ // 2. enabled models (user is actively using)
70
+ // 3. by releasedAt descending (newer models first)
71
+ // 4. models without releasedAt last
72
+ const sortedModels = useMemo(() => {
73
+ const chatModels = aiProviderModelList.filter((m) => m.type === 'chat');
74
+
75
+ const sorted = [...chatModels].sort((a, b) => {
76
+ // checkModel always first
77
+ if (a.id === model) return -1;
78
+ if (b.id === model) return 1;
79
+
80
+ // enabled models come before disabled
81
+ if (a.enabled !== b.enabled) return a.enabled ? -1 : 1;
82
+
83
+ // sort by releasedAt descending, models without releasedAt go last
84
+ if (a.releasedAt && b.releasedAt) {
85
+ return new Date(b.releasedAt).getTime() - new Date(a.releasedAt).getTime();
86
+ }
87
+ if (a.releasedAt && !b.releasedAt) return -1;
88
+ if (!a.releasedAt && b.releasedAt) return 1;
89
+
90
+ return 0;
91
+ });
92
+
93
+ return sorted.map((m) => m.id);
94
+ }, [aiProviderModelList, model]);
66
95
 
67
96
  const [loading, setLoading] = useState(false);
68
97
  const [pass, setPass] = useState(false);
@@ -154,7 +183,7 @@ const Checker = memo<ConnectionCheckerProps>(
154
183
  </Flexbox>
155
184
  );
156
185
  }}
157
- options={totalModels.map((id) => ({ label: id, value: id }))}
186
+ options={sortedModels.map((id) => ({ label: id, value: id }))}
158
187
  style={{
159
188
  flex: 1,
160
189
  overflow: 'hidden',
@@ -3,6 +3,9 @@ import { type ModelPerformance, type ModelUsage } from '@lobechat/types';
3
3
  import { Flexbox } from '@lobehub/ui';
4
4
  import { memo } from 'react';
5
5
 
6
+ import { useUserStore } from '@/store/user';
7
+ import { authSelectors } from '@/store/user/slices/auth/selectors';
8
+
6
9
  import { messageStateSelectors, useConversationStore } from '../../../store';
7
10
  import ExtraContainer from '../../components/Extras/ExtraContainer';
8
11
  import TTS from '../../components/Extras/TTS';
@@ -23,24 +26,27 @@ interface AssistantMessageExtraProps {
23
26
  export const AssistantMessageExtra = memo<AssistantMessageExtraProps>(
24
27
  ({ extra, id, content, performance, usage, tools, provider, model }) => {
25
28
  const loading = useConversationStore(messageStateSelectors.isMessageGenerating(id));
29
+ const isLogin = useUserStore(authSelectors.isLogin);
26
30
 
27
31
  return (
28
32
  <Flexbox gap={8} style={{ marginTop: !!tools?.length ? 8 : 4 }}>
29
33
  {content !== LOADING_FLAT && model && (
30
34
  <Usage model={model} performance={performance} provider={provider!} usage={usage} />
31
35
  )}
32
- <>
33
- {!!extra?.tts && (
34
- <ExtraContainer>
35
- <TTS content={content} id={id} loading={loading} {...extra?.tts} />
36
- </ExtraContainer>
37
- )}
38
- {!!extra?.translate && (
39
- <ExtraContainer>
40
- <Translate id={id} loading={loading} {...extra?.translate} />
41
- </ExtraContainer>
42
- )}
43
- </>
36
+ {isLogin && (
37
+ <>
38
+ {!!extra?.tts && (
39
+ <ExtraContainer>
40
+ <TTS content={content} id={id} loading={loading} {...extra?.tts} />
41
+ </ExtraContainer>
42
+ )}
43
+ {!!extra?.translate && (
44
+ <ExtraContainer>
45
+ <Translate id={id} loading={loading} {...extra?.translate} />
46
+ </ExtraContainer>
47
+ )}
48
+ </>
49
+ )}
44
50
  </Flexbox>
45
51
  );
46
52
  },
@@ -1,6 +1,9 @@
1
1
  import { Flexbox } from '@lobehub/ui';
2
2
  import { memo } from 'react';
3
3
 
4
+ import { useUserStore } from '@/store/user';
5
+ import { authSelectors } from '@/store/user/slices/auth/selectors';
6
+
4
7
  import { messageStateSelectors, useConversationStore } from '../../store';
5
8
  import ExtraContainer from '../components/Extras/ExtraContainer';
6
9
  import TTS from '../components/Extras/TTS';
@@ -14,11 +17,12 @@ interface UserMessageExtraProps {
14
17
 
15
18
  export const UserMessageExtra = memo<UserMessageExtraProps>(({ extra, id, content }) => {
16
19
  const loading = useConversationStore(messageStateSelectors.isMessageGenerating(id));
20
+ const isLogin = useUserStore(authSelectors.isLogin);
17
21
 
18
22
  const showTranslate = !!extra?.translate;
19
23
  const showTTS = !!extra?.tts;
20
24
 
21
- const showExtra = showTranslate || showTTS;
25
+ const showExtra = isLogin && (showTranslate || showTTS);
22
26
 
23
27
  if (!showExtra) return;
24
28
 
@@ -8,7 +8,7 @@ import {
8
8
  } from '@lobechat/const';
9
9
  import { Flexbox, Icon, Image, Tag, Text, Typography, useModalContext } from '@lobehub/ui';
10
10
  import { Button, Divider } from 'antd';
11
- import { createStyles, cssVar } from 'antd-style';
11
+ import { createStaticStyles, cssVar } from 'antd-style';
12
12
  import type { Klavis } from 'klavis';
13
13
  import { ExternalLink, Loader2, SquareArrowOutUpRight } from 'lucide-react';
14
14
  import { useEffect, useMemo, useRef } from 'react';
@@ -20,7 +20,7 @@ import { klavisStoreSelectors, lobehubSkillStoreSelectors } from '@/store/tool/s
20
20
  import { KlavisServerStatus } from '@/store/tool/slices/klavisStore';
21
21
  import { LobehubSkillStatus } from '@/store/tool/slices/lobehubSkillStore/types';
22
22
 
23
- const useStyles = createStyles(({ css, token }) => ({
23
+ const styles = createStaticStyles(({ css, cssVar }) => ({
24
24
  authorLink: css`
25
25
  cursor: pointer;
26
26
 
@@ -28,7 +28,7 @@ const useStyles = createStyles(({ css, token }) => ({
28
28
  gap: 4px;
29
29
  align-items: center;
30
30
 
31
- color: ${token.colorPrimary};
31
+ color: ${cssVar.colorPrimary};
32
32
 
33
33
  &:hover {
34
34
  text-decoration: underline;
@@ -41,7 +41,7 @@ const useStyles = createStyles(({ css, token }) => ({
41
41
  `,
42
42
  detailLabel: css`
43
43
  font-size: 12px;
44
- color: ${token.colorTextTertiary};
44
+ color: ${cssVar.colorTextTertiary};
45
45
  `,
46
46
  header: css`
47
47
  display: flex;
@@ -51,7 +51,7 @@ const useStyles = createStyles(({ css, token }) => ({
51
51
  padding: 16px;
52
52
  border-radius: 12px;
53
53
 
54
- background: ${token.colorFillTertiary};
54
+ background: ${cssVar.colorFillTertiary};
55
55
  `,
56
56
  icon: css`
57
57
  display: flex;
@@ -63,25 +63,25 @@ const useStyles = createStyles(({ css, token }) => ({
63
63
  height: 56px;
64
64
  border-radius: 12px;
65
65
 
66
- background: ${token.colorBgContainer};
66
+ background: ${cssVar.colorBgContainer};
67
67
  `,
68
68
  introduction: css`
69
69
  font-size: 14px;
70
70
  line-height: 1.8;
71
- color: ${token.colorText};
71
+ color: ${cssVar.colorText};
72
72
  `,
73
73
  sectionTitle: css`
74
74
  font-size: 14px;
75
75
  font-weight: 600;
76
- color: ${token.colorText};
76
+ color: ${cssVar.colorText};
77
77
  `,
78
78
  title: css`
79
79
  font-size: 18px;
80
80
  font-weight: 600;
81
- color: ${token.colorText};
81
+ color: ${cssVar.colorText};
82
82
  `,
83
83
  toolTag: css`
84
- font-family: ${token.fontFamilyCode};
84
+ font-family: ${cssVar.fontFamilyCode};
85
85
  font-size: 12px;
86
86
  `,
87
87
  toolsContainer: css`
@@ -92,7 +92,7 @@ const useStyles = createStyles(({ css, token }) => ({
92
92
  trustWarning: css`
93
93
  font-size: 12px;
94
94
  line-height: 1.6;
95
- color: ${token.colorTextTertiary};
95
+ color: ${cssVar.colorTextTertiary};
96
96
  `,
97
97
  }));
98
98
 
@@ -109,7 +109,6 @@ export const IntegrationDetailContent = ({
109
109
  identifier,
110
110
  serverName,
111
111
  }: IntegrationDetailContentProps) => {
112
- const { styles } = useStyles();
113
112
  const { t } = useTranslation(['plugin', 'setting']);
114
113
  const { close } = useModalContext();
115
114
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {
4
4
  Button,
5
+ Checkbox,
5
6
  Flexbox,
6
7
  LobeSelect,
7
8
  Popover,
@@ -20,6 +21,8 @@ import { useAppOrigin } from '@/hooks/useAppOrigin';
20
21
  import { useIsMobile } from '@/hooks/useIsMobile';
21
22
  import { topicService } from '@/services/topic';
22
23
  import { useChatStore } from '@/store/chat';
24
+ import { useGlobalStore } from '@/store/global';
25
+ import { systemStatusSelectors } from '@/store/global/selectors';
23
26
 
24
27
  import { styles } from './style';
25
28
 
@@ -38,6 +41,10 @@ const SharePopoverContent = memo<SharePopoverContentProps>(({ onOpenModal }) =>
38
41
  const appOrigin = useAppOrigin();
39
42
 
40
43
  const activeTopicId = useChatStore((s) => s.activeTopicId);
44
+ const [hideTopicSharePrivacyWarning, updateSystemStatus] = useGlobalStore((s) => [
45
+ systemStatusSelectors.systemStatus(s).hideTopicSharePrivacyWarning ?? false,
46
+ s.updateSystemStatus,
47
+ ]);
41
48
 
42
49
  const {
43
50
  data: shareInfo,
@@ -79,13 +86,38 @@ const SharePopoverContent = memo<SharePopoverContentProps>(({ onOpenModal }) =>
79
86
 
80
87
  const handleVisibilityChange = useCallback(
81
88
  (visibility: Visibility) => {
82
- // Show confirmation when changing from private to link
83
- if (currentVisibility === 'private' && visibility === 'link') {
89
+ // Show confirmation when changing from private to link (unless user has dismissed it)
90
+ if (
91
+ currentVisibility === 'private' &&
92
+ visibility === 'link' &&
93
+ !hideTopicSharePrivacyWarning
94
+ ) {
95
+ let doNotShowAgain = false;
96
+
84
97
  modal.confirm({
85
98
  cancelText: t('cancel', { ns: 'common' }),
86
- content: t('shareModal.popover.privacyWarning.content'),
99
+ centered: true,
100
+ content: (
101
+ <div>
102
+ <p>{t('shareModal.popover.privacyWarning.content')}</p>
103
+ <div style={{ marginTop: 16 }}>
104
+ <Checkbox
105
+ onChange={(v) => {
106
+ doNotShowAgain = v;
107
+ }}
108
+ >
109
+ {t('shareModal.popover.privacyWarning.doNotShowAgain')}
110
+ </Checkbox>
111
+ </div>
112
+ </div>
113
+ ),
87
114
  okText: t('shareModal.popover.privacyWarning.confirm'),
88
- onOk: () => updateVisibility(visibility),
115
+ onOk: () => {
116
+ if (doNotShowAgain) {
117
+ updateSystemStatus({ hideTopicSharePrivacyWarning: true });
118
+ }
119
+ updateVisibility(visibility);
120
+ },
89
121
  title: t('shareModal.popover.privacyWarning.title'),
90
122
  type: 'warning',
91
123
  });
@@ -93,7 +125,14 @@ const SharePopoverContent = memo<SharePopoverContentProps>(({ onOpenModal }) =>
93
125
  updateVisibility(visibility);
94
126
  }
95
127
  },
96
- [currentVisibility, modal, t, updateVisibility],
128
+ [
129
+ currentVisibility,
130
+ hideTopicSharePrivacyWarning,
131
+ modal,
132
+ t,
133
+ updateSystemStatus,
134
+ updateVisibility,
135
+ ],
97
136
  );
98
137
 
99
138
  const handleCopyLink = useCallback(async () => {
@@ -70,7 +70,7 @@ export const useDesktopUserStateRedirect = () => {
70
70
 
71
71
  export const useWebUserStateRedirect = () =>
72
72
  useCallback((state: UserInitializationState) => {
73
- const pathname = window.location.pathname;
73
+ const { pathname } = window.location;
74
74
  if (state.isInWaitList === true) {
75
75
  redirectIfNotOn(pathname, '/waitlist');
76
76
  return;
@@ -81,6 +81,12 @@ export const useWebUserStateRedirect = () =>
81
81
  return;
82
82
  }
83
83
 
84
+ // Redirect away from invite-code page if no longer required
85
+ if (pathname.startsWith('/invite-code')) {
86
+ window.location.href = '/';
87
+ return;
88
+ }
89
+
84
90
  if (!onboardingSelectors.needsOnboarding(state)) return;
85
91
 
86
92
  redirectIfNotOn(pathname, '/onboarding');
@@ -326,6 +326,7 @@ export default {
326
326
  'shareModal.popover.privacyWarning.confirm': 'I understand, continue',
327
327
  'shareModal.popover.privacyWarning.content':
328
328
  "Please make sure your conversation doesn't contain any personal or sensitive information. You are responsible for any content you choose to share and its consequences.",
329
+ 'shareModal.popover.privacyWarning.doNotShowAgain': "Don't show this again",
329
330
  'shareModal.popover.privacyWarning.title': 'Privacy Notice',
330
331
  'shareModal.popover.title': 'Share Topic',
331
332
  'shareModal.popover.visibility': 'Visibility',
@@ -12,12 +12,19 @@ export default {
12
12
  'import.incompatible.description':
13
13
  'This file was exported from a higher version. Please try upgrading to the latest version and then re-importing.',
14
14
  'import.incompatible.title': 'Current application does not support importing this file',
15
+ 'inviteCode.continue': 'Continue',
15
16
  'inviteCode.currentEmail': 'Current account: {{email}}',
16
17
  'inviteCode.desc':
17
18
  'An invite code is required to access LobeHub. Please enter a valid invite code to continue.',
18
- 'inviteCode.friends': 'Friends',
19
- 'inviteCode.getCodeHint': 'Get an invite code from:',
20
- 'inviteCode.title': 'Invite Code Required',
19
+ 'inviteCode.friends': 'Ask a friend',
20
+ 'inviteCode.joinUsOn': 'Join us on',
21
+ 'inviteCode.lookingForInvite': 'Looking for an invite?',
22
+ 'inviteCode.notYou': 'Not you?',
23
+ 'inviteCode.openingInStages': 'is opening in stages.',
24
+ 'inviteCode.placeholder': 'Invite code or link',
25
+ 'inviteCode.pleaseEnterCode': 'Enter your invite code to continue.',
26
+ 'inviteCode.switchAccount': 'Switch account',
27
+ 'inviteCode.title': "You're Almost In",
21
28
  'loginRequired.desc': 'You will be redirected to the login page shortly',
22
29
  'loginRequired.title': 'Please log in to use this feature',
23
30
  'notFound.backHome': 'Back to Home',
@@ -101,6 +101,7 @@ export interface SystemStatus {
101
101
  hideGemini2_5FlashImagePreviewChineseWarning?: boolean;
102
102
  hidePWAInstaller?: boolean;
103
103
  hideThreadLimitAlert?: boolean;
104
+ hideTopicSharePrivacyWarning?: boolean;
104
105
  imagePanelWidth: number;
105
106
  imageTopicPanelWidth?: number;
106
107
  /**
@@ -212,6 +213,7 @@ export const INITIAL_STATUS = {
212
213
  hideGemini2_5FlashImagePreviewChineseWarning: false,
213
214
  hidePWAInstaller: false,
214
215
  hideThreadLimitAlert: false,
216
+ hideTopicSharePrivacyWarning: false,
215
217
  imagePanelWidth: 320,
216
218
  imageTopicPanelWidth: 80,
217
219
  knowledgeBaseModalViewMode: 'list' as const,